From e8d3880269dfcc8adbfa4940afde77ebad7d794c Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Thu, 25 Jul 2019 01:27:59 -0500 Subject: [PATCH 001/315] Fix mightProducePrintableCharacter numeric keypad support. Fixes #71134 --- .../keybinding/browser/keybindingService.ts | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 4e64ebdc0c621..ed4bde0a9967d 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -46,7 +46,7 @@ import { IKeymapService } from 'vs/workbench/services/keybinding/common/keymapIn import { getDispatchConfig } from 'vs/workbench/services/keybinding/common/dispatchConfig'; import { isArray } from 'vs/base/common/types'; import { INavigatorWithKeyboard } from 'vs/workbench/services/keybinding/common/navigatorKeyboard'; -import { ScanCodeUtils, IMMUTABLE_CODE_TO_KEY_CODE } from 'vs/base/common/scanCode'; +import { ScanCode, ScanCodeUtils, IMMUTABLE_CODE_TO_KEY_CODE, IMMUTABLE_KEY_CODE_TO_CODE } from 'vs/base/common/scanCode'; interface ContributedKeyBinding { command: string; @@ -535,6 +535,30 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { return false; } const code = ScanCodeUtils.toEnum(event.code); + + const NUMPAD_PRINTABLE_SCANCODES = [ + ScanCode.NumpadDivide, + ScanCode.NumpadMultiply, + ScanCode.NumpadSubtract, + ScanCode.NumpadAdd, + ScanCode.Numpad1, + ScanCode.Numpad2, + ScanCode.Numpad3, + ScanCode.Numpad4, + ScanCode.Numpad5, + ScanCode.Numpad6, + ScanCode.Numpad7, + ScanCode.Numpad8, + ScanCode.Numpad9, + ScanCode.Numpad0, + ScanCode.NumpadDecimal + ]; + const immutableScanCode = IMMUTABLE_KEY_CODE_TO_CODE[event.keyCode]; + if (NUMPAD_PRINTABLE_SCANCODES.indexOf(code) >= 0 && code === immutableScanCode) { + // code === immutableScanCode when NumLock is enabled + return true; + } + const keycode = IMMUTABLE_CODE_TO_KEY_CODE[code]; if (keycode !== -1) { // https://github.com/microsoft/vscode/issues/74934 From e0cb4e2542f28bacb25bb5c0f79fb1f921b41d84 Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Sat, 26 Oct 2019 18:48:25 -0500 Subject: [PATCH 002/315] :lipstick: --- .../keybinding/browser/keybindingService.ts | 82 ++++++++++--------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 6b789b49a327f..91ed7d94a2107 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -141,6 +141,24 @@ const keybindingsExtPoint = ExtensionsRegistry.registerExtensionPoint= 0 && code === immutableScanCode) { - // code === immutableScanCode when NumLock is enabled - return true; + if (NUMPAD_PRINTABLE_SCANCODES.indexOf(code) === -1) { + const keycode = IMMUTABLE_CODE_TO_KEY_CODE[code]; + if (keycode !== -1) { + // https://github.com/microsoft/vscode/issues/74934 + return false; + } + // consult the KeyboardMapperFactory to check the given event for + // a printable value. + const mapping = this.keymapService.getRawKeyboardMapping(); + if (!mapping) { + return false; + } + const keyInfo = mapping[event.code]; + if (!keyInfo) { + return false; + } + if (!keyInfo.value || /\s/.test(keyInfo.value)) { + return false; + } + } else { + const immutableScanCode = IMMUTABLE_KEY_CODE_TO_CODE[event.keyCode]; + if (code !== immutableScanCode) { + // NumLock is disabled + return false; + } } - const keycode = IMMUTABLE_CODE_TO_KEY_CODE[code]; - if (keycode !== -1) { - // https://github.com/microsoft/vscode/issues/74934 - return false; - } - // consult the KeyboardMapperFactory to check the given event for - // a printable value. - const mapping = this.keymapService.getRawKeyboardMapping(); - if (!mapping) { - return false; - } - const keyInfo = mapping[event.code]; - if (!keyInfo) { - return false; - } - if (!keyInfo.value || /\s/.test(keyInfo.value)) { - return false; - } return true; } } From 2e93b5726d5727be9c7db979f7325a702839bd62 Mon Sep 17 00:00:00 2001 From: Sameer <11097096+sameer@users.noreply.github.com> Date: Sat, 17 Nov 2018 22:24:29 -0600 Subject: [PATCH 003/315] Enable Shift-Insert to paste primary clipboard on Linux, fixes #36170. --- .../electron-browser/selectionClipboard.ts | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts index 65b603080a412..c6898134ef756 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts @@ -11,7 +11,7 @@ import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import { ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { Range } from 'vs/editor/common/core/range'; -import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { IEditorContribution, Handler } from 'vs/editor/common/editorCommon'; import { EndOfLinePreference } from 'vs/editor/common/model'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; @@ -19,6 +19,8 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; export class SelectionClipboard extends Disposable implements IEditorContribution { private static readonly SELECTION_LENGTH_LIMIT = 65536; @@ -79,6 +81,30 @@ export class SelectionClipboard extends Disposable implements IEditorContributio } setSelectionToClipboard.schedule(); })); + + this._register(editor.onKeyDown((e: IKeyboardEvent) => { + if (!isEnabled) { + return; + } + + if (!editor.hasModel()) { + return; + } + + if (e.equals(KeyMod.Shift | KeyCode.Insert)) { + // prevent paste from 'clipboard' clipboard + e.preventDefault(); + + // trigger paste from 'primary' clipboard + clipboardService.readText('selection').then(text => { + editor.focus(); + editor.trigger('keyboard', Handler.Paste, { + text: text, + pasteOnNewLine: false + }); + }); + } + })); } } From 34c591eeb39986a5aca0fdbae0a21df9289d9771 Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Mon, 9 Dec 2019 09:37:52 -0800 Subject: [PATCH 004/315] Don't instrument any test code for coverage --- test/coverage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/coverage.js b/test/coverage.js index bf7e7aa3f957e..2c4c12ffaafd8 100644 --- a/test/coverage.js +++ b/test/coverage.js @@ -17,7 +17,7 @@ const REPO_PATH = toUpperDriveLetter(path.join(__dirname, '..')); exports.initialize = function (loaderConfig) { const instrumenter = iLibInstrument.createInstrumenter(); loaderConfig.nodeInstrumenter = function (contents, source) { - if (minimatch(source, '**/test/**/*.test.js')) { + if (minimatch(source, '**/test/**')) { // tests don't get instrumented return contents; } From fb2eca62c21d9112126835acf0c1b868f45019b2 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 11 Dec 2019 15:39:45 -0800 Subject: [PATCH 005/315] Show empty image instead of error in git diff view for newly staged images Fixes #86389 Fixes #86776 Uses `fs.stat` to check if we are showing an untracked image in the git diff view. Also required fixing `stat` for `gitfs` so that it can try to return the proper sizes for objects --- extensions/git/src/fileSystemProvider.ts | 40 +++++++++++++++--------- extensions/image-preview/src/preview.ts | 37 +++++++++++----------- 2 files changed, 43 insertions(+), 34 deletions(-) diff --git a/extensions/git/src/fileSystemProvider.ts b/extensions/git/src/fileSystemProvider.ts index 198f031309f52..9687dbf59de23 100644 --- a/extensions/git/src/fileSystemProvider.ts +++ b/extensions/git/src/fileSystemProvider.ts @@ -8,6 +8,7 @@ import { debounce, throttle } from './decorators'; import { fromGitUri, toGitUri } from './uri'; import { Model, ModelChangeEvent, OriginalResourceChangeEvent } from './model'; import { filterEvent, eventToPromise, isDescendant, pathEquals, EmptyDisposable } from './util'; +import { Repository } from './repository'; interface CacheRow { uri: Uri; @@ -116,15 +117,21 @@ export class GitFileSystemProvider implements FileSystemProvider { return EmptyDisposable; } - stat(uri: Uri): FileStat { - const { submoduleOf } = fromGitUri(uri); + async stat(uri: Uri): Promise { + const { submoduleOf, path, ref } = fromGitUri(uri); const repository = submoduleOf ? this.model.getRepository(submoduleOf) : this.model.getRepository(uri); - if (!repository) { throw FileSystemError.FileNotFound(); } - return { type: FileType.File, size: 0, mtime: this.mtime, ctime: 0 }; + let size = 0; + try { + const details = await repository.getObjectDetails(this.fixRef(ref, path, repository), path); + size = details.size; + } catch { + // noop + } + return { type: FileType.File, size: size, mtime: this.mtime, ctime: 0 }; } readDirectory(): Thenable<[string, FileType][]> { @@ -136,7 +143,7 @@ export class GitFileSystemProvider implements FileSystemProvider { } async readFile(uri: Uri): Promise { - let { path, ref, submoduleOf } = fromGitUri(uri); + const { path, ref, submoduleOf } = fromGitUri(uri); if (submoduleOf) { const repository = this.model.getRepository(submoduleOf); @@ -165,17 +172,8 @@ export class GitFileSystemProvider implements FileSystemProvider { this.cache.set(uri.toString(), cacheValue); - if (ref === '~') { - const fileUri = Uri.file(path); - const uriString = fileUri.toString(); - const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString); - ref = indexStatus ? '' : 'HEAD'; - } else if (/^~\d$/.test(ref)) { - ref = `:${ref[1]}`; - } - try { - return await repository.buffer(ref, path); + return await repository.buffer(this.fixRef(ref, path, repository), path); } catch (err) { return new Uint8Array(0); } @@ -196,4 +194,16 @@ export class GitFileSystemProvider implements FileSystemProvider { dispose(): void { this.disposables.forEach(d => d.dispose()); } + + private fixRef(ref: string, path: string, repository: Repository): string { + if (ref === '~') { + const fileUri = Uri.file(path); + const uriString = fileUri.toString(); + const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString); + return indexStatus ? '' : 'HEAD'; + } else if (/^~\d$/.test(ref)) { + return `:${ref[1]}`; + } + return ref; + } } diff --git a/extensions/image-preview/src/preview.ts b/extensions/image-preview/src/preview.ts index 83fcaafaaaf48..9ebbae8a74404 100644 --- a/extensions/image-preview/src/preview.ts +++ b/extensions/image-preview/src/preview.ts @@ -75,6 +75,8 @@ class Preview extends Disposable { private _imageBinarySize: number | undefined; private _imageZoom: Scale | undefined; + private readonly emptyPngDataUri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAEElEQVR42gEFAPr/AP///wAI/AL+Sr4t6gAAAABJRU5ErkJggg=='; + constructor( private readonly extensionRoot: vscode.Uri, private readonly resource: vscode.Uri, @@ -167,9 +169,9 @@ class Preview extends Disposable { } } - private render() { + private async render() { if (this._previewState !== PreviewState.Disposed) { - this.webviewEditor.webview.html = this.getWebiewContents(); + this.webviewEditor.webview.html = await this.getWebiewContents(); } } @@ -193,11 +195,11 @@ class Preview extends Disposable { } } - private getWebiewContents(): string { + private async getWebiewContents(): Promise { const version = Date.now().toString(); const settings = { isMac: process.platform === 'darwin', - src: this.getResourcePath(this.webviewEditor, this.resource, version), + src: await this.getResourcePath(this.webviewEditor, this.resource, version), }; const nonce = Date.now().toString(); @@ -226,22 +228,19 @@ class Preview extends Disposable { `; } - private getResourcePath(webviewEditor: vscode.WebviewPanel, resource: vscode.Uri, version: string) { - switch (resource.scheme) { - case 'data': - return resource.toString(true); - - case 'git': - // Show blank image - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAEElEQVR42gEFAPr/AP///wAI/AL+Sr4t6gAAAABJRU5ErkJggg=='; - - default: - // Avoid adding cache busting if there is already a query string - if (resource.query) { - return webviewEditor.webview.asWebviewUri(resource).toString(true); - } - return webviewEditor.webview.asWebviewUri(resource).with({ query: `version=${version}` }).toString(true); + private async getResourcePath(webviewEditor: vscode.WebviewPanel, resource: vscode.Uri, version: string): Promise { + if (resource.scheme === 'gitfs') { + const stat = await vscode.workspace.fs.stat(resource); + if (stat.size === 0) { + return this.emptyPngDataUri; + } + } + + // Avoid adding cache busting if there is already a query string + if (resource.query) { + return webviewEditor.webview.asWebviewUri(resource).toString(true); } + return webviewEditor.webview.asWebviewUri(resource).with({ query: `version=${version}` }).toString(true); } private extensionResource(path: string) { From a5be98587f1b3cb5241fc52695b77cb9afebce79 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 17 Dec 2019 18:49:38 +0100 Subject: [PATCH 006/315] implement tree things --- .../contrib/bulkEdit/browser/bulkEditTree.ts | 169 ++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts new file mode 100644 index 0000000000000..03c015bc851aa --- /dev/null +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -0,0 +1,169 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IAsyncDataSource, ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree'; +import * as modes from 'vs/editor/common/modes'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { FuzzyScore, createMatches } from 'vs/base/common/filters'; +import { IResourceLabel, ResourceLabels, DEFAULT_LABELS_CONTAINER } from 'vs/workbench/browser/labels'; +import { URI } from 'vs/base/common/uri'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; +import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { Range } from 'vs/editor/common/core/range'; +import * as dom from 'vs/base/browser/dom'; + +// --- VIEW MODEL + +export class FileElement { + + constructor(readonly edit: modes.ResourceFileEdit | modes.ResourceTextEdit) { } + + getUri(): URI { + return modes.isResourceTextEdit(this.edit) + ? this.edit.resource + : this.edit.oldUri || this.edit.newUri!; + } +} + +export class TextEditElement { + constructor(readonly line: string, readonly highlight: IHighlight, readonly edit: modes.TextEdit) { } +} + +export type Edit = FileElement | TextEditElement; + +// --- DATA SOURCE + +export class BulkEditDataSource implements IAsyncDataSource { + + constructor(@ITextModelService private readonly _textModelService: ITextModelService) { } + + hasChildren(element: modes.WorkspaceEdit | Edit): boolean { + if (element instanceof FileElement) { + return modes.isResourceTextEdit(element.edit); + } + if (element instanceof TextEditElement) { + return false; + } + return true; + } + + async getChildren(element: modes.WorkspaceEdit | Edit): Promise { + + // root -> file/text edits + if (Array.isArray((element).edits)) { + return (element).edits.map(edit => new FileElement(edit)); + } + + // file: text edit + if (element instanceof FileElement && modes.isResourceTextEdit(element.edit)) { + const ref = await this._textModelService.createModelReference(element.edit.resource); + const textModel = ref.object.textEditorModel; + + const result = element.edit.edits.map(edit => { + const range = Range.lift(edit.range); + const start = textModel.getOffsetAt(range.getStartPosition()); + const end = textModel.getOffsetAt(range.getEndPosition()); + const len = end - start; + + const previewStart = textModel.getPositionAt(start - 20); + const previewEnd = textModel.getPositionAt(end + 30); + + const preview = textModel.getValueInRange(Range.fromPositions(previewStart, previewEnd)); + const previewOffset = start - textModel.getOffsetAt(previewStart); + + return new TextEditElement(preview, { start: previewOffset, end: previewOffset + len }, edit); + }); + + return result; + } + + return []; + } +} + +// --- IDENT + +export class BulkEditIdentityProvider implements IIdentityProvider { + + private readonly _map = new WeakMap(); + private _idPool = 0; + + getId(element: Edit): { toString(): string; } { + let id = this._map.get(element); + if (typeof id === 'undefined') { + id = this._idPool++; + this._map.set(element, id); + } + return id; + } +} + +// --- RENDERER + +class FileElementTemplate { + constructor(readonly label: IResourceLabel) { } +} + +export class FileElementRenderer implements ITreeRenderer { + + static readonly id: string = 'FileElementRenderer'; + + readonly templateId: string = FileElementRenderer.id; + + private readonly _resourceLabel: ResourceLabels; + + constructor(@IInstantiationService instaService: IInstantiationService) { + this._resourceLabel = instaService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER); + } + + renderTemplate(container: HTMLElement): FileElementTemplate { + return new FileElementTemplate(this._resourceLabel.create(container, { supportHighlights: true })); + } + + renderElement(node: ITreeNode, _index: number, template: FileElementTemplate): void { + template.label.setFile(node.element.getUri(), { matches: createMatches(node.filterData) }); + } + + disposeTemplate(template: FileElementTemplate): void { + template.label.dispose(); + } +} + +class TextEditElementTemplate { + constructor(readonly label: HighlightedLabel) { } +} + +export class TextEditElementRenderer implements ITreeRenderer { + + static readonly id = 'TextEditElementRenderer'; + + readonly templateId: string = TextEditElementRenderer.id; + + renderTemplate(container: HTMLElement): TextEditElementTemplate { + const label = new HighlightedLabel(container, false); + dom.addClass(label.element, 'textedit'); + return new TextEditElementTemplate(label); + } + + renderElement(node: ITreeNode, _index: number, template: TextEditElementTemplate): void { + template.label.set(node.element.line, [node.element.highlight], undefined, true); + } + + disposeTemplate(_template: TextEditElementTemplate): void { } +} + +export class Delegate implements IListVirtualDelegate { + + getHeight(): number { + return 23; + } + + getTemplateId(element: Edit): string { + return element instanceof FileElement + ? FileElementRenderer.id + : TextEditElementRenderer.id; + } +} From 94a469319585ed5cce2e2d3fdd7e2bb740dacf3d Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 18 Dec 2019 11:25:45 +0100 Subject: [PATCH 007/315] add BulkEditPreviewHandler --- .../browser/services/bulkEditService.ts | 6 +++++- .../standalone/browser/simpleServices.ts | 4 ++++ .../bulkEdit/browser/bulkEditService.ts | 21 ++++++++++++++++--- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/browser/services/bulkEditService.ts b/src/vs/editor/browser/services/bulkEditService.ts index ada4ed7b23d2b..a21f72525905c 100644 --- a/src/vs/editor/browser/services/bulkEditService.ts +++ b/src/vs/editor/browser/services/bulkEditService.ts @@ -7,10 +7,10 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { WorkspaceEdit } from 'vs/editor/common/modes'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; +import { IDisposable } from 'vs/base/common/lifecycle'; export const IBulkEditService = createDecorator('IWorkspaceEditService'); - export interface IBulkEditOptions { editor?: ICodeEditor; progress?: IProgress; @@ -20,9 +20,13 @@ export interface IBulkEditResult { ariaSummary: string; } +export type IBulkEditPreviewHandler = (edit: WorkspaceEdit, options?: IBulkEditOptions) => Promise; + export interface IBulkEditService { _serviceBrand: undefined; + setPreviewHandler(handler: IBulkEditPreviewHandler): IDisposable; + apply(edit: WorkspaceEdit, options?: IBulkEditOptions): Promise; } diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index b5180aa9a2a1e..463a6940a6dd5 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -631,6 +631,10 @@ export class SimpleBulkEditService implements IBulkEditService { // } + setPreviewHandler(): IDisposable { + return Disposable.None; + } + apply(workspaceEdit: WorkspaceEdit, options?: IBulkEditOptions): Promise { let edits = new Map(); diff --git a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts index fd4c3bdaad945..40739d7055a9d 100644 --- a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { mergeSort } from 'vs/base/common/arrays'; -import { dispose, IDisposable, IReference } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable, IReference, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IBulkEditOptions, IBulkEditResult, IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; +import { IBulkEditOptions, IBulkEditResult, IBulkEditService, IBulkEditPreviewHandler } from 'vs/editor/browser/services/bulkEditService'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; import { EndOfLineSequence, IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; @@ -381,6 +381,8 @@ export class BulkEditService implements IBulkEditService { _serviceBrand: undefined; + private _previewHandler?: IBulkEditPreviewHandler; + constructor( @ILogService private readonly _logService: ILogService, @IModelService private readonly _modelService: IModelService, @@ -393,7 +395,20 @@ export class BulkEditService implements IBulkEditService { @IConfigurationService private readonly _configurationService: IConfigurationService ) { } - apply(edit: WorkspaceEdit, options: IBulkEditOptions = {}): Promise { + setPreviewHandler(handler: IBulkEditPreviewHandler): IDisposable { + this._previewHandler = handler; + return toDisposable(() => { + if (this._previewHandler === handler) { + this._previewHandler = undefined; + } + }); + } + + async apply(edit: WorkspaceEdit, options: IBulkEditOptions = {}): Promise { + + if (this._previewHandler) { + edit = await this._previewHandler(edit, options); + } let { edits } = edit; let codeEditor = options.editor; From ee9d42129da25fb1851bd4ab608fa2c24eb36d6e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 18 Dec 2019 12:08:06 +0100 Subject: [PATCH 008/315] first cut of panel and changes tree --- build/lib/i18n.resources.json | 4 + .../ui/highlightedlabel/highlightedLabel.ts | 3 +- .../bulkEdit/browser/bulkEdit.contribution.ts | 53 +++++++++ .../contrib/bulkEdit/browser/bulkEdit.css | 10 ++ .../contrib/bulkEdit/browser/bulkEditPanel.ts | 110 ++++++++++++++++++ .../bulkEdit/browser/bulkEditPreview.ts | 52 +++++++++ .../contrib/bulkEdit/browser/bulkEditTree.ts | 35 +++--- src/vs/workbench/workbench.common.main.ts | 3 + 8 files changed, 255 insertions(+), 15 deletions(-) create mode 100644 src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts create mode 100644 src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css create mode 100644 src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts create mode 100644 src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index e9a4f2796316e..b497e93f42f60 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -30,6 +30,10 @@ "name": "vs/workbench/api/common", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/bulkEdit", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/cli", "project": "vscode-workbench" diff --git a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts index 163dffb2d5a9a..9bb9292a4cdea 100644 --- a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts +++ b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts @@ -10,6 +10,7 @@ import { escape } from 'vs/base/common/strings'; export interface IHighlight { start: number; end: number; + extraClasses?: string; } export class HighlightedLabel { @@ -69,7 +70,7 @@ export class HighlightedLabel { htmlContent += ''; pos = highlight.end; } - htmlContent += ''; + htmlContent += ``; const substring = this.text.substring(highlight.start, highlight.end); htmlContent += this.supportCodicons ? renderCodicons(escape(substring)) : escape(substring); htmlContent += ''; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts new file mode 100644 index 0000000000000..1d6f1930587c5 --- /dev/null +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; +import { WorkspaceEdit } from 'vs/editor/common/modes'; +import { BulkEditPanel } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPanel'; +import { Extensions as PanelExtensions, PanelDescriptor, PanelRegistry } from 'vs/workbench/browser/panel'; +import { localize } from 'vs/nls'; + +class BulkEditPreviewContribution { + + constructor( + @IPanelService private _panelService: IPanelService, + @IBulkEditService bulkEditService: IBulkEditService, + ) { + + bulkEditService.setPreviewHandler(edit => this._previewEdit(edit)); + } + + private async _previewEdit(edit: WorkspaceEdit) { + + const panel = this._panelService.openPanel(BulkEditPanel.ID, true); + if (!(panel instanceof BulkEditPanel)) { + // error? + return edit; + } + + const apply = await panel.setInput(edit); + if (!apply) { + return { edits: [] }; + } + // todo@joh get 'real' edit + return edit; + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution( + BulkEditPreviewContribution, LifecyclePhase.Ready +); + +Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( + BulkEditPanel, + BulkEditPanel.ID, + localize('panel', "Refactor Preview"), + 'bulkEditPanel', + 10 +)); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css new file mode 100644 index 0000000000000..c46eb7bd8172a --- /dev/null +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench .bulk-edit-panel .highlight.remove { + text-decoration: line-through; +} + + diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts new file mode 100644 index 0000000000000..fa7add19ca6ae --- /dev/null +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts @@ -0,0 +1,110 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./bulkEdit'; +import { Panel } from 'vs/workbench/browser/panel'; +import { Dimension, addClass } from 'vs/base/browser/dom'; +import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; +import { WorkspaceEdit } from 'vs/editor/common/modes'; +import { Edit, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; +import { FuzzyScore } from 'vs/base/common/filters'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { Action, IAction } from 'vs/base/common/actions'; +import { diffInserted, diffRemoved } from 'vs/platform/theme/common/colorRegistry'; + +export class BulkEditPanel extends Panel { + + static readonly ID = 'BulkEditPanel'; + private static EmptyWorkspaceEdit = { edits: [] }; + + private _tree!: WorkbenchAsyncDataTree; + private _acceptAction: IAction; + private _discardAction: IAction; + + constructor( + @IInstantiationService private readonly _instaService: IInstantiationService, + @ITelemetryService telemetryService: ITelemetryService, + @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService + ) { + super(BulkEditPanel.ID, telemetryService, themeService, storageService); + + this._acceptAction = new Action('ok', 'Apply', 'codicon-check', false, async () => this._done(true)); + this._discardAction = new Action('ok', 'Discard', 'codicon-trash', false, async () => this._done(false)); + } + + create(parent: HTMLElement): void { + super.create(parent); + + addClass(parent, 'bulk-edit-panel'); + + const treeContainer = document.createElement('div'); + treeContainer.style.width = '100%'; + treeContainer.style.height = '100%'; + parent.appendChild(treeContainer); + + this._tree = this._instaService.createInstance( + WorkbenchAsyncDataTree, this.getId(), treeContainer, + new BulkEditDelegate(), + [new TextEditElementRenderer(), this._instaService.createInstance(FileElementRenderer)], + this._instaService.createInstance(BulkEditDataSource), + { + identityProvider: new BulkEditIdentityProvider() + } + ); + } + + layout(dimension: Dimension): void { + this._tree.layout(dimension.height, dimension.width); + } + + private _currentResolve?: (apply: boolean) => void; + + setInput(edit: WorkspaceEdit): Promise { + + if (this._currentResolve) { + this._currentResolve(false); + this._currentResolve = undefined; + } + + this._acceptAction.enabled = true; + this._discardAction.enabled = true; + + return new Promise(async resolve => { + this._currentResolve = resolve; + await this._tree.setInput(edit); + this._tree.domFocus(); + this._tree.focusFirst(); + }); + } + + private _done(accept: boolean): void { + if (this._currentResolve) { + this._currentResolve(accept); + this._acceptAction.enabled = false; + this._discardAction.enabled = false; + this._tree.setInput(BulkEditPanel.EmptyWorkspaceEdit); + } + } + + getActions() { + return [this._acceptAction, this._discardAction]; + } +} + +registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { + + const diffInsertedColor = theme.getColor(diffInserted); + if (diffInsertedColor) { + collector.addRule(`.monaco-workbench .bulk-edit-panel .highlight.insert { background-color: ${diffInsertedColor}; }`); + } + const diffRemovedColor = theme.getColor(diffRemoved); + if (diffRemovedColor) { + collector.addRule(`.monaco-workbench .bulk-edit-panel .highlight.remove { background-color: ${diffRemovedColor}; }`); + } +}); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts new file mode 100644 index 0000000000000..c5fdb4f4ce134 --- /dev/null +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// import { IAsyncDataSource, ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree'; +// import * as modes from 'vs/editor/common/modes'; +// import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; +// import { URI } from 'vs/base/common/uri'; +// import { ITextModel } from 'vs/editor/common/model'; +// import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +// import { IModeService } from 'vs/editor/common/services/modeService'; +// import { IModelService } from 'vs/editor/common/services/modelService'; +// import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; + +// export class BulkEditPreviewProvider implements ITextModelContentProvider { + +// static readonly Schema = 'vscode-bulkeditpreview'; + +// static asPreviewUri(uri: URI): URI { +// return URI.from({ scheme: BulkEditPreviewProvider.Schema, path: uri.toString() }); +// } + +// static fromPreviewUri(uri: URI): URI { +// return URI.parse(uri.path); +// } + +// constructor( +// @IModeService private readonly _modeService: IModeService, +// @IModelService private readonly _modelService: IModelService, +// @ITextModelService private readonly textModelResolverService: ITextModelService +// ) { +// this.textModelResolverService.registerTextModelContentProvider(BulkEditPreviewProvider.Schema, this); +// } + +// async provideTextContent(previewUri: URI) { + +// const resourceUri = BulkEditPreviewProvider.fromPreviewUri(previewUri); + +// const ref = await this.textModelResolverService.createModelReference(resourceUri); + +// const sourceModel = ref.object.textEditorModel; + +// const previewModel = this._modelService.createModel( +// createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()), +// this._modeService.create(sourceModel.getLanguageIdentifier().language), +// previewUri +// ); + +// return null; +// } +// } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index 03c015bc851aa..372366e9b0fcb 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -29,7 +29,7 @@ export class FileElement { } export class TextEditElement { - constructor(readonly line: string, readonly highlight: IHighlight, readonly edit: modes.TextEdit) { } + constructor(readonly edit: modes.TextEdit, readonly prefix: string, readonly selecting: string, readonly inserting: string, readonly suffix: string) { } } export type Edit = FileElement | TextEditElement; @@ -64,17 +64,14 @@ export class BulkEditDataSource implements IAsyncDataSource { const range = Range.lift(edit.range); - const start = textModel.getOffsetAt(range.getStartPosition()); - const end = textModel.getOffsetAt(range.getEndPosition()); - const len = end - start; - const previewStart = textModel.getPositionAt(start - 20); - const previewEnd = textModel.getPositionAt(end + 30); - - const preview = textModel.getValueInRange(Range.fromPositions(previewStart, previewEnd)); - const previewOffset = start - textModel.getOffsetAt(previewStart); - - return new TextEditElement(preview, { start: previewOffset, end: previewOffset + len }, edit); + return new TextEditElement( + edit, + textModel.getValueInRange(new Range(range.startLineNumber, 1, range.startLineNumber, range.startColumn)), // line start to edit start, + textModel.getValueInRange(range), + edit.text, + textModel.getValueInRange(new Range(range.endLineNumber, range.endColumn, range.endLineNumber, range.endColumn + 20)) + ); }); return result; @@ -148,14 +145,24 @@ export class TextEditElementRenderer implements ITreeRenderer, _index: number, template: TextEditElementTemplate): void { - template.label.set(node.element.line, [node.element.highlight], undefined, true); + renderElement({ element }: ITreeNode, _index: number, template: TextEditElementTemplate): void { + + let value = ''; + value += element.prefix; + value += element.selecting; + value += element.inserting; + value += element.suffix; + + let selectHighlight: IHighlight = { start: element.prefix.length, end: element.prefix.length + element.selecting.length, extraClasses: 'remove' }; + let insertHighlight: IHighlight = { start: selectHighlight.end, end: selectHighlight.end + element.inserting.length, extraClasses: 'insert' }; + + template.label.set(value, [selectHighlight, insertHighlight], undefined, true); } disposeTemplate(_template: TextEditElementTemplate): void { } } -export class Delegate implements IListVirtualDelegate { +export class BulkEditDelegate implements IListVirtualDelegate { getHeight(): number { return 23; diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 2c97f517bbc82..c437848af7efe 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -150,6 +150,9 @@ import 'vs/workbench/contrib/files/browser/files.contribution'; // Backup import 'vs/workbench/contrib/backup/common/backup.contribution'; +// bulkEdit +import 'vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution'; + // Search import 'vs/workbench/contrib/search/browser/search.contribution'; import 'vs/workbench/contrib/search/browser/searchView'; From d0012dd3329fcb9dfa341a1e8d7da397fced475b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 18 Dec 2019 12:27:52 +0100 Subject: [PATCH 009/315] prepare workspace edit a little before previewing --- .../bulkEdit/browser/bulkEditService.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts index 40739d7055a9d..7ad6c316423c2 100644 --- a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts @@ -404,8 +404,31 @@ export class BulkEditService implements IBulkEditService { }); } + private static _mergeSequentialTextEditsOfSameResource(workspaceEdit: WorkspaceEdit): WorkspaceEdit { + let newEdit: WorkspaceEdit = { edits: [] }; + let last: ResourceTextEdit | undefined; + for (let edit of workspaceEdit.edits) { + if (!isResourceTextEdit(edit)) { + last = undefined; + newEdit.edits.push(edit); + + } else { + if (!last || last.resource.toString() !== edit.resource.toString()) { + last = edit; + newEdit.edits.push(last); + } else { + last.edits.push(...edit.edits); + last.modelVersionId = last.modelVersionId || edit.modelVersionId; + } + } + } + return newEdit; + } + async apply(edit: WorkspaceEdit, options: IBulkEditOptions = {}): Promise { + edit = BulkEditService._mergeSequentialTextEditsOfSameResource(edit); + if (this._previewHandler) { edit = await this._previewHandler(edit, options); } From bd28239959546eaa83c1e72fb3dcb14edb2001e2 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 18 Dec 2019 16:05:17 +0100 Subject: [PATCH 010/315] token based line preview --- src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index 372366e9b0fcb..c613be3fcab75 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -65,12 +65,18 @@ export class BulkEditDataSource implements IAsyncDataSource { const range = Range.lift(edit.range); + const tokens = textModel.getLineTokens(range.endLineNumber); + let suffixLen = 0; + for (let idx = tokens.findTokenIndexAtOffset(range.endColumn); suffixLen < 50 && idx < tokens.getCount(); idx++) { + suffixLen += tokens.getEndOffset(idx) - tokens.getStartOffset(idx); + } + return new TextEditElement( edit, textModel.getValueInRange(new Range(range.startLineNumber, 1, range.startLineNumber, range.startColumn)), // line start to edit start, textModel.getValueInRange(range), edit.text, - textModel.getValueInRange(new Range(range.endLineNumber, range.endColumn, range.endLineNumber, range.endColumn + 20)) + textModel.getValueInRange(new Range(range.endLineNumber, range.endColumn, range.endLineNumber, range.endColumn + suffixLen)) ); }); From b1e6285a52f2266c928e91415d59191ebad2dfde Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 18 Dec 2019 16:11:58 +0100 Subject: [PATCH 011/315] expand first node --- .../workbench/contrib/bulkEdit/browser/bulkEditPanel.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts index fa7add19ca6ae..1333815aebc91 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts @@ -8,7 +8,7 @@ import { Panel } from 'vs/workbench/browser/panel'; import { Dimension, addClass } from 'vs/base/browser/dom'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { WorkspaceEdit } from 'vs/editor/common/modes'; -import { Edit, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; +import { Edit, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; import { FuzzyScore } from 'vs/base/common/filters'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -76,10 +76,16 @@ export class BulkEditPanel extends Panel { this._discardAction.enabled = true; return new Promise(async resolve => { + this._currentResolve = resolve; await this._tree.setInput(edit); this._tree.domFocus(); this._tree.focusFirst(); + + const first = this._tree.getFirstElementChild(); + if (first instanceof FileElement) { + this._tree.expand(first); + } }); } From 5a6e8e89362f5e43751494e095c46144e81bdefc Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 18 Dec 2019 16:30:09 +0100 Subject: [PATCH 012/315] show a default/empty message --- .../contrib/bulkEdit/browser/bulkEdit.css | 16 +++++++++ .../contrib/bulkEdit/browser/bulkEditPanel.ts | 35 ++++++++++++++++--- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css index c46eb7bd8172a..14043b9d48429 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css @@ -7,4 +7,20 @@ text-decoration: line-through; } +.monaco-workbench .bulk-edit-panel .message { + padding: 10px 20px +} + +.monaco-workbench .bulk-edit-panel[data-state="message"] .message, +.monaco-workbench .bulk-edit-panel[data-state="data"] .tree +{ + display: inherit; +} + +.monaco-workbench .bulk-edit-panel[data-state="data"] .message, +.monaco-workbench .bulk-edit-panel[data-state="message"] .tree +{ + display: none; +} + diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts index 1333815aebc91..c1f448a8d17d6 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts @@ -5,7 +5,7 @@ import 'vs/css!./bulkEdit'; import { Panel } from 'vs/workbench/browser/panel'; -import { Dimension, addClass } from 'vs/base/browser/dom'; +import { Dimension } from 'vs/base/browser/dom'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { WorkspaceEdit } from 'vs/editor/common/modes'; import { Edit, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; @@ -16,6 +16,12 @@ import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } import { IStorageService } from 'vs/platform/storage/common/storage'; import { Action, IAction } from 'vs/base/common/actions'; import { diffInserted, diffRemoved } from 'vs/platform/theme/common/colorRegistry'; +import { localize } from 'vs/nls'; + +const enum State { + Data = 'data', + Message = 'message' +} export class BulkEditPanel extends Panel { @@ -23,8 +29,10 @@ export class BulkEditPanel extends Panel { private static EmptyWorkspaceEdit = { edits: [] }; private _tree!: WorkbenchAsyncDataTree; - private _acceptAction: IAction; - private _discardAction: IAction; + private _message!: HTMLSpanElement; + + private readonly _acceptAction: IAction; + private readonly _discardAction: IAction; constructor( @IInstantiationService private readonly _instaService: IInstantiationService, @@ -40,10 +48,11 @@ export class BulkEditPanel extends Panel { create(parent: HTMLElement): void { super.create(parent); + parent.className = 'bulk-edit-panel'; - addClass(parent, 'bulk-edit-panel'); - + // tree const treeContainer = document.createElement('div'); + treeContainer.className = 'tree'; treeContainer.style.width = '100%'; treeContainer.style.height = '100%'; parent.appendChild(treeContainer); @@ -57,15 +66,29 @@ export class BulkEditPanel extends Panel { identityProvider: new BulkEditIdentityProvider() } ); + + // tree + this._message = document.createElement('span'); + this._message.className = 'message'; + this._message.innerText = localize('empty.msg', "Invoke a code action, like rename, to see a preview of its changes here."); + parent.appendChild(this._message); + + // + this._setState(State.Message); } layout(dimension: Dimension): void { this._tree.layout(dimension.height, dimension.width); } + private _setState(state: State): void { + this.getContainer()!.dataset['state'] = state; + } + private _currentResolve?: (apply: boolean) => void; setInput(edit: WorkspaceEdit): Promise { + this._setState(State.Data); if (this._currentResolve) { this._currentResolve(false); @@ -90,6 +113,8 @@ export class BulkEditPanel extends Panel { } private _done(accept: boolean): void { + this._setState(State.Message); + if (this._currentResolve) { this._currentResolve(accept); this._acceptAction.enabled = false; From b38926424a28270476b1d7037818c66c0b6359e7 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 18 Dec 2019 16:34:43 +0100 Subject: [PATCH 013/315] nls --- src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts index c1f448a8d17d6..bf2784facd882 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts @@ -42,8 +42,8 @@ export class BulkEditPanel extends Panel { ) { super(BulkEditPanel.ID, telemetryService, themeService, storageService); - this._acceptAction = new Action('ok', 'Apply', 'codicon-check', false, async () => this._done(true)); - this._discardAction = new Action('ok', 'Discard', 'codicon-trash', false, async () => this._done(false)); + this._acceptAction = new Action('ok', localize('ok', "Apply Refactoring"), 'codicon-check', false, async () => this._done(true)); + this._discardAction = new Action('discard', localize('discard', "Discard"), 'codicon-trash', false, async () => this._done(false)); } create(parent: HTMLElement): void { From a8e2d7ce83aa2e7450602d19b9e32358e70a7abb Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 18 Dec 2019 18:10:33 +0100 Subject: [PATCH 014/315] show preview diff editor --- .../bulkEdit/browser/bulkEdit.contribution.ts | 6 ++ .../contrib/bulkEdit/browser/bulkEditPanel.ts | 69 +++++++++++--- .../bulkEdit/browser/bulkEditPreview.ts | 91 +++++++++++-------- .../contrib/bulkEdit/browser/bulkEditTree.ts | 8 +- 4 files changed, 124 insertions(+), 50 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts index 1d6f1930587c5..3116faa32bf86 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts @@ -35,6 +35,12 @@ class BulkEditPreviewContribution { if (!apply) { return { edits: [] }; } + + // todo@joh/steVen add view state + // if (this._panelService.getLastActivePanelId() === BulkEditPanel.ID) { + // this._panelService.hideActivePanel(); + // } + // todo@joh get 'real' edit return edit; } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts index bf2784facd882..9b93404213ad3 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts @@ -8,15 +8,19 @@ import { Panel } from 'vs/workbench/browser/panel'; import { Dimension } from 'vs/base/browser/dom'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { WorkspaceEdit } from 'vs/editor/common/modes'; -import { Edit, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; +import { Edit, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; import { FuzzyScore } from 'vs/base/common/filters'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { Action, IAction } from 'vs/base/common/actions'; +import { Action } from 'vs/base/common/actions'; import { diffInserted, diffRemoved } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { BulkEditPreviewProvider } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; +import { ILabelService } from 'vs/platform/label/common/label'; const enum State { Data = 'data', @@ -31,19 +35,27 @@ export class BulkEditPanel extends Panel { private _tree!: WorkbenchAsyncDataTree; private _message!: HTMLSpanElement; - private readonly _acceptAction: IAction; - private readonly _discardAction: IAction; + private readonly _acceptAction = new Action('ok', localize('ok', "Apply Refactoring"), 'codicon-check', false, async () => this._done(true)); + private readonly _discardAction = new Action('discard', localize('discard', "Discard"), 'codicon-trash', false, async () => this._done(false)); + private readonly _disposables = new DisposableStore(); + + private readonly _sessionDisposables = new DisposableStore(); + private _currentResolve?: (apply: boolean) => void; constructor( @IInstantiationService private readonly _instaService: IInstantiationService, + @IEditorService private readonly _editorService: IEditorService, + @ILabelService private readonly _labelService: ILabelService, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, - @IStorageService storageService: IStorageService + @IStorageService storageService: IStorageService, ) { super(BulkEditPanel.ID, telemetryService, themeService, storageService); + } - this._acceptAction = new Action('ok', localize('ok', "Apply Refactoring"), 'codicon-check', false, async () => this._done(true)); - this._discardAction = new Action('discard', localize('discard', "Discard"), 'codicon-trash', false, async () => this._done(false)); + dispose(): void { + this._tree.dispose(); + this._disposables.dispose(); } create(parent: HTMLElement): void { @@ -67,7 +79,16 @@ export class BulkEditPanel extends Panel { } ); - // tree + this._disposables.add(this._tree.onDidOpen(e => { + const [first] = e.elements; + if (first instanceof TextEditElement) { + this._previewTextEditElement(first); + } else if (first instanceof FileElement) { + this._previewFileElement(first); + } + })); + + // message this._message = document.createElement('span'); this._message.className = 'message'; this._message.innerText = localize('empty.msg', "Invoke a code action, like rename, to see a preview of its changes here."); @@ -77,6 +98,10 @@ export class BulkEditPanel extends Panel { this._setState(State.Message); } + getActions() { + return [this._acceptAction, this._discardAction]; + } + layout(dimension: Dimension): void { this._tree.layout(dimension.height, dimension.width); } @@ -85,10 +110,11 @@ export class BulkEditPanel extends Panel { this.getContainer()!.dataset['state'] = state; } - private _currentResolve?: (apply: boolean) => void; - setInput(edit: WorkspaceEdit): Promise { this._setState(State.Data); + this._sessionDisposables.clear(); + + this._sessionDisposables.add(this._instaService.createInstance(BulkEditPreviewProvider, edit)); if (this._currentResolve) { this._currentResolve(false); @@ -114,7 +140,7 @@ export class BulkEditPanel extends Panel { private _done(accept: boolean): void { this._setState(State.Message); - + this._sessionDisposables.clear(); if (this._currentResolve) { this._currentResolve(accept); this._acceptAction.enabled = false; @@ -123,8 +149,25 @@ export class BulkEditPanel extends Panel { } } - getActions() { - return [this._acceptAction, this._discardAction]; + private async _previewTextEditElement(edit: TextEditElement): Promise { + + const previewUri = BulkEditPreviewProvider.asPreviewUri(edit.parent.resource); + + this._editorService.openEditor({ + leftResource: edit.parent.resource, + rightResource: previewUri, + label: localize('edt.title', "{0} (Refactor Preview)", this._labelService.getUriLabel(edit.parent.resource)), + options: { + selection: edit.edit.range + // preserveFocus, + // pinned, + // revealIfVisible: true + } + }); + } + + private _previewFileElement(edit: FileElement): void { + } } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts index c5fdb4f4ce134..df7e8edb34589 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts @@ -3,50 +3,69 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// import { IAsyncDataSource, ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree'; -// import * as modes from 'vs/editor/common/modes'; -// import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; -// import { URI } from 'vs/base/common/uri'; -// import { ITextModel } from 'vs/editor/common/model'; -// import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -// import { IModeService } from 'vs/editor/common/services/modeService'; -// import { IModelService } from 'vs/editor/common/services/modelService'; -// import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; +import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; +import { URI } from 'vs/base/common/uri'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; +import { WorkspaceEdit, isResourceTextEdit, TextEdit } from 'vs/editor/common/modes'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { flatten, mergeSort } from 'vs/base/common/arrays'; +import { Range } from 'vs/editor/common/core/range'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; -// export class BulkEditPreviewProvider implements ITextModelContentProvider { +export class BulkEditPreviewProvider implements ITextModelContentProvider { -// static readonly Schema = 'vscode-bulkeditpreview'; + static readonly Schema = 'vscode-bulkeditpreview'; -// static asPreviewUri(uri: URI): URI { -// return URI.from({ scheme: BulkEditPreviewProvider.Schema, path: uri.toString() }); -// } + static asPreviewUri(uri: URI): URI { + return URI.from({ scheme: BulkEditPreviewProvider.Schema, path: uri.toString() }); + } -// static fromPreviewUri(uri: URI): URI { -// return URI.parse(uri.path); -// } + static fromPreviewUri(uri: URI): URI { + return URI.parse(uri.path); + } -// constructor( -// @IModeService private readonly _modeService: IModeService, -// @IModelService private readonly _modelService: IModelService, -// @ITextModelService private readonly textModelResolverService: ITextModelService -// ) { -// this.textModelResolverService.registerTextModelContentProvider(BulkEditPreviewProvider.Schema, this); -// } + private readonly _reg: IDisposable; -// async provideTextContent(previewUri: URI) { + constructor( + private readonly _edit: WorkspaceEdit, + @IModeService private readonly _modeService: IModeService, + @IModelService private readonly _modelService: IModelService, + @ITextModelService private readonly textModelResolverService: ITextModelService + ) { + this._reg = this.textModelResolverService.registerTextModelContentProvider(BulkEditPreviewProvider.Schema, this); + } -// const resourceUri = BulkEditPreviewProvider.fromPreviewUri(previewUri); + dispose(): void { + this._reg.dispose(); + } -// const ref = await this.textModelResolverService.createModelReference(resourceUri); + async provideTextContent(previewUri: URI) { -// const sourceModel = ref.object.textEditorModel; + const resourceUri = BulkEditPreviewProvider.fromPreviewUri(previewUri); -// const previewModel = this._modelService.createModel( -// createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()), -// this._modeService.create(sourceModel.getLanguageIdentifier().language), -// previewUri -// ); + const ref = await this.textModelResolverService.createModelReference(resourceUri); -// return null; -// } -// } + const sourceModel = ref.object.textEditorModel; + + const previewModel = this._modelService.createModel( + createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()), + this._modeService.create(sourceModel.getLanguageIdentifier().language), + previewUri + ); + + const textEdits: TextEdit[][] = []; + for (let edit of this._edit.edits) { + if (isResourceTextEdit(edit) && edit.resource.toString() === resourceUri.toString()) { + textEdits.push(edit.edits); + } + } + + let allEdits = flatten(textEdits).map(edit => EditOperation.replaceMove(Range.lift(edit.range), edit.text)); + allEdits = mergeSort(allEdits, (a, b) => Range.compareRangesUsingStarts(a.range, b.range)); + previewModel.applyEdits(allEdits); + + return previewModel; + } +} diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index c613be3fcab75..ce48ebacc1c59 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -29,7 +29,12 @@ export class FileElement { } export class TextEditElement { - constructor(readonly edit: modes.TextEdit, readonly prefix: string, readonly selecting: string, readonly inserting: string, readonly suffix: string) { } + + constructor( + readonly parent: modes.ResourceTextEdit, + readonly edit: modes.TextEdit, + readonly prefix: string, readonly selecting: string, readonly inserting: string, readonly suffix: string + ) { } } export type Edit = FileElement | TextEditElement; @@ -72,6 +77,7 @@ export class BulkEditDataSource implements IAsyncDataSourceelement.edit, edit, textModel.getValueInRange(new Range(range.startLineNumber, 1, range.startLineNumber, range.startColumn)), // line start to edit start, textModel.getValueInRange(range), From 2385505c9d99d5e3a8ee557a9ba2765c856791b0 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 20 Dec 2019 16:19:36 +0100 Subject: [PATCH 015/315] release references --- src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts | 2 +- src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts index df7e8edb34589..bd30a397985f1 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts @@ -65,7 +65,7 @@ export class BulkEditPreviewProvider implements ITextModelContentProvider { let allEdits = flatten(textEdits).map(edit => EditOperation.replaceMove(Range.lift(edit.range), edit.text)); allEdits = mergeSort(allEdits, (a, b) => Range.compareRangesUsingStarts(a.range, b.range)); previewModel.applyEdits(allEdits); - + ref.dispose(); return previewModel; } } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index ce48ebacc1c59..02711a2c196e8 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -85,7 +85,7 @@ export class BulkEditDataSource implements IAsyncDataSource Date: Fri, 20 Dec 2019 16:45:16 +0100 Subject: [PATCH 016/315] add noPreview options --- src/vs/editor/browser/services/bulkEditService.ts | 1 + src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/browser/services/bulkEditService.ts b/src/vs/editor/browser/services/bulkEditService.ts index a21f72525905c..7c43316685660 100644 --- a/src/vs/editor/browser/services/bulkEditService.ts +++ b/src/vs/editor/browser/services/bulkEditService.ts @@ -14,6 +14,7 @@ export const IBulkEditService = createDecorator('IWorkspaceEdi export interface IBulkEditOptions { editor?: ICodeEditor; progress?: IProgress; + noPreview?: boolean; } export interface IBulkEditResult { diff --git a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts index 7ad6c316423c2..08390b52432f0 100644 --- a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts @@ -429,7 +429,7 @@ export class BulkEditService implements IBulkEditService { edit = BulkEditService._mergeSequentialTextEditsOfSameResource(edit); - if (this._previewHandler) { + if (this._previewHandler && !options.noPreview) { edit = await this._previewHandler(edit, options); } From 34fcef5cdd6eebb6e2951f62374b6a09ae235a76 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 23 Dec 2019 14:34:34 +0100 Subject: [PATCH 017/315] implement preview file system... --- .../bulkEdit/browser/bulkEditPreview.ts | 222 +++++++++++++++++- 1 file changed, 221 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts index bd30a397985f1..b8035d4478aa5 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts @@ -9,10 +9,14 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; import { WorkspaceEdit, isResourceTextEdit, TextEdit } from 'vs/editor/common/modes'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { flatten, mergeSort } from 'vs/base/common/arrays'; import { Range } from 'vs/editor/common/core/range'; import { EditOperation } from 'vs/editor/common/core/editOperation'; +import * as files from 'vs/platform/files/common/files'; +import { Event, Emitter } from 'vs/base/common/event'; +import { TernarySearchTree } from 'vs/base/common/map'; +import { basename } from 'vs/base/common/resources'; export class BulkEditPreviewProvider implements ITextModelContentProvider { @@ -69,3 +73,219 @@ export class BulkEditPreviewProvider implements ITextModelContentProvider { return previewModel; } } + + +export function asPreviewUri(uri: URI): URI { + return URI.from({ scheme: 'vscode-bulkedit-preview', path: uri.path, query: uri.toString() }); +} + +export function fromPreviewUri(uri: URI): URI { + return URI.parse(uri.query); +} + +export function asPreviewEdit(edit: WorkspaceEdit): WorkspaceEdit { + const result: WorkspaceEdit = { edits: [] }; + for (let child of edit.edits) { + if (isResourceTextEdit(child)) { + result.edits.push({ ...child, resource: asPreviewUri(child.resource) }); + } else { + result.edits.push({ + oldUri: child.oldUri && asPreviewUri(child.oldUri), + newUri: child.newUri && asPreviewUri(child.newUri), + options: child.options + }); + } + } + return result; +} + +class File implements files.IStat { + + type: files.FileType; + ctime: number; + mtime: number; + size: number; + + name: string; + data?: Uint8Array; + + constructor(name: string) { + this.type = files.FileType.File; + this.ctime = Date.now(); + this.mtime = Date.now(); + this.size = 0; + this.name = name; + } +} + +class Directory implements files.IStat { + + type: files.FileType; + ctime: number; + mtime: number; + size: number; + + name: string; + entries: Map; + + constructor(name: string) { + this.type = files.FileType.Directory; + this.ctime = Date.now(); + this.mtime = Date.now(); + this.size = 0; + this.name = name; + this.entries = new Map(); + } +} + +type Entry = File | Directory; + +export class BulkEditFileSystem implements files.IFileSystemProvider { + + readonly capabilities = files.FileSystemProviderCapabilities.FileReadWrite; + readonly onDidChangeCapabilities = Event.None; + + private readonly _registration: IDisposable; + private readonly _onDidChangeFile = new Emitter(); + readonly onDidChangeFile = this._onDidChangeFile.event; + + private readonly _entries = TernarySearchTree.forPaths(); + private readonly _deleted = TernarySearchTree.forPaths(); + + private _pendingChanges: files.IFileChange[] = []; + private _pendingHandle?: any; + + constructor(@files.IFileService private _fileService: files.IFileService) { + this._registration = _fileService.registerProvider('vscode-bulkedit-preview', this); + } + + dispose(): void { + this._registration.dispose(); + } + + private _fireSoon(event: files.IFileChange): void { + this._pendingChanges.push(event); + clearTimeout(this._pendingHandle); + this._pendingHandle = setTimeout(() => { + this._onDidChangeFile.fire(this._pendingChanges.slice(0)); + this._pendingChanges = []; + }, 0); + } + + private _checkDeleted(resource: URI): void { + if (this._deleted.findSubstr(resource.toString())) { + throw new Error('deleted ' + resource.toString()); + } + } + + watch(_resource: URI, _opts: files.IWatchOptions): IDisposable { + return Disposable.None; + } + + async stat(resource: URI): Promise { + this._checkDeleted(resource); + const entry = this._entries.get(resource.toString()); + if (entry) { + return entry; + } + + const stat = await this._fileService.resolve(fromPreviewUri(resource), { resolveMetadata: true }); + return { + type: (stat.isSymbolicLink ? files.FileType.SymbolicLink : 0) + (stat.isFile ? files.FileType.File : 0) + (stat.isDirectory ? files.FileType.Directory : 0), + ctime: stat.ctime, + mtime: stat.mtime, + size: stat.size, + }; + } + + async readdir(resource: URI): Promise<[string, files.FileType][]> { + // resource = fromPreviewUri(resource); + this._checkDeleted(resource); + + const entry = this._entries.get(resource.toString()); + const result: [string, files.FileType][] = []; + if (entry instanceof Directory) { + entry.entries.forEach(value => result.push([value.name, value.type])); + } + try { + const stat = await this._fileService.resolve(fromPreviewUri(resource), { resolveMetadata: true }); + if (stat.children) { + for (let child of stat.children) { + result.push([ + child.name, + (child.isSymbolicLink ? files.FileType.SymbolicLink : 0) + (child.isFile ? files.FileType.File : 0) + (child.isDirectory ? files.FileType.Directory : 0) + ]); + } + } + + + } catch { + // ignore + } + + return result; + } + + async mkdir(resource: URI): Promise { + // resource = fromPreviewUri(resource); + + const dir = new Directory(basename(resource)); + this._entries.set(resource.toString(), dir); + this._fireSoon({ resource, type: files.FileChangeType.ADDED }); + } + + async delete(resource: URI, _opts: files.FileDeleteOptions): Promise { + // resource = fromPreviewUri(resource); + this._deleted.set(resource.toString(), true); + } + + async rename(from: URI, to: URI, opts: files.FileOverwriteOptions): Promise { + // from = fromPreviewUri(from); + // to = fromPreviewUri(to); + + const target = new File(basename(to)); + target.type = (await this.stat(from)).type; + + this._deleted.set(from.toString(), true); + this._entries.set(to.toString(), target); + + // todo@joh copy files + // const iter = this._entries.findSuperstr(from.toString()); + // if (iter) { + // for (let next = iter.next(); !next.done; next = iter.next()) { + + // } + // } + // this._entries.delete(from.toString()); + + //todo@joh RENAME EVENT? + this._fireSoon({ resource: from, type: files.FileChangeType.DELETED }); + this._fireSoon({ resource: to, type: files.FileChangeType.ADDED }); + } + + // copy?(from: URI, to: URI, opts: FileOverwriteOptions): Promise; + + async readFile(resource: URI): Promise { + this._checkDeleted(resource); + + const entry = this._entries.get(resource.toString()); + if (entry instanceof File) { + return entry.data || new Uint8Array(); + } + return (await this._fileService.readFile(fromPreviewUri(resource))).value.buffer; + } + + async writeFile(resource: URI, content: Uint8Array, opts: files.FileWriteOptions): Promise { + + let entry = this._entries.get(resource.toString()); + if (!entry && opts.create) { + entry = new File(basename(resource)); + this._entries.set(resource.toString(), entry); + } + if (!(entry instanceof File)) { + throw new Error(); + } + entry.data = content; + this._fireSoon({ resource, type: files.FileChangeType.UPDATED }); + } +} From 8add944b5435f36b962796449ac8c556fdc32a6c Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 23 Dec 2019 14:40:20 +0100 Subject: [PATCH 018/315] use preview fs --- .../contrib/bulkEdit/browser/bulkEditPanel.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts index 9b93404213ad3..a312655cf4abf 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts @@ -19,8 +19,9 @@ import { diffInserted, diffRemoved } from 'vs/platform/theme/common/colorRegistr import { localize } from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { BulkEditPreviewProvider } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; +import { BulkEditFileSystem, asPreviewEdit, fromPreviewUri } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; import { ILabelService } from 'vs/platform/label/common/label'; +import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; const enum State { Data = 'data', @@ -44,6 +45,7 @@ export class BulkEditPanel extends Panel { constructor( @IInstantiationService private readonly _instaService: IInstantiationService, + @IBulkEditService private readonly _bulkEditService: IBulkEditService, @IEditorService private readonly _editorService: IEditorService, @ILabelService private readonly _labelService: ILabelService, @ITelemetryService telemetryService: ITelemetryService, @@ -110,11 +112,17 @@ export class BulkEditPanel extends Panel { this.getContainer()!.dataset['state'] = state; } - setInput(edit: WorkspaceEdit): Promise { + async setInput(edit: WorkspaceEdit): Promise { this._setState(State.Data); this._sessionDisposables.clear(); - this._sessionDisposables.add(this._instaService.createInstance(BulkEditPreviewProvider, edit)); + this._sessionDisposables.add(this._instaService.createInstance(BulkEditFileSystem)); + + const previewEdit = asPreviewEdit(edit); + + const summary = await this._bulkEditService.apply(previewEdit, { noPreview: true }); + console.log(summary); + // this._sessionDisposables.add(this._instaService.createInstance(BulkEditPreviewProvider, edit)); if (this._currentResolve) { this._currentResolve(false); @@ -127,7 +135,7 @@ export class BulkEditPanel extends Panel { return new Promise(async resolve => { this._currentResolve = resolve; - await this._tree.setInput(edit); + await this._tree.setInput(previewEdit); this._tree.domFocus(); this._tree.focusFirst(); @@ -151,11 +159,11 @@ export class BulkEditPanel extends Panel { private async _previewTextEditElement(edit: TextEditElement): Promise { - const previewUri = BulkEditPreviewProvider.asPreviewUri(edit.parent.resource); + // const previewUri = BulkEditPreviewProvider.asPreviewUri(edit.parent.resource); this._editorService.openEditor({ - leftResource: edit.parent.resource, - rightResource: previewUri, + leftResource: fromPreviewUri(edit.parent.resource), + rightResource: edit.parent.resource, label: localize('edt.title', "{0} (Refactor Preview)", this._labelService.getUriLabel(edit.parent.resource)), options: { selection: edit.edit.range From 6e06517057645121558accc1a3931a1e7a5f8360 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 23 Dec 2019 15:00:13 +0100 Subject: [PATCH 019/315] Revert "use preview fs" This reverts commit 8add944b5435f36b962796449ac8c556fdc32a6c. --- .../contrib/bulkEdit/browser/bulkEditPanel.ts | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts index a312655cf4abf..9b93404213ad3 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts @@ -19,9 +19,8 @@ import { diffInserted, diffRemoved } from 'vs/platform/theme/common/colorRegistr import { localize } from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { BulkEditFileSystem, asPreviewEdit, fromPreviewUri } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; +import { BulkEditPreviewProvider } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; import { ILabelService } from 'vs/platform/label/common/label'; -import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; const enum State { Data = 'data', @@ -45,7 +44,6 @@ export class BulkEditPanel extends Panel { constructor( @IInstantiationService private readonly _instaService: IInstantiationService, - @IBulkEditService private readonly _bulkEditService: IBulkEditService, @IEditorService private readonly _editorService: IEditorService, @ILabelService private readonly _labelService: ILabelService, @ITelemetryService telemetryService: ITelemetryService, @@ -112,17 +110,11 @@ export class BulkEditPanel extends Panel { this.getContainer()!.dataset['state'] = state; } - async setInput(edit: WorkspaceEdit): Promise { + setInput(edit: WorkspaceEdit): Promise { this._setState(State.Data); this._sessionDisposables.clear(); - this._sessionDisposables.add(this._instaService.createInstance(BulkEditFileSystem)); - - const previewEdit = asPreviewEdit(edit); - - const summary = await this._bulkEditService.apply(previewEdit, { noPreview: true }); - console.log(summary); - // this._sessionDisposables.add(this._instaService.createInstance(BulkEditPreviewProvider, edit)); + this._sessionDisposables.add(this._instaService.createInstance(BulkEditPreviewProvider, edit)); if (this._currentResolve) { this._currentResolve(false); @@ -135,7 +127,7 @@ export class BulkEditPanel extends Panel { return new Promise(async resolve => { this._currentResolve = resolve; - await this._tree.setInput(previewEdit); + await this._tree.setInput(edit); this._tree.domFocus(); this._tree.focusFirst(); @@ -159,11 +151,11 @@ export class BulkEditPanel extends Panel { private async _previewTextEditElement(edit: TextEditElement): Promise { - // const previewUri = BulkEditPreviewProvider.asPreviewUri(edit.parent.resource); + const previewUri = BulkEditPreviewProvider.asPreviewUri(edit.parent.resource); this._editorService.openEditor({ - leftResource: fromPreviewUri(edit.parent.resource), - rightResource: edit.parent.resource, + leftResource: edit.parent.resource, + rightResource: previewUri, label: localize('edt.title', "{0} (Refactor Preview)", this._labelService.getUriLabel(edit.parent.resource)), options: { selection: edit.edit.range From ea9c2f030bbb34df17015544908d84e9bbf98ac7 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 23 Dec 2019 16:09:38 +0100 Subject: [PATCH 020/315] follow text content provider approach --- .../contrib/bulkEdit/browser/bulkEditPanel.ts | 14 ++- .../bulkEdit/browser/bulkEditPreview.ts | 93 +++++++++++++------ .../contrib/bulkEdit/browser/bulkEditTree.ts | 19 +++- 3 files changed, 92 insertions(+), 34 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts index 9b93404213ad3..aab4b172409c4 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts @@ -21,6 +21,8 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { BulkEditPreviewProvider } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; import { ILabelService } from 'vs/platform/label/common/label'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { URI } from 'vs/base/common/uri'; const enum State { Data = 'data', @@ -46,6 +48,7 @@ export class BulkEditPanel extends Panel { @IInstantiationService private readonly _instaService: IInstantiationService, @IEditorService private readonly _editorService: IEditorService, @ILabelService private readonly _labelService: ILabelService, + @ITextModelService private readonly _textModelService: ITextModelService, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IStorageService storageService: IStorageService, @@ -151,10 +154,19 @@ export class BulkEditPanel extends Panel { private async _previewTextEditElement(edit: TextEditElement): Promise { + let leftResource: URI; + + try { + (await this._textModelService.createModelReference(edit.parent.resource)).dispose(); + leftResource = edit.parent.resource; + } catch { + leftResource = BulkEditPreviewProvider.emptyPreview; + } + const previewUri = BulkEditPreviewProvider.asPreviewUri(edit.parent.resource); this._editorService.openEditor({ - leftResource: edit.parent.resource, + leftResource, rightResource: previewUri, label: localize('edt.title', "{0} (Refactor Preview)", this._labelService.getUriLabel(edit.parent.resource)), options: { diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts index b8035d4478aa5..59695cdc2ec36 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts @@ -8,9 +8,9 @@ import { URI } from 'vs/base/common/uri'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; -import { WorkspaceEdit, isResourceTextEdit, TextEdit } from 'vs/editor/common/modes'; -import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { flatten, mergeSort } from 'vs/base/common/arrays'; +import { WorkspaceEdit, isResourceTextEdit, isResourceFileEdit } from 'vs/editor/common/modes'; +import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { mergeSort } from 'vs/base/common/arrays'; import { Range } from 'vs/editor/common/core/range'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import * as files from 'vs/platform/files/common/files'; @@ -22,55 +22,88 @@ export class BulkEditPreviewProvider implements ITextModelContentProvider { static readonly Schema = 'vscode-bulkeditpreview'; + static emptyPreview = URI.from({ scheme: BulkEditPreviewProvider.Schema, fragment: 'empty' }); + static asPreviewUri(uri: URI): URI { - return URI.from({ scheme: BulkEditPreviewProvider.Schema, path: uri.toString() }); + return URI.from({ scheme: BulkEditPreviewProvider.Schema, path: uri.path, query: uri.toString() }); } static fromPreviewUri(uri: URI): URI { - return URI.parse(uri.path); + return URI.parse(uri.query); } - private readonly _reg: IDisposable; + private readonly _disposables = new DisposableStore(); + private readonly _ready: Promise; constructor( private readonly _edit: WorkspaceEdit, @IModeService private readonly _modeService: IModeService, @IModelService private readonly _modelService: IModelService, - @ITextModelService private readonly textModelResolverService: ITextModelService + @ITextModelService private readonly _textModelResolverService: ITextModelService ) { - this._reg = this.textModelResolverService.registerTextModelContentProvider(BulkEditPreviewProvider.Schema, this); + this._disposables.add(this._textModelResolverService.registerTextModelContentProvider(BulkEditPreviewProvider.Schema, this)); + this._ready = this._prepareModels(); } dispose(): void { - this._reg.dispose(); + this._disposables.dispose(); } - async provideTextContent(previewUri: URI) { - - const resourceUri = BulkEditPreviewProvider.fromPreviewUri(previewUri); - - const ref = await this.textModelResolverService.createModelReference(resourceUri); - - const sourceModel = ref.object.textEditorModel; - - const previewModel = this._modelService.createModel( - createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()), - this._modeService.create(sourceModel.getLanguageIdentifier().language), - previewUri - ); + private async _prepareModels() { + + const getOrCreatePreviewModel = async (uri: URI) => { + const previewUri = BulkEditPreviewProvider.asPreviewUri(uri); + let model = this._modelService.getModel(previewUri); + if (!model) { + try { + // try: copy existing + const ref = await this._textModelResolverService.createModelReference(uri); + const sourceModel = ref.object.textEditorModel; + model = this._modelService.createModel( + createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()), + this._modeService.create(sourceModel.getLanguageIdentifier().language), + previewUri + ); + ref.dispose(); + + } catch { + // create NEW model + model = this._modelService.createModel( + '', + this._modeService.createByFilepathOrFirstLine(previewUri), + previewUri + ); + } + this._disposables.add(model); + } + return model; + }; - const textEdits: TextEdit[][] = []; for (let edit of this._edit.edits) { - if (isResourceTextEdit(edit) && edit.resource.toString() === resourceUri.toString()) { - textEdits.push(edit.edits); + if (isResourceFileEdit(edit)) { + if (URI.isUri(edit.newUri)) { + await getOrCreatePreviewModel(edit.newUri); + } + if (URI.isUri(edit.oldUri)) { + await getOrCreatePreviewModel(edit.oldUri); + } + } else { + const model = await getOrCreatePreviewModel(edit.resource); + const editOperations = mergeSort( + edit.edits.map(edit => EditOperation.replaceMove(Range.lift(edit.range), edit.text)), + (a, b) => Range.compareRangesUsingStarts(a.range, b.range) + ); + model.applyEdits(editOperations); } } + } - let allEdits = flatten(textEdits).map(edit => EditOperation.replaceMove(Range.lift(edit.range), edit.text)); - allEdits = mergeSort(allEdits, (a, b) => Range.compareRangesUsingStarts(a.range, b.range)); - previewModel.applyEdits(allEdits); - ref.dispose(); - return previewModel; + async provideTextContent(previewUri: URI) { + if (previewUri.toString() === BulkEditPreviewProvider.emptyPreview.toString()) { + return this._modelService.createModel('', null, previewUri); + } + await this._ready; + return this._modelService.getModel(previewUri); } } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index 02711a2c196e8..7fee573a6dcfb 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -14,6 +14,9 @@ import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabe import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { Range } from 'vs/editor/common/core/range'; import * as dom from 'vs/base/browser/dom'; +import { ITextModel } from 'vs/editor/common/model'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { TextModel } from 'vs/editor/common/model/textModel'; // --- VIEW MODEL @@ -64,8 +67,17 @@ export class BulkEditDataSource implements IAsyncDataSource { const range = Range.lift(edit.range); @@ -85,7 +97,8 @@ export class BulkEditDataSource implements IAsyncDataSource Date: Mon, 30 Dec 2019 14:59:20 +0100 Subject: [PATCH 021/315] editor: introduce setAria api --- src/vs/editor/browser/controller/textAreaHandler.ts | 13 +++++++++++++ src/vs/editor/browser/editorBrowser.ts | 13 +++++++++++++ src/vs/editor/browser/view/viewImpl.ts | 7 +++++-- src/vs/editor/browser/widget/codeEditorWidget.ts | 7 +++++++ src/vs/monaco.d.ts | 7 +++++++ 5 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 49ed53c81df97..eff63581e434c 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -29,6 +29,7 @@ import { RenderingContext, RestrictedRenderingContext, HorizontalPosition } from import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { IEditorAriaOptions } from 'vs/editor/browser/editorBrowser'; export interface ITextAreaHandlerHelper { visibleRangeForPositionRelativeToEditor(lineNumber: number, column: number): HorizontalPosition | null; @@ -424,6 +425,18 @@ export class TextAreaHandler extends ViewPart { return this._lastRenderPosition; } + public setAria(options: IEditorAriaOptions): void { + if (options.activeDescendent) { + this.textArea.setAttribute('aria-haspopup', 'true'); + this.textArea.setAttribute('aria-autocomplete', 'list'); + this.textArea.setAttribute('aria-activedescendant', options.activeDescendent); + } else { + this.textArea.setAttribute('aria-haspopup', 'false'); + this.textArea.setAttribute('aria-autocomplete', 'both'); + this.textArea.removeAttribute('aria-activedescendant'); + } + } + // --- end view API private _primaryCursorPosition: Position = new Position(1, 1); diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index c359f7ee54d95..4906fa6196ae2 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -319,6 +319,13 @@ export interface IOverviewRuler { setLayout(position: OverviewRulerPosition): void; } +/** + * Editor aria options. + */ +export interface IEditorAriaOptions { + activeDescendent: string | undefined; +} + /** * A rich code editor. */ @@ -689,6 +696,12 @@ export interface ICodeEditor extends editorCommon.IEditor { */ setHiddenAreas(ranges: IRange[]): void; + /** + * Sets the editor aria options, primarily the active descendent. + * @internal + */ + setAria(options: IEditorAriaOptions): void; + /** * @internal */ diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 28e858ea7e1ac..908dce7abb519 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -11,7 +11,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { IPointerHandlerHelper } from 'vs/editor/browser/controller/mouseHandler'; import { PointerHandler } from 'vs/editor/browser/controller/pointerHandler'; import { ITextAreaHandlerHelper, TextAreaHandler } from 'vs/editor/browser/controller/textAreaHandler'; -import { IContentWidget, IContentWidgetPosition, IOverlayWidget, IOverlayWidgetPosition, IMouseTarget, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser'; +import { IContentWidget, IContentWidgetPosition, IOverlayWidget, IOverlayWidgetPosition, IMouseTarget, IViewZoneChangeAccessor, IEditorAriaOptions } from 'vs/editor/browser/editorBrowser'; import { ICommandDelegate, ViewController } from 'vs/editor/browser/view/viewController'; import { ViewOutgoingEvents } from 'vs/editor/browser/view/viewOutgoingEvents'; import { ContentViewOverlays, MarginViewOverlays } from 'vs/editor/browser/view/viewOverlays'; @@ -510,6 +510,10 @@ export class View extends ViewEventHandler { this._textAreaHandler.refreshFocusState(); } + public setAria(options: IEditorAriaOptions): void { + this._textAreaHandler.setAria(options); + } + public addContentWidget(widgetData: IContentWidgetData): void { this.contentWidgets.addWidget(widgetData.widget); this.layoutContentWidget(widgetData); @@ -559,4 +563,3 @@ function safeInvokeNoArg(func: Function): any { onUnexpectedError(e); } } - diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 9c84e1a8062fe..d0257074276c9 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -1311,6 +1311,13 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._modelData.view.render(true, forceRedraw); } + public setAria(options: editorBrowser.IEditorAriaOptions): void { + if (!this._modelData || !this._modelData.hasRealView) { + return; + } + this._modelData.view.setAria(options); + } + public applyFontInfo(target: HTMLElement): void { Configuration.applyFontInfoSlow(target, this._configuration.options.get(EditorOption.fontInfo)); } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 27cece82f20de..a5510176fed68 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3726,6 +3726,13 @@ declare namespace monaco.editor { readonly target: IMouseTarget | null; } + /** + * Editor aria options. + */ + export interface IEditorAriaOptions { + activeDescendent: string | undefined; + } + /** * A rich code editor. */ From 623adc75e511ca54a93f28f6ff3c9eb83f9fb587 Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 30 Dec 2019 14:59:37 +0100 Subject: [PATCH 022/315] suggest: do not use aria alert, instead pass the active descendent to the editor --- .../editor/contrib/suggest/suggestWidget.ts | 60 +++++++------------ 1 file changed, 23 insertions(+), 37 deletions(-) diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index b3cf4a333ac35..781f20bb44c39 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -22,7 +22,6 @@ import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; import { Context as SuggestContext, CompletionItem } from './suggest'; import { CompletionModel } from './completionModel'; -import { alert } from 'vs/base/browser/ui/aria/aria'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { attachListStyler } from 'vs/platform/theme/common/styler'; import { IThemeService, ITheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; @@ -89,6 +88,10 @@ function canExpandCompletionItem(item: CompletionItem | null) { return (suggestion.detail && suggestion.detail !== suggestion.label); } +function getAriaId(index: number): string { + return `suggest-aria-id:${index}`; +} + class Renderer implements IListRenderer { constructor( @@ -162,6 +165,7 @@ class Renderer implements IListRenderer const data = templateData; const suggestion = (element).completion; + data.root.id = getAriaId(_index); data.icon.className = 'icon ' + completionKindToCssClass(suggestion.kind); data.colorspan.style.backgroundColor = ''; @@ -250,7 +254,6 @@ class SuggestionDetails { private header: HTMLElement; private type: HTMLElement; private docs: HTMLElement; - private ariaLabel: string | null; private readonly disposables: DisposableStore; private renderDisposeable?: IDisposable; private borderWidth: number = 1; @@ -279,7 +282,6 @@ class SuggestionDetails { this.type = append(this.header, $('p.type')); this.docs = append(this.body, $('p.docs')); - this.ariaLabel = null; this.configureFont(); @@ -318,7 +320,6 @@ class SuggestionDetails { this.type.textContent = ''; this.docs.textContent = ''; addClass(this.el, 'no-docs'); - this.ariaLabel = null; return; } removeClass(this.el, 'no-docs'); @@ -358,15 +359,6 @@ class SuggestionDetails { this.body.scrollTop = 0; this.scrollbar.scanDomNode(); - - this.ariaLabel = strings.format( - '{0}{1}', - detail || '', - documentation ? (typeof documentation === 'string' ? documentation : documentation.value) : ''); - } - - getAriaLabel() { - return this.ariaLabel; } scrollDown(much = 8): void { @@ -521,7 +513,22 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate false }, - mouseSupport: false + mouseSupport: false, + accessibilityProvider: { + getAriaLabel: (item: CompletionItem) => { + if (this.expandDocsSettingFromStorage()) { + const { documentation, detail } = item.completion; + const docs = strings.format( + '{0}{1}', + detail || '', + documentation ? (typeof documentation === 'string' ? documentation : documentation.value) : ''); + + return nls.localize('ariaCurrenttSuggestionReadDetails', "Item {0}, docs: {1}", item.completion.label, docs); + } else { + return item.completion.label; + } + } + } }); this.toDispose.add(attachListStyler(this.list, themeService, { @@ -610,25 +617,6 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate Date: Tue, 31 Dec 2019 12:20:23 +0100 Subject: [PATCH 023/315] suggest widget: _index should be index since it is used --- src/vs/editor/contrib/suggest/suggestWidget.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 781f20bb44c39..5d8670136b226 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -161,11 +161,11 @@ class Renderer implements IListRenderer return data; } - renderElement(element: CompletionItem, _index: number, templateData: ISuggestionTemplateData): void { + renderElement(element: CompletionItem, index: number, templateData: ISuggestionTemplateData): void { const data = templateData; const suggestion = (element).completion; - data.root.id = getAriaId(_index); + data.root.id = getAriaId(index); data.icon.className = 'icon ' + completionKindToCssClass(suggestion.kind); data.colorspan.style.backgroundColor = ''; From 5fc8d383e13dfb5cf5463f8c2fe12ef8cfeb9f8c Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 31 Dec 2019 15:22:49 +0100 Subject: [PATCH 024/315] completionItem isResolved --- src/vs/editor/contrib/suggest/suggest.ts | 7 ++++--- src/vs/editor/contrib/suggest/suggestWidget.ts | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/suggest/suggest.ts b/src/vs/editor/contrib/suggest/suggest.ts index d28423e1602f1..218ff2c70b4ca 100644 --- a/src/vs/editor/contrib/suggest/suggest.ts +++ b/src/vs/editor/contrib/suggest/suggest.ts @@ -44,6 +44,7 @@ export class CompletionItem { // sorting, filtering score: FuzzyScore = FuzzyScore.Default; distance: number = 0; + isResolved: boolean = false; idx?: number; word?: string; @@ -74,14 +75,14 @@ export class CompletionItem { const { resolveCompletionItem } = provider; if (typeof resolveCompletionItem !== 'function') { this.resolve = () => Promise.resolve(); + this.isResolved = true; } else { let cached: Promise | undefined; this.resolve = (token) => { if (!cached) { - let isDone = false; cached = Promise.resolve(resolveCompletionItem.call(provider, model, position, completion, token)).then(value => { assign(completion, value); - isDone = true; + this.isResolved = true; }, err => { if (isPromiseCanceledError(err)) { // the IPC queue will reject the request with the @@ -90,7 +91,7 @@ export class CompletionItem { } }); token.onCancellationRequested(() => { - if (!isDone) { + if (!this.isResolved) { // cancellation after the request has been // dispatched -> reset cache cached = undefined; diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 5d8670136b226..8a0b22cdb527b 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -516,7 +516,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate { - if (this.expandDocsSettingFromStorage()) { + if (item.isResolved && this.expandDocsSettingFromStorage()) { const { documentation, detail } = item.completion; const docs = strings.format( '{0}{1}', From c1a3ca9db9afaa04045a027af0d1e3645404e6d4 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Thu, 2 Jan 2020 13:14:15 -0800 Subject: [PATCH 025/315] Update diff icons to use codicons --- .../linesDecorations/linesDecorations.ts | 2 +- .../editor/browser/widget/diffEditorWidget.ts | 6 +-- .../editor/browser/widget/inlineDiffMargin.ts | 2 +- .../browser/widget/media/addition-dark.svg | 3 -- .../browser/widget/media/addition-light.svg | 3 -- .../browser/widget/media/close-dark.svg | 3 -- .../browser/widget/media/close-light.svg | 3 -- .../browser/widget/media/deletion-dark.svg | 3 -- .../browser/widget/media/deletion-light.svg | 3 -- .../browser/widget/media/diffEditor.css | 39 ++----------------- .../browser/widget/media/diffReview.css | 7 ---- .../browser/widget/media/lightbulb-dark.svg | 3 -- .../browser/widget/media/lightbulb-light.svg | 4 -- 13 files changed, 9 insertions(+), 72 deletions(-) delete mode 100644 src/vs/editor/browser/widget/media/addition-dark.svg delete mode 100644 src/vs/editor/browser/widget/media/addition-light.svg delete mode 100644 src/vs/editor/browser/widget/media/close-dark.svg delete mode 100644 src/vs/editor/browser/widget/media/close-light.svg delete mode 100644 src/vs/editor/browser/widget/media/deletion-dark.svg delete mode 100644 src/vs/editor/browser/widget/media/deletion-light.svg delete mode 100644 src/vs/editor/browser/widget/media/lightbulb-dark.svg delete mode 100644 src/vs/editor/browser/widget/media/lightbulb-light.svg diff --git a/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts b/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts index fc6a255eb8322..fb2970e763af0 100644 --- a/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts +++ b/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts @@ -97,7 +97,7 @@ export class LinesDecorationsOverlay extends DedupOverlay { const classNames = toRender[lineIndex]; let lineOutput = ''; for (let i = 0, len = classNames.length; i < len; i++) { - lineOutput += '
` + `
` ]); } } diff --git a/src/vs/editor/browser/widget/inlineDiffMargin.ts b/src/vs/editor/browser/widget/inlineDiffMargin.ts index 90bb8d9165a3d..6ad8f6c007dd0 100644 --- a/src/vs/editor/browser/widget/inlineDiffMargin.ts +++ b/src/vs/editor/browser/widget/inlineDiffMargin.ts @@ -57,7 +57,7 @@ export class InlineDiffMargin extends Disposable { this._marginDomNode.style.zIndex = '10'; this._diffActions = document.createElement('div'); - this._diffActions.className = 'lightbulb-glyph'; + this._diffActions.className = 'codicon codicon-lightbulb lightbulb-glyph'; this._diffActions.style.position = 'absolute'; const lineHeight = editor.getOption(EditorOption.lineHeight); const lineFeed = editor.getModel()!.getEOL(); diff --git a/src/vs/editor/browser/widget/media/addition-dark.svg b/src/vs/editor/browser/widget/media/addition-dark.svg deleted file mode 100644 index 4d9389336b93e..0000000000000 --- a/src/vs/editor/browser/widget/media/addition-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/browser/widget/media/addition-light.svg b/src/vs/editor/browser/widget/media/addition-light.svg deleted file mode 100644 index 01a9de7d5abc4..0000000000000 --- a/src/vs/editor/browser/widget/media/addition-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/browser/widget/media/close-dark.svg b/src/vs/editor/browser/widget/media/close-dark.svg deleted file mode 100644 index 6d16d212ae504..0000000000000 --- a/src/vs/editor/browser/widget/media/close-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/browser/widget/media/close-light.svg b/src/vs/editor/browser/widget/media/close-light.svg deleted file mode 100644 index 742fcae4ae7d6..0000000000000 --- a/src/vs/editor/browser/widget/media/close-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/browser/widget/media/deletion-dark.svg b/src/vs/editor/browser/widget/media/deletion-dark.svg deleted file mode 100644 index 4c5a9c1e3a5d2..0000000000000 --- a/src/vs/editor/browser/widget/media/deletion-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/browser/widget/media/deletion-light.svg b/src/vs/editor/browser/widget/media/deletion-light.svg deleted file mode 100644 index d12a8ee3135d2..0000000000000 --- a/src/vs/editor/browser/widget/media/deletion-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/browser/widget/media/diffEditor.css b/src/vs/editor/browser/widget/media/diffEditor.css index d1d0e48f366df..317916a107853 100644 --- a/src/vs/editor/browser/widget/media/diffEditor.css +++ b/src/vs/editor/browser/widget/media/diffEditor.css @@ -37,11 +37,10 @@ .monaco-diff-editor .insert-sign, .monaco-editor .delete-sign, .monaco-diff-editor .delete-sign { - background-size: 60%; - opacity: 0.7; - background-repeat: no-repeat; - background-position: 75% center; - background-size: 11px 11px; + font-size: 11px !important; + opacity: 0.7 !important; + display: flex !important; + align-items: center; } .monaco-editor.hc-black .insert-sign, .monaco-diff-editor.hc-black .insert-sign, @@ -49,27 +48,6 @@ .monaco-diff-editor.hc-black .delete-sign { opacity: 1; } -.monaco-editor .insert-sign, -.monaco-diff-editor .insert-sign { - background-image: url('addition-light.svg'); -} -.monaco-editor .delete-sign, -.monaco-diff-editor .delete-sign { - background-image: url('deletion-light.svg'); -} - -.monaco-editor.vs-dark .insert-sign, -.monaco-diff-editor.vs-dark .insert-sign, -.monaco-editor.hc-black .insert-sign, -.monaco-diff-editor.hc-black .insert-sign { - background-image: url('addition-dark.svg'); -} -.monaco-editor.vs-dark .delete-sign, -.monaco-diff-editor.vs-dark .delete-sign, -.monaco-editor.hc-black .delete-sign, -.monaco-diff-editor.hc-black .delete-sign { - background-image: url('deletion-dark.svg'); -} .monaco-editor .inline-deleted-margin-view-zone { text-align: right; @@ -94,15 +72,6 @@ display: inline-block; } -.monaco-editor .margin-view-zones .inline-deleted-margin-view-zone .lightbulb-glyph { - background: url('lightbulb-light.svg') center center no-repeat; -} - -.monaco-editor.vs-dark .margin-view-zones .inline-deleted-margin-view-zone .lightbulb-glyph, -.monaco-editor.hc-dark .margin-view-zones .inline-deleted-margin-view-zone .lightbulb-glyph { - background: url('lightbulb-dark.svg') center center no-repeat; -} - .monaco-editor .margin-view-zones .lightbulb-glyph:hover { cursor: pointer; } diff --git a/src/vs/editor/browser/widget/media/diffReview.css b/src/vs/editor/browser/widget/media/diffReview.css index f8db5bb1c67ef..b2b17028489ab 100644 --- a/src/vs/editor/browser/widget/media/diffReview.css +++ b/src/vs/editor/browser/widget/media/diffReview.css @@ -58,10 +58,3 @@ height: 16px; margin: 2px 0; } -.monaco-diff-editor .action-label.icon.close-diff-review { - background: url('close-light.svg') center center no-repeat; -} -.monaco-diff-editor.hc-black .action-label.icon.close-diff-review, -.monaco-diff-editor.vs-dark .action-label.icon.close-diff-review { - background: url('close-dark.svg') center center no-repeat; -} diff --git a/src/vs/editor/browser/widget/media/lightbulb-dark.svg b/src/vs/editor/browser/widget/media/lightbulb-dark.svg deleted file mode 100644 index 5fe8931a8159c..0000000000000 --- a/src/vs/editor/browser/widget/media/lightbulb-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/browser/widget/media/lightbulb-light.svg b/src/vs/editor/browser/widget/media/lightbulb-light.svg deleted file mode 100644 index 191c566fd2cb5..0000000000000 --- a/src/vs/editor/browser/widget/media/lightbulb-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - From 4553268ad94da3e854cd00dada7987206ad908e2 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Mon, 6 Jan 2020 17:50:43 +0100 Subject: [PATCH 026/315] Pass in the line mapper factory to the ViewModel --- .../editor/browser/widget/codeEditorWidget.ts | 3 ++- .../characterHardWrappingLineMapper.ts | 10 ++++++++- .../editor/common/viewModel/viewModelImpl.ts | 22 ++++++++----------- .../test/browser/commands/sideEditing.test.ts | 3 ++- .../test/browser/controller/cursor.test.ts | 15 +++++++------ .../controller/cursorMoveCommand.test.ts | 3 ++- .../test/common/viewModel/testViewModel.ts | 3 ++- 7 files changed, 34 insertions(+), 25 deletions(-) diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 9c84e1a8062fe..00d0d4ad3d500 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -50,6 +50,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; let EDITOR_ID = 0; @@ -1329,7 +1330,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE model.onBeforeAttached(); - const viewModel = new ViewModel(this._id, this._configuration, model, (callback) => dom.scheduleAtNextAnimationFrame(callback)); + const viewModel = new ViewModel(this._id, this._configuration, model, CharacterHardWrappingLineMapperFactory.create(this._configuration.options), (callback) => dom.scheduleAtNextAnimationFrame(callback)); listenersToRemove.push(model.onDidChangeDecorations((e) => this._onDidChangeModelDecorations.fire(e))); listenersToRemove.push(model.onDidChangeLanguage((e) => { diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index bbf71e817b93f..4b47666617c8f 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -5,7 +5,7 @@ import { CharCode } from 'vs/base/common/charCode'; import * as strings from 'vs/base/common/strings'; -import { WrappingIndent } from 'vs/editor/common/config/editorOptions'; +import { WrappingIndent, IComputedEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions'; import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier'; import { toUint32Array } from 'vs/base/common/uint'; import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; @@ -56,6 +56,14 @@ class WrappingCharacterClassifier extends CharacterClassifier { export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactory { + public static create(options: IComputedEditorOptions): CharacterHardWrappingLineMapperFactory { + return new CharacterHardWrappingLineMapperFactory( + options.get(EditorOption.wordWrapBreakBeforeCharacters), + options.get(EditorOption.wordWrapBreakAfterCharacters), + options.get(EditorOption.wordWrapBreakObtrusiveCharacters) + ); + } + private readonly classifier: WrappingCharacterClassifier; constructor(breakBeforeChars: string, breakAfterChars: string, breakObtrusiveChars: string) { diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index e08af64ebf9aa..d98148d5fab76 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -18,8 +18,7 @@ import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTokensColorTracker'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { ViewLayout } from 'vs/editor/common/viewLayout/viewLayout'; -import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; -import { IViewModelLinesCollection, IdentityLinesCollection, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection'; +import { IViewModelLinesCollection, IdentityLinesCollection, SplitLinesCollection, ILineMapperFactory } from 'vs/editor/common/viewModel/splitLinesCollection'; import { ICoordinatesConverter, IOverviewRulerDecorations, IViewModel, MinimapLinesRenderingData, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel'; import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations'; import { ITheme } from 'vs/platform/theme/common/themeService'; @@ -43,7 +42,13 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel public readonly viewLayout: ViewLayout; private readonly decorations: ViewModelDecorations; - constructor(editorId: number, configuration: editorCommon.IConfiguration, model: ITextModel, scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable) { + constructor( + editorId: number, + configuration: editorCommon.IConfiguration, + model: ITextModel, + lineMapperFactory: ILineMapperFactory, + scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable + ) { super(); this.editorId = editorId; @@ -63,20 +68,11 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel const options = this.configuration.options; const wrappingInfo = options.get(EditorOption.wrappingInfo); const fontInfo = options.get(EditorOption.fontInfo); - const wordWrapBreakAfterCharacters = options.get(EditorOption.wordWrapBreakAfterCharacters); - const wordWrapBreakBeforeCharacters = options.get(EditorOption.wordWrapBreakBeforeCharacters); - const wordWrapBreakObtrusiveCharacters = options.get(EditorOption.wordWrapBreakObtrusiveCharacters); const wrappingIndent = options.get(EditorOption.wrappingIndent); - let hardWrappingLineMapperFactory = new CharacterHardWrappingLineMapperFactory( - wordWrapBreakBeforeCharacters, - wordWrapBreakAfterCharacters, - wordWrapBreakObtrusiveCharacters - ); - this.lines = new SplitLinesCollection( this.model, - hardWrappingLineMapperFactory, + lineMapperFactory, this.model.getOptions().tabSize, wrappingInfo.wrappingColumn, fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth, diff --git a/src/vs/editor/test/browser/commands/sideEditing.test.ts b/src/vs/editor/test/browser/commands/sideEditing.test.ts index 250f2ff796092..205a169c495b2 100644 --- a/src/vs/editor/test/browser/commands/sideEditing.test.ts +++ b/src/vs/editor/test/browser/commands/sideEditing.test.ts @@ -14,6 +14,7 @@ import { TextModel } from 'vs/editor/common/model/textModel'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; +import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; function testCommand(lines: string[], selections: Selection[], edits: IIdentifiedSingleEditOperation[], expectedLines: string[], expectedSelections: Selection[]): void { withTestCodeEditor(lines, {}, (editor, cursor) => { @@ -200,7 +201,7 @@ suite('SideEditing', () => { function _runTest(selection: Selection, editRange: Range, editText: string, editForceMoveMarkers: boolean, expected: Selection, msg: string): void { const model = TextModel.createFromString(LINES.join('\n')); const config = new TestConfiguration({}); - const viewModel = new ViewModel(0, config, model, null!); + const viewModel = new ViewModel(0, config, model, CharacterHardWrappingLineMapperFactory.create(config.options), null!); const cursor = new Cursor(config, model, viewModel); cursor.setSelections('tests', [selection]); diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index d305b2e5ed8c7..9031f4056de64 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -25,6 +25,7 @@ import { IRelaxedTextModelCreationOptions, createTextModel } from 'vs/editor/tes import { MockMode } from 'vs/editor/test/common/mocks/mockMode'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules'; +import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; const H = Handler; @@ -152,7 +153,7 @@ suite('Editor Controller - Cursor', () => { thisModel = createTextModel(text); thisConfiguration = new TestConfiguration({}); - thisViewModel = new ViewModel(0, thisConfiguration, thisModel, null!); + thisViewModel = new ViewModel(0, thisConfiguration, thisModel, CharacterHardWrappingLineMapperFactory.create(thisConfiguration.options), null!); thisCursor = new Cursor(thisConfiguration, thisModel, thisViewModel); }); @@ -776,7 +777,7 @@ suite('Editor Controller - Cursor', () => { 'var newer = require("gulp-newer");', ].join('\n')); const config = new TestConfiguration({}); - const viewModel = new ViewModel(0, config, model, null!); + const viewModel = new ViewModel(0, config, model, CharacterHardWrappingLineMapperFactory.create(config.options), null!); const cursor = new Cursor(config, model, viewModel); moveTo(cursor, 1, 4, false); @@ -816,7 +817,7 @@ suite('Editor Controller - Cursor', () => { '', ].join('\n')); const config = new TestConfiguration({}); - const viewModel = new ViewModel(0, config, model, null!); + const viewModel = new ViewModel(0, config, model, CharacterHardWrappingLineMapperFactory.create(config.options), null!); const cursor = new Cursor(config, model, viewModel); moveTo(cursor, 10, 10, false); @@ -880,7 +881,7 @@ suite('Editor Controller - Cursor', () => { '', ].join('\n')); const config = new TestConfiguration({}); - const viewModel = new ViewModel(0, config, model, null!); + const viewModel = new ViewModel(0, config, model, CharacterHardWrappingLineMapperFactory.create(config.options), null!); const cursor = new Cursor(config, model, viewModel); moveTo(cursor, 10, 10, false); @@ -929,7 +930,7 @@ suite('Editor Controller - Cursor', () => { 'var newer = require("gulp-newer");', ].join('\n')); const config = new TestConfiguration({}); - const viewModel = new ViewModel(0, config, model, null!); + const viewModel = new ViewModel(0, config, model, CharacterHardWrappingLineMapperFactory.create(config.options), null!); const cursor = new Cursor(config, model, viewModel); moveTo(cursor, 1, 4, false); @@ -2074,7 +2075,7 @@ suite('Editor Controller - Regression tests', () => { wordWrap: 'wordWrapColumn', wordWrapColumn: 100 }); - const viewModel = new ViewModel(0, config, model, null!); + const viewModel = new ViewModel(0, config, model, CharacterHardWrappingLineMapperFactory.create(config.options), null!); const cursor = new Cursor(config, model, viewModel); moveTo(cursor, 1, 43, false); @@ -3834,7 +3835,7 @@ function usingCursor(opts: ICursorOpts, callback: (model: TextModel, cursor: Cur let model = createTextModel(opts.text.join('\n'), opts.modelOpts, opts.languageIdentifier); model.forceTokenization(model.getLineCount()); let config = new TestConfiguration(opts.editorOpts || {}); - let viewModel = new ViewModel(0, config, model, null!); + let viewModel = new ViewModel(0, config, model, CharacterHardWrappingLineMapperFactory.create(config.options), null!); let cursor = new Cursor(config, model, viewModel); callback(model, cursor); diff --git a/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts b/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts index a58db2d6c3fff..dad6b955cdaad 100644 --- a/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts +++ b/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts @@ -13,6 +13,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { TextModel } from 'vs/editor/common/model/textModel'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; +import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; suite('Cursor move command test', () => { @@ -32,7 +33,7 @@ suite('Cursor move command test', () => { thisModel = TextModel.createFromString(text); thisConfiguration = new TestConfiguration({}); - thisViewModel = new ViewModel(0, thisConfiguration, thisModel, null!); + thisViewModel = new ViewModel(0, thisConfiguration, thisModel, CharacterHardWrappingLineMapperFactory.create(thisConfiguration.options), null!); thisCursor = new Cursor(thisConfiguration, thisModel, thisViewModel); }); diff --git a/src/vs/editor/test/common/viewModel/testViewModel.ts b/src/vs/editor/test/common/viewModel/testViewModel.ts index 75b429dd095bc..6513d075b5ecc 100644 --- a/src/vs/editor/test/common/viewModel/testViewModel.ts +++ b/src/vs/editor/test/common/viewModel/testViewModel.ts @@ -7,6 +7,7 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { TextModel } from 'vs/editor/common/model/textModel'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; +import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; export function testViewModel(text: string[], options: IEditorOptions, callback: (viewModel: ViewModel, model: TextModel) => void): void { const EDITOR_ID = 1; @@ -15,7 +16,7 @@ export function testViewModel(text: string[], options: IEditorOptions, callback: let model = TextModel.createFromString(text.join('\n')); - let viewModel = new ViewModel(EDITOR_ID, configuration, model, null!); + let viewModel = new ViewModel(EDITOR_ID, configuration, model, CharacterHardWrappingLineMapperFactory.create(configuration.options), null!); callback(viewModel, model); From c9aeb8a49729d2b8c8259f4429edaf5d2f9bf4f1 Mon Sep 17 00:00:00 2001 From: Marko Novakovic Date: Fri, 3 Jan 2020 17:17:54 -0800 Subject: [PATCH 027/315] CallStackView attempts to position the active stack frame to the middle of the visible area --- src/vs/workbench/contrib/debug/browser/callStackView.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 4ae48c7e38ca2..2d7c2b31849a1 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -289,7 +289,13 @@ export class CallStackView extends ViewPane { this.ignoreSelectionChangedEvent = true; try { this.tree.setSelection([element]); - this.tree.reveal(element); + // If the element is outside of the screen bounds, + // position it in the middle + if (this.tree.getRelativeTop(element) === null) { + this.tree.reveal(element, 0.5); + } else { + this.tree.reveal(element); + } } catch (e) { } finally { this.ignoreSelectionChangedEvent = false; From 2c15f88cd55dffc7372d0b8ab5c9e910e78daa2d Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 7 Jan 2020 10:22:40 +0100 Subject: [PATCH 028/315] Have line mappings be computed in batches --- .../characterHardWrappingLineMapper.ts | 20 +++++- .../common/viewModel/splitLinesCollection.ts | 61 +++++++++++++------ .../editor/common/viewModel/viewModelImpl.ts | 32 ++++++++-- .../characterHardWrappingLineMapper.test.ts | 5 +- 4 files changed, 94 insertions(+), 24 deletions(-) diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index 4b47666617c8f..beea12ee74f92 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -9,7 +9,7 @@ import { WrappingIndent, IComputedEditorOptions, EditorOption } from 'vs/editor/ import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier'; import { toUint32Array } from 'vs/base/common/uint'; import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; -import { ILineMapperFactory, ILineMapping, OutputPosition } from 'vs/editor/common/viewModel/splitLinesCollection'; +import { ILineMapperFactory, ILineMapping, OutputPosition, ILineMappingComputer } from 'vs/editor/common/viewModel/splitLinesCollection'; const enum CharacterClass { NONE = 0, @@ -82,7 +82,23 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor return currentVisibleColumn + columnSize; } - public createLineMapping(lineText: string, tabSize: number, breakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): ILineMapping | null { + public createLineMappingComputer(tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ILineMappingComputer { + let requests: string[] = []; + return { + addRequest: (lineText: string) => { + requests.push(lineText); + }, + finalize: () => { + let result: (ILineMapping | null)[] = []; + for (let i = 0, len = requests.length; i < len; i++) { + result[i] = this._createLineMapping(requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent); + } + return result; + } + }; + } + + private _createLineMapping(lineText: string, tabSize: number, breakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): ILineMapping | null { if (breakingColumn === -1) { return null; } diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 3cd2afbafaf85..833aabad313b2 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -33,8 +33,13 @@ export interface ILineMapping { getOutputPositionOfInputOffset(inputOffset: number): OutputPosition; } +export interface ILineMappingComputer { + addRequest(lineText: string): void; + finalize(): (ILineMapping | null)[]; +} + export interface ILineMapperFactory { - createLineMapping(lineText: string, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ILineMapping | null; + createLineMappingComputer(tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ILineMappingComputer; } export interface ISimpleModel { @@ -71,10 +76,11 @@ export interface IViewModelLinesCollection extends IDisposable { getHiddenAreas(): Range[]; setHiddenAreas(_ranges: Range[]): boolean; + createLineMappingComputer(): ILineMappingComputer; onModelFlushed(): void; onModelLinesDeleted(versionId: number, fromLineNumber: number, toLineNumber: number): viewEvents.ViewLinesDeletedEvent | null; - onModelLinesInserted(versionId: number, fromLineNumber: number, toLineNumber: number, text: string[]): viewEvents.ViewLinesInsertedEvent | null; - onModelLineChanged(versionId: number, lineNumber: number, newText: string): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null]; + onModelLinesInserted(versionId: number, fromLineNumber: number, toLineNumber: number, linesMappings: (ILineMapping | null)[]): viewEvents.ViewLinesInsertedEvent | null; + onModelLineChanged(versionId: number, lineNumber: number, lineMapping: ILineMapping | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null]; acceptVersionId(versionId: number): void; getViewLineCount(): number; @@ -201,7 +207,13 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } let linesContent = this.model.getLinesContent(); - let lineCount = linesContent.length; + const lineCount = linesContent.length; + const lineMappingComputer = this.linePositionMapperFactory.createLineMappingComputer(this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent); + for (let i = 0; i < lineCount; i++) { + lineMappingComputer.addRequest(linesContent[i]); + } + const lineMappings = lineMappingComputer.finalize(); + let values = new Uint32Array(lineCount); let hiddenAreas = this.hiddenAreasIds.map((areaId) => this.model.getDecorationRange(areaId)!).sort(Range.compareRangesUsingStarts); @@ -220,7 +232,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } let isInHiddenArea = (lineNumber >= hiddenAreaStart && lineNumber <= hiddenAreaEnd); - let line = createSplitLine(this.linePositionMapperFactory, linesContent[i], this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent, !isInHiddenArea); + let line = createSplitLine(lineMappings[i], !isInHiddenArea); values[i] = line.getViewLineCount(); this.lines[i] = line; } @@ -370,6 +382,10 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return true; } + public createLineMappingComputer(): ILineMappingComputer { + return this.linePositionMapperFactory.createLineMappingComputer(this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent); + } + public onModelFlushed(): void { this._constructLines(true); } @@ -390,7 +406,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return new viewEvents.ViewLinesDeletedEvent(outputFromLineNumber, outputToLineNumber); } - public onModelLinesInserted(versionId: number, fromLineNumber: number, _toLineNumber: number, text: string[]): viewEvents.ViewLinesInsertedEvent | null { + public onModelLinesInserted(versionId: number, fromLineNumber: number, _toLineNumber: number, linesMappings: (ILineMapping | null)[]): viewEvents.ViewLinesInsertedEvent | null { if (versionId <= this._validModelVersionId) { // Here we check for versionId in case the lines were reconstructed in the meantime. // We don't want to apply stale change events on top of a newer read model state. @@ -411,10 +427,10 @@ export class SplitLinesCollection implements IViewModelLinesCollection { let totalOutputLineCount = 0; let insertLines: ISplitLine[] = []; - let insertPrefixSumValues = new Uint32Array(text.length); + let insertPrefixSumValues = new Uint32Array(linesMappings.length); - for (let i = 0, len = text.length; i < len; i++) { - let line = createSplitLine(this.linePositionMapperFactory, text[i], this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent, !isInHiddenArea); + for (let i = 0, len = linesMappings.length; i < len; i++) { + let line = createSplitLine(linesMappings[i], !isInHiddenArea); insertLines.push(line); let outputLineCount = line.getViewLineCount(); @@ -430,7 +446,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return new viewEvents.ViewLinesInsertedEvent(outputFromLineNumber, outputFromLineNumber + totalOutputLineCount - 1); } - public onModelLineChanged(versionId: number, lineNumber: number, newText: string): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] { + public onModelLineChanged(versionId: number, lineNumber: number, lineMapping: ILineMapping | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] { if (versionId <= this._validModelVersionId) { // Here we check for versionId in case the lines were reconstructed in the meantime. // We don't want to apply stale change events on top of a newer read model state. @@ -441,7 +457,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { let oldOutputLineCount = this.lines[lineIndex].getViewLineCount(); let isVisible = this.lines[lineIndex].isVisible(); - let line = createSplitLine(this.linePositionMapperFactory, newText, this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent, isVisible); + let line = createSplitLine(lineMapping, isVisible); this.lines[lineIndex] = line; let newOutputLineCount = this.lines[lineIndex].getViewLineCount(); @@ -1199,16 +1215,15 @@ export class SplitLine implements ISplitLine { } } -function createSplitLine(linePositionMapperFactory: ILineMapperFactory, text: string, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent, isVisible: boolean): ISplitLine { - let positionMapper = linePositionMapperFactory.createLineMapping(text, tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent); - if (positionMapper === null) { +function createSplitLine(lineMapping: ILineMapping | null, isVisible: boolean): ISplitLine { + if (lineMapping === null) { // No mapping needed if (isVisible) { return VisibleIdentitySplitLine.INSTANCE; } return InvisibleIdentitySplitLine.INSTANCE; } else { - return new SplitLine(positionMapper, isVisible); + return new SplitLine(lineMapping, isVisible); } } @@ -1298,6 +1313,18 @@ export class IdentityLinesCollection implements IViewModelLinesCollection { return false; } + public createLineMappingComputer(): ILineMappingComputer { + let result: null[] = []; + return { + addRequest: (lineText: string) => { + result.push(null); + }, + finalize: () => { + return result; + } + }; + } + public onModelFlushed(): void { } @@ -1305,11 +1332,11 @@ export class IdentityLinesCollection implements IViewModelLinesCollection { return new viewEvents.ViewLinesDeletedEvent(fromLineNumber, toLineNumber); } - public onModelLinesInserted(_versionId: number, fromLineNumber: number, toLineNumber: number, _text: string[]): viewEvents.ViewLinesInsertedEvent | null { + public onModelLinesInserted(_versionId: number, fromLineNumber: number, toLineNumber: number, linesMappings: (ILineMapping | null)[]): viewEvents.ViewLinesInsertedEvent | null { return new viewEvents.ViewLinesInsertedEvent(fromLineNumber, toLineNumber); } - public onModelLineChanged(_versionId: number, lineNumber: number, _newText: string): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] { + public onModelLineChanged(_versionId: number, lineNumber: number, lineMapping: ILineMapping | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] { return [false, new viewEvents.ViewLinesChangedEvent(lineNumber, lineNumber), null, null]; } diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index d98148d5fab76..3bb303641c781 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -196,8 +196,26 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel const changes = e.changes; const versionId = e.versionId; - for (let j = 0, lenJ = changes.length; j < lenJ; j++) { - const change = changes[j]; + // Do a first pass to compute line mappings, and a second pass to actually interpret them + const lineMappingComputer = this.lines.createLineMappingComputer(); + for (const change of changes) { + switch (change.changeType) { + case textModelEvents.RawContentChangedType.LinesInserted: { + for (const line of change.detail) { + lineMappingComputer.addRequest(line); + } + break; + } + case textModelEvents.RawContentChangedType.LineChanged: { + lineMappingComputer.addRequest(change.detail); + break; + } + } + } + const lineMappings = lineMappingComputer.finalize(); + let lineMappingsOffset = 0; + + for (const change of changes) { switch (change.changeType) { case textModelEvents.RawContentChangedType.Flush: { @@ -218,7 +236,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel break; } case textModelEvents.RawContentChangedType.LinesInserted: { - const linesInsertedEvent = this.lines.onModelLinesInserted(versionId, change.fromLineNumber, change.toLineNumber, change.detail); + const insertedLinesMappings = lineMappings.slice(lineMappingsOffset, lineMappingsOffset + change.detail.length); + lineMappingsOffset += change.detail.length; + + const linesInsertedEvent = this.lines.onModelLinesInserted(versionId, change.fromLineNumber, change.toLineNumber, insertedLinesMappings); if (linesInsertedEvent !== null) { eventsCollector.emit(linesInsertedEvent); this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber); @@ -227,7 +248,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel break; } case textModelEvents.RawContentChangedType.LineChanged: { - const [lineMappingChanged, linesChangedEvent, linesInsertedEvent, linesDeletedEvent] = this.lines.onModelLineChanged(versionId, change.lineNumber, change.detail); + const changedLineMapping = lineMappings[lineMappingsOffset]; + lineMappingsOffset++; + + const [lineMappingChanged, linesChangedEvent, linesInsertedEvent, linesDeletedEvent] = this.lines.onModelLineChanged(versionId, change.lineNumber, changedLineMapping); hadModelLineChangeThatChangedLineMapping = lineMappingChanged; if (linesChangedEvent) { eventsCollector.emit(linesChangedEvent); diff --git a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts index aec12368fa143..2c498980c8649 100644 --- a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts +++ b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts @@ -21,7 +21,10 @@ function assertLineMapping(factory: ILineMapperFactory, tabSize: number, breakAf } } - const mapper = factory.createLineMapping(rawText, tabSize, breakAfter, 2, wrappingIndent); + const lineMappingComputer = factory.createLineMappingComputer(tabSize, breakAfter, 2, wrappingIndent); + lineMappingComputer.addRequest(rawText); + const lineMappings = lineMappingComputer.finalize(); + const mapper = lineMappings[0]; // Insert line break markers again, according to algorithm let actualAnnotatedText = ''; From 47d359935ffc07779b4ebd1f5f24b10885bbae32 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 7 Jan 2020 10:28:45 +0100 Subject: [PATCH 029/315] Save a visibleRangeForPosition call if possible --- .../viewParts/indentGuides/indentGuides.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts b/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts index 6af61888a62b8..33979994f9dee 100644 --- a/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts +++ b/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts @@ -136,14 +136,16 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { const indent = indents[lineIndex]; let result = ''; - const leftMostVisiblePosition = ctx.visibleRangeForPosition(new Position(lineNumber, 1)); - let left = leftMostVisiblePosition ? leftMostVisiblePosition.left : 0; - for (let i = 1; i <= indent; i++) { - const className = (containsActiveIndentGuide && i === activeIndentLevel ? 'cigra' : 'cigr'); - result += `
`; - left += indentWidth; - if (left > scrollWidth || (this._maxIndentLeft > 0 && left > this._maxIndentLeft)) { - break; + if (indent >= 1) { + const leftMostVisiblePosition = ctx.visibleRangeForPosition(new Position(lineNumber, 1)); + let left = leftMostVisiblePosition ? leftMostVisiblePosition.left : 0; + for (let i = 1; i <= indent; i++) { + const className = (containsActiveIndentGuide && i === activeIndentLevel ? 'cigra' : 'cigr'); + result += `
`; + left += indentWidth; + if (left > scrollWidth || (this._maxIndentLeft > 0 && left > this._maxIndentLeft)) { + break; + } } } From 09d3bbf788cb2c09509dcf7152657e23a3a3307a Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 7 Jan 2020 10:59:21 +0100 Subject: [PATCH 030/315] Speed up the common case in WrappingCharacterClassifier (#21196) --- .../editor/common/core/characterClassifier.ts | 6 ++-- .../characterHardWrappingLineMapper.ts | 28 +++++++++++-------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/vs/editor/common/core/characterClassifier.ts b/src/vs/editor/common/core/characterClassifier.ts index 214bb2abf0533..e5aac27c816bb 100644 --- a/src/vs/editor/common/core/characterClassifier.ts +++ b/src/vs/editor/common/core/characterClassifier.ts @@ -12,14 +12,14 @@ export class CharacterClassifier { /** * Maintain a compact (fully initialized ASCII map for quickly classifying ASCII characters - used more often in code). */ - private _asciiMap: Uint8Array; + protected _asciiMap: Uint8Array; /** * The entire map (sparse array). */ - private _map: Map; + protected _map: Map; - private _defaultValue: number; + protected _defaultValue: number; constructor(_defaultValue: T) { let defaultValue = toUint8(_defaultValue); diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index beea12ee74f92..a9f91d025dcdf 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -38,19 +38,23 @@ class WrappingCharacterClassifier extends CharacterClassifier { } public get(charCode: number): CharacterClass { - // Initialize CharacterClass.BREAK_IDEOGRAPHIC for these Unicode ranges: - // 1. CJK Unified Ideographs (0x4E00 -- 0x9FFF) - // 2. CJK Unified Ideographs Extension A (0x3400 -- 0x4DBF) - // 3. Hiragana and Katakana (0x3040 -- 0x30FF) - if ( - (charCode >= 0x3040 && charCode <= 0x30FF) - || (charCode >= 0x3400 && charCode <= 0x4DBF) - || (charCode >= 0x4E00 && charCode <= 0x9FFF) - ) { - return CharacterClass.BREAK_IDEOGRAPHIC; - } + if (charCode >= 0 && charCode < 256) { + return this._asciiMap[charCode]; + } else { + // Initialize CharacterClass.BREAK_IDEOGRAPHIC for these Unicode ranges: + // 1. CJK Unified Ideographs (0x4E00 -- 0x9FFF) + // 2. CJK Unified Ideographs Extension A (0x3400 -- 0x4DBF) + // 3. Hiragana and Katakana (0x3040 -- 0x30FF) + if ( + (charCode >= 0x3040 && charCode <= 0x30FF) + || (charCode >= 0x3400 && charCode <= 0x4DBF) + || (charCode >= 0x4E00 && charCode <= 0x9FFF) + ) { + return CharacterClass.BREAK_IDEOGRAPHIC; + } - return super.get(charCode); + return (this._map.get(charCode) || this._defaultValue); + } } } From 57758d6750390feef59d8a863d930070813eb2bc Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 7 Jan 2020 11:15:40 +0100 Subject: [PATCH 031/315] Remove obtrusive wrapping characters since it was used only for dot (#21196) --- src/vs/editor/common/config/editorOptions.ts | 16 ++------- .../characterHardWrappingLineMapper.ts | 36 +++---------------- .../characterHardWrappingLineMapper.test.ts | 28 +++++++-------- .../viewModel/splitLinesCollection.test.ts | 8 ++--- src/vs/monaco.d.ts | 9 ++--- 5 files changed, 26 insertions(+), 71 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 3adac8cf46b92..94af6b10d1b55 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -264,19 +264,14 @@ export interface IEditorOptions { wrappingIndent?: 'none' | 'same' | 'indent' | 'deepIndent'; /** * Configure word wrapping characters. A break will be introduced before these characters. - * Defaults to '{([+'. + * Defaults to '([{‘“〈《「『【〔([{「£¥$£¥++'. */ wordWrapBreakBeforeCharacters?: string; /** * Configure word wrapping characters. A break will be introduced after these characters. - * Defaults to ' \t})]?|&,;'. + * Defaults to ' \t})]?|/&.,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」'. */ wordWrapBreakAfterCharacters?: string; - /** - * Configure word wrapping characters. A break will be introduced after these characters only if no `wordWrapBreakBeforeCharacters` or `wordWrapBreakAfterCharacters` were found. - * Defaults to '.'. - */ - wordWrapBreakObtrusiveCharacters?: string; /** * Performance guard: Stop rendering a line after x characters. * Defaults to 10000. @@ -3154,7 +3149,6 @@ export const enum EditorOption { wordWrap, wordWrapBreakAfterCharacters, wordWrapBreakBeforeCharacters, - wordWrapBreakObtrusiveCharacters, wordWrapColumn, wordWrapMinified, wrappingIndent, @@ -3681,16 +3675,12 @@ export const EditorOptions = { )), wordWrapBreakAfterCharacters: register(new EditorStringOption( EditorOption.wordWrapBreakAfterCharacters, 'wordWrapBreakAfterCharacters', - ' \t})]?|/&,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」', + ' \t})]?|/&.,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」', )), wordWrapBreakBeforeCharacters: register(new EditorStringOption( EditorOption.wordWrapBreakBeforeCharacters, 'wordWrapBreakBeforeCharacters', '([{‘“〈《「『【〔([{「£¥$£¥++' )), - wordWrapBreakObtrusiveCharacters: register(new EditorStringOption( - EditorOption.wordWrapBreakObtrusiveCharacters, 'wordWrapBreakObtrusiveCharacters', - '.' - )), wordWrapColumn: register(new EditorIntOption( EditorOption.wordWrapColumn, 'wordWrapColumn', 80, 1, Constants.MAX_SAFE_SMALL_INTEGER, diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index a9f91d025dcdf..6053822efa5d8 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -15,13 +15,12 @@ const enum CharacterClass { NONE = 0, BREAK_BEFORE = 1, BREAK_AFTER = 2, - BREAK_OBTRUSIVE = 3, - BREAK_IDEOGRAPHIC = 4 // for Han and Kana. + BREAK_IDEOGRAPHIC = 3 // for Han and Kana. } class WrappingCharacterClassifier extends CharacterClassifier { - constructor(BREAK_BEFORE: string, BREAK_AFTER: string, BREAK_OBTRUSIVE: string) { + constructor(BREAK_BEFORE: string, BREAK_AFTER: string) { super(CharacterClass.NONE); for (let i = 0; i < BREAK_BEFORE.length; i++) { @@ -31,10 +30,6 @@ class WrappingCharacterClassifier extends CharacterClassifier { for (let i = 0; i < BREAK_AFTER.length; i++) { this.set(BREAK_AFTER.charCodeAt(i), CharacterClass.BREAK_AFTER); } - - for (let i = 0; i < BREAK_OBTRUSIVE.length; i++) { - this.set(BREAK_OBTRUSIVE.charCodeAt(i), CharacterClass.BREAK_OBTRUSIVE); - } } public get(charCode: number): CharacterClass { @@ -63,15 +58,14 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor public static create(options: IComputedEditorOptions): CharacterHardWrappingLineMapperFactory { return new CharacterHardWrappingLineMapperFactory( options.get(EditorOption.wordWrapBreakBeforeCharacters), - options.get(EditorOption.wordWrapBreakAfterCharacters), - options.get(EditorOption.wordWrapBreakObtrusiveCharacters) + options.get(EditorOption.wordWrapBreakAfterCharacters) ); } private readonly classifier: WrappingCharacterClassifier; - constructor(breakBeforeChars: string, breakAfterChars: string, breakObtrusiveChars: string) { - this.classifier = new WrappingCharacterClassifier(breakBeforeChars, breakAfterChars, breakObtrusiveChars); + constructor(breakBeforeChars: string, breakAfterChars: string) { + this.classifier = new WrappingCharacterClassifier(breakBeforeChars, breakAfterChars); } // TODO@Alex -> duplicated in lineCommentCommand @@ -152,8 +146,6 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor let visibleColumn = 0; // Visible column since the beginning of the current line let niceBreakOffset = -1; // Last index of a character that indicates a break should happen before it (more desirable) let niceBreakVisibleColumn = 0; // visible column if a break were to be later introduced before `niceBreakOffset` - let obtrusiveBreakOffset = -1; // Last index of a character that indicates a break should happen before it (less desirable) - let obtrusiveBreakVisibleColumn = 0; // visible column if a break were to be later introduced before `obtrusiveBreakOffset` let len = lineText.length; for (let i = 0; i < len; i++) { @@ -212,12 +204,6 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor breakBeforeOffset = niceBreakOffset; restoreVisibleColumnFrom = niceBreakVisibleColumn; - } else if (obtrusiveBreakOffset !== -1 && obtrusiveBreakVisibleColumn <= breakingColumn) { - - // We will break before `obtrusiveBreakLastOffset` - breakBeforeOffset = obtrusiveBreakOffset; - restoreVisibleColumnFrom = obtrusiveBreakVisibleColumn; - } else { // We will break before `i` @@ -236,8 +222,6 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor // Reset markers niceBreakOffset = -1; niceBreakVisibleColumn = 0; - obtrusiveBreakOffset = -1; - obtrusiveBreakVisibleColumn = 0; } // At this point, there is a certainty that the character at `i` fits on the current line @@ -246,10 +230,6 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor // Advance niceBreakVisibleColumn niceBreakVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(niceBreakVisibleColumn, tabSize, charCodeIsTab, charColumnSize); } - if (obtrusiveBreakOffset !== -1) { - // Advance obtrusiveBreakVisibleColumn - obtrusiveBreakVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(obtrusiveBreakVisibleColumn, tabSize, charCodeIsTab, charColumnSize); - } if (charCodeClass === CharacterClass.BREAK_AFTER && (hardWrappingIndent === WrappingIndent.None || i >= firstNonWhitespaceIndex)) { // This is a character that indicates that a break should happen after it @@ -266,12 +246,6 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor niceBreakVisibleColumn = wrappedTextIndentVisibleColumn; } } - - if (charCodeClass === CharacterClass.BREAK_OBTRUSIVE) { - // This is an obtrusive character that indicates that a break should happen after it - obtrusiveBreakOffset = i + 1; - obtrusiveBreakVisibleColumn = wrappedTextIndentVisibleColumn; - } } if (breakingLengthsIndex === 0) { diff --git a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts index 2c498980c8649..4195ba5544eac 100644 --- a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts +++ b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts @@ -51,7 +51,7 @@ function assertLineMapping(factory: ILineMapperFactory, tabSize: number, breakAf suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { test('CharacterHardWrappingLineMapper', () => { - let factory = new CharacterHardWrappingLineMapperFactory('(', ')', '.'); + let factory = new CharacterHardWrappingLineMapperFactory('(', ').'); // Empty string assertLineMapping(factory, 4, 5, ''); @@ -64,7 +64,7 @@ suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { // Acts like hard wrapping if no char found assertLineMapping(factory, 4, 5, 'aaaaa|a'); - // Honors obtrusive wrapping character + // Honors wrapping character assertLineMapping(factory, 4, 5, 'aaaaa|.'); assertLineMapping(factory, 4, 5, 'aaaaa|a.|aaa.|aa'); assertLineMapping(factory, 4, 5, 'aaaaa|a..|aaa.|aa'); @@ -80,19 +80,19 @@ suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { // Honors wrapping before characters (& gives it priority) assertLineMapping(factory, 4, 5, 'aaa.|aa'); - assertLineMapping(factory, 4, 5, 'aaa|(.aa'); + assertLineMapping(factory, 4, 5, 'aaa(.|aa'); // Honors wrapping after characters (& gives it priority) assertLineMapping(factory, 4, 5, 'aaa))|).aaa'); - assertLineMapping(factory, 4, 5, 'aaa))|)|.aaaa'); - assertLineMapping(factory, 4, 5, 'aaa)|()|.aaa'); - assertLineMapping(factory, 4, 5, 'aaa(|()|.aaa'); - assertLineMapping(factory, 4, 5, 'aa.(|()|.aaa'); - assertLineMapping(factory, 4, 5, 'aa.|(.)|.aaa'); + assertLineMapping(factory, 4, 5, 'aaa))|).|aaaa'); + assertLineMapping(factory, 4, 5, 'aaa)|().|aaa'); + assertLineMapping(factory, 4, 5, 'aaa(|().|aaa'); + assertLineMapping(factory, 4, 5, 'aa.(|().|aaa'); + assertLineMapping(factory, 4, 5, 'aa.(.|).aaa'); }); test('CharacterHardWrappingLineMapper - CJK and Kinsoku Shori', () => { - let factory = new CharacterHardWrappingLineMapperFactory('(', ')', '.'); + let factory = new CharacterHardWrappingLineMapperFactory('(', ')'); assertLineMapping(factory, 4, 5, 'aa \u5b89|\u5b89'); assertLineMapping(factory, 4, 5, '\u3042 \u5b89|\u5b89'); assertLineMapping(factory, 4, 5, '\u3042\u3042|\u5b89\u5b89'); @@ -102,28 +102,28 @@ suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { }); test('CharacterHardWrappingLineMapper - WrappingIndent.Same', () => { - let factory = new CharacterHardWrappingLineMapperFactory('', ' ', ''); + let factory = new CharacterHardWrappingLineMapperFactory('', ' '); assertLineMapping(factory, 4, 38, ' *123456789012345678901234567890123456|7890', WrappingIndent.Same); }); test('issue #16332: Scroll bar overlaying on top of text', () => { - let factory = new CharacterHardWrappingLineMapperFactory('', ' ', ''); + let factory = new CharacterHardWrappingLineMapperFactory('', ' '); assertLineMapping(factory, 4, 24, 'a/ very/long/line/of/tex|t/that/expands/beyon|d/your/typical/line/|of/code/', WrappingIndent.Indent); }); test('issue #35162: wrappingIndent not consistently working', () => { - let factory = new CharacterHardWrappingLineMapperFactory('', ' ', ''); + let factory = new CharacterHardWrappingLineMapperFactory('', ' '); let mapper = assertLineMapping(factory, 4, 24, ' t h i s |i s |a l |o n |g l |i n |e', WrappingIndent.Indent); assert.equal(mapper!.getWrappedLinesIndent(), ' \t'); }); test('issue #75494: surrogate pairs', () => { - let factory = new CharacterHardWrappingLineMapperFactory('', ' ', ''); + let factory = new CharacterHardWrappingLineMapperFactory('', ' '); assertLineMapping(factory, 4, 49, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇|👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬', WrappingIndent.Same); }); test('CharacterHardWrappingLineMapper - WrappingIndent.DeepIndent', () => { - let factory = new CharacterHardWrappingLineMapperFactory('', ' ', ''); + let factory = new CharacterHardWrappingLineMapperFactory('', ' '); let mapper = assertLineMapping(factory, 4, 26, ' W e A r e T e s t |i n g D e |e p I n d |e n t a t |i o n', WrappingIndent.DeepIndent); assert.equal(mapper!.getWrappedLinesIndent(), ' \t\t'); }); diff --git a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts index a8b64c4da7a19..be941b900e185 100644 --- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts +++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts @@ -95,13 +95,11 @@ suite('Editor ViewModel - SplitLinesCollection', () => { const fontInfo = config.options.get(EditorOption.fontInfo); const wordWrapBreakAfterCharacters = config.options.get(EditorOption.wordWrapBreakAfterCharacters); const wordWrapBreakBeforeCharacters = config.options.get(EditorOption.wordWrapBreakBeforeCharacters); - const wordWrapBreakObtrusiveCharacters = config.options.get(EditorOption.wordWrapBreakObtrusiveCharacters); const wrappingIndent = config.options.get(EditorOption.wrappingIndent); const hardWrappingLineMapperFactory = new CharacterHardWrappingLineMapperFactory( wordWrapBreakBeforeCharacters, - wordWrapBreakAfterCharacters, - wordWrapBreakObtrusiveCharacters + wordWrapBreakAfterCharacters ); const model = TextModel.createFromString([ @@ -749,13 +747,11 @@ suite('SplitLinesCollection', () => { const fontInfo = configuration.options.get(EditorOption.fontInfo); const wordWrapBreakAfterCharacters = configuration.options.get(EditorOption.wordWrapBreakAfterCharacters); const wordWrapBreakBeforeCharacters = configuration.options.get(EditorOption.wordWrapBreakBeforeCharacters); - const wordWrapBreakObtrusiveCharacters = configuration.options.get(EditorOption.wordWrapBreakObtrusiveCharacters); const wrappingIndent = configuration.options.get(EditorOption.wrappingIndent); const factory = new CharacterHardWrappingLineMapperFactory( wordWrapBreakBeforeCharacters, - wordWrapBreakAfterCharacters, - wordWrapBreakObtrusiveCharacters + wordWrapBreakAfterCharacters ); const linesCollection = new SplitLinesCollection( diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 32a3f131d6bfe..9c9b413ede1ee 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2709,19 +2709,14 @@ declare namespace monaco.editor { wrappingIndent?: 'none' | 'same' | 'indent' | 'deepIndent'; /** * Configure word wrapping characters. A break will be introduced before these characters. - * Defaults to '{([+'. + * Defaults to '([{‘“〈《「『【〔([{「£¥$£¥++'. */ wordWrapBreakBeforeCharacters?: string; /** * Configure word wrapping characters. A break will be introduced after these characters. - * Defaults to ' \t})]?|&,;'. + * Defaults to ' \t})]?|/&.,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」'. */ wordWrapBreakAfterCharacters?: string; - /** - * Configure word wrapping characters. A break will be introduced after these characters only if no `wordWrapBreakBeforeCharacters` or `wordWrapBreakAfterCharacters` were found. - * Defaults to '.'. - */ - wordWrapBreakObtrusiveCharacters?: string; /** * Performance guard: Stop rendering a line after x characters. * Defaults to 10000. From ce3a96347a738753ac9be68bcfc17b315e5e9290 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 7 Jan 2020 11:23:17 +0100 Subject: [PATCH 032/315] :lipstick: --- .../characterHardWrappingLineMapper.ts | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index 6053822efa5d8..5e8265cf0b61b 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -139,22 +139,22 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor } } - let classifier = this.classifier; + const classifier = this.classifier; let lastBreakingOffset = 0; // Last 0-based offset in the lineText at which a break happened let breakingLengths: number[] = []; // The length of each broken-up line text let breakingLengthsIndex: number = 0; // The count of breaks already done let visibleColumn = 0; // Visible column since the beginning of the current line let niceBreakOffset = -1; // Last index of a character that indicates a break should happen before it (more desirable) let niceBreakVisibleColumn = 0; // visible column if a break were to be later introduced before `niceBreakOffset` - let len = lineText.length; + const len = lineText.length; for (let i = 0; i < len; i++) { // At this point, there is a certainty that the character before `i` fits on the current line, // but the character at `i` might not fit - let charCode = lineText.charCodeAt(i); - let charCodeIsTab = (charCode === CharCode.Tab); - let charCodeClass = classifier.get(charCode); + const charCode = lineText.charCodeAt(i); + const charCodeIsTab = (charCode === CharCode.Tab); + const charCodeClass = classifier.get(charCode); if (strings.isLowSurrogate(charCode)/* && i + 1 < len */) { // A surrogate pair must always be considered as a single unit, so it is never to be broken @@ -173,18 +173,14 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor // CJK breaking : before break if (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && i > 0) { - let prevCode = lineText.charCodeAt(i - 1); - let prevClass = classifier.get(prevCode); + const prevClass = classifier.get(lineText.charCodeAt(i - 1)); if (prevClass !== CharacterClass.BREAK_BEFORE) { // Kinsoku Shori: Don't break after a leading character, like an open bracket niceBreakOffset = i; niceBreakVisibleColumn = wrappedTextIndentVisibleColumn; } } - let charColumnSize = 1; - if (strings.isFullWidthCharacter(charCode)) { - charColumnSize = columnsForFullWidthChar; - } + const charColumnSize = strings.isFullWidthCharacter(charCode) ? columnsForFullWidthChar : 1; // Advance visibleColumn with character at `i` visibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(visibleColumn, tabSize, charCodeIsTab, charColumnSize); @@ -239,8 +235,7 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor // CJK breaking : after break if (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && i < len - 1) { - let nextCode = lineText.charCodeAt(i + 1); - let nextClass = classifier.get(nextCode); + const nextClass = classifier.get(lineText.charCodeAt(i + 1)); if (nextClass !== CharacterClass.BREAK_AFTER) { // Kinsoku Shori: Don't break before a trailing character, like a period niceBreakOffset = i + 1; niceBreakVisibleColumn = wrappedTextIndentVisibleColumn; From 881c21693d6c783e8cecd41265760d324c1965e3 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 7 Jan 2020 11:25:47 +0100 Subject: [PATCH 033/315] render debug labels --- .../contrib/bulkEdit/browser/bulkEditTree.ts | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index 7fee573a6dcfb..321eb64bf45fd 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -17,18 +17,25 @@ import * as dom from 'vs/base/browser/dom'; import { ITextModel } from 'vs/editor/common/model'; import { IDisposable } from 'vs/base/common/lifecycle'; import { TextModel } from 'vs/editor/common/model/textModel'; +import { ILabelService } from 'vs/platform/label/common/label'; // --- VIEW MODEL export class FileElement { - constructor(readonly edit: modes.ResourceFileEdit | modes.ResourceTextEdit) { } + readonly uri: URI; + readonly _debugName: string; - getUri(): URI { - return modes.isResourceTextEdit(this.edit) + constructor(readonly edit: modes.ResourceFileEdit | modes.ResourceTextEdit) { + this.uri = modes.isResourceTextEdit(this.edit) ? this.edit.resource : this.edit.oldUri || this.edit.newUri!; + + this._debugName = modes.isResourceTextEdit(this.edit) + ? 'text edit' + : this.edit.oldUri && this.edit.newUri ? 'rename' : this.edit.newUri ? 'create' : 'delete'; } + } export class TextEditElement { @@ -135,18 +142,32 @@ export class FileElementRenderer implements ITreeRenderer, _index: number, template: FileElementTemplate): void { - template.label.setFile(node.element.getUri(), { matches: createMatches(node.filterData) }); + + template.label.setResource({ + name: this._labelService.getUriLabel(node.element.uri, { relative: true }), + description: node.element._debugName, + resource: node.element.uri, + }, { + matches: createMatches(node.filterData), + }); } disposeTemplate(template: FileElementTemplate): void { From fd628855e1a1bccf3ee8266c767a6c4279705dce Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 7 Jan 2020 11:57:32 +0100 Subject: [PATCH 034/315] Fix \t as BREAK_AFTER in tests --- .../characterHardWrappingLineMapper.test.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts index 4195ba5544eac..ac4c75c2dae3f 100644 --- a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts +++ b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts @@ -51,7 +51,7 @@ function assertLineMapping(factory: ILineMapperFactory, tabSize: number, breakAf suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { test('CharacterHardWrappingLineMapper', () => { - let factory = new CharacterHardWrappingLineMapperFactory('(', ').'); + let factory = new CharacterHardWrappingLineMapperFactory('(', '\t).'); // Empty string assertLineMapping(factory, 4, 5, ''); @@ -73,10 +73,10 @@ suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { // Honors tabs when computing wrapping position assertLineMapping(factory, 4, 5, '\t'); - assertLineMapping(factory, 4, 5, '\ta|aa'); - assertLineMapping(factory, 4, 5, '\ta|\ta|a'); + assertLineMapping(factory, 4, 5, '\t|aaa'); + assertLineMapping(factory, 4, 5, '\t|a\t|aa'); assertLineMapping(factory, 4, 5, 'aa\ta'); - assertLineMapping(factory, 4, 5, 'aa\ta|a'); + assertLineMapping(factory, 4, 5, 'aa\t|aa'); // Honors wrapping before characters (& gives it priority) assertLineMapping(factory, 4, 5, 'aaa.|aa'); @@ -92,7 +92,7 @@ suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { }); test('CharacterHardWrappingLineMapper - CJK and Kinsoku Shori', () => { - let factory = new CharacterHardWrappingLineMapperFactory('(', ')'); + let factory = new CharacterHardWrappingLineMapperFactory('(', '\t)'); assertLineMapping(factory, 4, 5, 'aa \u5b89|\u5b89'); assertLineMapping(factory, 4, 5, '\u3042 \u5b89|\u5b89'); assertLineMapping(factory, 4, 5, '\u3042\u3042|\u5b89\u5b89'); @@ -102,28 +102,28 @@ suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { }); test('CharacterHardWrappingLineMapper - WrappingIndent.Same', () => { - let factory = new CharacterHardWrappingLineMapperFactory('', ' '); + let factory = new CharacterHardWrappingLineMapperFactory('', '\t '); assertLineMapping(factory, 4, 38, ' *123456789012345678901234567890123456|7890', WrappingIndent.Same); }); test('issue #16332: Scroll bar overlaying on top of text', () => { - let factory = new CharacterHardWrappingLineMapperFactory('', ' '); + let factory = new CharacterHardWrappingLineMapperFactory('', '\t '); assertLineMapping(factory, 4, 24, 'a/ very/long/line/of/tex|t/that/expands/beyon|d/your/typical/line/|of/code/', WrappingIndent.Indent); }); test('issue #35162: wrappingIndent not consistently working', () => { - let factory = new CharacterHardWrappingLineMapperFactory('', ' '); + let factory = new CharacterHardWrappingLineMapperFactory('', '\t '); let mapper = assertLineMapping(factory, 4, 24, ' t h i s |i s |a l |o n |g l |i n |e', WrappingIndent.Indent); assert.equal(mapper!.getWrappedLinesIndent(), ' \t'); }); test('issue #75494: surrogate pairs', () => { - let factory = new CharacterHardWrappingLineMapperFactory('', ' '); + let factory = new CharacterHardWrappingLineMapperFactory('\t', ' '); assertLineMapping(factory, 4, 49, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇|👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬', WrappingIndent.Same); }); test('CharacterHardWrappingLineMapper - WrappingIndent.DeepIndent', () => { - let factory = new CharacterHardWrappingLineMapperFactory('', ' '); + let factory = new CharacterHardWrappingLineMapperFactory('', '\t '); let mapper = assertLineMapping(factory, 4, 26, ' W e A r e T e s t |i n g D e |e p I n d |e n t a t |i o n', WrappingIndent.DeepIndent); assert.equal(mapper!.getWrappedLinesIndent(), ' \t\t'); }); From 2f90debe00823fdce8be1be040610affc20c5b70 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 7 Jan 2020 12:07:37 +0100 Subject: [PATCH 035/315] :lipstick: --- .../characterHardWrappingLineMapper.ts | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index 5e8265cf0b61b..fad43a06e26e2 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -153,16 +153,16 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor // but the character at `i` might not fit const charCode = lineText.charCodeAt(i); - const charCodeIsTab = (charCode === CharCode.Tab); - const charCodeClass = classifier.get(charCode); - - if (strings.isLowSurrogate(charCode)/* && i + 1 < len */) { + if (strings.isLowSurrogate(charCode)) { // A surrogate pair must always be considered as a single unit, so it is never to be broken // => advance visibleColumn by 1 and advance to next char code... visibleColumn = visibleColumn + 1; continue; } + const charCodeIsTab = (charCode === CharCode.Tab); + const charCodeClass = classifier.get(charCode); + if (charCodeClass === CharacterClass.BREAK_BEFORE) { // This is a character that indicates that a break should happen before it // Since we are certain the character before `i` fits, there's no extra checking needed, @@ -191,33 +191,27 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor // - otherwise, break before obtrusiveBreakLastOffset if it exists (and re-establish a correct visibleColumn by using obtrusiveBreakVisibleColumn + charAt(i)) // - otherwise, break before i (and re-establish a correct visibleColumn by charAt(i)) - let breakBeforeOffset: number; - let restoreVisibleColumnFrom: number; - if (niceBreakOffset !== -1 && niceBreakVisibleColumn <= breakingColumn) { // We will break before `niceBreakLastOffset` - breakBeforeOffset = niceBreakOffset; - restoreVisibleColumnFrom = niceBreakVisibleColumn; + breakingLengths[breakingLengthsIndex++] = niceBreakOffset - lastBreakingOffset; + lastBreakingOffset = niceBreakOffset; + visibleColumn = niceBreakVisibleColumn; } else { // We will break before `i` - breakBeforeOffset = i; - restoreVisibleColumnFrom = wrappedTextIndentVisibleColumn; + breakingLengths[breakingLengthsIndex++] = i - lastBreakingOffset; + lastBreakingOffset = i; + visibleColumn = wrappedTextIndentVisibleColumn; } - // Break before character at `breakBeforeOffset` - breakingLengths[breakingLengthsIndex++] = breakBeforeOffset - lastBreakingOffset; - lastBreakingOffset = breakBeforeOffset; - // Re-establish visibleColumn by taking character at `i` into account - visibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(restoreVisibleColumnFrom, tabSize, charCodeIsTab, charColumnSize); + visibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(visibleColumn, tabSize, charCodeIsTab, charColumnSize); // Reset markers niceBreakOffset = -1; - niceBreakVisibleColumn = 0; } // At this point, there is a certainty that the character at `i` fits on the current line From 9ddc82f5c6a250bcce97ec9f7e6990e05e9e6dd5 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 7 Jan 2020 12:12:27 +0100 Subject: [PATCH 036/315] Also advance niceBreakVisibleColumn in case of surrogate pairs --- .../characterHardWrappingLineMapper.ts | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index fad43a06e26e2..207082b55db4c 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -155,12 +155,17 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor const charCode = lineText.charCodeAt(i); if (strings.isLowSurrogate(charCode)) { // A surrogate pair must always be considered as a single unit, so it is never to be broken - // => advance visibleColumn by 1 and advance to next char code... + + // Advance visibleColumn visibleColumn = visibleColumn + 1; + + if (niceBreakOffset !== -1) { + // Advance niceBreakVisibleColumn + niceBreakVisibleColumn = niceBreakVisibleColumn + 1; + } continue; } - const charCodeIsTab = (charCode === CharCode.Tab); const charCodeClass = classifier.get(charCode); if (charCodeClass === CharacterClass.BREAK_BEFORE) { @@ -172,14 +177,13 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor } // CJK breaking : before break - if (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && i > 0) { - const prevClass = classifier.get(lineText.charCodeAt(i - 1)); - if (prevClass !== CharacterClass.BREAK_BEFORE) { // Kinsoku Shori: Don't break after a leading character, like an open bracket - niceBreakOffset = i; - niceBreakVisibleColumn = wrappedTextIndentVisibleColumn; - } + if (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && i > 0 && classifier.get(lineText.charCodeAt(i - 1)) !== CharacterClass.BREAK_BEFORE) { + // Kinsoku Shori: Don't break after a leading character, like an open bracket + niceBreakOffset = i; + niceBreakVisibleColumn = wrappedTextIndentVisibleColumn; } + const charCodeIsTab = (charCode === CharCode.Tab); const charColumnSize = strings.isFullWidthCharacter(charCode) ? columnsForFullWidthChar : 1; // Advance visibleColumn with character at `i` @@ -228,12 +232,10 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor } // CJK breaking : after break - if (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && i < len - 1) { - const nextClass = classifier.get(lineText.charCodeAt(i + 1)); - if (nextClass !== CharacterClass.BREAK_AFTER) { // Kinsoku Shori: Don't break before a trailing character, like a period - niceBreakOffset = i + 1; - niceBreakVisibleColumn = wrappedTextIndentVisibleColumn; - } + if (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && i + 1 < len && classifier.get(lineText.charCodeAt(i + 1)) !== CharacterClass.BREAK_AFTER) { + // Kinsoku Shori: Don't break before a trailing character, like a period + niceBreakOffset = i + 1; + niceBreakVisibleColumn = wrappedTextIndentVisibleColumn; } } From 13660367d4de905d7238e707f1cc6eaab082c992 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 7 Jan 2020 12:18:39 +0100 Subject: [PATCH 037/315] use BulkFileOperation model for tree and preview --- .../contrib/bulkEdit/browser/bulkEditPanel.ts | 23 ++-- .../bulkEdit/browser/bulkEditPreview.ts | 101 ++++++++++++++---- .../contrib/bulkEdit/browser/bulkEditTree.ts | 34 +++--- 3 files changed, 109 insertions(+), 49 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts index aab4b172409c4..e75ce385d300a 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts @@ -19,7 +19,7 @@ import { diffInserted, diffRemoved } from 'vs/platform/theme/common/colorRegistr import { localize } from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { BulkEditPreviewProvider } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; +import { BulkEditPreviewProvider, BulkFileOperations } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; import { ILabelService } from 'vs/platform/label/common/label'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { URI } from 'vs/base/common/uri'; @@ -32,9 +32,8 @@ const enum State { export class BulkEditPanel extends Panel { static readonly ID = 'BulkEditPanel'; - private static EmptyWorkspaceEdit = { edits: [] }; - private _tree!: WorkbenchAsyncDataTree; + private _tree!: WorkbenchAsyncDataTree; private _message!: HTMLSpanElement; private readonly _acceptAction = new Action('ok', localize('ok', "Apply Refactoring"), 'codicon-check', false, async () => this._done(true)); @@ -113,24 +112,26 @@ export class BulkEditPanel extends Panel { this.getContainer()!.dataset['state'] = state; } - setInput(edit: WorkspaceEdit): Promise { + async setInput(edit: WorkspaceEdit): Promise { this._setState(State.Data); this._sessionDisposables.clear(); - this._sessionDisposables.add(this._instaService.createInstance(BulkEditPreviewProvider, edit)); if (this._currentResolve) { this._currentResolve(false); this._currentResolve = undefined; } + this._sessionDisposables.add(this._instaService.createInstance(BulkEditPreviewProvider, edit)); + const input = await this._instaService.invokeFunction(BulkFileOperations.create, edit); + this._acceptAction.enabled = true; this._discardAction.enabled = true; return new Promise(async resolve => { this._currentResolve = resolve; - await this._tree.setInput(edit); + await this._tree.setInput(input); this._tree.domFocus(); this._tree.focusFirst(); @@ -148,7 +149,7 @@ export class BulkEditPanel extends Panel { this._currentResolve(accept); this._acceptAction.enabled = false; this._discardAction.enabled = false; - this._tree.setInput(BulkEditPanel.EmptyWorkspaceEdit); + this._tree.setInput(new BulkFileOperations([])); } } @@ -157,18 +158,18 @@ export class BulkEditPanel extends Panel { let leftResource: URI; try { - (await this._textModelService.createModelReference(edit.parent.resource)).dispose(); - leftResource = edit.parent.resource; + (await this._textModelService.createModelReference(edit.parent.uri)).dispose(); + leftResource = edit.parent.uri; } catch { leftResource = BulkEditPreviewProvider.emptyPreview; } - const previewUri = BulkEditPreviewProvider.asPreviewUri(edit.parent.resource); + const previewUri = BulkEditPreviewProvider.asPreviewUri(edit.parent.uri); this._editorService.openEditor({ leftResource, rightResource: previewUri, - label: localize('edt.title', "{0} (Refactor Preview)", this._labelService.getUriLabel(edit.parent.resource)), + label: localize('edt.title', "{0} (Refactor Preview)", this._labelService.getUriLabel(edit.parent.uri)), options: { selection: edit.edit.range // preserveFocus, diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts index 59695cdc2ec36..d656d7596aa6e 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts @@ -8,15 +8,95 @@ import { URI } from 'vs/base/common/uri'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; -import { WorkspaceEdit, isResourceTextEdit, isResourceFileEdit } from 'vs/editor/common/modes'; +import { WorkspaceEdit, isResourceTextEdit, isResourceFileEdit, TextEdit } from 'vs/editor/common/modes'; import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { mergeSort } from 'vs/base/common/arrays'; import { Range } from 'vs/editor/common/core/range'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import * as files from 'vs/platform/files/common/files'; import { Event, Emitter } from 'vs/base/common/event'; -import { TernarySearchTree } from 'vs/base/common/map'; +import { TernarySearchTree, values } from 'vs/base/common/map'; import { basename } from 'vs/base/common/resources'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; + +export const enum BulkFileOperationType { + None = 0, + Create = 0b0001, + Delete = 0b0010, + Rename = 0b0100, +} + +export class BulkFileOperation { + + type = BulkFileOperationType.None; + textEdits: TextEdit[] = []; + + constructor(readonly uri: URI) { } + + addType(type: BulkFileOperationType) { + this.type += type; + } + + addEdits(edits: TextEdit[]) { + this.textEdits = this.textEdits.concat(edits); + } +} + +export class BulkFileOperations { + + static async create(_accessor: ServicesAccessor, bulkEdit: WorkspaceEdit): Promise { + + const operationByResource = new Map(); + + for (const edit of bulkEdit.edits) { + + let uri: URI; + let type: BulkFileOperationType; + let textEdits: TextEdit[] | undefined; + + if (isResourceTextEdit(edit)) { + type = BulkFileOperationType.None; + uri = edit.resource; + textEdits = edit.edits; + + } else if (edit.newUri && edit.oldUri) { + type = BulkFileOperationType.Rename; + uri = edit.oldUri; + + } else if (edit.oldUri) { + type = BulkFileOperationType.Delete; + uri = edit.oldUri; + + } else if (edit.newUri) { + type = BulkFileOperationType.Create; + uri = edit.newUri; + + } else { + // invalid edit -> skip + continue; + } + + + const key = uri.toString(); + let operation = operationByResource.get(key); + if (!operation) { + operation = new BulkFileOperation(uri); + operationByResource.set(key, operation); + } + + operation.addType(type); + if (textEdits) { + operation.addEdits(textEdits); + } + } + + //todo@joh filter noops + + return new BulkFileOperations(values(operationByResource)); + } + + constructor(readonly fileOperations: BulkFileOperation[]) { } +} export class BulkEditPreviewProvider implements ITextModelContentProvider { @@ -107,7 +187,6 @@ export class BulkEditPreviewProvider implements ITextModelContentProvider { } } - export function asPreviewUri(uri: URI): URI { return URI.from({ scheme: 'vscode-bulkedit-preview', path: uri.path, query: uri.toString() }); } @@ -116,22 +195,6 @@ export function fromPreviewUri(uri: URI): URI { return URI.parse(uri.query); } -export function asPreviewEdit(edit: WorkspaceEdit): WorkspaceEdit { - const result: WorkspaceEdit = { edits: [] }; - for (let child of edit.edits) { - if (isResourceTextEdit(child)) { - result.edits.push({ ...child, resource: asPreviewUri(child.resource) }); - } else { - result.edits.push({ - oldUri: child.oldUri && asPreviewUri(child.oldUri), - newUri: child.newUri && asPreviewUri(child.newUri), - options: child.options - }); - } - } - return result; -} - class File implements files.IStat { type: files.FileType; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index 321eb64bf45fd..9b0eb46c031f0 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -18,6 +18,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { IDisposable } from 'vs/base/common/lifecycle'; import { TextModel } from 'vs/editor/common/model/textModel'; import { ILabelService } from 'vs/platform/label/common/label'; +import { BulkFileOperations, BulkFileOperation } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; // --- VIEW MODEL @@ -26,14 +27,9 @@ export class FileElement { readonly uri: URI; readonly _debugName: string; - constructor(readonly edit: modes.ResourceFileEdit | modes.ResourceTextEdit) { - this.uri = modes.isResourceTextEdit(this.edit) - ? this.edit.resource - : this.edit.oldUri || this.edit.newUri!; - - this._debugName = modes.isResourceTextEdit(this.edit) - ? 'text edit' - : this.edit.oldUri && this.edit.newUri ? 'rename' : this.edit.newUri ? 'create' : 'delete'; + constructor(readonly edit: BulkFileOperation) { + this.uri = edit.uri; + this._debugName = `0b${edit.type.toString(2)}`; } } @@ -41,7 +37,7 @@ export class FileElement { export class TextEditElement { constructor( - readonly parent: modes.ResourceTextEdit, + readonly parent: FileElement, readonly edit: modes.TextEdit, readonly prefix: string, readonly selecting: string, readonly inserting: string, readonly suffix: string ) { } @@ -51,13 +47,13 @@ export type Edit = FileElement | TextEditElement; // --- DATA SOURCE -export class BulkEditDataSource implements IAsyncDataSource { +export class BulkEditDataSource implements IAsyncDataSource { constructor(@ITextModelService private readonly _textModelService: ITextModelService) { } - hasChildren(element: modes.WorkspaceEdit | Edit): boolean { + hasChildren(element: BulkFileOperations | Edit): boolean { if (element instanceof FileElement) { - return modes.isResourceTextEdit(element.edit); + return element.edit.textEdits.length > 0; } if (element instanceof TextEditElement) { return false; @@ -65,20 +61,20 @@ export class BulkEditDataSource implements IAsyncDataSource { + async getChildren(element: BulkFileOperations | Edit): Promise { // root -> file/text edits - if (Array.isArray((element).edits)) { - return (element).edits.map(edit => new FileElement(edit)); + if (element instanceof BulkFileOperations) { + return element.fileOperations.map(op => new FileElement(op)); } // file: text edit - if (element instanceof FileElement && modes.isResourceTextEdit(element.edit)) { + if (element instanceof FileElement && element.edit.textEdits.length > 0) { // const previewUri = BulkEditPreviewProvider.asPreviewUri(element.edit.resource); let textModel: ITextModel; let textModelDisposable: IDisposable; try { - const ref = await this._textModelService.createModelReference(element.edit.resource); + const ref = await this._textModelService.createModelReference(element.edit.uri); textModel = ref.object.textEditorModel; textModelDisposable = ref; } catch { @@ -86,7 +82,7 @@ export class BulkEditDataSource implements IAsyncDataSource { + const result = element.edit.textEdits.map(edit => { const range = Range.lift(edit.range); const tokens = textModel.getLineTokens(range.endLineNumber); @@ -96,7 +92,7 @@ export class BulkEditDataSource implements IAsyncDataSourceelement.edit, + element, edit, textModel.getValueInRange(new Range(range.startLineNumber, 1, range.startLineNumber, range.startColumn)), // line start to edit start, textModel.getValueInRange(range), From 8c8e320238cc0cc8f84d68422eb9bb7dd46f7bd1 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 7 Jan 2020 12:44:25 +0100 Subject: [PATCH 038/315] simplify --- .../characterHardWrappingLineMapper.ts | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index 207082b55db4c..714d397d6d8ff 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -144,7 +144,7 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor let breakingLengths: number[] = []; // The length of each broken-up line text let breakingLengthsIndex: number = 0; // The count of breaks already done let visibleColumn = 0; // Visible column since the beginning of the current line - let niceBreakOffset = -1; // Last index of a character that indicates a break should happen before it (more desirable) + let niceBreakOffset = 0; // Last index of a character that indicates a break should happen before it (more desirable) let niceBreakVisibleColumn = 0; // visible column if a break were to be later introduced before `niceBreakOffset` const len = lineText.length; @@ -156,11 +156,9 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor if (strings.isLowSurrogate(charCode)) { // A surrogate pair must always be considered as a single unit, so it is never to be broken - // Advance visibleColumn + // Advance visibleColumn & niceBreakVisibleColumn visibleColumn = visibleColumn + 1; - - if (niceBreakOffset !== -1) { - // Advance niceBreakVisibleColumn + if (niceBreakOffset !== 0) { niceBreakVisibleColumn = niceBreakVisibleColumn + 1; } continue; @@ -168,21 +166,18 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor const charCodeClass = classifier.get(charCode); - if (charCodeClass === CharacterClass.BREAK_BEFORE) { + if ( + (charCodeClass === CharacterClass.BREAK_BEFORE) + || (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && i > 0 && classifier.get(lineText.charCodeAt(i - 1)) !== CharacterClass.BREAK_BEFORE) + ) { // This is a character that indicates that a break should happen before it + // (or) CJK breaking : before break : Kinsoku Shori : Don't break after a leading character, like an open bracket // Since we are certain the character before `i` fits, there's no extra checking needed, // just mark it as a nice breaking opportunity niceBreakOffset = i; niceBreakVisibleColumn = wrappedTextIndentVisibleColumn; } - // CJK breaking : before break - if (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && i > 0 && classifier.get(lineText.charCodeAt(i - 1)) !== CharacterClass.BREAK_BEFORE) { - // Kinsoku Shori: Don't break after a leading character, like an open bracket - niceBreakOffset = i; - niceBreakVisibleColumn = wrappedTextIndentVisibleColumn; - } - const charCodeIsTab = (charCode === CharCode.Tab); const charColumnSize = strings.isFullWidthCharacter(charCode) ? columnsForFullWidthChar : 1; @@ -195,7 +190,7 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor // - otherwise, break before obtrusiveBreakLastOffset if it exists (and re-establish a correct visibleColumn by using obtrusiveBreakVisibleColumn + charAt(i)) // - otherwise, break before i (and re-establish a correct visibleColumn by charAt(i)) - if (niceBreakOffset !== -1 && niceBreakVisibleColumn <= breakingColumn) { + if (niceBreakOffset !== 0 && niceBreakVisibleColumn <= breakingColumn) { // We will break before `niceBreakLastOffset` breakingLengths[breakingLengthsIndex++] = niceBreakOffset - lastBreakingOffset; @@ -215,25 +210,22 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor visibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(visibleColumn, tabSize, charCodeIsTab, charColumnSize); // Reset markers - niceBreakOffset = -1; + niceBreakOffset = 0; } // At this point, there is a certainty that the character at `i` fits on the current line - if (niceBreakOffset !== -1) { + if (niceBreakOffset !== 0) { // Advance niceBreakVisibleColumn niceBreakVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(niceBreakVisibleColumn, tabSize, charCodeIsTab, charColumnSize); } - if (charCodeClass === CharacterClass.BREAK_AFTER && (hardWrappingIndent === WrappingIndent.None || i >= firstNonWhitespaceIndex)) { + if ( + (charCodeClass === CharacterClass.BREAK_AFTER && (hardWrappingIndent === WrappingIndent.None || i >= firstNonWhitespaceIndex)) + || (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && i + 1 < len && classifier.get(lineText.charCodeAt(i + 1)) !== CharacterClass.BREAK_AFTER) + ) { // This is a character that indicates that a break should happen after it - niceBreakOffset = i + 1; - niceBreakVisibleColumn = wrappedTextIndentVisibleColumn; - } - - // CJK breaking : after break - if (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && i + 1 < len && classifier.get(lineText.charCodeAt(i + 1)) !== CharacterClass.BREAK_AFTER) { - // Kinsoku Shori: Don't break before a trailing character, like a period + // (or) CJK breaking : after break : Kinsoku Shori : Don't break before a trailing character, like a period niceBreakOffset = i + 1; niceBreakVisibleColumn = wrappedTextIndentVisibleColumn; } From d5a5421bbfd30c2d5a6b06e14808a68e74a85d93 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 7 Jan 2020 12:54:04 +0100 Subject: [PATCH 039/315] some cleanup --- .../bulkEdit/browser/bulkEditService.ts | 33 +++---------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts index 08390b52432f0..7d543daaaddb2 100644 --- a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts @@ -404,37 +404,14 @@ export class BulkEditService implements IBulkEditService { }); } - private static _mergeSequentialTextEditsOfSameResource(workspaceEdit: WorkspaceEdit): WorkspaceEdit { - let newEdit: WorkspaceEdit = { edits: [] }; - let last: ResourceTextEdit | undefined; - for (let edit of workspaceEdit.edits) { - if (!isResourceTextEdit(edit)) { - last = undefined; - newEdit.edits.push(edit); + async apply(edit: WorkspaceEdit, options?: IBulkEditOptions): Promise { - } else { - if (!last || last.resource.toString() !== edit.resource.toString()) { - last = edit; - newEdit.edits.push(last); - } else { - last.edits.push(...edit.edits); - last.modelVersionId = last.modelVersionId || edit.modelVersionId; - } - } - } - return newEdit; - } - - async apply(edit: WorkspaceEdit, options: IBulkEditOptions = {}): Promise { - - edit = BulkEditService._mergeSequentialTextEditsOfSameResource(edit); - - if (this._previewHandler && !options.noPreview) { + if (this._previewHandler && !options?.noPreview) { edit = await this._previewHandler(edit, options); } - let { edits } = edit; - let codeEditor = options.editor; + const { edits } = edit; + let codeEditor = options?.editor; // First check if loaded models were not changed in the meantime for (const edit of edits) { @@ -461,7 +438,7 @@ export class BulkEditService implements IBulkEditService { codeEditor = undefined; } const bulkEdit = new BulkEdit( - codeEditor, options.progress, edits, + codeEditor, options?.progress, edits, this._logService, this._textModelService, this._fileService, this._workerService, this._textFileService, this._labelService, this._configurationService ); return bulkEdit.perform().then(() => { From 3892a7d05fc83062046a3941a860e26428b1e938 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 7 Jan 2020 12:54:15 +0100 Subject: [PATCH 040/315] only preview rename and code actions --- src/vs/editor/contrib/rename/rename.ts | 20 +++++++++++-------- .../api/browser/mainThreadEditors.ts | 2 +- .../contrib/search/browser/replaceService.ts | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index 92fccb848b47a..f85e8d9d80e44 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -30,6 +30,7 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance import { DisposableStore } from 'vs/base/common/lifecycle'; import { IdleValue, raceCancellation } from 'vs/base/common/async'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { ILogService } from 'vs/platform/log/common/log'; class RenameSkeleton { @@ -114,6 +115,7 @@ class RenameController implements IEditorContribution { @IEditorProgressService private readonly _progressService: IEditorProgressService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IThemeService private readonly _themeService: IThemeService, + @ILogService private readonly _logService: ILogService, ) { this._renameInputField = this._dispoableStore.add(new IdleValue(() => this._dispoableStore.add(new RenameInputField(this.editor, this._themeService, this._contextKeyService)))); } @@ -197,16 +199,18 @@ class RenameController implements IEditorContribution { return; } - const editResult = await this._bulkEditService.apply(renameResult, { editor: this.editor }); - - // alert - if (editResult.ariaSummary) { - alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", loc!.text, newNameOrFocusFlag, editResult.ariaSummary)); - } + this._bulkEditService.apply(renameResult, { editor: this.editor }).then(result => { + if (result.ariaSummary) { + alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", loc!.text, newNameOrFocusFlag, result.ariaSummary)); + } + }).catch(err => { + this._notificationService.error(nls.localize('rename.failedApply', "Rename failed to apply edits")); + this._logService.error(err); + }); }, err => { - this._notificationService.error(nls.localize('rename.failed', "Rename failed to execute.")); - return Promise.reject(err); + this._notificationService.error(nls.localize('rename.failed', "Rename failed to compute edits")); + this._logService.error(err); }); this._progressService.showWhile(renameOperation, 250); diff --git a/src/vs/workbench/api/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index c4658d50a69f9..8ac224f6de089 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -217,7 +217,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { $tryApplyWorkspaceEdit(dto: IWorkspaceEditDto): Promise { const { edits } = reviveWorkspaceEditDto(dto); - return this._bulkEditService.apply({ edits }, undefined).then(() => true, err => false); + return this._bulkEditService.apply({ edits }, { noPreview: true }).then(() => true, err => false); } $tryInsertSnippet(id: string, template: string, ranges: readonly IRange[], opts: IUndoStopOptions): Promise { diff --git a/src/vs/workbench/contrib/search/browser/replaceService.ts b/src/vs/workbench/contrib/search/browser/replaceService.ts index bc7e14ab2e28f..92a9cf4b56b4c 100644 --- a/src/vs/workbench/contrib/search/browser/replaceService.ts +++ b/src/vs/workbench/contrib/search/browser/replaceService.ts @@ -105,7 +105,7 @@ export class ReplaceService implements IReplaceService { replace(match: FileMatchOrMatch, progress?: IProgress, resource?: URI): Promise; replace(arg: any, progress: IProgress | undefined = undefined, resource: URI | null = null): Promise { const edits: ResourceTextEdit[] = this.createEdits(arg, resource); - return this.bulkEditorService.apply({ edits }, { progress }).then(() => this.textFileService.saveAll(edits.map(e => e.resource))); + return this.bulkEditorService.apply({ edits }, { progress, noPreview: true }).then(() => this.textFileService.saveAll(edits.map(e => e.resource))); } openReplacePreview(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { From 1b573d75296630e87c914e778e6a67691b4a050c Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 7 Jan 2020 13:47:43 +0100 Subject: [PATCH 041/315] Avoid GCing (#21196) --- .../characterHardWrappingLineMapper.ts | 59 ++++++++++++------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index 714d397d6d8ff..937cddc0a991b 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -7,8 +7,6 @@ import { CharCode } from 'vs/base/common/charCode'; import * as strings from 'vs/base/common/strings'; import { WrappingIndent, IComputedEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions'; import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier'; -import { toUint32Array } from 'vs/base/common/uint'; -import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; import { ILineMapperFactory, ILineMapping, OutputPosition, ILineMappingComputer } from 'vs/editor/common/viewModel/splitLinesCollection'; const enum CharacterClass { @@ -140,9 +138,8 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor } const classifier = this.classifier; - let lastBreakingOffset = 0; // Last 0-based offset in the lineText at which a break happened - let breakingLengths: number[] = []; // The length of each broken-up line text - let breakingLengthsIndex: number = 0; // The count of breaks already done + let breakingOffsets: number[] = []; // The offset where a break occurs + let breakingOffsetsIndex: number = 0; // The count of breaks already done let visibleColumn = 0; // Visible column since the beginning of the current line let niceBreakOffset = 0; // Last index of a character that indicates a break should happen before it (more desirable) let niceBreakVisibleColumn = 0; // visible column if a break were to be later introduced before `niceBreakOffset` @@ -193,15 +190,13 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor if (niceBreakOffset !== 0 && niceBreakVisibleColumn <= breakingColumn) { // We will break before `niceBreakLastOffset` - breakingLengths[breakingLengthsIndex++] = niceBreakOffset - lastBreakingOffset; - lastBreakingOffset = niceBreakOffset; + breakingOffsets[breakingOffsetsIndex++] = niceBreakOffset; visibleColumn = niceBreakVisibleColumn; } else { // We will break before `i` - breakingLengths[breakingLengthsIndex++] = i - lastBreakingOffset; - lastBreakingOffset = i; + breakingOffsets[breakingOffsetsIndex++] = i; visibleColumn = wrappedTextIndentVisibleColumn; } @@ -231,32 +226,29 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor } } - if (breakingLengthsIndex === 0) { + if (breakingOffsetsIndex === 0) { return null; } // Add last segment - breakingLengths[breakingLengthsIndex++] = len - lastBreakingOffset; + breakingOffsets[breakingOffsetsIndex++] = len; - return new CharacterHardWrappingLineMapping( - new PrefixSumComputer(toUint32Array(breakingLengths)), - wrappedTextIndent - ); + return new CharacterHardWrappingLineMapping(breakingOffsets, wrappedTextIndent); } } export class CharacterHardWrappingLineMapping implements ILineMapping { - private readonly _prefixSums: PrefixSumComputer; + private readonly _breakingOffsets: number[]; private readonly _wrappedLinesIndent: string; - constructor(prefixSums: PrefixSumComputer, wrappedLinesIndent: string) { - this._prefixSums = prefixSums; + constructor(breakingOffsets: number[], wrappedLinesIndent: string) { + this._breakingOffsets = breakingOffsets; this._wrappedLinesIndent = wrappedLinesIndent; } public getOutputLineCount(): number { - return this._prefixSums.getCount(); + return this._breakingOffsets.length; } public getWrappedLinesIndent(): string { @@ -267,12 +259,35 @@ export class CharacterHardWrappingLineMapping implements ILineMapping { if (outputLineIndex === 0) { return outputOffset; } else { - return this._prefixSums.getAccumulatedValue(outputLineIndex - 1) + outputOffset; + return this._breakingOffsets[outputLineIndex - 1] + outputOffset; } } public getOutputPositionOfInputOffset(inputOffset: number): OutputPosition { - let r = this._prefixSums.getIndexOf(inputOffset); - return new OutputPosition(r.index, r.remainder); + inputOffset = inputOffset | 0; //@perf + const breakingOffsets = this._breakingOffsets; + + let low = 0; + let high = breakingOffsets.length - 1; + let mid = 0; + let midStop = 0; + let midStart = 0; + + while (low <= high) { + mid = low + ((high - low) / 2) | 0; + + midStop = breakingOffsets[mid]; + midStart = mid > 0 ? breakingOffsets[mid - 1] : 0; + + if (inputOffset < midStart) { + high = mid - 1; + } else if (inputOffset >= midStop) { + low = mid + 1; + } else { + break; + } + } + + return new OutputPosition(mid, inputOffset - midStart); } } From 3fbc4687b2569b1f14aad14dcede350d770851f0 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 7 Jan 2020 13:51:25 +0100 Subject: [PATCH 042/315] Fix compilation errors --- src/vs/base/common/uint.ts | 9 --------- .../test/common/viewModel/prefixSumComputer.test.ts | 11 ++++++++++- .../common/viewModel/splitLinesCollection.test.ts | 11 +++++------ 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/vs/base/common/uint.ts b/src/vs/base/common/uint.ts index b44e0fdaec7bd..347af57eec20d 100644 --- a/src/vs/base/common/uint.ts +++ b/src/vs/base/common/uint.ts @@ -57,12 +57,3 @@ export function toUint32(v: number): number { } return v | 0; } - -export function toUint32Array(arr: number[]): Uint32Array { - const len = arr.length; - const r = new Uint32Array(len); - for (let i = 0; i < len; i++) { - r[i] = toUint32(arr[i]); - } - return r; -} diff --git a/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts b/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts index 313c672b10165..65cc26efd4c12 100644 --- a/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts +++ b/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts @@ -4,9 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { toUint32Array } from 'vs/base/common/uint'; +import { toUint32 } from 'vs/base/common/uint'; import { PrefixSumComputer, PrefixSumIndexOfResult } from 'vs/editor/common/viewModel/prefixSumComputer'; +function toUint32Array(arr: number[]): Uint32Array { + const len = arr.length; + const r = new Uint32Array(len); + for (let i = 0; i < len; i++) { + r[i] = toUint32(arr[i]); + } + return r; +} + suite('Editor ViewModel - PrefixSumComputer', () => { test('PrefixSumComputer', () => { diff --git a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts index be941b900e185..833d0d7313b69 100644 --- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts +++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts @@ -9,13 +9,11 @@ import { IViewLineTokens } from 'vs/editor/common/core/lineTokens'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { TokenizationResult2 } from 'vs/editor/common/core/token'; -import { toUint32Array } from 'vs/base/common/uint'; import { EndOfLinePreference } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import * as modes from 'vs/editor/common/modes'; import { NULL_STATE } from 'vs/editor/common/modes/nullMode'; import { CharacterHardWrappingLineMapperFactory, CharacterHardWrappingLineMapping } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; -import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; import { ILineMapping, ISimpleModel, SplitLine, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection'; import { ViewLineData } from 'vs/editor/common/viewModel/viewModel'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; @@ -779,10 +777,11 @@ function createSplitLine(splitLengths: number[], wrappedLinesPrefix: string, isV } function createLineMapping(breakingLengths: number[], wrappedLinesPrefix: string): ILineMapping { - return new CharacterHardWrappingLineMapping( - new PrefixSumComputer(toUint32Array(breakingLengths)), - wrappedLinesPrefix - ); + let sums: number[] = []; + for (let i = 0; i < breakingLengths.length; i++) { + sums[i] = (i > 0 ? sums[i - 1] : 0) + breakingLengths[i]; + } + return new CharacterHardWrappingLineMapping(sums, wrappedLinesPrefix); } function createModel(text: string): ISimpleModel { From b8a1bd17bff98a76678b64c2d37bb100e909d684 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 7 Jan 2020 13:57:09 +0100 Subject: [PATCH 043/315] use file operations for preview provider, make sure preview provider keeps models alive --- .../contrib/bulkEdit/browser/bulkEditPanel.ts | 2 +- .../bulkEdit/browser/bulkEditPreview.ts | 32 ++++++++----------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts index e75ce385d300a..82a1eb77df162 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts @@ -122,8 +122,8 @@ export class BulkEditPanel extends Panel { this._currentResolve = undefined; } - this._sessionDisposables.add(this._instaService.createInstance(BulkEditPreviewProvider, edit)); const input = await this._instaService.invokeFunction(BulkFileOperations.create, edit); + this._sessionDisposables.add(this._instaService.createInstance(BulkEditPreviewProvider, input)); this._acceptAction.enabled = true; this._discardAction.enabled = true; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts index d656d7596aa6e..aee181049b0ff 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; -import { WorkspaceEdit, isResourceTextEdit, isResourceFileEdit, TextEdit } from 'vs/editor/common/modes'; +import { WorkspaceEdit, isResourceTextEdit, TextEdit } from 'vs/editor/common/modes'; import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { mergeSort } from 'vs/base/common/arrays'; import { Range } from 'vs/editor/common/core/range'; @@ -116,7 +116,7 @@ export class BulkEditPreviewProvider implements ITextModelContentProvider { private readonly _ready: Promise; constructor( - private readonly _edit: WorkspaceEdit, + private readonly _operations: BulkFileOperations, @IModeService private readonly _modeService: IModeService, @IModelService private readonly _modelService: IModelService, @ITextModelService private readonly _textModelResolverService: ITextModelService @@ -154,27 +154,21 @@ export class BulkEditPreviewProvider implements ITextModelContentProvider { previewUri ); } - this._disposables.add(model); } + // this is a little weird but otherwise editors and other cusomers + // will dispose my models before they should be disposed... + // And all of this is off the eventloop to prevent endless recursion + new Promise(async () => this._disposables.add(await this._textModelResolverService.createModelReference(model!.uri))); return model; }; - for (let edit of this._edit.edits) { - if (isResourceFileEdit(edit)) { - if (URI.isUri(edit.newUri)) { - await getOrCreatePreviewModel(edit.newUri); - } - if (URI.isUri(edit.oldUri)) { - await getOrCreatePreviewModel(edit.oldUri); - } - } else { - const model = await getOrCreatePreviewModel(edit.resource); - const editOperations = mergeSort( - edit.edits.map(edit => EditOperation.replaceMove(Range.lift(edit.range), edit.text)), - (a, b) => Range.compareRangesUsingStarts(a.range, b.range) - ); - model.applyEdits(editOperations); - } + for (let operation of this._operations.fileOperations) { + const model = await getOrCreatePreviewModel(operation.uri); + const editOperations = mergeSort( + operation.textEdits.map(edit => EditOperation.replaceMove(Range.lift(edit.range), edit.text)), + (a, b) => Range.compareRangesUsingStarts(a.range, b.range) + ); + model.applyEdits(editOperations); } } From 3cdfbd5fb19ede14a5793ec5c0b8f5eee090572b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 7 Jan 2020 14:32:16 +0100 Subject: [PATCH 044/315] (re)enable preview for vscode.workspace.applyWorkspaceEdit --- src/vs/workbench/api/browser/mainThreadEditors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index 8ac224f6de089..421201a9c64df 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -217,7 +217,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { $tryApplyWorkspaceEdit(dto: IWorkspaceEditDto): Promise { const { edits } = reviveWorkspaceEditDto(dto); - return this._bulkEditService.apply({ edits }, { noPreview: true }).then(() => true, err => false); + return this._bulkEditService.apply({ edits }, { noPreview: false }).then(() => true, err => false); } $tryInsertSnippet(id: string, template: string, ranges: readonly IRange[], opts: IUndoStopOptions): Promise { From faea8dc0be559420e477ce0b85402c2afa876172 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 7 Jan 2020 15:01:31 +0100 Subject: [PATCH 045/315] Reduce memory usage for SplitLines --- .../characterHardWrappingLineMapper.ts | 63 +---------- .../common/viewModel/splitLinesCollection.ts | 107 +++++++++++------- .../characterHardWrappingLineMapper.test.ts | 10 +- .../viewModel/splitLinesCollection.test.ts | 8 +- 4 files changed, 80 insertions(+), 108 deletions(-) diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index 937cddc0a991b..5f4bc7f1828cf 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -7,7 +7,7 @@ import { CharCode } from 'vs/base/common/charCode'; import * as strings from 'vs/base/common/strings'; import { WrappingIndent, IComputedEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions'; import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier'; -import { ILineMapperFactory, ILineMapping, OutputPosition, ILineMappingComputer } from 'vs/editor/common/viewModel/splitLinesCollection'; +import { ILineMapperFactory, LineBreakingData, ILineMappingComputer } from 'vs/editor/common/viewModel/splitLinesCollection'; const enum CharacterClass { NONE = 0, @@ -85,7 +85,7 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor requests.push(lineText); }, finalize: () => { - let result: (ILineMapping | null)[] = []; + let result: (LineBreakingData | null)[] = []; for (let i = 0, len = requests.length; i < len; i++) { result[i] = this._createLineMapping(requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent); } @@ -94,7 +94,7 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor }; } - private _createLineMapping(lineText: string, tabSize: number, breakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): ILineMapping | null { + private _createLineMapping(lineText: string, tabSize: number, breakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): LineBreakingData | null { if (breakingColumn === -1) { return null; } @@ -233,61 +233,6 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor // Add last segment breakingOffsets[breakingOffsetsIndex++] = len; - return new CharacterHardWrappingLineMapping(breakingOffsets, wrappedTextIndent); - } -} - -export class CharacterHardWrappingLineMapping implements ILineMapping { - - private readonly _breakingOffsets: number[]; - private readonly _wrappedLinesIndent: string; - - constructor(breakingOffsets: number[], wrappedLinesIndent: string) { - this._breakingOffsets = breakingOffsets; - this._wrappedLinesIndent = wrappedLinesIndent; - } - - public getOutputLineCount(): number { - return this._breakingOffsets.length; - } - - public getWrappedLinesIndent(): string { - return this._wrappedLinesIndent; - } - - public getInputOffsetOfOutputPosition(outputLineIndex: number, outputOffset: number): number { - if (outputLineIndex === 0) { - return outputOffset; - } else { - return this._breakingOffsets[outputLineIndex - 1] + outputOffset; - } - } - - public getOutputPositionOfInputOffset(inputOffset: number): OutputPosition { - inputOffset = inputOffset | 0; //@perf - const breakingOffsets = this._breakingOffsets; - - let low = 0; - let high = breakingOffsets.length - 1; - let mid = 0; - let midStop = 0; - let midStart = 0; - - while (low <= high) { - mid = low + ((high - low) / 2) | 0; - - midStop = breakingOffsets[mid]; - midStart = mid > 0 ? breakingOffsets[mid - 1] : 0; - - if (inputOffset < midStart) { - high = mid - 1; - } else if (inputOffset >= midStop) { - low = mid + 1; - } else { - break; - } - } - - return new OutputPosition(mid, inputOffset - midStart); + return new LineBreakingData(breakingOffsets, wrappedTextIndent); } } diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 833aabad313b2..1b64f46d59e6a 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -16,7 +16,6 @@ import { ITheme } from 'vs/platform/theme/common/themeService'; import { IDisposable } from 'vs/base/common/lifecycle'; export class OutputPosition { - _outputPositionBrand: void; outputLineIndex: number; outputOffset: number; @@ -26,16 +25,49 @@ export class OutputPosition { } } -export interface ILineMapping { - getOutputLineCount(): number; - getWrappedLinesIndent(): string; - getInputOffsetOfOutputPosition(outputLineIndex: number, outputOffset: number): number; - getOutputPositionOfInputOffset(inputOffset: number): OutputPosition; +export class LineBreakingData { + constructor( + public readonly breakOffsets: number[], + public readonly wrappedLinesIndent: string + ) { } + + public static getInputOffsetOfOutputPosition(breakOffsets: number[], outputLineIndex: number, outputOffset: number): number { + if (outputLineIndex === 0) { + return outputOffset; + } else { + return breakOffsets[outputLineIndex - 1] + outputOffset; + } + } + + public static getOutputPositionOfInputOffset(breakOffsets: number[], inputOffset: number): OutputPosition { + let low = 0; + let high = breakOffsets.length - 1; + let mid = 0; + let midStop = 0; + let midStart = 0; + + while (low <= high) { + mid = low + ((high - low) / 2) | 0; + + midStop = breakOffsets[mid]; + midStart = mid > 0 ? breakOffsets[mid - 1] : 0; + + if (inputOffset < midStart) { + high = mid - 1; + } else if (inputOffset >= midStop) { + low = mid + 1; + } else { + break; + } + } + + return new OutputPosition(mid, inputOffset - midStart); + } } export interface ILineMappingComputer { addRequest(lineText: string): void; - finalize(): (ILineMapping | null)[]; + finalize(): (LineBreakingData | null)[]; } export interface ILineMapperFactory { @@ -79,8 +111,8 @@ export interface IViewModelLinesCollection extends IDisposable { createLineMappingComputer(): ILineMappingComputer; onModelFlushed(): void; onModelLinesDeleted(versionId: number, fromLineNumber: number, toLineNumber: number): viewEvents.ViewLinesDeletedEvent | null; - onModelLinesInserted(versionId: number, fromLineNumber: number, toLineNumber: number, linesMappings: (ILineMapping | null)[]): viewEvents.ViewLinesInsertedEvent | null; - onModelLineChanged(versionId: number, lineNumber: number, lineMapping: ILineMapping | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null]; + onModelLinesInserted(versionId: number, fromLineNumber: number, toLineNumber: number, linesMappings: (LineBreakingData | null)[]): viewEvents.ViewLinesInsertedEvent | null; + onModelLineChanged(versionId: number, lineNumber: number, lineMapping: LineBreakingData | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null]; acceptVersionId(versionId: number): void; getViewLineCount(): number; @@ -406,7 +438,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return new viewEvents.ViewLinesDeletedEvent(outputFromLineNumber, outputToLineNumber); } - public onModelLinesInserted(versionId: number, fromLineNumber: number, _toLineNumber: number, linesMappings: (ILineMapping | null)[]): viewEvents.ViewLinesInsertedEvent | null { + public onModelLinesInserted(versionId: number, fromLineNumber: number, _toLineNumber: number, linesMappings: (LineBreakingData | null)[]): viewEvents.ViewLinesInsertedEvent | null { if (versionId <= this._validModelVersionId) { // Here we check for versionId in case the lines were reconstructed in the meantime. // We don't want to apply stale change events on top of a newer read model state. @@ -446,7 +478,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return new viewEvents.ViewLinesInsertedEvent(outputFromLineNumber, outputFromLineNumber + totalOutputLineCount - 1); } - public onModelLineChanged(versionId: number, lineNumber: number, lineMapping: ILineMapping | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] { + public onModelLineChanged(versionId: number, lineNumber: number, lineMapping: LineBreakingData | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] { if (versionId <= this._validModelVersionId) { // Here we check for versionId in case the lines were reconstructed in the meantime. // We don't want to apply stale change events on top of a newer read model state. @@ -1027,18 +1059,13 @@ class InvisibleIdentitySplitLine implements ISplitLine { export class SplitLine implements ISplitLine { - private readonly positionMapper: ILineMapping; - private readonly outputLineCount: number; - - private readonly wrappedIndent: string; - private readonly wrappedIndentLength: number; + private readonly _breakOffsets: number[]; + private readonly _wrappedIndent: string; private _isVisible: boolean; - constructor(positionMapper: ILineMapping, isVisible: boolean) { - this.positionMapper = positionMapper; - this.wrappedIndent = this.positionMapper.getWrappedLinesIndent(); - this.wrappedIndentLength = this.wrappedIndent.length; - this.outputLineCount = this.positionMapper.getOutputLineCount(); + constructor(lineBreaking: LineBreakingData, isVisible: boolean) { + this._breakOffsets = lineBreaking.breakOffsets; + this._wrappedIndent = lineBreaking.wrappedLinesIndent; this._isVisible = isVisible; } @@ -1055,18 +1082,18 @@ export class SplitLine implements ISplitLine { if (!this._isVisible) { return 0; } - return this.outputLineCount; + return this._breakOffsets.length; } private getInputStartOffsetOfOutputLineIndex(outputLineIndex: number): number { - return this.positionMapper.getInputOffsetOfOutputPosition(outputLineIndex, 0); + return LineBreakingData.getInputOffsetOfOutputPosition(this._breakOffsets, outputLineIndex, 0); } private getInputEndOffsetOfOutputLineIndex(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number { - if (outputLineIndex + 1 === this.outputLineCount) { + if (outputLineIndex + 1 === this._breakOffsets.length) { return model.getLineMaxColumn(modelLineNumber) - 1; } - return this.positionMapper.getInputOffsetOfOutputPosition(outputLineIndex + 1, 0); + return LineBreakingData.getInputOffsetOfOutputPosition(this._breakOffsets, outputLineIndex + 1, 0); } public getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string { @@ -1083,7 +1110,7 @@ export class SplitLine implements ISplitLine { }); if (outputLineIndex > 0) { - r = this.wrappedIndent + r; + r = this._wrappedIndent + r; } return r; @@ -1098,7 +1125,7 @@ export class SplitLine implements ISplitLine { let r = endOffset - startOffset; if (outputLineIndex > 0) { - r = this.wrappedIndent.length + r; + r = this._wrappedIndent.length + r; } return r; @@ -1109,7 +1136,7 @@ export class SplitLine implements ISplitLine { throw new Error('Not supported'); } if (outputLineIndex > 0) { - return this.wrappedIndentLength + 1; + return this._wrappedIndent.length + 1; } return 1; } @@ -1137,17 +1164,17 @@ export class SplitLine implements ISplitLine { }); if (outputLineIndex > 0) { - lineContent = this.wrappedIndent + lineContent; + lineContent = this._wrappedIndent + lineContent; } - let minColumn = (outputLineIndex > 0 ? this.wrappedIndentLength + 1 : 1); + let minColumn = (outputLineIndex > 0 ? this._wrappedIndent.length + 1 : 1); let maxColumn = lineContent.length + 1; let continuesWithWrappedLine = (outputLineIndex + 1 < this.getViewLineCount()); let deltaStartIndex = 0; if (outputLineIndex > 0) { - deltaStartIndex = this.wrappedIndentLength; + deltaStartIndex = this._wrappedIndent.length; } let lineTokens = model.getLineTokens(modelLineNumber); @@ -1181,25 +1208,25 @@ export class SplitLine implements ISplitLine { } let adjustedColumn = outputColumn - 1; if (outputLineIndex > 0) { - if (adjustedColumn < this.wrappedIndentLength) { + if (adjustedColumn < this._wrappedIndent.length) { adjustedColumn = 0; } else { - adjustedColumn -= this.wrappedIndentLength; + adjustedColumn -= this._wrappedIndent.length; } } - return this.positionMapper.getInputOffsetOfOutputPosition(outputLineIndex, adjustedColumn) + 1; + return LineBreakingData.getInputOffsetOfOutputPosition(this._breakOffsets, outputLineIndex, adjustedColumn) + 1; } public getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number): Position { if (!this._isVisible) { throw new Error('Not supported'); } - let r = this.positionMapper.getOutputPositionOfInputOffset(inputColumn - 1); + let r = LineBreakingData.getOutputPositionOfInputOffset(this._breakOffsets, inputColumn - 1); let outputLineIndex = r.outputLineIndex; let outputColumn = r.outputOffset + 1; if (outputLineIndex > 0) { - outputColumn += this.wrappedIndentLength; + outputColumn += this._wrappedIndent.length; } // console.log('in -> out ' + deltaLineNumber + ',' + inputColumn + ' ===> ' + (deltaLineNumber+outputLineIndex) + ',' + outputColumn); @@ -1210,12 +1237,12 @@ export class SplitLine implements ISplitLine { if (!this._isVisible) { throw new Error('Not supported'); } - const r = this.positionMapper.getOutputPositionOfInputOffset(inputColumn - 1); + const r = LineBreakingData.getOutputPositionOfInputOffset(this._breakOffsets, inputColumn - 1); return (deltaLineNumber + r.outputLineIndex); } } -function createSplitLine(lineMapping: ILineMapping | null, isVisible: boolean): ISplitLine { +function createSplitLine(lineMapping: LineBreakingData | null, isVisible: boolean): ISplitLine { if (lineMapping === null) { // No mapping needed if (isVisible) { @@ -1332,11 +1359,11 @@ export class IdentityLinesCollection implements IViewModelLinesCollection { return new viewEvents.ViewLinesDeletedEvent(fromLineNumber, toLineNumber); } - public onModelLinesInserted(_versionId: number, fromLineNumber: number, toLineNumber: number, linesMappings: (ILineMapping | null)[]): viewEvents.ViewLinesInsertedEvent | null { + public onModelLinesInserted(_versionId: number, fromLineNumber: number, toLineNumber: number, linesMappings: (LineBreakingData | null)[]): viewEvents.ViewLinesInsertedEvent | null { return new viewEvents.ViewLinesInsertedEvent(fromLineNumber, toLineNumber); } - public onModelLineChanged(_versionId: number, lineNumber: number, lineMapping: ILineMapping | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] { + public onModelLineChanged(_versionId: number, lineNumber: number, lineMapping: LineBreakingData | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] { return [false, new viewEvents.ViewLinesChangedEvent(lineNumber, lineNumber), null, null]; } diff --git a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts index ac4c75c2dae3f..88c4fe05bc52b 100644 --- a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts +++ b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts @@ -5,9 +5,9 @@ import * as assert from 'assert'; import { WrappingIndent } from 'vs/editor/common/config/editorOptions'; import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; -import { ILineMapperFactory, ILineMapping } from 'vs/editor/common/viewModel/splitLinesCollection'; +import { ILineMapperFactory, LineBreakingData } from 'vs/editor/common/viewModel/splitLinesCollection'; -function assertLineMapping(factory: ILineMapperFactory, tabSize: number, breakAfter: number, annotatedText: string, wrappingIndent = WrappingIndent.None): ILineMapping | null { +function assertLineMapping(factory: ILineMapperFactory, tabSize: number, breakAfter: number, annotatedText: string, wrappingIndent = WrappingIndent.None): LineBreakingData | null { // Create version of `annotatedText` with line break markers removed let rawText = ''; let currentLineIndex = 0; @@ -31,7 +31,7 @@ function assertLineMapping(factory: ILineMapperFactory, tabSize: number, breakAf if (mapper) { let previousLineIndex = 0; for (let i = 0, len = rawText.length; i < len; i++) { - let r = mapper.getOutputPositionOfInputOffset(i); + let r = LineBreakingData.getOutputPositionOfInputOffset(mapper.breakOffsets, i); if (previousLineIndex !== r.outputLineIndex) { previousLineIndex = r.outputLineIndex; actualAnnotatedText += '|'; @@ -114,7 +114,7 @@ suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { test('issue #35162: wrappingIndent not consistently working', () => { let factory = new CharacterHardWrappingLineMapperFactory('', '\t '); let mapper = assertLineMapping(factory, 4, 24, ' t h i s |i s |a l |o n |g l |i n |e', WrappingIndent.Indent); - assert.equal(mapper!.getWrappedLinesIndent(), ' \t'); + assert.equal(mapper!.wrappedLinesIndent, ' \t'); }); test('issue #75494: surrogate pairs', () => { @@ -125,6 +125,6 @@ suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { test('CharacterHardWrappingLineMapper - WrappingIndent.DeepIndent', () => { let factory = new CharacterHardWrappingLineMapperFactory('', '\t '); let mapper = assertLineMapping(factory, 4, 26, ' W e A r e T e s t |i n g D e |e p I n d |e n t a t |i o n', WrappingIndent.DeepIndent); - assert.equal(mapper!.getWrappedLinesIndent(), ' \t\t'); + assert.equal(mapper!.wrappedLinesIndent, ' \t\t'); }); }); diff --git a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts index 833d0d7313b69..52dc680d78a6d 100644 --- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts +++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts @@ -13,8 +13,8 @@ import { EndOfLinePreference } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import * as modes from 'vs/editor/common/modes'; import { NULL_STATE } from 'vs/editor/common/modes/nullMode'; -import { CharacterHardWrappingLineMapperFactory, CharacterHardWrappingLineMapping } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; -import { ILineMapping, ISimpleModel, SplitLine, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection'; +import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; +import { LineBreakingData, ISimpleModel, SplitLine, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection'; import { ViewLineData } from 'vs/editor/common/viewModel/viewModel'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -776,12 +776,12 @@ function createSplitLine(splitLengths: number[], wrappedLinesPrefix: string, isV return new SplitLine(createLineMapping(splitLengths, wrappedLinesPrefix), isVisible); } -function createLineMapping(breakingLengths: number[], wrappedLinesPrefix: string): ILineMapping { +function createLineMapping(breakingLengths: number[], wrappedLinesPrefix: string): LineBreakingData { let sums: number[] = []; for (let i = 0; i < breakingLengths.length; i++) { sums[i] = (i > 0 ? sums[i - 1] : 0) + breakingLengths[i]; } - return new CharacterHardWrappingLineMapping(sums, wrappedLinesPrefix); + return new LineBreakingData(sums, wrappedLinesPrefix); } function createModel(text: string): ISimpleModel { From 53ebe7809c8cb08a67e803d7669ea621b15755ee Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 7 Jan 2020 17:03:18 +0100 Subject: [PATCH 046/315] rename input field polish --- .../editor/contrib/rename/renameInputField.ts | 52 +++++++------------ 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/src/vs/editor/contrib/rename/renameInputField.ts b/src/vs/editor/contrib/rename/renameInputField.ts index 5f36cd8ae2175..815cc4a34adf1 100644 --- a/src/vs/editor/contrib/rename/renameInputField.ts +++ b/src/vs/editor/contrib/rename/renameInputField.ts @@ -19,7 +19,7 @@ export const CONTEXT_RENAME_INPUT_VISIBLE = new RawContextKey('renameIn export class RenameInputField implements IContentWidget, IDisposable { - private _editor: ICodeEditor; + private _position?: Position; private _domNode?: HTMLElement; private _inputField?: HTMLInputElement; @@ -31,38 +31,33 @@ export class RenameInputField implements IContentWidget, IDisposable { allowEditorOverflow: boolean = true; constructor( - editor: ICodeEditor, - private readonly themeService: IThemeService, + private readonly _editor: ICodeEditor, + private readonly _themeService: IThemeService, contextKeyService: IContextKeyService, ) { this._visibleContextKey = CONTEXT_RENAME_INPUT_VISIBLE.bindTo(contextKeyService); - this._editor = editor; this._editor.addContentWidget(this); - this._disposables.add(editor.onDidChangeConfiguration(e => { + this._disposables.add(this._editor.onDidChangeConfiguration(e => { if (e.hasChanged(EditorOption.fontInfo)) { - this.updateFont(); + this._updateFont(); } })); - this._disposables.add(themeService.onThemeChange(theme => this.onThemeChange(theme))); - } - - private onThemeChange(theme: ITheme): void { - this.updateStyles(theme); + this._disposables.add(_themeService.onThemeChange(this._updateStyles, this)); } - public dispose(): void { + dispose(): void { this._disposables.dispose(); this._editor.removeContentWidget(this); } - public getId(): string { + getId(): string { return '__renameInputWidget'; } - public getDomNode(): HTMLElement { + getDomNode(): HTMLElement { if (!this._domNode) { this._inputField = document.createElement('input'); this._inputField.className = 'rename-input'; @@ -73,13 +68,13 @@ export class RenameInputField implements IContentWidget, IDisposable { this._domNode.className = 'monaco-editor rename-box'; this._domNode.appendChild(this._inputField); - this.updateFont(); - this.updateStyles(this.themeService.getTheme()); + this._updateFont(); + this._updateStyles(this._themeService.getTheme()); } return this._domNode; } - private updateStyles(theme: ITheme): void { + private _updateStyles(theme: ITheme): void { if (!this._inputField) { return; } @@ -99,7 +94,7 @@ export class RenameInputField implements IContentWidget, IDisposable { this._domNode!.style.boxShadow = widgetShadowColor ? ` 0 2px 8px ${widgetShadowColor}` : ''; } - private updateFont(): void { + private _updateFont(): void { if (!this._inputField) { return; } @@ -110,7 +105,7 @@ export class RenameInputField implements IContentWidget, IDisposable { this._inputField.style.fontSize = `${fontInfo.fontSize}px`; } - public getPosition(): IContentWidgetPosition | null { + getPosition(): IContentWidgetPosition | null { return this._visible ? { position: this._position!, preference: [ContentWidgetPositionPreference.BELOW, ContentWidgetPositionPreference.ABOVE] } : null; @@ -119,19 +114,19 @@ export class RenameInputField implements IContentWidget, IDisposable { private _currentAcceptInput: (() => void) | null = null; private _currentCancelInput: ((focusEditor: boolean) => void) | null = null; - public acceptInput(): void { + acceptInput(): void { if (this._currentAcceptInput) { this._currentAcceptInput(); } } - public cancelInput(focusEditor: boolean): void { + cancelInput(focusEditor: boolean): void { if (this._currentCancelInput) { this._currentCancelInput(focusEditor); } } - public getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number): Promise { + getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number): Promise { this._position = new Position(where.startLineNumber, where.startColumn); this._inputField!.value = value; @@ -140,10 +135,6 @@ export class RenameInputField implements IContentWidget, IDisposable { this._inputField!.size = Math.max((where.endColumn - where.startColumn) * 1.1, 20); const disposeOnDone = new DisposableStore(); - const always = () => { - disposeOnDone.dispose(); - this._hide(); - }; return new Promise(resolve => { @@ -178,12 +169,9 @@ export class RenameInputField implements IContentWidget, IDisposable { this._show(); - }).then(newValue => { - always(); - return newValue; - }, err => { - always(); - return Promise.reject(err); + }).finally(() => { + disposeOnDone.dispose(); + this._hide(); }); } From b0bd81383921f8f0234021a7d02466b3f01b57f9 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 7 Jan 2020 17:32:18 +0100 Subject: [PATCH 047/315] Switch to use a LineNumberMapper for view line - model line mapping --- .../common/viewModel/prefixSumComputer.ts | 70 -------- .../common/viewModel/splitLinesCollection.ts | 157 ++++++++++++------ .../editor/common/viewModel/viewModelImpl.ts | 2 - 3 files changed, 106 insertions(+), 123 deletions(-) diff --git a/src/vs/editor/common/viewModel/prefixSumComputer.ts b/src/vs/editor/common/viewModel/prefixSumComputer.ts index d4e8c1f0df259..6cb1312c07e2d 100644 --- a/src/vs/editor/common/viewModel/prefixSumComputer.ts +++ b/src/vs/editor/common/viewModel/prefixSumComputer.ts @@ -187,73 +187,3 @@ export class PrefixSumComputer { return new PrefixSumIndexOfResult(mid, accumulatedValue - midStart); } } - -export class PrefixSumComputerWithCache { - - private readonly _actual: PrefixSumComputer; - private _cacheAccumulatedValueStart: number = 0; - private _cache: PrefixSumIndexOfResult[] | null = null; - - constructor(values: Uint32Array) { - this._actual = new PrefixSumComputer(values); - this._bustCache(); - } - - private _bustCache(): void { - this._cacheAccumulatedValueStart = 0; - this._cache = null; - } - - public insertValues(insertIndex: number, insertValues: Uint32Array): void { - if (this._actual.insertValues(insertIndex, insertValues)) { - this._bustCache(); - } - } - - public changeValue(index: number, value: number): void { - if (this._actual.changeValue(index, value)) { - this._bustCache(); - } - } - - public removeValues(startIndex: number, cnt: number): void { - if (this._actual.removeValues(startIndex, cnt)) { - this._bustCache(); - } - } - - public getTotalValue(): number { - return this._actual.getTotalValue(); - } - - public getAccumulatedValue(index: number): number { - return this._actual.getAccumulatedValue(index); - } - - public getIndexOf(accumulatedValue: number): PrefixSumIndexOfResult { - accumulatedValue = Math.floor(accumulatedValue); //@perf - - if (this._cache !== null) { - let cacheIndex = accumulatedValue - this._cacheAccumulatedValueStart; - if (cacheIndex >= 0 && cacheIndex < this._cache.length) { - // Cache hit! - return this._cache[cacheIndex]; - } - } - - // Cache miss! - return this._actual.getIndexOf(accumulatedValue); - } - - /** - * Gives a hint that a lot of requests are about to come in for these accumulated values. - */ - public warmUpCache(accumulatedValueStart: number, accumulatedValueEnd: number): void { - let newCache: PrefixSumIndexOfResult[] = []; - for (let accumulatedValue = accumulatedValueStart; accumulatedValue <= accumulatedValueEnd; accumulatedValue++) { - newCache[accumulatedValue - accumulatedValueStart] = this.getIndexOf(accumulatedValue); - } - this._cache = newCache; - this._cacheAccumulatedValueStart = accumulatedValueStart; - } -} diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 1b64f46d59e6a..f90d1dd637289 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as arrays from 'vs/base/common/arrays'; import { WrappingIndent } from 'vs/editor/common/config/editorOptions'; import { LineTokens } from 'vs/editor/common/core/lineTokens'; import { Position } from 'vs/editor/common/core/position'; @@ -10,7 +11,7 @@ import { IRange, Range } from 'vs/editor/common/core/range'; import { EndOfLinePreference, IActiveIndentGuideInfo, IModelDecoration, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; import { ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModel'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; -import { PrefixSumComputerWithCache } from 'vs/editor/common/viewModel/prefixSumComputer'; +import { PrefixSumIndexOfResult } from 'vs/editor/common/viewModel/prefixSumComputer'; import { ICoordinatesConverter, IOverviewRulerDecorations, ViewLineData } from 'vs/editor/common/viewModel/viewModel'; import { ITheme } from 'vs/platform/theme/common/themeService'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -43,13 +44,12 @@ export class LineBreakingData { let low = 0; let high = breakOffsets.length - 1; let mid = 0; - let midStop = 0; let midStart = 0; while (low <= high) { mid = low + ((high - low) / 2) | 0; - midStop = breakOffsets[mid]; + const midStop = breakOffsets[mid]; midStart = mid > 0 ? breakOffsets[mid - 1] : 0; if (inputOffset < midStart) { @@ -116,7 +116,6 @@ export interface IViewModelLinesCollection extends IDisposable { acceptVersionId(versionId: number): void; getViewLineCount(): number; - warmUpLookupCache(viewStartLineNumber: number, viewEndLineNumber: number): void; getActiveIndentGuide(viewLineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo; getViewLinesIndentGuides(viewStartLineNumber: number, viewEndLineNumber: number): number[]; getViewLineContent(viewLineNumber: number): string; @@ -145,9 +144,7 @@ export class CoordinatesConverter implements ICoordinatesConverter { } public convertViewRangeToModelRange(viewRange: Range): Range { - let start = this._lines.convertViewPositionToModelPosition(viewRange.startLineNumber, viewRange.startColumn); - let end = this._lines.convertViewPositionToModelPosition(viewRange.endLineNumber, viewRange.endColumn); - return new Range(start.lineNumber, start.column, end.lineNumber, end.column); + return this._lines.convertViewRangeToModelRange(viewRange); } public validateViewPosition(viewPosition: Position, expectedModelPosition: Position): Position { @@ -155,9 +152,7 @@ export class CoordinatesConverter implements ICoordinatesConverter { } public validateViewRange(viewRange: Range, expectedModelRange: Range): Range { - const validViewStart = this._lines.validateViewPosition(viewRange.startLineNumber, viewRange.startColumn, expectedModelRange.getStartPosition()); - const validViewEnd = this._lines.validateViewPosition(viewRange.endLineNumber, viewRange.endColumn, expectedModelRange.getEndPosition()); - return new Range(validViewStart.lineNumber, validViewStart.column, validViewEnd.lineNumber, validViewEnd.column); + return this._lines.validateViewRange(viewRange, expectedModelRange); } // Model -> View conversion and related methods @@ -173,7 +168,6 @@ export class CoordinatesConverter implements ICoordinatesConverter { public modelPositionIsVisible(modelPosition: Position): boolean { return this._lines.modelPositionIsVisible(modelPosition.lineNumber, modelPosition.column); } - } const enum IndentGuideRepeatOption { @@ -182,6 +176,89 @@ const enum IndentGuideRepeatOption { BlockAll = 2 } +class LineNumberMapper { + + private _counts: number[]; + private _isValid: boolean; + private _validEndIndex: number; + + private _modelToView: number[]; + private _viewToModel: number[]; + + constructor(viewLineCounts: number[]) { + this._counts = viewLineCounts; + this._isValid = false; + this._validEndIndex = -1; + this._modelToView = []; + this._viewToModel = []; + } + + private _invalidate(index: number): void { + this._isValid = false; + this._validEndIndex = Math.min(this._validEndIndex, index - 1); + } + + private _ensureValid(): void { + if (this._isValid) { + return; + } + + for (let i = this._validEndIndex + 1, len = this._counts.length; i < len; i++) { + const viewLineCount = this._counts[i]; + const viewLinesAbove = (i > 0 ? this._modelToView[i - 1] : 0); + + this._modelToView[i] = viewLinesAbove + viewLineCount; + for (let j = 0; j < viewLineCount; j++) { + this._viewToModel[viewLinesAbove + j] = i; + } + } + + // trim things + this._modelToView.length = this._counts.length; + this._viewToModel.length = this._modelToView[this._modelToView.length - 1]; + + // mark as valid + this._isValid = true; + this._validEndIndex = this._counts.length - 1; + } + + public changeValue(index: number, value: number): void { + if (this._counts[index] === value) { + // no change + return; + } + this._counts[index] = value; + this._invalidate(index); + } + + public removeValues(start: number, deleteCount: number): void { + this._counts.splice(start, deleteCount); + this._invalidate(start); + } + + public insertValues(insertIndex: number, insertArr: number[]): void { + this._counts = arrays.arrayInsert(this._counts, insertIndex, insertArr); + this._invalidate(insertIndex); + } + + public getTotalValue(): number { + this._ensureValid(); + return this._viewToModel.length; + } + + public getAccumulatedValue(index: number): number { + this._ensureValid(); + return this._modelToView[index]; + } + + public getIndexOf(accumulatedValue: number): PrefixSumIndexOfResult { + this._ensureValid(); + const modelLineIndex = this._viewToModel[accumulatedValue]; + const viewLinesAbove = (modelLineIndex > 0 ? this._modelToView[modelLineIndex - 1] : 0); + return new PrefixSumIndexOfResult(modelLineIndex, accumulatedValue - viewLinesAbove); + } +} + export class SplitLinesCollection implements IViewModelLinesCollection { private readonly model: ITextModel; @@ -193,7 +270,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { private tabSize: number; private lines!: ISplitLine[]; - private prefixSumComputer!: PrefixSumComputerWithCache; + private prefixSumComputer!: LineNumberMapper; private readonly linePositionMapperFactory: ILineMapperFactory; @@ -219,18 +296,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return new CoordinatesConverter(this); } - private _ensureValidState(): void { - let modelVersion = this.model.getVersionId(); - if (modelVersion !== this._validModelVersionId) { - // This is pretty bad, it means we lost track of the model... - throw new Error(`ViewModel is out of sync with Model!`); - } - if (this.lines.length !== this.model.getLineCount()) { - // This is pretty bad, it means we lost track of the model... - this._constructLines(false); - } - } - private _constructLines(resetHiddenAreas: boolean): void { this.lines = []; @@ -246,7 +311,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } const lineMappings = lineMappingComputer.finalize(); - let values = new Uint32Array(lineCount); + let values: number[] = []; let hiddenAreas = this.hiddenAreasIds.map((areaId) => this.model.getDecorationRange(areaId)!).sort(Range.compareRangesUsingStarts); let hiddenAreaStart = 1, hiddenAreaEnd = 0; @@ -271,7 +336,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { this._validModelVersionId = this.model.getVersionId(); - this.prefixSumComputer = new PrefixSumComputerWithCache(values); + this.prefixSumComputer = new LineNumberMapper(values); } public getHiddenAreas(): Range[] { @@ -459,7 +524,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { let totalOutputLineCount = 0; let insertLines: ISplitLine[] = []; - let insertPrefixSumValues = new Uint32Array(linesMappings.length); + let insertPrefixSumValues: number[] = []; for (let i = 0, len = linesMappings.length; i < len; i++) { let line = createSplitLine(linesMappings[i], !isInHiddenArea); @@ -536,7 +601,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } public getViewLineCount(): number { - this._ensureValidState(); return this.prefixSumComputer.getTotalValue(); } @@ -544,22 +608,14 @@ export class SplitLinesCollection implements IViewModelLinesCollection { if (viewLineNumber < 1) { return 1; } - let viewLineCount = this.getViewLineCount(); + const viewLineCount = this.getViewLineCount(); if (viewLineNumber > viewLineCount) { return viewLineCount; } - return viewLineNumber; - } - - /** - * Gives a hint that a lot of requests are about to come in for these line numbers. - */ - public warmUpLookupCache(viewStartLineNumber: number, viewEndLineNumber: number): void { - this.prefixSumComputer.warmUpCache(viewStartLineNumber - 1, viewEndLineNumber - 1); + return viewLineNumber | 0; } public getActiveIndentGuide(viewLineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo { - this._ensureValidState(); viewLineNumber = this._toValidViewLineNumber(viewLineNumber); minLineNumber = this._toValidViewLineNumber(minLineNumber); maxLineNumber = this._toValidViewLineNumber(maxLineNumber); @@ -579,7 +635,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } public getViewLinesIndentGuides(viewStartLineNumber: number, viewEndLineNumber: number): number[] { - this._ensureValidState(); viewStartLineNumber = this._toValidViewLineNumber(viewStartLineNumber); viewEndLineNumber = this._toValidViewLineNumber(viewEndLineNumber); @@ -650,7 +705,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } public getViewLineContent(viewLineNumber: number): string { - this._ensureValidState(); viewLineNumber = this._toValidViewLineNumber(viewLineNumber); let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); let lineIndex = r.index; @@ -660,7 +714,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } public getViewLineLength(viewLineNumber: number): number { - this._ensureValidState(); viewLineNumber = this._toValidViewLineNumber(viewLineNumber); let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); let lineIndex = r.index; @@ -670,7 +723,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } public getViewLineMinColumn(viewLineNumber: number): number { - this._ensureValidState(); viewLineNumber = this._toValidViewLineNumber(viewLineNumber); let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); let lineIndex = r.index; @@ -680,7 +732,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } public getViewLineMaxColumn(viewLineNumber: number): number { - this._ensureValidState(); viewLineNumber = this._toValidViewLineNumber(viewLineNumber); let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); let lineIndex = r.index; @@ -690,7 +741,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } public getViewLineData(viewLineNumber: number): ViewLineData { - this._ensureValidState(); viewLineNumber = this._toValidViewLineNumber(viewLineNumber); let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); let lineIndex = r.index; @@ -700,7 +750,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } public getViewLinesData(viewStartLineNumber: number, viewEndLineNumber: number, needed: boolean[]): ViewLineData[] { - this._ensureValidState(); viewStartLineNumber = this._toValidViewLineNumber(viewStartLineNumber); viewEndLineNumber = this._toValidViewLineNumber(viewEndLineNumber); @@ -739,7 +788,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } public validateViewPosition(viewLineNumber: number, viewColumn: number, expectedModelPosition: Position): Position { - this._ensureValidState(); viewLineNumber = this._toValidViewLineNumber(viewLineNumber); let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); @@ -767,8 +815,13 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return this.convertModelPositionToViewPosition(expectedModelPosition.lineNumber, expectedModelPosition.column); } + public validateViewRange(viewRange: Range, expectedModelRange: Range): Range { + const validViewStart = this.validateViewPosition(viewRange.startLineNumber, viewRange.startColumn, expectedModelRange.getStartPosition()); + const validViewEnd = this.validateViewPosition(viewRange.endLineNumber, viewRange.endColumn, expectedModelRange.getEndPosition()); + return new Range(validViewStart.lineNumber, validViewStart.column, validViewEnd.lineNumber, validViewEnd.column); + } + public convertViewPositionToModelPosition(viewLineNumber: number, viewColumn: number): Position { - this._ensureValidState(); viewLineNumber = this._toValidViewLineNumber(viewLineNumber); let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1); @@ -780,8 +833,13 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return this.model.validatePosition(new Position(lineIndex + 1, inputColumn)); } + public convertViewRangeToModelRange(viewRange: Range): Range { + const start = this.convertViewPositionToModelPosition(viewRange.startLineNumber, viewRange.startColumn); + const end = this.convertViewPositionToModelPosition(viewRange.endLineNumber, viewRange.endColumn); + return new Range(start.lineNumber, start.column, end.lineNumber, end.column); + } + public convertModelPositionToViewPosition(_modelLineNumber: number, _modelColumn: number): Position { - this._ensureValidState(); const validPosition = this.model.validatePosition(new Position(_modelLineNumber, _modelColumn)); const inputLineNumber = validPosition.lineNumber; @@ -1374,9 +1432,6 @@ export class IdentityLinesCollection implements IViewModelLinesCollection { return this.model.getLineCount(); } - public warmUpLookupCache(_viewStartLineNumber: number, _viewEndLineNumber: number): void { - } - public getActiveIndentGuide(viewLineNumber: number, _minLineNumber: number, _maxLineNumber: number): IActiveIndentGuideInfo { return { startLineNumber: viewLineNumber, diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 3bb303641c781..229df6e3614cd 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -495,8 +495,6 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel * Gives a hint that a lot of requests are about to come in for these line numbers. */ public setViewport(startLineNumber: number, endLineNumber: number, centeredLineNumber: number): void { - this.lines.warmUpLookupCache(startLineNumber, endLineNumber); - this.viewportStartLine = startLineNumber; let position = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(startLineNumber, this.getLineMinColumn(startLineNumber))); this.viewportStartLineTrackedRange = this.model._setTrackedRange(this.viewportStartLineTrackedRange, new Range(position.lineNumber, position.column, position.lineNumber, position.column), TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges); From 405707fec77242a1b2e335f3f8f27716dc3bb3a3 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 7 Jan 2020 17:44:38 +0100 Subject: [PATCH 048/315] add Shift+Enter for acceptRenameWithPreview --- src/vs/editor/contrib/rename/rename.ts | 34 +++++++++++++------ .../editor/contrib/rename/renameInputField.ts | 30 +++++++++------- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index f85e8d9d80e44..39ffb621870cc 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -176,11 +176,11 @@ class RenameController implements IEditorContribution { selectionEnd = Math.min(loc.range.endColumn, selection.endColumn) - loc.range.startColumn; } - const newNameOrFocusFlag = await this._renameInputField.getValue().getInput(loc.range, loc.text, selectionStart, selectionEnd); + const inputFieldResult = await this._renameInputField.getValue().getInput(loc.range, loc.text, selectionStart, selectionEnd); - - if (typeof newNameOrFocusFlag === 'boolean') { - if (newNameOrFocusFlag) { + // no result, only hint to focus the editor or not + if (typeof inputFieldResult === 'boolean') { + if (inputFieldResult) { this.editor.focus(); } return undefined; @@ -188,7 +188,7 @@ class RenameController implements IEditorContribution { this.editor.focus(); - const renameOperation = raceCancellation(skeleton.provideRenameEdits(newNameOrFocusFlag, 0, [], this._cts.token), this._cts.token).then(async renameResult => { + const renameOperation = raceCancellation(skeleton.provideRenameEdits(inputFieldResult.newName, 0, [], this._cts.token), this._cts.token).then(async renameResult => { if (!renameResult || !this.editor.hasModel()) { return; @@ -199,9 +199,12 @@ class RenameController implements IEditorContribution { return; } - this._bulkEditService.apply(renameResult, { editor: this.editor }).then(result => { + this._bulkEditService.apply(renameResult, { + editor: this.editor, + noPreview: !inputFieldResult.wantsPreview + }).then(result => { if (result.ariaSummary) { - alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", loc!.text, newNameOrFocusFlag, result.ariaSummary)); + alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", loc!.text, inputFieldResult.newName, result.ariaSummary)); } }).catch(err => { this._notificationService.error(nls.localize('rename.failedApply', "Rename failed to apply edits")); @@ -218,8 +221,8 @@ class RenameController implements IEditorContribution { } - acceptRenameInput(): void { - this._renameInputField.getValue().acceptInput(); + acceptRenameInput(wantsPreview: boolean): void { + this._renameInputField.getValue().acceptInput(wantsPreview); } cancelRenameInput(): void { @@ -286,7 +289,7 @@ const RenameCommand = EditorCommand.bindToContribution(RenameC registerEditorCommand(new RenameCommand({ id: 'acceptRenameInput', precondition: CONTEXT_RENAME_INPUT_VISIBLE, - handler: x => x.acceptRenameInput(), + handler: x => x.acceptRenameInput(false), kbOpts: { weight: KeybindingWeight.EditorContrib + 99, kbExpr: EditorContextKeys.focus, @@ -294,6 +297,17 @@ registerEditorCommand(new RenameCommand({ } })); +registerEditorCommand(new RenameCommand({ + id: 'acceptRenameInputWithPreview', + precondition: CONTEXT_RENAME_INPUT_VISIBLE, + handler: x => x.acceptRenameInput(true), + kbOpts: { + weight: KeybindingWeight.EditorContrib + 99, + kbExpr: EditorContextKeys.focus, + primary: KeyMod.Shift + KeyCode.Enter + } +})); + registerEditorCommand(new RenameCommand({ id: 'cancelRenameInput', precondition: CONTEXT_RENAME_INPUT_VISIBLE, diff --git a/src/vs/editor/contrib/rename/renameInputField.ts b/src/vs/editor/contrib/rename/renameInputField.ts index 815cc4a34adf1..ec9c5f10cc719 100644 --- a/src/vs/editor/contrib/rename/renameInputField.ts +++ b/src/vs/editor/contrib/rename/renameInputField.ts @@ -17,8 +17,12 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; export const CONTEXT_RENAME_INPUT_VISIBLE = new RawContextKey('renameInputVisible', false); -export class RenameInputField implements IContentWidget, IDisposable { +export interface RenameInputFieldResult { + newName: string; + wantsPreview?: boolean; +} +export class RenameInputField implements IContentWidget, IDisposable { private _position?: Position; private _domNode?: HTMLElement; @@ -111,12 +115,12 @@ export class RenameInputField implements IContentWidget, IDisposable { : null; } - private _currentAcceptInput: (() => void) | null = null; - private _currentCancelInput: ((focusEditor: boolean) => void) | null = null; + private _currentAcceptInput?: (wantsPreview: boolean) => void; + private _currentCancelInput?: (focusEditor: boolean) => void; - acceptInput(): void { + acceptInput(wantsPreview: boolean): void { if (this._currentAcceptInput) { - this._currentAcceptInput(); + this._currentAcceptInput(wantsPreview); } } @@ -126,7 +130,7 @@ export class RenameInputField implements IContentWidget, IDisposable { } } - getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number): Promise { + getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number): Promise { this._position = new Position(where.startLineNumber, where.startColumn); this._inputField!.value = value; @@ -136,25 +140,25 @@ export class RenameInputField implements IContentWidget, IDisposable { const disposeOnDone = new DisposableStore(); - return new Promise(resolve => { + return new Promise(resolve => { this._currentCancelInput = (focusEditor) => { - this._currentAcceptInput = null; - this._currentCancelInput = null; + this._currentAcceptInput = undefined; + this._currentCancelInput = undefined; resolve(focusEditor); return true; }; - this._currentAcceptInput = () => { + this._currentAcceptInput = (wantsPreview) => { if (this._inputField!.value.trim().length === 0 || this._inputField!.value === value) { // empty or whitespace only or not changed this.cancelInput(true); return; } - this._currentAcceptInput = null; - this._currentCancelInput = null; - resolve(this._inputField!.value); + this._currentAcceptInput = undefined; + this._currentCancelInput = undefined; + resolve({ newName: this._inputField!.value, wantsPreview }); }; let onCursorChanged = () => { From beb6b203a665627bed10ee0285b5bfdf9dc48fbf Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 7 Jan 2020 18:24:39 +0100 Subject: [PATCH 049/315] show rename, preview labels in rename input box --- src/vs/editor/contrib/rename/rename.ts | 9 +- .../contrib/rename/renameInputField.css | 2 + .../editor/contrib/rename/renameInputField.ts | 107 +++++++++++------- 3 files changed, 70 insertions(+), 48 deletions(-) diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index 39ffb621870cc..29b4bb8f7cf84 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { illegalArgument, onUnexpectedError } from 'vs/base/common/errors'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; -import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, EditorCommand, registerEditorCommand, registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; @@ -14,7 +14,6 @@ import { ITextModel } from 'vs/editor/common/model'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { RenameInputField, CONTEXT_RENAME_INPUT_VISIBLE } from './renameInputField'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; import { WorkspaceEdit, RenameProviderRegistry, RenameProvider, RenameLocation, Rejection } from 'vs/editor/common/modes'; import { Position, IPosition } from 'vs/editor/common/core/position'; import { alert } from 'vs/base/browser/ui/aria/aria'; @@ -31,6 +30,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { IdleValue, raceCancellation } from 'vs/base/common/async'; import { withNullAsUndefined } from 'vs/base/common/types'; import { ILogService } from 'vs/platform/log/common/log'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; class RenameSkeleton { @@ -110,14 +110,13 @@ class RenameController implements IEditorContribution { constructor( private readonly editor: ICodeEditor, + @IInstantiationService private readonly _instaService: IInstantiationService, @INotificationService private readonly _notificationService: INotificationService, @IBulkEditService private readonly _bulkEditService: IBulkEditService, @IEditorProgressService private readonly _progressService: IEditorProgressService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IThemeService private readonly _themeService: IThemeService, @ILogService private readonly _logService: ILogService, ) { - this._renameInputField = this._dispoableStore.add(new IdleValue(() => this._dispoableStore.add(new RenameInputField(this.editor, this._themeService, this._contextKeyService)))); + this._renameInputField = this._dispoableStore.add(new IdleValue(() => this._dispoableStore.add(this._instaService.createInstance(RenameInputField, this.editor, ['acceptRenameInput', 'acceptRenameInputWithPreview'])))); } dispose(): void { diff --git a/src/vs/editor/contrib/rename/renameInputField.css b/src/vs/editor/contrib/rename/renameInputField.css index 0478cd7ce48c3..a61b3741c2f07 100644 --- a/src/vs/editor/contrib/rename/renameInputField.css +++ b/src/vs/editor/contrib/rename/renameInputField.css @@ -6,8 +6,10 @@ .monaco-editor .rename-box { z-index: 100; color: inherit; + padding: 4px; } .monaco-editor .rename-box .rename-input { padding: 4px; + width: calc(100% - 8px); } diff --git a/src/vs/editor/contrib/rename/renameInputField.ts b/src/vs/editor/contrib/rename/renameInputField.ts index ec9c5f10cc719..f70ed3c15d1d6 100644 --- a/src/vs/editor/contrib/rename/renameInputField.ts +++ b/src/vs/editor/contrib/rename/renameInputField.ts @@ -4,16 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./renameInputField'; -import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { inputBackground, inputBorder, inputForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; +import { inputBackground, inputBorder, inputForeground, widgetShadow, editorWidgetBackground } from 'vs/platform/theme/common/colorRegistry'; import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; export const CONTEXT_RENAME_INPUT_VISIBLE = new RawContextKey('renameInputVisible', false); @@ -22,22 +23,24 @@ export interface RenameInputFieldResult { wantsPreview?: boolean; } -export class RenameInputField implements IContentWidget, IDisposable { +export class RenameInputField implements IContentWidget { private _position?: Position; private _domNode?: HTMLElement; - private _inputField?: HTMLInputElement; + private _input?: HTMLInputElement; + private _label?: HTMLDivElement; private _visible?: boolean; private readonly _visibleContextKey: IContextKey; private readonly _disposables = new DisposableStore(); - // Editor.IContentWidget.allowEditorOverflow - allowEditorOverflow: boolean = true; + readonly allowEditorOverflow: boolean = true; constructor( private readonly _editor: ICodeEditor, - private readonly _themeService: IThemeService, - contextKeyService: IContextKeyService, + private readonly _acceptKeybindings: [string, string], + @IThemeService private readonly _themeService: IThemeService, + @IKeybindingService private readonly _keybindingService: IKeybindingService, + @IContextKeyService contextKeyService: IContextKeyService, ) { this._visibleContextKey = CONTEXT_RENAME_INPUT_VISIBLE.bindTo(contextKeyService); @@ -63,14 +66,25 @@ export class RenameInputField implements IContentWidget, IDisposable { getDomNode(): HTMLElement { if (!this._domNode) { - this._inputField = document.createElement('input'); - this._inputField.className = 'rename-input'; - this._inputField.type = 'text'; - this._inputField.setAttribute('aria-label', localize('renameAriaLabel', "Rename input. Type new name and press Enter to commit.")); this._domNode = document.createElement('div'); - this._domNode.style.height = `${this._editor.getOption(EditorOption.lineHeight)}px`; this._domNode.className = 'monaco-editor rename-box'; - this._domNode.appendChild(this._inputField); + + this._input = document.createElement('input'); + this._input.className = 'rename-input'; + this._input.type = 'text'; + this._input.setAttribute('aria-label', localize('renameAriaLabel', "Rename input. Type new name and press Enter to commit.")); + this._domNode.appendChild(this._input); + + this._label = document.createElement('div'); + this._label.className = 'rename-label'; + this._domNode.appendChild(this._label); + const updateLabel = () => { + const [accept, preview] = this._acceptKeybindings; + this._keybindingService.lookupKeybinding(accept); + this._label!.innerText = localize('label', "Press {0} to Rename, {1} to Preview", this._keybindingService.lookupKeybinding(accept)?.getLabel(), this._keybindingService.lookupKeybinding(preview)?.getLabel()); + }; + updateLabel(); + this._disposables.add(this._keybindingService.onDidUpdateKeybindings(updateLabel)); this._updateFont(); this._updateStyles(this._themeService.getTheme()); @@ -79,40 +93,44 @@ export class RenameInputField implements IContentWidget, IDisposable { } private _updateStyles(theme: ITheme): void { - if (!this._inputField) { + if (!this._input || !this._domNode) { return; } - const background = theme.getColor(inputBackground); - const foreground = theme.getColor(inputForeground); const widgetShadowColor = theme.getColor(widgetShadow); - const border = theme.getColor(inputBorder); - - this._inputField.style.backgroundColor = background ? background.toString() : ''; - this._inputField.style.color = foreground ? foreground.toString() : ''; + this._domNode.style.backgroundColor = String(theme.getColor(editorWidgetBackground) ?? ''); + this._domNode.style.boxShadow = widgetShadowColor ? ` 0 2px 8px ${widgetShadowColor}` : ''; + this._domNode.style.color = String(theme.getColor(inputForeground) ?? ''); - this._inputField.style.borderWidth = border ? '1px' : '0px'; - this._inputField.style.borderStyle = border ? 'solid' : 'none'; - this._inputField.style.borderColor = border ? border.toString() : 'none'; - - this._domNode!.style.boxShadow = widgetShadowColor ? ` 0 2px 8px ${widgetShadowColor}` : ''; + this._input.style.backgroundColor = String(theme.getColor(inputBackground) ?? ''); + // this._input.style.color = String(theme.getColor(inputForeground) ?? ''); + const border = theme.getColor(inputBorder); + this._input.style.borderWidth = border ? '1px' : '0px'; + this._input.style.borderStyle = border ? 'solid' : 'none'; + this._input.style.borderColor = border?.toString() ?? 'none'; } private _updateFont(): void { - if (!this._inputField) { + if (!this._input || !this._label) { return; } const fontInfo = this._editor.getOption(EditorOption.fontInfo); - this._inputField.style.fontFamily = fontInfo.fontFamily; - this._inputField.style.fontWeight = fontInfo.fontWeight; - this._inputField.style.fontSize = `${fontInfo.fontSize}px`; + this._input.style.fontFamily = fontInfo.fontFamily; + this._input.style.fontWeight = fontInfo.fontWeight; + this._input.style.fontSize = `${fontInfo.fontSize}px`; + + this._label.style.fontSize = `${fontInfo.fontSize * 0.8}px`; } getPosition(): IContentWidgetPosition | null { - return this._visible - ? { position: this._position!, preference: [ContentWidgetPositionPreference.BELOW, ContentWidgetPositionPreference.ABOVE] } - : null; + if (!this._visible) { + return null; + } + return { + position: this._position!, + preference: [ContentWidgetPositionPreference.BELOW, ContentWidgetPositionPreference.ABOVE] + }; } private _currentAcceptInput?: (wantsPreview: boolean) => void; @@ -133,10 +151,10 @@ export class RenameInputField implements IContentWidget, IDisposable { getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number): Promise { this._position = new Position(where.startLineNumber, where.startColumn); - this._inputField!.value = value; - this._inputField!.setAttribute('selectionStart', selectionStart.toString()); - this._inputField!.setAttribute('selectionEnd', selectionEnd.toString()); - this._inputField!.size = Math.max((where.endColumn - where.startColumn) * 1.1, 20); + this._input!.value = value; + this._input!.setAttribute('selectionStart', selectionStart.toString()); + this._input!.setAttribute('selectionEnd', selectionEnd.toString()); + this._input!.size = Math.max((where.endColumn - where.startColumn) * 1.1, 20); const disposeOnDone = new DisposableStore(); @@ -150,7 +168,7 @@ export class RenameInputField implements IContentWidget, IDisposable { }; this._currentAcceptInput = (wantsPreview) => { - if (this._inputField!.value.trim().length === 0 || this._inputField!.value === value) { + if (this._input!.value.trim().length === 0 || this._input!.value === value) { // empty or whitespace only or not changed this.cancelInput(true); return; @@ -158,7 +176,10 @@ export class RenameInputField implements IContentWidget, IDisposable { this._currentAcceptInput = undefined; this._currentCancelInput = undefined; - resolve({ newName: this._inputField!.value, wantsPreview }); + resolve({ + newName: this._input!.value, + wantsPreview + }); }; let onCursorChanged = () => { @@ -186,10 +207,10 @@ export class RenameInputField implements IContentWidget, IDisposable { this._editor.layoutContentWidget(this); setTimeout(() => { - this._inputField!.focus(); - this._inputField!.setSelectionRange( - parseInt(this._inputField!.getAttribute('selectionStart')!), - parseInt(this._inputField!.getAttribute('selectionEnd')!)); + this._input!.focus(); + this._input!.setSelectionRange( + parseInt(this._input!.getAttribute('selectionStart')!), + parseInt(this._input!.getAttribute('selectionEnd')!)); }, 100); } From 406ac6a34f096a6ce71626d36d0c38e08a025464 Mon Sep 17 00:00:00 2001 From: Konstantin Solomatov Date: Tue, 7 Jan 2020 12:26:06 -0800 Subject: [PATCH 050/315] Code cleanup --- .../contrib/terminal/browser/terminalPanel.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts index 0cec6b4b0c6dd..cfa67e91d8f04 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts @@ -97,19 +97,14 @@ export class TerminalPanel extends Panel { this._register(this.onDidChangeVisibility(visible => { if (visible) { - if (this._terminalService.terminalInstances.length > 0) { - this._updateFont(); - this._updateTheme(); + const hasTerminals = this._terminalService.terminalInstances.length > 0; + if (!hasTerminals) { + this._terminalService.createTerminal(); + } + this._updateFont(); + this._updateTheme(); + if (!hasTerminals) { this._terminalService.getActiveTab()?.setVisible(visible); - } else { - // Check if instances were already restored as part of workbench restore - if (this._terminalService.terminalInstances.length === 0) { - this._terminalService.createTerminal(); - } - if (this._terminalService.terminalInstances.length > 0) { - this._updateFont(); - this._updateTheme(); - } } } })); From 22b031930baf9e8f5b610c655c5057b16571a24b Mon Sep 17 00:00:00 2001 From: Konstantin Solomatov Date: Tue, 7 Jan 2020 13:03:11 -0800 Subject: [PATCH 051/315] Fix incorrect negation --- src/vs/workbench/contrib/terminal/browser/terminalPanel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts index cfa67e91d8f04..2a0408ff55633 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts @@ -103,7 +103,7 @@ export class TerminalPanel extends Panel { } this._updateFont(); this._updateTheme(); - if (!hasTerminals) { + if (hasTerminals) { this._terminalService.getActiveTab()?.setVisible(visible); } } From fd936fb58556bc26313e58e12bc743b29a13f8c5 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 8 Jan 2020 09:33:20 +0100 Subject: [PATCH 052/315] remove fs implementation --- .../bulkEdit/browser/bulkEditPreview.ts | 206 +----------------- 1 file changed, 2 insertions(+), 204 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts index aee181049b0ff..a9e8050e2d26a 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts @@ -9,14 +9,11 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; import { WorkspaceEdit, isResourceTextEdit, TextEdit } from 'vs/editor/common/modes'; -import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { mergeSort } from 'vs/base/common/arrays'; import { Range } from 'vs/editor/common/core/range'; import { EditOperation } from 'vs/editor/common/core/editOperation'; -import * as files from 'vs/platform/files/common/files'; -import { Event, Emitter } from 'vs/base/common/event'; -import { TernarySearchTree, values } from 'vs/base/common/map'; -import { basename } from 'vs/base/common/resources'; +import { values } from 'vs/base/common/map'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; export const enum BulkFileOperationType { @@ -180,202 +177,3 @@ export class BulkEditPreviewProvider implements ITextModelContentProvider { return this._modelService.getModel(previewUri); } } - -export function asPreviewUri(uri: URI): URI { - return URI.from({ scheme: 'vscode-bulkedit-preview', path: uri.path, query: uri.toString() }); -} - -export function fromPreviewUri(uri: URI): URI { - return URI.parse(uri.query); -} - -class File implements files.IStat { - - type: files.FileType; - ctime: number; - mtime: number; - size: number; - - name: string; - data?: Uint8Array; - - constructor(name: string) { - this.type = files.FileType.File; - this.ctime = Date.now(); - this.mtime = Date.now(); - this.size = 0; - this.name = name; - } -} - -class Directory implements files.IStat { - - type: files.FileType; - ctime: number; - mtime: number; - size: number; - - name: string; - entries: Map; - - constructor(name: string) { - this.type = files.FileType.Directory; - this.ctime = Date.now(); - this.mtime = Date.now(); - this.size = 0; - this.name = name; - this.entries = new Map(); - } -} - -type Entry = File | Directory; - -export class BulkEditFileSystem implements files.IFileSystemProvider { - - readonly capabilities = files.FileSystemProviderCapabilities.FileReadWrite; - readonly onDidChangeCapabilities = Event.None; - - private readonly _registration: IDisposable; - private readonly _onDidChangeFile = new Emitter(); - readonly onDidChangeFile = this._onDidChangeFile.event; - - private readonly _entries = TernarySearchTree.forPaths(); - private readonly _deleted = TernarySearchTree.forPaths(); - - private _pendingChanges: files.IFileChange[] = []; - private _pendingHandle?: any; - - constructor(@files.IFileService private _fileService: files.IFileService) { - this._registration = _fileService.registerProvider('vscode-bulkedit-preview', this); - } - - dispose(): void { - this._registration.dispose(); - } - - private _fireSoon(event: files.IFileChange): void { - this._pendingChanges.push(event); - clearTimeout(this._pendingHandle); - this._pendingHandle = setTimeout(() => { - this._onDidChangeFile.fire(this._pendingChanges.slice(0)); - this._pendingChanges = []; - }, 0); - } - - private _checkDeleted(resource: URI): void { - if (this._deleted.findSubstr(resource.toString())) { - throw new Error('deleted ' + resource.toString()); - } - } - - watch(_resource: URI, _opts: files.IWatchOptions): IDisposable { - return Disposable.None; - } - - async stat(resource: URI): Promise { - this._checkDeleted(resource); - const entry = this._entries.get(resource.toString()); - if (entry) { - return entry; - } - - const stat = await this._fileService.resolve(fromPreviewUri(resource), { resolveMetadata: true }); - return { - type: (stat.isSymbolicLink ? files.FileType.SymbolicLink : 0) + (stat.isFile ? files.FileType.File : 0) + (stat.isDirectory ? files.FileType.Directory : 0), - ctime: stat.ctime, - mtime: stat.mtime, - size: stat.size, - }; - } - - async readdir(resource: URI): Promise<[string, files.FileType][]> { - // resource = fromPreviewUri(resource); - this._checkDeleted(resource); - - const entry = this._entries.get(resource.toString()); - const result: [string, files.FileType][] = []; - if (entry instanceof Directory) { - entry.entries.forEach(value => result.push([value.name, value.type])); - } - try { - const stat = await this._fileService.resolve(fromPreviewUri(resource), { resolveMetadata: true }); - if (stat.children) { - for (let child of stat.children) { - result.push([ - child.name, - (child.isSymbolicLink ? files.FileType.SymbolicLink : 0) + (child.isFile ? files.FileType.File : 0) + (child.isDirectory ? files.FileType.Directory : 0) - ]); - } - } - - - } catch { - // ignore - } - - return result; - } - - async mkdir(resource: URI): Promise { - // resource = fromPreviewUri(resource); - - const dir = new Directory(basename(resource)); - this._entries.set(resource.toString(), dir); - this._fireSoon({ resource, type: files.FileChangeType.ADDED }); - } - - async delete(resource: URI, _opts: files.FileDeleteOptions): Promise { - // resource = fromPreviewUri(resource); - this._deleted.set(resource.toString(), true); - } - - async rename(from: URI, to: URI, opts: files.FileOverwriteOptions): Promise { - // from = fromPreviewUri(from); - // to = fromPreviewUri(to); - - const target = new File(basename(to)); - target.type = (await this.stat(from)).type; - - this._deleted.set(from.toString(), true); - this._entries.set(to.toString(), target); - - // todo@joh copy files - // const iter = this._entries.findSuperstr(from.toString()); - // if (iter) { - // for (let next = iter.next(); !next.done; next = iter.next()) { - - // } - // } - // this._entries.delete(from.toString()); - - //todo@joh RENAME EVENT? - this._fireSoon({ resource: from, type: files.FileChangeType.DELETED }); - this._fireSoon({ resource: to, type: files.FileChangeType.ADDED }); - } - - // copy?(from: URI, to: URI, opts: FileOverwriteOptions): Promise; - - async readFile(resource: URI): Promise { - this._checkDeleted(resource); - - const entry = this._entries.get(resource.toString()); - if (entry instanceof File) { - return entry.data || new Uint8Array(); - } - return (await this._fileService.readFile(fromPreviewUri(resource))).value.buffer; - } - - async writeFile(resource: URI, content: Uint8Array, opts: files.FileWriteOptions): Promise { - - let entry = this._entries.get(resource.toString()); - if (!entry && opts.create) { - entry = new File(basename(resource)); - this._entries.set(resource.toString(), entry); - } - if (!(entry instanceof File)) { - throw new Error(); - } - entry.data = content; - this._fireSoon({ resource, type: files.FileChangeType.UPDATED }); - } -} From bfe4acac71f5c7034b5f20d56f19fb7f5fcbb7d1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 8 Jan 2020 09:41:40 +0100 Subject: [PATCH 053/315] simplify IdentProvider, rename view model object --- .../contrib/bulkEdit/browser/bulkEditPanel.ts | 4 +-- .../contrib/bulkEdit/browser/bulkEditTree.ts | 28 ++++++++----------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts index 82a1eb77df162..45593fb855808 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts @@ -8,7 +8,7 @@ import { Panel } from 'vs/workbench/browser/panel'; import { Dimension } from 'vs/base/browser/dom'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { WorkspaceEdit } from 'vs/editor/common/modes'; -import { Edit, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; +import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; import { FuzzyScore } from 'vs/base/common/filters'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -33,7 +33,7 @@ export class BulkEditPanel extends Panel { static readonly ID = 'BulkEditPanel'; - private _tree!: WorkbenchAsyncDataTree; + private _tree!: WorkbenchAsyncDataTree; private _message!: HTMLSpanElement; private readonly _acceptAction = new Action('ok', localize('ok', "Apply Refactoring"), 'codicon-check', false, async () => this._done(true)); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index 9b0eb46c031f0..633aed1e58b77 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -43,15 +43,15 @@ export class TextEditElement { ) { } } -export type Edit = FileElement | TextEditElement; +export type BulkEditElement = FileElement | TextEditElement; // --- DATA SOURCE -export class BulkEditDataSource implements IAsyncDataSource { +export class BulkEditDataSource implements IAsyncDataSource { constructor(@ITextModelService private readonly _textModelService: ITextModelService) { } - hasChildren(element: BulkFileOperations | Edit): boolean { + hasChildren(element: BulkFileOperations | BulkEditElement): boolean { if (element instanceof FileElement) { return element.edit.textEdits.length > 0; } @@ -61,7 +61,7 @@ export class BulkEditDataSource implements IAsyncDataSource { + async getChildren(element: BulkFileOperations | BulkEditElement): Promise { // root -> file/text edits if (element instanceof BulkFileOperations) { @@ -111,18 +111,14 @@ export class BulkEditDataSource implements IAsyncDataSource { +export class BulkEditIdentityProvider implements IIdentityProvider { - private readonly _map = new WeakMap(); - private _idPool = 0; - - getId(element: Edit): { toString(): string; } { - let id = this._map.get(element); - if (typeof id === 'undefined') { - id = this._idPool++; - this._map.set(element, id); + getId(element: BulkEditElement): { toString(): string; } { + if (element instanceof FileElement) { + return element.uri; + } else { + return element.parent.uri.toString() + JSON.stringify(element.edit); } - return id; } } @@ -204,13 +200,13 @@ export class TextEditElementRenderer implements ITreeRenderer { +export class BulkEditDelegate implements IListVirtualDelegate { getHeight(): number { return 23; } - getTemplateId(element: Edit): string { + getTemplateId(element: BulkEditElement): string { return element instanceof FileElement ? FileElementRenderer.id : TextEditElementRenderer.id; From 53ae4c8aaf65a8ef34b266647e4acb95ea6f5999 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 8 Jan 2020 09:47:47 +0100 Subject: [PATCH 054/315] show file operation labels with file name --- .../contrib/bulkEdit/browser/bulkEditTree.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index 633aed1e58b77..f043537045690 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -18,7 +18,8 @@ import { ITextModel } from 'vs/editor/common/model'; import { IDisposable } from 'vs/base/common/lifecycle'; import { TextModel } from 'vs/editor/common/model/textModel'; import { ILabelService } from 'vs/platform/label/common/label'; -import { BulkFileOperations, BulkFileOperation } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; +import { BulkFileOperations, BulkFileOperation, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; +import { localize } from 'vs/nls'; // --- VIEW MODEL @@ -153,9 +154,20 @@ export class FileElementRenderer implements ITreeRenderer, _index: number, template: FileElementTemplate): void { + let fileOperationsLabels: string[] = []; + if (node.element.edit.type & BulkFileOperationType.Create) { + fileOperationsLabels.push(localize('file.create', "create file")); + } + if (node.element.edit.type & BulkFileOperationType.Delete) { + fileOperationsLabels.push(localize('file.delete', "delete file")); + } + if (node.element.edit.type & BulkFileOperationType.Rename) { + fileOperationsLabels.push(localize('file.rename', "rename file")); + } + template.label.setResource({ name: this._labelService.getUriLabel(node.element.uri, { relative: true }), - description: node.element._debugName, + description: fileOperationsLabels.join(', '), resource: node.element.uri, }, { matches: createMatches(node.filterData), From bb799f2bc7c3deb2a77b89c36f6aab53d40e8731 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 8 Jan 2020 10:07:52 +0100 Subject: [PATCH 055/315] filter file noops --- .../bulkEdit/browser/bulkEditPreview.ts | 21 +++++++++++---- .../contrib/bulkEdit/browser/bulkEditTree.ts | 27 +++++++++---------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts index a9e8050e2d26a..654ad0d836112 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts @@ -15,6 +15,7 @@ import { Range } from 'vs/editor/common/core/range'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { values } from 'vs/base/common/map'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IFileService } from 'vs/platform/files/common/files'; export const enum BulkFileOperationType { None = 0, @@ -41,10 +42,12 @@ export class BulkFileOperation { export class BulkFileOperations { - static async create(_accessor: ServicesAccessor, bulkEdit: WorkspaceEdit): Promise { + static async create(accessor: ServicesAccessor, bulkEdit: WorkspaceEdit): Promise { const operationByResource = new Map(); + const fileService = accessor.get(IFileService); + for (const edit of bulkEdit.edits) { let uri: URI; @@ -55,25 +58,35 @@ export class BulkFileOperations { type = BulkFileOperationType.None; uri = edit.resource; textEdits = edit.edits; - } else if (edit.newUri && edit.oldUri) { type = BulkFileOperationType.Rename; uri = edit.oldUri; + if (edit.options?.overwrite === undefined && edit.options?.ignoreIfExists && await fileService.exists(uri)) { + // noop -> "soft" rename to something that already exists + continue; + } } else if (edit.oldUri) { type = BulkFileOperationType.Delete; uri = edit.oldUri; + if (edit.options?.ignoreIfNotExists && !await fileService.exists(uri)) { + // noop -> "soft" delete something that doesn't exist + continue; + } } else if (edit.newUri) { type = BulkFileOperationType.Create; uri = edit.newUri; + if (edit.options?.overwrite === undefined && edit.options?.ignoreIfExists && await fileService.exists(uri)) { + // noop -> "soft" create something that already exists + continue; + } } else { // invalid edit -> skip continue; } - const key = uri.toString(); let operation = operationByResource.get(key); if (!operation) { @@ -87,8 +100,6 @@ export class BulkFileOperations { } } - //todo@joh filter noops - return new BulkFileOperations(values(operationByResource)); } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index f043537045690..13fa844ffb5e2 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -25,12 +25,22 @@ import { localize } from 'vs/nls'; export class FileElement { + private static _typeLabels: Record = { + [BulkFileOperationType.Create]: localize('create', "create"), + [BulkFileOperationType.Delete]: localize('delete', "delete"), + [BulkFileOperationType.Rename]: localize('rename', "rename"), + [BulkFileOperationType.Create | BulkFileOperationType.Delete]: localize('createDelete', "create & delete"), + [BulkFileOperationType.Create | BulkFileOperationType.Rename]: localize('createRename', "create & rename"), + [BulkFileOperationType.Delete | BulkFileOperationType.Rename]: localize('deleteRename', "delete & rename"), + [BulkFileOperationType.Create | BulkFileOperationType.Delete | BulkFileOperationType.Rename]: localize('createRenameDelete', "create, rename, delete"), + }; + readonly uri: URI; - readonly _debugName: string; + readonly typeLabel: string; constructor(readonly edit: BulkFileOperation) { this.uri = edit.uri; - this._debugName = `0b${edit.type.toString(2)}`; + this.typeLabel = FileElement._typeLabels[edit.type] || ''; } } @@ -154,20 +164,9 @@ export class FileElementRenderer implements ITreeRenderer, _index: number, template: FileElementTemplate): void { - let fileOperationsLabels: string[] = []; - if (node.element.edit.type & BulkFileOperationType.Create) { - fileOperationsLabels.push(localize('file.create', "create file")); - } - if (node.element.edit.type & BulkFileOperationType.Delete) { - fileOperationsLabels.push(localize('file.delete', "delete file")); - } - if (node.element.edit.type & BulkFileOperationType.Rename) { - fileOperationsLabels.push(localize('file.rename', "rename file")); - } - template.label.setResource({ name: this._labelService.getUriLabel(node.element.uri, { relative: true }), - description: fileOperationsLabels.join(', '), + description: node.element.typeLabel, resource: node.element.uri, }, { matches: createMatches(node.filterData), From 1e2ca4dbddc58aacae0900276bed07685ee4fdbd Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 8 Jan 2020 12:16:10 +0100 Subject: [PATCH 056/315] Have the rendering width of a tab character be independent of breaking points --- .../browser/viewParts/lines/viewLine.ts | 1 + .../editor/browser/widget/diffEditorWidget.ts | 1 + src/vs/editor/browser/widget/diffReview.ts | 1 + .../common/viewLayout/viewLineRenderer.ts | 102 ++++++------- .../characterHardWrappingLineMapper.ts | 136 +++++++----------- .../common/viewModel/splitLinesCollection.ts | 45 ++++-- src/vs/editor/common/viewModel/viewModel.ts | 14 +- .../editor/common/viewModel/viewModelImpl.ts | 3 +- src/vs/editor/standalone/browser/colorizer.ts | 3 + .../viewLayout/viewLineRenderer.test.ts | 31 ++++ .../characterHardWrappingLineMapper.test.ts | 4 +- .../viewModel/splitLinesCollection.test.ts | 90 ++++++------ 12 files changed, 232 insertions(+), 199 deletions(-) diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index 8840a0140322e..cbe9a08f45fad 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -213,6 +213,7 @@ export class ViewLine implements IVisibleLine { lineData.tokens, actualInlineDecorations, lineData.tabSize, + lineData.startVisibleColumn, options.spaceWidth, options.stopRenderingLineAfter, options.renderWhitespace, diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 91b9173203368..e6ae832954a33 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -2144,6 +2144,7 @@ class InlineViewZonesComputer extends ViewZonesComputer { lineTokens, actualDecorations, tabSize, + 0, fontInfo.spaceWidth, options.get(EditorOption.stopRenderingLineAfter), options.get(EditorOption.renderWhitespace), diff --git a/src/vs/editor/browser/widget/diffReview.ts b/src/vs/editor/browser/widget/diffReview.ts index b0437b6d47fb9..4ac1919a690ea 100644 --- a/src/vs/editor/browser/widget/diffReview.ts +++ b/src/vs/editor/browser/widget/diffReview.ts @@ -780,6 +780,7 @@ export class DiffReview extends Disposable { lineTokens, [], tabSize, + 0, fontInfo.spaceWidth, options.get(EditorOption.stopRenderingLineAfter), options.get(EditorOption.renderWhitespace), diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index 5298d234529bc..fec4ba1dfb3a0 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -66,6 +66,7 @@ export class RenderLineInput { public readonly lineTokens: IViewLineTokens; public readonly lineDecorations: LineDecoration[]; public readonly tabSize: number; + public readonly startVisibleColumn: number; public readonly spaceWidth: number; public readonly stopRenderingLineAfter: number; public readonly renderWhitespace: RenderWhitespace; @@ -89,6 +90,7 @@ export class RenderLineInput { lineTokens: IViewLineTokens, lineDecorations: LineDecoration[], tabSize: number, + startVisibleColumn: number, spaceWidth: number, stopRenderingLineAfter: number, renderWhitespace: 'none' | 'boundary' | 'selection' | 'all', @@ -106,6 +108,7 @@ export class RenderLineInput { this.lineTokens = lineTokens; this.lineDecorations = lineDecorations; this.tabSize = tabSize; + this.startVisibleColumn = startVisibleColumn; this.spaceWidth = spaceWidth; this.stopRenderingLineAfter = stopRenderingLineAfter; this.renderWhitespace = ( @@ -154,6 +157,7 @@ export class RenderLineInput { && this.containsRTL === other.containsRTL && this.fauxIndentLength === other.fauxIndentLength && this.tabSize === other.tabSize + && this.startVisibleColumn === other.startVisibleColumn && this.spaceWidth === other.spaceWidth && this.stopRenderingLineAfter === other.stopRenderingLineAfter && this.renderWhitespace === other.renderWhitespace @@ -368,7 +372,9 @@ class ResolvedRenderLineInput { public readonly isOverflowing: boolean, public readonly parts: LinePart[], public readonly containsForeignElements: ForeignElementType, + public readonly fauxIndentLength: number, public readonly tabSize: number, + public readonly startVisibleColumn: number, public readonly containsRTL: boolean, public readonly spaceWidth: number, public readonly renderWhitespace: RenderWhitespace, @@ -395,7 +401,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput let tokens = transformAndRemoveOverflowing(input.lineTokens, input.fauxIndentLength, len); if (input.renderWhitespace === RenderWhitespace.All || input.renderWhitespace === RenderWhitespace.Boundary || (input.renderWhitespace === RenderWhitespace.Selection && !!input.selectionsOnLine)) { - tokens = _applyRenderWhitespace(lineContent, len, input.continuesWithWrappedLine, tokens, input.fauxIndentLength, input.tabSize, useMonospaceOptimizations, input.selectionsOnLine, input.renderWhitespace === RenderWhitespace.Boundary); + tokens = _applyRenderWhitespace(lineContent, len, input.continuesWithWrappedLine, tokens, input.fauxIndentLength, input.tabSize, input.startVisibleColumn, useMonospaceOptimizations, input.selectionsOnLine, input.renderWhitespace === RenderWhitespace.Boundary); } let containsForeignElements = ForeignElementType.None; if (input.lineDecorations.length > 0) { @@ -425,7 +431,9 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput isOverflowing, tokens, containsForeignElements, + input.fauxIndentLength, input.tabSize, + input.startVisibleColumn, input.containsRTL, input.spaceWidth, input.renderWhitespace, @@ -537,7 +545,7 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[], onlyAtSpaces: * Moreover, a token is created for every visual indent because on some fonts the glyphs used for rendering whitespace (→ or ·) do not have the same width as  . * The rendering phase will generate `style="width:..."` for these tokens. */ -function _applyRenderWhitespace(lineContent: string, len: number, continuesWithWrappedLine: boolean, tokens: LinePart[], fauxIndentLength: number, tabSize: number, useMonospaceOptimizations: boolean, selections: LineRange[] | null, onlyBoundary: boolean): LinePart[] { +function _applyRenderWhitespace(lineContent: string, len: number, continuesWithWrappedLine: boolean, tokens: LinePart[], fauxIndentLength: number, tabSize: number, startVisibleColumn: number, useMonospaceOptimizations: boolean, selections: LineRange[] | null, onlyBoundary: boolean): LinePart[] { let result: LinePart[] = [], resultLen = 0; let tokenIndex = 0; @@ -555,21 +563,10 @@ function _applyRenderWhitespace(lineContent: string, len: number, continuesWithW lastNonWhitespaceIndex = strings.lastNonWhitespaceIndex(lineContent); } - let tmpIndent = 0; - for (let charIndex = 0; charIndex < fauxIndentLength; charIndex++) { - const chCode = lineContent.charCodeAt(charIndex); - if (chCode === CharCode.Tab) { - tmpIndent = tabSize; - } else if (strings.isFullWidthCharacter(chCode)) { - tmpIndent += 2; - } else { - tmpIndent++; - } - } - tmpIndent = tmpIndent % tabSize; let wasInWhitespace = false; let currentSelectionIndex = 0; let currentSelection = selections && selections[currentSelectionIndex]; + let tmpIndent = startVisibleColumn % tabSize; for (let charIndex = fauxIndentLength; charIndex < len; charIndex++) { const chCode = lineContent.charCodeAt(charIndex); @@ -729,7 +726,9 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render const len = input.len; const isOverflowing = input.isOverflowing; const parts = input.parts; + const fauxIndentLength = input.fauxIndentLength; const tabSize = input.tabSize; + const startVisibleColumn = input.startVisibleColumn; const containsRTL = input.containsRTL; const spaceWidth = input.spaceWidth; const renderWhitespace = input.renderWhitespace; @@ -738,7 +737,7 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render const characterMapping = new CharacterMapping(len + 1, parts.length); let charIndex = 0; - let tabsCharDelta = 0; + let visibleColumn = startVisibleColumn; let charOffsetInPart = 0; let prevPartContentCnt = 0; @@ -764,18 +763,14 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render let partContentCnt = 0; { let _charIndex = charIndex; - let _tabsCharDelta = tabsCharDelta; + let _visibleColumn = visibleColumn; for (; _charIndex < partEndIndex; _charIndex++) { const charCode = lineContent.charCodeAt(_charIndex); - - if (charCode === CharCode.Tab) { - let insertSpacesCount = tabSize - (_charIndex + _tabsCharDelta) % tabSize; - _tabsCharDelta += insertSpacesCount - 1; - partContentCnt += insertSpacesCount; - } else { - // must be CharCode.Space - partContentCnt++; + const charWidth = (charCode === CharCode.Tab ? (tabSize - (_visibleColumn % tabSize)) : 1) | 0; + partContentCnt += charWidth; + if (_charIndex >= fauxIndentLength) { + _visibleColumn += charWidth; } } } @@ -793,29 +788,30 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render for (; charIndex < partEndIndex; charIndex++) { characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset); const charCode = lineContent.charCodeAt(charIndex); + let charWidth: number; if (charCode === CharCode.Tab) { - let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize; - tabsCharDelta += insertSpacesCount - 1; - charOffsetInPart += insertSpacesCount - 1; - if (insertSpacesCount > 0) { - if (!canUseHalfwidthRightwardsArrow || insertSpacesCount > 1) { - sb.write1(0x2192); // RIGHTWARDS ARROW - } else { - sb.write1(0xFFEB); // HALFWIDTH RIGHTWARDS ARROW - } - insertSpacesCount--; + charWidth = (tabSize - (visibleColumn % tabSize)) | 0; + + if (!canUseHalfwidthRightwardsArrow || charWidth > 1) { + sb.write1(0x2192); // RIGHTWARDS ARROW + } else { + sb.write1(0xFFEB); // HALFWIDTH RIGHTWARDS ARROW } - while (insertSpacesCount > 0) { + for (let space = 2; space <= charWidth; space++) { sb.write1(0xA0); //   - insertSpacesCount--; } - } else { - // must be CharCode.Space + + } else { // must be CharCode.Space + charWidth = 1; + sb.write1(0xB7); // · } - charOffsetInPart++; + charOffsetInPart += charWidth; + if (charIndex >= fauxIndentLength) { + visibleColumn += charWidth; + } } prevPartContentCnt = partContentCnt; @@ -833,63 +829,59 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset); const charCode = lineContent.charCodeAt(charIndex); + let producedCharacters = 1; + let charWidth = 1; + switch (charCode) { case CharCode.Tab: - let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize; - tabsCharDelta += insertSpacesCount - 1; - charOffsetInPart += insertSpacesCount - 1; - while (insertSpacesCount > 0) { + producedCharacters = (tabSize - (visibleColumn % tabSize)); + charWidth = producedCharacters; + for (let space = 1; space <= producedCharacters; space++) { sb.write1(0xA0); //   - partContentCnt++; - insertSpacesCount--; } break; case CharCode.Space: sb.write1(0xA0); //   - partContentCnt++; break; case CharCode.LessThan: sb.appendASCIIString('<'); - partContentCnt++; break; case CharCode.GreaterThan: sb.appendASCIIString('>'); - partContentCnt++; break; case CharCode.Ampersand: sb.appendASCIIString('&'); - partContentCnt++; break; case CharCode.Null: sb.appendASCIIString('�'); - partContentCnt++; break; case CharCode.UTF8_BOM: case CharCode.LINE_SEPARATOR_2028: sb.write1(0xFFFD); - partContentCnt++; break; default: if (strings.isFullWidthCharacter(charCode)) { - tabsCharDelta++; + charWidth++; } if (renderControlCharacters && charCode < 32) { sb.write1(9216 + charCode); - partContentCnt++; } else { sb.write1(charCode); - partContentCnt++; } } - charOffsetInPart++; + charOffsetInPart += producedCharacters; + partContentCnt += producedCharacters; + if (charIndex >= fauxIndentLength) { + visibleColumn += charWidth; + } } prevPartContentCnt = partContentCnt; diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index 5f4bc7f1828cf..e1fff8b193eba 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -66,19 +66,11 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor this.classifier = new WrappingCharacterClassifier(breakBeforeChars, breakAfterChars); } - // TODO@Alex -> duplicated in lineCommentCommand - private static nextVisibleColumn(currentVisibleColumn: number, tabSize: number, isTab: boolean, columnSize: number): number { - currentVisibleColumn = +currentVisibleColumn; //@perf - tabSize = +tabSize; //@perf - columnSize = +columnSize; //@perf - - if (isTab) { - return currentVisibleColumn + (tabSize - (currentVisibleColumn % tabSize)); - } - return currentVisibleColumn + columnSize; - } - public createLineMappingComputer(tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ILineMappingComputer { + tabSize = tabSize | 0; //@perf + wrappingColumn = +wrappingColumn; //@perf + columnsForFullWidthChar = +columnsForFullWidthChar; //@perf + let requests: string[] = []; return { addRequest: (lineText: string) => { @@ -94,70 +86,57 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor }; } - private _createLineMapping(lineText: string, tabSize: number, breakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): LineBreakingData | null { - if (breakingColumn === -1) { + private _createLineMapping(lineText: string, tabSize: number, firstLineBreakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): LineBreakingData | null { + if (firstLineBreakingColumn === -1) { return null; } - tabSize = +tabSize; //@perf - breakingColumn = +breakingColumn; //@perf - columnsForFullWidthChar = +columnsForFullWidthChar; //@perf - hardWrappingIndent = +hardWrappingIndent; //@perf - - let wrappedTextIndentVisibleColumn = 0; - let wrappedTextIndent = ''; - let firstNonWhitespaceIndex = -1; + let wrappedTextIndentLength = 0; if (hardWrappingIndent !== WrappingIndent.None) { firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineText); if (firstNonWhitespaceIndex !== -1) { // Track existing indent - wrappedTextIndent = lineText.substring(0, firstNonWhitespaceIndex); + for (let i = 0; i < firstNonWhitespaceIndex; i++) { - wrappedTextIndentVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(wrappedTextIndentVisibleColumn, tabSize, lineText.charCodeAt(i) === CharCode.Tab, 1); + const charWidth = (lineText.charCodeAt(i) === CharCode.Tab ? tabCharacterWidth(wrappedTextIndentLength, tabSize) : 1); + wrappedTextIndentLength += charWidth; } // Increase indent of continuation lines, if desired - let numberOfAdditionalTabs = 0; - if (hardWrappingIndent === WrappingIndent.Indent) { - numberOfAdditionalTabs = 1; - } else if (hardWrappingIndent === WrappingIndent.DeepIndent) { - numberOfAdditionalTabs = 2; - } + const numberOfAdditionalTabs = (hardWrappingIndent === WrappingIndent.DeepIndent ? 2 : hardWrappingIndent === WrappingIndent.Indent ? 1 : 0); for (let i = 0; i < numberOfAdditionalTabs; i++) { - wrappedTextIndent += '\t'; - wrappedTextIndentVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(wrappedTextIndentVisibleColumn, tabSize, true, 1); + const charWidth = tabCharacterWidth(wrappedTextIndentLength, tabSize); + wrappedTextIndentLength += charWidth; } // Force sticking to beginning of line if no character would fit except for the indentation - if (wrappedTextIndentVisibleColumn + columnsForFullWidthChar > breakingColumn) { - wrappedTextIndent = ''; - wrappedTextIndentVisibleColumn = 0; + if (wrappedTextIndentLength + columnsForFullWidthChar > firstLineBreakingColumn) { + wrappedTextIndentLength = 0; } } } + const wrappedLineBreakingColumn = firstLineBreakingColumn - wrappedTextIndentLength; const classifier = this.classifier; - let breakingOffsets: number[] = []; // The offset where a break occurs - let breakingOffsetsIndex: number = 0; // The count of breaks already done - let visibleColumn = 0; // Visible column since the beginning of the current line - let niceBreakOffset = 0; // Last index of a character that indicates a break should happen before it (more desirable) - let niceBreakVisibleColumn = 0; // visible column if a break were to be later introduced before `niceBreakOffset` + let breakingOffsets: number[] = []; + let breakingOffsetsVisibleColumn: number[] = []; + let breakingOffsetsCount: number = 0; + let visibleColumn = 0; + let breakOffset = 0; + let breakOffsetVisibleColumn = 0; const len = lineText.length; + let breakingColumn = firstLineBreakingColumn; for (let i = 0; i < len; i++) { // At this point, there is a certainty that the character before `i` fits on the current line, // but the character at `i` might not fit const charCode = lineText.charCodeAt(i); + if (strings.isLowSurrogate(charCode)) { // A surrogate pair must always be considered as a single unit, so it is never to be broken - - // Advance visibleColumn & niceBreakVisibleColumn - visibleColumn = visibleColumn + 1; - if (niceBreakOffset !== 0) { - niceBreakVisibleColumn = niceBreakVisibleColumn + 1; - } + visibleColumn += 1; continue; } @@ -171,68 +150,59 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor // (or) CJK breaking : before break : Kinsoku Shori : Don't break after a leading character, like an open bracket // Since we are certain the character before `i` fits, there's no extra checking needed, // just mark it as a nice breaking opportunity - niceBreakOffset = i; - niceBreakVisibleColumn = wrappedTextIndentVisibleColumn; + breakOffset = i; + breakOffsetVisibleColumn = visibleColumn; } - const charCodeIsTab = (charCode === CharCode.Tab); - const charColumnSize = strings.isFullWidthCharacter(charCode) ? columnsForFullWidthChar : 1; + const charColumnSize = ( + charCode === CharCode.Tab + ? tabCharacterWidth(visibleColumn, tabSize) + : (strings.isFullWidthCharacter(charCode) ? columnsForFullWidthChar : 1) + ); - // Advance visibleColumn with character at `i` - visibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(visibleColumn, tabSize, charCodeIsTab, charColumnSize); + visibleColumn += charColumnSize; + // check if adding character at `i` will go over the breaking column if (visibleColumn > breakingColumn && i !== 0) { // We need to break at least before character at `i`: - // - break before niceBreakLastOffset if it exists (and re-establish a correct visibleColumn by using niceBreakVisibleColumn + charAt(i)) - // - otherwise, break before obtrusiveBreakLastOffset if it exists (and re-establish a correct visibleColumn by using obtrusiveBreakVisibleColumn + charAt(i)) - // - otherwise, break before i (and re-establish a correct visibleColumn by charAt(i)) - - if (niceBreakOffset !== 0 && niceBreakVisibleColumn <= breakingColumn) { - - // We will break before `niceBreakLastOffset` - breakingOffsets[breakingOffsetsIndex++] = niceBreakOffset; - visibleColumn = niceBreakVisibleColumn; - - } else { - - // We will break before `i` - breakingOffsets[breakingOffsetsIndex++] = i; - visibleColumn = wrappedTextIndentVisibleColumn; + if (breakOffset === 0 || visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakingColumn) { + // Cannot break at `breakOffset`, must break at `i` + breakOffset = i; + breakOffsetVisibleColumn = visibleColumn - charColumnSize; } - // Re-establish visibleColumn by taking character at `i` into account - visibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(visibleColumn, tabSize, charCodeIsTab, charColumnSize); - - // Reset markers - niceBreakOffset = 0; + breakingOffsets[breakingOffsetsCount] = breakOffset; + breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn; + breakingOffsetsCount++; + breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakingColumn; + breakOffset = 0; } // At this point, there is a certainty that the character at `i` fits on the current line - - if (niceBreakOffset !== 0) { - // Advance niceBreakVisibleColumn - niceBreakVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(niceBreakVisibleColumn, tabSize, charCodeIsTab, charColumnSize); - } - if ( (charCodeClass === CharacterClass.BREAK_AFTER && (hardWrappingIndent === WrappingIndent.None || i >= firstNonWhitespaceIndex)) || (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && i + 1 < len && classifier.get(lineText.charCodeAt(i + 1)) !== CharacterClass.BREAK_AFTER) ) { // This is a character that indicates that a break should happen after it // (or) CJK breaking : after break : Kinsoku Shori : Don't break before a trailing character, like a period - niceBreakOffset = i + 1; - niceBreakVisibleColumn = wrappedTextIndentVisibleColumn; + breakOffset = i + 1; + breakOffsetVisibleColumn = visibleColumn; } } - if (breakingOffsetsIndex === 0) { + if (breakingOffsetsCount === 0) { return null; } // Add last segment - breakingOffsets[breakingOffsetsIndex++] = len; + breakingOffsets[breakingOffsetsCount] = len; + breakingOffsetsVisibleColumn[breakingOffsetsCount] = visibleColumn; - return new LineBreakingData(breakingOffsets, wrappedTextIndent); + return new LineBreakingData(breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); } } + +function tabCharacterWidth(visibleColumn: number, tabSize: number): number { + return (tabSize - (visibleColumn % tabSize)); +} diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index f90d1dd637289..113059661f9e8 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -29,7 +29,8 @@ export class OutputPosition { export class LineBreakingData { constructor( public readonly breakOffsets: number[], - public readonly wrappedLinesIndent: string + public readonly breakingOffsetsVisibleColumn: number[], + public readonly wrappedTextIndentLength: number ) { } public static getInputOffsetOfOutputPosition(breakOffsets: number[], outputLineIndex: number, outputOffset: number): number { @@ -1032,6 +1033,7 @@ class VisibleIdentitySplitLine implements ISplitLine { false, 1, lineContent.length + 1, + 0, lineTokens.inflate() ); } @@ -1118,12 +1120,14 @@ class InvisibleIdentitySplitLine implements ISplitLine { export class SplitLine implements ISplitLine { private readonly _breakOffsets: number[]; - private readonly _wrappedIndent: string; + private readonly _breakingOffsetsVisibleColumn: number[]; + private readonly _wrappedTextIndentLength: number; private _isVisible: boolean; constructor(lineBreaking: LineBreakingData, isVisible: boolean) { this._breakOffsets = lineBreaking.breakOffsets; - this._wrappedIndent = lineBreaking.wrappedLinesIndent; + this._breakingOffsetsVisibleColumn = lineBreaking.breakingOffsetsVisibleColumn; + this._wrappedTextIndentLength = lineBreaking.wrappedTextIndentLength; this._isVisible = isVisible; } @@ -1168,7 +1172,7 @@ export class SplitLine implements ISplitLine { }); if (outputLineIndex > 0) { - r = this._wrappedIndent + r; + r = spaces(this._wrappedTextIndentLength) + r; } return r; @@ -1183,7 +1187,7 @@ export class SplitLine implements ISplitLine { let r = endOffset - startOffset; if (outputLineIndex > 0) { - r = this._wrappedIndent.length + r; + r = this._wrappedTextIndentLength + r; } return r; @@ -1194,7 +1198,7 @@ export class SplitLine implements ISplitLine { throw new Error('Not supported'); } if (outputLineIndex > 0) { - return this._wrappedIndent.length + 1; + return this._wrappedTextIndentLength + 1; } return 1; } @@ -1222,25 +1226,28 @@ export class SplitLine implements ISplitLine { }); if (outputLineIndex > 0) { - lineContent = this._wrappedIndent + lineContent; + lineContent = spaces(this._wrappedTextIndentLength) + lineContent; } - let minColumn = (outputLineIndex > 0 ? this._wrappedIndent.length + 1 : 1); + let minColumn = (outputLineIndex > 0 ? this._wrappedTextIndentLength + 1 : 1); let maxColumn = lineContent.length + 1; let continuesWithWrappedLine = (outputLineIndex + 1 < this.getViewLineCount()); let deltaStartIndex = 0; if (outputLineIndex > 0) { - deltaStartIndex = this._wrappedIndent.length; + deltaStartIndex = this._wrappedTextIndentLength; } let lineTokens = model.getLineTokens(modelLineNumber); + const startVisibleColumn = (outputLineIndex === 0 ? 0 : this._breakingOffsetsVisibleColumn[outputLineIndex - 1]); + return new ViewLineData( lineContent, continuesWithWrappedLine, minColumn, maxColumn, + startVisibleColumn, lineTokens.sliceAndInflate(startOffset, endOffset, deltaStartIndex) ); } @@ -1266,10 +1273,10 @@ export class SplitLine implements ISplitLine { } let adjustedColumn = outputColumn - 1; if (outputLineIndex > 0) { - if (adjustedColumn < this._wrappedIndent.length) { + if (adjustedColumn < this._wrappedTextIndentLength) { adjustedColumn = 0; } else { - adjustedColumn -= this._wrappedIndent.length; + adjustedColumn -= this._wrappedTextIndentLength; } } return LineBreakingData.getInputOffsetOfOutputPosition(this._breakOffsets, outputLineIndex, adjustedColumn) + 1; @@ -1284,7 +1291,7 @@ export class SplitLine implements ISplitLine { let outputColumn = r.outputOffset + 1; if (outputLineIndex > 0) { - outputColumn += this._wrappedIndent.length; + outputColumn += this._wrappedTextIndentLength; } // console.log('in -> out ' + deltaLineNumber + ',' + inputColumn + ' ===> ' + (deltaLineNumber+outputLineIndex) + ',' + outputColumn); @@ -1300,6 +1307,19 @@ export class SplitLine implements ISplitLine { } } +let _spaces: string[] = ['']; +function spaces(count: number): string { + if (count >= _spaces.length) { + for (let i = 1; i <= count; i++) { + _spaces[i] = _makeSpaces(i); + } + } + return _spaces[count]; +} +function _makeSpaces(count: number): string { + return new Array(count + 1).join(' '); +} + function createSplitLine(lineMapping: LineBreakingData | null, isVisible: boolean): ISplitLine { if (lineMapping === null) { // No mapping needed @@ -1473,6 +1493,7 @@ export class IdentityLinesCollection implements IViewModelLinesCollection { false, 1, lineContent.length + 1, + 0, lineTokens.inflate() ); } diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index a3abd74ee4ebc..a79c030bf9666 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -174,6 +174,10 @@ export class ViewLineData { * The maximum allowed column at this view line. */ public readonly maxColumn: number; + /** + * The visible column at the start of the line (after the fauxIndent). + */ + public readonly startVisibleColumn: number; /** * The tokens at this view line. */ @@ -184,12 +188,14 @@ export class ViewLineData { continuesWithWrappedLine: boolean, minColumn: number, maxColumn: number, + startVisibleColumn: number, tokens: IViewLineTokens ) { this.content = content; this.continuesWithWrappedLine = continuesWithWrappedLine; this.minColumn = minColumn; this.maxColumn = maxColumn; + this.startVisibleColumn = startVisibleColumn; this.tokens = tokens; } } @@ -231,6 +237,10 @@ export class ViewLineRenderingData { * The tab size for this view model. */ public readonly tabSize: number; + /** + * The visible column at the start of the line (after the fauxIndent) + */ + public readonly startVisibleColumn: number; constructor( minColumn: number, @@ -241,7 +251,8 @@ export class ViewLineRenderingData { mightContainNonBasicASCII: boolean, tokens: IViewLineTokens, inlineDecorations: InlineDecoration[], - tabSize: number + tabSize: number, + startVisibleColumn: number ) { this.minColumn = minColumn; this.maxColumn = maxColumn; @@ -254,6 +265,7 @@ export class ViewLineRenderingData { this.tokens = tokens; this.inlineDecorations = inlineDecorations; this.tabSize = tabSize; + this.startVisibleColumn = startVisibleColumn; } public static isBasicASCII(lineContent: string, mightContainNonBasicASCII: boolean): boolean { diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 229df6e3614cd..f437ceeab264e 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -564,7 +564,8 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel mightContainNonBasicASCII, lineData.tokens, inlineDecorations, - tabSize + tabSize, + lineData.startVisibleColumn ); } diff --git a/src/vs/editor/standalone/browser/colorizer.ts b/src/vs/editor/standalone/browser/colorizer.ts index d72d141046bcf..679734f865259 100644 --- a/src/vs/editor/standalone/browser/colorizer.ts +++ b/src/vs/editor/standalone/browser/colorizer.ts @@ -125,6 +125,7 @@ export class Colorizer { [], tabSize, 0, + 0, -1, 'none', false, @@ -193,6 +194,7 @@ function _fakeColorize(lines: string[], tabSize: number): string { [], tabSize, 0, + 0, -1, 'none', false, @@ -230,6 +232,7 @@ function _actualColorize(lines: string[], tabSize: number, tokenizationSupport: [], tabSize, 0, + 0, -1, 'none', false, diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index 040629e325b6b..ccb50958ccdf2 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -38,6 +38,7 @@ suite('viewLineRenderer.renderLine', () => { [], tabSize, 0, + 0, -1, 'none', false, @@ -88,6 +89,7 @@ suite('viewLineRenderer.renderLine', () => { [], tabSize, 0, + 0, -1, 'none', false, @@ -140,6 +142,7 @@ suite('viewLineRenderer.renderLine', () => { ]), [], 4, + 0, 10, 6, 'boundary', @@ -232,6 +235,7 @@ suite('viewLineRenderer.renderLine', () => { lineParts, [], 4, + 0, 10, -1, 'boundary', @@ -295,6 +299,7 @@ suite('viewLineRenderer.renderLine', () => { lineParts, [], 4, + 0, 10, -1, 'none', @@ -358,6 +363,7 @@ suite('viewLineRenderer.renderLine', () => { lineParts, [], 4, + 0, 10, -1, 'none', @@ -398,6 +404,7 @@ suite('viewLineRenderer.renderLine', () => { lineParts, [], 4, + 0, 10, -1, 'none', @@ -429,6 +436,7 @@ suite('viewLineRenderer.renderLine', () => { lineParts, [], 4, + 0, 10, -1, 'none', @@ -530,6 +538,7 @@ suite('viewLineRenderer.renderLine', () => { lineParts, [], 4, + 0, 10, -1, 'none', @@ -569,6 +578,7 @@ suite('viewLineRenderer.renderLine', () => { lineParts, [], 4, + 0, 10, -1, 'none', @@ -599,6 +609,7 @@ suite('viewLineRenderer.renderLine', () => { lineParts, [], 4, + 0, 10, -1, 'none', @@ -646,6 +657,7 @@ suite('viewLineRenderer.renderLine', () => { lineParts, [], 4, + 0, 10, -1, 'none', @@ -728,6 +740,7 @@ suite('viewLineRenderer.renderLine 2', () => { createViewLineTokens(tokens), [], 4, + 0, 10, -1, renderWhitespace, @@ -754,6 +767,7 @@ suite('viewLineRenderer.renderLine 2', () => { createViewLineTokens([createPart(21, 3)]), [new LineDecoration(1, 22, 'link', InlineDecorationType.Regular)], 4, + 0, 10, -1, 'none', @@ -794,6 +808,7 @@ suite('viewLineRenderer.renderLine 2', () => { new LineDecoration(13, 51, 'detected-link', InlineDecorationType.Regular) ], 4, + 0, 10, -1, 'none', @@ -1209,6 +1224,7 @@ suite('viewLineRenderer.renderLine 2', () => { new LineDecoration(2, 8, 'c', InlineDecorationType.Regular), ], 4, + 0, 10, -1, 'none', @@ -1250,6 +1266,7 @@ suite('viewLineRenderer.renderLine 2', () => { createViewLineTokens([createPart(4, 3)]), [new LineDecoration(1, 2, 'before', InlineDecorationType.Before)], 4, + 0, 10, -1, 'all', @@ -1283,6 +1300,7 @@ suite('viewLineRenderer.renderLine 2', () => { createViewLineTokens([createPart(4, 3)]), [new LineDecoration(2, 3, 'before', InlineDecorationType.Before)], 4, + 0, 10, -1, 'all', @@ -1317,6 +1335,7 @@ suite('viewLineRenderer.renderLine 2', () => { createViewLineTokens([createPart(0, 3)]), [new LineDecoration(1, 2, 'before', InlineDecorationType.Before)], 4, + 0, 10, -1, 'all', @@ -1347,6 +1366,7 @@ suite('viewLineRenderer.renderLine 2', () => { createViewLineTokens([createPart(7, 3)]), [new LineDecoration(7, 8, 'inline-folded', InlineDecorationType.After)], 2, + 0, 10, 10000, 'none', @@ -1381,6 +1401,7 @@ suite('viewLineRenderer.renderLine 2', () => { new LineDecoration(0, 1, 'after', InlineDecorationType.After), ], 2, + 0, 10, 10000, 'none', @@ -1414,6 +1435,7 @@ suite('viewLineRenderer.renderLine 2', () => { new LineDecoration(3, 3, 'ced-TextEditorDecorationType2-5e9b9b3f-4 ced-TextEditorDecorationType2-4', InlineDecorationType.After), ], 4, + 0, 10, 10000, 'none', @@ -1445,6 +1467,7 @@ suite('viewLineRenderer.renderLine 2', () => { createViewLineTokens([createPart(15, 3)]), [], 4, + 0, 10, 10000, 'none', @@ -1475,6 +1498,7 @@ suite('viewLineRenderer.renderLine 2', () => { createViewLineTokens([createPart(15, 3)]), [], 4, + 0, 10, 10000, 'all', @@ -1511,6 +1535,7 @@ suite('viewLineRenderer.renderLine 2', () => { createViewLineTokens([createPart(53, 3)]), [], 4, + 0, 10, 10000, 'none', @@ -1541,6 +1566,7 @@ suite('viewLineRenderer.renderLine 2', () => { createViewLineTokens([createPart(100, 3)]), [], 4, + 0, 10, 10000, 'none', @@ -1573,6 +1599,7 @@ suite('viewLineRenderer.renderLine 2', () => { createViewLineTokens([createPart(105, 3)]), [], 4, + 0, 10, 10000, 'none', @@ -1604,6 +1631,7 @@ suite('viewLineRenderer.renderLine 2', () => { createViewLineTokens([createPart(59, 3)]), [], 4, + 0, 10, 10000, 'boundary', @@ -1633,6 +1661,7 @@ suite('viewLineRenderer.renderLine 2', () => { createViewLineTokens([createPart(194, 3)]), [], 4, + 0, 10, 10000, 'none', @@ -1666,6 +1695,7 @@ suite('viewLineRenderer.renderLine 2', () => { createViewLineTokens([createPart(194, 3)]), [], 4, + 0, 10, 10000, 'none', @@ -1695,6 +1725,7 @@ suite('viewLineRenderer.renderLine 2', () => { createViewLineTokens(parts), [], tabSize, + 0, 10, -1, 'none', diff --git a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts index 88c4fe05bc52b..a46124eccb176 100644 --- a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts +++ b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts @@ -114,7 +114,7 @@ suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { test('issue #35162: wrappingIndent not consistently working', () => { let factory = new CharacterHardWrappingLineMapperFactory('', '\t '); let mapper = assertLineMapping(factory, 4, 24, ' t h i s |i s |a l |o n |g l |i n |e', WrappingIndent.Indent); - assert.equal(mapper!.wrappedLinesIndent, ' \t'); + assert.equal(mapper!.wrappedTextIndentLength, ' '.length); }); test('issue #75494: surrogate pairs', () => { @@ -125,6 +125,6 @@ suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { test('CharacterHardWrappingLineMapper - WrappingIndent.DeepIndent', () => { let factory = new CharacterHardWrappingLineMapperFactory('', '\t '); let mapper = assertLineMapping(factory, 4, 26, ' W e A r e T e s t |i n g D e |e p I n d |e n t a t |i o n', WrappingIndent.DeepIndent); - assert.equal(mapper!.wrappedLinesIndent, ' \t\t'); + assert.equal(mapper!.wrappedTextIndentLength, ' '.length); }); }); diff --git a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts index 52dc680d78a6d..ba4228ce9a644 100644 --- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts +++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts @@ -23,7 +23,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; suite('Editor ViewModel - SplitLinesCollection', () => { test('SplitLine', () => { let model1 = createModel('My First LineMy Second LineAnd another one'); - let line1 = createSplitLine([13, 14, 15], ''); + let line1 = createSplitLine([13, 14, 15], [13, 13 + 14, 13 + 14 + 15], 0); assert.equal(line1.getViewLineCount(), 3); assert.equal(line1.getViewLineContent(model1, 1, 0), 'My First Line'); @@ -52,38 +52,38 @@ suite('Editor ViewModel - SplitLinesCollection', () => { } model1 = createModel('My First LineMy Second LineAnd another one'); - line1 = createSplitLine([13, 14, 15], '\t'); + line1 = createSplitLine([13, 14, 15], [13, 13 + 14, 13 + 14 + 15], 4); assert.equal(line1.getViewLineCount(), 3); assert.equal(line1.getViewLineContent(model1, 1, 0), 'My First Line'); - assert.equal(line1.getViewLineContent(model1, 1, 1), '\tMy Second Line'); - assert.equal(line1.getViewLineContent(model1, 1, 2), '\tAnd another one'); + assert.equal(line1.getViewLineContent(model1, 1, 1), ' My Second Line'); + assert.equal(line1.getViewLineContent(model1, 1, 2), ' And another one'); assert.equal(line1.getViewLineMaxColumn(model1, 1, 0), 14); - assert.equal(line1.getViewLineMaxColumn(model1, 1, 1), 16); - assert.equal(line1.getViewLineMaxColumn(model1, 1, 2), 17); - for (let col = 1; col <= 14; col++) { - assert.equal(line1.getModelColumnOfViewPosition(0, col), col, 'getInputColumnOfOutputPosition(0, ' + col + ')'); - } - for (let col = 1; col <= 1; col++) { - assert.equal(line1.getModelColumnOfViewPosition(1, 1), 13 + col, 'getInputColumnOfOutputPosition(1, ' + col + ')'); - } - for (let col = 2; col <= 16; col++) { - assert.equal(line1.getModelColumnOfViewPosition(1, col), 13 + col - 1, 'getInputColumnOfOutputPosition(1, ' + col + ')'); - } - for (let col = 1; col <= 1; col++) { - assert.equal(line1.getModelColumnOfViewPosition(2, col), 13 + 14 + col, 'getInputColumnOfOutputPosition(2, ' + col + ')'); - } - for (let col = 2; col <= 17; col++) { - assert.equal(line1.getModelColumnOfViewPosition(2, col), 13 + 14 + col - 1, 'getInputColumnOfOutputPosition(2, ' + col + ')'); + assert.equal(line1.getViewLineMaxColumn(model1, 1, 1), 19); + assert.equal(line1.getViewLineMaxColumn(model1, 1, 2), 20); + + let actualViewColumnMapping: number[][] = []; + for (let lineIndex = 0; lineIndex < line1.getViewLineCount(); lineIndex++) { + let actualLineViewColumnMapping: number[] = []; + for (let col = 1; col <= line1.getViewLineMaxColumn(model1, 1, lineIndex); col++) { + actualLineViewColumnMapping.push(line1.getModelColumnOfViewPosition(lineIndex, col)); + } + actualViewColumnMapping.push(actualLineViewColumnMapping); } + assert.deepEqual(actualViewColumnMapping, [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], + [14, 14, 14, 14, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28], + [28, 28, 28, 28, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43], + ]); + for (let col = 1; col <= 13; col++) { - assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(0, col), 'getOutputPositionOfInputPosition(' + col + ')'); + assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(0, col), '6.getOutputPositionOfInputPosition(' + col + ')'); } for (let col = 1 + 13; col <= 14 + 13; col++) { - assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(1, 1 + col - 13), 'getOutputPositionOfInputPosition(' + col + ')'); + assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(1, 4 + col - 13), '7.getOutputPositionOfInputPosition(' + col + ')'); } for (let col = 1 + 13 + 14; col <= 15 + 14 + 13; col++) { - assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(2, 1 + col - 13 - 14), 'getOutputPositionOfInputPosition(' + col + ')'); + assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(2, 4 + col - 13 - 14), '8.getOutputPositionOfInputPosition(' + col + ')'); } }); @@ -612,12 +612,12 @@ suite('SplitLinesCollection', () => { ] }, { - content: ' world");', - minColumn: 4, - maxColumn: 12, + content: ' world");', + minColumn: 13, + maxColumn: 21, tokens: [ - { endIndex: 9, value: 15 }, - { endIndex: 11, value: 16 }, + { endIndex: 18, value: 15 }, + { endIndex: 20, value: 16 }, ] }, { @@ -654,28 +654,28 @@ suite('SplitLinesCollection', () => { ] }, { - content: ' world, this is a ', - minColumn: 4, - maxColumn: 21, + content: ' world, this is a ', + minColumn: 13, + maxColumn: 30, tokens: [ - { endIndex: 20, value: 28 }, + { endIndex: 29, value: 28 }, ] }, { - content: ' somewhat longer ', - minColumn: 4, - maxColumn: 20, + content: ' somewhat longer ', + minColumn: 13, + maxColumn: 29, tokens: [ - { endIndex: 19, value: 28 }, + { endIndex: 28, value: 28 }, ] }, { - content: ' line");', - minColumn: 4, - maxColumn: 11, + content: ' line");', + minColumn: 13, + maxColumn: 20, tokens: [ - { endIndex: 8, value: 28 }, - { endIndex: 10, value: 29 }, + { endIndex: 17, value: 28 }, + { endIndex: 19, value: 29 }, ] }, { @@ -772,16 +772,16 @@ function pos(lineNumber: number, column: number): Position { return new Position(lineNumber, column); } -function createSplitLine(splitLengths: number[], wrappedLinesPrefix: string, isVisible: boolean = true): SplitLine { - return new SplitLine(createLineMapping(splitLengths, wrappedLinesPrefix), isVisible); +function createSplitLine(splitLengths: number[], breakingOffsetsVisibleColumn: number[], wrappedTextIndentWidth: number, isVisible: boolean = true): SplitLine { + return new SplitLine(createLineMapping(splitLengths, breakingOffsetsVisibleColumn, wrappedTextIndentWidth), isVisible); } -function createLineMapping(breakingLengths: number[], wrappedLinesPrefix: string): LineBreakingData { +function createLineMapping(breakingLengths: number[], breakingOffsetsVisibleColumn: number[], wrappedTextIndentWidth: number): LineBreakingData { let sums: number[] = []; for (let i = 0; i < breakingLengths.length; i++) { sums[i] = (i > 0 ? sums[i - 1] : 0) + breakingLengths[i]; } - return new LineBreakingData(sums, wrappedLinesPrefix); + return new LineBreakingData(sums, breakingOffsetsVisibleColumn, wrappedTextIndentWidth); } function createModel(text: string): ISimpleModel { From c476068dc7d19b803266588609ad473a881a075c Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 8 Jan 2020 13:09:00 +0100 Subject: [PATCH 057/315] Use better editor API --- .../contrib/welcome/walkThrough/browser/walkThroughInput.ts | 3 ++- .../contrib/welcome/walkThrough/browser/walkThroughPart.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts index 2b7a9668bd278..c8c3d2d6cb897 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts @@ -11,6 +11,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import * as marked from 'vs/base/common/marked/marked'; import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; +import { EndOfLinePreference } from 'vs/editor/common/model'; export class WalkThroughModel extends EditorModel { @@ -111,7 +112,7 @@ export class WalkThroughInput extends EditorInput { return ''; }; - const markdown = ref.object.textEditorModel.getLinesContent().join('\n'); + const markdown = ref.object.textEditorModel.getValue(EndOfLinePreference.LF); marked(markdown, { renderer }); return Promise.all(snippets) diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts index 149c916f53dd0..7b4b87ebad06b 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts @@ -39,6 +39,7 @@ import { Dimension, size } from 'vs/base/browser/dom'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { domEvent } from 'vs/base/browser/event'; +import { EndOfLinePreference } from 'vs/editor/common/model'; export const WALK_THROUGH_FOCUS = new RawContextKey('interactivePlaygroundFocus', false); @@ -278,7 +279,7 @@ export class WalkThroughPart extends BaseEditor { return; } - const content = model.main.textEditorModel.getLinesContent().join('\n'); + const content = model.main.textEditorModel.getValue(EndOfLinePreference.LF); if (!strings.endsWith(input.getResource().path, '.md')) { this.content.innerHTML = content; this.updateSizeClasses(); From 2d4510bd8702f50981918c2fcb7816a0a227d773 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 8 Jan 2020 14:50:53 +0100 Subject: [PATCH 058/315] ability to check/uncheck changes --- .../bulkEdit/browser/bulkEdit.contribution.ts | 7 +- .../contrib/bulkEdit/browser/bulkEdit.css | 25 ++ .../contrib/bulkEdit/browser/bulkEditPanel.ts | 33 +-- .../bulkEdit/browser/bulkEditPreview.ts | 235 +++++++++++++----- .../contrib/bulkEdit/browser/bulkEditTree.ts | 130 +++++++--- 5 files changed, 315 insertions(+), 115 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts index 3116faa32bf86..fbd30d0436162 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts @@ -31,8 +31,8 @@ class BulkEditPreviewContribution { return edit; } - const apply = await panel.setInput(edit); - if (!apply) { + const newEditOrUndefined = await panel.setInput(edit); + if (!newEditOrUndefined) { return { edits: [] }; } @@ -41,8 +41,7 @@ class BulkEditPreviewContribution { // this._panelService.hideActivePanel(); // } - // todo@joh get 'real' edit - return edit; + return newEditOrUndefined; } } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css index 14043b9d48429..e23be66097c3b 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css @@ -23,4 +23,29 @@ display: none; } +.monaco-workbench .bulk-edit-panel .monaco-tl-contents { + display: flex; +} + +.monaco-workbench .bulk-edit-panel .monaco-tl-contents .edit-checkbox { + height: 16px; + width: 16px; + min-height: 16px; + min-width: 16px; + align-self: center; + margin-right: 4px; + border: 1px solid transparent; + border-radius: 2px; +} + +.monaco-workbench .bulk-edit-panel .monaco-tl-contents .edit-checkbox.disabled { + opacity: .5; +} + +.monaco-workbench .bulk-edit-panel .monaco-tl-contents .edit-checkbox.codicon[class*='codicon-'] { + font-size: 13px; +} +.monaco-workbench .bulk-edit-panel .monaco-tl-contents .edit-checkbox.codicon:not(.checked)::before { + opacity: 0; +} diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts index 45593fb855808..11e71dfdfb419 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts @@ -41,7 +41,8 @@ export class BulkEditPanel extends Panel { private readonly _disposables = new DisposableStore(); private readonly _sessionDisposables = new DisposableStore(); - private _currentResolve?: (apply: boolean) => void; + private _currentResolve?: (edit?: WorkspaceEdit) => void; + private _currentInput?: BulkFileOperations; constructor( @IInstantiationService private readonly _instaService: IInstantiationService, @@ -74,10 +75,11 @@ export class BulkEditPanel extends Panel { this._tree = this._instaService.createInstance( WorkbenchAsyncDataTree, this.getId(), treeContainer, new BulkEditDelegate(), - [new TextEditElementRenderer(), this._instaService.createInstance(FileElementRenderer)], + [this._instaService.createInstance(TextEditElementRenderer), this._instaService.createInstance(FileElementRenderer)], this._instaService.createInstance(BulkEditDataSource), { - identityProvider: new BulkEditIdentityProvider() + identityProvider: new BulkEditIdentityProvider(), + expandOnlyOnTwistieClick: true } ); @@ -112,18 +114,20 @@ export class BulkEditPanel extends Panel { this.getContainer()!.dataset['state'] = state; } - async setInput(edit: WorkspaceEdit): Promise { + async setInput(edit: WorkspaceEdit): Promise { this._setState(State.Data); this._sessionDisposables.clear(); - if (this._currentResolve) { - this._currentResolve(false); + this._currentResolve(undefined); this._currentResolve = undefined; } const input = await this._instaService.invokeFunction(BulkFileOperations.create, edit); - this._sessionDisposables.add(this._instaService.createInstance(BulkEditPreviewProvider, input)); + const provider = this._instaService.createInstance(BulkEditPreviewProvider, input); + + this._sessionDisposables.add(provider); + this._currentInput = input; this._acceptAction.enabled = true; this._discardAction.enabled = true; @@ -146,32 +150,31 @@ export class BulkEditPanel extends Panel { this._setState(State.Message); this._sessionDisposables.clear(); if (this._currentResolve) { - this._currentResolve(accept); + this._currentResolve(accept ? this._currentInput?.asWorkspaceEdit() : undefined); this._acceptAction.enabled = false; this._discardAction.enabled = false; - this._tree.setInput(new BulkFileOperations([])); } } - private async _previewTextEditElement(edit: TextEditElement): Promise { + private async _previewTextEditElement(element: TextEditElement): Promise { let leftResource: URI; try { - (await this._textModelService.createModelReference(edit.parent.uri)).dispose(); - leftResource = edit.parent.uri; + (await this._textModelService.createModelReference(element.parent.uri)).dispose(); + leftResource = element.parent.uri; } catch { leftResource = BulkEditPreviewProvider.emptyPreview; } - const previewUri = BulkEditPreviewProvider.asPreviewUri(edit.parent.uri); + const previewUri = BulkEditPreviewProvider.asPreviewUri(element.parent.uri); this._editorService.openEditor({ leftResource, rightResource: previewUri, - label: localize('edt.title', "{0} (Refactor Preview)", this._labelService.getUriLabel(edit.parent.uri)), + label: localize('edt.title', "{0} (Refactor Preview)", this._labelService.getUriLabel(element.parent.uri)), options: { - selection: edit.edit.range + selection: element.edit.edit.range // preserveFocus, // pinned, // revealIfVisible: true diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts index 654ad0d836112..4f93c0b9ad1cb 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts @@ -8,14 +8,44 @@ import { URI } from 'vs/base/common/uri'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; -import { WorkspaceEdit, isResourceTextEdit, TextEdit } from 'vs/editor/common/modes'; +import { WorkspaceEdit, isResourceTextEdit, TextEdit, ResourceTextEdit, ResourceFileEdit } from 'vs/editor/common/modes'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { mergeSort } from 'vs/base/common/arrays'; +import { mergeSort, coalesceInPlace } from 'vs/base/common/arrays'; import { Range } from 'vs/editor/common/core/range'; import { EditOperation } from 'vs/editor/common/core/editOperation'; -import { values } from 'vs/base/common/map'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IFileService } from 'vs/platform/files/common/files'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; + +class CheckedObject { + + private _checked: boolean = true; + + constructor(protected _emitter: Emitter) { } + + updateChecked(checked: boolean) { + if (this._checked !== checked) { + this._checked = checked; + this._emitter.fire(this); + } + } + + isChecked(): boolean { + return this._checked; + } +} + +export class BulkTextEdit extends CheckedObject { + + constructor( + readonly parent: BulkFileOperation, + readonly edit: TextEdit, + emitter: Emitter + ) { + super(emitter); + } +} export const enum BulkFileOperationType { None = 0, @@ -24,44 +54,62 @@ export const enum BulkFileOperationType { Rename = 0b0100, } -export class BulkFileOperation { +export class BulkFileOperation extends CheckedObject { type = BulkFileOperationType.None; - textEdits: TextEdit[] = []; - - constructor(readonly uri: URI) { } + textEdits: BulkTextEdit[] = []; + originalEdits = new Map(); - addType(type: BulkFileOperationType) { - this.type += type; + constructor( + readonly uri: URI, + readonly parent: BulkFileOperations + ) { + super(parent._onDidChangeCheckedState); } - addEdits(edits: TextEdit[]) { - this.textEdits = this.textEdits.concat(edits); + addEdit(index: number, type: BulkFileOperationType, edit: ResourceTextEdit | ResourceFileEdit, ) { + this.type += type; + this.originalEdits.set(index, edit); + if (isResourceTextEdit(edit)) { + this.textEdits = this.textEdits.concat(edit.edits.map(edit => new BulkTextEdit(this, edit, this._emitter))); + } } } export class BulkFileOperations { static async create(accessor: ServicesAccessor, bulkEdit: WorkspaceEdit): Promise { + const result = accessor.get(IInstantiationService).createInstance(BulkFileOperations, bulkEdit); + return await result._init(); + } - const operationByResource = new Map(); + readonly _onDidChangeCheckedState = new Emitter(); + readonly onDidChangeCheckedState: Event = this._onDidChangeCheckedState.event; + + readonly fileOperations: BulkFileOperation[] = []; - const fileService = accessor.get(IFileService); + constructor( + private readonly _bulkEdit: WorkspaceEdit, + @IFileService private readonly _fileService: IFileService, + ) { } + + async _init() { + const operationByResource = new Map(); - for (const edit of bulkEdit.edits) { + for (let idx = 0; idx < this._bulkEdit.edits.length; idx++) { + const edit = this._bulkEdit.edits[idx]; let uri: URI; let type: BulkFileOperationType; - let textEdits: TextEdit[] | undefined; if (isResourceTextEdit(edit)) { type = BulkFileOperationType.None; uri = edit.resource; - textEdits = edit.edits; + } else if (edit.newUri && edit.oldUri) { type = BulkFileOperationType.Rename; uri = edit.oldUri; - if (edit.options?.overwrite === undefined && edit.options?.ignoreIfExists && await fileService.exists(uri)) { + if (edit.options?.overwrite === undefined && edit.options?.ignoreIfExists && await this._fileService.exists(uri)) { // noop -> "soft" rename to something that already exists continue; } @@ -69,7 +117,7 @@ export class BulkFileOperations { } else if (edit.oldUri) { type = BulkFileOperationType.Delete; uri = edit.oldUri; - if (edit.options?.ignoreIfNotExists && !await fileService.exists(uri)) { + if (edit.options?.ignoreIfNotExists && !await this._fileService.exists(uri)) { // noop -> "soft" delete something that doesn't exist continue; } @@ -77,7 +125,7 @@ export class BulkFileOperations { } else if (edit.newUri) { type = BulkFileOperationType.Create; uri = edit.newUri; - if (edit.options?.overwrite === undefined && edit.options?.ignoreIfExists && await fileService.exists(uri)) { + if (edit.options?.overwrite === undefined && edit.options?.ignoreIfExists && await this._fileService.exists(uri)) { // noop -> "soft" create something that already exists continue; } @@ -90,20 +138,64 @@ export class BulkFileOperations { const key = uri.toString(); let operation = operationByResource.get(key); if (!operation) { - operation = new BulkFileOperation(uri); + operation = new BulkFileOperation(uri, this); operationByResource.set(key, operation); } + operation.addEdit(idx, type, edit); + } + + operationByResource.forEach(value => this.fileOperations.push(value)); + return this; + } - operation.addType(type); - if (textEdits) { - operation.addEdits(textEdits); + asWorkspaceEdit(): WorkspaceEdit { + const result: WorkspaceEdit = { edits: [] }; + let allAccepted = true; + for (let file of this.fileOperations) { + + if (!file.isChecked()) { + allAccepted = false; + continue; } + + const keyOfEdit = (edit: TextEdit) => JSON.stringify(edit); + const checkedEdits = new Set(); + + for (let edit of file.textEdits) { + if (edit.isChecked()) { + checkedEdits.add(keyOfEdit(edit.edit)); + } + } + + file.originalEdits.forEach((value, idx) => { + + if (isResourceTextEdit(value)) { + let newValue: ResourceTextEdit = { ...value, edits: [] }; + let allEditsAccepted = true; + for (let edit of value.edits) { + if (!checkedEdits.has(keyOfEdit(edit))) { + allEditsAccepted = false; + } else { + newValue.edits.push(edit); + } + } + if (!allEditsAccepted) { + value = newValue; + allAccepted = false; + } + } + + result.edits[idx] = value; + }); } + if (!allAccepted) { + // only return a new edit when something has changed + coalesceInPlace(result.edits); + return result; + } + return this._bulkEdit; - return new BulkFileOperations(values(operationByResource)); } - - constructor(readonly fileOperations: BulkFileOperation[]) { } } export class BulkEditPreviewProvider implements ITextModelContentProvider { @@ -122,6 +214,7 @@ export class BulkEditPreviewProvider implements ITextModelContentProvider { private readonly _disposables = new DisposableStore(); private readonly _ready: Promise; + private readonly _modelPreviewEdits = new Map(); constructor( private readonly _operations: BulkFileOperations, @@ -130,54 +223,70 @@ export class BulkEditPreviewProvider implements ITextModelContentProvider { @ITextModelService private readonly _textModelResolverService: ITextModelService ) { this._disposables.add(this._textModelResolverService.registerTextModelContentProvider(BulkEditPreviewProvider.Schema, this)); - this._ready = this._prepareModels(); + this._ready = this._init(); } dispose(): void { this._disposables.dispose(); } - private async _prepareModels() { - - const getOrCreatePreviewModel = async (uri: URI) => { - const previewUri = BulkEditPreviewProvider.asPreviewUri(uri); - let model = this._modelService.getModel(previewUri); - if (!model) { - try { - // try: copy existing - const ref = await this._textModelResolverService.createModelReference(uri); - const sourceModel = ref.object.textEditorModel; - model = this._modelService.createModel( - createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()), - this._modeService.create(sourceModel.getLanguageIdentifier().language), - previewUri - ); - ref.dispose(); - - } catch { - // create NEW model - model = this._modelService.createModel( - '', - this._modeService.createByFilepathOrFirstLine(previewUri), - previewUri - ); - } + private async _init() { + for (let operation of this._operations.fileOperations) { + await this._applyTextEditsToPreviewModel(operation); + } + this._disposables.add(this._operations.onDidChangeCheckedState(element => { + let operation = element instanceof BulkFileOperation ? element : element.parent; + this._applyTextEditsToPreviewModel(operation); + })); + } + + private async _applyTextEditsToPreviewModel(operation: BulkFileOperation) { + const model = await this._getOrCreatePreviewModel(operation.uri); + + // undo edits that have been done before + let undoEdits = this._modelPreviewEdits.get(model.id); + if (undoEdits) { + model.applyEdits(undoEdits); + } + // compute new edits + const newEdits = mergeSort( + operation.textEdits.filter(edit => edit.isChecked() && edit.parent.isChecked()).map(edit => EditOperation.replaceMove(Range.lift(edit.edit.range), edit.edit.text)), + (a, b) => Range.compareRangesUsingStarts(a.range, b.range) + ); + // apply edits and keep undo edits + undoEdits = model.applyEdits(newEdits); + this._modelPreviewEdits.set(model.id, undoEdits); + } + + private async _getOrCreatePreviewModel(uri: URI) { + const previewUri = BulkEditPreviewProvider.asPreviewUri(uri); + let model = this._modelService.getModel(previewUri); + if (!model) { + try { + // try: copy existing + const ref = await this._textModelResolverService.createModelReference(uri); + const sourceModel = ref.object.textEditorModel; + model = this._modelService.createModel( + createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()), + this._modeService.create(sourceModel.getLanguageIdentifier().language), + previewUri + ); + ref.dispose(); + + } catch { + // create NEW model + model = this._modelService.createModel( + '', + this._modeService.createByFilepathOrFirstLine(previewUri), + previewUri + ); } // this is a little weird but otherwise editors and other cusomers // will dispose my models before they should be disposed... // And all of this is off the eventloop to prevent endless recursion new Promise(async () => this._disposables.add(await this._textModelResolverService.createModelReference(model!.uri))); - return model; - }; - - for (let operation of this._operations.fileOperations) { - const model = await getOrCreatePreviewModel(operation.uri); - const editOperations = mergeSort( - operation.textEdits.map(edit => EditOperation.replaceMove(Range.lift(edit.range), edit.text)), - (a, b) => Range.compareRangesUsingStarts(a.range, b.range) - ); - model.applyEdits(editOperations); } + return model; } async provideTextContent(previewUri: URI) { diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index 13fa844ffb5e2..b1c5e7b821979 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { IAsyncDataSource, ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree'; -import * as modes from 'vs/editor/common/modes'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { IResourceLabel, ResourceLabels, DEFAULT_LABELS_CONTAINER } from 'vs/workbench/browser/labels'; @@ -15,11 +14,14 @@ import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list import { Range } from 'vs/editor/common/core/range'; import * as dom from 'vs/base/browser/dom'; import { ITextModel } from 'vs/editor/common/model'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { TextModel } from 'vs/editor/common/model/textModel'; import { ILabelService } from 'vs/platform/label/common/label'; -import { BulkFileOperations, BulkFileOperation, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; +import { BulkFileOperations, BulkFileOperation, BulkFileOperationType, BulkTextEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; import { localize } from 'vs/nls'; +import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { attachCheckboxStyler } from 'vs/platform/theme/common/styler'; // --- VIEW MODEL @@ -42,14 +44,13 @@ export class FileElement { this.uri = edit.uri; this.typeLabel = FileElement._typeLabels[edit.type] || ''; } - } export class TextEditElement { constructor( readonly parent: FileElement, - readonly edit: modes.TextEdit, + readonly edit: BulkTextEdit, readonly prefix: string, readonly selecting: string, readonly inserting: string, readonly suffix: string ) { } } @@ -94,7 +95,7 @@ export class BulkEditDataSource implements IAsyncDataSource { - const range = Range.lift(edit.range); + const range = Range.lift(edit.edit.range); const tokens = textModel.getLineTokens(range.endLineNumber); let suffixLen = 0; @@ -107,7 +108,7 @@ export class BulkEditDataSource implements IAsyncDataSource { + if (this._element) { + this._element.edit.updateChecked(_checkbox.checked); + } + })); + this._disposables.add(attachCheckboxStyler(_checkbox, themeService)); + } + + dispose(): void { + this._element = undefined; + this._disposables.dispose(); + this._checkbox.dispose(); + this._label.dispose(); + } + + set(element: FileElement, matches: FuzzyScore | undefined) { + this._element = element; + this._checkbox.checked = element.edit.isChecked(); + this._label.setResource({ + name: this._labelService.getUriLabel(element.uri, { relative: true }), + description: element.typeLabel, + resource: element.uri, + }, { + matches: createMatches(matches), + }); + } } export class FileElementRenderer implements ITreeRenderer { @@ -148,10 +184,9 @@ export class FileElementRenderer implements ITreeRenderer, _index: number, template: FileElementTemplate): void { - - template.label.setResource({ - name: this._labelService.getUriLabel(node.element.uri, { relative: true }), - description: node.element.typeLabel, - resource: node.element.uri, - }, { - matches: createMatches(node.filterData), - }); + template.set(node.element, node.filterData); } disposeTemplate(template: FileElementTemplate): void { - template.label.dispose(); + template.dispose(); } } class TextEditElementTemplate { - constructor(readonly label: HighlightedLabel) { } -} -export class TextEditElementRenderer implements ITreeRenderer { + private readonly _disposables = new DisposableStore(); + private readonly _localDisposables = new DisposableStore(); - static readonly id = 'TextEditElementRenderer'; + constructor( + private readonly _checkbox: Checkbox, + private readonly _label: HighlightedLabel, + @IThemeService themeService: IThemeService + ) { - readonly templateId: string = TextEditElementRenderer.id; + this._disposables.add(attachCheckboxStyler(_checkbox, themeService)); + } - renderTemplate(container: HTMLElement): TextEditElementTemplate { - const label = new HighlightedLabel(container, false); - dom.addClass(label.element, 'textedit'); - return new TextEditElementTemplate(label); + dispose(): void { + this._localDisposables.dispose(); + this._disposables.dispose(); + this._checkbox.dispose(); } - renderElement({ element }: ITreeNode, _index: number, template: TextEditElementTemplate): void { + set(element: TextEditElement) { + this._localDisposables.clear(); + this._localDisposables.add(this._checkbox.onChange(() => element.edit.updateChecked(this._checkbox.checked))); + this._localDisposables.add(element.edit.parent.parent.onDidChangeCheckedState(() => { + dom.toggleClass(this._checkbox.domNode, 'disabled', !element.edit.parent.isChecked()); + })); let value = ''; value += element.prefix; @@ -205,7 +246,30 @@ export class TextEditElementRenderer implements ITreeRenderer { + + static readonly id = 'TextEditElementRenderer'; + + readonly templateId: string = TextEditElementRenderer.id; + + constructor(@IInstantiationService private readonly _instaService: IInstantiationService) { } + + renderTemplate(container: HTMLElement): TextEditElementTemplate { + const checkbox = new Checkbox({ actionClassName: 'codicon-check edit-checkbox', isChecked: true, title: '' }); + container.appendChild(checkbox.domNode); + + const label = new HighlightedLabel(container, false); + dom.addClass(label.element, 'textedit'); + return this._instaService.createInstance(TextEditElementTemplate, checkbox, label); + } + + renderElement({ element }: ITreeNode, _index: number, template: TextEditElementTemplate): void { + template.set(element); } disposeTemplate(_template: TextEditElementTemplate): void { } From c4b499dc63beb64bf3e4564af4c1184f8a72a568 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 8 Jan 2020 14:57:46 +0100 Subject: [PATCH 059/315] Clarify members visibility --- .../pieceTreeTextBuffer/pieceTreeBase.ts | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts index 1c844dd66c4bb..984931ce7c780 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts @@ -728,7 +728,7 @@ export class PieceTreeBase { // #endregion // #region Piece Table - insert(offset: number, value: string, eolNormalized: boolean = false): void { + public insert(offset: number, value: string, eolNormalized: boolean = false): void { this._EOLNormalized = this._EOLNormalized && eolNormalized; this._lastVisitedLine.lineNumber = 0; this._lastVisitedLine.value = ''; @@ -826,7 +826,7 @@ export class PieceTreeBase { this.computeBufferMetadata(); } - delete(offset: number, cnt: number): void { + public delete(offset: number, cnt: number): void { this._lastVisitedLine.lineNumber = 0; this._lastVisitedLine.value = ''; @@ -899,7 +899,7 @@ export class PieceTreeBase { this.computeBufferMetadata(); } - insertContentToNodeLeft(value: string, node: TreeNode) { + private insertContentToNodeLeft(value: string, node: TreeNode) { // we are inserting content to the beginning of node let nodesToDel: TreeNode[] = []; if (this.shouldCheckCRLF() && this.endWithCR(value) && this.startWithLF(node)) { @@ -934,7 +934,7 @@ export class PieceTreeBase { this.deleteNodes(nodesToDel); } - insertContentToNodeRight(value: string, node: TreeNode) { + private insertContentToNodeRight(value: string, node: TreeNode) { // we are inserting to the right of this node. if (this.adjustCarriageReturnFromNext(value, node)) { // move \n to the new node. @@ -952,9 +952,9 @@ export class PieceTreeBase { this.validateCRLFWithPrevNode(newNode); } - positionInBuffer(node: TreeNode, remainder: number): BufferCursor; - positionInBuffer(node: TreeNode, remainder: number, ret: BufferCursor): null; - positionInBuffer(node: TreeNode, remainder: number, ret?: BufferCursor): BufferCursor | null { + private positionInBuffer(node: TreeNode, remainder: number): BufferCursor; + private positionInBuffer(node: TreeNode, remainder: number, ret: BufferCursor): null; + private positionInBuffer(node: TreeNode, remainder: number, ret?: BufferCursor): BufferCursor | null { let piece = node.piece; let bufferIndex = node.piece.bufferIndex; let lineStarts = this._buffers[bufferIndex].lineStarts; @@ -1002,7 +1002,7 @@ export class PieceTreeBase { }; } - getLineFeedCnt(bufferIndex: number, start: BufferCursor, end: BufferCursor): number { + private getLineFeedCnt(bufferIndex: number, start: BufferCursor, end: BufferCursor): number { // we don't need to worry about start: abc\r|\n, or abc|\r, or abc|\n, or abc|\r\n doesn't change the fact that, there is one line break after start. // now let's take care of end: abc\r|\n, if end is in between \r and \n, we need to add line feed count by 1 if (end.column === 0) { @@ -1032,18 +1032,18 @@ export class PieceTreeBase { } } - offsetInBuffer(bufferIndex: number, cursor: BufferCursor): number { + private offsetInBuffer(bufferIndex: number, cursor: BufferCursor): number { let lineStarts = this._buffers[bufferIndex].lineStarts; return lineStarts[cursor.line] + cursor.column; } - deleteNodes(nodes: TreeNode[]): void { + private deleteNodes(nodes: TreeNode[]): void { for (let i = 0; i < nodes.length; i++) { rbDelete(this, nodes[i]); } } - createNewPieces(text: string): Piece[] { + private createNewPieces(text: string): Piece[] { if (text.length > AverageBufferSize) { // the content is large, operations like substring, charCode becomes slow // so here we split it into smaller chunks, just like what we did for CR/LF normalization @@ -1128,11 +1128,11 @@ export class PieceTreeBase { return [newPiece]; } - getLinesRawContent(): string { + public getLinesRawContent(): string { return this.getContentOfSubTree(this.root); } - getLineRawContent(lineNumber: number, endOffset: number = 0): string { + public getLineRawContent(lineNumber: number, endOffset: number = 0): string { let x = this.root; let ret = ''; @@ -1204,7 +1204,7 @@ export class PieceTreeBase { return ret; } - computeBufferMetadata() { + private computeBufferMetadata() { let x = this.root; let lfCnt = 1; @@ -1222,7 +1222,7 @@ export class PieceTreeBase { } // #region node operations - getIndexOf(node: TreeNode, accumulatedValue: number): { index: number, remainder: number } { + private getIndexOf(node: TreeNode, accumulatedValue: number): { index: number, remainder: number } { let piece = node.piece; let pos = this.positionInBuffer(node, accumulatedValue); let lineCnt = pos.line - piece.start.line; @@ -1239,7 +1239,7 @@ export class PieceTreeBase { return { index: lineCnt, remainder: pos.column }; } - getAccumulatedValue(node: TreeNode, index: number) { + private getAccumulatedValue(node: TreeNode, index: number) { if (index < 0) { return 0; } @@ -1253,7 +1253,7 @@ export class PieceTreeBase { } } - deleteNodeTail(node: TreeNode, pos: BufferCursor) { + private deleteNodeTail(node: TreeNode, pos: BufferCursor) { const piece = node.piece; const originalLFCnt = piece.lineFeedCnt; const originalEndOffset = this.offsetInBuffer(piece.bufferIndex, piece.end); @@ -1277,7 +1277,7 @@ export class PieceTreeBase { updateTreeMetadata(this, node, size_delta, lf_delta); } - deleteNodeHead(node: TreeNode, pos: BufferCursor) { + private deleteNodeHead(node: TreeNode, pos: BufferCursor) { const piece = node.piece; const originalLFCnt = piece.lineFeedCnt; const originalStartOffset = this.offsetInBuffer(piece.bufferIndex, piece.start); @@ -1299,7 +1299,7 @@ export class PieceTreeBase { updateTreeMetadata(this, node, size_delta, lf_delta); } - shrinkNode(node: TreeNode, start: BufferCursor, end: BufferCursor) { + private shrinkNode(node: TreeNode, start: BufferCursor, end: BufferCursor) { const piece = node.piece; const originalStartPos = piece.start; const originalEndPos = piece.end; @@ -1334,7 +1334,7 @@ export class PieceTreeBase { this.validateCRLFWithPrevNode(newNode); } - appendToNode(node: TreeNode, value: string): void { + private appendToNode(node: TreeNode, value: string): void { if (this.adjustCarriageReturnFromNext(value, node)) { value += '\n'; } @@ -1374,7 +1374,7 @@ export class PieceTreeBase { updateTreeMetadata(this, node, value.length, lf_delta); } - nodeAt(offset: number): NodePosition { + private nodeAt(offset: number): NodePosition { let x = this.root; let cache = this._searchCache.get(offset); if (cache) { @@ -1409,7 +1409,7 @@ export class PieceTreeBase { return null!; } - nodeAt2(lineNumber: number, column: number): NodePosition { + private nodeAt2(lineNumber: number, column: number): NodePosition { let x = this.root; let nodeStartOffset = 0; @@ -1476,7 +1476,7 @@ export class PieceTreeBase { return null!; } - nodeCharCodeAt(node: TreeNode, offset: number): number { + private nodeCharCodeAt(node: TreeNode, offset: number): number { if (node.piece.lineFeedCnt < 1) { return -1; } @@ -1485,7 +1485,7 @@ export class PieceTreeBase { return buffer.buffer.charCodeAt(newOffset); } - offsetOfNode(node: TreeNode): number { + private offsetOfNode(node: TreeNode): number { if (!node) { return 0; } @@ -1504,11 +1504,11 @@ export class PieceTreeBase { // #endregion // #region CRLF - shouldCheckCRLF() { + private shouldCheckCRLF() { return !(this._EOLNormalized && this._EOL === '\n'); } - startWithLF(val: string | TreeNode): boolean { + private startWithLF(val: string | TreeNode): boolean { if (typeof val === 'string') { return val.charCodeAt(0) === 10; } @@ -1532,7 +1532,7 @@ export class PieceTreeBase { return this._buffers[piece.bufferIndex].buffer.charCodeAt(startOffset) === 10; } - endWithCR(val: string | TreeNode): boolean { + private endWithCR(val: string | TreeNode): boolean { if (typeof val === 'string') { return val.charCodeAt(val.length - 1) === 13; } @@ -1544,7 +1544,7 @@ export class PieceTreeBase { return this.nodeCharCodeAt(val, val.piece.length - 1) === 13; } - validateCRLFWithPrevNode(nextNode: TreeNode) { + private validateCRLFWithPrevNode(nextNode: TreeNode) { if (this.shouldCheckCRLF() && this.startWithLF(nextNode)) { let node = nextNode.prev(); if (this.endWithCR(node)) { @@ -1553,7 +1553,7 @@ export class PieceTreeBase { } } - validateCRLFWithNextNode(node: TreeNode) { + private validateCRLFWithNextNode(node: TreeNode) { if (this.shouldCheckCRLF() && this.endWithCR(node)) { let nextNode = node.next(); if (this.startWithLF(nextNode)) { @@ -1562,7 +1562,7 @@ export class PieceTreeBase { } } - fixCRLF(prev: TreeNode, next: TreeNode) { + private fixCRLF(prev: TreeNode, next: TreeNode) { let nodesToDel: TreeNode[] = []; // update node let lineStarts = this._buffers[prev.piece.bufferIndex].lineStarts; @@ -1617,7 +1617,7 @@ export class PieceTreeBase { } } - adjustCarriageReturnFromNext(value: string, node: TreeNode): boolean { + private adjustCarriageReturnFromNext(value: string, node: TreeNode): boolean { if (this.shouldCheckCRLF() && this.endWithCR(value)) { let nextNode = node.next(); if (this.startWithLF(nextNode)) { @@ -1667,7 +1667,7 @@ export class PieceTreeBase { return callback(node) && this.iterate(node.right, callback); } - getNodeContent(node: TreeNode) { + private getNodeContent(node: TreeNode) { if (node === SENTINEL) { return ''; } @@ -1695,7 +1695,7 @@ export class PieceTreeBase { * / * z */ - rbInsertRight(node: TreeNode | null, p: Piece): TreeNode { + private rbInsertRight(node: TreeNode | null, p: Piece): TreeNode { let z = new TreeNode(p, NodeColor.Red); z.left = SENTINEL; z.right = SENTINEL; @@ -1727,7 +1727,7 @@ export class PieceTreeBase { * \ * z */ - rbInsertLeft(node: TreeNode | null, p: Piece): TreeNode { + private rbInsertLeft(node: TreeNode | null, p: Piece): TreeNode { let z = new TreeNode(p, NodeColor.Red); z.left = SENTINEL; z.right = SENTINEL; @@ -1751,7 +1751,7 @@ export class PieceTreeBase { return z; } - getContentOfSubTree(node: TreeNode): string { + private getContentOfSubTree(node: TreeNode): string { let str = ''; this.iterate(node, node => { From fae5b730f89be9d89fc5765f802cd23e73dbe1bc Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 8 Jan 2020 15:04:29 +0100 Subject: [PATCH 060/315] Make PieceTreeBase.getLinesContent a lot faster --- .../pieceTreeTextBuffer/pieceTreeBase.ts | 88 ++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts index 984931ce7c780..ac23e98889c56 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts @@ -511,7 +511,93 @@ export class PieceTreeBase { } public getLinesContent(): string[] { - return this.getContentOfSubTree(this.root).split(/\r\n|\r|\n/); + let lines: string[] = []; + let linesLength = 0; + let currentLine = ''; + let danglingCR = false; + + this.iterate(this.root, node => { + if (node === SENTINEL) { + return true; + } + + const piece = node.piece; + let pieceLength = piece.length; + if (pieceLength === 0) { + return true; + } + + const buffer = this._buffers[piece.bufferIndex].buffer; + const lineStarts = this._buffers[piece.bufferIndex].lineStarts; + + const pieceStartLine = piece.start.line; + const pieceEndLine = piece.end.line; + let pieceStartOffset = lineStarts[pieceStartLine] + piece.start.column; + + if (danglingCR) { + if (buffer.charCodeAt(pieceStartOffset) === CharCode.LineFeed) { + // pretend the \n was in the previous piece.. + pieceStartOffset++; + pieceLength--; + } + lines[linesLength++] = currentLine; + currentLine = ''; + danglingCR = false; + if (pieceLength === 0) { + return true; + } + } + + if (pieceStartLine === pieceEndLine) { + // this piece has no new lines + if (!this._EOLNormalized && buffer.charCodeAt(pieceStartOffset + pieceLength - 1) === CharCode.CarriageReturn) { + danglingCR = true; + currentLine += buffer.substr(pieceStartOffset, pieceLength - 1); + } else { + currentLine += buffer.substr(pieceStartOffset, pieceLength); + } + return true; + } + + // add the text before the first line start in this piece + currentLine += ( + this._EOLNormalized + ? buffer.substring(pieceStartOffset, Math.max(pieceStartOffset, lineStarts[pieceStartLine + 1] - this._EOLLength)) + : buffer.substring(pieceStartOffset, lineStarts[pieceStartLine + 1]).replace(/(\r\n|\r|\n)$/, '') + ); + lines[linesLength++] = currentLine; + + for (let line = pieceStartLine + 1; line < pieceEndLine; line++) { + currentLine = ( + this._EOLNormalized + ? buffer.substring(lineStarts[line], lineStarts[line + 1] - this._EOLLength) + : buffer.substring(lineStarts[line], lineStarts[line + 1]).replace(/(\r\n|\r|\n)$/, '') + ); + lines[linesLength++] = currentLine; + } + + if (!this._EOLNormalized && buffer.charCodeAt(lineStarts[pieceEndLine] + piece.end.column - 1) === CharCode.CarriageReturn) { + danglingCR = true; + if (piece.end.column === 0) { + // The last line ended with a \r, let's undo the push, it will be pushed by next iteration + linesLength--; + } else { + currentLine = buffer.substr(lineStarts[pieceEndLine], piece.end.column - 1); + } + } else { + currentLine = buffer.substr(lineStarts[pieceEndLine], piece.end.column); + } + + return true; + }); + + if (danglingCR) { + lines[linesLength++] = currentLine; + currentLine = ''; + } + + lines[linesLength++] = currentLine; + return lines; } public getLength(): number { From ed4173796d08cdbbddcbcd0fa3bca74ee41dee41 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 8 Jan 2020 17:08:48 +0100 Subject: [PATCH 061/315] ts sem --- .../src/features/semanticColoring.ts | 210 ++++++++++++++++++ .../src/languageProvider.ts | 1 + 2 files changed, 211 insertions(+) create mode 100644 extensions/typescript-language-features/src/features/semanticColoring.ts diff --git a/extensions/typescript-language-features/src/features/semanticColoring.ts b/extensions/typescript-language-features/src/features/semanticColoring.ts new file mode 100644 index 0000000000000..10a92c50bd2fb --- /dev/null +++ b/extensions/typescript-language-features/src/features/semanticColoring.ts @@ -0,0 +1,210 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { ITypeScriptServiceClient, ExecConfig, ServerResponse } from '../typescriptService'; +import * as Proto from '../protocol'; + +enum TokenType { + 'class', + 'enum', + 'interface', + 'namespace', + 'typeParameter', + 'type', + 'parameter', + 'variable', + 'property', + 'constant', + 'function', + 'member', + _sentinel +} + + +enum TokenModifier { + 'declaration', + 'static', + 'async', + _sentinel +} + +class SemanticTokensProvider implements vscode.SemanticTokensProvider { + + constructor( + private readonly client: ITypeScriptServiceClient + ) { + } + + getLegend(): vscode.SemanticTokensLegend { + const tokenTypes = []; + for (let i = 0; i < TokenType._sentinel; i++) { + tokenTypes.push(TokenType[i]); + } + const tokenModifiers = []; + for (let i = 0; i < TokenModifier._sentinel; i++) { + tokenModifiers.push(TokenModifier[i]); + } + return new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers); + } + + async provideSemanticTokens(document: vscode.TextDocument, _options: vscode.SemanticTokensRequestOptions, token: vscode.CancellationToken): Promise { + const file = this.client.toOpenedFilePath(document); + if (!file) { + return null; + } + + const args: ExperimentalProtocol.EncodedSemanticClassificationsRequestArgs = { + file: file, + start: 0, + length: document.getText().length, + }; + + const versionBeforeRequest = document.version; + + const response = await (this.client as ExperimentalProtocol.IExtendedTypeScriptServiceClient).execute('encodedSemanticClassifications-full', args, token); + const versionAfterRequest = document.version; + + if (versionBeforeRequest !== versionAfterRequest) { + // A new request will come in soon... + return null; + } + + if (response.type !== 'response') { + return null; + } + if (!response.body) { + return null; + } + + const builder = new vscode.SemanticTokensBuilder(); + + const tsTokens = response.body.spans; + for (let i = 0, len = Math.floor(tsTokens.length / 3); i < len; i++) { + + const tokenType = tokenTypeMap[tsTokens[3 * i + 2]]; + if (typeof tokenType === 'number') { + console.log(TokenType[tokenType]); + const offset = tsTokens[3 * i]; + const length = tsTokens[3 * i + 1]; + + // we can use the document's range conversion methods because + // the result is at the same version as the document + const startPos = document.positionAt(offset); + const endPos = document.positionAt(offset + length); + + for (let line = startPos.line; line <= endPos.line; line++) { + const startCharacter = (line === startPos.line ? startPos.character : 0); + const endCharacter = (line === endPos.line ? endPos.character : document.lineAt(line).text.length); + builder.push(line, startCharacter, endCharacter - startCharacter, tokenType, 0); + } + } + } + + return new vscode.SemanticTokens(builder.build()); + } + +} + +export function register( + selector: vscode.DocumentSelector, + client: ITypeScriptServiceClient +) { + const provider = new SemanticTokensProvider(client); + return vscode.languages.registerSemanticTokensProvider(selector, provider, provider.getLegend()); +} + +const tokenTypeMap: number[] = []; +tokenTypeMap[ExperimentalProtocol.ClassificationType.className] = TokenType.class; +tokenTypeMap[ExperimentalProtocol.ClassificationType.enumName] = TokenType.enum; +tokenTypeMap[ExperimentalProtocol.ClassificationType.interfaceName] = TokenType.interface; +tokenTypeMap[ExperimentalProtocol.ClassificationType.moduleName] = TokenType.namespace; +tokenTypeMap[ExperimentalProtocol.ClassificationType.typeParameterName] = TokenType.typeParameter; +tokenTypeMap[ExperimentalProtocol.ClassificationType.typeAliasName] = TokenType.type; +tokenTypeMap[ExperimentalProtocol.ClassificationType.parameterName] = TokenType.parameter; + +export namespace ExperimentalProtocol { + + export interface IExtendedTypeScriptServiceClient { + execute( + command: K, + args: ExperimentalProtocol.ExtendedTsServerRequests[K][0], + token: vscode.CancellationToken, + config?: ExecConfig + ): Promise>; + } + + /** + * A request to get encoded semantic classifications for a span in the file + */ + export interface EncodedSemanticClassificationsRequest extends Proto.FileRequest { + arguments: EncodedSemanticClassificationsRequestArgs; + } + + /** + * Arguments for EncodedSemanticClassificationsRequest request. + */ + export interface EncodedSemanticClassificationsRequestArgs extends Proto.FileRequestArgs { + /** + * Start position of the span. + */ + start: number; + /** + * Length of the span. + */ + length: number; + } + + export const enum EndOfLineState { + None, + InMultiLineCommentTrivia, + InSingleQuoteStringLiteral, + InDoubleQuoteStringLiteral, + InTemplateHeadOrNoSubstitutionTemplate, + InTemplateMiddleOrTail, + InTemplateSubstitutionPosition, + } + + export const enum ClassificationType { + comment = 1, + identifier = 2, + keyword = 3, + numericLiteral = 4, + operator = 5, + stringLiteral = 6, + regularExpressionLiteral = 7, + whiteSpace = 8, + text = 9, + punctuation = 10, + className = 11, + enumName = 12, + interfaceName = 13, + moduleName = 14, + typeParameterName = 15, + typeAliasName = 16, + parameterName = 17, + docCommentTagName = 18, + jsxOpenTagName = 19, + jsxCloseTagName = 20, + jsxSelfClosingTagName = 21, + jsxAttribute = 22, + jsxText = 23, + jsxAttributeStringLiteralValue = 24, + bigintLiteral = 25, + } + + export interface EncodedSemanticClassificationsResponse extends Proto.Response { + body?: { + endOfLineState: EndOfLineState; + spans: number[]; + }; + } + + export interface ExtendedTsServerRequests { + 'encodedSemanticClassifications-full': [ExperimentalProtocol.EncodedSemanticClassificationsRequestArgs, ExperimentalProtocol.EncodedSemanticClassificationsResponse]; + } +} + + diff --git a/extensions/typescript-language-features/src/languageProvider.ts b/extensions/typescript-language-features/src/languageProvider.ts index 0d434f24ff656..9d06ecdeddc53 100644 --- a/extensions/typescript-language-features/src/languageProvider.ts +++ b/extensions/typescript-language-features/src/languageProvider.ts @@ -78,6 +78,7 @@ export default class LanguageProvider extends Disposable { import('./features/signatureHelp').then(provider => this._register(provider.register(selector, this.client))), import('./features/tagClosing').then(provider => this._register(provider.register(selector, this.description.id, this.client))), import('./features/typeDefinitions').then(provider => this._register(provider.register(selector, this.client))), + import('./features/semanticColoring').then(provider => this._register(provider.register(selector, this.client))), ]); } From 9971b4a9c6728287e9fafd63cb8b6e98a2841749 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 8 Jan 2020 18:15:20 +0100 Subject: [PATCH 062/315] ViewPane instead of Panel --- src/vs/base/browser/ui/splitview/paneview.ts | 4 +- .../bulkEdit/browser/bulkEdit.contribution.ts | 43 +++++++++++++------ .../{bulkEditPanel.ts => bulkEditPane.ts} | 40 +++++++++-------- 3 files changed, 53 insertions(+), 34 deletions(-) rename src/vs/workbench/contrib/bulkEdit/browser/{bulkEditPanel.ts => bulkEditPane.ts} (82%) diff --git a/src/vs/base/browser/ui/splitview/paneview.ts b/src/vs/base/browser/ui/splitview/paneview.ts index 037ffdce27b9c..e50add0846a28 100644 --- a/src/vs/base/browser/ui/splitview/paneview.ts +++ b/src/vs/base/browser/ui/splitview/paneview.ts @@ -44,8 +44,8 @@ export abstract class Pane extends Disposable implements IView { private static readonly HEADER_SIZE = 22; readonly element: HTMLElement; - private header!: HTMLElement; - private body!: HTMLElement; + protected header!: HTMLElement; + protected body!: HTMLElement; protected _expanded: boolean; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts index fbd30d0436162..1882e742a445d 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts @@ -9,9 +9,11 @@ import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } fr import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { WorkspaceEdit } from 'vs/editor/common/modes'; -import { BulkEditPanel } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPanel'; -import { Extensions as PanelExtensions, PanelDescriptor, PanelRegistry } from 'vs/workbench/browser/panel'; +import { BulkEditPanel as BulkEditPane } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPane'; +import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry } from 'vs/workbench/common/views'; import { localize } from 'vs/nls'; +import { ViewPaneContainer, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { PaneCompositePanel } from 'vs/workbench/browser/panel'; class BulkEditPreviewContribution { @@ -19,19 +21,22 @@ class BulkEditPreviewContribution { @IPanelService private _panelService: IPanelService, @IBulkEditService bulkEditService: IBulkEditService, ) { - bulkEditService.setPreviewHandler(edit => this._previewEdit(edit)); } private async _previewEdit(edit: WorkspaceEdit) { - const panel = this._panelService.openPanel(BulkEditPanel.ID, true); - if (!(panel instanceof BulkEditPanel)) { - // error? + let view: ViewPane | undefined; + const activePanel = this._panelService.openPanel(BulkEditPane.ID, true); + if (activePanel instanceof PaneCompositePanel) { + view = activePanel.getViewPaneContainer().getView(BulkEditPane.ID); + } + + if (!(view instanceof BulkEditPane)) { return edit; } - const newEditOrUndefined = await panel.setInput(edit); + const newEditOrUndefined = await view.setInput(edit); if (!newEditOrUndefined) { return { edits: [] }; } @@ -49,10 +54,20 @@ Registry.as(WorkbenchExtensions.Workbench).regi BulkEditPreviewContribution, LifecyclePhase.Ready ); -Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( - BulkEditPanel, - BulkEditPanel.ID, - localize('panel', "Refactor Preview"), - 'bulkEditPanel', - 10 -)); + +const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ + id: BulkEditPane.ID, + name: localize('panel', "Refactor Preview"), + ctorDescriptor: { + ctor: ViewPaneContainer, + arguments: [BulkEditPane.ID, '', { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }] + } +}, ViewContainerLocation.Panel); + +Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ + id: BulkEditPane.ID, + name: localize('panel', "Refactor Preview"), + canToggleVisibility: false, + ctorDescriptor: { ctor: BulkEditPane }, +}], container); + diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts similarity index 82% rename from src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts rename to src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index 11e71dfdfb419..610130d47f1b9 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -4,16 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./bulkEdit'; -import { Panel } from 'vs/workbench/browser/panel'; -import { Dimension } from 'vs/base/browser/dom'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { WorkspaceEdit } from 'vs/editor/common/modes'; import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; import { FuzzyScore } from 'vs/base/common/filters'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; -import { IStorageService } from 'vs/platform/storage/common/storage'; +import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { Action } from 'vs/base/common/actions'; import { diffInserted, diffRemoved } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; @@ -23,13 +19,18 @@ import { BulkEditPreviewProvider, BulkFileOperations } from 'vs/workbench/contri import { ILabelService } from 'vs/platform/label/common/label'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { URI } from 'vs/base/common/uri'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; const enum State { Data = 'data', Message = 'message' } -export class BulkEditPanel extends Panel { +export class BulkEditPanel extends ViewPane { static readonly ID = 'BulkEditPanel'; @@ -49,11 +50,15 @@ export class BulkEditPanel extends Panel { @IEditorService private readonly _editorService: IEditorService, @ILabelService private readonly _labelService: ILabelService, @ITextModelService private readonly _textModelService: ITextModelService, - @ITelemetryService telemetryService: ITelemetryService, - @IThemeService themeService: IThemeService, - @IStorageService storageService: IStorageService, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService configurationService: IConfigurationService, + @IContextKeyService contextKeyService: IContextKeyService ) { - super(BulkEditPanel.ID, telemetryService, themeService, storageService); + super( + { id: BulkEditPanel.ID, title: localize('title', "Refactor Preview") }, + keybindingService, contextMenuService, configurationService, contextKeyService + ); } dispose(): void { @@ -61,8 +66,7 @@ export class BulkEditPanel extends Panel { this._disposables.dispose(); } - create(parent: HTMLElement): void { - super.create(parent); + protected renderBody(parent: HTMLElement): void { parent.className = 'bulk-edit-panel'; // tree @@ -73,7 +77,7 @@ export class BulkEditPanel extends Panel { parent.appendChild(treeContainer); this._tree = this._instaService.createInstance( - WorkbenchAsyncDataTree, this.getId(), treeContainer, + WorkbenchAsyncDataTree, this.id, treeContainer, new BulkEditDelegate(), [this._instaService.createInstance(TextEditElementRenderer), this._instaService.createInstance(FileElementRenderer)], this._instaService.createInstance(BulkEditDataSource), @@ -88,7 +92,7 @@ export class BulkEditPanel extends Panel { if (first instanceof TextEditElement) { this._previewTextEditElement(first); } else if (first instanceof FileElement) { - this._previewFileElement(first); + this._previewFileElement(); } })); @@ -106,12 +110,12 @@ export class BulkEditPanel extends Panel { return [this._acceptAction, this._discardAction]; } - layout(dimension: Dimension): void { - this._tree.layout(dimension.height, dimension.width); + protected layoutBody(height: number, width: number): void { + this._tree.layout(height, width); } private _setState(state: State): void { - this.getContainer()!.dataset['state'] = state; + this.body.dataset['state'] = state; } async setInput(edit: WorkspaceEdit): Promise { @@ -182,7 +186,7 @@ export class BulkEditPanel extends Panel { }); } - private _previewFileElement(edit: FileElement): void { + private _previewFileElement(): void { } } From 8fcec963744ae426c2358005c428f00038233c44 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 8 Jan 2020 18:34:38 +0100 Subject: [PATCH 063/315] use standard file rendering, file operation type as css-class (need UI) --- .../contrib/bulkEdit/browser/bulkEditPane.ts | 2 +- .../contrib/bulkEdit/browser/bulkEditTree.ts | 30 ++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index 610130d47f1b9..1982c15c8d58f 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -67,7 +67,7 @@ export class BulkEditPanel extends ViewPane { } protected renderBody(parent: HTMLElement): void { - parent.className = 'bulk-edit-panel'; + parent.classList.add('bulk-edit-panel', 'show-file-icons'); // tree const treeContainer = document.createElement('div'); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index b1c5e7b821979..c7d86541996f2 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -16,12 +16,12 @@ import * as dom from 'vs/base/browser/dom'; import { ITextModel } from 'vs/editor/common/model'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { ILabelService } from 'vs/platform/label/common/label'; import { BulkFileOperations, BulkFileOperation, BulkFileOperationType, BulkTextEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; import { localize } from 'vs/nls'; import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachCheckboxStyler } from 'vs/platform/theme/common/styler'; +import { FileKind } from 'vs/platform/files/common/files'; // --- VIEW MODEL @@ -144,8 +144,7 @@ class FileElementTemplate { constructor( private readonly _checkbox: Checkbox, private readonly _label: IResourceLabel, - @IThemeService themeService: IThemeService, - @ILabelService private readonly _labelService: ILabelService, + @IThemeService themeService: IThemeService ) { this._disposables.add(_checkbox.onChange(() => { if (this._element) { @@ -162,15 +161,26 @@ class FileElementTemplate { this._label.dispose(); } - set(element: FileElement, matches: FuzzyScore | undefined) { + set(element: FileElement, score: FuzzyScore | undefined) { this._element = element; this._checkbox.checked = element.edit.isChecked(); - this._label.setResource({ - name: this._labelService.getUriLabel(element.uri, { relative: true }), - description: element.typeLabel, - resource: element.uri, - }, { - matches: createMatches(matches), + + const extraClasses: string[] = []; + if (element.edit.type & BulkFileOperationType.Create) { + extraClasses.push('create'); + } + if (element.edit.type & BulkFileOperationType.Delete) { + extraClasses.push('delete'); + } + if (element.edit.type & BulkFileOperationType.Rename) { + extraClasses.push('rename'); + } + this._label.setFile(element.uri, { + matches: createMatches(score), + fileKind: FileKind.FILE, + fileDecorations: { colors: true, badges: false }, + // parentCount: element.edit.textEdits.length || undefined, + extraClasses, }); } } From 71b60d0d2201e5ba0a52d9bd2b914da2cee11ab5 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 8 Jan 2020 14:35:44 -0800 Subject: [PATCH 064/315] Add call hierarchy support for TypeScript 3.8 (#88168) * Add call hierarchy support for TypeScript 3.8 * Add version dependent registration for call hierarchy provider * Revert TS version, PR feedback --- .../src/features/callHierarchy.ts | 111 ++++++++++++++++++ .../src/languageProvider.ts | 1 + .../src/protocol.const.ts | 1 + .../src/protocol.d.ts | 52 ++++++++ .../src/typescriptService.ts | 3 + .../src/utils/api.ts | 1 + .../src/utils/typeConverters.ts | 36 ++++++ 7 files changed, 205 insertions(+) create mode 100644 extensions/typescript-language-features/src/features/callHierarchy.ts diff --git a/extensions/typescript-language-features/src/features/callHierarchy.ts b/extensions/typescript-language-features/src/features/callHierarchy.ts new file mode 100644 index 0000000000000..b5b4501d2adce --- /dev/null +++ b/extensions/typescript-language-features/src/features/callHierarchy.ts @@ -0,0 +1,111 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { ITypeScriptServiceClient } from '../typescriptService'; +import * as typeConverters from '../utils/typeConverters'; +import API from '../utils/api'; +import { VersionDependentRegistration } from '../utils/dependentRegistration'; +import * as Proto from '../protocol'; +import * as path from 'path'; +import * as PConst from '../protocol.const'; + +class TypeScriptCallHierarchySupport implements vscode.CallHierarchyProvider { + public static readonly minVersion = API.v380; + public constructor( + private readonly client: ITypeScriptServiceClient) { } + + public async prepareCallHierarchy( + document: vscode.TextDocument, + position: vscode.Position, + token: vscode.CancellationToken + ): Promise { + const filepath = this.client.toOpenedFilePath(document); + if (!filepath) { + return undefined; + } + + const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position); + const response = await this.client.execute('prepareCallHierarchy', args, token); + if (response.type !== 'response' || !response.body) { + return undefined; + } + + return Array.isArray(response.body) + ? response.body.map(fromProtocolCallHierarchyItem) + : fromProtocolCallHierarchyItem(response.body); + } + + public async provideCallHierarchyIncomingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise { + const filepath = this.client.toPath(item.uri); + if (!filepath) { + return undefined; + } + + const args = typeConverters.Position.toFileLocationRequestArgs(filepath, item.selectionRange.start); + const response = await this.client.execute('provideCallHierarchyIncomingCalls', args, token); + if (response.type !== 'response' || !response.body) { + return undefined; + } + + return response.body.map(fromProtocolCallHierchyIncomingCall); + } + + public async provideCallHierarchyOutgoingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise { + const filepath = this.client.toPath(item.uri); + if (!filepath) { + return undefined; + } + + const args = typeConverters.Position.toFileLocationRequestArgs(filepath, item.selectionRange.start); + const response = await this.client.execute('provideCallHierarchyOutgoingCalls', args, token); + if (response.type !== 'response' || !response.body) { + return undefined; + } + + return response.body.map(fromProtocolCallHierchyOutgoingCall); + } +} + +function isSourceFileItem(item: Proto.CallHierarchyItem) { + return item.kind === PConst.Kind.script || item.kind === PConst.Kind.module && item.selectionSpan.start.line === 0 && item.selectionSpan.start.offset === 0; +} + +function fromProtocolCallHierarchyItem(item: Proto.CallHierarchyItem): vscode.CallHierarchyItem { + const useFileName = isSourceFileItem(item); + const name = useFileName ? path.basename(item.file) : item.name; + const detail = useFileName ? vscode.workspace.asRelativePath(path.dirname(item.file)) : ''; + return new vscode.CallHierarchyItem( + typeConverters.SymbolKind.fromProtocolScriptElementKind(item.kind), + name, + detail, + vscode.Uri.file(item.file), + typeConverters.Range.fromTextSpan(item.span), + typeConverters.Range.fromTextSpan(item.selectionSpan) + ); +} + +function fromProtocolCallHierchyIncomingCall(item: Proto.CallHierarchyIncomingCall): vscode.CallHierarchyIncomingCall { + return new vscode.CallHierarchyIncomingCall( + fromProtocolCallHierarchyItem(item.from), + item.fromSpans.map(typeConverters.Range.fromTextSpan) + ); +} + +function fromProtocolCallHierchyOutgoingCall(item: Proto.CallHierarchyOutgoingCall): vscode.CallHierarchyOutgoingCall { + return new vscode.CallHierarchyOutgoingCall( + fromProtocolCallHierarchyItem(item.to), + item.fromSpans.map(typeConverters.Range.fromTextSpan) + ); +} + +export function register( + selector: vscode.DocumentSelector, + client: ITypeScriptServiceClient +) { + return new VersionDependentRegistration(client, TypeScriptCallHierarchySupport.minVersion, + () => vscode.languages.registerCallHierarchyProvider(selector, + new TypeScriptCallHierarchySupport(client))); +} diff --git a/extensions/typescript-language-features/src/languageProvider.ts b/extensions/typescript-language-features/src/languageProvider.ts index 0d434f24ff656..494ea90230a19 100644 --- a/extensions/typescript-language-features/src/languageProvider.ts +++ b/extensions/typescript-language-features/src/languageProvider.ts @@ -78,6 +78,7 @@ export default class LanguageProvider extends Disposable { import('./features/signatureHelp').then(provider => this._register(provider.register(selector, this.client))), import('./features/tagClosing').then(provider => this._register(provider.register(selector, this.description.id, this.client))), import('./features/typeDefinitions').then(provider => this._register(provider.register(selector, this.client))), + import('./features/callHierarchy').then(provider => this._register(provider.register(selector, this.client))), ]); } diff --git a/extensions/typescript-language-features/src/protocol.const.ts b/extensions/typescript-language-features/src/protocol.const.ts index 7a6a7d7023569..a44c175f295fc 100644 --- a/extensions/typescript-language-features/src/protocol.const.ts +++ b/extensions/typescript-language-features/src/protocol.const.ts @@ -33,6 +33,7 @@ export class Kind { public static readonly warning = 'warning'; public static readonly string = 'string'; public static readonly parameter = 'parameter'; + public static readonly typeParameter = 'type parameter'; } diff --git a/extensions/typescript-language-features/src/protocol.d.ts b/extensions/typescript-language-features/src/protocol.d.ts index 6e926eb8d7ee2..31bc4746cb2ee 100644 --- a/extensions/typescript-language-features/src/protocol.d.ts +++ b/extensions/typescript-language-features/src/protocol.d.ts @@ -1,2 +1,54 @@ import * as Proto from 'typescript/lib/protocol'; export = Proto; + +declare module "typescript/lib/protocol" { + const enum CommandTypes { + PrepareCallHierarchy = "prepareCallHierarchy", + ProvideCallHierarchyIncomingCalls = "provideCallHierarchyIncomingCalls", + ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls", + } + + interface CallHierarchyItem { + name: string; + kind: ScriptElementKind; + file: string; + span: TextSpan; + selectionSpan: TextSpan; + } + + interface CallHierarchyIncomingCall { + from: CallHierarchyItem; + fromSpans: TextSpan[]; + } + + interface CallHierarchyOutgoingCall { + to: CallHierarchyItem; + fromSpans: TextSpan[]; + } + + interface PrepareCallHierarchyRequest extends FileLocationRequest { + command: CommandTypes.PrepareCallHierarchy; + } + + interface PrepareCallHierarchyResponse extends Response { + readonly body: CallHierarchyItem | CallHierarchyItem[]; + } + + interface ProvideCallHierarchyIncomingCallsRequest extends FileLocationRequest { + command: CommandTypes.ProvideCallHierarchyIncomingCalls; + kind: ScriptElementKind; + } + + interface ProvideCallHierarchyIncomingCallsResponse extends Response { + readonly body: CallHierarchyIncomingCall[]; + } + + interface ProvideCallHierarchyOutgoingCallsRequest extends FileLocationRequest { + command: CommandTypes.ProvideCallHierarchyOutgoingCalls; + kind: ScriptElementKind; + } + + interface ProvideCallHierarchyOutgoingCallsResponse extends Response { + readonly body: CallHierarchyOutgoingCall[]; + } +} diff --git a/extensions/typescript-language-features/src/typescriptService.ts b/extensions/typescript-language-features/src/typescriptService.ts index 3b5a139cd0af2..043284c124473 100644 --- a/extensions/typescript-language-features/src/typescriptService.ts +++ b/extensions/typescript-language-features/src/typescriptService.ts @@ -58,6 +58,9 @@ interface StandardTsServerRequests { 'signatureHelp': [Proto.SignatureHelpRequestArgs, Proto.SignatureHelpResponse]; 'typeDefinition': [Proto.FileLocationRequestArgs, Proto.TypeDefinitionResponse]; 'updateOpen': [Proto.UpdateOpenRequestArgs, Proto.Response]; + 'prepareCallHierarchy': [Proto.FileLocationRequestArgs, Proto.PrepareCallHierarchyResponse]; + 'provideCallHierarchyIncomingCalls': [Proto.FileLocationRequestArgs, Proto.ProvideCallHierarchyIncomingCallsResponse]; + 'provideCallHierarchyOutgoingCalls': [Proto.FileLocationRequestArgs, Proto.ProvideCallHierarchyOutgoingCallsResponse]; } interface NoResponseTsServerRequests { diff --git a/extensions/typescript-language-features/src/utils/api.ts b/extensions/typescript-language-features/src/utils/api.ts index c30bee16c7e1c..0fa41cb01a0b8 100644 --- a/extensions/typescript-language-features/src/utils/api.ts +++ b/extensions/typescript-language-features/src/utils/api.ts @@ -31,6 +31,7 @@ export default class API { public static readonly v340 = API.fromSimpleString('3.4.0'); public static readonly v345 = API.fromSimpleString('3.4.5'); public static readonly v350 = API.fromSimpleString('3.5.0'); + public static readonly v380 = API.fromSimpleString('3.8.0'); public static fromVersionString(versionString: string): API { let version = semver.valid(versionString); diff --git a/extensions/typescript-language-features/src/utils/typeConverters.ts b/extensions/typescript-language-features/src/utils/typeConverters.ts index 37947b38810d5..2ccf306885ff9 100644 --- a/extensions/typescript-language-features/src/utils/typeConverters.ts +++ b/extensions/typescript-language-features/src/utils/typeConverters.ts @@ -9,12 +9,18 @@ import * as vscode from 'vscode'; import * as Proto from '../protocol'; +import * as PConst from '../protocol.const'; import { ITypeScriptServiceClient } from '../typescriptService'; export namespace Range { export const fromTextSpan = (span: Proto.TextSpan): vscode.Range => fromLocations(span.start, span.end); + export const toTextSpan = (range: vscode.Range): Proto.TextSpan => ({ + start: Position.toLocation(range.start), + end: Position.toLocation(range.end) + }); + export const fromLocations = (start: Proto.Location, end: Proto.Location): vscode.Range => new vscode.Range( Math.max(0, start.line - 1), Math.max(start.offset - 1, 0), @@ -90,3 +96,33 @@ export namespace WorkspaceEdit { return workspaceEdit; } } + +export namespace SymbolKind { + export function fromProtocolScriptElementKind(kind: Proto.ScriptElementKind) { + switch (kind) { + case PConst.Kind.module: return vscode.SymbolKind.Module; + case PConst.Kind.class: return vscode.SymbolKind.Class; + case PConst.Kind.enum: return vscode.SymbolKind.Enum; + case PConst.Kind.enumMember: return vscode.SymbolKind.EnumMember; + case PConst.Kind.interface: return vscode.SymbolKind.Interface; + case PConst.Kind.indexSignature: return vscode.SymbolKind.Method; + case PConst.Kind.callSignature: return vscode.SymbolKind.Method; + case PConst.Kind.memberFunction: return vscode.SymbolKind.Method; + case PConst.Kind.memberVariable: return vscode.SymbolKind.Property; + case PConst.Kind.memberGetAccessor: return vscode.SymbolKind.Property; + case PConst.Kind.memberSetAccessor: return vscode.SymbolKind.Property; + case PConst.Kind.variable: return vscode.SymbolKind.Variable; + case PConst.Kind.let: return vscode.SymbolKind.Variable; + case PConst.Kind.const: return vscode.SymbolKind.Variable; + case PConst.Kind.localVariable: return vscode.SymbolKind.Variable; + case PConst.Kind.alias: return vscode.SymbolKind.Variable; + case PConst.Kind.function: return vscode.SymbolKind.Function; + case PConst.Kind.localFunction: return vscode.SymbolKind.Function; + case PConst.Kind.constructSignature: return vscode.SymbolKind.Constructor; + case PConst.Kind.constructorImplementation: return vscode.SymbolKind.Constructor; + case PConst.Kind.typeParameter: return vscode.SymbolKind.TypeParameter; + case PConst.Kind.string: return vscode.SymbolKind.String; + default: return vscode.SymbolKind.Variable; + } + } +} From cb47915e3b13b9d415497ad9336fbd26a607a25d Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 8 Jan 2020 15:37:59 -0800 Subject: [PATCH 065/315] Pick up latest ts nightly for building VS Code --- build/package.json | 2 +- build/yarn.lock | 8 ++++---- package.json | 2 +- yarn.lock | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build/package.json b/build/package.json index 69b98df35e475..33c254aafe1d2 100644 --- a/build/package.json +++ b/build/package.json @@ -43,7 +43,7 @@ "minimist": "^1.2.0", "request": "^2.85.0", "terser": "4.3.8", - "typescript": "^3.8.0-dev.20200104", + "typescript": "^3.8.0-dev.20200108", "vsce": "1.48.0", "vscode-telemetry-extractor": "^1.5.4", "xml2js": "^0.4.17" diff --git a/build/yarn.lock b/build/yarn.lock index 2e66b691a459f..38c1cbec7d941 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -2458,10 +2458,10 @@ typescript@^3.0.1: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== -typescript@^3.8.0-dev.20200104: - version "3.8.0-dev.20200104" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.0-dev.20200104.tgz#521b2f0b5a288b6e3f8a095525f64712330cc649" - integrity sha512-Zdb8X1uzvUPrRvRBqega83NxqCuN/kyxuXG1u8BV10mGOqfwQb0SreSDoDDM1zUgrqFZ93neVh3DVyWTvx6XlA== +typescript@^3.8.0-dev.20200108: + version "3.8.0-dev.20200108" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.0-dev.20200108.tgz#ca3a4d950cd19112d80758be779fb07d577e49bc" + integrity sha512-SD3VEYUUrDGc0djorpi0zVdmVwmvuaSHta18WP3sS9X0HC7eA4izdjj07pVUc99IBpBw55ljUATm5vkNdvxX6w== typical@^4.0.0: version "4.0.0" diff --git a/package.json b/package.json index cee5e19332714..aa000ded9df63 100644 --- a/package.json +++ b/package.json @@ -144,7 +144,7 @@ "sinon": "^1.17.2", "source-map": "^0.4.4", "ts-loader": "^4.4.2", - "typescript": "^3.8.0-dev.20200104", + "typescript": "^3.8.0-dev.20200108", "typescript-formatter": "7.1.0", "underscore": "^1.8.2", "vinyl": "^2.0.0", diff --git a/yarn.lock b/yarn.lock index 6ef2a9f7c77e3..e4ca9c5cc9c33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9185,10 +9185,10 @@ typescript@^2.6.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q= -typescript@^3.8.0-dev.20200104: - version "3.8.0-dev.20200104" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.0-dev.20200104.tgz#521b2f0b5a288b6e3f8a095525f64712330cc649" - integrity sha512-Zdb8X1uzvUPrRvRBqega83NxqCuN/kyxuXG1u8BV10mGOqfwQb0SreSDoDDM1zUgrqFZ93neVh3DVyWTvx6XlA== +typescript@^3.8.0-dev.20200108: + version "3.8.0-dev.20200108" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.0-dev.20200108.tgz#ca3a4d950cd19112d80758be779fb07d577e49bc" + integrity sha512-SD3VEYUUrDGc0djorpi0zVdmVwmvuaSHta18WP3sS9X0HC7eA4izdjj07pVUc99IBpBw55ljUATm5vkNdvxX6w== uc.micro@^1.0.1, uc.micro@^1.0.3: version "1.0.3" From 7c6897aca0c85662d66d0b180aa7ca0d39a43bc3 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 8 Jan 2020 16:34:21 -0800 Subject: [PATCH 066/315] Report updateGraph performance Telemetry from the TypeScript server Fixes #88313 --- .../src/typescriptServiceClient.ts | 37 +++++++++++++++++-- .../src/utils/telemetry.ts | 6 ++- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index e487ac835a79a..11f9673a83410 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -22,7 +22,7 @@ import LogDirectoryProvider from './utils/logDirectoryProvider'; import Logger from './utils/logger'; import { TypeScriptPluginPathsProvider } from './utils/pluginPathsProvider'; import { PluginManager } from './utils/plugins'; -import TelemetryReporter, { VSCodeTelemetryReporter } from './utils/telemetry'; +import TelemetryReporter, { VSCodeTelemetryReporter, TelemetrtyProperties } from './utils/telemetry'; import Tracer from './utils/tracer'; import { inferredProjectCompilerOptions } from './utils/tsconfig'; import { TypeScriptVersionPicker } from './utils/versionPicker'; @@ -80,6 +80,11 @@ namespace ServerState { export type State = typeof None | Running | Errored; } +// TODO: Remove this hardcoded type once we update to TS 3.8+ that brings in the proper types +type TS38ResponseWithPerfMetadata = Proto.Response & { + updateGraphDurationMs?: number; +}; + export default class TypeScriptServiceClient extends Disposable implements ITypeScriptServiceClient { private static readonly WALK_THROUGH_SNIPPET_SCHEME_COLON = `${fileSchemes.walkThroughSnippet}:`; @@ -271,7 +276,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.logger.error(message, data); } - private logTelemetry(eventName: string, properties?: { readonly [prop: string]: string }) { + private logTelemetry(eventName: string, properties?: TelemetrtyProperties) { this.telemetryReporter.logTelemetry(eventName, properties); } @@ -698,7 +703,30 @@ export default class TypeScriptServiceClient extends Disposable implements IType private executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise> | undefined { this.bufferSyncSupport.beforeCommand(command); const runningServerState = this.service(); - return runningServerState.server.executeImpl(command, args, executeInfo); + return runningServerState.server.executeImpl(command, args, executeInfo).then(result => { + if (result?.type === 'response') { + this.reportRequestTelemetry(result, command); + } + return result; + }); + } + + private reportRequestTelemetry(result: TS38ResponseWithPerfMetadata, command: string): void { + if (typeof result.updateGraphDurationMs === 'number') { + /* __GDPR__ + "updateGraphPerformance" : { + "${include}": [ + "${TypeScriptCommonProperties}" + ], + "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "updateGraphDurationMs" : { "classification": "SystemMetaData", "purpose": "updateGraphDurationMs" } + } + */ + this.logTelemetry('updateGraphPerformance', { + command, + updateGraphDurationMs: result.updateGraphDurationMs + }); + } } public interruptGetErr(f: () => R): R { @@ -711,7 +739,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType "${include}": [ "${TypeScriptCommonProperties}", "${TypeScriptRequestErrorProperties}" - ] + ], + "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, } */ this.logTelemetry('fatalError', { command, ...(error instanceof TypeScriptServerError ? error.telemetry : {}) }); diff --git a/extensions/typescript-language-features/src/utils/telemetry.ts b/extensions/typescript-language-features/src/utils/telemetry.ts index e17e0188e6d91..a6cc2b891e16b 100644 --- a/extensions/typescript-language-features/src/utils/telemetry.ts +++ b/extensions/typescript-language-features/src/utils/telemetry.ts @@ -13,8 +13,12 @@ interface PackageInfo { readonly aiKey: string; } +export interface TelemetrtyProperties { + readonly [prop: string]: string | number; +} + export default interface TelemetryReporter { - logTelemetry(eventName: string, properties?: { readonly [prop: string]: string }): void; + logTelemetry(eventName: string, properties?: TelemetrtyProperties): void; dispose(): void; } From e7b44e70e94170e2b893458891d041dde36217fa Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 8 Jan 2020 16:36:31 -0800 Subject: [PATCH 067/315] Dont' use default export for TelemetryReporter We are also exproting other values/types, so use a named export instead --- .../typescript-language-features/src/features/completions.ts | 2 +- .../src/features/organizeImports.ts | 2 +- .../typescript-language-features/src/features/quickFix.ts | 2 +- .../typescript-language-features/src/features/refactor.ts | 2 +- extensions/typescript-language-features/src/languageProvider.ts | 2 +- extensions/typescript-language-features/src/test/server.test.ts | 2 +- extensions/typescript-language-features/src/tsServer/server.ts | 2 +- extensions/typescript-language-features/src/tsServer/spawner.ts | 2 +- .../typescript-language-features/src/typescriptServiceClient.ts | 2 +- .../typescript-language-features/src/utils/projectStatus.ts | 2 +- extensions/typescript-language-features/src/utils/telemetry.ts | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/extensions/typescript-language-features/src/features/completions.ts b/extensions/typescript-language-features/src/features/completions.ts index 5fdefe1b2ad54..0463eb8e4f198 100644 --- a/extensions/typescript-language-features/src/features/completions.ts +++ b/extensions/typescript-language-features/src/features/completions.ts @@ -16,7 +16,7 @@ import { ConfigurationDependentRegistration } from '../utils/dependentRegistrati import { memoize } from '../utils/memoize'; import * as Previewer from '../utils/previewer'; import { snippetForFunctionCall } from '../utils/snippetForFunctionCall'; -import TelemetryReporter from '../utils/telemetry'; +import { TelemetryReporter } from '../utils/telemetry'; import * as typeConverters from '../utils/typeConverters'; import TypingsStatus from '../utils/typingsStatus'; import FileConfigurationManager from './fileConfigurationManager'; diff --git a/extensions/typescript-language-features/src/features/organizeImports.ts b/extensions/typescript-language-features/src/features/organizeImports.ts index bb7880800a609..2e5871d183551 100644 --- a/extensions/typescript-language-features/src/features/organizeImports.ts +++ b/extensions/typescript-language-features/src/features/organizeImports.ts @@ -12,7 +12,7 @@ import { Command, CommandManager } from '../utils/commandManager'; import { VersionDependentRegistration } from '../utils/dependentRegistration'; import * as typeconverts from '../utils/typeConverters'; import FileConfigurationManager from './fileConfigurationManager'; -import TelemetryReporter from '../utils/telemetry'; +import { TelemetryReporter } from '../utils/telemetry'; import { nulToken } from '../utils/cancellation'; const localize = nls.loadMessageBundle(); diff --git a/extensions/typescript-language-features/src/features/quickFix.ts b/extensions/typescript-language-features/src/features/quickFix.ts index 1b1ad27da858d..879abf07f8b46 100644 --- a/extensions/typescript-language-features/src/features/quickFix.ts +++ b/extensions/typescript-language-features/src/features/quickFix.ts @@ -12,7 +12,7 @@ import { nulToken } from '../utils/cancellation'; import { applyCodeActionCommands, getEditForCodeAction } from '../utils/codeAction'; import { Command, CommandManager } from '../utils/commandManager'; import { memoize } from '../utils/memoize'; -import TelemetryReporter from '../utils/telemetry'; +import { TelemetryReporter } from '../utils/telemetry'; import * as typeConverters from '../utils/typeConverters'; import { DiagnosticsManager } from './diagnostics'; import FileConfigurationManager from './fileConfigurationManager'; diff --git a/extensions/typescript-language-features/src/features/refactor.ts b/extensions/typescript-language-features/src/features/refactor.ts index 3ccb596991362..017ea358b2a37 100644 --- a/extensions/typescript-language-features/src/features/refactor.ts +++ b/extensions/typescript-language-features/src/features/refactor.ts @@ -11,7 +11,7 @@ import API from '../utils/api'; import { nulToken } from '../utils/cancellation'; import { Command, CommandManager } from '../utils/commandManager'; import { VersionDependentRegistration } from '../utils/dependentRegistration'; -import TelemetryReporter from '../utils/telemetry'; +import { TelemetryReporter } from '../utils/telemetry'; import * as typeConverters from '../utils/typeConverters'; import FormattingOptionsManager from './fileConfigurationManager'; import * as fileSchemes from '../utils/fileSchemes'; diff --git a/extensions/typescript-language-features/src/languageProvider.ts b/extensions/typescript-language-features/src/languageProvider.ts index 494ea90230a19..a73cd46409de7 100644 --- a/extensions/typescript-language-features/src/languageProvider.ts +++ b/extensions/typescript-language-features/src/languageProvider.ts @@ -14,7 +14,7 @@ import { Disposable } from './utils/dispose'; import * as fileSchemes from './utils/fileSchemes'; import { LanguageDescription } from './utils/languageDescription'; import { memoize } from './utils/memoize'; -import TelemetryReporter from './utils/telemetry'; +import { TelemetryReporter } from './utils/telemetry'; import TypingsStatus from './utils/typingsStatus'; diff --git a/extensions/typescript-language-features/src/test/server.test.ts b/extensions/typescript-language-features/src/test/server.test.ts index 651967fc12c02..fd3210670ffef 100644 --- a/extensions/typescript-language-features/src/test/server.test.ts +++ b/extensions/typescript-language-features/src/test/server.test.ts @@ -9,7 +9,7 @@ import * as stream from 'stream'; import { PipeRequestCanceller, TsServerProcess, ProcessBasedTsServer } from '../tsServer/server'; import { nulToken } from '../utils/cancellation'; import Logger from '../utils/logger'; -import TelemetryReporter from '../utils/telemetry'; +import { TelemetryReporter } from '../utils/telemetry'; import Tracer from '../utils/tracer'; import * as Proto from '../protocol'; diff --git a/extensions/typescript-language-features/src/tsServer/server.ts b/extensions/typescript-language-features/src/tsServer/server.ts index 782c1c56f05c9..d23b4b13f839d 100644 --- a/extensions/typescript-language-features/src/tsServer/server.ts +++ b/extensions/typescript-language-features/src/tsServer/server.ts @@ -9,7 +9,7 @@ import * as vscode from 'vscode'; import * as Proto from '../protocol'; import { ServerResponse, TypeScriptRequests } from '../typescriptService'; import { Disposable } from '../utils/dispose'; -import TelemetryReporter from '../utils/telemetry'; +import { TelemetryReporter } from '../utils/telemetry'; import Tracer from '../utils/tracer'; import { TypeScriptVersion } from '../utils/versionProvider'; import { Reader } from '../utils/wireProtocol'; diff --git a/extensions/typescript-language-features/src/tsServer/spawner.ts b/extensions/typescript-language-features/src/tsServer/spawner.ts index 171f204703eaf..dfd71edfc5185 100644 --- a/extensions/typescript-language-features/src/tsServer/spawner.ts +++ b/extensions/typescript-language-features/src/tsServer/spawner.ts @@ -15,7 +15,7 @@ import LogDirectoryProvider from '../utils/logDirectoryProvider'; import Logger from '../utils/logger'; import { TypeScriptPluginPathsProvider } from '../utils/pluginPathsProvider'; import { PluginManager } from '../utils/plugins'; -import TelemetryReporter from '../utils/telemetry'; +import { TelemetryReporter } from '../utils/telemetry'; import Tracer from '../utils/tracer'; import { TypeScriptVersion, TypeScriptVersionProvider } from '../utils/versionProvider'; import { ITypeScriptServer, PipeRequestCanceller, ProcessBasedTsServer, SyntaxRoutingTsServer, TsServerProcess, TsServerDelegate } from './server'; diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 11f9673a83410..8b6ca41268dc3 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -22,7 +22,7 @@ import LogDirectoryProvider from './utils/logDirectoryProvider'; import Logger from './utils/logger'; import { TypeScriptPluginPathsProvider } from './utils/pluginPathsProvider'; import { PluginManager } from './utils/plugins'; -import TelemetryReporter, { VSCodeTelemetryReporter, TelemetrtyProperties } from './utils/telemetry'; +import { TelemetryReporter, VSCodeTelemetryReporter, TelemetrtyProperties } from './utils/telemetry'; import Tracer from './utils/tracer'; import { inferredProjectCompilerOptions } from './utils/tsconfig'; import { TypeScriptVersionPicker } from './utils/versionPicker'; diff --git a/extensions/typescript-language-features/src/utils/projectStatus.ts b/extensions/typescript-language-features/src/utils/projectStatus.ts index 15aa72a80d09c..b9a3b2328c7bf 100644 --- a/extensions/typescript-language-features/src/utils/projectStatus.ts +++ b/extensions/typescript-language-features/src/utils/projectStatus.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import { loadMessageBundle } from 'vscode-nls'; import { ITypeScriptServiceClient } from '../typescriptService'; -import TelemetryReporter from './telemetry'; +import { TelemetryReporter } from './telemetry'; import { isImplicitProjectConfigFile, openOrCreateConfigFile } from './tsconfig'; const localize = loadMessageBundle(); diff --git a/extensions/typescript-language-features/src/utils/telemetry.ts b/extensions/typescript-language-features/src/utils/telemetry.ts index a6cc2b891e16b..51b22c66c3297 100644 --- a/extensions/typescript-language-features/src/utils/telemetry.ts +++ b/extensions/typescript-language-features/src/utils/telemetry.ts @@ -17,7 +17,7 @@ export interface TelemetrtyProperties { readonly [prop: string]: string | number; } -export default interface TelemetryReporter { +export interface TelemetryReporter { logTelemetry(eventName: string, properties?: TelemetrtyProperties): void; dispose(): void; From ce5ffc76c09aedf433fce6c6ae7998cd2bfaae8f Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 8 Jan 2020 16:47:10 -0800 Subject: [PATCH 068/315] Fix type --- .../src/typescriptServiceClient.ts | 4 ++-- .../typescript-language-features/src/utils/telemetry.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 8b6ca41268dc3..0b4a27d33969e 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -22,7 +22,7 @@ import LogDirectoryProvider from './utils/logDirectoryProvider'; import Logger from './utils/logger'; import { TypeScriptPluginPathsProvider } from './utils/pluginPathsProvider'; import { PluginManager } from './utils/plugins'; -import { TelemetryReporter, VSCodeTelemetryReporter, TelemetrtyProperties } from './utils/telemetry'; +import { TelemetryReporter, VSCodeTelemetryReporter, TelemetryProperties } from './utils/telemetry'; import Tracer from './utils/tracer'; import { inferredProjectCompilerOptions } from './utils/tsconfig'; import { TypeScriptVersionPicker } from './utils/versionPicker'; @@ -276,7 +276,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.logger.error(message, data); } - private logTelemetry(eventName: string, properties?: TelemetrtyProperties) { + private logTelemetry(eventName: string, properties?: TelemetryProperties) { this.telemetryReporter.logTelemetry(eventName, properties); } diff --git a/extensions/typescript-language-features/src/utils/telemetry.ts b/extensions/typescript-language-features/src/utils/telemetry.ts index 51b22c66c3297..b3f77e99d0049 100644 --- a/extensions/typescript-language-features/src/utils/telemetry.ts +++ b/extensions/typescript-language-features/src/utils/telemetry.ts @@ -13,12 +13,12 @@ interface PackageInfo { readonly aiKey: string; } -export interface TelemetrtyProperties { +export interface TelemetryProperties { readonly [prop: string]: string | number; } export interface TelemetryReporter { - logTelemetry(eventName: string, properties?: TelemetrtyProperties): void; + logTelemetry(eventName: string, properties?: TelemetryProperties): void; dispose(): void; } From fc65a7c41b844b194ba25403ec1d07025bb1ada6 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 8 Jan 2020 16:57:26 -0800 Subject: [PATCH 069/315] Update logging of updateGraph to match TS protocol changes For #88313 Also log updateGraph on `completions.execute` instead of as its own event --- .../src/features/completions.ts | 8 +++-- .../src/protocol.d.ts | 7 +++++ .../src/typescriptServiceClient.ts | 30 +------------------ .../src/utils/telemetry.ts | 2 +- 4 files changed, 14 insertions(+), 33 deletions(-) diff --git a/extensions/typescript-language-features/src/features/completions.ts b/extensions/typescript-language-features/src/features/completions.ts index 0463eb8e4f198..cd887f1f4aaf5 100644 --- a/extensions/typescript-language-features/src/features/completions.ts +++ b/extensions/typescript-language-features/src/features/completions.ts @@ -405,15 +405,17 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider "duration" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }, "type" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }, "count" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }, + "updateGraphDurationMs" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }, "${include}": [ "${TypeScriptCommonProperties}" ] } */ this.telemetryReporter.logTelemetry('completions.execute', { - duration: duration + '', - type: response ? response.type : 'unknown', - count: (response && response.type === 'response' && response.body ? response.body.entries.length : 0) + '' + duration: duration, + type: response?.type ?? 'unknown', + count: response?.type === 'response' && response.body ? response.body.entries.length : 0, + updateGraphDurationMs: response?.type === 'response' ? response.performanceData?.updateGraphDurationMs : undefined, }); } diff --git a/extensions/typescript-language-features/src/protocol.d.ts b/extensions/typescript-language-features/src/protocol.d.ts index 31bc4746cb2ee..62d6c4793ca44 100644 --- a/extensions/typescript-language-features/src/protocol.d.ts +++ b/extensions/typescript-language-features/src/protocol.d.ts @@ -2,6 +2,13 @@ import * as Proto from 'typescript/lib/protocol'; export = Proto; declare module "typescript/lib/protocol" { + // TODO: Remove this hardcoded type once we update to TS 3.8+ that brings in the proper types + interface Response { + performanceData?: { + updateGraphDurationMs?: number; + } + } + const enum CommandTypes { PrepareCallHierarchy = "prepareCallHierarchy", ProvideCallHierarchyIncomingCalls = "provideCallHierarchyIncomingCalls", diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 0b4a27d33969e..bb041b7676e1a 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -80,11 +80,6 @@ namespace ServerState { export type State = typeof None | Running | Errored; } -// TODO: Remove this hardcoded type once we update to TS 3.8+ that brings in the proper types -type TS38ResponseWithPerfMetadata = Proto.Response & { - updateGraphDurationMs?: number; -}; - export default class TypeScriptServiceClient extends Disposable implements ITypeScriptServiceClient { private static readonly WALK_THROUGH_SNIPPET_SCHEME_COLON = `${fileSchemes.walkThroughSnippet}:`; @@ -703,30 +698,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType private executeImpl(command: keyof TypeScriptRequests, args: any, executeInfo: { isAsync: boolean, token?: vscode.CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise> | undefined { this.bufferSyncSupport.beforeCommand(command); const runningServerState = this.service(); - return runningServerState.server.executeImpl(command, args, executeInfo).then(result => { - if (result?.type === 'response') { - this.reportRequestTelemetry(result, command); - } - return result; - }); - } - - private reportRequestTelemetry(result: TS38ResponseWithPerfMetadata, command: string): void { - if (typeof result.updateGraphDurationMs === 'number') { - /* __GDPR__ - "updateGraphPerformance" : { - "${include}": [ - "${TypeScriptCommonProperties}" - ], - "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "updateGraphDurationMs" : { "classification": "SystemMetaData", "purpose": "updateGraphDurationMs" } - } - */ - this.logTelemetry('updateGraphPerformance', { - command, - updateGraphDurationMs: result.updateGraphDurationMs - }); - } + return runningServerState.server.executeImpl(command, args, executeInfo); } public interruptGetErr(f: () => R): R { diff --git a/extensions/typescript-language-features/src/utils/telemetry.ts b/extensions/typescript-language-features/src/utils/telemetry.ts index b3f77e99d0049..296277ba0280a 100644 --- a/extensions/typescript-language-features/src/utils/telemetry.ts +++ b/extensions/typescript-language-features/src/utils/telemetry.ts @@ -14,7 +14,7 @@ interface PackageInfo { } export interface TelemetryProperties { - readonly [prop: string]: string | number; + readonly [prop: string]: string | number | undefined; } export interface TelemetryReporter { From ea1097d2b0baeee21e219a8395aed3c8741faf85 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 9 Jan 2020 02:33:14 +0100 Subject: [PATCH 070/315] #26707 Inspect returns languages --- .../configuration/common/configuration.ts | 2 ++ .../common/configurationModels.ts | 3 +++ .../test/common/configurationModels.test.ts | 19 ++++++++++++++++++- src/vs/vscode.proposed.d.ts | 2 ++ .../api/common/extHostConfiguration.ts | 4 ++++ 5 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 06a98c7918f45..2937bca8f1f33 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -83,6 +83,8 @@ export interface IConfigurationValue { readonly workspace?: { value?: T, override?: T }; readonly workspaceFolder?: { value?: T, override?: T }; readonly memory?: { value?: T, override?: T }; + + readonly overrideIdentifiers?: string[]; } export interface IConfigurationService { diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 759376b98620b..ee715c1631fa9 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -391,6 +391,7 @@ export class Configuration { const workspaceFolderValue = folderConfigurationModel ? overrides.overrideIdentifier ? folderConfigurationModel.freeze().override(overrides.overrideIdentifier).getValue(key) : folderConfigurationModel.freeze().getValue(key) : undefined; const memoryValue = overrides.overrideIdentifier ? memoryConfigurationModel.override(overrides.overrideIdentifier).getValue(key) : memoryConfigurationModel.getValue(key); const value = consolidateConfigurationModel.getValue(key); + const overrideIdentifiers: string[] = arrays.distinct(arrays.flatten(consolidateConfigurationModel.overrides.map(override => override.identifiers))).filter(overrideIdentifier => consolidateConfigurationModel.getOverrideValue(key, overrideIdentifier) !== undefined); return { defaultValue: defaultValue, @@ -409,6 +410,8 @@ export class Configuration { workspace: workspaceValue !== undefined ? { value: this._workspaceConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this._workspaceConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, workspaceFolder: workspaceFolderValue !== undefined ? { value: folderConfigurationModel?.freeze().getValue(key), override: overrides.overrideIdentifier ? folderConfigurationModel?.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, memory: memoryValue !== undefined ? { value: memoryConfigurationModel.getValue(key), override: overrides.overrideIdentifier ? memoryConfigurationModel.getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, + + overrideIdentifiers: overrideIdentifiers.length ? overrideIdentifiers : undefined }; } diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index daf54b4b9f59e..1ff040dce847d 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -361,6 +361,17 @@ suite('CustomConfigurationModel', () => { suite('Configuration', () => { + test('Test inspect for overrideIdentifiers', () => { + const defaultConfigurationModel = parseConfigurationModel({ '[l1]': { 'a': 1 }, '[l2]': { 'b': 1 } }); + const userConfigurationModel = parseConfigurationModel({ '[l3]': { 'a': 2 } }); + const workspaceConfigurationModel = parseConfigurationModel({ '[l1]': { 'a': 3 }, '[l4]': { 'a': 3 } }); + const testObject: Configuration = new Configuration(defaultConfigurationModel, userConfigurationModel, new ConfigurationModel(), workspaceConfigurationModel); + + const { overrideIdentifiers } = testObject.inspect('a', {}, undefined); + + assert.deepEqual(overrideIdentifiers, ['l1', 'l3', 'l4']); + }); + test('Test update value', () => { const parser = new ConfigurationModelParser('test'); parser.parseContent(JSON.stringify({ 'a': 1 })); @@ -468,7 +479,7 @@ suite('Configuration', () => { }); - test('Test compare and deletre workspace folder configuration', () => { + test('Test compare and delete workspace folder configuration', () => { const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); testObject.updateFolderConfiguration(URI.file('file1'), toConfigurationModel({ 'editor.lineNumbers': 'off', @@ -484,6 +495,12 @@ suite('Configuration', () => { }); + function parseConfigurationModel(content: any): ConfigurationModel { + const parser = new ConfigurationModelParser('test'); + parser.parseContent(JSON.stringify(content)); + return parser.configurationModel; + } + }); suite('ConfigurationChangeEvent', () => { diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 4db9195fd09b3..15d667e14fc66 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1431,6 +1431,8 @@ declare module 'vscode' { workspaceLanguageValue?: T; workspaceFolderLanguageValue?: T; + languages?: string[]; + } | undefined; /** diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index aeabbaed7c1f9..ad9446b73948e 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -45,6 +45,8 @@ type ConfigurationInspect = { userLanguageValue?: T; workspaceLanguageValue?: T; workspaceFolderLanguageValue?: T; + + languages?: string[]; }; function isUri(thing: any): thing is vscode.Uri { @@ -264,6 +266,8 @@ export class ExtHostConfigProvider { userLanguageValue: config.user?.override, workspaceLanguageValue: config.workspace?.override, workspaceFolderLanguageValue: config.workspaceFolder?.override, + + languages: config.overrideIdentifiers }; } return undefined; From 7e64866a703c83dcdd3b84a8b48dd1673895fb7d Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 8 Jan 2020 17:48:09 -0800 Subject: [PATCH 071/315] Use flex to layout extension page header/body (#88318) Fixes #83209 **Bug** The extension page currently uses hardcoded height values for the header. This height breaks if the extension is recomended **Fix** Use flex layout instead --- .../contrib/extensions/browser/media/extensionEditor.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index 2c4b2623d40e1..a2e6e0d7721cd 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -6,6 +6,8 @@ .extension-editor { height: 100%; overflow: hidden; + display: flex; + flex-direction: column; } .extension-editor .clickable { @@ -188,7 +190,7 @@ } .extension-editor > .body { - height: calc(100% - 168px); + flex: 1; overflow: hidden; } From d6a3ca72b5a4ff505353006d39f38b7228c7b36e Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Mon, 6 Jan 2020 22:06:51 -0500 Subject: [PATCH 072/315] Setting to control whether to focus the inline editor in peek widget by default. Fixes #23001 --- src/vs/editor/common/config/editorOptions.ts | 10 ++++++++++ .../contrib/gotoSymbol/peek/referencesController.ts | 10 +++++++++- src/vs/monaco.d.ts | 5 +++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 3adac8cf46b92..4daf79c916759 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -542,6 +542,11 @@ export interface IEditorOptions { * Controls fading out of unused variables. */ showUnused?: boolean; + /** + * Controls whether to focus the inline editor in the peek widget by default. + * Defaults to false. + */ + peekWidgetFocusInlineEditor?: boolean; } export interface IEditorConstructionOptions extends IEditorOptions { @@ -3121,6 +3126,7 @@ export const enum EditorOption { overviewRulerBorder, overviewRulerLanes, parameterHints, + peekWidgetFocusInlineEditor, quickSuggestions, quickSuggestionsDelay, readOnly, @@ -3485,6 +3491,10 @@ export const EditorOptions = { 3, 0, 3 )), parameterHints: register(new EditorParameterHints()), + peekWidgetFocusInlineEditor: register(new EditorBooleanOption( + EditorOption.peekWidgetFocusInlineEditor, 'peekWidgetFocusInlineEditor', false, + { description: nls.localize('peekWidgetFocusInlineEditor', "Controls whether to focus the inline editor in the peek widget by default.") } + )), quickSuggestions: register(new EditorQuickSuggestions()), quickSuggestionsDelay: register(new EditorIntOption( EditorOption.quickSuggestionsDelay, 'quickSuggestionsDelay', diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts index a140ff4501a7c..8bf6c0966dabf 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts @@ -25,6 +25,7 @@ import { IListService, WorkbenchListFocusContextKey } from 'vs/platform/list/bro import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; export const ctxReferenceSearchVisible = new RawContextKey('referenceSearchVisible', false); @@ -163,7 +164,11 @@ export abstract class ReferencesController implements editorCommon.IEditorContri let pos = new Position(range.startLineNumber, range.startColumn); let selection = this._model.nearestReference(uri, pos); if (selection) { - return this._widget.setSelection(selection); + return this._widget.setSelection(selection).then(() => { + if (this._widget && this._editor.getOption(EditorOption.peekWidgetFocusInlineEditor)) { + this._widget.focusOnPreviewEditor(); + } + }); } } return undefined; @@ -201,10 +206,13 @@ export abstract class ReferencesController implements editorCommon.IEditorContri } const target = this._model.nextOrPreviousReference(source, fwd); const editorFocus = this._editor.hasTextFocus(); + const previewEditorFocus = this._widget.isPreviewEditorFocused(); await this._widget.setSelection(target); await this._gotoReference(target); if (editorFocus) { this._editor.focus(); + } else if (this._widget && previewEditorFocus) { + this._widget.focusOnPreviewEditor(); } } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 32a3f131d6bfe..956c79ffad231 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2987,6 +2987,11 @@ declare namespace monaco.editor { * Controls fading out of unused variables. */ showUnused?: boolean; + /** + * Controls whether to focus the inline editor in the peek widget by default. + * Defaults to false. + */ + peekWidgetFocusInlineEditor?: boolean; } export interface IEditorConstructionOptions extends IEditorOptions { From 2352d6ffc1597aafa5ecf9d366a7e830b087136d Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Thu, 9 Jan 2020 09:18:19 +0100 Subject: [PATCH 073/315] Update copycat.yml --- .github/copycat.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/copycat.yml b/.github/copycat.yml index 192f225684fa2..690c803bd0a80 100644 --- a/.github/copycat.yml +++ b/.github/copycat.yml @@ -1,5 +1,5 @@ { - perform: false, + perform: true, target_owner: 'chrmarti', target_repo: 'testissues' } From 978373f14b9d54382c6e02b832521dfeba46d27a Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 9 Jan 2020 09:29:09 +0100 Subject: [PATCH 074/315] TunnelFactory web api (#88200) Web API for tunnels Part of #81388 --- .../platform/remote/common/tunnelService.ts | 26 --- .../remote/common/remote.contribution.ts | 2 + .../contrib/remote/common/tunnelFactory.ts | 39 +++++ .../services/remote/common/tunnelService.ts | 151 ++++++++++++++++++ .../services/remote/node/tunnelService.ts | 125 ++------------- src/vs/workbench/workbench.web.api.ts | 25 +++ src/vs/workbench/workbench.web.main.ts | 4 +- 7 files changed, 228 insertions(+), 144 deletions(-) delete mode 100644 src/vs/platform/remote/common/tunnelService.ts create mode 100644 src/vs/workbench/contrib/remote/common/tunnelFactory.ts create mode 100644 src/vs/workbench/services/remote/common/tunnelService.ts diff --git a/src/vs/platform/remote/common/tunnelService.ts b/src/vs/platform/remote/common/tunnelService.ts deleted file mode 100644 index a5fa07ac174ee..0000000000000 --- a/src/vs/platform/remote/common/tunnelService.ts +++ /dev/null @@ -1,26 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ITunnelService, RemoteTunnel, ITunnelProvider } from 'vs/platform/remote/common/tunnel'; -import { Event, Emitter } from 'vs/base/common/event'; -import { IDisposable } from 'vs/base/common/lifecycle'; - -export class NoOpTunnelService implements ITunnelService { - _serviceBrand: undefined; - - public readonly tunnels: Promise = Promise.resolve([]); - private _onTunnelOpened: Emitter = new Emitter(); - public onTunnelOpened: Event = this._onTunnelOpened.event; - private _onTunnelClosed: Emitter<{ host: string, port: number }> = new Emitter(); - public onTunnelClosed: Event<{ host: string, port: number }> = this._onTunnelClosed.event; - openTunnel(_remoteHost: string, _remotePort: number): Promise | undefined { - return undefined; - } - async closeTunnel(_remoteHost: string, _remotePort: number): Promise { - } - setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable { - throw new Error('Method not implemented.'); - } -} diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index bd64362867000..e5bdd22c73bbc 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -16,6 +16,7 @@ import { IOutputChannelRegistry, Extensions as OutputExt, } from 'vs/workbench/s import { localize } from 'vs/nls'; import { joinPath } from 'vs/base/common/resources'; import { Disposable } from 'vs/base/common/lifecycle'; +import { TunnelFactoryContribution } from 'vs/workbench/contrib/remote/common/tunnelFactory'; export const VIEWLET_ID = 'workbench.view.remote'; @@ -83,3 +84,4 @@ const workbenchContributionsRegistry = Registry.as | undefined => { + const tunnelPromise = workbenchEnvironmentService.options!.tunnelFactory!(tunnelOptions); + if (!tunnelPromise) { + return undefined; + } + return new Promise(resolve => { + tunnelPromise.then(tunnel => { + const remoteTunnel: RemoteTunnel = { + tunnelRemotePort: tunnel.remoteAddress.port, + tunnelRemoteHost: tunnel.remoteAddress.host, + localAddress: tunnel.localAddress, + dispose: tunnel.dispose + }; + resolve(remoteTunnel); + }); + }); + } + })); + } + } +} diff --git a/src/vs/workbench/services/remote/common/tunnelService.ts b/src/vs/workbench/services/remote/common/tunnelService.ts new file mode 100644 index 0000000000000..967299787d85d --- /dev/null +++ b/src/vs/workbench/services/remote/common/tunnelService.ts @@ -0,0 +1,151 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ITunnelService, RemoteTunnel, ITunnelProvider } from 'vs/platform/remote/common/tunnel'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { ILogService } from 'vs/platform/log/common/log'; + +export abstract class AbstractTunnelService implements ITunnelService { + _serviceBrand: undefined; + + private _onTunnelOpened: Emitter = new Emitter(); + public onTunnelOpened: Event = this._onTunnelOpened.event; + private _onTunnelClosed: Emitter<{ host: string, port: number }> = new Emitter(); + public onTunnelClosed: Event<{ host: string, port: number }> = this._onTunnelClosed.event; + protected readonly _tunnels = new Map }>>(); + protected _tunnelProvider: ITunnelProvider | undefined; + + public constructor( + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @ILogService protected readonly logService: ILogService + ) { } + + setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable { + if (!provider) { + return { + dispose: () => { } + }; + } + this._tunnelProvider = provider; + return { + dispose: () => { + this._tunnelProvider = undefined; + } + }; + } + + public get tunnels(): Promise { + const promises: Promise[] = []; + Array.from(this._tunnels.values()).forEach(portMap => Array.from(portMap.values()).forEach(x => promises.push(x.value))); + return Promise.all(promises); + } + + dispose(): void { + for (const portMap of this._tunnels.values()) { + for (const { value } of portMap.values()) { + value.then(tunnel => tunnel.dispose()); + } + portMap.clear(); + } + this._tunnels.clear(); + } + + openTunnel(remoteHost: string | undefined, remotePort: number, localPort: number): Promise | undefined { + const remoteAuthority = this.environmentService.configuration.remoteAuthority; + if (!remoteAuthority) { + return undefined; + } + + if (!remoteHost || (remoteHost === '127.0.0.1')) { + remoteHost = 'localhost'; + } + + const resolvedTunnel = this.retainOrCreateTunnel(remoteAuthority, remoteHost, remotePort, localPort); + if (!resolvedTunnel) { + return resolvedTunnel; + } + + return resolvedTunnel.then(tunnel => { + const newTunnel = this.makeTunnel(tunnel); + if (tunnel.tunnelRemoteHost !== remoteHost || tunnel.tunnelRemotePort !== remotePort) { + this.logService.warn('Created tunnel does not match requirements of requested tunnel. Host or port mismatch.'); + } + this._onTunnelOpened.fire(newTunnel); + return newTunnel; + }); + } + + private makeTunnel(tunnel: RemoteTunnel): RemoteTunnel { + return { + tunnelRemotePort: tunnel.tunnelRemotePort, + tunnelRemoteHost: tunnel.tunnelRemoteHost, + tunnelLocalPort: tunnel.tunnelLocalPort, + localAddress: tunnel.localAddress, + dispose: () => { + const existingHost = this._tunnels.get(tunnel.tunnelRemoteHost); + if (existingHost) { + const existing = existingHost.get(tunnel.tunnelRemotePort); + if (existing) { + existing.refcount--; + this.tryDisposeTunnel(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort, existing); + } + } + } + }; + } + + private async tryDisposeTunnel(remoteHost: string, remotePort: number, tunnel: { refcount: number, readonly value: Promise }): Promise { + if (tunnel.refcount <= 0) { + const disposePromise: Promise = tunnel.value.then(tunnel => { + tunnel.dispose(); + this._onTunnelClosed.fire({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }); + }); + if (this._tunnels.has(remoteHost)) { + this._tunnels.get(remoteHost)!.delete(remotePort); + } + return disposePromise; + } + } + + async closeTunnel(remoteHost: string, remotePort: number): Promise { + const portMap = this._tunnels.get(remoteHost); + if (portMap && portMap.has(remotePort)) { + const value = portMap.get(remotePort)!; + value.refcount = 0; + await this.tryDisposeTunnel(remoteHost, remotePort, value); + } + } + + protected addTunnelToMap(remoteHost: string, remotePort: number, tunnel: Promise) { + if (!this._tunnels.has(remoteHost)) { + this._tunnels.set(remoteHost, new Map()); + } + this._tunnels.get(remoteHost)!.set(remotePort, { refcount: 1, value: tunnel }); + } + + protected abstract retainOrCreateTunnel(remoteAuthority: string, remoteHost: string, remotePort: number, localPort?: number): Promise | undefined; +} + +export class TunnelService extends AbstractTunnelService { + protected retainOrCreateTunnel(remoteAuthority: string, remoteHost: string, remotePort: number, localPort?: number | undefined): Promise | undefined { + const portMap = this._tunnels.get(remoteHost); + const existing = portMap ? portMap.get(remotePort) : undefined; + if (existing) { + ++existing.refcount; + return existing.value; + } + + if (this._tunnelProvider) { + const tunnel = this._tunnelProvider.forwardPort({ remoteAddress: { host: remoteHost, port: remotePort } }); + if (tunnel) { + this.addTunnelToMap(remoteHost, remotePort, tunnel); + } + return tunnel; + } + return undefined; + } +} diff --git a/src/vs/workbench/services/remote/node/tunnelService.ts b/src/vs/workbench/services/remote/node/tunnelService.ts index 2c9fd2942514f..194f4edfe586b 100644 --- a/src/vs/workbench/services/remote/node/tunnelService.ts +++ b/src/vs/workbench/services/remote/node/tunnelService.ts @@ -5,19 +5,19 @@ import * as net from 'net'; import { Barrier } from 'vs/base/common/async'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import product from 'vs/platform/product/common/product'; import { connectRemoteAgentTunnel, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { ITunnelService, RemoteTunnel, ITunnelProvider } from 'vs/platform/remote/common/tunnel'; +import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory'; import { ISignService } from 'vs/platform/sign/common/sign'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { findFreePort } from 'vs/base/node/ports'; -import { Event, Emitter } from 'vs/base/common/event'; +import { AbstractTunnelService } from 'vs/workbench/services/remote/common/tunnelService'; export async function createRemoteTunnel(options: IConnectionOptions, tunnelRemoteHost: string, tunnelRemotePort: number, tunnelLocalPort?: number): Promise { const tunnel = new NodeRemoteTunnel(options, tunnelRemoteHost, tunnelRemotePort, tunnelLocalPort); @@ -98,124 +98,17 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { } } -export class TunnelService implements ITunnelService { - _serviceBrand: undefined; - - private _onTunnelOpened: Emitter = new Emitter(); - public onTunnelOpened: Event = this._onTunnelOpened.event; - private _onTunnelClosed: Emitter<{ host: string, port: number }> = new Emitter(); - public onTunnelClosed: Event<{ host: string, port: number }> = this._onTunnelClosed.event; - private readonly _tunnels = new Map }>>(); - private _tunnelProvider: ITunnelProvider | undefined; - +export class TunnelService extends AbstractTunnelService { public constructor( - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @ILogService logService: ILogService, @IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService, @ISignService private readonly signService: ISignService, - @ILogService private readonly logService: ILogService, - ) { } - - setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable { - if (!provider) { - return { - dispose: () => { } - }; - } - this._tunnelProvider = provider; - return { - dispose: () => { - this._tunnelProvider = undefined; - } - }; - } - - public get tunnels(): Promise { - const promises: Promise[] = []; - Array.from(this._tunnels.values()).forEach(portMap => Array.from(portMap.values()).forEach(x => promises.push(x.value))); - return Promise.all(promises); - } - - dispose(): void { - for (const portMap of this._tunnels.values()) { - for (const { value } of portMap.values()) { - value.then(tunnel => tunnel.dispose()); - } - portMap.clear(); - } - this._tunnels.clear(); - } - - openTunnel(remoteHost: string | undefined, remotePort: number, localPort: number): Promise | undefined { - const remoteAuthority = this.environmentService.configuration.remoteAuthority; - if (!remoteAuthority) { - return undefined; - } - - if (!remoteHost || (remoteHost === '127.0.0.1')) { - remoteHost = 'localhost'; - } - - const resolvedTunnel = this.retainOrCreateTunnel(remoteAuthority, remoteHost, remotePort, localPort); - if (!resolvedTunnel) { - return resolvedTunnel; - } - - return resolvedTunnel.then(tunnel => { - const newTunnel = this.makeTunnel(tunnel); - this._onTunnelOpened.fire(newTunnel); - return newTunnel; - }); - } - - private makeTunnel(tunnel: RemoteTunnel): RemoteTunnel { - return { - tunnelRemotePort: tunnel.tunnelRemotePort, - tunnelRemoteHost: tunnel.tunnelRemoteHost, - tunnelLocalPort: tunnel.tunnelLocalPort, - localAddress: tunnel.localAddress, - dispose: () => { - const existingHost = this._tunnels.get(tunnel.tunnelRemoteHost); - if (existingHost) { - const existing = existingHost.get(tunnel.tunnelRemotePort); - if (existing) { - existing.refcount--; - this.tryDisposeTunnel(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort, existing); - } - } - } - }; - } - - private async tryDisposeTunnel(remoteHost: string, remotePort: number, tunnel: { refcount: number, readonly value: Promise }): Promise { - if (tunnel.refcount <= 0) { - const disposePromise: Promise = tunnel.value.then(tunnel => { - tunnel.dispose(); - this._onTunnelClosed.fire({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }); - }); - if (this._tunnels.has(remoteHost)) { - this._tunnels.get(remoteHost)!.delete(remotePort); - } - return disposePromise; - } - } - - async closeTunnel(remoteHost: string, remotePort: number): Promise { - const portMap = this._tunnels.get(remoteHost); - if (portMap && portMap.has(remotePort)) { - const value = portMap.get(remotePort)!; - value.refcount = 0; - await this.tryDisposeTunnel(remoteHost, remotePort, value); - } - } - - private addTunnelToMap(remoteHost: string, remotePort: number, tunnel: Promise) { - if (!this._tunnels.has(remoteHost)) { - this._tunnels.set(remoteHost, new Map()); - } - this._tunnels.get(remoteHost)!.set(remotePort, { refcount: 1, value: tunnel }); + ) { + super(environmentService, logService); } - private retainOrCreateTunnel(remoteAuthority: string, remoteHost: string, remotePort: number, localPort?: number): Promise | undefined { + protected retainOrCreateTunnel(remoteAuthority: string, remoteHost: string, remotePort: number, localPort?: number): Promise | undefined { const portMap = this._tunnels.get(remoteHost); const existing = portMap ? portMap.get(remotePort) : undefined; if (existing) { diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 5e2ea4fd604b9..d33ec02d21b79 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -34,6 +34,26 @@ interface IExternalUriResolver { (uri: URI): Promise; } +interface TunnelOptions { + remoteAddress: { port: number, host: string }; + // The desired local port. If this port can't be used, then another will be chosen. + localAddressPort?: number; + label?: string; +} + +interface Tunnel { + remoteAddress: { port: number, host: string }; + //The complete local address(ex. localhost:1234) + localAddress: string; + // Implementers of Tunnel should fire onDidDispose when dispose is called. + onDidDispose: Event; + dispose(): void; +} + +interface ITunnelFactory { + (tunnelOptions: TunnelOptions): Thenable | undefined; +} + interface IWorkbenchConstructionOptions { /** @@ -104,6 +124,11 @@ interface IWorkbenchConstructionOptions { */ readonly resolveExternalUri?: IExternalUriResolver; + /** + * Support for creating tunnels. + */ + readonly tunnelFactory?: ITunnelFactory; + /** * Current logging level. Default is `LogLevel.Info`. */ diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 65b74060fa184..ecd9ff04e173e 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -60,7 +60,7 @@ import { BackupFileService } from 'vs/workbench/services/backup/common/backupFil import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; -import { NoOpTunnelService } from 'vs/platform/remote/common/tunnelService'; +import { TunnelService } from 'vs/workbench/services/remote/common/tunnelService'; import { ILoggerService } from 'vs/platform/log/common/log'; import { FileLoggerService } from 'vs/platform/log/common/fileLogService'; import { IAuthTokenService } from 'vs/platform/auth/common/auth'; @@ -75,7 +75,7 @@ registerSingleton(IExtensionManagementService, ExtensionManagementService); registerSingleton(IBackupFileService, BackupFileService); registerSingleton(IAccessibilityService, BrowserAccessibilityService, true); registerSingleton(IContextMenuService, ContextMenuService); -registerSingleton(ITunnelService, NoOpTunnelService, true); +registerSingleton(ITunnelService, TunnelService, true); registerSingleton(ILoggerService, FileLoggerService); registerSingleton(IAuthTokenService, AuthTokenService); registerSingleton(IUserDataSyncLogService, UserDataSyncLogService); From fd1a44238f75296babd5ea644598a6eff99a020e Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 9 Jan 2020 09:27:06 +0100 Subject: [PATCH 075/315] fixes #88261 --- src/vs/workbench/contrib/files/browser/fileActions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 3cdf705bf44d9..87a82bf8b7c7a 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -1099,6 +1099,7 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { if (pasteShouldMove) { // Cut is done. Make sure to clear cut state. explorerService.setToCopy([], false); + pasteShouldMove = false; } if (stats.length >= 1) { const stat = stats[0]; From b3d45da5833e4c6455a256af2b87b9dc28fde4ed Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 9 Jan 2020 09:43:16 +0100 Subject: [PATCH 076/315] fixes #88292 --- .../workbench/contrib/debug/browser/debugEditorContribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 230f047ad028b..5f6e668169516 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -36,7 +36,7 @@ import { getHover } from 'vs/editor/contrib/hover/getHover'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; const HOVER_DELAY = 300; -const LAUNCH_JSON_REGEX = /launch\.json$/; +const LAUNCH_JSON_REGEX = /\.vscode\/launch\.json$/; const INLINE_VALUE_DECORATION_KEY = 'inlinevaluedecoration'; const MAX_NUM_INLINE_VALUES = 100; // JS Global scope can have 700+ entries. We want to limit ourselves for perf reasons const MAX_INLINE_DECORATOR_LENGTH = 150; // Max string length of each inline decorator when debugging. If exceeded ... is added From f3a313a360f4959249aff59533120975a693c2c8 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Thu, 9 Jan 2020 10:15:01 +0100 Subject: [PATCH 077/315] Extract breaking conditions --- .../characterHardWrappingLineMapper.ts | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index e1fff8b193eba..013e7c25bd95e 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -126,13 +126,26 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor let breakOffset = 0; let breakOffsetVisibleColumn = 0; const len = lineText.length; + const len1 = len - 1; let breakingColumn = firstLineBreakingColumn; + let prevCharCode = CharCode.Null; + let prevCharCodeClass = CharacterClass.NONE; + let charCode = CharCode.Null; + let charCodeClass = CharacterClass.NONE; + let nextCharCode = (len > 0 ? lineText.charCodeAt(0) : CharCode.Null); + let nextCharCodeClass = classifier.get(nextCharCode); + for (let i = 0; i < len; i++) { // At this point, there is a certainty that the character before `i` fits on the current line, // but the character at `i` might not fit - const charCode = lineText.charCodeAt(i); + prevCharCode = charCode; + prevCharCodeClass = charCodeClass; + charCode = nextCharCode; + charCodeClass = nextCharCodeClass; + nextCharCode = (i < len1 ? lineText.charCodeAt(i + 1) : CharCode.Null); + nextCharCodeClass = classifier.get(nextCharCode); if (strings.isLowSurrogate(charCode)) { // A surrogate pair must always be considered as a single unit, so it is never to be broken @@ -140,16 +153,7 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor continue; } - const charCodeClass = classifier.get(charCode); - - if ( - (charCodeClass === CharacterClass.BREAK_BEFORE) - || (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && i > 0 && classifier.get(lineText.charCodeAt(i - 1)) !== CharacterClass.BREAK_BEFORE) - ) { - // This is a character that indicates that a break should happen before it - // (or) CJK breaking : before break : Kinsoku Shori : Don't break after a leading character, like an open bracket - // Since we are certain the character before `i` fits, there's no extra checking needed, - // just mark it as a nice breaking opportunity + if (prevCharCode !== CharCode.Null && canBreakBefore(charCodeClass, prevCharCodeClass)) { breakOffset = i; breakOffsetVisibleColumn = visibleColumn; } @@ -180,12 +184,7 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor } // At this point, there is a certainty that the character at `i` fits on the current line - if ( - (charCodeClass === CharacterClass.BREAK_AFTER && (hardWrappingIndent === WrappingIndent.None || i >= firstNonWhitespaceIndex)) - || (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && i + 1 < len && classifier.get(lineText.charCodeAt(i + 1)) !== CharacterClass.BREAK_AFTER) - ) { - // This is a character that indicates that a break should happen after it - // (or) CJK breaking : after break : Kinsoku Shori : Don't break before a trailing character, like a period + if (nextCharCode !== CharCode.Null && canBreakAfter(charCodeClass, nextCharCodeClass)) { breakOffset = i + 1; breakOffsetVisibleColumn = visibleColumn; } @@ -206,3 +205,21 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor function tabCharacterWidth(visibleColumn: number, tabSize: number): number { return (tabSize - (visibleColumn % tabSize)); } + +function canBreakBefore(charCodeClass: CharacterClass, prevCharCodeClass: CharacterClass): boolean { + // This is a character that indicates that a break should happen before it + // (or) CJK breaking : before break : Kinsoku Shori : Don't break after a leading character, like an open bracket + return ( + (charCodeClass === CharacterClass.BREAK_BEFORE) + || (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && prevCharCodeClass !== CharacterClass.BREAK_BEFORE) + ); +} + +function canBreakAfter(charCodeClass: CharacterClass, nextCharCodeClass: CharacterClass): boolean { + // This is a character that indicates that a break should happen after it + // (or) CJK breaking : after break : Kinsoku Shori : Don't break before a trailing character, like a period + return ( + (charCodeClass === CharacterClass.BREAK_AFTER) + || (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && nextCharCodeClass !== CharacterClass.BREAK_AFTER) + ); +} From 4845bbbdda77efab603a43a339a8bd26b1878ac2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 9 Jan 2020 10:24:32 +0100 Subject: [PATCH 078/315] editor tracker - also open untitled editors when being dirty --- .../browser/editors/fileEditorTracker.ts | 56 ++++++---- .../test/browser/fileEditorTracker.test.ts | 102 ++++++++++++++++-- 2 files changed, 132 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index e82b2a505d4a2..bebcb9fcf9976 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -21,17 +21,16 @@ import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { timeout } from 'vs/base/common/async'; +import { timeout, RunOnceWorker } from 'vs/base/common/async'; import { withNullAsUndefined } from 'vs/base/common/types'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { isEqualOrParent, joinPath } from 'vs/base/common/resources'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; export class FileEditorTracker extends Disposable implements IWorkbenchContribution { private readonly activeOutOfWorkspaceWatchers = new ResourceMap(); - private closeOnFileDelete: boolean = false; - constructor( @IEditorService private readonly editorService: IEditorService, @ITextFileService private readonly textFileService: ITextFileService, @@ -43,6 +42,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IHostService private readonly hostService: IHostService, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService ) { super(); @@ -59,9 +59,10 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut // Update editors from disk changes this._register(this.fileService.onFileChanges(e => this.onFileChanges(e))); - // Ensure dirty text file models are always opened as editors + // Ensure dirty text file and untitled models are always opened as editors this._register(this.textFileService.models.onModelsDirty(e => this.ensureDirtyTextFilesAreOpened(e))); this._register(this.textFileService.models.onModelsSaveError(e => this.ensureDirtyTextFilesAreOpened(e))); + this._register(this.untitledTextEditorService.onDidChangeDirty(e => this.ensureDirtyUntitledTextFilesAreOpenedWorker.work(e))); // Out of workspace file watchers this._register(this.editorService.onDidVisibleEditorsChange(() => this.onDidVisibleEditorsChange())); @@ -175,7 +176,17 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut //#endregion - //#region File Changes: Close editors of deleted files + //#region File Changes: Close editors of deleted files unless configured otherwise + + private closeOnFileDelete: boolean = false; + + private onConfigurationUpdated(configuration: IWorkbenchEditorConfiguration): void { + if (typeof configuration.workbench?.editor?.closeOnFileDelete === 'boolean') { + this.closeOnFileDelete = configuration.workbench.editor.closeOnFileDelete; + } else { + this.closeOnFileDelete = false; // default + } + } private onFileChanges(e: FileChangesEvent): void { if (e.gotDeleted()) { @@ -264,16 +275,33 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut //#endregion - //#region Text File: Ensure every dirty text file is opened in an editor + //#region Text File: Ensure every dirty text and untitled file is opened in an editor + + private readonly ensureDirtyUntitledTextFilesAreOpenedWorker = this._register(new RunOnceWorker(units => this.ensureDirtyUntitledTextFilesAreOpened(units), 250)); private ensureDirtyTextFilesAreOpened(events: ReadonlyArray): void { - this.editorService.openEditors(distinct(events.filter(({ resource }) => { + this.doEnsureDirtyFilesAreOpened(distinct(events.filter(({ resource }) => { const model = this.textFileService.models.get(resource); return model?.hasState(ModelState.DIRTY) && // model must be dirty !model.hasState(ModelState.PENDING_SAVE) && // model should not be saving currently !this.editorService.isOpen({ resource }); // model is not currently opened as editor - }).map(event => event.resource), resource => resource.toString()).map(resource => ({ + }).map(event => event.resource), resource => resource.toString())); + } + + private ensureDirtyUntitledTextFilesAreOpened(resources: URI[]): void { + this.doEnsureDirtyFilesAreOpened(distinct(resources.filter(resource => { + return this.untitledTextEditorService.isDirty(resource) && // untitled must be dirty + !this.editorService.isOpen({ resource }); // untitled is not currently opened as editor + }), resource => resource.toString())); + } + + private doEnsureDirtyFilesAreOpened(resources: URI[]): void { + if (!resources.length) { + return; + } + + this.editorService.openEditors(resources.map(resource => ({ resource, options: { inactive: true, pinned: true, preserveFocus: true } }))); @@ -352,18 +380,6 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut //#endregion - //#region Configuration Change - - private onConfigurationUpdated(configuration: IWorkbenchEditorConfiguration): void { - if (typeof configuration.workbench?.editor?.closeOnFileDelete === 'boolean') { - this.closeOnFileDelete = configuration.workbench.editor.closeOnFileDelete; - } else { - this.closeOnFileDelete = false; // default - } - } - - //#endregion - dispose(): void { super.dispose(); diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts index 15d60eb041d8f..5d28531ffe619 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts @@ -8,33 +8,57 @@ import { FileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/fi import { toResource } from 'vs/base/test/common/utils'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { workbenchInstantiationService, TestTextFileService, TestFileService } from 'vs/workbench/test/workbenchTestServices'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITextFileService, IResolvedTextFileEditorModel, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { timeout } from 'vs/base/common/async'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { EditorInput } from 'vs/workbench/common/editor'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; +import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; +import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; class ServiceAccessor { constructor( @IEditorService public editorService: IEditorService, @IEditorGroupsService public editorGroupService: IEditorGroupsService, @ITextFileService public textFileService: TestTextFileService, - @IFileService public fileService: TestFileService + @IFileService public fileService: TestFileService, + @IUntitledTextEditorService public untitledTextEditorService: IUntitledTextEditorService ) { } } suite('Files - FileEditorTracker', () => { - let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let disposables: IDisposable[] = []; setup(() => { - instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + disposables.push(Registry.as(EditorExtensions.Editors).registerEditor( + EditorDescriptor.create( + TextFileEditor, + TextFileEditor.ID, + 'Text File Editor' + ), + [new SyncDescriptor(FileEditorInput)] + )); + }); + + teardown(() => { + dispose(disposables); + disposables = []; }); test('file change event updates model', async function () { + const instantiationService = workbenchInstantiationService(); + const accessor = instantiationService.createInstance(ServiceAccessor); + const tracker = instantiationService.createInstance(FileEditorTracker); const resource = toResource.call(this, '/path/index.txt'); @@ -54,5 +78,71 @@ suite('Files - FileEditorTracker', () => { assert.equal(snapshotToString(model.createSnapshot()!), 'Hello Html'); tracker.dispose(); + (accessor.textFileService.models).clear(); + (accessor.textFileService.models).dispose(); + }); + + test('dirty text file model opens as editor', async function () { + const instantiationService = workbenchInstantiationService(); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + instantiationService.stub(IEditorGroupsService, part); + + const editorService: EditorService = instantiationService.createInstance(EditorService); + instantiationService.stub(IEditorService, editorService); + + const accessor = instantiationService.createInstance(ServiceAccessor); + + const tracker = instantiationService.createInstance(FileEditorTracker); + + const resource = toResource.call(this, '/path/index.txt'); + + assert.ok(!editorService.isOpen({ resource })); + + const model = await accessor.textFileService.models.loadOrCreate(resource) as IResolvedTextFileEditorModel; + + model.textEditorModel.setValue('Super Good'); + + await timeout(300 /* 250ms debounce delay in text file editor model manager */); + assert.ok(editorService.isOpen({ resource })); + + part.dispose(); + tracker.dispose(); + (accessor.textFileService.models).clear(); + (accessor.textFileService.models).dispose(); + }); + + test('dirty untitled text file model opens as editor', async function () { + const instantiationService = workbenchInstantiationService(); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + instantiationService.stub(IEditorGroupsService, part); + + const editorService: EditorService = instantiationService.createInstance(EditorService); + instantiationService.stub(IEditorService, editorService); + + const accessor = instantiationService.createInstance(ServiceAccessor); + + const tracker = instantiationService.createInstance(FileEditorTracker); + + const untitledEditor = accessor.untitledTextEditorService.createOrGet(); + const model = await untitledEditor.resolve(); + + assert.ok(!editorService.isOpen(untitledEditor)); + + model.textEditorModel.setValue('Super Good'); + + await timeout(300 /* 250ms debounce delay in tracker */); + assert.ok(editorService.isOpen(untitledEditor)); + + part.dispose(); + tracker.dispose(); + model.dispose(); }); }); From 4f6af32f431b18ef07dc0981d444b9f8163d6e67 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 9 Jan 2020 10:25:59 +0100 Subject: [PATCH 079/315] untitled - merge createOrGet and loadOrCreate into one --- .../api/browser/mainThreadDocuments.ts | 4 +-- .../backup/common/backupModelTracker.ts | 4 +-- .../textfile/browser/textFileService.ts | 4 +-- .../common/textModelResolverService.ts | 2 +- .../common/untitledTextEditorService.ts | 26 +++++++------------ .../common/editor/untitledTextEditor.test.ts | 10 +++---- 6 files changed, 22 insertions(+), 28 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index 2b743a9484fbc..c54c261302807 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -227,12 +227,12 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { } private _doCreateUntitled(resource?: URI, mode?: string, initialValue?: string): Promise { - return this._untitledTextEditorService.loadOrCreate({ + return this._untitledTextEditorService.createOrGet({ resource, mode, initialValue, useResourcePath: Boolean(resource && resource.path) - }).then(model => { + }).resolve().then(model => { const resource = model.resource; if (!this._modelIsSynced.has(resource.toString())) { diff --git a/src/vs/workbench/contrib/backup/common/backupModelTracker.ts b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts index 584291fb08845..2f44e59ad78cd 100644 --- a/src/vs/workbench/contrib/backup/common/backupModelTracker.ts +++ b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts @@ -67,13 +67,13 @@ export class BackupModelTracker extends Disposable implements IWorkbenchContribu private onUntitledModelCreated(resource: Uri): void { if (this.untitledTextEditorService.isDirty(resource)) { - this.untitledTextEditorService.loadOrCreate({ resource }).then(model => model.backup()); + this.untitledTextEditorService.createOrGet({ resource }).resolve().then(model => model.backup()); } } private onUntitledModelChanged(resource: Uri): void { if (this.untitledTextEditorService.isDirty(resource)) { - this.untitledTextEditorService.loadOrCreate({ resource }).then(model => model.backup()); + this.untitledTextEditorService.createOrGet({ resource }).resolve().then(model => model.backup()); } else { this.discardBackup(resource); } diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 983cf4c991cf0..93658cabb39e3 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -218,7 +218,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Handle untitled resources await Promise.all(untitledResources .filter(untitled => this.untitledTextEditorService.exists(untitled)) - .map(async untitled => (await this.untitledTextEditorService.loadOrCreate({ resource: untitled })).backup())); + .map(async untitled => (await this.untitledTextEditorService.createOrGet({ resource: untitled }).resolve()).backup())); } private async confirmBeforeShutdown(): Promise { @@ -710,7 +710,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex if (this.fileService.canHandleResource(resource)) { model = this._models.get(resource); } else if (resource.scheme === Schemas.untitled && this.untitledTextEditorService.exists(resource)) { - model = await this.untitledTextEditorService.loadOrCreate({ resource }); + model = await this.untitledTextEditorService.createOrGet({ resource }).resolve(); } // We have a model: Use it (can be null e.g. if this file is binary and not a text file or was never opened before) diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index 154daabf6bb99..2b630820d7fe8 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -141,7 +141,7 @@ export class TextModelResolverService implements ITextModelService { // Untitled Schema: go through cached input if (resource.scheme === network.Schemas.untitled) { - const model = await this.untitledTextEditorService.loadOrCreate({ resource }); + const model = await this.untitledTextEditorService.createOrGet({ resource }).resolve(); return new ImmortalReference(model as IResolvedTextEditorModel); } diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts index 7d8d31497aead..9131aa8def527 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts @@ -11,7 +11,6 @@ import { IFilesConfiguration, IFileService } from 'vs/platform/files/common/file import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Event, Emitter } from 'vs/base/common/event'; import { ResourceMap } from 'vs/base/common/map'; -import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; import { Schemas } from 'vs/base/common/network'; import { Disposable } from 'vs/base/common/lifecycle'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -19,7 +18,7 @@ import { basename } from 'vs/base/common/resources'; export const IUntitledTextEditorService = createDecorator('untitledTextEditorService'); -export interface IModelLoadOrCreateOptions { +export interface IUntitledCreationOptions { resource?: URI; mode?: string; initialValue?: string; @@ -88,16 +87,8 @@ export interface IUntitledTextEditorService { * It is valid to pass in a file resource. In that case the path will be used as identifier. * The use case is to be able to create a new file with a specific path with VSCode. */ - createOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string): UntitledTextEditorInput; - - /** - * Creates a new untitled model with the optional resource URI or returns an existing one - * if the provided resource exists already as untitled model. - * - * It is valid to pass in a file resource. In that case the path will be used as identifier. - * The use case is to be able to create a new file with a specific path with VSCode. - */ - loadOrCreate(options: IModelLoadOrCreateOptions): Promise; + createOrGet(options?: IUntitledCreationOptions): UntitledTextEditorInput; + createOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath?: boolean): UntitledTextEditorInput; /** * A check to find out if a untitled resource has a file path associated or not. @@ -201,11 +192,14 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe .map(i => i.getResource()); } - loadOrCreate(options: IModelLoadOrCreateOptions = Object.create(null)): Promise { - return this.createOrGet(options.resource, options.mode, options.initialValue, options.encoding, options.useResourcePath).resolve(); - } + createOrGet(options?: IUntitledCreationOptions): UntitledTextEditorInput; + createOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath?: boolean): UntitledTextEditorInput; + createOrGet(resourceOrOptions?: URI | IUntitledCreationOptions, mode?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath: boolean = false): UntitledTextEditorInput { + if (resourceOrOptions && !URI.isUri(resourceOrOptions)) { + return this.createOrGet(resourceOrOptions.resource, resourceOrOptions.mode, resourceOrOptions.initialValue, resourceOrOptions.encoding, resourceOrOptions.useResourcePath); + } - createOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath: boolean = false): UntitledTextEditorInput { + let resource = resourceOrOptions; if (resource) { // Massage resource if it comes with known file based resource diff --git a/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts index 0303b39b6dab8..c9ca35dc1440d 100644 --- a/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts +++ b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts @@ -148,10 +148,10 @@ suite('Workbench untitled text editors', () => { input.dispose(); }); - test('Untitled via loadOrCreate', async () => { + test('Untitled via createOrGet options', async () => { const service = accessor.untitledTextEditorService; - const model1 = await service.loadOrCreate(); + const model1 = await service.createOrGet().resolve(); model1.textEditorModel!.setValue('foo bar'); assert.ok(model1.isDirty()); @@ -159,17 +159,17 @@ suite('Workbench untitled text editors', () => { model1.textEditorModel!.setValue(''); assert.ok(!model1.isDirty()); - const model2 = await service.loadOrCreate({ initialValue: 'Hello World' }); + const model2 = await service.createOrGet({ initialValue: 'Hello World' }).resolve(); assert.equal(snapshotToString(model2.createSnapshot()!), 'Hello World'); const input = service.createOrGet(); - const model3 = await service.loadOrCreate({ resource: input.getResource() }); + const model3 = await service.createOrGet({ resource: input.getResource() }).resolve(); assert.equal(model3.resource.toString(), input.getResource().toString()); const file = URI.file(join('C:\\', '/foo/file44.txt')); - const model4 = await service.loadOrCreate({ resource: file }); + const model4 = await service.createOrGet({ resource: file }).resolve(); assert.ok(service.hasAssociatedFilePath(model4.resource)); assert.ok(model4.isDirty()); From e9bb8b306c94be2d66ec64f2da186e58399a08fd Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 9 Jan 2020 10:34:35 +0100 Subject: [PATCH 080/315] do not autoclose quotes when in strings. Fixes #88265 --- extensions/bat/language-configuration.json | 2 +- extensions/clojure/language-configuration.json | 4 ++-- extensions/coffeescript/language-configuration.json | 4 ++-- extensions/groovy/language-configuration.json | 6 +++--- extensions/ini/ini.language-configuration.json | 6 +++--- extensions/ini/properties.language-configuration.json | 4 ++-- extensions/java/language-configuration.json | 4 ++-- extensions/lua/language-configuration.json | 4 ++-- extensions/objective-c/language-configuration.json | 6 +++--- extensions/perl/perl.language-configuration.json | 8 ++++---- extensions/perl/perl6.language-configuration.json | 8 ++++---- extensions/pug/language-configuration.json | 6 +++--- extensions/r/language-configuration.json | 8 +++++--- extensions/ruby/language-configuration.json | 8 +++++--- extensions/rust/language-configuration.json | 5 ++--- extensions/shaderlab/language-configuration.json | 4 ++-- extensions/shellscript/language-configuration.json | 6 +++--- extensions/swift/language-configuration.json | 8 ++++---- extensions/vb/language-configuration.json | 4 ++-- extensions/xml/xml.language-configuration.json | 4 ++-- 20 files changed, 56 insertions(+), 53 deletions(-) diff --git a/extensions/bat/language-configuration.json b/extensions/bat/language-configuration.json index 2fb5445a34a47..e4639541142a0 100644 --- a/extensions/bat/language-configuration.json +++ b/extensions/bat/language-configuration.json @@ -11,7 +11,7 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""] + { "open": "\"", "close": "\"", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], diff --git a/extensions/clojure/language-configuration.json b/extensions/clojure/language-configuration.json index 24f5248001560..a62cb968ece20 100644 --- a/extensions/clojure/language-configuration.json +++ b/extensions/clojure/language-configuration.json @@ -11,7 +11,7 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""] + { "open": "\"", "close": "\"", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -22,4 +22,4 @@ "folding": { "offSide": true } -} \ No newline at end of file +} diff --git a/extensions/coffeescript/language-configuration.json b/extensions/coffeescript/language-configuration.json index fd4a1b88c87dd..c2b8662039d61 100644 --- a/extensions/coffeescript/language-configuration.json +++ b/extensions/coffeescript/language-configuration.json @@ -12,8 +12,8 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], diff --git a/extensions/groovy/language-configuration.json b/extensions/groovy/language-configuration.json index e4656d0babb69..a81a8864a5127 100644 --- a/extensions/groovy/language-configuration.json +++ b/extensions/groovy/language-configuration.json @@ -12,8 +12,8 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -22,4 +22,4 @@ ["\"", "\""], ["'", "'"] ] -} \ No newline at end of file +} diff --git a/extensions/ini/ini.language-configuration.json b/extensions/ini/ini.language-configuration.json index 93cfa1ce3b9d2..c688aee426d69 100644 --- a/extensions/ini/ini.language-configuration.json +++ b/extensions/ini/ini.language-configuration.json @@ -12,8 +12,8 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -22,4 +22,4 @@ ["\"", "\""], ["'", "'"] ] -} \ No newline at end of file +} diff --git a/extensions/ini/properties.language-configuration.json b/extensions/ini/properties.language-configuration.json index 85b401f07676c..7dd2e4ebe2e81 100644 --- a/extensions/ini/properties.language-configuration.json +++ b/extensions/ini/properties.language-configuration.json @@ -12,7 +12,7 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""] + { "open": "\"", "close": "\"", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -21,4 +21,4 @@ ["\"", "\""], ["'", "'"] ] -} \ No newline at end of file +} diff --git a/extensions/java/language-configuration.json b/extensions/java/language-configuration.json index a2909abb800e9..e19d2d749f875 100644 --- a/extensions/java/language-configuration.json +++ b/extensions/java/language-configuration.json @@ -12,8 +12,8 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"], + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] }, { "open": "/**", "close": " */", "notIn": ["string"] } ], "surroundingPairs": [ diff --git a/extensions/lua/language-configuration.json b/extensions/lua/language-configuration.json index 89e5c45b9ff01..5e5e0ab447706 100644 --- a/extensions/lua/language-configuration.json +++ b/extensions/lua/language-configuration.json @@ -12,8 +12,8 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], diff --git a/extensions/objective-c/language-configuration.json b/extensions/objective-c/language-configuration.json index e4656d0babb69..a81a8864a5127 100644 --- a/extensions/objective-c/language-configuration.json +++ b/extensions/objective-c/language-configuration.json @@ -12,8 +12,8 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -22,4 +22,4 @@ ["\"", "\""], ["'", "'"] ] -} \ No newline at end of file +} diff --git a/extensions/perl/perl.language-configuration.json b/extensions/perl/perl.language-configuration.json index c142f2d58f697..4d18f1fbaa485 100644 --- a/extensions/perl/perl.language-configuration.json +++ b/extensions/perl/perl.language-configuration.json @@ -11,9 +11,9 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"], - ["`", "`"] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] }, + { "open": "`", "close": "`", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -29,4 +29,4 @@ "end": "^(?:(?:=cut\\s*$)|(?:\\s*#endregion\\b))" } } -} \ No newline at end of file +} diff --git a/extensions/perl/perl6.language-configuration.json b/extensions/perl/perl6.language-configuration.json index 01b6a8a28239e..be52105cbdf14 100644 --- a/extensions/perl/perl6.language-configuration.json +++ b/extensions/perl/perl6.language-configuration.json @@ -11,9 +11,9 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"], - ["`", "`"] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] }, + { "open": "`", "close": "`", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -23,4 +23,4 @@ ["'", "'"], ["`", "`"] ] -} \ No newline at end of file +} diff --git a/extensions/pug/language-configuration.json b/extensions/pug/language-configuration.json index 00f4885d46e8a..98a2b77ca27d3 100644 --- a/extensions/pug/language-configuration.json +++ b/extensions/pug/language-configuration.json @@ -11,8 +11,8 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["'", "'"], - ["\"", "\""] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -24,4 +24,4 @@ "folding": { "offSide": true } -} \ No newline at end of file +} diff --git a/extensions/r/language-configuration.json b/extensions/r/language-configuration.json index 6293d4d61a694..dd691e2a6d4c4 100644 --- a/extensions/r/language-configuration.json +++ b/extensions/r/language-configuration.json @@ -11,12 +11,14 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""] + ["\"", "\""], + ["'", "'"] ] -} \ No newline at end of file +} diff --git a/extensions/ruby/language-configuration.json b/extensions/ruby/language-configuration.json index 47c434deffafa..81fdee540f216 100644 --- a/extensions/ruby/language-configuration.json +++ b/extensions/ruby/language-configuration.json @@ -12,15 +12,17 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] }, + { "open": "`", "close": "`", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], ["[", "]"], ["(", ")"], ["\"", "\""], - ["'", "'"] + ["'", "'"], + ["`", "`"] ], "indentationRules": { "increaseIndentPattern": "^\\s*((begin|class|(private|protected)\\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|while|case)|([^#]*\\sdo\\b)|([^#]*=\\s*(case|if|unless)))\\b([^#\\{;]|(\"|'|\/).*\\4)*(#.*)?$", diff --git a/extensions/rust/language-configuration.json b/extensions/rust/language-configuration.json index bd4cbb8d8f079..5d4f16d26a650 100644 --- a/extensions/rust/language-configuration.json +++ b/extensions/rust/language-configuration.json @@ -12,14 +12,13 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""] + { "open": "\"", "close": "\"", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"] + ["\"", "\""] ], "indentationRules": { "increaseIndentPattern": "^.*\\{[^}\"']*$|^.*\\([^\\)\"']*$", diff --git a/extensions/shaderlab/language-configuration.json b/extensions/shaderlab/language-configuration.json index 8d91e91a89e40..6af8237672646 100644 --- a/extensions/shaderlab/language-configuration.json +++ b/extensions/shaderlab/language-configuration.json @@ -12,7 +12,7 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""] + { "open": "\"", "close": "\"", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -20,4 +20,4 @@ ["(", ")"], ["\"", "\""] ] -} \ No newline at end of file +} diff --git a/extensions/shellscript/language-configuration.json b/extensions/shellscript/language-configuration.json index 964623ac4cc15..8421a3817a275 100644 --- a/extensions/shellscript/language-configuration.json +++ b/extensions/shellscript/language-configuration.json @@ -11,9 +11,9 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"], - ["`", "`"] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] }, + { "open": "`", "close": "`", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], diff --git a/extensions/swift/language-configuration.json b/extensions/swift/language-configuration.json index 2bdc27527498e..54095ef5212e2 100644 --- a/extensions/swift/language-configuration.json +++ b/extensions/swift/language-configuration.json @@ -12,9 +12,9 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"], - ["`", "`"] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] }, + { "open": "`", "close": "`", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -24,4 +24,4 @@ ["'", "'"], ["`", "`"] ] -} \ No newline at end of file +} diff --git a/extensions/vb/language-configuration.json b/extensions/vb/language-configuration.json index d9a6b21014a9b..a31b67bec0fe0 100644 --- a/extensions/vb/language-configuration.json +++ b/extensions/vb/language-configuration.json @@ -12,7 +12,7 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""] + { "open": "\"", "close": "\"", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -27,4 +27,4 @@ "end": "^\\s*#End Region\\b" } } -} \ No newline at end of file +} diff --git a/extensions/xml/xml.language-configuration.json b/extensions/xml/xml.language-configuration.json index 702b6dc6eb724..15664cbb4f5f3 100644 --- a/extensions/xml/xml.language-configuration.json +++ b/extensions/xml/xml.language-configuration.json @@ -12,8 +12,8 @@ { "open": "{", "close": "}"}, { "open": "[", "close": "]"}, { "open": "(", "close": ")" }, - { "open": "'", "close": "'" }, - { "open": "\"", "close": "\"" }, + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] }, { "open": "", "notIn": [ "comment", "string" ]}, { "open": "", "notIn": [ "comment", "string" ]} ], From 3d2581a5385355988f13b69f10a2bf4bbce1308a Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Thu, 9 Jan 2020 10:36:11 +0100 Subject: [PATCH 081/315] Pass in previousBreakingData to line mapping computation --- .../characterHardWrappingLineMapper.ts | 62 +++++++------ .../common/viewModel/splitLinesCollection.ts | 86 ++++++++++++------- .../editor/common/viewModel/viewModelImpl.ts | 4 +- .../characterHardWrappingLineMapper.test.ts | 2 +- 4 files changed, 91 insertions(+), 63 deletions(-) diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index 013e7c25bd95e..8d58bcaeccfb8 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -72,50 +72,28 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor columnsForFullWidthChar = +columnsForFullWidthChar; //@perf let requests: string[] = []; + let previousBreakingData: (LineBreakingData | null)[] = []; return { - addRequest: (lineText: string) => { + addRequest: (lineText: string, previousLineBreakingData: LineBreakingData | null) => { requests.push(lineText); + previousBreakingData.push(previousLineBreakingData); }, finalize: () => { let result: (LineBreakingData | null)[] = []; for (let i = 0, len = requests.length; i < len; i++) { - result[i] = this._createLineMapping(requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent); + result[i] = this._createLineMapping(requests[i], previousBreakingData[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent); } return result; } }; } - private _createLineMapping(lineText: string, tabSize: number, firstLineBreakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): LineBreakingData | null { + private _createLineMapping(lineText: string, previousBreakingData: LineBreakingData | null, tabSize: number, firstLineBreakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): LineBreakingData | null { if (firstLineBreakingColumn === -1) { return null; } - let firstNonWhitespaceIndex = -1; - let wrappedTextIndentLength = 0; - if (hardWrappingIndent !== WrappingIndent.None) { - firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineText); - if (firstNonWhitespaceIndex !== -1) { - // Track existing indent - - for (let i = 0; i < firstNonWhitespaceIndex; i++) { - const charWidth = (lineText.charCodeAt(i) === CharCode.Tab ? tabCharacterWidth(wrappedTextIndentLength, tabSize) : 1); - wrappedTextIndentLength += charWidth; - } - - // Increase indent of continuation lines, if desired - const numberOfAdditionalTabs = (hardWrappingIndent === WrappingIndent.DeepIndent ? 2 : hardWrappingIndent === WrappingIndent.Indent ? 1 : 0); - for (let i = 0; i < numberOfAdditionalTabs; i++) { - const charWidth = tabCharacterWidth(wrappedTextIndentLength, tabSize); - wrappedTextIndentLength += charWidth; - } - - // Force sticking to beginning of line if no character would fit except for the indentation - if (wrappedTextIndentLength + columnsForFullWidthChar > firstLineBreakingColumn) { - wrappedTextIndentLength = 0; - } - } - } + const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakingColumn, columnsForFullWidthChar, hardWrappingIndent); const wrappedLineBreakingColumn = firstLineBreakingColumn - wrappedTextIndentLength; const classifier = this.classifier; @@ -223,3 +201,31 @@ function canBreakAfter(charCodeClass: CharacterClass, nextCharCodeClass: Charact || (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && nextCharCodeClass !== CharacterClass.BREAK_AFTER) ); } + +function computeWrappedTextIndentLength(lineText: string, tabSize: number, firstLineBreakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): number { + let wrappedTextIndentLength = 0; + if (hardWrappingIndent !== WrappingIndent.None) { + const firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineText); + if (firstNonWhitespaceIndex !== -1) { + // Track existing indent + + for (let i = 0; i < firstNonWhitespaceIndex; i++) { + const charWidth = (lineText.charCodeAt(i) === CharCode.Tab ? tabCharacterWidth(wrappedTextIndentLength, tabSize) : 1); + wrappedTextIndentLength += charWidth; + } + + // Increase indent of continuation lines, if desired + const numberOfAdditionalTabs = (hardWrappingIndent === WrappingIndent.DeepIndent ? 2 : hardWrappingIndent === WrappingIndent.Indent ? 1 : 0); + for (let i = 0; i < numberOfAdditionalTabs; i++) { + const charWidth = tabCharacterWidth(wrappedTextIndentLength, tabSize); + wrappedTextIndentLength += charWidth; + } + + // Force sticking to beginning of line if no character would fit except for the indentation + if (wrappedTextIndentLength + columnsForFullWidthChar > firstLineBreakingColumn) { + wrappedTextIndentLength = 0; + } + } + } + return wrappedTextIndentLength; +} diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 113059661f9e8..7f6dcddd4ce78 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -67,7 +67,10 @@ export class LineBreakingData { } export interface ILineMappingComputer { - addRequest(lineText: string): void; + /** + * Pass in previousLineBreakingData if the only difference is in breaking columns!!! + */ + addRequest(lineText: string, previousLineBreakingData: LineBreakingData | null): void; finalize(): (LineBreakingData | null)[]; } @@ -88,6 +91,7 @@ export interface ISplitLine { isVisible(): boolean; setVisible(isVisible: boolean): ISplitLine; + getLineBreakingData(): LineBreakingData | null; getViewLineCount(): number; getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string; getViewLineLength(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number; @@ -286,7 +290,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { this.wrappingIndent = wrappingIndent; this.linePositionMapperFactory = linePositionMapperFactory; - this._constructLines(true); + this._constructLines(/*resetHiddenAreas*/true, null); } public dispose(): void { @@ -297,7 +301,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return new CoordinatesConverter(this); } - private _constructLines(resetHiddenAreas: boolean): void { + private _constructLines(resetHiddenAreas: boolean, previousLineMapping: ((LineBreakingData | null)[]) | null): void { this.lines = []; if (resetHiddenAreas) { @@ -308,7 +312,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { const lineCount = linesContent.length; const lineMappingComputer = this.linePositionMapperFactory.createLineMappingComputer(this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent); for (let i = 0; i < lineCount; i++) { - lineMappingComputer.addRequest(linesContent[i]); + lineMappingComputer.addRequest(linesContent[i], previousLineMapping ? previousLineMapping[i] : null); } const lineMappings = lineMappingComputer.finalize(); @@ -461,7 +465,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } this.tabSize = newTabSize; - this._constructLines(false); + this._constructLines(/*resetHiddenAreas*/false, null); return true; } @@ -471,11 +475,21 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return false; } + const onlyWrappingColumnChanged = (this.wrappingIndent === wrappingIndent && this.wrappingColumn !== wrappingColumn && this.columnsForFullWidthChar === columnsForFullWidthChar); + this.wrappingIndent = wrappingIndent; this.wrappingColumn = wrappingColumn; this.columnsForFullWidthChar = columnsForFullWidthChar; - this._constructLines(false); + let previousLineMapping: ((LineBreakingData | null)[]) | null = null; + if (onlyWrappingColumnChanged) { + previousLineMapping = []; + for (let i = 0, len = this.lines.length; i < len; i++) { + previousLineMapping[i] = this.lines[i].getLineBreakingData(); + } + } + + this._constructLines(/*resetHiddenAreas*/false, previousLineMapping); return true; } @@ -485,7 +499,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } public onModelFlushed(): void { - this._constructLines(true); + this._constructLines(/*resetHiddenAreas*/true, null); } public onModelLinesDeleted(versionId: number, fromLineNumber: number, toLineNumber: number): viewEvents.ViewLinesDeletedEvent | null { @@ -1005,6 +1019,10 @@ class VisibleIdentitySplitLine implements ISplitLine { return InvisibleIdentitySplitLine.INSTANCE; } + public getLineBreakingData(): LineBreakingData | null { + return null; + } + public getViewLineCount(): number { return 1; } @@ -1076,6 +1094,10 @@ class InvisibleIdentitySplitLine implements ISplitLine { return VisibleIdentitySplitLine.INSTANCE; } + public getLineBreakingData(): LineBreakingData | null { + return null; + } + public getViewLineCount(): number { return 0; } @@ -1119,15 +1141,11 @@ class InvisibleIdentitySplitLine implements ISplitLine { export class SplitLine implements ISplitLine { - private readonly _breakOffsets: number[]; - private readonly _breakingOffsetsVisibleColumn: number[]; - private readonly _wrappedTextIndentLength: number; + private readonly _lineBreakingData: LineBreakingData; private _isVisible: boolean; - constructor(lineBreaking: LineBreakingData, isVisible: boolean) { - this._breakOffsets = lineBreaking.breakOffsets; - this._breakingOffsetsVisibleColumn = lineBreaking.breakingOffsetsVisibleColumn; - this._wrappedTextIndentLength = lineBreaking.wrappedTextIndentLength; + constructor(lineBreakingData: LineBreakingData, isVisible: boolean) { + this._lineBreakingData = lineBreakingData; this._isVisible = isVisible; } @@ -1140,22 +1158,26 @@ export class SplitLine implements ISplitLine { return this; } + public getLineBreakingData(): LineBreakingData | null { + return this._lineBreakingData; + } + public getViewLineCount(): number { if (!this._isVisible) { return 0; } - return this._breakOffsets.length; + return this._lineBreakingData.breakOffsets.length; } private getInputStartOffsetOfOutputLineIndex(outputLineIndex: number): number { - return LineBreakingData.getInputOffsetOfOutputPosition(this._breakOffsets, outputLineIndex, 0); + return LineBreakingData.getInputOffsetOfOutputPosition(this._lineBreakingData.breakOffsets, outputLineIndex, 0); } private getInputEndOffsetOfOutputLineIndex(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number { - if (outputLineIndex + 1 === this._breakOffsets.length) { + if (outputLineIndex + 1 === this._lineBreakingData.breakOffsets.length) { return model.getLineMaxColumn(modelLineNumber) - 1; } - return LineBreakingData.getInputOffsetOfOutputPosition(this._breakOffsets, outputLineIndex + 1, 0); + return LineBreakingData.getInputOffsetOfOutputPosition(this._lineBreakingData.breakOffsets, outputLineIndex + 1, 0); } public getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string { @@ -1172,7 +1194,7 @@ export class SplitLine implements ISplitLine { }); if (outputLineIndex > 0) { - r = spaces(this._wrappedTextIndentLength) + r; + r = spaces(this._lineBreakingData.wrappedTextIndentLength) + r; } return r; @@ -1187,7 +1209,7 @@ export class SplitLine implements ISplitLine { let r = endOffset - startOffset; if (outputLineIndex > 0) { - r = this._wrappedTextIndentLength + r; + r = this._lineBreakingData.wrappedTextIndentLength + r; } return r; @@ -1198,7 +1220,7 @@ export class SplitLine implements ISplitLine { throw new Error('Not supported'); } if (outputLineIndex > 0) { - return this._wrappedTextIndentLength + 1; + return this._lineBreakingData.wrappedTextIndentLength + 1; } return 1; } @@ -1226,21 +1248,21 @@ export class SplitLine implements ISplitLine { }); if (outputLineIndex > 0) { - lineContent = spaces(this._wrappedTextIndentLength) + lineContent; + lineContent = spaces(this._lineBreakingData.wrappedTextIndentLength) + lineContent; } - let minColumn = (outputLineIndex > 0 ? this._wrappedTextIndentLength + 1 : 1); + let minColumn = (outputLineIndex > 0 ? this._lineBreakingData.wrappedTextIndentLength + 1 : 1); let maxColumn = lineContent.length + 1; let continuesWithWrappedLine = (outputLineIndex + 1 < this.getViewLineCount()); let deltaStartIndex = 0; if (outputLineIndex > 0) { - deltaStartIndex = this._wrappedTextIndentLength; + deltaStartIndex = this._lineBreakingData.wrappedTextIndentLength; } let lineTokens = model.getLineTokens(modelLineNumber); - const startVisibleColumn = (outputLineIndex === 0 ? 0 : this._breakingOffsetsVisibleColumn[outputLineIndex - 1]); + const startVisibleColumn = (outputLineIndex === 0 ? 0 : this._lineBreakingData.breakingOffsetsVisibleColumn[outputLineIndex - 1]); return new ViewLineData( lineContent, @@ -1273,25 +1295,25 @@ export class SplitLine implements ISplitLine { } let adjustedColumn = outputColumn - 1; if (outputLineIndex > 0) { - if (adjustedColumn < this._wrappedTextIndentLength) { + if (adjustedColumn < this._lineBreakingData.wrappedTextIndentLength) { adjustedColumn = 0; } else { - adjustedColumn -= this._wrappedTextIndentLength; + adjustedColumn -= this._lineBreakingData.wrappedTextIndentLength; } } - return LineBreakingData.getInputOffsetOfOutputPosition(this._breakOffsets, outputLineIndex, adjustedColumn) + 1; + return LineBreakingData.getInputOffsetOfOutputPosition(this._lineBreakingData.breakOffsets, outputLineIndex, adjustedColumn) + 1; } public getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number): Position { if (!this._isVisible) { throw new Error('Not supported'); } - let r = LineBreakingData.getOutputPositionOfInputOffset(this._breakOffsets, inputColumn - 1); + let r = LineBreakingData.getOutputPositionOfInputOffset(this._lineBreakingData.breakOffsets, inputColumn - 1); let outputLineIndex = r.outputLineIndex; let outputColumn = r.outputOffset + 1; if (outputLineIndex > 0) { - outputColumn += this._wrappedTextIndentLength; + outputColumn += this._lineBreakingData.wrappedTextIndentLength; } // console.log('in -> out ' + deltaLineNumber + ',' + inputColumn + ' ===> ' + (deltaLineNumber+outputLineIndex) + ',' + outputColumn); @@ -1302,7 +1324,7 @@ export class SplitLine implements ISplitLine { if (!this._isVisible) { throw new Error('Not supported'); } - const r = LineBreakingData.getOutputPositionOfInputOffset(this._breakOffsets, inputColumn - 1); + const r = LineBreakingData.getOutputPositionOfInputOffset(this._lineBreakingData.breakOffsets, inputColumn - 1); return (deltaLineNumber + r.outputLineIndex); } } @@ -1421,7 +1443,7 @@ export class IdentityLinesCollection implements IViewModelLinesCollection { public createLineMappingComputer(): ILineMappingComputer { let result: null[] = []; return { - addRequest: (lineText: string) => { + addRequest: (lineText: string, previousLineBreakingData: LineBreakingData | null) => { result.push(null); }, finalize: () => { diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index f437ceeab264e..55100bc6c2377 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -202,12 +202,12 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel switch (change.changeType) { case textModelEvents.RawContentChangedType.LinesInserted: { for (const line of change.detail) { - lineMappingComputer.addRequest(line); + lineMappingComputer.addRequest(line, null); } break; } case textModelEvents.RawContentChangedType.LineChanged: { - lineMappingComputer.addRequest(change.detail); + lineMappingComputer.addRequest(change.detail, null); break; } } diff --git a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts index a46124eccb176..a1aec876698a8 100644 --- a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts +++ b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts @@ -22,7 +22,7 @@ function assertLineMapping(factory: ILineMapperFactory, tabSize: number, breakAf } const lineMappingComputer = factory.createLineMappingComputer(tabSize, breakAfter, 2, wrappingIndent); - lineMappingComputer.addRequest(rawText); + lineMappingComputer.addRequest(rawText, null); const lineMappings = lineMappingComputer.finalize(); const mapper = lineMappings[0]; From acb595b158f74129c66e5b15663eb2fa78239932 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 9 Jan 2020 10:36:35 +0100 Subject: [PATCH 082/315] Exclude .vscode-test folder from npm task detection Part of #88328 --- extensions/npm/src/tasks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/npm/src/tasks.ts b/extensions/npm/src/tasks.ts index c8a1eb852360e..b9cedcc9a3357 100644 --- a/extensions/npm/src/tasks.ts +++ b/extensions/npm/src/tasks.ts @@ -144,7 +144,7 @@ async function detectNpmScripts(): Promise { for (const folder of folders) { if (isAutoDetectionEnabled(folder)) { let relativePattern = new RelativePattern(folder, '**/package.json'); - let paths = await workspace.findFiles(relativePattern, '**/node_modules/**'); + let paths = await workspace.findFiles(relativePattern, '**/{node_modules,.vscode-test}/**'); for (const path of paths) { if (!isExcluded(folder, path) && !visitedPackageJsonFiles.has(path.fsPath)) { let tasks = await provideNpmScriptsForFolder(path); From 4cb0c653fbd4ea698c7ee6a433d12ffbd369f2ee Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Thu, 9 Jan 2020 10:38:34 +0100 Subject: [PATCH 083/315] :lipstick: --- .../characterHardWrappingLineMapper.ts | 155 +++++++++--------- 1 file changed, 77 insertions(+), 78 deletions(-) diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index 8d58bcaeccfb8..61f5d8f02eb34 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -81,103 +81,102 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor finalize: () => { let result: (LineBreakingData | null)[] = []; for (let i = 0, len = requests.length; i < len; i++) { - result[i] = this._createLineMapping(requests[i], previousBreakingData[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent); + result[i] = createLineMapping(this.classifier, previousBreakingData[i], requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent); } return result; } }; } +} - private _createLineMapping(lineText: string, previousBreakingData: LineBreakingData | null, tabSize: number, firstLineBreakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): LineBreakingData | null { - if (firstLineBreakingColumn === -1) { - return null; - } - - const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakingColumn, columnsForFullWidthChar, hardWrappingIndent); - const wrappedLineBreakingColumn = firstLineBreakingColumn - wrappedTextIndentLength; - - const classifier = this.classifier; - let breakingOffsets: number[] = []; - let breakingOffsetsVisibleColumn: number[] = []; - let breakingOffsetsCount: number = 0; - let visibleColumn = 0; - let breakOffset = 0; - let breakOffsetVisibleColumn = 0; - const len = lineText.length; - const len1 = len - 1; - - let breakingColumn = firstLineBreakingColumn; - let prevCharCode = CharCode.Null; - let prevCharCodeClass = CharacterClass.NONE; - let charCode = CharCode.Null; - let charCodeClass = CharacterClass.NONE; - let nextCharCode = (len > 0 ? lineText.charCodeAt(0) : CharCode.Null); - let nextCharCodeClass = classifier.get(nextCharCode); - - for (let i = 0; i < len; i++) { - // At this point, there is a certainty that the character before `i` fits on the current line, - // but the character at `i` might not fit - - prevCharCode = charCode; - prevCharCodeClass = charCodeClass; - charCode = nextCharCode; - charCodeClass = nextCharCodeClass; - nextCharCode = (i < len1 ? lineText.charCodeAt(i + 1) : CharCode.Null); - nextCharCodeClass = classifier.get(nextCharCode); - - if (strings.isLowSurrogate(charCode)) { - // A surrogate pair must always be considered as a single unit, so it is never to be broken - visibleColumn += 1; - continue; - } +function createLineMapping(classifier: WrappingCharacterClassifier, previousBreakingData: LineBreakingData | null, lineText: string, tabSize: number, firstLineBreakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): LineBreakingData | null { + if (firstLineBreakingColumn === -1) { + return null; + } - if (prevCharCode !== CharCode.Null && canBreakBefore(charCodeClass, prevCharCodeClass)) { - breakOffset = i; - breakOffsetVisibleColumn = visibleColumn; - } + const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakingColumn, columnsForFullWidthChar, hardWrappingIndent); + const wrappedLineBreakingColumn = firstLineBreakingColumn - wrappedTextIndentLength; + + let breakingOffsets: number[] = []; + let breakingOffsetsVisibleColumn: number[] = []; + let breakingOffsetsCount: number = 0; + let visibleColumn = 0; + let breakOffset = 0; + let breakOffsetVisibleColumn = 0; + const len = lineText.length; + const len1 = len - 1; + + let breakingColumn = firstLineBreakingColumn; + let prevCharCode = CharCode.Null; + let prevCharCodeClass = CharacterClass.NONE; + let charCode = CharCode.Null; + let charCodeClass = CharacterClass.NONE; + let nextCharCode = (len > 0 ? lineText.charCodeAt(0) : CharCode.Null); + let nextCharCodeClass = classifier.get(nextCharCode); + + for (let i = 0; i < len; i++) { + // At this point, there is a certainty that the character before `i` fits on the current line, + // but the character at `i` might not fit + + prevCharCode = charCode; + prevCharCodeClass = charCodeClass; + charCode = nextCharCode; + charCodeClass = nextCharCodeClass; + nextCharCode = (i < len1 ? lineText.charCodeAt(i + 1) : CharCode.Null); + nextCharCodeClass = classifier.get(nextCharCode); + + if (strings.isLowSurrogate(charCode)) { + // A surrogate pair must always be considered as a single unit, so it is never to be broken + visibleColumn += 1; + continue; + } - const charColumnSize = ( - charCode === CharCode.Tab - ? tabCharacterWidth(visibleColumn, tabSize) - : (strings.isFullWidthCharacter(charCode) ? columnsForFullWidthChar : 1) - ); + if (prevCharCode !== CharCode.Null && canBreakBefore(charCodeClass, prevCharCodeClass)) { + breakOffset = i; + breakOffsetVisibleColumn = visibleColumn; + } - visibleColumn += charColumnSize; + const charColumnSize = ( + charCode === CharCode.Tab + ? tabCharacterWidth(visibleColumn, tabSize) + : (strings.isFullWidthCharacter(charCode) ? columnsForFullWidthChar : 1) + ); - // check if adding character at `i` will go over the breaking column - if (visibleColumn > breakingColumn && i !== 0) { - // We need to break at least before character at `i`: + visibleColumn += charColumnSize; - if (breakOffset === 0 || visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakingColumn) { - // Cannot break at `breakOffset`, must break at `i` - breakOffset = i; - breakOffsetVisibleColumn = visibleColumn - charColumnSize; - } + // check if adding character at `i` will go over the breaking column + if (visibleColumn > breakingColumn && i !== 0) { + // We need to break at least before character at `i`: - breakingOffsets[breakingOffsetsCount] = breakOffset; - breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn; - breakingOffsetsCount++; - breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakingColumn; - breakOffset = 0; + if (breakOffset === 0 || visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakingColumn) { + // Cannot break at `breakOffset`, must break at `i` + breakOffset = i; + breakOffsetVisibleColumn = visibleColumn - charColumnSize; } - // At this point, there is a certainty that the character at `i` fits on the current line - if (nextCharCode !== CharCode.Null && canBreakAfter(charCodeClass, nextCharCodeClass)) { - breakOffset = i + 1; - breakOffsetVisibleColumn = visibleColumn; - } + breakingOffsets[breakingOffsetsCount] = breakOffset; + breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn; + breakingOffsetsCount++; + breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakingColumn; + breakOffset = 0; } - if (breakingOffsetsCount === 0) { - return null; + // At this point, there is a certainty that the character at `i` fits on the current line + if (nextCharCode !== CharCode.Null && canBreakAfter(charCodeClass, nextCharCodeClass)) { + breakOffset = i + 1; + breakOffsetVisibleColumn = visibleColumn; } + } - // Add last segment - breakingOffsets[breakingOffsetsCount] = len; - breakingOffsetsVisibleColumn[breakingOffsetsCount] = visibleColumn; - - return new LineBreakingData(breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); + if (breakingOffsetsCount === 0) { + return null; } + + // Add last segment + breakingOffsets[breakingOffsetsCount] = len; + breakingOffsetsVisibleColumn[breakingOffsetsCount] = visibleColumn; + + return new LineBreakingData(breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); } function tabCharacterWidth(visibleColumn: number, tabSize: number): number { From 6e17ea9494dde1313978c2269ccc5145b0513e09 Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Thu, 9 Jan 2020 10:40:50 +0100 Subject: [PATCH 084/315] Update emmet helper --- extensions/emmet/package.json | 4 ++-- extensions/emmet/yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/extensions/emmet/package.json b/extensions/emmet/package.json index 6342dba74f1a1..fc149b28e54b8 100644 --- a/extensions/emmet/package.json +++ b/extensions/emmet/package.json @@ -83,7 +83,7 @@ "type": [ "string", "null" - ], + ], "default": null, "description": "%emmetExtensionsPath%" }, @@ -453,7 +453,7 @@ "@emmetio/html-matcher": "^0.3.3", "@emmetio/math-expression": "^0.1.1", "image-size": "^0.5.2", - "vscode-emmet-helper": "^1.2.16", + "vscode-emmet-helper": "^1.2.17", "vscode-html-languageservice": "^3.0.3" } } diff --git a/extensions/emmet/yarn.lock b/extensions/emmet/yarn.lock index a801de886fd0d..37e06ecb52270 100644 --- a/extensions/emmet/yarn.lock +++ b/extensions/emmet/yarn.lock @@ -2469,10 +2469,10 @@ vinyl@~2.0.1: remove-trailing-separator "^1.0.1" replace-ext "^1.0.0" -vscode-emmet-helper@^1.2.16: - version "1.2.16" - resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-1.2.16.tgz#cfefb8b54c68178b4696d4abae806bb3a2b043e8" - integrity sha512-BuQK6fTV2w65Yd0/CJGj1EOvcJ9NHWfrMJ9nA8pjnu9jzAAnXLhnbviuGT9medMiPU0mp0tJqc/8Z0qlXcqdGw== +vscode-emmet-helper@^1.2.17: + version "1.2.17" + resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-1.2.17.tgz#f0c6bfcebc4285d081fb2618e6e5b9a08c567afa" + integrity sha512-X4pzcrJ8dE7M3ArFuySF5fgipKDd/EauXkiJwtjBIVRWpVNq0tF9+lNCyuC7iDUwP3Oq7ow/TGssD3GdG96Jow== dependencies: "@emmetio/extract-abbreviation" "0.1.6" jsonc-parser "^1.0.0" From 1ced4ebbc78e0074dc6986f61bda775745c9a0e9 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 9 Jan 2020 10:47:06 +0100 Subject: [PATCH 085/315] fixes #88162 --- .../workbench/contrib/debug/browser/debugActionViewItems.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index 767b547b64f82..2bdfc9c8bf97f 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -161,9 +161,6 @@ export class StartDebugActionViewItem implements IActionViewItem { let lastGroup: string | undefined; const disabledIdxs: number[] = []; manager.getAllConfigurations().forEach(({ launch, name, presentation }) => { - if (name === manager.selectedConfiguration.name && launch === manager.selectedConfiguration.launch) { - this.selected = this.options.length; - } if (lastGroup !== presentation?.group) { lastGroup = presentation?.group; if (this.options.length) { @@ -171,6 +168,9 @@ export class StartDebugActionViewItem implements IActionViewItem { disabledIdxs.push(this.options.length - 1); } } + if (name === manager.selectedConfiguration.name && launch === manager.selectedConfiguration.launch) { + this.selected = this.options.length; + } const label = inWorkspace ? `${name} (${launch.name})` : name; this.options.push({ label, handler: () => { manager.selectConfiguration(launch, name); return true; } }); From 25e65e6e64618435934f54b8c24102f899ce69e7 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Thu, 9 Jan 2020 11:21:52 +0100 Subject: [PATCH 086/315] Simplify wrapping computation --- .../characterHardWrappingLineMapper.ts | 89 +++++++++---------- .../common/viewModel/splitLinesCollection.ts | 1 + .../viewModel/splitLinesCollection.test.ts | 2 +- 3 files changed, 42 insertions(+), 50 deletions(-) diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index 61f5d8f02eb34..d1d9a51306dfe 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -94,64 +94,53 @@ function createLineMapping(classifier: WrappingCharacterClassifier, previousBrea return null; } + const len = lineText.length; + if (len <= 1) { + return null; + } + const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakingColumn, columnsForFullWidthChar, hardWrappingIndent); const wrappedLineBreakingColumn = firstLineBreakingColumn - wrappedTextIndentLength; let breakingOffsets: number[] = []; let breakingOffsetsVisibleColumn: number[] = []; let breakingOffsetsCount: number = 0; - let visibleColumn = 0; let breakOffset = 0; let breakOffsetVisibleColumn = 0; - const len = lineText.length; - const len1 = len - 1; let breakingColumn = firstLineBreakingColumn; - let prevCharCode = CharCode.Null; - let prevCharCodeClass = CharacterClass.NONE; - let charCode = CharCode.Null; - let charCodeClass = CharacterClass.NONE; - let nextCharCode = (len > 0 ? lineText.charCodeAt(0) : CharCode.Null); - let nextCharCodeClass = classifier.get(nextCharCode); + let prevCharCode = lineText.charCodeAt(0); + let prevCharCodeClass = classifier.get(prevCharCode); + let visibleColumn = computeCharWidth(prevCharCode, 0, tabSize, columnsForFullWidthChar); - for (let i = 0; i < len; i++) { - // At this point, there is a certainty that the character before `i` fits on the current line, - // but the character at `i` might not fit + for (let i = 1; i < len; i++) { + const charCode = lineText.charCodeAt(i); + const charCodeClass = classifier.get(charCode); - prevCharCode = charCode; - prevCharCodeClass = charCodeClass; - charCode = nextCharCode; - charCodeClass = nextCharCodeClass; - nextCharCode = (i < len1 ? lineText.charCodeAt(i + 1) : CharCode.Null); - nextCharCodeClass = classifier.get(nextCharCode); - - if (strings.isLowSurrogate(charCode)) { + if (strings.isHighSurrogate(prevCharCode)) { // A surrogate pair must always be considered as a single unit, so it is never to be broken visibleColumn += 1; + prevCharCode = charCode; + prevCharCodeClass = charCodeClass; continue; } - if (prevCharCode !== CharCode.Null && canBreakBefore(charCodeClass, prevCharCodeClass)) { + if (canBreak(prevCharCodeClass, charCodeClass)) { breakOffset = i; breakOffsetVisibleColumn = visibleColumn; } - const charColumnSize = ( - charCode === CharCode.Tab - ? tabCharacterWidth(visibleColumn, tabSize) - : (strings.isFullWidthCharacter(charCode) ? columnsForFullWidthChar : 1) - ); - - visibleColumn += charColumnSize; + const charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar); + visibleColumn += charWidth; // check if adding character at `i` will go over the breaking column - if (visibleColumn > breakingColumn && i !== 0) { + if (visibleColumn > breakingColumn) { // We need to break at least before character at `i`: if (breakOffset === 0 || visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakingColumn) { // Cannot break at `breakOffset`, must break at `i` breakOffset = i; - breakOffsetVisibleColumn = visibleColumn - charColumnSize; + breakOffsetVisibleColumn = visibleColumn - charWidth; } breakingOffsets[breakingOffsetsCount] = breakOffset; @@ -161,11 +150,8 @@ function createLineMapping(classifier: WrappingCharacterClassifier, previousBrea breakOffset = 0; } - // At this point, there is a certainty that the character at `i` fits on the current line - if (nextCharCode !== CharCode.Null && canBreakAfter(charCodeClass, nextCharCodeClass)) { - breakOffset = i + 1; - breakOffsetVisibleColumn = visibleColumn; - } + prevCharCode = charCode; + prevCharCodeClass = charCodeClass; } if (breakingOffsetsCount === 0) { @@ -176,31 +162,36 @@ function createLineMapping(classifier: WrappingCharacterClassifier, previousBrea breakingOffsets[breakingOffsetsCount] = len; breakingOffsetsVisibleColumn[breakingOffsetsCount] = visibleColumn; - return new LineBreakingData(breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); + return new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); +} + +function computeCharWidth(charCode: number, visibleColumn: number, tabSize: number, columnsForFullWidthChar: number): number { + if (charCode === CharCode.Tab) { + return (tabSize - (visibleColumn % tabSize)); + } + if (strings.isFullWidthCharacter(charCode)) { + return columnsForFullWidthChar; + } + return 1; } function tabCharacterWidth(visibleColumn: number, tabSize: number): number { return (tabSize - (visibleColumn % tabSize)); } -function canBreakBefore(charCodeClass: CharacterClass, prevCharCodeClass: CharacterClass): boolean { - // This is a character that indicates that a break should happen before it - // (or) CJK breaking : before break : Kinsoku Shori : Don't break after a leading character, like an open bracket +/** + * Kinsoku Shori : Don't break after a leading character, like an open bracket + * Kinsoku Shori : Don't break before a trailing character, like a period + */ +function canBreak(prevCharCodeClass: CharacterClass, charCodeClass: CharacterClass): boolean { return ( - (charCodeClass === CharacterClass.BREAK_BEFORE) + (prevCharCodeClass === CharacterClass.BREAK_AFTER) + || (prevCharCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && charCodeClass !== CharacterClass.BREAK_AFTER) + || (charCodeClass === CharacterClass.BREAK_BEFORE) || (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && prevCharCodeClass !== CharacterClass.BREAK_BEFORE) ); } -function canBreakAfter(charCodeClass: CharacterClass, nextCharCodeClass: CharacterClass): boolean { - // This is a character that indicates that a break should happen after it - // (or) CJK breaking : after break : Kinsoku Shori : Don't break before a trailing character, like a period - return ( - (charCodeClass === CharacterClass.BREAK_AFTER) - || (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && nextCharCodeClass !== CharacterClass.BREAK_AFTER) - ); -} - function computeWrappedTextIndentLength(lineText: string, tabSize: number, firstLineBreakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): number { let wrappedTextIndentLength = 0; if (hardWrappingIndent !== WrappingIndent.None) { diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 7f6dcddd4ce78..6346d5bbc4cfd 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -28,6 +28,7 @@ export class OutputPosition { export class LineBreakingData { constructor( + public readonly breakingColumn: number, public readonly breakOffsets: number[], public readonly breakingOffsetsVisibleColumn: number[], public readonly wrappedTextIndentLength: number diff --git a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts index ba4228ce9a644..56f06a2a2bc6d 100644 --- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts +++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts @@ -781,7 +781,7 @@ function createLineMapping(breakingLengths: number[], breakingOffsetsVisibleColu for (let i = 0; i < breakingLengths.length; i++) { sums[i] = (i > 0 ? sums[i - 1] : 0) + breakingLengths[i]; } - return new LineBreakingData(sums, breakingOffsetsVisibleColumn, wrappedTextIndentWidth); + return new LineBreakingData(0, sums, breakingOffsetsVisibleColumn, wrappedTextIndentWidth); } function createModel(text: string): ISimpleModel { From b47db66826126a19efc55fcd8509c90f049d9b50 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 9 Jan 2020 11:31:24 +0100 Subject: [PATCH 087/315] Focus on empty custom trees to improve sidebar indicator Part of #86686 --- src/vs/workbench/browser/parts/views/customView.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 157494928b59b..6d582d72c39ec 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -357,6 +357,8 @@ export class CustomTreeView extends Disposable implements ITreeView { // Pass Focus to Viewer this.tree.domFocus(); + } else if (this.tree) { + this.tree.domFocus(); } else { this.domNode.focus(); } From 91d634e0b0584bde233d5709a09697ffa61f4bfd Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 9 Jan 2020 12:04:48 +0100 Subject: [PATCH 088/315] Forward a Port in Forwarded Ports context menu Part of #86064 --- .../workbench/contrib/remote/browser/tunnelView.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 1b046e781697d..10383cf758fa7 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -507,7 +507,7 @@ export class TunnelPanel extends ViewPane { } private onContextMenu(treeEvent: ITreeContextMenuEvent, actionRunner: ActionRunner): void { - if (!(treeEvent.element instanceof TunnelItem)) { + if ((treeEvent.element !== null) && !(treeEvent.element instanceof TunnelItem)) { return; } const node: ITunnelItem | null = treeEvent.element; @@ -516,9 +516,13 @@ export class TunnelPanel extends ViewPane { event.preventDefault(); event.stopPropagation(); - this.tree!.setFocus([node]); - this.tunnelTypeContext.set(node.tunnelType); - this.tunnelCloseableContext.set(!!node.closeable); + if (node) { + this.tree!.setFocus([node]); + this.tunnelTypeContext.set(node.tunnelType); + this.tunnelCloseableContext.set(!!node.closeable); + } else { + this.tunnelTypeContext.set(TunnelType.Add); + } const actions: IAction[] = []; this._register(createAndFillInContextMenuActions(this.contributedContextMenu, { shouldForwardArgs: true }, actions, this.contextMenuService)); @@ -793,7 +797,7 @@ MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ id: ForwardPortAction.INLINE_ID, title: ForwardPortAction.LABEL, }, - when: TunnelTypeContextKey.isEqualTo(TunnelType.Candidate) + when: ContextKeyExpr.or(TunnelTypeContextKey.isEqualTo(TunnelType.Candidate), TunnelTypeContextKey.isEqualTo(TunnelType.Add)) })); MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ group: '0_manage', From 8aeca9fed968bf2a22370278c3ef9ebec3ce5f97 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 9 Jan 2020 12:00:54 +0100 Subject: [PATCH 089/315] :lipstick: --- src/vs/workbench/browser/parts/views/viewPaneContainer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 3400b61747e6c..fc7f3528c07d3 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -316,7 +316,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } create(parent: HTMLElement): void { - // super.create(parent); this.paneview = this._register(new PaneView(parent, this.options)); this._register(this.paneview.onDidDrop(({ from, to }) => this.movePane(from as ViewPane, to as ViewPane))); this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(e)))); From 65985c1ee9be6e41c2901338ef25cd283249e23f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 9 Jan 2020 12:30:48 +0100 Subject: [PATCH 090/315] :lipstick: editor tests --- .../files/test/browser/fileEditorTracker.test.ts | 11 +++++++++-- .../electron-browser/configurationService.test.ts | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts index 5d28531ffe619..8c06d19a919cc 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { Event } from 'vs/base/common/event'; import { FileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/fileEditorTracker'; import { toResource } from 'vs/base/test/common/utils'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -106,7 +107,7 @@ suite('Files - FileEditorTracker', () => { model.textEditorModel.setValue('Super Good'); - await timeout(300 /* 250ms debounce delay in text file editor model manager */); + await awaitEditorOpening(editorService); assert.ok(editorService.isOpen({ resource })); part.dispose(); @@ -138,11 +139,17 @@ suite('Files - FileEditorTracker', () => { model.textEditorModel.setValue('Super Good'); - await timeout(300 /* 250ms debounce delay in tracker */); + await awaitEditorOpening(editorService); assert.ok(editorService.isOpen(untitledEditor)); part.dispose(); tracker.dispose(); model.dispose(); }); + + function awaitEditorOpening(editorService: IEditorService): Promise { + return new Promise(c => { + Event.once(editorService.onDidActiveEditorChange)(c); + }); + } }); diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index bd9070dd0b579..5d715082fc6cc 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -1094,7 +1094,7 @@ suite('WorkspaceConfigurationService - Folder', () => { test('no change event when there are no global tasks', async () => { const target = sinon.spy(); testObject.onDidChangeConfiguration(target); - await timeout(500); + await timeout(5); assert.ok(target.notCalled); }); From 9b3d137c178afd02697257c5dc12048470bdf4ed Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 9 Jan 2020 12:52:11 +0100 Subject: [PATCH 091/315] Only show Forward a Port... when view would otherwise be empty Part of #86064 --- .../contrib/remote/browser/tunnelView.ts | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 10383cf758fa7..8674919db8964 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -56,7 +56,7 @@ export interface ITunnelViewModel { readonly forwarded: TunnelItem[]; readonly detected: TunnelItem[]; readonly candidates: Promise; - readonly input: ITunnelItem | ITunnelGroup | undefined; + readonly input: TunnelItem; groups(): Promise; } @@ -64,16 +64,23 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { private _onForwardedPortsChanged: Emitter = new Emitter(); public onForwardedPortsChanged: Event = this._onForwardedPortsChanged.event; private model: TunnelModel; - private _input: ITunnelItem | ITunnelGroup | undefined; + private _input: TunnelItem; constructor( - @IRemoteExplorerService remoteExplorerService: IRemoteExplorerService) { + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService) { super(); this.model = remoteExplorerService.tunnelModel; this._register(this.model.onForwardPort(() => this._onForwardedPortsChanged.fire())); this._register(this.model.onClosePort(() => this._onForwardedPortsChanged.fire())); this._register(this.model.onPortName(() => this._onForwardedPortsChanged.fire())); this._register(this.model.onCandidatesChanged(() => this._onForwardedPortsChanged.fire())); + this._input = { + label: nls.localize('remote.tunnelsView.add', "Forward a Port..."), + tunnelType: TunnelType.Add, + remoteHost: 'localhost', + remotePort: 0, + description: '' + }; } async groups(): Promise { @@ -100,20 +107,20 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { items: candidates }); } - if (!this._input) { - this._input = { - label: nls.localize('remote.tunnelsView.add', "Forward a Port..."), - tunnelType: TunnelType.Add, - }; + if (groups.length === 0) { + groups.push(this._input); } - groups.push(this._input); return groups; } get forwarded(): TunnelItem[] { - return Array.from(this.model.forwarded.values()).map(tunnel => { + const forwarded = Array.from(this.model.forwarded.values()).map(tunnel => { return new TunnelItem(TunnelType.Forwarded, tunnel.remoteHost, tunnel.remotePort, tunnel.localAddress, tunnel.closeable, tunnel.name, tunnel.description); }); + if (this.remoteExplorerService.getEditableData(undefined)) { + forwarded.push(this._input); + } + return forwarded; } get detected(): TunnelItem[] { @@ -135,7 +142,7 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { }); } - get input(): ITunnelItem | ITunnelGroup | undefined { + get input(): TunnelItem { return this._input; } @@ -522,6 +529,7 @@ export class TunnelPanel extends ViewPane { this.tunnelCloseableContext.set(!!node.closeable); } else { this.tunnelTypeContext.set(TunnelType.Add); + this.tunnelCloseableContext.set(false); } const actions: IAction[] = []; From 1f618a27aa9439d05a7be29d142b3074ebebddd3 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 9 Jan 2020 12:46:26 +0100 Subject: [PATCH 092/315] add IProgressNotificationOptions#delay, #87449 --- src/vs/platform/progress/common/progress.ts | 1 + .../progress/browser/progressService.ts | 41 ++++++++++++------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index 560ed3de65143..49c827f2bbc7b 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -57,6 +57,7 @@ export interface IProgressNotificationOptions extends IProgressOptions { readonly location: ProgressLocation.Notification; readonly primaryActions?: ReadonlyArray; readonly secondaryActions?: ReadonlyArray; + delay?: number; } export interface IProgressWindowOptions extends IProgressOptions { diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index 562773832a592..4a5831b56671c 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -146,10 +146,7 @@ export class ProgressService extends Disposable implements IProgressService { private withNotificationProgress

, R = unknown>(options: IProgressNotificationOptions, callback: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: (choice?: number) => void): P { const toDispose = new DisposableStore(); - const createNotification = (message: string | undefined, increment?: number): INotificationHandle | undefined => { - if (!message) { - return undefined; // we need a message at least - } + const createNotification = (message: string, increment?: number): INotificationHandle => { const primaryActions = options.primaryActions ? Array.from(options.primaryActions) : []; const secondaryActions = options.secondaryActions ? Array.from(options.secondaryActions) : []; @@ -222,21 +219,34 @@ export class ProgressService extends Disposable implements IProgressService { }; let handle: INotificationHandle | undefined; + let handleSoon: any | undefined; + + let titleAndMessage: string | undefined; // hoisted to make sure a delayed notification shows the most recent message + const updateNotification = (message?: string, increment?: number): void => { - if (!handle) { - handle = createNotification(message, increment); + + // full message (inital or update) + if (message && options.title) { + titleAndMessage = `${options.title}: ${message}`; // always prefix with overall title if we have it (https://github.com/Microsoft/vscode/issues/50932) } else { - if (typeof message === 'string') { - let newMessage: string; - if (typeof options.title === 'string') { - newMessage = `${options.title}: ${message}`; // always prefix with overall title if we have it (https://github.com/Microsoft/vscode/issues/50932) - } else { - newMessage = message; - } + titleAndMessage = options.title || message; + } - handle.updateMessage(newMessage); + if (!handle && titleAndMessage) { + // create notification now or after a delay + if (typeof options.delay === 'number' && options.delay > 0) { + if (typeof handleSoon !== 'number') { + handleSoon = setTimeout(() => handle = createNotification(titleAndMessage!, increment), options.delay); + } + } else { + handle = createNotification(titleAndMessage, increment); } + } + if (handle) { + if (titleAndMessage) { + handle.updateMessage(titleAndMessage); + } if (typeof increment === 'number') { updateProgress(handle, increment); } @@ -244,7 +254,7 @@ export class ProgressService extends Disposable implements IProgressService { }; // Show initially - updateNotification(options.title); + updateNotification(); // Update based on progress const promise = callback({ @@ -255,6 +265,7 @@ export class ProgressService extends Disposable implements IProgressService { // Show progress for at least 800ms and then hide once done or canceled Promise.all([timeout(800), promise]).finally(() => { + clearTimeout(handleSoon); if (handle) { handle.close(); } From 082511b4fed6d19601eb4dfd4df0596dd6e2ed7d Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 9 Jan 2020 12:47:31 +0100 Subject: [PATCH 093/315] Show cancellable progress when running save participants, #87449 --- .../api/browser/mainThreadSaveParticipant.ts | 69 +++++++++++++------ 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index 62282e00c5f8f..ad1cc921d17c1 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -27,7 +27,7 @@ import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/c import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IProgressService, ProgressLocation, IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { ISaveParticipant, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; @@ -37,6 +37,8 @@ import { IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/sta import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { SettingsEditor2 } from 'vs/workbench/contrib/preferences/browser/settingsEditor2'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { canceled, isPromiseCanceledError } from 'vs/base/common/errors'; export interface ICodeActionsOnSaveOptions { [kind: string]: boolean; @@ -48,8 +50,8 @@ class SaveParticipantError extends Error { } } -export interface ISaveParticipantParticipant extends ISaveParticipant { - // progressMessage: string; +export interface ISaveParticipantParticipant { + participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }, progress: IProgress, token: CancellationToken): Promise; } class TrimWhitespaceParticipant implements ISaveParticipantParticipant { @@ -92,7 +94,7 @@ class TrimWhitespaceParticipant implements ISaveParticipantParticipant { return; // Nothing to do } - model.pushEditOperations(prevSelection, ops, (edits) => prevSelection); + model.pushEditOperations(prevSelection, ops, (_edits) => prevSelection); } } @@ -123,7 +125,7 @@ export class FinalNewLineParticipant implements ISaveParticipantParticipant { // Nothing } - async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { + async participate(model: IResolvedTextFileEditorModel, _env: { reason: SaveReason; }): Promise { if (this.configurationService.getValue('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { this.doInsertFinalNewLine(model.textEditorModel); } @@ -209,7 +211,7 @@ export class TrimFinalNewLinesParticipant implements ISaveParticipantParticipant return; } - model.pushEditOperations(prevSelection, [EditOperation.delete(deletionRange)], edits => prevSelection); + model.pushEditOperations(prevSelection, [EditOperation.delete(deletionRange)], _edits => prevSelection); if (editor) { editor.setSelections(prevSelection); @@ -227,7 +229,7 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant { // Nothing } - async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { + async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress, token: CancellationToken): Promise { const model = editorModel.textEditorModel; const overrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: model.uri }; @@ -237,9 +239,12 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant { } return new Promise((resolve, reject) => { - const source = new CancellationTokenSource(); + + progress.report({ message: localize('formatting', "Formatting") }); + + const source = new CancellationTokenSource(token); const editorOrModel = findEditor(model, this._codeEditorService) || model; - const timeout = this._configurationService.getValue('editor.formatOnSaveTimeout', overrides); + const timeout = this._configurationService.getValue('editor.formatOnSaveTimeout', overrides) * 1000; const request = this._instantiationService.invokeFunction(formatDocumentWithSelectedProvider, editorOrModel, FormattingMode.Silent, source.token); setTimeout(() => { @@ -255,7 +260,7 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant { } } -class CodeActionOnSaveParticipant implements ISaveParticipant { +class CodeActionOnSaveParticipant implements ISaveParticipantParticipant { constructor( @IBulkEditService private readonly _bulkEditService: IBulkEditService, @@ -264,7 +269,7 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { } - async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { + async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress, token: CancellationToken): Promise { if (env.reason === SaveReason.AUTO) { return undefined; } @@ -300,10 +305,12 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { .filter(x => setting[x] === false) .map(x => new CodeActionKind(x)); - const tokenSource = new CancellationTokenSource(); + const tokenSource = new CancellationTokenSource(token); const timeout = this._configurationService.getValue('editor.codeActionsOnSaveTimeout', settingsOverrides); + progress.report({ message: localize('codeaction', "Quick Fixes") }); + return Promise.race([ new Promise((_resolve, reject) => setTimeout(() => { @@ -354,7 +361,7 @@ class ExtHostSaveParticipant implements ISaveParticipantParticipant { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentSaveParticipant); } - async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { + async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, _progress: IProgress, token: CancellationToken): Promise { if (!shouldSynchronizeModel(editorModel.textEditorModel)) { // the model never made it to the extension @@ -363,6 +370,9 @@ class ExtHostSaveParticipant implements ISaveParticipantParticipant { } return new Promise((resolve, reject) => { + + token.onCancellationRequested(() => reject(canceled())); + setTimeout( () => reject(new SaveParticipantError(localize('timeout.onWillSave', "Aborted onWillSaveTextDocument-event after 1750ms"))), 1750 @@ -388,7 +398,8 @@ export class SaveParticipant implements ISaveParticipant { @IInstantiationService instantiationService: IInstantiationService, @IProgressService private readonly _progressService: IProgressService, @IStatusbarService private readonly _statusbarService: IStatusbarService, - @ILogService private readonly _logService: ILogService + @ILogService private readonly _logService: ILogService, + @ILabelService private readonly _labelService: ILabelService, ) { this._saveParticipants = new IdleValue(() => [ instantiationService.createInstance(TrimWhitespaceParticipant), @@ -408,23 +419,41 @@ export class SaveParticipant implements ISaveParticipant { } async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { - return this._progressService.withProgress({ location: ProgressLocation.Window }, async progress => { - progress.report({ message: localize('saveParticipants', "Running Save Participants...") }); - let firstError: SaveParticipantError | undefined; + const cts = new CancellationTokenSource(); + + return this._progressService.withProgress({ + title: localize('saveParticipants', "Running Save Participants for '{0}'", this._labelService.getUriLabel(model.resource, { relative: true })), + location: ProgressLocation.Notification, + cancellable: true, + delay: model.isDirty() ? 3000 : 5000 + }, async progress => { + let firstError: SaveParticipantError | undefined; for (let p of this._saveParticipants.getValue()) { + + if (cts.token.isCancellationRequested) { + break; + } + try { - await p.participate(model, env); + await p.participate(model, env, progress, cts.token); + } catch (err) { - this._logService.warn(err); - firstError = !firstError && err instanceof SaveParticipantError ? err : firstError; + if (!isPromiseCanceledError(err)) { + this._logService.warn(err); + firstError = !firstError && err instanceof SaveParticipantError ? err : firstError; + } } } if (firstError) { this._showParticipantError(firstError); } + + }, () => { + // user cancel + cts.dispose(true); }); } From f7fb208b39e30b3ae1a3e17eabc708ccabb6bbf2 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 9 Jan 2020 12:53:41 +0100 Subject: [PATCH 094/315] remove timeouts (and their settings) for format and code actions, #87449 --- .../api/browser/mainThreadSaveParticipant.ts | 41 ++----------------- .../common/codeActionsContribution.ts | 8 +--- .../files/browser/files.contribution.ts | 6 --- 3 files changed, 5 insertions(+), 50 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index ad1cc921d17c1..64d9ae97df772 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -238,25 +238,9 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant { return undefined; } - return new Promise((resolve, reject) => { - - progress.report({ message: localize('formatting', "Formatting") }); - - const source = new CancellationTokenSource(token); - const editorOrModel = findEditor(model, this._codeEditorService) || model; - const timeout = this._configurationService.getValue('editor.formatOnSaveTimeout', overrides) * 1000; - const request = this._instantiationService.invokeFunction(formatDocumentWithSelectedProvider, editorOrModel, FormattingMode.Silent, source.token); - - setTimeout(() => { - reject(new SaveParticipantError( - localize('timeout.formatOnSave', "Aborted format on save after {0}ms", timeout), - 'editor.formatOnSaveTimeout' - )); - source.cancel(); - }, timeout); - - request.then(resolve, reject); - }); + progress.report({ message: localize('formatting', "Formatting") }); + const editorOrModel = findEditor(model, this._codeEditorService) || model; + await this._instantiationService.invokeFunction(formatDocumentWithSelectedProvider, editorOrModel, FormattingMode.Silent, token); } } @@ -305,25 +289,8 @@ class CodeActionOnSaveParticipant implements ISaveParticipantParticipant { .filter(x => setting[x] === false) .map(x => new CodeActionKind(x)); - const tokenSource = new CancellationTokenSource(token); - - const timeout = this._configurationService.getValue('editor.codeActionsOnSaveTimeout', settingsOverrides); - progress.report({ message: localize('codeaction', "Quick Fixes") }); - - return Promise.race([ - new Promise((_resolve, reject) => - setTimeout(() => { - tokenSource.cancel(); - reject(new SaveParticipantError( - localize('codeActionsOnSave.didTimeout', "Aborted codeActionsOnSave after {0}ms", timeout), - 'editor.codeActionsOnSaveTimeout' - )); - }, timeout)), - this.applyOnSaveActions(model, codeActionsOnSave, excludedActions, tokenSource.token) - ]).finally(() => { - tokenSource.cancel(); - }); + await this.applyOnSaveActions(model, codeActionsOnSave, excludedActions, token); } private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: readonly CodeActionKind[], excludes: readonly CodeActionKind[], token: CancellationToken): Promise { diff --git a/src/vs/workbench/contrib/codeActions/common/codeActionsContribution.ts b/src/vs/workbench/contrib/codeActions/common/codeActionsContribution.ts index 53153e88dca2c..16c1f7aa131bc 100644 --- a/src/vs/workbench/contrib/codeActions/common/codeActionsContribution.ts +++ b/src/vs/workbench/contrib/codeActions/common/codeActionsContribution.ts @@ -40,13 +40,7 @@ const codeActionsOnSaveSchema: IConfigurationPropertySchema = { export const editorConfiguration = Object.freeze({ ...editorConfigurationBaseNode, properties: { - 'editor.codeActionsOnSave': codeActionsOnSaveSchema, - 'editor.codeActionsOnSaveTimeout': { - type: 'number', - default: 750, - description: nls.localize('codeActionsOnSaveTimeout', "Timeout in milliseconds after which the code actions that are run on save are cancelled."), - scope: ConfigurationScope.RESOURCE_LANGUAGE, - }, + 'editor.codeActionsOnSave': codeActionsOnSaveSchema } }); diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index e78e63d4c3edd..39744d2ae4ac3 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -344,12 +344,6 @@ configurationRegistry.registerConfiguration({ 'default': false, 'description': nls.localize('formatOnSave', "Format a file on save. A formatter must be available, the file must not be saved after delay, and the editor must not be shutting down."), scope: ConfigurationScope.RESOURCE_LANGUAGE, - }, - 'editor.formatOnSaveTimeout': { - 'type': 'number', - 'default': 750, - 'description': nls.localize('formatOnSaveTimeout', "Timeout in milliseconds after which the formatting that is run on file save is cancelled."), - scope: ConfigurationScope.RESOURCE_LANGUAGE, } } }); From 2bcef5a10227a65c670f688511acb8d9f9c68e1d Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 9 Jan 2020 12:58:14 +0100 Subject: [PATCH 095/315] Add double-click in empty area of Forwarded Ports view Part of #86064 --- src/vs/workbench/contrib/remote/browser/tunnelView.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 8674919db8964..440a7ee8000c5 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -18,7 +18,7 @@ import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { ICommandService, ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { Event, Emitter } from 'vs/base/common/event'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; +import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent, ITreeMouseEvent } from 'vs/base/browser/ui/tree/tree'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { Disposable, IDisposable, toDisposable, MutableDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { ActionBar, ActionViewItem, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -467,6 +467,7 @@ export class TunnelPanel extends ViewPane { renderer.actionRunner = actionRunner; this._register(this.tree.onContextMenu(e => this.onContextMenu(e, actionRunner))); + this._register(this.tree.onMouseDblClick(e => this.onMouseDblClick(e))); this.tree.setInput(this.viewModel); this._register(this.viewModel.onForwardedPortsChanged(() => { @@ -555,6 +556,12 @@ export class TunnelPanel extends ViewPane { }); } + private onMouseDblClick(e: ITreeMouseEvent): void { + if (!e.element) { + this.commandService.executeCommand(ForwardPortAction.INLINE_ID); + } + } + protected layoutBody(height: number, width: number): void { this.tree.layout(height, width); } From 4cfb719aa1748793d518c6869725c19f84ef24c9 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 9 Jan 2020 12:20:53 +0100 Subject: [PATCH 096/315] Fix #88274 --- src/vs/platform/userDataSync/common/userDataSync.ts | 2 +- src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 40ae0d325cb45..93dcbdce2bcfa 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -57,7 +57,7 @@ export function registerConfiguration(): IDisposable { }, 'sync.enableUIState': { type: 'boolean', - description: localize('sync.enableUIState', "Enable synchronizing UI state."), + description: localize('sync.enableUIState', "Enable synchronizing UI state (Only Display Language)."), default: true, scope: ConfigurationScope.APPLICATION, }, diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 69eac6711ac10..77789580a70e9 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -214,7 +214,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo label: localize('user keybindings', "User Keybindings") }, { id: 'sync.enableUIState', - label: localize('ui state', "UI State") + label: localize('ui state', "UI State"), + description: localize('ui state description', "Display Language (Only)") }, { id: 'sync.enableExtensions', label: localize('extensions', "Extensions") From cd4472ed5fd79551d95acf7d9ed23ce20cb2233d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 9 Jan 2020 13:22:46 +0100 Subject: [PATCH 097/315] #86681 Change API to access conflict settings in sync way --- .../userDataSync/common/settingsSync.ts | 44 ++++++++++++------- .../userDataSync/common/userDataSync.ts | 3 +- .../userDataSync/common/userDataSyncIpc.ts | 3 +- .../electron-browser/settingsSyncService.ts | 20 +++++++-- 4 files changed, 47 insertions(+), 23 deletions(-) diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 835e4d242f56e..09dd0a4647e17 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -19,6 +19,8 @@ import { startsWith } from 'vs/base/common/strings'; import { CancellationToken } from 'vs/base/common/cancellation'; import { computeRemoteContent, merge } from 'vs/platform/userDataSync/common/settingsMerge'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; +import * as arrays from 'vs/base/common/arrays'; +import * as objects from 'vs/base/common/objects'; interface ISyncPreviewResult { readonly fileContent: IFileContent | null; @@ -41,6 +43,11 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer private _onDidChangStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangStatus.event; + private _conflicts: IConflictSetting[] = []; + get conflicts(): IConflictSetting[] { return this._conflicts; } + private _onDidChangeConflicts: Emitter = this._register(new Emitter()); + readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; + private _onDidChangeLocal: Emitter = this._register(new Emitter()); readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; @@ -65,6 +72,18 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer this._status = status; this._onDidChangStatus.fire(status); } + if (this._status !== SyncStatus.HasConflicts) { + this.setConflicts([]); + } + } + + private setConflicts(conflicts: IConflictSetting[]): void { + if (!arrays.equals(this.conflicts, conflicts, + (a, b) => a.key === b.key && objects.equals(a.localValue, b.localValue) && objects.equals(a.remoteValue, b.remoteValue)) + ) { + this._conflicts = conflicts; + this._onDidChangeConflicts.fire(conflicts); + } } async sync(_continue?: boolean): Promise { @@ -83,6 +102,8 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer return false; } + this.logService.trace('Settings: Started synchronizing settings...'); + this.setStatus(SyncStatus.Syncing); return this.doSync([]); } @@ -96,28 +117,15 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer this.setStatus(SyncStatus.Idle); } - async getConflicts(): Promise { - if (!this.syncPreviewResultPromise) { - return []; - } - if (this.status !== SyncStatus.HasConflicts) { - return []; - } - const preview = await this.syncPreviewResultPromise; - return preview.conflicts; - } - async resolveConflicts(resolvedConflicts: { key: string, value: any | undefined }[]): Promise { if (this.status === SyncStatus.HasConflicts) { - this.stop(); + this.syncPreviewResultPromise!.cancel(); + this.syncPreviewResultPromise = null; await this.doSync(resolvedConflicts); } } private async doSync(resolvedConflicts: { key: string, value: any | undefined }[]): Promise { - this.logService.trace('Settings: Started synchronizing settings...'); - this.setStatus(SyncStatus.Syncing); - try { const result = await this.getPreview(resolvedConflicts); if (result.conflicts.length) { @@ -222,12 +230,13 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer if (remoteContent) { const localContent: string = fileContent ? fileContent.value.toString() : '{}'; + + // No action when there are errors if (this.hasErrors(localContent)) { this.logService.error('Settings: Unable to sync settings as there are errors/warning in settings file.'); - return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, conflicts }; } - if (!lastSyncData // First time sync + else if (!lastSyncData // First time sync || lastSyncData.content !== localContent // Local has forwarded || lastSyncData.content !== remoteContent // Remote has forwarded ) { @@ -255,6 +264,7 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(previewContent)); } + this.setConflicts(conflicts); return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, conflicts }; } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 93dcbdce2bcfa..ca76015a8851c 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -200,7 +200,8 @@ export interface IConflictSetting { export const ISettingsSyncService = createDecorator('ISettingsSyncService'); export interface ISettingsSyncService extends ISynchroniser { _serviceBrand: any; - getConflicts(): Promise; + readonly onDidChangeConflicts: Event; + readonly conflicts: IConflictSetting[]; resolveConflicts(resolvedConflicts: { key: string, value: any | undefined }[]): Promise; } diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index 3841e850cb4b3..bcd53cae1b700 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -42,6 +42,7 @@ export class SettingsSyncChannel implements IServerChannel { switch (event) { case 'onDidChangeStatus': return this.service.onDidChangeStatus; case 'onDidChangeLocal': return this.service.onDidChangeLocal; + case 'onDidChangeConflicts': return this.service.onDidChangeConflicts; } throw new Error(`Event not found: ${event}`); } @@ -50,8 +51,8 @@ export class SettingsSyncChannel implements IServerChannel { switch (command) { case 'sync': return this.service.sync(args[0]); case '_getInitialStatus': return Promise.resolve(this.service.status); + case '_getInitialConflicts': return Promise.resolve(this.service.conflicts); case 'stop': this.service.stop(); return Promise.resolve(); - case 'getConflicts': return this.service.getConflicts(); case 'resolveConflicts': return this.service.resolveConflicts(args[0]); } throw new Error('Invalid call'); diff --git a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts index 58f65eee3f464..0a2adc53f3a97 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts @@ -21,6 +21,11 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ private _onDidChangeStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; + private _conflicts: IConflictSetting[] = []; + get conflicts(): IConflictSetting[] { return this._conflicts; } + private _onDidChangeConflicts: Emitter = this._register(new Emitter()); + readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; + get onDidChangeLocal(): Event { return this.channel.listen('onDidChangeLocal'); } constructor( @@ -32,6 +37,12 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ this.updateStatus(status); this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status))); }); + this.channel.call('_getInitialConflicts').then(conflicts => { + if (conflicts.length) { + this.updateConflicts(conflicts); + } + this._register(this.channel.listen('onDidChangeConflicts')(conflicts => this.updateConflicts(conflicts))); + }); } sync(_continue?: boolean): Promise { @@ -42,10 +53,6 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ this.channel.call('stop'); } - getConflicts(): Promise { - return this.channel.call('getConflicts'); - } - resolveConflicts(conflicts: { key: string, value: any | undefined }[]): Promise { return this.channel.call('resolveConflicts', [conflicts]); } @@ -55,6 +62,11 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ this._onDidChangeStatus.fire(status); } + private async updateConflicts(conflicts: IConflictSetting[]): Promise { + this._conflicts = conflicts; + this._onDidChangeConflicts.fire(conflicts); + } + } registerSingleton(ISettingsSyncService, SettingsSyncService); From b8b0b558146b0d22a1190f63aaaabf9dd87c73ba Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Thu, 9 Jan 2020 13:58:44 +0100 Subject: [PATCH 098/315] wip --- .../characterHardWrappingLineMapper.ts | 272 ++++++++++++++++++ .../common/viewModel/splitLinesCollection.ts | 23 ++ .../characterHardWrappingLineMapper.test.ts | 95 ++++-- 3 files changed, 372 insertions(+), 18 deletions(-) diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index d1d9a51306dfe..0790c9a644401 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -105,6 +105,222 @@ function createLineMapping(classifier: WrappingCharacterClassifier, previousBrea let breakingOffsets: number[] = []; let breakingOffsetsVisibleColumn: number[] = []; let breakingOffsetsCount: number = 0; + + if (previousBreakingData/* && firstLineBreakingColumn >= 10 && Math.abs(previousBreakingData.breakingColumn - firstLineBreakingColumn) <= 3 */) { + const prevBreakingOffsets = previousBreakingData.breakOffsets; + const prevBreakingOffsetsVisibleColumn = previousBreakingData.breakingOffsetsVisibleColumn; + + let breakingColumn = firstLineBreakingColumn; + const prevLen = prevBreakingOffsets.length; + let prevIndex = 0; + while (prevIndex < prevLen) { + + // Allow for prevIndex to be -1 (for the case where we hit a tab when walking backwards from the first break) + let breakOffset = prevIndex < 0 ? 0 : prevBreakingOffsets[prevIndex]; + let breakOffsetVisibleColumn = prevIndex < 0 ? 0 : prevBreakingOffsetsVisibleColumn[prevIndex]; + + if (breakOffsetVisibleColumn === breakingColumn) { + // perfect fit, nothing to do + breakingOffsets[breakingOffsetsCount] = breakOffset; + breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn; + breakingOffsetsCount++; + breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakingColumn; + prevIndex++; + } else if (breakOffsetVisibleColumn < breakingColumn) { + // try to add more characters + const initialBreakOffset = breakOffset; + let visibleColumn = breakOffsetVisibleColumn; + breakOffset = 0; + + let prevCharCode = lineText.charCodeAt(initialBreakOffset - 1); + let prevCharCodeClass = classifier.get(prevCharCode); + let mustBreak = false; + for (let i = initialBreakOffset; i < len; i++) { + const charCode = lineText.charCodeAt(i); + const charCodeClass = classifier.get(charCode); + + if (strings.isHighSurrogate(prevCharCode)) { + // A surrogate pair must always be considered as a single unit, so it is never to be broken + visibleColumn += 1; + prevCharCode = charCode; + prevCharCodeClass = charCodeClass; + continue; + } + + if (canBreak(prevCharCodeClass, charCodeClass)) { + breakOffset = i; + breakOffsetVisibleColumn = visibleColumn; + } + + const charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar); + visibleColumn += charWidth; + + if (visibleColumn > breakingColumn) { + // We need to break at least before character at `i`: + + if (breakOffset === 0 || visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakingColumn) { + // Cannot break at `breakOffset`, must break at `i` + breakOffset = i; + breakOffsetVisibleColumn = visibleColumn - charWidth; + } + + mustBreak = true; + break; + } + + prevCharCode = charCode; + prevCharCodeClass = charCodeClass; + } + + if (!mustBreak) { + // there is no more need to break => stop the outer loop! + // Add last segment + breakingOffsets[breakingOffsetsCount] = prevBreakingOffsets[prevBreakingOffsets.length - 1]; + breakingOffsetsVisibleColumn[breakingOffsetsCount] = prevBreakingOffsetsVisibleColumn[prevBreakingOffsets.length - 1]; + break; + } + + breakingOffsets[breakingOffsetsCount] = breakOffset; + breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn; + breakingOffsetsCount++; + breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakingColumn; + prevIndex++; + } else if (breakOffsetVisibleColumn > breakingColumn) { + const initialBreakOffset = breakOffset; + let visibleColumn = breakOffsetVisibleColumn; + breakOffset = 0; + + let charCode = lineText.charCodeAt(initialBreakOffset); + let charCodeClass = classifier.get(charCode); + let hitTab = false; + + let firstValidBreakOffset = 0; + let firstValidBreakOffsetVisibleColumn = 0; + for (let i = initialBreakOffset - 1; i >= 0; i--) { + let prevCharCode = lineText.charCodeAt(i); + let prevCharCodeClass = classifier.get(prevCharCode); + + if (strings.isHighSurrogate(prevCharCode)) { + // A surrogate pair must always be considered as a single unit, so it is never to be broken + visibleColumn -= 1; + charCode = prevCharCode; + charCodeClass = prevCharCodeClass; + continue; + } + + if (prevCharCode === CharCode.Tab) { + // cannot determine the width of a tab when going backwards, so we must go forwards + hitTab = true; + break; + } + + const charWidth = (strings.isFullWidthCharacter(prevCharCode) ? columnsForFullWidthChar : 1); + + if (visibleColumn <= breakingColumn) { + if (firstValidBreakOffset === 0) { + firstValidBreakOffset = i + 1; + firstValidBreakOffsetVisibleColumn = visibleColumn; + } + + if (visibleColumn <= breakingColumn - wrappedLineBreakingColumn) { + // went too far! + break; + } + + if (canBreak(prevCharCodeClass, charCodeClass)) { + breakOffset = i + 1; + breakOffsetVisibleColumn = visibleColumn; + break; + } + } + + visibleColumn -= charWidth; + charCode = prevCharCode; + charCodeClass = prevCharCodeClass; + } + + if (hitTab) { + // cannot determine the width of a tab when going backwards, so we must go forwards + prevIndex--; + continue; + } + + if (breakOffset === 0) { + // Could not find a good breaking point + breakOffset = firstValidBreakOffset; + breakOffsetVisibleColumn = firstValidBreakOffsetVisibleColumn; + } + + breakingOffsets[breakingOffsetsCount] = breakOffset; + breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn; + breakingOffsetsCount++; + breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakingColumn; + } + + if (prevIndex < 0) { + prevIndex = 0; + } else { + let currentDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex] - breakingColumn); + while (prevIndex + 1 < prevLen) { + const potentialDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex + 1] - breakingColumn); + if (potentialDiff >= currentDiff) { + break; + } + currentDiff = potentialDiff; + prevIndex++; + } + } + } + + if (breakingOffsetsCount === 0) { + return null; + } + + return new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); + const expected = createLineMapping(classifier, null, lineText, tabSize, firstLineBreakingColumn, columnsForFullWidthChar, hardWrappingIndent); + const actual = new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); + try { + actual.assertEqual(expected); + } catch (err) { + console.log(`BREAKING!!`); + console.log(err); + console.log(` + assertIncrementalLineMapping( + factory, ${str(lineText)}, 4, + ${previousBreakingData.breakingColumn}, ${str(toAnnotatedText(lineText, previousBreakingData))}, + ${expected!.breakingColumn}, ${str(toAnnotatedText(lineText, expected))} + ); +`); + function str(strr: string) { + return `'${strr.replace(/'/g, '\\\'')}'`; + } + function toAnnotatedText(text: string, lineBreakingData: LineBreakingData | null): string { + // Insert line break markers again, according to algorithm + let actualAnnotatedText = ''; + if (lineBreakingData) { + let previousLineIndex = 0; + for (let i = 0, len = text.length; i < len; i++) { + let r = LineBreakingData.getOutputPositionOfInputOffset(lineBreakingData.breakOffsets, i); + if (previousLineIndex !== r.outputLineIndex) { + previousLineIndex = r.outputLineIndex; + actualAnnotatedText += '|'; + } + actualAnnotatedText += text.charAt(i); + } + } else { + // No wrapping + actualAnnotatedText = text; + } + return actualAnnotatedText; + } + } + return actual; + + breakingOffsets = []; + breakingOffsetsVisibleColumn = []; + breakingOffsetsCount = 0; + } + let breakOffset = 0; let breakOffsetVisibleColumn = 0; @@ -165,6 +381,62 @@ function createLineMapping(classifier: WrappingCharacterClassifier, previousBrea return new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); } +// class BreakSearchResult { + +// public static INSTANCE = new BreakSearchResult(); + +// prevCharCode: number = CharCode.Null; +// prevCharCodeClass: CharacterClass = CharacterClass.NONE; +// breakOffset: number = 0; +// breakOffsetVisibleColumn: number = 0; +// visibleColumn: number = 0; +// } + +// function searchForBreak(classifier: WrappingCharacterClassifier, lineText: string, len: number, prevCharCode: number, prevCharCodeClass: number): boolean { +// let breakOffset = 0; +// let breakOffsetVisibleColumn = 0; +// for (let i = 1; i < len; i++) { +// const charCode = lineText.charCodeAt(i); +// const charCodeClass = classifier.get(charCode); + +// if (strings.isHighSurrogate(prevCharCode)) { +// // A surrogate pair must always be considered as a single unit, so it is never to be broken +// visibleColumn += 1; +// prevCharCode = charCode; +// prevCharCodeClass = charCodeClass; +// continue; +// } + +// if (canBreak(prevCharCodeClass, charCodeClass)) { +// breakOffset = i; +// breakOffsetVisibleColumn = visibleColumn; +// } + +// const charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar); +// visibleColumn += charWidth; + +// // check if adding character at `i` will go over the breaking column +// if (visibleColumn > breakingColumn) { +// // We need to break at least before character at `i`: + +// if (breakOffset === 0 || visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakingColumn) { +// // Cannot break at `breakOffset`, must break at `i` +// breakOffset = i; +// breakOffsetVisibleColumn = visibleColumn - charWidth; +// } + +// breakingOffsets[breakingOffsetsCount] = breakOffset; +// breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn; +// breakingOffsetsCount++; +// breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakingColumn; +// breakOffset = 0; +// } + +// prevCharCode = charCode; +// prevCharCodeClass = charCodeClass; +// } +// } + function computeCharWidth(charCode: number, visibleColumn: number, tabSize: number, columnsForFullWidthChar: number): number { if (charCode === CharCode.Tab) { return (tabSize - (visibleColumn % tabSize)); diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 6346d5bbc4cfd..2f0af80ac2455 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -34,6 +34,29 @@ export class LineBreakingData { public readonly wrappedTextIndentLength: number ) { } + assertEqual(other: LineBreakingData | null): void { + if (other === null) { + throw new Error(`x--unexpected--1`); + } + if (other.breakingColumn !== this.breakingColumn) { + throw new Error(`x--unexpected--2`); + } + if (other.wrappedTextIndentLength !== this.wrappedTextIndentLength) { + throw new Error(`x--unexpected--3`); + } + if (other.breakOffsets.length !== this.breakOffsets.length) { + throw new Error(`x--unexpected--4`); + } + for (let i = 0; i < this.breakOffsets.length; i++) { + if (this.breakOffsets[i] !== other.breakOffsets[i]) { + throw new Error(`x--unexpected--5`); + } + if (this.breakingOffsetsVisibleColumn[i] !== other.breakingOffsetsVisibleColumn[i]) { + throw new Error(`x--unexpected--6`); + } + } + } + public static getInputOffsetOfOutputPosition(breakOffsets: number[], outputLineIndex: number, outputOffset: number): number { if (outputLineIndex === 0) { return outputOffset; diff --git a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts index a1aec876698a8..4890907d5f971 100644 --- a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts +++ b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts @@ -3,49 +3,60 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { WrappingIndent } from 'vs/editor/common/config/editorOptions'; +import { WrappingIndent, EditorOptions } from 'vs/editor/common/config/editorOptions'; import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; import { ILineMapperFactory, LineBreakingData } from 'vs/editor/common/viewModel/splitLinesCollection'; -function assertLineMapping(factory: ILineMapperFactory, tabSize: number, breakAfter: number, annotatedText: string, wrappingIndent = WrappingIndent.None): LineBreakingData | null { - // Create version of `annotatedText` with line break markers removed - let rawText = ''; +function parseAnnotatedText(annotatedText: string): { text: string; indices: number[]; } { + let text = ''; let currentLineIndex = 0; - let lineIndices: number[] = []; + let indices: number[] = []; for (let i = 0, len = annotatedText.length; i < len; i++) { if (annotatedText.charAt(i) === '|') { currentLineIndex++; } else { - rawText += annotatedText.charAt(i); - lineIndices[rawText.length - 1] = currentLineIndex; + text += annotatedText.charAt(i); + indices[text.length - 1] = currentLineIndex; } } + return { text: text, indices: indices }; +} - const lineMappingComputer = factory.createLineMappingComputer(tabSize, breakAfter, 2, wrappingIndent); - lineMappingComputer.addRequest(rawText, null); - const lineMappings = lineMappingComputer.finalize(); - const mapper = lineMappings[0]; - +function toAnnotatedText(text: string, lineBreakingData: LineBreakingData | null): string { // Insert line break markers again, according to algorithm let actualAnnotatedText = ''; - if (mapper) { + if (lineBreakingData) { let previousLineIndex = 0; - for (let i = 0, len = rawText.length; i < len; i++) { - let r = LineBreakingData.getOutputPositionOfInputOffset(mapper.breakOffsets, i); + for (let i = 0, len = text.length; i < len; i++) { + let r = LineBreakingData.getOutputPositionOfInputOffset(lineBreakingData.breakOffsets, i); if (previousLineIndex !== r.outputLineIndex) { previousLineIndex = r.outputLineIndex; actualAnnotatedText += '|'; } - actualAnnotatedText += rawText.charAt(i); + actualAnnotatedText += text.charAt(i); } } else { // No wrapping - actualAnnotatedText = rawText; + actualAnnotatedText = text; } + return actualAnnotatedText; +} + +function getLineBreakingData(factory: ILineMapperFactory, tabSize: number, breakAfter: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent, text: string, previousLineBreakingData: LineBreakingData | null): LineBreakingData | null { + const lineMappingComputer = factory.createLineMappingComputer(tabSize, breakAfter, columnsForFullWidthChar, wrappingIndent); + lineMappingComputer.addRequest(text, previousLineBreakingData); + return lineMappingComputer.finalize()[0]; +} + +function assertLineMapping(factory: ILineMapperFactory, tabSize: number, breakAfter: number, annotatedText: string, wrappingIndent = WrappingIndent.None): LineBreakingData | null { + // Create version of `annotatedText` with line break markers removed + const text = parseAnnotatedText(annotatedText).text; + const lineBreakingData = getLineBreakingData(factory, tabSize, breakAfter, 2, wrappingIndent, text, null); + const actualAnnotatedText = toAnnotatedText(text, lineBreakingData); assert.equal(actualAnnotatedText, annotatedText); - return mapper; + return lineBreakingData; } suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { @@ -91,6 +102,54 @@ suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { assertLineMapping(factory, 4, 5, 'aa.(.|).aaa'); }); + function assertIncrementalLineMapping(factory: ILineMapperFactory, text: string, tabSize: number, breakAfter1: number, annotatedText1: string, breakAfter2: number, annotatedText2: string, wrappingIndent = WrappingIndent.None): void { + // sanity check the test + assert.equal(text, parseAnnotatedText(annotatedText1).text); + assert.equal(text, parseAnnotatedText(annotatedText2).text); + + // check that the direct mapping is ok for 1 + const directLineBreakingData1 = getLineBreakingData(factory, tabSize, breakAfter1, 2, wrappingIndent, text, null); + assert.equal(toAnnotatedText(text, directLineBreakingData1), annotatedText1); + + // check that the direct mapping is ok for 2 + const directLineBreakingData2 = getLineBreakingData(factory, tabSize, breakAfter2, 2, wrappingIndent, text, null); + assert.equal(toAnnotatedText(text, directLineBreakingData2), annotatedText2); + + // check that going from 1 to 2 is ok + const lineBreakingData2from1 = getLineBreakingData(factory, tabSize, breakAfter2, 2, wrappingIndent, text, directLineBreakingData1); + assert.equal(toAnnotatedText(text, lineBreakingData2from1), annotatedText2); + assert.deepEqual(lineBreakingData2from1, directLineBreakingData2); + + // check that going from 2 to 1 is ok + const lineBreakingData1from2 = getLineBreakingData(factory, tabSize, breakAfter1, 2, wrappingIndent, text, directLineBreakingData2); + assert.equal(toAnnotatedText(text, lineBreakingData1from2), annotatedText1); + assert.deepEqual(lineBreakingData1from2, directLineBreakingData1); + } + + test('CharacterHardWrappingLineMapper incremental 1', () => { + + let factory = new CharacterHardWrappingLineMapperFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue); + + assertIncrementalLineMapping( + factory, 'just some text and more', 4, + 10, 'just some |text and |more', + 15, 'just some text |and more' + ); + + assertIncrementalLineMapping( + factory, 'Cu scripserit suscipiantur eos, in affert pericula contentiones sed, cetero sanctus et pro. Ius vidit magna regione te, sit ei elaboraret liberavisse. Mundi verear eu mea, eam vero scriptorem in, vix in menandri assueverit. Natum definiebas cu vim. Vim doming vocibus efficiantur id. In indoctum deseruisse voluptatum vim, ad debitis verterem sed.', 4, + 47, 'Cu scripserit suscipiantur eos, in affert |pericula contentiones sed, cetero sanctus et |pro. Ius vidit magna regione te, sit ei |elaboraret liberavisse. Mundi verear eu mea, |eam vero scriptorem in, vix in menandri |assueverit. Natum definiebas cu vim. Vim |doming vocibus efficiantur id. In indoctum |deseruisse voluptatum vim, ad debitis verterem |sed.', + 142, 'Cu scripserit suscipiantur eos, in affert pericula contentiones sed, cetero sanctus et pro. Ius vidit magna regione te, sit ei elaboraret |liberavisse. Mundi verear eu mea, eam vero scriptorem in, vix in menandri assueverit. Natum definiebas cu vim. Vim doming vocibus efficiantur |id. In indoctum deseruisse voluptatum vim, ad debitis verterem sed.', + ); + + assertIncrementalLineMapping( + factory, 'An his legere persecuti, oblique delicata efficiantur ex vix, vel at graecis officiis maluisset. Et per impedit voluptua, usu discere maiorum at. Ut assum ornatus temporibus vis, an sea melius pericula. Ea dicunt oblique phaedrum nam, eu duo movet nobis. His melius facilis eu, vim malorum temporibus ne. Nec no sale regione, meliore civibus placerat id eam. Mea alii fabulas definitionem te, agam volutpat ad vis, et per bonorum nonumes repudiandae.', 4, + 57, 'An his legere persecuti, oblique delicata efficiantur ex |vix, vel at graecis officiis maluisset. Et per impedit |voluptua, usu discere maiorum at. Ut assum ornatus |temporibus vis, an sea melius pericula. Ea dicunt |oblique phaedrum nam, eu duo movet nobis. His melius |facilis eu, vim malorum temporibus ne. Nec no sale |regione, meliore civibus placerat id eam. Mea alii |fabulas definitionem te, agam volutpat ad vis, et per |bonorum nonumes repudiandae.', + 58, 'An his legere persecuti, oblique delicata efficiantur ex |vix, vel at graecis officiis maluisset. Et per impedit |voluptua, usu discere maiorum at. Ut assum ornatus |temporibus vis, an sea melius pericula. Ea dicunt oblique |phaedrum nam, eu duo movet nobis. His melius facilis eu, |vim malorum temporibus ne. Nec no sale regione, meliore |civibus placerat id eam. Mea alii fabulas definitionem te,| agam volutpat ad vis, et per bonorum nonumes repudiandae.' + ); + }); + + test('CharacterHardWrappingLineMapper - CJK and Kinsoku Shori', () => { let factory = new CharacterHardWrappingLineMapperFactory('(', '\t)'); assertLineMapping(factory, 4, 5, 'aa \u5b89|\u5b89'); From 4c7431ca8d6e48bdc5ff9e6ebc7c55dd379e3188 Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 9 Jan 2020 19:16:52 +0530 Subject: [PATCH 099/315] chore: Upgrade to electron 7.x (#83796) * chore: bump electron@7.0.0 * chore: update api * chore: Bump electron@7.0.1 * chore: bump electron@7.1.0 * chore: Bump electron@7.1.1 * chore: Bump electron@7.1.2 * FIXME: Skip webview tests that have improper shutdown path * chore: Bump electron@7.1.3 * bump electron@7.1.5 * debug test failures * chore: bump electron@7.1.7 * skip test for https://github.com/microsoft/vscode/issues/87330 Co-authored-by: Benjamin Pasero --- .yarnrc | 2 +- cgmanifest.json | 12 +- .../src/singlefolder-tests/webview.test.ts | 2 +- .../src/singlefolder-tests/window.test.ts | 17 +- package.json | 2 +- src/vs/code/electron-main/app.ts | 34 +- src/vs/code/electron-main/window.ts | 6 +- .../platform/dialogs/electron-main/dialogs.ts | 2 +- .../platform/driver/electron-main/driver.ts | 3 +- .../electron-main/electronMainService.ts | 16 +- .../electron-browser/webviewElement.ts | 6 +- src/vs/workbench/electron-browser/window.ts | 6 +- yarn.lock | 542 ++++++++++-------- 13 files changed, 374 insertions(+), 276 deletions(-) diff --git a/.yarnrc b/.yarnrc index 85baaa63a786b..2c769cfba1823 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "6.1.6" +target "7.1.7" runtime "electron" diff --git a/cgmanifest.json b/cgmanifest.json index c102a04f70541..e9dfd3d6ef32b 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "chromium", "repositoryUrl": "https://chromium.googlesource.com/chromium/src", - "commitHash": "91f08db83c2ce8c722ddf0911ead8f7c473bedfa" + "commitHash": "e4745133a1d3745f066e068b8033c6a269b59caf" } }, "licenseDetail": [ @@ -40,7 +40,7 @@ "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ], "isOnlyProductionDependency": true, - "version": "76.0.3809.146" + "version": "78.0.3904.130" }, { "component": { @@ -48,11 +48,11 @@ "git": { "name": "nodejs", "repositoryUrl": "https://github.com/nodejs/node", - "commitHash": "64219741218aa87e259cf8257596073b8e747f0a" + "commitHash": "787378879acfb212ed4ff824bf9f767a24a5cb43a" } }, "isOnlyProductionDependency": true, - "version": "12.4.0" + "version": "12.8.1" }, { "component": { @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "19c705ab80cd6fdccca3d65803ec2c4addb9540a" + "commitHash": "bef0dd868b7d6d32716c319664ed480f2ae17396" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "6.1.6" + "version": "7.1.7" }, { "component": { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts index e785f1d4afbe1..e149da8995b7d 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts @@ -13,7 +13,7 @@ const webviewId = 'myWebview'; const testDocument = join(vscode.workspace.rootPath || '', './bower.json'); -suite('Webview tests', () => { +suite.skip('Webview tests', () => { const disposables: vscode.Disposable[] = []; function _register(disposable: T) { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts index c2ec4f13681f3..1e6d2f60669d3 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts @@ -145,17 +145,24 @@ suite('window namespace tests', () => { }); }); - test('active editor not always correct... #49125', async function () { + test.skip('active editor not always correct... #49125', async function () { + const randomFile1 = await createRandomFile(); + const randomFile2 = await createRandomFile(); + + console.log('Created random files: ' + randomFile1.toString() + ' and ' + randomFile2.toString()); + const [docA, docB] = await Promise.all([ - workspace.openTextDocument(await createRandomFile()), - workspace.openTextDocument(await createRandomFile()), + workspace.openTextDocument(randomFile1), + workspace.openTextDocument(randomFile2) ]); for (let c = 0; c < 4; c++) { let editorA = await window.showTextDocument(docA, ViewColumn.One); - assert(window.activeTextEditor === editorA); + console.log('Showing: ' + editorA.document.fileName + ' and active editor is: ' + window.activeTextEditor?.document.fileName); + assert.equal(window.activeTextEditor, editorA); let editorB = await window.showTextDocument(docB, ViewColumn.Two); - assert(window.activeTextEditor === editorB); + console.log('Showing: ' + editorB.document.fileName + ' and active editor is: ' + window.activeTextEditor?.document.fileName); + assert.equal(window.activeTextEditor, editorB); } }); diff --git a/package.json b/package.json index aa000ded9df63..11d823edf02f3 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "coveralls": "^2.11.11", "cson-parser": "^1.3.3", "debounce": "^1.0.0", - "electron": "6.1.6", + "electron": "7.1.7", "eslint": "6.8.0", "eslint-plugin-jsdoc": "^19.1.0", "event-stream": "3.3.4", diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 4d544450cc82e..549c348240bb1 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -171,7 +171,7 @@ export class CodeApplication extends Disposable { app.on('web-contents-created', (_event: Event, contents) => { contents.on('will-attach-webview', (event: Event, webPreferences, params) => { - const isValidWebviewSource = (source: string): boolean => { + const isValidWebviewSource = (source: string | undefined): boolean => { if (!source) { return false; } @@ -191,11 +191,11 @@ export class CodeApplication extends Disposable { webPreferences.nodeIntegration = false; // Verify URLs being loaded - if (isValidWebviewSource(params.src) && isValidWebviewSource(webPreferences.preloadURL)) { + if (isValidWebviewSource(params.src) && isValidWebviewSource(webPreferences.preload)) { return; } - delete webPreferences.preloadUrl; + delete (webPreferences as { preloadURL: string }).preloadURL; // https://github.com/electron/electron/issues/21553 // Otherwise prevent loading this.logService.error('webContents#web-contents-created: Prevented webview attach'); @@ -497,27 +497,27 @@ export class CodeApplication extends Disposable { this.logService.info(`Tracing: waiting for windows to get ready...`); let recordingStopped = false; - const stopRecording = (timeout: boolean) => { + const stopRecording = async (timeout: boolean) => { if (recordingStopped) { return; } recordingStopped = true; // only once - contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`), path => { - if (!timeout) { - if (this.dialogMainService) { - this.dialogMainService.showMessageBox({ - type: 'info', - message: localize('trace.message', "Successfully created trace."), - detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path), - buttons: [localize('trace.ok', "Ok")] - }, withNullAsUndefined(BrowserWindow.getFocusedWindow())); - } - } else { - this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`); + const path = await contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`)); + + if (!timeout) { + if (this.dialogMainService) { + this.dialogMainService.showMessageBox({ + type: 'info', + message: localize('trace.message', "Successfully created trace."), + detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path), + buttons: [localize('trace.ok', "Ok")] + }, withNullAsUndefined(BrowserWindow.getFocusedWindow())); } - }); + } else { + this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`); + } }; // Wait up to 30s before creating the trace anyways diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index d0ea8752aae25..fadf97300166d 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -347,9 +347,9 @@ export class CodeWindow extends Disposable implements ICodeWindow { }); this._win.webContents.session.webRequest.onHeadersReceived(null!, (details, callback) => { - const responseHeaders = details.responseHeaders as { [key: string]: string[] }; + const responseHeaders = details.responseHeaders as Record; - const contentType: string[] = (responseHeaders['content-type'] || responseHeaders['Content-Type']); + const contentType = (responseHeaders['content-type'] || responseHeaders['Content-Type']); if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) { return callback({ cancel: true }); } @@ -441,7 +441,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Inject headers when requests are incoming const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*']; this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => - this.marketplaceHeadersPromise.then(headers => cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) as { [key: string]: string | undefined } }))); + this.marketplaceHeadersPromise.then(headers => cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) as Record }))); } private onWindowError(error: WindowError): void { diff --git a/src/vs/platform/dialogs/electron-main/dialogs.ts b/src/vs/platform/dialogs/electron-main/dialogs.ts index 7b49ca50c2e33..c694c003e9bdc 100644 --- a/src/vs/platform/dialogs/electron-main/dialogs.ts +++ b/src/vs/platform/dialogs/electron-main/dialogs.ts @@ -173,7 +173,7 @@ export class DialogMainService implements IDialogMainService { showOpenDialog(options: OpenDialogOptions, window?: BrowserWindow): Promise { - function normalizePaths(paths: string[] | undefined): string[] | undefined { + function normalizePaths(paths: string[]): string[] { if (paths && paths.length > 0 && isMacintosh) { paths = paths.map(path => normalizeNFC(path)); // normalize paths returned from the OS } diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index e0beffc55edfb..bae556076230b 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -18,7 +18,6 @@ import { ScanCodeBinding } from 'vs/base/common/scanCode'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { timeout } from 'vs/base/common/async'; import { IDriver, IElement, IWindowDriver } from 'vs/platform/driver/common/driver'; -import { NativeImage } from 'electron'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IElectronMainService } from 'vs/platform/electron/electron-main/electronMainService'; @@ -67,7 +66,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { throw new Error('Invalid window'); } const webContents = window.win.webContents; - const image = await new Promise(c => webContents.capturePage(c)); + const image = await webContents.capturePage(); return image.toPNG().toString('base64'); } diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index 24ef0a029abc8..98b0e2a1f6672 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -367,15 +367,13 @@ export class ElectronMainService implements IElectronMainService { //#region Connectivity async resolveProxy(windowId: number | undefined, url: string): Promise { - return new Promise(resolve => { - const window = this.windowById(windowId); - const session = window?.win?.webContents?.session; - if (session) { - session.resolveProxy(url, proxy => resolve(proxy)); - } else { - resolve(); - } - }); + const window = this.windowById(windowId); + const session = window?.win?.webContents?.session; + if (session) { + return session.resolveProxy(url); + } else { + return undefined; + } } //#endregion diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 929b367097b70..00aa85761895c 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { FindInPageOptions, OnBeforeRequestDetails, OnHeadersReceivedDetails, Response, WebContents, WebviewTag } from 'electron'; +import { FindInPageOptions, OnBeforeRequestListenerDetails, OnHeadersReceivedListenerDetails, Response, WebContents, WebviewTag } from 'electron'; import { addDisposableListener } from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; import { once } from 'vs/base/common/functional'; @@ -65,8 +65,8 @@ class WebviewTagHandle extends Disposable { } } -type OnBeforeRequestDelegate = (details: OnBeforeRequestDetails) => Promise; -type OnHeadersReceivedDelegate = (details: OnHeadersReceivedDetails) => { cancel: boolean; } | undefined; +type OnBeforeRequestDelegate = (details: OnBeforeRequestListenerDetails) => Promise; +type OnHeadersReceivedDelegate = (details: OnHeadersReceivedListenerDetails) => { cancel: boolean; } | undefined; class WebviewSession extends Disposable { diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 18166cdef374a..8b2f1b17d6b1c 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -21,7 +21,7 @@ import * as browser from 'vs/base/browser/browser'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IResourceInput } from 'vs/platform/editor/common/editor'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; -import { ipcRenderer as ipc, webFrame, crashReporter, Event as IpcEvent } from 'electron'; +import { ipcRenderer as ipc, webFrame, crashReporter, CrashReporterStartOptions, Event as IpcEvent } from 'electron'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -537,13 +537,13 @@ export class ElectronWindow extends Disposable { } // base options with product info - const options = { + const options: CrashReporterStartOptions = { companyName, productName, submitURL: isWindows ? hockeyAppConfig[process.arch === 'ia32' ? 'win32-ia32' : 'win32-x64'] : isLinux ? hockeyAppConfig[`linux-x64`] : hockeyAppConfig.darwin, extra: { vscode_version: product.version, - vscode_commit: product.commit + vscode_commit: product.commit || '' } }; diff --git a/yarn.lock b/yarn.lock index e4ca9c5cc9c33..36fee127c849e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -95,6 +95,33 @@ lodash "^4.17.11" to-fast-properties "^2.0.0" +"@electron/get@^1.0.1": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.7.2.tgz#286436a9fb56ff1a1fcdf0e80131fd65f4d1e0fd" + integrity sha512-LSE4LZGMjGS9TloDx0yO44D2UTbaeKRk+QjlhWLiQlikV6J4spgDCjb6z4YIcqmPAwNzlNCnWF4dubytwI+ATA== + dependencies: + debug "^4.1.1" + env-paths "^2.2.0" + fs-extra "^8.1.0" + got "^9.6.0" + sanitize-filename "^1.6.2" + sumchecker "^3.0.1" + optionalDependencies: + global-agent "^2.0.2" + global-tunnel-ng "^2.7.1" + +"@sindresorhus/is@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" + integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== + +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== + dependencies: + defer-to-connect "^1.0.1" + "@types/applicationinsights@0.20.0": version "0.20.0" resolved "https://registry.yarnpkg.com/@types/applicationinsights/-/applicationinsights-0.20.0.tgz#fa7b36dc954f635fa9037cad27c378446b1048fb" @@ -172,10 +199,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== -"@types/node@^10.12.18": - version "10.17.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.9.tgz#4f251a1ed77ac7ef09d456247d67fc8173f6b9da" - integrity sha512-+6VygF9LbG7Gaqeog2G7u1+RUcmo0q1rI+2ZxdIg2fAUngk5Vz9fOCHXdloNUOHEPd1EuuOpL5O0CdgN9Fx5UQ== +"@types/node@^12.0.12": + version "12.12.24" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.24.tgz#d4606afd8cf6c609036b854360367d1b2c78931f" + integrity sha512-1Ciqv9pqwVtW6FsIUKSZNB82E5Cu1I2bBTj1xuIHXLe/1zYLl3956Nbhg2MzSYHVfl9/rmanjbQIb7LibfCnug== "@types/node@^12.11.7": version "12.12.14" @@ -825,11 +852,6 @@ array-each@^1.0.0, array-each@^1.0.1: resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" integrity sha1-p5SvDAWrF1KEbudTofIRoFugxE8= -array-find-index@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" - integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= - array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -1200,6 +1222,11 @@ boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +boolean@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.0.tgz#fab78d5907dbae6216ab46d32733bb7b76b99e76" + integrity sha512-OElxJ1lUSinuoUnkpOgLmxp0DC4ytEhODEL6QJU0NpxE/mI4rUSh8h1P1Wkvfi3xQEBcxXR2gBIPNYNuaFcAbQ== + boom@2.x.x: version "2.10.1" resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" @@ -1444,24 +1471,24 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^3.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^1.0.2" + callsites@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.0.0.tgz#fb7eb569b72ad7a45812f93fd9430a3e410b3dd3" integrity sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw== -camelcase-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" - integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= - dependencies: - camelcase "^2.0.0" - map-obj "^1.0.0" - -camelcase@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" - integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= - camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" @@ -1720,6 +1747,13 @@ clone-buffer@^1.0.0: resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= +clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + dependencies: + mimic-response "^1.0.0" + clone-stats@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" @@ -1947,7 +1981,7 @@ concat-with-sourcemaps@^1.0.0: dependencies: source-map "^0.5.1" -config-chain@^1.1.12: +config-chain@^1.1.11, config-chain@^1.1.12: version "1.1.12" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== @@ -2051,6 +2085,11 @@ copy-webpack-plugin@^4.5.2: p-limit "^1.0.0" serialize-javascript "^1.4.0" +core-js@^3.4.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.1.tgz#39d5e2e346258cc01eb7d44345b1c3c014ca3f05" + integrity sha512-186WjSik2iTGfDjfdCZAxv2ormxtKgemjC3SI6PL31qOA0j5LhTDVjHChccoc7brwLvpvLPiMyRlcO88C4l1QQ== + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -2235,13 +2274,6 @@ cuint@^0.2.1: resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b" integrity sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs= -currently-unhandled@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" - integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= - dependencies: - array-find-index "^1.0.1" - cyclist@~0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" @@ -2278,7 +2310,7 @@ debug@2.2.0: dependencies: ms "0.7.1" -debug@2.6.9, debug@^2.1.2, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3: +debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -2292,7 +2324,7 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@^3.0.0, debug@^3.1.0: +debug@^3.1.0: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -2363,7 +2395,12 @@ default-resolution@^2.0.0: resolved "https://registry.yarnpkg.com/default-resolution/-/default-resolution-2.0.0.tgz#bcb82baa72ad79b426a76732f1a81ad6df26d684" integrity sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ= -define-properties@^1.1.2: +defer-to-connect@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.1.tgz#88ae694b93f67b81815a2c8c769aef6574ac8f2f" + integrity sha512-J7thop4u3mRTkYRQ+Vpfwy2G5Ehoy82I14+14W4YMDLKdWloI9gSzRbV30s/NckQGVJtPkWNcW4oMAUigTdqiQ== + +define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== @@ -2463,6 +2500,11 @@ detect-libc@^1.0.2, detect-libc@^1.0.3: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= +detect-node@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" + integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== + diagnostic-channel-publishers@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3" @@ -2562,6 +2604,11 @@ domutils@^1.5.1: dom-serializer "0" domelementtype "1" +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= + duplexer@^0.1.1, duplexer@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" @@ -2631,33 +2678,18 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-download@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-4.1.1.tgz#02e69556705cc456e520f9e035556ed5a015ebe8" - integrity sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg== - dependencies: - debug "^3.0.0" - env-paths "^1.0.0" - fs-extra "^4.0.1" - minimist "^1.2.0" - nugget "^2.0.1" - path-exists "^3.0.0" - rc "^1.2.1" - semver "^5.4.1" - sumchecker "^2.0.2" - electron-to-chromium@^1.2.7: version "1.3.27" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.27.tgz#78ecb8a399066187bb374eede35d9c70565a803d" integrity sha1-eOy4o5kGYYe7N07t412ccFZagD0= -electron@6.1.6: - version "6.1.6" - resolved "https://registry.yarnpkg.com/electron/-/electron-6.1.6.tgz#d63ea9c89b85b981a29eb3088bf391bf52bd8d73" - integrity sha512-4c4GiFTbWY2Mgv20HB4Bfhf1hDKb0MWgC35wkwNepNom1ioWfumocPHZrSs1xNAEe+tOmezY6lq74n3LbwTnVQ== +electron@7.1.7: + version "7.1.7" + resolved "https://registry.yarnpkg.com/electron/-/electron-7.1.7.tgz#520e2bc422e3dfd4bae166dd3be62101f2cbdc52" + integrity sha512-aCLJ4BJwnvOckJgovNul22AYlMFDzm4S4KqKCG2iBlFJyMHBxXAKFKMsgYd40LBZWS3hcY6RHpaYjHSAPLS1pw== dependencies: - "@types/node" "^10.12.18" - electron-download "^4.1.0" + "@electron/get" "^1.0.1" + "@types/node" "^12.0.12" extract-zip "^1.0.3" elliptic@^6.0.0: @@ -2695,6 +2727,11 @@ emojis-list@^2.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= +encodeurl@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + encodeurl@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" @@ -2721,10 +2758,10 @@ entities@^1.1.1, entities@~1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" integrity sha1-blwtClYhtdra7O+AuQ7ftc13cvA= -env-paths@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0" - integrity sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA= +env-paths@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" + integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== errno@^0.1.3, errno@~0.1.7: version "0.1.7" @@ -2748,6 +2785,11 @@ es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: es6-iterator "~2.0.1" es6-symbol "~3.1.1" +es6-error@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" + integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== + es6-iterator@^2.0.1, es6-iterator@~2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" @@ -2802,6 +2844,11 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.3, escape-string-regexp@^ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + eslint-plugin-jsdoc@^19.1.0: version "19.1.0" resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-19.1.0.tgz#fcc17f0378fdd6ee1c847a79b7211745cb05d014" @@ -3562,15 +3609,6 @@ fs-extra@0.26.7: path-is-absolute "^1.0.0" rimraf "^2.2.8" -fs-extra@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" - integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg== - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - fs-extra@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" @@ -3580,6 +3618,15 @@ fs-extra@^7.0.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-minipass@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" @@ -3687,18 +3734,20 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-stdin@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" - integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= - -get-stream@^4.0.0: +get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== dependencies: pump "^3.0.0" +get-stream@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" + integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== + dependencies: + pump "^3.0.0" + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -3864,6 +3913,19 @@ glob@^7.1.4, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" +global-agent@^2.0.2: + version "2.1.7" + resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-2.1.7.tgz#12d7bc2b07cd862d0fa76b0f1b2c48cd5ffcf150" + integrity sha512-ooK7eqGYZku+LgnbfH/Iv0RJ74XfhrBZDlke1QSzcBt0bw1PmJcnRADPAQuFE+R45pKKDTynAr25SBasY2kvow== + dependencies: + boolean "^3.0.0" + core-js "^3.4.1" + es6-error "^4.1.1" + matcher "^2.0.0" + roarr "^2.14.5" + semver "^6.3.0" + serialize-error "^5.0.0" + global-modules@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" @@ -3900,6 +3962,16 @@ global-prefix@^3.0.0: kind-of "^6.0.2" which "^1.3.1" +global-tunnel-ng@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz#d03b5102dfde3a69914f5ee7d86761ca35d57d8f" + integrity sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg== + dependencies: + encodeurl "^1.0.2" + lodash "^4.17.10" + npm-conf "^1.1.3" + tunnel "^0.0.6" + globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -3917,6 +3989,13 @@ globals@^12.1.0: dependencies: type-fest "^0.8.1" +globalthis@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.1.tgz#40116f5d9c071f9e8fb0037654df1ab3a83b7ef9" + integrity sha512-mJPRTc/P39NH/iNG4mXa9aIhNymaQikTrnspeCa2ZuJ+mH2QN/rXwtX3XwKrHqWgUQFbNZKtHM105aHzJalElw== + dependencies: + define-properties "^1.1.3" + globby@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" @@ -3948,11 +4027,33 @@ glogg@^1.0.0: dependencies: sparkles "^1.0.0" +got@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" + graceful-fs@4.1.11, graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= +graceful-fs@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + growl@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" @@ -4416,6 +4517,11 @@ html-comment-regex@^1.1.0: inherits "^2.0.1" readable-stream "^2.0.2" +http-cache-semantics@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz#495704773277eeef6e43f9ab2c2c7d259dda25c5" + integrity sha512-TcIMG3qeVLgDr1TEd2XvHaTnMPwYQUQMIBLy+5pLSDKYFc7UIqj39w8EGzZkaxoLv/l2K8HaI0t5AVA+YYgUew== + http-errors@1.6.2, http-errors@~1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" @@ -4549,13 +4655,6 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= -indent-string@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" - integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= - dependencies: - repeating "^2.0.0" - indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" @@ -4811,13 +4910,6 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-finite@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" - integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko= - dependencies: - number-is-nan "^1.0.0" - is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" @@ -5214,6 +5306,11 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= + json-edm-parser@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/json-edm-parser/-/json-edm-parser-0.1.2.tgz#1e60b0fef1bc0af67bc0d146dfdde5486cd615b4" @@ -5253,7 +5350,7 @@ json-stable-stringify@^1.0.0: dependencies: jsonify "~0.0.0" -json-stringify-safe@~5.0.1: +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= @@ -5322,6 +5419,13 @@ keytar@^4.11.0: nan "2.14.0" prebuild-install "5.3.0" +keyv@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== + dependencies: + json-buffer "3.0.0" + kind-of@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44" @@ -5595,13 +5699,15 @@ long@^3.2.0: resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b" integrity sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s= -loud-rejection@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" - integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= - dependencies: - currently-unhandled "^0.4.1" - signal-exit "^3.0.0" +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== lru-cache@2: version "2.7.3" @@ -5668,11 +5774,6 @@ map-cache@^0.2.0, map-cache@^0.2.2: resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= -map-obj@^1.0.0, map-obj@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" - integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= - map-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.0.7.tgz#8a1f07896d82b10926bd3744a2420009f88974a8" @@ -5711,6 +5812,13 @@ matchdep@^2.0.0: resolve "^1.4.0" stack-trace "0.0.10" +matcher@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/matcher/-/matcher-2.1.0.tgz#64e1041c15b993e23b786f93320a7474bf833c28" + integrity sha512-o+nZr+vtJtgPNklyeUKkkH42OsK8WAfdgaJE2FNxcjLPg+5QbeEoT6vRj8Xq/iv18JlQ9cmKsEu0b94ixWf1YQ== + dependencies: + escape-string-regexp "^2.0.0" + math-expression-evaluator@^1.2.14: version "1.2.17" resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac" @@ -5760,22 +5868,6 @@ memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" -meow@^3.1.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" - integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= - dependencies: - camelcase-keys "^2.0.0" - decamelize "^1.1.2" - loud-rejection "^1.0.0" - map-obj "^1.0.1" - minimist "^1.1.3" - normalize-package-data "^2.3.4" - object-assign "^4.0.1" - read-pkg-up "^1.0.1" - redent "^1.0.0" - trim-newlines "^1.0.0" - merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -5895,6 +5987,11 @@ mimic-response@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e" integrity sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4= +mimic-response@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -5925,7 +6022,7 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0: +minimist@1.2.0, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= @@ -6269,16 +6366,6 @@ normalize-package-data@^2.3.2: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-package-data@^2.3.4: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - normalize-path@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-1.0.0.tgz#32d0e472f91ff345701c15a8311018d3b0a90379" @@ -6316,6 +6403,11 @@ normalize-url@^1.4.0: query-string "^4.1.0" sort-keys "^1.0.0" +normalize-url@^4.1.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" + integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== + now-and-later@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.0.tgz#bc61cbb456d79cb32207ce47ca05136ff2e7d6ee" @@ -6328,6 +6420,14 @@ npm-bundled@^1.0.1: resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.3.tgz#7e71703d973af3370a9591bafe3a63aca0be2308" integrity sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow== +npm-conf@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" + integrity sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw== + dependencies: + config-chain "^1.1.11" + pify "^3.0.0" + npm-packlist@^1.1.6: version "1.1.11" resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.11.tgz#84e8c683cbe7867d34b1d357d893ce29e28a02de" @@ -6360,19 +6460,6 @@ nth-check@~1.0.1: dependencies: boolbase "~1.0.0" -nugget@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/nugget/-/nugget-2.0.1.tgz#201095a487e1ad36081b3432fa3cada4f8d071b0" - integrity sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA= - dependencies: - debug "^2.1.3" - minimist "^1.1.0" - pretty-bytes "^1.0.2" - progress-stream "^1.1.0" - request "^2.45.0" - single-line-log "^1.1.2" - throttleit "0.0.2" - num2fraction@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" @@ -6640,6 +6727,11 @@ p-all@^1.0.0: dependencies: p-map "^1.0.0" +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== + p-defer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" @@ -7263,19 +7355,16 @@ prepend-http@^1.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= + preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= -pretty-bytes@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84" - integrity sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ= - dependencies: - get-stdin "^4.0.1" - meow "^3.1.0" - pretty-hrtime@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" @@ -7304,14 +7393,6 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= -progress-stream@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/progress-stream/-/progress-stream-1.2.0.tgz#2cd3cfea33ba3a89c9c121ec3347abe9ab125f77" - integrity sha1-LNPP6jO6OonJwSHsM0er6asSX3c= - dependencies: - speedometer "~0.1.2" - through2 "~0.2.3" - progress@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" @@ -7519,7 +7600,7 @@ raw-body@2.3.2: iconv-lite "0.4.19" unpipe "1.0.0" -rc@^1.2.1, rc@^1.2.7: +rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -7595,7 +7676,7 @@ read@^1.0.7: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^1.1.8, readable-stream@~1.1.9: +readable-stream@^1.1.8: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= @@ -7663,14 +7744,6 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" -redent@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" - integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= - dependencies: - indent-string "^2.1.0" - strip-indent "^1.0.1" - reduce-css-calc@^1.2.6: version "1.3.0" resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" @@ -7750,13 +7823,6 @@ repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= -repeating@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" - integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= - dependencies: - is-finite "^1.0.0" - replace-ext@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" @@ -7839,7 +7905,7 @@ request@2.79.0: tunnel-agent "^0.6.0" uuid "^3.1.0" -request@^2.45.0, request@^2.86.0, request@^2.88.0: +request@^2.86.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== @@ -7933,13 +7999,6 @@ resolve@^1.1.6, resolve@^1.1.7: dependencies: path-parse "^1.0.5" -resolve@^1.10.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.13.1.tgz#be0aa4c06acd53083505abb35f4d66932ab35d16" - integrity sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w== - dependencies: - path-parse "^1.0.6" - resolve@^1.4.0: version "1.10.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" @@ -7947,6 +8006,13 @@ resolve@^1.4.0: dependencies: path-parse "^1.0.6" +responselike@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= + dependencies: + lowercase-keys "^1.0.0" + restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -8002,6 +8068,18 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +roarr@^2.14.5: + version "2.14.6" + resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.14.6.tgz#cebe8ad7ecbfd15bfa37b02dacf00809dd633912" + integrity sha512-qjbw0BEesKA+3XFBPt+KVe1PC/Z6ShfJ4wPlx2XifqH5h2Lj8/KQT5XJTsy3n1Es5kai+BwKALaECW3F70B1cg== + dependencies: + boolean "^3.0.0" + detect-node "^2.0.4" + globalthis "^1.0.0" + json-stringify-safe "^5.0.1" + semver-compare "^1.0.0" + sprintf-js "^1.1.2" + run-async@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" @@ -8062,6 +8140,13 @@ samsam@~1.1: resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.3.tgz#9f5087419b4d091f232571e7fa52e90b0f552621" integrity sha1-n1CHQZtNCR8jJXHn+lLpCw9VJiE= +sanitize-filename@^1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.3.tgz#755ebd752045931977e30b2025d340d7c9090378" + integrity sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg== + dependencies: + truncate-utf8-bytes "^1.0.0" + sax@0.5.x: version "0.5.8" resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" @@ -8080,6 +8165,11 @@ schema-utils@^0.4.4, schema-utils@^0.4.5: ajv "^6.1.0" ajv-keywords "^3.1.0" +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= + semver-greatest-satisfied-range@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b" @@ -8141,6 +8231,13 @@ send@0.16.1: range-parser "~1.2.0" statuses "~1.3.1" +serialize-error@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-5.0.0.tgz#a7ebbcdb03a5d71a6ed8461ffe0fc1a1afed62ac" + integrity sha512-/VtpuyzYf82mHYTtI4QKtwHa79vAdU5OQpNPAmE/0UDdlGT0ZxHwC+J6gXkw29wwoVI8fMPsfcVHOwXtUQYYQA== + dependencies: + type-fest "^0.8.0" + serialize-javascript@^1.4.0: version "1.5.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.5.0.tgz#1aa336162c88a890ddad5384baebc93a655161fe" @@ -8250,13 +8347,6 @@ simple-get@^2.7.0: once "^1.3.1" simple-concat "^1.0.0" -single-line-log@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/single-line-log/-/single-line-log-1.1.2.tgz#c2f83f273a3e1a16edb0995661da0ed5ef033364" - integrity sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q= - dependencies: - string-width "^1.0.1" - sinon@^1.17.2: version "1.17.7" resolved "https://registry.yarnpkg.com/sinon/-/sinon-1.17.7.tgz#4542a4f49ba0c45c05eb2e9dd9d203e2b8efe0bf" @@ -8440,11 +8530,6 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== -speedometer@~0.1.2: - version "0.1.4" - resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d" - integrity sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0= - split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" @@ -8466,6 +8551,11 @@ split@^1.0.1: dependencies: through "2" +sprintf-js@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" + integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -8711,13 +8801,6 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= -strip-indent@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" - integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= - dependencies: - get-stdin "^4.0.1" - strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -8733,12 +8816,12 @@ sudo-prompt@9.1.1: resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.1.1.tgz#73853d729770392caec029e2470db9c221754db0" integrity sha512-es33J1g2HjMpyAhz8lOR+ICmXXAqTuKbuXuUWLhOLew20oN9oUCgCJx615U/v7aioZg7IX5lIh9x34vwneu4pA== -sumchecker@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-2.0.2.tgz#0f42c10e5d05da5d42eea3e56c3399a37d6c5b3e" - integrity sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4= +sumchecker@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" + integrity sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg== dependencies: - debug "^2.2.0" + debug "^4.1.0" supports-color@1.2.0: version "1.2.0" @@ -8887,11 +8970,6 @@ textextensions@~1.0.0: resolved "https://registry.yarnpkg.com/textextensions/-/textextensions-1.0.2.tgz#65486393ee1f2bb039a60cbba05b0b68bd9501d2" integrity sha1-ZUhjk+4fK7A5pgy7oFsLaL2VAdI= -throttleit@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf" - integrity sha1-z+34jmDADdlpe2H90qg0OptoDq8= - through2-filter@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec" @@ -8924,14 +9002,6 @@ through2@^3.0.0: readable-stream "2 || 3" xtend "~4.0.1" -through2@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/through2/-/through2-0.2.3.tgz#eb3284da4ea311b6cc8ace3653748a52abf25a3f" - integrity sha1-6zKE2k6jEbbMis42U3SKUqvyWj8= - dependencies: - readable-stream "~1.1.9" - xtend "~2.1.1" - through2@~0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/through2/-/through2-0.4.2.tgz#dbf5866031151ec8352bb6c4db64a2292a840b9b" @@ -9020,6 +9090,11 @@ to-object-path@^0.3.0: dependencies: kind-of "^3.0.2" +to-readable-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" @@ -9079,16 +9154,18 @@ tough-cookie@~2.4.3: resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= -trim-newlines@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" - integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= - trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= +truncate-utf8-bytes@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b" + integrity sha1-QFkjkJWS1W94pYGENLC3hInKXys= + dependencies: + utf8-byte-length "^1.0.1" + ts-loader@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-4.4.2.tgz#778d4464b24436873c78f7f9e914d88194c2a248" @@ -9134,6 +9211,11 @@ tunnel@0.0.4: resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.4.tgz#2d3785a158c174c9a16dc2c046ec5fc5f1742213" integrity sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM= +tunnel@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" + integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -9146,7 +9228,7 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-fest@^0.8.1: +type-fest@^0.8.0, type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== @@ -9349,6 +9431,13 @@ url-join@^1.1.0: resolved "https://registry.yarnpkg.com/url-join/-/url-join-1.1.0.tgz#741c6c2f4596c4830d6718460920d0c92202dc78" integrity sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg= +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= + dependencies: + prepend-http "^2.0.0" + url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -9362,6 +9451,11 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== +utf8-byte-length@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61" + integrity sha1-9F8VDExm7uloGGUFq5P8u4rWv2E= + util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" From 816064e3b334e200965864fd67caef6a6a4dd8fb Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 9 Jan 2020 15:08:27 +0100 Subject: [PATCH 100/315] Use arrow instead of 'to' in forwarded ports view Part of #86066 --- src/vs/workbench/contrib/remote/browser/tunnelView.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 440a7ee8000c5..ec5fedf10f9e1 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -364,9 +364,9 @@ class TunnelItem implements ITunnelItem { if (this.name) { return nls.localize('remote.tunnelsView.forwardedPortLabel0', "{0}", this.name); } else if (this.localAddress && (this.remoteHost !== 'localhost')) { - return nls.localize('remote.tunnelsView.forwardedPortLabel2', "{0}:{1} to {2}", this.remoteHost, this.remotePort, this.localAddress); + return nls.localize('remote.tunnelsView.forwardedPortLabel2', "{0}:{1} \u2192 {2}", this.remoteHost, this.remotePort, this.localAddress); } else if (this.localAddress) { - return nls.localize('remote.tunnelsView.forwardedPortLabel3', "{0} to {1}", this.remotePort, this.localAddress); + return nls.localize('remote.tunnelsView.forwardedPortLabel3', "{0} \u2192 {1}", this.remotePort, this.localAddress); } else if (this.remoteHost !== 'localhost') { return nls.localize('remote.tunnelsView.forwardedPortLabel4', "{0}:{1} not forwarded", this.remoteHost, this.remotePort); } else { From a66c1f592dce65c3863cb09aff605ff2aabc8d03 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 9 Jan 2020 15:25:57 +0100 Subject: [PATCH 101/315] Error while computing semantic tokens. Fixes #88366 --- .../server/src/modes/javascriptSemanticTokens.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts index fa615319354ef..7e4deafa262e7 100644 --- a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts +++ b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts @@ -20,7 +20,7 @@ export function getSemanticTokens(jsLanguageService: ts.LanguageService, current if (node.kind === ts.SyntaxKind.Identifier) { const symbol = typeChecker.getSymbolAtLocation(node); if (symbol) { - const decl = symbol.valueDeclaration || symbol.declarations[0]; + const decl = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0]; if (decl) { let typeIdx = tokenFromDeclarationMapping[decl.kind]; let modifierSet = 0; From c40b6072bc10cb795a67f112feaa6d2bff6d7fb7 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 9 Jan 2020 15:50:21 +0100 Subject: [PATCH 102/315] propose CompletionList#isDetailsResolved, #39441 --- src/vs/editor/common/modes.ts | 1 + src/vs/editor/contrib/suggest/suggest.ts | 5 +++++ src/vs/monaco.d.ts | 1 + src/vs/vscode.proposed.d.ts | 11 +++++++++++ .../api/browser/mainThreadLanguageFeatures.ts | 1 + src/vs/workbench/api/common/extHost.protocol.ts | 1 + .../workbench/api/common/extHostLanguageFeatures.ts | 3 ++- src/vs/workbench/api/common/extHostTypes.ts | 2 +- 8 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 9db95948d2430..78495217dc32b 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -481,6 +481,7 @@ export interface CompletionItem { export interface CompletionList { suggestions: CompletionItem[]; incomplete?: boolean; + isDetailsResolved?: boolean; dispose?(): void; } diff --git a/src/vs/editor/contrib/suggest/suggest.ts b/src/vs/editor/contrib/suggest/suggest.ts index d28423e1602f1..c2d3185ce7d52 100644 --- a/src/vs/editor/contrib/suggest/suggest.ts +++ b/src/vs/editor/contrib/suggest/suggest.ts @@ -47,6 +47,9 @@ export class CompletionItem { idx?: number; word?: string; + // + readonly isDetailsResolved: boolean; + constructor( readonly position: IPosition, readonly completion: modes.CompletionItem, @@ -70,6 +73,8 @@ export class CompletionItem { this.editReplaceEnd = new Position(completion.range.replace.endLineNumber, completion.range.replace.endColumn); } + this.isDetailsResolved = container.isDetailsResolved || typeof provider.resolveCompletionItem === 'undefined'; + // create the suggestion resolver const { resolveCompletionItem } = provider; if (typeof resolveCompletionItem !== 'function') { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 32a3f131d6bfe..38597bc5d3b76 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4854,6 +4854,7 @@ declare namespace monaco.languages { export interface CompletionList { suggestions: CompletionItem[]; incomplete?: boolean; + isDetailsResolved?: boolean; dispose?(): void; } diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 15d667e14fc66..27a17629b1339 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1504,4 +1504,15 @@ declare module 'vscode' { export const onDidChangeActiveColorTheme: Event; } + //#endregion + + + //#region https://github.com/microsoft/vscode/issues/39441 + + export interface CompletionList { + isDetailsResolved?: boolean; + } + + //#endregion + } diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 0ae9f98745040..da77e770f4e09 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -367,6 +367,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return { suggestions: result.b.map(d => MainThreadLanguageFeatures._inflateSuggestDto(result.a, d)), incomplete: result.c, + isDetailsResolved: result.d, dispose: () => typeof result.x === 'number' && this._proxy.$releaseCompletionItems(handle, result.x) }; }); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 076f847e2f6b8..227a885f876f5 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1012,6 +1012,7 @@ export interface ISuggestResultDto { a: { insert: IRange, replace: IRange; }; b: ISuggestDataDto[]; c?: boolean; + d?: boolean; } export interface ISignatureHelpDto { diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index ba0f638fefdf8..737ed4be71402 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -787,7 +787,8 @@ class SuggestAdapter { x: pid, b: [], a: { replace: typeConvert.Range.from(replaceRange), insert: typeConvert.Range.from(insertRange) }, - c: list.isIncomplete || undefined + c: list.isIncomplete || undefined, + d: list.isDetailsResolved || undefined }; for (let i = 0; i < list.items.length; i++) { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 19819497a1e2d..5239da9499b21 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -1395,7 +1395,7 @@ export class CompletionItem implements vscode.CompletionItem { export class CompletionList { isIncomplete?: boolean; - + isDetailsResolved?: boolean; items: vscode.CompletionItem[]; constructor(items: vscode.CompletionItem[] = [], isIncomplete: boolean = false) { From c7bd9c76f0bbf86594827e7bf034c2c384605727 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 9 Jan 2020 16:03:09 +0100 Subject: [PATCH 103/315] Better ipv6 display in forwarded ports view --- .../workbench/api/node/extHostTunnelService.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts index ef2679b4f9d74..a593299072661 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -190,10 +190,19 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe private parseIpAddress(hex: string): string { let result = ''; - for (let i = hex.length - 2; (i >= 0); i -= 2) { - result += parseInt(hex.substr(i, 2), 16); - if (i !== 0) { - result += '.'; + if (hex.length === 8) { + for (let i = hex.length - 2; i >= 0; i -= 2) { + result += parseInt(hex.substr(i, 2), 16); + if (i !== 0) { + result += '.'; + } + } + } else { + for (let i = hex.length - 4; i >= 0; i -= 4) { + result += parseInt(hex.substr(i, 4), 16).toString(16); + if (i !== 0) { + result += ':'; + } } } return result; From 99f0da6e5418879a5221099a164e96243709ede8 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 9 Jan 2020 16:11:24 +0100 Subject: [PATCH 104/315] adopt isDetailsResolved for snippets, #39441 --- .../contrib/snippets/browser/snippetCompletionProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts index 96d9f026104d7..e30853b4c2c58 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts @@ -146,7 +146,7 @@ export class SnippetCompletionProvider implements CompletionItemProvider { i = to; } } - return { suggestions }; + return { suggestions, isDetailsResolved: true }; }); } From 4d9cbf3bde1e1ea570d65b2b7414199833f8b61a Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Thu, 9 Jan 2020 16:22:59 +0100 Subject: [PATCH 105/315] wip --- .../characterHardWrappingLineMapper.ts | 153 ++++++++++-------- .../characterHardWrappingLineMapper.test.ts | 7 + 2 files changed, 90 insertions(+), 70 deletions(-) diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index 0790c9a644401..32b607c356e29 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -111,31 +111,40 @@ function createLineMapping(classifier: WrappingCharacterClassifier, previousBrea const prevBreakingOffsetsVisibleColumn = previousBreakingData.breakingOffsetsVisibleColumn; let breakingColumn = firstLineBreakingColumn; + let lastBreakingOffsetVisibleColumn = 0; const prevLen = prevBreakingOffsets.length; let prevIndex = 0; - while (prevIndex < prevLen) { - // Allow for prevIndex to be -1 (for the case where we hit a tab when walking backwards from the first break) - let breakOffset = prevIndex < 0 ? 0 : prevBreakingOffsets[prevIndex]; - let breakOffsetVisibleColumn = prevIndex < 0 ? 0 : prevBreakingOffsetsVisibleColumn[prevIndex]; - - if (breakOffsetVisibleColumn === breakingColumn) { - // perfect fit, nothing to do - breakingOffsets[breakingOffsetsCount] = breakOffset; - breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn; - breakingOffsetsCount++; - breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakingColumn; + if (prevIndex >= 0) { + let currentDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex] - breakingColumn); + while (prevIndex + 1 < prevLen) { + const potentialDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex + 1] - breakingColumn); + if (potentialDiff >= currentDiff) { + break; + } + currentDiff = potentialDiff; prevIndex++; - } else if (breakOffsetVisibleColumn < breakingColumn) { - // try to add more characters - const initialBreakOffset = breakOffset; - let visibleColumn = breakOffsetVisibleColumn; - breakOffset = 0; + } + } + + while (prevIndex < prevLen) { + // Allow for prevIndex to be -1 (for the case where we hit a tab when walking backwards from the first break) + const prevBreakOffset = prevIndex < 0 ? 0 : prevBreakingOffsets[prevIndex]; + const prevBreakoffsetVisibleColumn = prevIndex < 0 ? 0 : prevBreakingOffsetsVisibleColumn[prevIndex];; - let prevCharCode = lineText.charCodeAt(initialBreakOffset - 1); + let breakOffset = 0; + let breakOffsetVisibleColumn = 0; + + let forcedBreakOffset = 0; + let forcedBreakOffsetVisibleColumn = 0; + + // initially, we search as much as possible to the right (if it fits) + if (prevBreakoffsetVisibleColumn <= breakingColumn) { + let visibleColumn = prevBreakoffsetVisibleColumn; + let prevCharCode = lineText.charCodeAt(prevBreakOffset - 1); let prevCharCodeClass = classifier.get(prevCharCode); - let mustBreak = false; - for (let i = initialBreakOffset; i < len; i++) { + let entireLineFits = true; + for (let i = prevBreakOffset; i < len; i++) { const charCode = lineText.charCodeAt(i); const charCodeClass = classifier.get(charCode); @@ -145,6 +154,7 @@ function createLineMapping(classifier: WrappingCharacterClassifier, previousBrea prevCharCode = charCode; prevCharCodeClass = charCodeClass; continue; + } if (canBreak(prevCharCodeClass, charCodeClass)) { @@ -157,14 +167,15 @@ function createLineMapping(classifier: WrappingCharacterClassifier, previousBrea if (visibleColumn > breakingColumn) { // We need to break at least before character at `i`: + forcedBreakOffset = i; + forcedBreakOffsetVisibleColumn = visibleColumn - charWidth; - if (breakOffset === 0 || visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakingColumn) { - // Cannot break at `breakOffset`, must break at `i` - breakOffset = i; - breakOffsetVisibleColumn = visibleColumn - charWidth; + if (visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakingColumn) { + // Cannot break at `breakOffset` => reset it if it was set + breakOffset = 0; } - mustBreak = true; + entireLineFits = false; break; } @@ -172,33 +183,24 @@ function createLineMapping(classifier: WrappingCharacterClassifier, previousBrea prevCharCodeClass = charCodeClass; } - if (!mustBreak) { + if (entireLineFits) { // there is no more need to break => stop the outer loop! // Add last segment breakingOffsets[breakingOffsetsCount] = prevBreakingOffsets[prevBreakingOffsets.length - 1]; breakingOffsetsVisibleColumn[breakingOffsetsCount] = prevBreakingOffsetsVisibleColumn[prevBreakingOffsets.length - 1]; break; } + } - breakingOffsets[breakingOffsetsCount] = breakOffset; - breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn; - breakingOffsetsCount++; - breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakingColumn; - prevIndex++; - } else if (breakOffsetVisibleColumn > breakingColumn) { - const initialBreakOffset = breakOffset; - let visibleColumn = breakOffsetVisibleColumn; - breakOffset = 0; - - let charCode = lineText.charCodeAt(initialBreakOffset); + if (breakOffset === 0) { + // must search left + let visibleColumn = prevBreakoffsetVisibleColumn; + let charCode = lineText.charCodeAt(prevBreakOffset); let charCodeClass = classifier.get(charCode); - let hitTab = false; - - let firstValidBreakOffset = 0; - let firstValidBreakOffsetVisibleColumn = 0; - for (let i = initialBreakOffset - 1; i >= 0; i--) { - let prevCharCode = lineText.charCodeAt(i); - let prevCharCodeClass = classifier.get(prevCharCode); + let hitATabCharacter = false; + for (let i = prevBreakOffset - 1; i >= 0; i--) { + const prevCharCode = lineText.charCodeAt(i); + const prevCharCodeClass = classifier.get(prevCharCode); if (strings.isHighSurrogate(prevCharCode)) { // A surrogate pair must always be considered as a single unit, so it is never to be broken @@ -210,16 +212,16 @@ function createLineMapping(classifier: WrappingCharacterClassifier, previousBrea if (prevCharCode === CharCode.Tab) { // cannot determine the width of a tab when going backwards, so we must go forwards - hitTab = true; + hitATabCharacter = true; break; } const charWidth = (strings.isFullWidthCharacter(prevCharCode) ? columnsForFullWidthChar : 1); if (visibleColumn <= breakingColumn) { - if (firstValidBreakOffset === 0) { - firstValidBreakOffset = i + 1; - firstValidBreakOffsetVisibleColumn = visibleColumn; + if (forcedBreakOffset === 0) { + forcedBreakOffset = i + 1; + forcedBreakOffsetVisibleColumn = visibleColumn; } if (visibleColumn <= breakingColumn - wrappedLineBreakingColumn) { @@ -239,36 +241,37 @@ function createLineMapping(classifier: WrappingCharacterClassifier, previousBrea charCodeClass = prevCharCodeClass; } - if (hitTab) { - // cannot determine the width of a tab when going backwards, so we must go forwards + if (hitATabCharacter) { + // cannot determine the width of a tab when going backwards, so we must go forwards from the previous break prevIndex--; continue; } + } - if (breakOffset === 0) { - // Could not find a good breaking point - breakOffset = firstValidBreakOffset; - breakOffsetVisibleColumn = firstValidBreakOffsetVisibleColumn; - } + if (breakOffset === 0) { + // Could not find a good breaking point + breakOffset = forcedBreakOffset; + breakOffsetVisibleColumn = forcedBreakOffsetVisibleColumn; + } + + breakingOffsets[breakingOffsetsCount] = breakOffset; + breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn; + breakingOffsetsCount++; + breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakingColumn; - breakingOffsets[breakingOffsetsCount] = breakOffset; - breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn; - breakingOffsetsCount++; - breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakingColumn; + while (prevIndex < 0 || (prevIndex < prevLen && prevBreakingOffsetsVisibleColumn[prevIndex] < breakOffsetVisibleColumn)) { + prevIndex++; } - if (prevIndex < 0) { - prevIndex = 0; - } else { - let currentDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex] - breakingColumn); - while (prevIndex + 1 < prevLen) { - const potentialDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex + 1] - breakingColumn); - if (potentialDiff >= currentDiff) { - break; - } - currentDiff = potentialDiff; - prevIndex++; + let currentDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex] - breakingColumn); + // let lastBreakingColumn + while (prevIndex + 1 < prevLen) { + const potentialDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex + 1] - breakingColumn); + if (potentialDiff >= currentDiff) { + break; } + currentDiff = potentialDiff; + prevIndex++; } } @@ -284,11 +287,19 @@ function createLineMapping(classifier: WrappingCharacterClassifier, previousBrea } catch (err) { console.log(`BREAKING!!`); console.log(err); + console.log(`previous breaks: ${JSON.stringify(prevBreakingOffsets)}, breakingOffsetsVisibleColumn: ${JSON.stringify(prevBreakingOffsetsVisibleColumn)}`); + console.log(`expected breakOffsets: ${JSON.stringify(expected?.breakOffsets)}, breakingOffsetsVisibleColumn: ${JSON.stringify(expected?.breakingOffsetsVisibleColumn)}`); + console.log(`actual breakOffsets: ${JSON.stringify(actual?.breakOffsets)}, breakingOffsetsVisibleColumn: ${JSON.stringify(actual?.breakingOffsetsVisibleColumn)}`); + + console.log(`actual str: ${toAnnotatedText(lineText, actual)}`); + + console.log(` assertIncrementalLineMapping( factory, ${str(lineText)}, 4, ${previousBreakingData.breakingColumn}, ${str(toAnnotatedText(lineText, previousBreakingData))}, - ${expected!.breakingColumn}, ${str(toAnnotatedText(lineText, expected))} + ${expected!.breakingColumn}, ${str(toAnnotatedText(lineText, expected))}, + WrappingIndent.${hardWrappingIndent === WrappingIndent.None ? 'None' : hardWrappingIndent === WrappingIndent.Same ? 'Same' : hardWrappingIndent === WrappingIndent.Indent ? 'Indent' : 'DeepIndent'} ); `); function str(strr: string) { @@ -381,6 +392,8 @@ function createLineMapping(classifier: WrappingCharacterClassifier, previousBrea return new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); } +function + // class BreakSearchResult { // public static INSTANCE = new BreakSearchResult(); diff --git a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts index 4890907d5f971..7d141bfdad0b8 100644 --- a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts +++ b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts @@ -147,6 +147,13 @@ suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { 57, 'An his legere persecuti, oblique delicata efficiantur ex |vix, vel at graecis officiis maluisset. Et per impedit |voluptua, usu discere maiorum at. Ut assum ornatus |temporibus vis, an sea melius pericula. Ea dicunt |oblique phaedrum nam, eu duo movet nobis. His melius |facilis eu, vim malorum temporibus ne. Nec no sale |regione, meliore civibus placerat id eam. Mea alii |fabulas definitionem te, agam volutpat ad vis, et per |bonorum nonumes repudiandae.', 58, 'An his legere persecuti, oblique delicata efficiantur ex |vix, vel at graecis officiis maluisset. Et per impedit |voluptua, usu discere maiorum at. Ut assum ornatus |temporibus vis, an sea melius pericula. Ea dicunt oblique |phaedrum nam, eu duo movet nobis. His melius facilis eu, |vim malorum temporibus ne. Nec no sale regione, meliore |civibus placerat id eam. Mea alii fabulas definitionem te,| agam volutpat ad vis, et per bonorum nonumes repudiandae.' ); + + assertIncrementalLineMapping( + factory, '\t\t"owner": "vscode",', 4, + 14, '\t\t"owner|": |"vscod|e",', + 16, '\t\t"owner":| |"vscode"|,', + WrappingIndent.Same + ); }); From 5c02e4b2a149c1e2e9bb03b267d3b6727607cf72 Mon Sep 17 00:00:00 2001 From: Jason Fields Date: Thu, 9 Jan 2020 10:38:28 -0500 Subject: [PATCH 106/315] Remove extraneous line from doc comment in vscode.d.ts (#88336) --- src/vs/vscode.d.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 0ea85bb7df2eb..eabfc93a66b4d 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -1193,7 +1193,6 @@ declare module 'vscode' { * A complex edit that will be applied in one transaction on a TextEditor. * This holds a description of the edits and if the edits are valid (i.e. no overlapping regions, document was not changed in the meantime, etc.) * they can be applied on a [document](#TextDocument) associated with a [text editor](#TextEditor). - * */ export interface TextEditorEdit { /** From b8a7184825f62443845c6850fc49a099bb5c6617 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 9 Jan 2020 17:36:45 +0100 Subject: [PATCH 107/315] use plugin --- .../src/modes/javascriptSemanticTokens.ts | 53 ++++++++++--------- .../typescript-language-features/package.json | 8 ++- ...{semanticColoring.ts => semanticTokens.ts} | 49 +++++++++++------ .../src/languageProvider.ts | 2 +- 4 files changed, 69 insertions(+), 43 deletions(-) rename extensions/typescript-language-features/src/features/{semanticColoring.ts => semanticTokens.ts} (81%) diff --git a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts index 7e4deafa262e7..9a1f394977a9a 100644 --- a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts +++ b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts @@ -55,35 +55,40 @@ export function getSemanticTokens(jsLanguageService: ts.LanguageService, current return resultTokens; } - -export function getSemanticTokenLegend() { - return { types: tokenTypes, modifiers: tokenModifiers }; +enum TokenType { + 'class', + 'enum', + 'interface', + 'namespace', + 'typeParameter', + 'type', + 'parameter', + 'variable', + 'property', + 'constant', + 'function', + 'member', + _sentinel } -const tokenTypes: string[] = ['class', 'enum', 'interface', 'namespace', 'typeParameter', 'type', 'parameter', 'variable', 'property', 'constant', 'function', 'member']; -const tokenModifiers: string[] = ['declaration', 'static', 'async']; - -const enum TokenType { - 'class' = 0, - 'enum' = 1, - 'interface' = 2, - 'namespace' = 3, - 'typeParameter' = 4, - 'type' = 5, - 'parameter' = 6, - 'variable' = 7, - 'property' = 8, - 'constant' = 9, - 'function' = 10, - 'member' = 11 +enum TokenModifier { + 'declaration', + 'static', + 'async', + _sentinel } - -const enum TokenModifier { - 'declaration' = 0x01, - 'static' = 0x02, - 'async' = 0x04, +export function getSemanticTokenLegend() { + const tokenTypes = []; + for (let i = 0; i < TokenType._sentinel; i++) { + tokenTypes.push(TokenType[i]); + } + const tokenModifiers = []; + for (let i = 0; i < TokenModifier._sentinel; i++) { + tokenModifiers.push(TokenModifier[i]); + } + return { types: tokenTypes, modifiers: tokenModifiers }; } const tokenFromDeclarationMapping: { [name: string]: TokenType } = { diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 577a4fd1686cf..ce453e58d9a66 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -20,7 +20,8 @@ "rimraf": "^2.6.3", "semver": "5.5.1", "vscode-extension-telemetry": "0.1.1", - "vscode-nls": "^4.0.0" + "vscode-nls": "^4.0.0", + "typescript-vscode-sh-plugin":"^0.1.0" }, "devDependencies": { "@types/node": "^12.11.7", @@ -953,6 +954,11 @@ } ] } + ], + "typescriptServerPlugins": [ + { + "name": "typescript-vscode-sh-plugin" + } ] } } diff --git a/extensions/typescript-language-features/src/features/semanticColoring.ts b/extensions/typescript-language-features/src/features/semanticTokens.ts similarity index 81% rename from extensions/typescript-language-features/src/features/semanticColoring.ts rename to extensions/typescript-language-features/src/features/semanticTokens.ts index 10a92c50bd2fb..cc9a8ba582aae 100644 --- a/extensions/typescript-language-features/src/features/semanticColoring.ts +++ b/extensions/typescript-language-features/src/features/semanticTokens.ts @@ -56,14 +56,19 @@ class SemanticTokensProvider implements vscode.SemanticTokensProvider { return null; } + const versionBeforeRequest = document.version; + + if (_options.ranges) { + + // const allArgs = _options.ranges.map(r => ({file, start: document.offsetAt(r.start), length: document.offsetAt(r.end) - document.offsetAt(r.start)})); + } + const args: ExperimentalProtocol.EncodedSemanticClassificationsRequestArgs = { file: file, start: 0, length: document.getText().length, }; - const versionBeforeRequest = document.version; - const response = await (this.client as ExperimentalProtocol.IExtendedTypeScriptServiceClient).execute('encodedSemanticClassifications-full', args, token); const versionAfterRequest = document.version; @@ -84,23 +89,33 @@ class SemanticTokensProvider implements vscode.SemanticTokensProvider { const tsTokens = response.body.spans; for (let i = 0, len = Math.floor(tsTokens.length / 3); i < len; i++) { - const tokenType = tokenTypeMap[tsTokens[3 * i + 2]]; - if (typeof tokenType === 'number') { - console.log(TokenType[tokenType]); - const offset = tsTokens[3 * i]; - const length = tsTokens[3 * i + 1]; - - // we can use the document's range conversion methods because - // the result is at the same version as the document - const startPos = document.positionAt(offset); - const endPos = document.positionAt(offset + length); - - for (let line = startPos.line; line <= endPos.line; line++) { - const startCharacter = (line === startPos.line ? startPos.character : 0); - const endCharacter = (line === endPos.line ? endPos.character : document.lineAt(line).text.length); - builder.push(line, startCharacter, endCharacter - startCharacter, tokenType, 0); + const tsClassification = tsTokens[3 * i + 2]; + let tokenType = 0; + let tokenModifiers = 0; + if (tsClassification > 0xFF) { + // classifications as returned by the typescript-vscode-sh-plugin + tokenType = (tsClassification >> 8) - 1; + tokenModifiers = tsClassification & 0xFF; + } else { + tokenType = tokenTypeMap[tsClassification]; + if (tokenType === undefined) { + continue; } } + + const offset = tsTokens[3 * i]; + const length = tsTokens[3 * i + 1]; + + // we can use the document's range conversion methods because + // the result is at the same version as the document + const startPos = document.positionAt(offset); + const endPos = document.positionAt(offset + length); + + for (let line = startPos.line; line <= endPos.line; line++) { + const startCharacter = (line === startPos.line ? startPos.character : 0); + const endCharacter = (line === endPos.line ? endPos.character : document.lineAt(line).text.length); + builder.push(line, startCharacter, endCharacter - startCharacter, tokenType, tokenModifiers); + } } return new vscode.SemanticTokens(builder.build()); diff --git a/extensions/typescript-language-features/src/languageProvider.ts b/extensions/typescript-language-features/src/languageProvider.ts index 692cee35cbbac..644eed4044196 100644 --- a/extensions/typescript-language-features/src/languageProvider.ts +++ b/extensions/typescript-language-features/src/languageProvider.ts @@ -78,7 +78,7 @@ export default class LanguageProvider extends Disposable { import('./features/signatureHelp').then(provider => this._register(provider.register(selector, this.client))), import('./features/tagClosing').then(provider => this._register(provider.register(selector, this.description.id, this.client))), import('./features/typeDefinitions').then(provider => this._register(provider.register(selector, this.client))), - import('./features/semanticColoring').then(provider => this._register(provider.register(selector, this.client))), + import('./features/semanticTokens').then(provider => this._register(provider.register(selector, this.client))), import('./features/callHierarchy').then(provider => this._register(provider.register(selector, this.client))), ]); } From 5b5f01d72943396359555a25096e54677d7902a6 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 9 Jan 2020 17:41:17 +0100 Subject: [PATCH 108/315] :lipstick: --- .../characterHardWrappingLineMapper.ts | 446 ++++++++---------- 1 file changed, 202 insertions(+), 244 deletions(-) diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index 32b607c356e29..91c4071798c08 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -81,7 +81,12 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor finalize: () => { let result: (LineBreakingData | null)[] = []; for (let i = 0, len = requests.length; i < len; i++) { - result[i] = createLineMapping(this.classifier, previousBreakingData[i], requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent); + const previousLineBreakingData = previousBreakingData[i]; + if (previousLineBreakingData) { + result[i] = createLineMappingFromPreviousLineMapping(this.classifier, previousLineBreakingData, requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent); + } else { + result[i] = createLineMapping(this.classifier, requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent); + } } return result; } @@ -89,7 +94,7 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor } } -function createLineMapping(classifier: WrappingCharacterClassifier, previousBreakingData: LineBreakingData | null, lineText: string, tabSize: number, firstLineBreakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): LineBreakingData | null { +function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterClassifier, previousBreakingData: LineBreakingData, lineText: string, tabSize: number, firstLineBreakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): LineBreakingData | null { if (firstLineBreakingColumn === -1) { return null; } @@ -106,232 +111,243 @@ function createLineMapping(classifier: WrappingCharacterClassifier, previousBrea let breakingOffsetsVisibleColumn: number[] = []; let breakingOffsetsCount: number = 0; - if (previousBreakingData/* && firstLineBreakingColumn >= 10 && Math.abs(previousBreakingData.breakingColumn - firstLineBreakingColumn) <= 3 */) { - const prevBreakingOffsets = previousBreakingData.breakOffsets; - const prevBreakingOffsetsVisibleColumn = previousBreakingData.breakingOffsetsVisibleColumn; - - let breakingColumn = firstLineBreakingColumn; - let lastBreakingOffsetVisibleColumn = 0; - const prevLen = prevBreakingOffsets.length; - let prevIndex = 0; + const prevBreakingOffsets = previousBreakingData.breakOffsets; + const prevBreakingOffsetsVisibleColumn = previousBreakingData.breakingOffsetsVisibleColumn; - if (prevIndex >= 0) { - let currentDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex] - breakingColumn); - while (prevIndex + 1 < prevLen) { - const potentialDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex + 1] - breakingColumn); - if (potentialDiff >= currentDiff) { - break; - } - currentDiff = potentialDiff; - prevIndex++; + let breakingColumn = firstLineBreakingColumn; + const prevLen = prevBreakingOffsets.length; + let prevIndex = 0; + + if (prevIndex >= 0) { + let currentDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex] - breakingColumn); + while (prevIndex + 1 < prevLen) { + const potentialDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex + 1] - breakingColumn); + if (potentialDiff >= currentDiff) { + break; } + currentDiff = potentialDiff; + prevIndex++; } + } - while (prevIndex < prevLen) { - // Allow for prevIndex to be -1 (for the case where we hit a tab when walking backwards from the first break) - const prevBreakOffset = prevIndex < 0 ? 0 : prevBreakingOffsets[prevIndex]; - const prevBreakoffsetVisibleColumn = prevIndex < 0 ? 0 : prevBreakingOffsetsVisibleColumn[prevIndex];; - - let breakOffset = 0; - let breakOffsetVisibleColumn = 0; - - let forcedBreakOffset = 0; - let forcedBreakOffsetVisibleColumn = 0; - - // initially, we search as much as possible to the right (if it fits) - if (prevBreakoffsetVisibleColumn <= breakingColumn) { - let visibleColumn = prevBreakoffsetVisibleColumn; - let prevCharCode = lineText.charCodeAt(prevBreakOffset - 1); - let prevCharCodeClass = classifier.get(prevCharCode); - let entireLineFits = true; - for (let i = prevBreakOffset; i < len; i++) { - const charCode = lineText.charCodeAt(i); - const charCodeClass = classifier.get(charCode); - - if (strings.isHighSurrogate(prevCharCode)) { - // A surrogate pair must always be considered as a single unit, so it is never to be broken - visibleColumn += 1; - prevCharCode = charCode; - prevCharCodeClass = charCodeClass; - continue; - - } + while (prevIndex < prevLen) { + // Allow for prevIndex to be -1 (for the case where we hit a tab when walking backwards from the first break) + const prevBreakOffset = prevIndex < 0 ? 0 : prevBreakingOffsets[prevIndex]; + const prevBreakoffsetVisibleColumn = prevIndex < 0 ? 0 : prevBreakingOffsetsVisibleColumn[prevIndex];; + + let breakOffset = 0; + let breakOffsetVisibleColumn = 0; + + let forcedBreakOffset = 0; + let forcedBreakOffsetVisibleColumn = 0; + + // initially, we search as much as possible to the right (if it fits) + if (prevBreakoffsetVisibleColumn <= breakingColumn) { + let visibleColumn = prevBreakoffsetVisibleColumn; + let prevCharCode = lineText.charCodeAt(prevBreakOffset - 1); + let prevCharCodeClass = classifier.get(prevCharCode); + let entireLineFits = true; + for (let i = prevBreakOffset; i < len; i++) { + const charCode = lineText.charCodeAt(i); + const charCodeClass = classifier.get(charCode); + + if (strings.isHighSurrogate(prevCharCode)) { + // A surrogate pair must always be considered as a single unit, so it is never to be broken + visibleColumn += 1; + prevCharCode = charCode; + prevCharCodeClass = charCodeClass; + continue; - if (canBreak(prevCharCodeClass, charCodeClass)) { - breakOffset = i; - breakOffsetVisibleColumn = visibleColumn; - } + } - const charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar); - visibleColumn += charWidth; + if (canBreak(prevCharCodeClass, charCodeClass)) { + breakOffset = i; + breakOffsetVisibleColumn = visibleColumn; + } - if (visibleColumn > breakingColumn) { - // We need to break at least before character at `i`: - forcedBreakOffset = i; - forcedBreakOffsetVisibleColumn = visibleColumn - charWidth; + const charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar); + visibleColumn += charWidth; - if (visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakingColumn) { - // Cannot break at `breakOffset` => reset it if it was set - breakOffset = 0; - } + if (visibleColumn > breakingColumn) { + // We need to break at least before character at `i`: + forcedBreakOffset = i; + forcedBreakOffsetVisibleColumn = visibleColumn - charWidth; - entireLineFits = false; - break; + if (visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakingColumn) { + // Cannot break at `breakOffset` => reset it if it was set + breakOffset = 0; } - prevCharCode = charCode; - prevCharCodeClass = charCodeClass; - } - - if (entireLineFits) { - // there is no more need to break => stop the outer loop! - // Add last segment - breakingOffsets[breakingOffsetsCount] = prevBreakingOffsets[prevBreakingOffsets.length - 1]; - breakingOffsetsVisibleColumn[breakingOffsetsCount] = prevBreakingOffsetsVisibleColumn[prevBreakingOffsets.length - 1]; + entireLineFits = false; break; } + + prevCharCode = charCode; + prevCharCodeClass = charCodeClass; } - if (breakOffset === 0) { - // must search left - let visibleColumn = prevBreakoffsetVisibleColumn; - let charCode = lineText.charCodeAt(prevBreakOffset); - let charCodeClass = classifier.get(charCode); - let hitATabCharacter = false; - for (let i = prevBreakOffset - 1; i >= 0; i--) { - const prevCharCode = lineText.charCodeAt(i); - const prevCharCodeClass = classifier.get(prevCharCode); - - if (strings.isHighSurrogate(prevCharCode)) { - // A surrogate pair must always be considered as a single unit, so it is never to be broken - visibleColumn -= 1; - charCode = prevCharCode; - charCodeClass = prevCharCodeClass; - continue; - } + if (entireLineFits) { + // there is no more need to break => stop the outer loop! + // Add last segment + breakingOffsets[breakingOffsetsCount] = prevBreakingOffsets[prevBreakingOffsets.length - 1]; + breakingOffsetsVisibleColumn[breakingOffsetsCount] = prevBreakingOffsetsVisibleColumn[prevBreakingOffsets.length - 1]; + break; + } + } - if (prevCharCode === CharCode.Tab) { - // cannot determine the width of a tab when going backwards, so we must go forwards - hitATabCharacter = true; - break; - } + if (breakOffset === 0) { + // must search left + let visibleColumn = prevBreakoffsetVisibleColumn; + let charCode = lineText.charCodeAt(prevBreakOffset); + let charCodeClass = classifier.get(charCode); + let hitATabCharacter = false; + for (let i = prevBreakOffset - 1; i >= 0; i--) { + const prevCharCode = lineText.charCodeAt(i); + const prevCharCodeClass = classifier.get(prevCharCode); + + if (strings.isHighSurrogate(prevCharCode)) { + // A surrogate pair must always be considered as a single unit, so it is never to be broken + visibleColumn -= 1; + charCode = prevCharCode; + charCodeClass = prevCharCodeClass; + continue; + } - const charWidth = (strings.isFullWidthCharacter(prevCharCode) ? columnsForFullWidthChar : 1); + if (prevCharCode === CharCode.Tab) { + // cannot determine the width of a tab when going backwards, so we must go forwards + hitATabCharacter = true; + break; + } - if (visibleColumn <= breakingColumn) { - if (forcedBreakOffset === 0) { - forcedBreakOffset = i + 1; - forcedBreakOffsetVisibleColumn = visibleColumn; - } + const charWidth = (strings.isFullWidthCharacter(prevCharCode) ? columnsForFullWidthChar : 1); - if (visibleColumn <= breakingColumn - wrappedLineBreakingColumn) { - // went too far! - break; - } + if (visibleColumn <= breakingColumn) { + if (forcedBreakOffset === 0) { + forcedBreakOffset = i + 1; + forcedBreakOffsetVisibleColumn = visibleColumn; + } - if (canBreak(prevCharCodeClass, charCodeClass)) { - breakOffset = i + 1; - breakOffsetVisibleColumn = visibleColumn; - break; - } + if (visibleColumn <= breakingColumn - wrappedLineBreakingColumn) { + // went too far! + break; } - visibleColumn -= charWidth; - charCode = prevCharCode; - charCodeClass = prevCharCodeClass; + if (canBreak(prevCharCodeClass, charCodeClass)) { + breakOffset = i + 1; + breakOffsetVisibleColumn = visibleColumn; + break; + } } - if (hitATabCharacter) { - // cannot determine the width of a tab when going backwards, so we must go forwards from the previous break - prevIndex--; - continue; - } + visibleColumn -= charWidth; + charCode = prevCharCode; + charCodeClass = prevCharCodeClass; } - if (breakOffset === 0) { - // Could not find a good breaking point - breakOffset = forcedBreakOffset; - breakOffsetVisibleColumn = forcedBreakOffsetVisibleColumn; + if (hitATabCharacter) { + // cannot determine the width of a tab when going backwards, so we must go forwards from the previous break + prevIndex--; + continue; } + } - breakingOffsets[breakingOffsetsCount] = breakOffset; - breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn; - breakingOffsetsCount++; - breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakingColumn; + if (breakOffset === 0) { + // Could not find a good breaking point + breakOffset = forcedBreakOffset; + breakOffsetVisibleColumn = forcedBreakOffsetVisibleColumn; + } - while (prevIndex < 0 || (prevIndex < prevLen && prevBreakingOffsetsVisibleColumn[prevIndex] < breakOffsetVisibleColumn)) { - prevIndex++; - } + breakingOffsets[breakingOffsetsCount] = breakOffset; + breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn; + breakingOffsetsCount++; + breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakingColumn; - let currentDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex] - breakingColumn); - // let lastBreakingColumn - while (prevIndex + 1 < prevLen) { - const potentialDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex + 1] - breakingColumn); - if (potentialDiff >= currentDiff) { - break; - } - currentDiff = potentialDiff; - prevIndex++; - } + while (prevIndex < 0 || (prevIndex < prevLen && prevBreakingOffsetsVisibleColumn[prevIndex] < breakOffsetVisibleColumn)) { + prevIndex++; } - if (breakingOffsetsCount === 0) { - return null; + let currentDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex] - breakingColumn); + // let lastBreakingColumn + while (prevIndex + 1 < prevLen) { + const potentialDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex + 1] - breakingColumn); + if (potentialDiff >= currentDiff) { + break; + } + currentDiff = potentialDiff; + prevIndex++; } + } - return new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); - const expected = createLineMapping(classifier, null, lineText, tabSize, firstLineBreakingColumn, columnsForFullWidthChar, hardWrappingIndent); - const actual = new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); - try { - actual.assertEqual(expected); - } catch (err) { - console.log(`BREAKING!!`); - console.log(err); - console.log(`previous breaks: ${JSON.stringify(prevBreakingOffsets)}, breakingOffsetsVisibleColumn: ${JSON.stringify(prevBreakingOffsetsVisibleColumn)}`); - console.log(`expected breakOffsets: ${JSON.stringify(expected?.breakOffsets)}, breakingOffsetsVisibleColumn: ${JSON.stringify(expected?.breakingOffsetsVisibleColumn)}`); - console.log(`actual breakOffsets: ${JSON.stringify(actual?.breakOffsets)}, breakingOffsetsVisibleColumn: ${JSON.stringify(actual?.breakingOffsetsVisibleColumn)}`); - - console.log(`actual str: ${toAnnotatedText(lineText, actual)}`); - - - console.log(` - assertIncrementalLineMapping( - factory, ${str(lineText)}, 4, - ${previousBreakingData.breakingColumn}, ${str(toAnnotatedText(lineText, previousBreakingData))}, - ${expected!.breakingColumn}, ${str(toAnnotatedText(lineText, expected))}, - WrappingIndent.${hardWrappingIndent === WrappingIndent.None ? 'None' : hardWrappingIndent === WrappingIndent.Same ? 'Same' : hardWrappingIndent === WrappingIndent.Indent ? 'Indent' : 'DeepIndent'} - ); + if (breakingOffsetsCount === 0) { + return null; + } + + // return new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); + const expected = createLineMapping(classifier, lineText, tabSize, firstLineBreakingColumn, columnsForFullWidthChar, hardWrappingIndent); + const actual = new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); + try { + actual.assertEqual(expected); + } catch (err) { + console.log(`BREAKING!!`); + console.log(err); + console.log(`previous breaks: ${JSON.stringify(prevBreakingOffsets)}, breakingOffsetsVisibleColumn: ${JSON.stringify(prevBreakingOffsetsVisibleColumn)}`); + console.log(`expected breakOffsets: ${JSON.stringify(expected?.breakOffsets)}, breakingOffsetsVisibleColumn: ${JSON.stringify(expected?.breakingOffsetsVisibleColumn)}`); + console.log(`actual breakOffsets: ${JSON.stringify(actual?.breakOffsets)}, breakingOffsetsVisibleColumn: ${JSON.stringify(actual?.breakingOffsetsVisibleColumn)}`); + + console.log(`actual str: ${toAnnotatedText(lineText, actual)}`); + + + console.log(` + assertIncrementalLineMapping( + factory, ${str(lineText)}, 4, + ${previousBreakingData.breakingColumn}, ${str(toAnnotatedText(lineText, previousBreakingData))}, + ${expected!.breakingColumn}, ${str(toAnnotatedText(lineText, expected))}, + WrappingIndent.${hardWrappingIndent === WrappingIndent.None ? 'None' : hardWrappingIndent === WrappingIndent.Same ? 'Same' : hardWrappingIndent === WrappingIndent.Indent ? 'Indent' : 'DeepIndent'} + ); `); - function str(strr: string) { - return `'${strr.replace(/'/g, '\\\'')}'`; - } - function toAnnotatedText(text: string, lineBreakingData: LineBreakingData | null): string { - // Insert line break markers again, according to algorithm - let actualAnnotatedText = ''; - if (lineBreakingData) { - let previousLineIndex = 0; - for (let i = 0, len = text.length; i < len; i++) { - let r = LineBreakingData.getOutputPositionOfInputOffset(lineBreakingData.breakOffsets, i); - if (previousLineIndex !== r.outputLineIndex) { - previousLineIndex = r.outputLineIndex; - actualAnnotatedText += '|'; - } - actualAnnotatedText += text.charAt(i); + function str(strr: string) { + return `'${strr.replace(/'/g, '\\\'')}'`; + } + function toAnnotatedText(text: string, lineBreakingData: LineBreakingData | null): string { + // Insert line break markers again, according to algorithm + let actualAnnotatedText = ''; + if (lineBreakingData) { + let previousLineIndex = 0; + for (let i = 0, len = text.length; i < len; i++) { + let r = LineBreakingData.getOutputPositionOfInputOffset(lineBreakingData.breakOffsets, i); + if (previousLineIndex !== r.outputLineIndex) { + previousLineIndex = r.outputLineIndex; + actualAnnotatedText += '|'; } - } else { - // No wrapping - actualAnnotatedText = text; + actualAnnotatedText += text.charAt(i); } - return actualAnnotatedText; + } else { + // No wrapping + actualAnnotatedText = text; } + return actualAnnotatedText; } - return actual; + } + return actual; + +} + +function createLineMapping(classifier: WrappingCharacterClassifier, lineText: string, tabSize: number, firstLineBreakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): LineBreakingData | null { + if (firstLineBreakingColumn === -1) { + return null; + } - breakingOffsets = []; - breakingOffsetsVisibleColumn = []; - breakingOffsetsCount = 0; + const len = lineText.length; + if (len <= 1) { + return null; } + const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakingColumn, columnsForFullWidthChar, hardWrappingIndent); + const wrappedLineBreakingColumn = firstLineBreakingColumn - wrappedTextIndentLength; + + let breakingOffsets: number[] = []; + let breakingOffsetsVisibleColumn: number[] = []; + let breakingOffsetsCount: number = 0; let breakOffset = 0; let breakOffsetVisibleColumn = 0; @@ -392,64 +408,6 @@ function createLineMapping(classifier: WrappingCharacterClassifier, previousBrea return new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); } -function - -// class BreakSearchResult { - -// public static INSTANCE = new BreakSearchResult(); - -// prevCharCode: number = CharCode.Null; -// prevCharCodeClass: CharacterClass = CharacterClass.NONE; -// breakOffset: number = 0; -// breakOffsetVisibleColumn: number = 0; -// visibleColumn: number = 0; -// } - -// function searchForBreak(classifier: WrappingCharacterClassifier, lineText: string, len: number, prevCharCode: number, prevCharCodeClass: number): boolean { -// let breakOffset = 0; -// let breakOffsetVisibleColumn = 0; -// for (let i = 1; i < len; i++) { -// const charCode = lineText.charCodeAt(i); -// const charCodeClass = classifier.get(charCode); - -// if (strings.isHighSurrogate(prevCharCode)) { -// // A surrogate pair must always be considered as a single unit, so it is never to be broken -// visibleColumn += 1; -// prevCharCode = charCode; -// prevCharCodeClass = charCodeClass; -// continue; -// } - -// if (canBreak(prevCharCodeClass, charCodeClass)) { -// breakOffset = i; -// breakOffsetVisibleColumn = visibleColumn; -// } - -// const charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar); -// visibleColumn += charWidth; - -// // check if adding character at `i` will go over the breaking column -// if (visibleColumn > breakingColumn) { -// // We need to break at least before character at `i`: - -// if (breakOffset === 0 || visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakingColumn) { -// // Cannot break at `breakOffset`, must break at `i` -// breakOffset = i; -// breakOffsetVisibleColumn = visibleColumn - charWidth; -// } - -// breakingOffsets[breakingOffsetsCount] = breakOffset; -// breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn; -// breakingOffsetsCount++; -// breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakingColumn; -// breakOffset = 0; -// } - -// prevCharCode = charCode; -// prevCharCodeClass = charCodeClass; -// } -// } - function computeCharWidth(charCode: number, visibleColumn: number, tabSize: number, columnsForFullWidthChar: number): number { if (charCode === CharCode.Tab) { return (tabSize - (visibleColumn % tabSize)); From a07286f7f9f0d0810cad80ca3314061c39079452 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 9 Jan 2020 22:47:06 +0100 Subject: [PATCH 109/315] use @aeschli/typescript-vscode-sh-plugin --- .../typescript-language-features/package.json | 2 +- .../typescript-language-features/yarn.lock | 1746 +++-------------- 2 files changed, 264 insertions(+), 1484 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index ce453e58d9a66..3191e9fc274f7 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -21,7 +21,7 @@ "semver": "5.5.1", "vscode-extension-telemetry": "0.1.1", "vscode-nls": "^4.0.0", - "typescript-vscode-sh-plugin":"^0.1.0" + "@aeschli/typescript-vscode-sh-plugin": "0.2.0" }, "devDependencies": { "@types/node": "^12.11.7", diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index a45e26290c6ec..054c7c6f14659 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@aeschli/typescript-vscode-sh-plugin@0.2.0": + version "0.2.0" + resolved "https://npm.pkg.github.com/download/@aeschli/typescript-vscode-sh-plugin/0.2.0/6e1ccd1d9ed6c3251091de08bb83589bc555f203b1c594fc9c2bc9d38b66b44d#" + integrity sha512-QHORCwbuln5T9gdfDoqJ2nNXzY/8yOBgTG2f+GiQPeQ0rGrNIydbKe0IYi7VmCvIm1LGhEcNOihcF4FWmsKFow== + "@types/events@*": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" @@ -22,14 +27,14 @@ integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== "@types/node@*": - version "12.0.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.2.tgz#3452a24edf9fea138b48fad4a0a028a683da1e40" - integrity sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA== + version "13.1.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.6.tgz#076028d0b0400be8105b89a0a55550c86684ffec" + integrity "sha1-B2Ao0LBAC+gQW4mgpVVQyGaE/+w= sha512-Jg1F+bmxcpENHP23sVKkNuU3uaxPnsBMW0cLjleiikFKomJQbsn0Cqk2yDvQArqzZN6ABfBkZ0To7pQ8sLdWDg==" "@types/node@^12.11.7": - version "12.11.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" - integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== + version "12.12.24" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.24.tgz#d4606afd8cf6c609036b854360367d1b2c78931f" + integrity "sha1-1GBq/Yz2xgkDa4VDYDZ9Gyx4kx8= sha512-1Ciqv9pqwVtW6FsIUKSZNB82E5Cu1I2bBTj1xuIHXLe/1zYLl3956Nbhg2MzSYHVfl9/rmanjbQIb7LibfCnug==" "@types/rimraf@2.0.2": version "2.0.2" @@ -44,51 +49,22 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45" integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ== -ajv@^5.1.0: - version "5.5.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" - integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= +agent-base@4, agent-base@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" + integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== dependencies: - co "^4.6.0" - fast-deep-equal "^1.0.0" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.3.0" + es6-promisify "^5.0.0" -ansi-cyan@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ansi-cyan/-/ansi-cyan-0.1.1.tgz#538ae528af8982f28ae30d86f2f17456d2609873" - integrity sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM= - dependencies: - ansi-wrap "0.1.0" - -ansi-gray@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ansi-gray/-/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251" - integrity sha1-KWLPVOyXksSFEKPetSRDaGHvclE= +ajv@^6.5.5: + version "6.10.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" + integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw== dependencies: - ansi-wrap "0.1.0" - -ansi-red@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ansi-red/-/ansi-red-0.1.1.tgz#8c638f9d1080800a353c9c28c8a81ca4705d946c" - integrity sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw= - dependencies: - ansi-wrap "0.1.0" - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= - -ansi-wrap@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" - integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768= + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" applicationinsights@1.0.8: version "1.0.8" @@ -99,67 +75,12 @@ applicationinsights@1.0.8: diagnostic-channel-publishers "0.2.1" zone.js "0.7.6" -arr-diff@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-1.1.0.tgz#687c32758163588fef7de7b36fabe495eb1a399a" - integrity sha1-aHwydYFjWI/vfeezb6vklesaOZo= - dependencies: - arr-flatten "^1.0.1" - array-slice "^0.2.3" - -arr-diff@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" - integrity sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8= - dependencies: - arr-flatten "^1.0.1" - -arr-flatten@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-2.1.0.tgz#20f9eab5ec70f5c7d215b1077b1c39161d292c7d" - integrity sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0= - -array-differ@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" - integrity sha1-7/UuN1gknTO+QCuLuOVkuytdQDE= - -array-slice@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5" - integrity sha1-3Tz7gO15c6dRF82sabC5nshhhvU= - -array-union@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" - integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= - dependencies: - array-uniq "^1.0.1" - -array-uniq@^1.0.1, array-uniq@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" - integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= - -array-unique@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" - integrity sha1-odl8yvy8JiXMcPrc6zalDFiwGlM= - -arrify@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= - asn1@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" - integrity sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y= + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" @@ -176,10 +97,10 @@ aws-sign2@~0.7.0: resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= -aws4@^1.6.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.7.0.tgz#d4d0e9b9dbfca77bf08eeb0a8a471550fe39e289" - integrity sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w== +aws4@^1.8.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.0.tgz#24390e6ad61386b0a747265754d2a17219de862c" + integrity "sha1-JDkOatYThrCnRyZXVNKhchnehiw= sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A==" balanced-match@^1.0.0: version "1.0.0" @@ -187,38 +108,12 @@ balanced-match@^1.0.0: integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= bcrypt-pbkdf@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" - integrity sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40= + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= dependencies: tweetnacl "^0.14.3" -beeper@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809" - integrity sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak= - -block-stream@*: - version "0.0.9" - resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" - integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo= - dependencies: - inherits "~2.0.0" - -boom@4.x.x: - version "4.3.1" - resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31" - integrity sha1-T4owBctKfjiJ90kDD9JbluAdLjE= - dependencies: - hoek "4.x.x" - -boom@5.x.x: - version "5.2.0" - resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02" - integrity sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw== - dependencies: - hoek "4.x.x" - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -227,129 +122,43 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^1.8.2: - version "1.8.5" - resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" - integrity sha1-uneWLhLf+WnWt2cR6RS3N4V79qc= - dependencies: - expand-range "^1.8.1" - preserve "^0.2.0" - repeat-element "^1.1.2" - -browser-stdout@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" - integrity sha1-81HTKWnTL6XXpVZxVCY9korjvR8= - -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== buffer-from@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531" - integrity sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA== + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chalk@^1.0.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -clone-buffer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" - integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= - -clone-stats@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" - integrity sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE= - -clone-stats@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" - integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA= - -clone@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/clone/-/clone-0.2.0.tgz#c6126a90ad4f72dbf5acdb243cc37724fe93fc1f" - integrity sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8= - -clone@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" - integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= - -clone@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb" - integrity sha1-0hfR6WERjjrJpLi7oyhVU79kfNs= - -cloneable-readable@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.1.2.tgz#d591dee4a8f8bc15da43ce97dceeba13d43e2a65" - integrity sha512-Bq6+4t+lbM8vhTs/Bef5c5AdEMtapp/iFb6+s4/Hh9MVTt8OLKH7ZOOZSCT+Ys7hsHvqv0GuMPJ1lnQJVHvxpg== - dependencies: - inherits "^2.0.1" - process-nextick-args "^2.0.0" - readable-stream "^2.3.5" - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= - -color-support@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" - integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== - -combined-stream@1.0.6, combined-stream@~1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" - integrity sha1-cj599ugBrFYTETp+RFqbactjKBg= +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" -commander@2.11.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" - integrity sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ== +commander@2.15.1: + version "2.15.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" + integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -convert-source-map@^1.1.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" - integrity sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU= - -core-util-is@1.0.2, core-util-is@~1.0.0: +core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -cryptiles@3.x.x: - version "3.1.2" - resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe" - integrity sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4= - dependencies: - boom "5.x.x" - dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -357,11 +166,6 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -dateformat@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" - integrity sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI= - debug@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -369,12 +173,12 @@ debug@3.1.0: dependencies: ms "2.0.0" -deep-assign@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/deep-assign/-/deep-assign-1.0.0.tgz#b092743be8427dc621ea0067cdec7e70dd19f37b" - integrity sha1-sJJ0O+hCfcYh6gBnzex+cN0Z83s= +debug@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== dependencies: - is-obj "^1.0.0" + ms "^2.1.1" delayed-stream@~1.0.0: version "1.0.0" @@ -393,104 +197,40 @@ diagnostic-channel@0.2.0: dependencies: semver "^5.3.0" -diff@3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" - integrity sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww== - -duplexer2@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" - integrity sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds= - dependencies: - readable-stream "~1.1.9" - -duplexer@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" - integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= - -duplexify@^3.2.0: - version "3.5.4" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.4.tgz#4bb46c1796eabebeec4ca9a2e66b808cb7a3d8b4" - integrity sha512-JzYSLYMhoVVBe8+mbHQ4KgpvHpm0DZpJuL8PY93Vyv1fW7jYJ90LoXa1di/CVbJM+TgMs91rbDapE/RNIfnJsA== - dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" +diff@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== ecc-jsbn@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" - integrity sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU= + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= dependencies: jsbn "~0.1.0" + safer-buffer "^2.1.0" -end-of-stream@^1.0.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" - integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== +es6-promise@^4.0.3: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= dependencies: - once "^1.4.0" + es6-promise "^4.0.3" -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2: +escape-string-regexp@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -event-stream@^3.3.1, event-stream@^3.3.4, event-stream@~3.3.4: - version "3.3.4" - resolved "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" - integrity sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE= - dependencies: - duplexer "~0.1.1" - from "~0" - map-stream "~0.1.0" - pause-stream "0.0.11" - split "0.3" - stream-combiner "~0.0.4" - through "~2.3.1" - -expand-brackets@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" - integrity sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s= - dependencies: - is-posix-bracket "^0.1.0" - -expand-range@^1.8.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" - integrity sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc= - dependencies: - fill-range "^2.1.0" - -extend-shallow@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-1.1.4.tgz#19d6bf94dfc09d76ba711f39b872d21ff4dd9071" - integrity sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE= - dependencies: - kind-of "^1.1.0" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend@^3.0.0, extend@~3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" - integrity sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ= - -extglob@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" - integrity sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE= - dependencies: - is-extglob "^1.0.0" +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== extsprintf@1.3.0: version "1.3.0" @@ -502,99 +242,35 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -fancy-log@^1.1.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.2.tgz#f41125e3d84f2e7d89a43d06d958c8f78be16be1" - integrity sha1-9BEl49hPLn2JpD0G2VjI94vha+E= - dependencies: - ansi-gray "^0.1.1" - color-support "^1.1.3" - time-stamp "^1.0.0" - -fast-deep-equal@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" - integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= - -fast-json-stable-stringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" - integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= - -fd-slicer@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" - integrity sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU= - dependencies: - pend "~1.2.0" - -filename-regex@^2.0.0: +fast-deep-equal@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" - integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY= + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= -fill-range@^2.1.0: - version "2.2.3" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" - integrity sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM= - dependencies: - is-number "^2.1.0" - isobject "^2.0.0" - randomatic "^1.1.3" - repeat-element "^1.1.2" - repeat-string "^1.5.2" - -first-chunk-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e" - integrity sha1-Wb+1DNkF9g18OUzT2ayqtOatk04= - -for-in@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - -for-own@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" - integrity sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4= - dependencies: - for-in "^1.0.1" +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity "sha1-h0v2nG9ATCtdmcSBNBOZ/VWJJjM= sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= -form-data@~2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" - integrity sha1-SXBJi+YEwgwAXU9cI67NIda0kJk= +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== dependencies: asynckit "^0.4.0" - combined-stream "1.0.6" + combined-stream "^1.0.6" mime-types "^2.1.12" -from@~0: - version "0.1.7" - resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" - integrity sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4= - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fstream@^1.0.2: - version "1.0.11" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" - integrity sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE= - dependencies: - graceful-fs "^4.1.2" - inherits "~2.0.0" - mkdirp ">=0.5 0" - rimraf "2" - getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -602,44 +278,7 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -glob-base@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" - integrity sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q= - dependencies: - glob-parent "^2.0.0" - is-glob "^2.0.0" - -glob-parent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" - integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg= - dependencies: - is-glob "^2.0.0" - -glob-parent@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= - dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" - -glob-stream@^5.3.2: - version "5.3.5" - resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-5.3.5.tgz#a55665a9a8ccdc41915a87c701e32d4e016fad22" - integrity sha1-pVZlqajM3EGRWofHAeMtTgFvrSI= - dependencies: - extend "^3.0.0" - glob "^5.0.3" - glob-parent "^3.0.0" - micromatch "^2.3.7" - ordered-read-streams "^0.3.0" - through2 "^0.6.0" - to-absolute-glob "^0.1.1" - unique-stream "^2.0.2" - -glob@7.1.2, glob@^7.0.5, glob@^7.1.2: +glob@7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== @@ -651,21 +290,10 @@ glob@7.1.2, glob@^7.0.5, glob@^7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^5.0.3: - version "5.0.15" - resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" - integrity sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E= - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.1.3: - version "7.1.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" - integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== +glob@^7.1.2, glob@^7.1.3: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -674,187 +302,41 @@ glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -glogg@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.1.tgz#dcf758e44789cc3f3d32c1f3562a3676e6a34810" - integrity sha512-ynYqXLoluBKf9XGR1gA59yEJisIL7YHEH4xr3ZziHB5/yl4qWfaK8Js9jGe6gBGCSCKVqiyO30WnRZADvemUNw== - dependencies: - sparkles "^1.0.0" - -graceful-fs@^4.0.0, graceful-fs@^4.1.2: - version "4.1.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" - integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= - -growl@1.10.3: - version "1.10.3" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f" - integrity sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q== - -gulp-chmod@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/gulp-chmod/-/gulp-chmod-2.0.0.tgz#00c390b928a0799b251accf631aa09e01cc6299c" - integrity sha1-AMOQuSigeZslGsz2MaoJ4BzGKZw= - dependencies: - deep-assign "^1.0.0" - stat-mode "^0.2.0" - through2 "^2.0.0" - -gulp-filter@^5.0.1: - version "5.1.0" - resolved "https://registry.yarnpkg.com/gulp-filter/-/gulp-filter-5.1.0.tgz#a05e11affb07cf7dcf41a7de1cb7b63ac3783e73" - integrity sha1-oF4Rr/sHz33PQafeHLe2OsN4PnM= - dependencies: - multimatch "^2.0.0" - plugin-error "^0.1.2" - streamfilter "^1.0.5" - -gulp-gunzip@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gulp-gunzip/-/gulp-gunzip-1.0.0.tgz#15b741145e83a9c6f50886241b57cc5871f151a9" - integrity sha1-FbdBFF6Dqcb1CIYkG1fMWHHxUak= - dependencies: - through2 "~0.6.5" - vinyl "~0.4.6" - -gulp-remote-src-vscode@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/gulp-remote-src-vscode/-/gulp-remote-src-vscode-0.5.0.tgz#71785553bc491880088ad971f90910c4b2d80a99" - integrity sha512-/9vtSk9eI9DEWCqzGieglPqmx0WUQ9pwPHyHFpKmfxqdgqGJC2l0vFMdYs54hLdDsMDEZFLDL2J4ikjc4hQ5HQ== - dependencies: - event-stream "^3.3.4" - node.extend "^1.1.2" - request "^2.79.0" - through2 "^2.0.3" - vinyl "^2.0.1" - -gulp-sourcemaps@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/gulp-sourcemaps/-/gulp-sourcemaps-1.6.0.tgz#b86ff349d801ceb56e1d9e7dc7bbcb4b7dee600c" - integrity sha1-uG/zSdgBzrVuHZ59x7vLS33uYAw= - dependencies: - convert-source-map "^1.1.1" - graceful-fs "^4.1.2" - strip-bom "^2.0.0" - through2 "^2.0.0" - vinyl "^1.0.0" - -gulp-symdest@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/gulp-symdest/-/gulp-symdest-1.1.0.tgz#c165320732d192ce56fd94271ffa123234bf2ae0" - integrity sha1-wWUyBzLRks5W/ZQnH/oSMjS/KuA= - dependencies: - event-stream "^3.3.1" - mkdirp "^0.5.1" - queue "^3.1.0" - vinyl-fs "^2.4.3" - -gulp-untar@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/gulp-untar/-/gulp-untar-0.0.6.tgz#d6bdefde7e9a8e054c9f162385a0782c4be74000" - integrity sha1-1r3v3n6ajgVMnxYjhaB4LEvnQAA= - dependencies: - event-stream "~3.3.4" - gulp-util "~3.0.8" - streamifier "~0.1.1" - tar "^2.2.1" - through2 "~2.0.3" - -gulp-util@~3.0.8: - version "3.0.8" - resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" - integrity sha1-AFTh50RQLifATBh8PsxQXdVLu08= - dependencies: - array-differ "^1.0.0" - array-uniq "^1.0.2" - beeper "^1.0.0" - chalk "^1.0.0" - dateformat "^2.0.0" - fancy-log "^1.1.0" - gulplog "^1.0.0" - has-gulplog "^0.1.0" - lodash._reescape "^3.0.0" - lodash._reevaluate "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.template "^3.0.0" - minimist "^1.1.0" - multipipe "^0.1.2" - object-assign "^3.0.0" - replace-ext "0.0.1" - through2 "^2.0.0" - vinyl "^0.5.0" - -gulp-vinyl-zip@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/gulp-vinyl-zip/-/gulp-vinyl-zip-2.1.0.tgz#24e40685dc05b7149995245099e0590263be8dad" - integrity sha1-JOQGhdwFtxSZlSRQmeBZAmO+ja0= - dependencies: - event-stream "^3.3.1" - queue "^4.2.1" - through2 "^2.0.3" - vinyl "^2.0.2" - vinyl-fs "^2.0.0" - yauzl "^2.2.1" - yazl "^2.2.1" - -gulplog@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gulplog/-/gulplog-1.0.0.tgz#e28c4d45d05ecbbed818363ce8f9c5926229ffe5" - integrity sha1-4oxNRdBey77YGDY86PnFkmIp/+U= - dependencies: - glogg "^1.0.0" +growl@1.10.5: + version "1.10.5" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= -har-validator@~5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" - integrity sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0= +har-validator@~5.1.0: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== dependencies: - ajv "^5.1.0" + ajv "^6.5.5" har-schema "^2.0.0" -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= - dependencies: - ansi-regex "^2.0.0" - -has-flag@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" - integrity sha1-6CB68cx7MNRGzHC3NLXovhj4jVE= - -has-gulplog@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/has-gulplog/-/has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce" - integrity sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4= - dependencies: - sparkles "^1.0.0" - -hawk@~6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038" - integrity sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ== - dependencies: - boom "4.x.x" - cryptiles "3.x.x" - hoek "4.x.x" - sntp "2.x.x" +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= he@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= -hoek@4.x.x: - version "4.2.1" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" - integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA== +http-proxy-agent@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" + integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== + dependencies: + agent-base "4" + debug "3.1.0" http-signature@~1.2.0: version "1.2.0" @@ -865,6 +347,14 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +https-proxy-agent@^2.2.1: + version "2.2.4" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" + integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== + dependencies: + agent-base "^4.3.0" + debug "^3.1.0" + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -873,128 +363,16 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-dotfile@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" - integrity sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE= - -is-equal-shallow@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" - integrity sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ= - dependencies: - is-primitive "^2.0.0" - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extglob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" - integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA= - -is-extglob@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-glob@^2.0.0, is-glob@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" - integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM= - dependencies: - is-extglob "^1.0.0" - -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= - dependencies: - is-extglob "^2.1.0" - -is-number@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" - integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8= - dependencies: - kind-of "^3.0.2" - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" - -is-obj@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" - integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= - -is-posix-bracket@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" - integrity sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q= - -is-primitive@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" - integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU= - -is-stream@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= -is-utf8@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" - integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= - -is-valid-glob@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-0.3.0.tgz#d4b55c69f51886f9b65c70d6c2622d37e29f48fe" - integrity sha1-1LVcafUYhvm2XHDWwmItN+KfSP4= - -is@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/is/-/is-3.2.1.tgz#d0ac2ad55eb7b0bec926a5266f6c662aaa83dca5" - integrity sha1-0Kwq1V63sL7JJqUmb2xmKqqD3KU= - -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= - -isarray@1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -1005,37 +383,25 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -json-schema-traverse@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" - integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A= +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= -json-stable-stringify@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" - integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= - dependencies: - jsonify "~0.0.0" - json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= jsonc-parser@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.1.1.tgz#83dc3d7a6e7186346b889b1280eefa04446c6d3e" - integrity sha512-VC0CjnWJylKB1iov4u76/W/5Ef0ydDkjtYWxoZ9t3HdWlSnZQwZL5MgFikaB/EtQ4RmMEw3tmQzuYnZA2/Ja1g== - -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= + version "2.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.0.tgz#f206f87f9d49d644b7502052c04e82dd6392e9ef" + integrity sha512-4fLQxW1j/5fWj6p78vAlAafoCKtuBm6ghv+Ij5W2DrDx0qE+ZdEl2c6Ko1mgJNF5ftX1iEWQQ4Ap7+3GlhjkOA== jsprim@^1.2.2: version "1.4.1" @@ -1047,180 +413,19 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -kind-of@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44" - integrity sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ= - -kind-of@^3.0.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -lazystream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" - integrity sha1-9plf4PggOS9hOWvolGJAe7dxaOQ= - dependencies: - readable-stream "^2.0.5" - -lodash._basecopy@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" - integrity sha1-jaDmqHbPNEwK2KVIghEd08XHyjY= - -lodash._basetostring@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz#d1861d877f824a52f669832dcaf3ee15566a07d5" - integrity sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U= - -lodash._basevalues@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz#5b775762802bde3d3297503e26300820fdf661b7" - integrity sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc= - -lodash._getnative@^3.0.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" - integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U= - -lodash._isiterateecall@^3.0.0: - version "3.0.9" - resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" - integrity sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw= - -lodash._reescape@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reescape/-/lodash._reescape-3.0.0.tgz#2b1d6f5dfe07c8a355753e5f27fac7f1cde1616a" - integrity sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo= - -lodash._reevaluate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz#58bc74c40664953ae0b124d806996daca431e2ed" - integrity sha1-WLx0xAZklTrgsSTYBpltrKQx4u0= - -lodash._reinterpolate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= - -lodash._root@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" - integrity sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI= - -lodash.escape@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698" - integrity sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg= - dependencies: - lodash._root "^3.0.0" - -lodash.isarguments@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" - integrity sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo= - -lodash.isarray@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" - integrity sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U= - -lodash.isequal@^4.0.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" - integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= - -lodash.keys@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" - integrity sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo= - dependencies: - lodash._getnative "^3.0.0" - lodash.isarguments "^3.0.0" - lodash.isarray "^3.0.0" - -lodash.restparam@^3.0.0: - version "3.6.1" - resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" - integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= - -lodash.template@^3.0.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f" - integrity sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8= - dependencies: - lodash._basecopy "^3.0.0" - lodash._basetostring "^3.0.0" - lodash._basevalues "^3.0.0" - lodash._isiterateecall "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" - lodash.keys "^3.0.0" - lodash.restparam "^3.0.0" - lodash.templatesettings "^3.0.0" - -lodash.templatesettings@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz#fb307844753b66b9f1afa54e262c745307dba8e5" - integrity sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU= - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" - -map-stream@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" - integrity sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ= +mime-db@1.43.0: + version "1.43.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" + integrity "sha1-ChLgUCZQ5HPXNVNQUOfI9OtPrlg= sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" -merge-stream@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1" - integrity sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE= - dependencies: - readable-stream "^2.0.1" - -micromatch@^2.3.7: - version "2.3.11" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" - integrity sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU= - dependencies: - arr-diff "^2.0.0" - array-unique "^0.2.1" - braces "^1.8.2" - expand-brackets "^0.1.4" - extglob "^0.3.1" - filename-regex "^2.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.1" - kind-of "^3.0.2" - normalize-path "^2.0.1" - object.omit "^2.0.0" - parse-glob "^3.0.4" - regex-cache "^0.4.2" - -mime-db@~1.33.0: - version "1.33.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" - integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== - -mime-types@^2.1.12, mime-types@~2.1.17: - version "2.1.18" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" - integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.26" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" + integrity "sha1-nJIfwJt+FJpl39wNpNIJlyALCgY= sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==" dependencies: - mime-db "~1.33.0" + mime-db "1.43.0" -"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.4: +minimatch@3.0.4, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -1232,319 +437,134 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -minimist@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= - -mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1: +mkdirp@0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= dependencies: minimist "0.0.8" -mocha@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.1.0.tgz#7d86cfbcf35cb829e2754c32e17355ec05338794" - integrity sha512-0RVnjg1HJsXY2YFDoTNzcc1NKhYuXKRrBAG2gDygmJJA136Cs2QlRliZG1mA0ap7cuaT30mw16luAeln+4RiNA== +mocha@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6" + integrity sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ== dependencies: - browser-stdout "1.3.0" - commander "2.11.0" + browser-stdout "1.3.1" + commander "2.15.1" debug "3.1.0" - diff "3.3.1" + diff "3.5.0" escape-string-regexp "1.0.5" glob "7.1.2" - growl "1.10.3" + growl "1.10.5" he "1.1.1" + minimatch "3.0.4" mkdirp "0.5.1" - supports-color "4.4.0" + supports-color "5.4.0" ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -multimatch@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-2.1.0.tgz#9c7906a22fb4c02919e2f5f75161b4cdbd4b2a2b" - integrity sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis= - dependencies: - array-differ "^1.0.0" - array-union "^1.0.1" - arrify "^1.0.0" - minimatch "^3.0.0" - -multipipe@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b" - integrity sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s= - dependencies: - duplexer2 "0.0.2" +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -node.extend@^1.1.2: - version "1.1.6" - resolved "https://registry.yarnpkg.com/node.extend/-/node.extend-1.1.6.tgz#a7b882c82d6c93a4863a5504bd5de8ec86258b96" - integrity sha1-p7iCyC1sk6SGOlUEvV3o7IYli5Y= - dependencies: - is "^3.1.0" +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -normalize-path@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= - dependencies: - remove-trailing-separator "^1.0.1" - -oauth-sign@~0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" - integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM= - -object-assign@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" - integrity sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I= - -object-assign@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -object.omit@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" - integrity sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo= - dependencies: - for-own "^0.1.4" - is-extendable "^0.1.1" - -once@^1.3.0, once@^1.4.0: +once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= dependencies: wrappy "1" -ordered-read-streams@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz#7137e69b3298bb342247a1bbee3881c80e2fd78b" - integrity sha1-cTfmmzKYuzQiR6G77jiByA4v14s= - dependencies: - is-stream "^1.0.1" - readable-stream "^2.0.1" - -parse-glob@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" - integrity sha1-ssN2z7EfNVE7rdFz7wu246OIORw= - dependencies: - glob-base "^0.3.0" - is-dotfile "^1.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.0" - -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= - path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -pause-stream@0.0.11: - version "0.0.11" - resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" - integrity sha1-/lo0sMvOErWqaitAPuLnO2AvFEU= - dependencies: - through "~2.3" - -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= - performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -plugin-error@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-0.1.2.tgz#3b9bb3335ccf00f425e07437e19276967da47ace" - integrity sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4= - dependencies: - ansi-cyan "^0.1.1" - ansi-red "^0.1.1" - arr-diff "^1.0.1" - arr-union "^2.0.1" - extend-shallow "^1.1.2" - -preserve@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" - integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= - -process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" - integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== +psl@^1.1.24: + version "1.7.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" + integrity "sha1-8cTEeo75cWfepda79IFtc26ISjw= sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= -qs@~6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" - integrity sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A== - -querystringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.0.0.tgz#fa3ed6e68eb15159457c89b37bc6472833195755" - integrity sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw== - -queue@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/queue/-/queue-3.1.0.tgz#6c49d01f009e2256788789f2bffac6b8b9990585" - integrity sha1-bEnQHwCeIlZ4h4nyv/rGuLmZBYU= - dependencies: - inherits "~2.0.0" - -queue@^4.2.1: - version "4.4.2" - resolved "https://registry.yarnpkg.com/queue/-/queue-4.4.2.tgz#5a9733d9a8b8bd1b36e934bc9c55ab89b28e29c7" - integrity sha512-fSMRXbwhMwipcDZ08enW2vl+YDmAmhcNcr43sCJL8DIg+CFOsoRLG23ctxA+fwNk1w55SePSiS7oqQQSgQoVJQ== - dependencies: - inherits "~2.0.0" - -randomatic@^1.1.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" - integrity sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how== - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -"readable-stream@>=1.0.33-1 <1.1.0-0": - version "1.0.34" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" - integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@^2.3.5: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@~1.1.9: - version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -regex-cache@^0.4.2: - version "0.4.4" - resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" - integrity sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ== - dependencies: - is-equal-shallow "^0.1.3" - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= - -repeat-element@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" - integrity sha1-7wiaF40Ug7quTZPrmLT55OEdmQo= - -repeat-string@^1.5.2: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -replace-ext@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" - integrity sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ= +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -replace-ext@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" - integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= +querystringify@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" + integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== -request@^2.79.0, request@^2.83.0: - version "2.85.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa" - integrity sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg== +request@^2.88.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== dependencies: aws-sign2 "~0.7.0" - aws4 "^1.6.0" + aws4 "^1.8.0" caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.1" + combined-stream "~1.0.6" + extend "~3.0.2" forever-agent "~0.6.1" - form-data "~2.3.1" - har-validator "~5.0.3" - hawk "~6.0.2" + form-data "~2.3.2" + har-validator "~5.1.0" http-signature "~1.2.0" is-typedarray "~1.0.0" isstream "~0.1.2" json-stringify-safe "~5.0.1" - mime-types "~2.1.17" - oauth-sign "~0.8.2" + mime-types "~2.1.19" + oauth-sign "~0.9.0" performance-now "^2.1.0" - qs "~6.5.1" - safe-buffer "^5.1.1" - stringstream "~0.0.5" - tough-cookie "~2.3.3" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.4.3" tunnel-agent "^0.6.0" - uuid "^3.1.0" + uuid "^3.3.2" requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= -rimraf@2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" - integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== - dependencies: - glob "^7.0.5" - rimraf@^2.6.3: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: glob "^7.1.3" -safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-buffer@^5.0.1, safe-buffer@^5.1.2: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + +safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== semver@5.5.1: version "5.5.1" @@ -1552,21 +572,14 @@ semver@5.5.1: integrity sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw== semver@^5.3.0, semver@^5.4.1: - version "5.5.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" - integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA== - -sntp@2.x.x: - version "2.1.0" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8" - integrity sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg== - dependencies: - hoek "4.x.x" + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== source-map-support@^0.5.0: - version "0.5.5" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.5.tgz#0d4af9e00493e855402e8ec36ebed2d266fceb90" - integrity sha512-mR7/Nd5l1z6g99010shcXJiNEaf3fEtmLhRB/sBcQVJGodcHCULPp2y4Sfa43Kv2zq7T+Izmfp/WHCR6dYkQCA== + version "0.5.16" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" + integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -1576,168 +589,34 @@ source-map@^0.6.0: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -sparkles@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" - integrity sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM= - -split@0.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f" - integrity sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8= - dependencies: - through "2" - sshpk@^1.7.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.1.tgz#130f5975eddad963f1d56f92b9ac6c51fa9f83eb" - integrity sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s= + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" - dashdash "^1.12.0" - getpass "^0.1.1" - optionalDependencies: bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" ecc-jsbn "~0.1.1" + getpass "^0.1.1" jsbn "~0.1.0" + safer-buffer "^2.0.2" tweetnacl "~0.14.0" -stat-mode@^0.2.0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/stat-mode/-/stat-mode-0.2.2.tgz#e6c80b623123d7d80cf132ce538f346289072502" - integrity sha1-5sgLYjEj19gM8TLOU480YokHJQI= - -stream-combiner@~0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" - integrity sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ= - dependencies: - duplexer "~0.1.1" - -stream-shift@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" - integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= - -streamfilter@^1.0.5: - version "1.0.7" - resolved "https://registry.yarnpkg.com/streamfilter/-/streamfilter-1.0.7.tgz#ae3e64522aa5a35c061fd17f67620c7653c643c9" - integrity sha512-Gk6KZM+yNA1JpW0KzlZIhjo3EaBJDkYfXtYSbOwNIQ7Zd6006E6+sCFlW1NDvFG/vnXhKmw6TJJgiEQg/8lXfQ== - dependencies: - readable-stream "^2.0.2" - -streamifier@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/streamifier/-/streamifier-0.1.1.tgz#97e98d8fa4d105d62a2691d1dc07e820db8dfc4f" - integrity sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8= - -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -stringstream@~0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" - integrity sha1-TkhM1N5aC7vuGORjB3EKioFiGHg= - -strip-ansi@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-bom-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz#e7144398577d51a6bed0fa1994fa05f43fd988ee" - integrity sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4= - dependencies: - first-chunk-stream "^1.0.0" - strip-bom "^2.0.0" - -strip-bom@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" - integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= - dependencies: - is-utf8 "^0.2.0" - -supports-color@4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" - integrity sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ== +supports-color@5.4.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" + integrity sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w== dependencies: - has-flag "^2.0.0" + has-flag "^3.0.0" -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= - -tar@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" - integrity sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE= - dependencies: - block-stream "*" - fstream "^1.0.2" - inherits "2" - -through2-filter@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec" - integrity sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw= - dependencies: - through2 "~2.0.0" - xtend "~4.0.0" - -through2@^0.6.0, through2@~0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" - integrity sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg= - dependencies: - readable-stream ">=1.0.33-1 <1.1.0-0" - xtend ">=4.0.0 <4.1.0-0" - -through2@^2.0.0, through2@^2.0.3, through2@~2.0.0, through2@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" - integrity sha1-AARWmzfHx0ujnEPzzteNGtlBQL4= - dependencies: - readable-stream "^2.1.5" - xtend "~4.0.1" - -through@2, through@~2.3, through@~2.3.1: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= - -time-stamp@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" - integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= - -to-absolute-glob@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz#1cdfa472a9ef50c239ee66999b662ca0eb39937f" - integrity sha1-HN+kcqnvUMI57maZm2YsoOs5k38= - dependencies: - extend-shallow "^2.0.1" - -tough-cookie@~2.3.3: - version "2.3.4" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" - integrity sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA== +tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== dependencies: + psl "^1.1.24" punycode "^1.4.1" tunnel-agent@^0.6.0: @@ -1752,36 +631,25 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -unique-stream@^2.0.2: - version "2.2.1" - resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.2.1.tgz#5aa003cfbe94c5ff866c4e7d668bb1c4dbadb369" - integrity sha1-WqADz76Uxf+GbE59ZouxxNuts2k= +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== dependencies: - json-stable-stringify "^1.0.0" - through2-filter "^2.0.0" + punycode "^2.1.0" -url-parse@^1.1.9: - version "1.4.0" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.0.tgz#6bfdaad60098c7fe06f623e42b22de62de0d3d75" - integrity sha512-ERuGxDiQ6Xw/agN4tuoCRbmwRuZP0cJ1lJxJubXr5Q/5cDa78+Dc4wfvtxzhzhkm5VvmW6Mf8EVj9SPGN4l8Lg== +url-parse@^1.4.4: + version "1.4.7" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" + integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== dependencies: - querystringify "^2.0.0" + querystringify "^2.1.1" requires-port "^1.0.0" -util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -uuid@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" - integrity sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA== - -vali-date@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/vali-date/-/vali-date-1.0.0.tgz#1b904a59609fb328ef078138420934f6b86709a6" - integrity sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY= +uuid@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" + integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== verror@1.10.0: version "1.10.0" @@ -1792,75 +660,6 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -vinyl-fs@^2.0.0, vinyl-fs@^2.4.3: - version "2.4.4" - resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-2.4.4.tgz#be6ff3270cb55dfd7d3063640de81f25d7532239" - integrity sha1-vm/zJwy1Xf19MGNkDegfJddTIjk= - dependencies: - duplexify "^3.2.0" - glob-stream "^5.3.2" - graceful-fs "^4.0.0" - gulp-sourcemaps "1.6.0" - is-valid-glob "^0.3.0" - lazystream "^1.0.0" - lodash.isequal "^4.0.0" - merge-stream "^1.0.0" - mkdirp "^0.5.0" - object-assign "^4.0.0" - readable-stream "^2.0.4" - strip-bom "^2.0.0" - strip-bom-stream "^1.0.0" - through2 "^2.0.0" - through2-filter "^2.0.0" - vali-date "^1.0.0" - vinyl "^1.0.0" - -vinyl-source-stream@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vinyl-source-stream/-/vinyl-source-stream-1.1.2.tgz#62b53a135610a896e98ca96bee3a87f008a8e780" - integrity sha1-YrU6E1YQqJbpjKlr7jqH8Aio54A= - dependencies: - through2 "^2.0.3" - vinyl "^0.4.3" - -vinyl@^0.4.3, vinyl@~0.4.6: - version "0.4.6" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847" - integrity sha1-LzVsh6VQolVGHza76ypbqL94SEc= - dependencies: - clone "^0.2.0" - clone-stats "^0.0.1" - -vinyl@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.5.3.tgz#b0455b38fc5e0cf30d4325132e461970c2091cde" - integrity sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4= - dependencies: - clone "^1.0.0" - clone-stats "^0.0.1" - replace-ext "0.0.1" - -vinyl@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-1.2.0.tgz#5c88036cf565e5df05558bfc911f8656df218884" - integrity sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ= - dependencies: - clone "^1.0.0" - clone-stats "^0.0.1" - replace-ext "0.0.1" - -vinyl@^2.0.1, vinyl@^2.0.2: - version "2.1.0" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.1.0.tgz#021f9c2cf951d6b939943c89eb5ee5add4fd924c" - integrity sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw= - dependencies: - clone "^2.1.1" - clone-buffer "^1.0.0" - clone-stats "^1.0.0" - cloneable-readable "^1.0.0" - remove-trailing-separator "^1.0.1" - replace-ext "^1.0.0" - vscode-extension-telemetry@0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.1.tgz#91387e06b33400c57abd48979b0e790415ae110b" @@ -1869,55 +668,36 @@ vscode-extension-telemetry@0.1.1: applicationinsights "1.0.8" vscode-nls@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" - integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== + version "4.1.1" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c" + integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A== + +vscode-test@^0.4.1: + version "0.4.3" + resolved "https://registry.yarnpkg.com/vscode-test/-/vscode-test-0.4.3.tgz#461ebf25fc4bc93d77d982aed556658a2e2b90b8" + integrity sha512-EkMGqBSefZH2MgW65nY05rdRSko15uvzq4VAPM5jVmwYuFQKE7eikKXNJDRxL+OITXHB6pI+a3XqqD32Y3KC5w== + dependencies: + http-proxy-agent "^2.1.0" + https-proxy-agent "^2.2.1" vscode@^1.1.10: - version "1.1.17" - resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.1.17.tgz#cc2a61731e925301f03f003c009cbf454022cd83" - integrity sha512-yNMyrgEua2qyW7+trNNYhA6PeldRrBcwtLtlazkdtzcmkHMKECM/08bPF8HF2ZFuwHgD+8FQsdqd/DvJYQYjJg== + version "1.1.36" + resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.1.36.tgz#5e1a0d1bf4977d0c7bc5159a9a13d5b104d4b1b6" + integrity sha512-cGFh9jmGLcTapCpPCKvn8aG/j9zVQ+0x5hzYJq5h5YyUXVGa1iamOaB2M2PZXoumQPES4qeAP1FwkI0b6tL4bQ== dependencies: glob "^7.1.2" - gulp-chmod "^2.0.0" - gulp-filter "^5.0.1" - gulp-gunzip "1.0.0" - gulp-remote-src-vscode "^0.5.0" - gulp-symdest "^1.1.0" - gulp-untar "^0.0.6" - gulp-vinyl-zip "^2.1.0" - mocha "^4.0.1" - request "^2.83.0" + mocha "^5.2.0" + request "^2.88.0" semver "^5.4.1" source-map-support "^0.5.0" - url-parse "^1.1.9" - vinyl-source-stream "^1.1.0" + url-parse "^1.4.4" + vscode-test "^0.4.1" wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -"xtend@>=4.0.0 <4.1.0-0", xtend@~4.0.0, xtend@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" - integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= - -yauzl@^2.2.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.9.1.tgz#a81981ea70a57946133883f029c5821a89359a7f" - integrity sha1-qBmB6nCleUYTOIPwKcWCGok1mn8= - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.0.1" - -yazl@^2.2.1: - version "2.4.3" - resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.4.3.tgz#ec26e5cc87d5601b9df8432dbdd3cd2e5173a071" - integrity sha1-7CblzIfVYBud+EMtvdPNLlFzoHE= - dependencies: - buffer-crc32 "~0.2.3" - zone.js@0.7.6: version "0.7.6" resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009" From 900100b745fee5a53be67930406aee16d0f2c2f5 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 9 Jan 2020 23:23:55 +0100 Subject: [PATCH 110/315] use typescript-vscode-sh-plugin --- extensions/typescript-language-features/package.json | 2 +- extensions/typescript-language-features/yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 3191e9fc274f7..df72fe3d01ec4 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -21,7 +21,7 @@ "semver": "5.5.1", "vscode-extension-telemetry": "0.1.1", "vscode-nls": "^4.0.0", - "@aeschli/typescript-vscode-sh-plugin": "0.2.0" + "typescript-vscode-sh-plugin": "0.2.0" }, "devDependencies": { "@types/node": "^12.11.7", diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index 054c7c6f14659..6f2da650dd8f2 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -2,11 +2,6 @@ # yarn lockfile v1 -"@aeschli/typescript-vscode-sh-plugin@0.2.0": - version "0.2.0" - resolved "https://npm.pkg.github.com/download/@aeschli/typescript-vscode-sh-plugin/0.2.0/6e1ccd1d9ed6c3251091de08bb83589bc555f203b1c594fc9c2bc9d38b66b44d#" - integrity sha512-QHORCwbuln5T9gdfDoqJ2nNXzY/8yOBgTG2f+GiQPeQ0rGrNIydbKe0IYi7VmCvIm1LGhEcNOihcF4FWmsKFow== - "@types/events@*": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" @@ -631,6 +626,11 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= +typescript-vscode-sh-plugin@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.2.0.tgz#8f1b79f2bc5a7d225235bf2b9fffdec9499f00f7" + integrity sha512-jp3B45VPwBuJ7I7IshkNuxn92/DUPqFq6Y3wiNE64Mmws0UCkXKbTsDT8+CKt5rFFCQL7PUtApj6aQkN8OKfxw== + uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" From ad97bd7493758e5337b9344a88d35102e0e719db Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 9 Jan 2020 14:25:39 -0800 Subject: [PATCH 111/315] Exclude tsconfig files under dot file directories Fixes #88328 --- .../typescript-language-features/src/utils/tsconfigProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/typescript-language-features/src/utils/tsconfigProvider.ts b/extensions/typescript-language-features/src/utils/tsconfigProvider.ts index 04843d0058a38..e77dfb306a9b1 100644 --- a/extensions/typescript-language-features/src/utils/tsconfigProvider.ts +++ b/extensions/typescript-language-features/src/utils/tsconfigProvider.ts @@ -18,7 +18,7 @@ export default class TsConfigProvider { return []; } const configs = new Map(); - for (const config of await vscode.workspace.findFiles('**/tsconfig*.json', '**/node_modules/**')) { + for (const config of await vscode.workspace.findFiles('**/tsconfig*.json', '**/{node_modules,.*}/**')) { const root = vscode.workspace.getWorkspaceFolder(config); if (root) { configs.set(config.fsPath, { From c8123dee962847abcbd988d8c180ae1dec0b56d5 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 9 Jan 2020 15:06:11 -0800 Subject: [PATCH 112/315] Fix webviews for electron 7 Caused by #83796 Also re-enables the webview tests --- .../vscode-api-tests/src/singlefolder-tests/webview.test.ts | 2 +- src/vs/code/electron-main/app.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts index e149da8995b7d..e785f1d4afbe1 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts @@ -13,7 +13,7 @@ const webviewId = 'myWebview'; const testDocument = join(vscode.workspace.rootPath || '', './bower.json'); -suite.skip('Webview tests', () => { +suite('Webview tests', () => { const disposables: vscode.Disposable[] = []; function _register(disposable: T) { diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 549c348240bb1..712d6f05c5b50 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -191,7 +191,8 @@ export class CodeApplication extends Disposable { webPreferences.nodeIntegration = false; // Verify URLs being loaded - if (isValidWebviewSource(params.src) && isValidWebviewSource(webPreferences.preload)) { + // https://github.com/electron/electron/issues/21553 + if (isValidWebviewSource(params.src) && isValidWebviewSource((webPreferences as { preloadURL: string }).preloadURL)) { return; } From efd7f099d9a572bacb5a6a37c286093c85a40a7c Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 10 Jan 2020 00:44:02 +0100 Subject: [PATCH 113/315] Fix issues around surrogate pairs --- .../characterHardWrappingLineMapper.ts | 233 ++++++++++-------- .../characterHardWrappingLineMapper.test.ts | 40 ++- 2 files changed, 175 insertions(+), 98 deletions(-) diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index 91c4071798c08..5f36638ef6f96 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -104,6 +104,9 @@ function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterC return null; } + const prevBreakingOffsets = previousBreakingData.breakOffsets; + const prevBreakingOffsetsVisibleColumn = previousBreakingData.breakingOffsetsVisibleColumn; + const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakingColumn, columnsForFullWidthChar, hardWrappingIndent); const wrappedLineBreakingColumn = firstLineBreakingColumn - wrappedTextIndentLength; @@ -111,21 +114,18 @@ function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterC let breakingOffsetsVisibleColumn: number[] = []; let breakingOffsetsCount: number = 0; - const prevBreakingOffsets = previousBreakingData.breakOffsets; - const prevBreakingOffsetsVisibleColumn = previousBreakingData.breakingOffsetsVisibleColumn; - let breakingColumn = firstLineBreakingColumn; const prevLen = prevBreakingOffsets.length; let prevIndex = 0; if (prevIndex >= 0) { - let currentDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex] - breakingColumn); + let bestDistance = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex] - breakingColumn); while (prevIndex + 1 < prevLen) { - const potentialDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex + 1] - breakingColumn); - if (potentialDiff >= currentDiff) { + const distance = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex + 1] - breakingColumn); + if (distance >= bestDistance) { break; } - currentDiff = potentialDiff; + bestDistance = distance; prevIndex++; } } @@ -133,7 +133,7 @@ function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterC while (prevIndex < prevLen) { // Allow for prevIndex to be -1 (for the case where we hit a tab when walking backwards from the first break) const prevBreakOffset = prevIndex < 0 ? 0 : prevBreakingOffsets[prevIndex]; - const prevBreakoffsetVisibleColumn = prevIndex < 0 ? 0 : prevBreakingOffsetsVisibleColumn[prevIndex];; + const prevBreakoffsetVisibleColumn = prevIndex < 0 ? 0 : prevBreakingOffsetsVisibleColumn[prevIndex]; let breakOffset = 0; let breakOffsetVisibleColumn = 0; @@ -148,29 +148,32 @@ function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterC let prevCharCodeClass = classifier.get(prevCharCode); let entireLineFits = true; for (let i = prevBreakOffset; i < len; i++) { + const charStartOffset = i; const charCode = lineText.charCodeAt(i); - const charCodeClass = classifier.get(charCode); + let charCodeClass: number; + let charWidth: number; - if (strings.isHighSurrogate(prevCharCode)) { + if (strings.isHighSurrogate(charCode)) { // A surrogate pair must always be considered as a single unit, so it is never to be broken - visibleColumn += 1; - prevCharCode = charCode; - prevCharCodeClass = charCodeClass; - continue; - + i++; + charCodeClass = CharacterClass.NONE; + charWidth = 2; + } else { + charCodeClass = classifier.get(charCode); + charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar); } if (canBreak(prevCharCodeClass, charCodeClass)) { - breakOffset = i; + breakOffset = charStartOffset; breakOffsetVisibleColumn = visibleColumn; } - const charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar); visibleColumn += charWidth; + // check if adding character at `i` will go over the breaking column if (visibleColumn > breakingColumn) { // We need to break at least before character at `i`: - forcedBreakOffset = i; + forcedBreakOffset = charStartOffset; forcedBreakOffsetVisibleColumn = visibleColumn - charWidth; if (visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakingColumn) { @@ -188,9 +191,11 @@ function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterC if (entireLineFits) { // there is no more need to break => stop the outer loop! - // Add last segment - breakingOffsets[breakingOffsetsCount] = prevBreakingOffsets[prevBreakingOffsets.length - 1]; - breakingOffsetsVisibleColumn[breakingOffsetsCount] = prevBreakingOffsetsVisibleColumn[prevBreakingOffsets.length - 1]; + if (breakingOffsetsCount > 0) { + // Add last segment + breakingOffsets[breakingOffsetsCount] = prevBreakingOffsets[prevBreakingOffsets.length - 1]; + breakingOffsetsVisibleColumn[breakingOffsetsCount] = prevBreakingOffsetsVisibleColumn[prevBreakingOffsets.length - 1]; + } break; } } @@ -202,16 +207,8 @@ function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterC let charCodeClass = classifier.get(charCode); let hitATabCharacter = false; for (let i = prevBreakOffset - 1; i >= 0; i--) { + const charStartOffset = i + 1; const prevCharCode = lineText.charCodeAt(i); - const prevCharCodeClass = classifier.get(prevCharCode); - - if (strings.isHighSurrogate(prevCharCode)) { - // A surrogate pair must always be considered as a single unit, so it is never to be broken - visibleColumn -= 1; - charCode = prevCharCode; - charCodeClass = prevCharCodeClass; - continue; - } if (prevCharCode === CharCode.Tab) { // cannot determine the width of a tab when going backwards, so we must go forwards @@ -219,11 +216,22 @@ function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterC break; } - const charWidth = (strings.isFullWidthCharacter(prevCharCode) ? columnsForFullWidthChar : 1); + let prevCharCodeClass: number; + let prevCharWidth: number; + + if (strings.isLowSurrogate(prevCharCode)) { + // A surrogate pair must always be considered as a single unit, so it is never to be broken + i--; + prevCharCodeClass = CharacterClass.NONE; + prevCharWidth = 2; + } else { + prevCharCodeClass = classifier.get(prevCharCode); + prevCharWidth = (strings.isFullWidthCharacter(prevCharCode) ? columnsForFullWidthChar : 1); + } if (visibleColumn <= breakingColumn) { if (forcedBreakOffset === 0) { - forcedBreakOffset = i + 1; + forcedBreakOffset = charStartOffset; forcedBreakOffsetVisibleColumn = visibleColumn; } @@ -233,17 +241,35 @@ function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterC } if (canBreak(prevCharCodeClass, charCodeClass)) { - breakOffset = i + 1; + breakOffset = charStartOffset; breakOffsetVisibleColumn = visibleColumn; break; } } - visibleColumn -= charWidth; + visibleColumn -= prevCharWidth; charCode = prevCharCode; charCodeClass = prevCharCodeClass; } + if (breakOffset !== 0) { + const remainingWidthOfNextLine = wrappedLineBreakingColumn - (forcedBreakOffsetVisibleColumn - breakOffsetVisibleColumn); + if (remainingWidthOfNextLine <= tabSize) { + const charCodeAtForcedBreakOffset = lineText.charCodeAt(forcedBreakOffset); + let charWidth: number; + if (strings.isHighSurrogate(charCodeAtForcedBreakOffset)) { + // A surrogate pair must always be considered as a single unit, so it is never to be broken + charWidth = 2; + } else { + charWidth = computeCharWidth(charCodeAtForcedBreakOffset, forcedBreakOffsetVisibleColumn, tabSize, columnsForFullWidthChar); + } + if (remainingWidthOfNextLine - charWidth < 0) { + // it is not worth it to break at breakOffset, it just introduces an extra needless line! + breakOffset = 0; + } + } + } + if (hitATabCharacter) { // cannot determine the width of a tab when going backwards, so we must go forwards from the previous break prevIndex--; @@ -266,14 +292,13 @@ function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterC prevIndex++; } - let currentDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex] - breakingColumn); - // let lastBreakingColumn + let bestDistance = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex] - breakingColumn); while (prevIndex + 1 < prevLen) { - const potentialDiff = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex + 1] - breakingColumn); - if (potentialDiff >= currentDiff) { + const distance = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex + 1] - breakingColumn); + if (distance >= bestDistance) { break; } - currentDiff = potentialDiff; + bestDistance = distance; prevIndex++; } } @@ -282,54 +307,56 @@ function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterC return null; } - // return new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); - const expected = createLineMapping(classifier, lineText, tabSize, firstLineBreakingColumn, columnsForFullWidthChar, hardWrappingIndent); - const actual = new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); - try { - actual.assertEqual(expected); - } catch (err) { - console.log(`BREAKING!!`); - console.log(err); - console.log(`previous breaks: ${JSON.stringify(prevBreakingOffsets)}, breakingOffsetsVisibleColumn: ${JSON.stringify(prevBreakingOffsetsVisibleColumn)}`); - console.log(`expected breakOffsets: ${JSON.stringify(expected?.breakOffsets)}, breakingOffsetsVisibleColumn: ${JSON.stringify(expected?.breakingOffsetsVisibleColumn)}`); - console.log(`actual breakOffsets: ${JSON.stringify(actual?.breakOffsets)}, breakingOffsetsVisibleColumn: ${JSON.stringify(actual?.breakingOffsetsVisibleColumn)}`); - - console.log(`actual str: ${toAnnotatedText(lineText, actual)}`); - - - console.log(` - assertIncrementalLineMapping( - factory, ${str(lineText)}, 4, - ${previousBreakingData.breakingColumn}, ${str(toAnnotatedText(lineText, previousBreakingData))}, - ${expected!.breakingColumn}, ${str(toAnnotatedText(lineText, expected))}, - WrappingIndent.${hardWrappingIndent === WrappingIndent.None ? 'None' : hardWrappingIndent === WrappingIndent.Same ? 'Same' : hardWrappingIndent === WrappingIndent.Indent ? 'Indent' : 'DeepIndent'} - ); -`); - function str(strr: string) { - return `'${strr.replace(/'/g, '\\\'')}'`; - } - function toAnnotatedText(text: string, lineBreakingData: LineBreakingData | null): string { - // Insert line break markers again, according to algorithm - let actualAnnotatedText = ''; - if (lineBreakingData) { - let previousLineIndex = 0; - for (let i = 0, len = text.length; i < len; i++) { - let r = LineBreakingData.getOutputPositionOfInputOffset(lineBreakingData.breakOffsets, i); - if (previousLineIndex !== r.outputLineIndex) { - previousLineIndex = r.outputLineIndex; - actualAnnotatedText += '|'; - } - actualAnnotatedText += text.charAt(i); - } - } else { - // No wrapping - actualAnnotatedText = text; - } - return actualAnnotatedText; - } - } - return actual; - + return new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); + // const expected = createLineMapping(classifier, lineText, tabSize, firstLineBreakingColumn, columnsForFullWidthChar, hardWrappingIndent); + // const actual = new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); + // try { + // actual.assertEqual(expected); + // } catch (err) { + // console.log(`BREAKING!!`); + // console.log(err); + // console.log(` + // firstLineBreakingColumn: ${firstLineBreakingColumn} + + // previous breaks: ${JSON.stringify(prevBreakingOffsets)}, breakingOffsetsVisibleColumn: ${JSON.stringify(prevBreakingOffsetsVisibleColumn)} + // expected breaks: ${JSON.stringify(expected?.breakOffsets)}, breakingOffsetsVisibleColumn: ${JSON.stringify(expected?.breakingOffsetsVisibleColumn)} + // actual breaks: ${JSON.stringify(actual?.breakOffsets)}, breakingOffsetsVisibleColumn: ${JSON.stringify(actual?.breakingOffsetsVisibleColumn)} + + // previous str: ${toAnnotatedText(lineText, previousBreakingData)} + // expected str: ${toAnnotatedText(lineText, expected)} + // actual str: ${toAnnotatedText(lineText, actual)} + + // assertIncrementalLineMapping( + // factory, ${str(lineText)}, 4, + // ${previousBreakingData.breakingColumn}, ${str(toAnnotatedText(lineText, previousBreakingData))}, + // ${expected!.breakingColumn}, ${str(toAnnotatedText(lineText, expected))}, + // WrappingIndent.${hardWrappingIndent === WrappingIndent.None ? 'None' : hardWrappingIndent === WrappingIndent.Same ? 'Same' : hardWrappingIndent === WrappingIndent.Indent ? 'Indent' : 'DeepIndent'} + // ); + // `); + // function str(strr: string) { + // return `'${strr.replace(/\\/g, '\\\\').replace(/'/g, '\\\'')}'`; + // } + // function toAnnotatedText(text: string, lineBreakingData: LineBreakingData | null): string { + // // Insert line break markers again, according to algorithm + // let actualAnnotatedText = ''; + // if (lineBreakingData) { + // let previousLineIndex = 0; + // for (let i = 0, len = text.length; i < len; i++) { + // let r = LineBreakingData.getOutputPositionOfInputOffset(lineBreakingData.breakOffsets, i); + // if (previousLineIndex !== r.outputLineIndex) { + // previousLineIndex = r.outputLineIndex; + // actualAnnotatedText += '|'; + // } + // actualAnnotatedText += text.charAt(i); + // } + // } else { + // // No wrapping + // actualAnnotatedText = text; + // } + // return actualAnnotatedText; + // } + // } + // return actual; } function createLineMapping(classifier: WrappingCharacterClassifier, lineText: string, tabSize: number, firstLineBreakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): LineBreakingData | null { @@ -356,24 +383,36 @@ function createLineMapping(classifier: WrappingCharacterClassifier, lineText: st let prevCharCodeClass = classifier.get(prevCharCode); let visibleColumn = computeCharWidth(prevCharCode, 0, tabSize, columnsForFullWidthChar); - for (let i = 1; i < len; i++) { + let startOffset = 1; + if (strings.isHighSurrogate(prevCharCode)) { + // A surrogate pair must always be considered as a single unit, so it is never to be broken + visibleColumn += 1; + prevCharCode = lineText.charCodeAt(1); + prevCharCodeClass = classifier.get(prevCharCode); + startOffset++; + } + + for (let i = startOffset; i < len; i++) { + const charStartOffset = i; const charCode = lineText.charCodeAt(i); - const charCodeClass = classifier.get(charCode); + let charCodeClass: number; + let charWidth: number; - if (strings.isHighSurrogate(prevCharCode)) { + if (strings.isHighSurrogate(charCode)) { // A surrogate pair must always be considered as a single unit, so it is never to be broken - visibleColumn += 1; - prevCharCode = charCode; - prevCharCodeClass = charCodeClass; - continue; + i++; + charCodeClass = CharacterClass.NONE; + charWidth = 2; + } else { + charCodeClass = classifier.get(charCode); + charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar); } if (canBreak(prevCharCodeClass, charCodeClass)) { - breakOffset = i; + breakOffset = charStartOffset; breakOffsetVisibleColumn = visibleColumn; } - const charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar); visibleColumn += charWidth; // check if adding character at `i` will go over the breaking column @@ -382,7 +421,7 @@ function createLineMapping(classifier: WrappingCharacterClassifier, lineText: st if (breakOffset === 0 || visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakingColumn) { // Cannot break at `breakOffset`, must break at `i` - breakOffset = i; + breakOffset = charStartOffset; breakOffsetVisibleColumn = visibleColumn - charWidth; } diff --git a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts index 7d141bfdad0b8..ef61ca5b18353 100644 --- a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts +++ b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts @@ -154,6 +154,34 @@ suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { 16, '\t\t"owner":| |"vscode"|,', WrappingIndent.Same ); + + assertIncrementalLineMapping( + factory, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇&👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬', 4, + 51, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇&|👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬', + 50, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇|&|👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬', + WrappingIndent.Same + ); + + assertIncrementalLineMapping( + factory, '🐇👬&🌞🌖', 4, + 5, '🐇👬&|🌞🌖', + 4, '🐇👬|&|🌞🌖', + WrappingIndent.Same + ); + + assertIncrementalLineMapping( + factory, '\t\tfunc(\'🌞🏇🍼🌞🏇🍼🐇&👬🌖🌞👬🌖🌞🏇🍼🐇👬\', WrappingIndent.Same);', 4, + 26, '\t\tfunc|(\'🌞🏇🍼🌞🏇🍼🐇&|👬🌖🌞👬🌖🌞🏇🍼🐇|👬\', |WrappingIndent.|Same);', + 27, '\t\tfunc|(\'🌞🏇🍼🌞🏇🍼🐇&|👬🌖🌞👬🌖🌞🏇🍼🐇|👬\', |WrappingIndent.|Same);', + WrappingIndent.Same + ); + + assertIncrementalLineMapping( + factory, 'factory, "xtxtfunc(x"🌞🏇🍼🌞🏇🍼🐇&👬🌖🌞👬🌖🌞🏇🍼🐇👬x"', 4, + 16, 'factory, |"xtxtfunc|(x"🌞🏇🍼🌞🏇🍼|🐇&|👬🌖🌞👬🌖🌞🏇🍼|🐇👬x"', + 17, 'factory, |"xtxtfunc|(x"🌞🏇🍼🌞🏇🍼🐇|&👬🌖🌞👬🌖🌞🏇🍼|🐇👬x"', + WrappingIndent.Same + ); }); @@ -185,7 +213,17 @@ suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { test('issue #75494: surrogate pairs', () => { let factory = new CharacterHardWrappingLineMapperFactory('\t', ' '); - assertLineMapping(factory, 4, 49, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇|👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬', WrappingIndent.Same); + assertLineMapping(factory, 4, 49, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼|🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼|🐇👬', WrappingIndent.Same); + }); + + test('issue #75494: surrogate pairs overrun 1', () => { + const factory = new CharacterHardWrappingLineMapperFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue); + assertLineMapping(factory, 4, 4, '🐇👬|&|🌞🌖', WrappingIndent.Same); + }); + + test('issue #75494: surrogate pairs overrun 2', () => { + const factory = new CharacterHardWrappingLineMapperFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue); + assertLineMapping(factory, 4, 17, 'factory, |"xtxtfunc|(x"🌞🏇🍼🌞🏇🍼🐇|&👬🌖🌞👬🌖🌞🏇🍼|🐇👬x"', WrappingIndent.Same); }); test('CharacterHardWrappingLineMapper - WrappingIndent.DeepIndent', () => { From 326914ae98a957f9fefd77e252e64b438adee08a Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 10 Jan 2020 01:00:14 +0100 Subject: [PATCH 114/315] Avoid object churn --- .../characterHardWrappingLineMapper.ts | 20 ++++++++++++++++--- .../common/viewModel/splitLinesCollection.ts | 8 ++++---- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index 5f36638ef6f96..6d78f602fd254 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -94,6 +94,8 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor } } +let arrPool1: number[] = []; +let arrPool2: number[] = []; function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterClassifier, previousBreakingData: LineBreakingData, lineText: string, tabSize: number, firstLineBreakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): LineBreakingData | null { if (firstLineBreakingColumn === -1) { return null; @@ -110,8 +112,8 @@ function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterC const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakingColumn, columnsForFullWidthChar, hardWrappingIndent); const wrappedLineBreakingColumn = firstLineBreakingColumn - wrappedTextIndentLength; - let breakingOffsets: number[] = []; - let breakingOffsetsVisibleColumn: number[] = []; + let breakingOffsets: number[] = arrPool1; + let breakingOffsetsVisibleColumn: number[] = arrPool2; let breakingOffsetsCount: number = 0; let breakingColumn = firstLineBreakingColumn; @@ -307,7 +309,19 @@ function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterC return null; } - return new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); + // Doing here some object reuse which ends up helping a huge deal with GC pauses! + breakingOffsets.length = breakingOffsetsCount; + breakingOffsetsVisibleColumn.length = breakingOffsetsCount; + arrPool1 = previousBreakingData.breakOffsets; + arrPool2 = previousBreakingData.breakingOffsetsVisibleColumn; + previousBreakingData.breakingColumn = firstLineBreakingColumn; + previousBreakingData.breakOffsets = breakingOffsets; + previousBreakingData.breakingOffsetsVisibleColumn = breakingOffsetsVisibleColumn; + previousBreakingData.wrappedTextIndentLength = wrappedTextIndentLength; + return previousBreakingData; + + // return new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); + // const expected = createLineMapping(classifier, lineText, tabSize, firstLineBreakingColumn, columnsForFullWidthChar, hardWrappingIndent); // const actual = new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); // try { diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 2f0af80ac2455..90f3c4c20d8ab 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -28,10 +28,10 @@ export class OutputPosition { export class LineBreakingData { constructor( - public readonly breakingColumn: number, - public readonly breakOffsets: number[], - public readonly breakingOffsetsVisibleColumn: number[], - public readonly wrappedTextIndentLength: number + public breakingColumn: number, + public breakOffsets: number[], + public breakingOffsetsVisibleColumn: number[], + public wrappedTextIndentLength: number ) { } assertEqual(other: LineBreakingData | null): void { From 1d752b1e25126a13256a433ee4559c3b360f8abd Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 9 Jan 2020 16:52:00 -0800 Subject: [PATCH 115/315] Hook up basic custom editor update on rename For #86599 --- .../customEditor/browser/customEditors.ts | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 52d8f474f43c1..908c38979fc49 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -13,6 +13,7 @@ import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { FileOperation, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; @@ -25,7 +26,7 @@ import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_HAS_CUSTOM_EDITORS, import { CustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditorModelManager'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { IWebviewService, webviewHasOwnEditFunctionsContext } from 'vs/workbench/contrib/webview/browser/webview'; -import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { CustomFileEditorInput } from './customEditorInput'; @@ -81,8 +82,10 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ constructor( @IContextKeyService contextKeyService: IContextKeyService, @IWorkingCopyService workingCopyService: IWorkingCopyService, + @IFileService fileService: IFileService, @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IQuickInputService private readonly quickInputService: IQuickInputService, @IWebviewService private readonly webviewService: IWebviewService, @@ -112,6 +115,13 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ this._webviewHasOwnEditFunctions = webviewHasOwnEditFunctionsContext.bindTo(contextKeyService); this._register(this.editorService.onDidActiveEditorChange(() => this.updateContexts())); + + this._register(fileService.onAfterOperation(e => { + if (e.isOperation(FileOperation.MOVE)) { + this.handleMovedFileInOpenedFileEditors(e.resource, e.target.resource); + } + })); + this.updateContexts(); } @@ -262,6 +272,31 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ this._focusedCustomEditorIsEditable.set(activeControl?.input instanceof CustomFileEditorInput); this._webviewHasOwnEditFunctions.set(true); } + + private handleMovedFileInOpenedFileEditors(oldResource: URI, newResource: URI): void { + for (const group of this.editorGroupService.groups) { + for (const editor of group.editors) { + if (!(editor instanceof CustomFileEditorInput)) { + continue; + } + + const editorInfo = this._editorInfoStore.get(editor.viewType); + if (!editorInfo) { + continue; + } + + if (!editorInfo.matches(newResource)) { + continue; + } + + const replacement = this.createInput(newResource, editor.viewType, group); + this.editorService.replaceEditors([{ + editor: editor, + replacement: replacement, + }], group); + } + } + } } export const customEditorsAssociationsKey = 'workbench.experimental.editorAssociations'; From 960cddb992cc96d47f56b633b361909f1b5b3353 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 9 Jan 2020 17:31:42 -0800 Subject: [PATCH 116/315] fix #88394. --- .../contrib/welcome/walkThrough/browser/walkThroughPart.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts index 149c916f53dd0..add87f122e078 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts @@ -421,7 +421,8 @@ export class WalkThroughPart extends BaseEditor { horizontal: 'auto', useShadows: true, verticalHasArrows: false, - horizontalHasArrows: false + horizontalHasArrows: false, + alwaysConsumeMouseWheel: false }, overviewRulerLanes: 3, fixedOverflowWidgets: true, From 95b6bb00e2e87535a19ca22273722d1d96ccdcc1 Mon Sep 17 00:00:00 2001 From: kieferrm Date: Thu, 9 Jan 2020 20:04:07 -0800 Subject: [PATCH 117/315] issue flow configuration --- .github/feature-requests.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/feature-requests.yml diff --git a/.github/feature-requests.yml b/.github/feature-requests.yml new file mode 100644 index 0000000000000..18055b844862d --- /dev/null +++ b/.github/feature-requests.yml @@ -0,0 +1,34 @@ +{ + typeLabel: { + name: 'feature-request' + }, + candidateMilestone: { + number: 107, + name: 'Backlog Candidates' + }, + approvedMilestone: { + number: 8, + name: 'Backlog' + }, + onLabeled: { + delay: 60, + perform: true + }, + onCandidateMilestoned: { + candidatesComment: "This feature request is now a candidate for our backlog. The community has 60 days to upvote the issue. If it receives 20 upvotes we will move it to our backlog. If not, we will close it. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding!", + perform: true + }, + onMonitorUpvotes: { + upvoteThreshold: 20, + acceptanceComment: ":slightly_smiling_face: This feature request received a sufficient number of community upvotes and we moved it to our backlog. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding!", + perform: true + }, + onMonitorDaysOnCandidateMilestone: { + daysOnMilestone: 60, + warningPeriod: 10, + numberOfCommentsToPreventAutomaticRejection: 20, + rejectionComment: ":slightly_frowning_face: In the last 60 days, this feature request has received less than 20 community upvotes and we closed it. Still a big Thank You to you for taking the time to create this issue! To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding!", + warningComment: "This feature request has not yet received the 20 community upvotes it takes to make to our backlog. 10 days to go. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding", + perform: true + } +} From 3e6ba164cce3f643b560eef8dbcf5ec32a4e22ca Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 10 Jan 2020 07:00:00 +0100 Subject: [PATCH 118/315] show preview only for rename --- src/vs/editor/browser/services/bulkEditService.ts | 2 +- src/vs/editor/contrib/rename/rename.ts | 2 +- src/vs/workbench/api/browser/mainThreadEditors.ts | 2 +- src/vs/workbench/contrib/search/browser/replaceService.ts | 2 +- src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/browser/services/bulkEditService.ts b/src/vs/editor/browser/services/bulkEditService.ts index 7c43316685660..0aecb86edfaa6 100644 --- a/src/vs/editor/browser/services/bulkEditService.ts +++ b/src/vs/editor/browser/services/bulkEditService.ts @@ -14,7 +14,7 @@ export const IBulkEditService = createDecorator('IWorkspaceEdi export interface IBulkEditOptions { editor?: ICodeEditor; progress?: IProgress; - noPreview?: boolean; + showPreview?: boolean; } export interface IBulkEditResult { diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index 29b4bb8f7cf84..20ef921a6c123 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -200,7 +200,7 @@ class RenameController implements IEditorContribution { this._bulkEditService.apply(renameResult, { editor: this.editor, - noPreview: !inputFieldResult.wantsPreview + showPreview: inputFieldResult.wantsPreview }).then(result => { if (result.ariaSummary) { alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", loc!.text, inputFieldResult.newName, result.ariaSummary)); diff --git a/src/vs/workbench/api/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index 421201a9c64df..8dd058290d076 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -217,7 +217,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { $tryApplyWorkspaceEdit(dto: IWorkspaceEditDto): Promise { const { edits } = reviveWorkspaceEditDto(dto); - return this._bulkEditService.apply({ edits }, { noPreview: false }).then(() => true, err => false); + return this._bulkEditService.apply({ edits }).then(() => true, _err => false); } $tryInsertSnippet(id: string, template: string, ranges: readonly IRange[], opts: IUndoStopOptions): Promise { diff --git a/src/vs/workbench/contrib/search/browser/replaceService.ts b/src/vs/workbench/contrib/search/browser/replaceService.ts index 92a9cf4b56b4c..bc7e14ab2e28f 100644 --- a/src/vs/workbench/contrib/search/browser/replaceService.ts +++ b/src/vs/workbench/contrib/search/browser/replaceService.ts @@ -105,7 +105,7 @@ export class ReplaceService implements IReplaceService { replace(match: FileMatchOrMatch, progress?: IProgress, resource?: URI): Promise; replace(arg: any, progress: IProgress | undefined = undefined, resource: URI | null = null): Promise { const edits: ResourceTextEdit[] = this.createEdits(arg, resource); - return this.bulkEditorService.apply({ edits }, { progress, noPreview: true }).then(() => this.textFileService.saveAll(edits.map(e => e.resource))); + return this.bulkEditorService.apply({ edits }, { progress }).then(() => this.textFileService.saveAll(edits.map(e => e.resource))); } openReplacePreview(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { diff --git a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts index 7d543daaaddb2..5808f9a78fdc0 100644 --- a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts @@ -406,7 +406,7 @@ export class BulkEditService implements IBulkEditService { async apply(edit: WorkspaceEdit, options?: IBulkEditOptions): Promise { - if (this._previewHandler && !options?.noPreview) { + if (this._previewHandler && options?.showPreview) { edit = await this._previewHandler(edit, options); } From 7c6a2a7889bfd386cab902dbec83f0fc2f698139 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 10 Jan 2020 07:21:34 +0100 Subject: [PATCH 119/315] show panel only while refactoring preview is active --- .../bulkEdit/browser/bulkEdit.contribution.ts | 52 ++++++++++++------- .../contrib/bulkEdit/browser/bulkEditPane.ts | 2 +- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts index 1882e742a445d..dffe6a8715b37 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts @@ -14,39 +14,52 @@ import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewCon import { localize } from 'vs/nls'; import { ViewPaneContainer, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { PaneCompositePanel } from 'vs/workbench/browser/panel'; +import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; class BulkEditPreviewContribution { + static readonly ctxEnabled = new RawContextKey('refactorPreviewIsEnabled', false); + + private readonly _ctxEnabled: IContextKey; + constructor( @IPanelService private _panelService: IPanelService, @IBulkEditService bulkEditService: IBulkEditService, + @IContextKeyService contextKeyService: IContextKeyService, ) { bulkEditService.setPreviewHandler(edit => this._previewEdit(edit)); + this._ctxEnabled = BulkEditPreviewContribution.ctxEnabled.bindTo(contextKeyService); } private async _previewEdit(edit: WorkspaceEdit) { + this._ctxEnabled.set(true); + try { - let view: ViewPane | undefined; - const activePanel = this._panelService.openPanel(BulkEditPane.ID, true); - if (activePanel instanceof PaneCompositePanel) { - view = activePanel.getViewPaneContainer().getView(BulkEditPane.ID); - } + let view: ViewPane | undefined; + const activePanel = this._panelService.openPanel(BulkEditPane.ID, true); + if (activePanel instanceof PaneCompositePanel) { + view = activePanel.getViewPaneContainer().getView(BulkEditPane.ID); + } - if (!(view instanceof BulkEditPane)) { - return edit; - } + if (!(view instanceof BulkEditPane)) { + return edit; + } - const newEditOrUndefined = await view.setInput(edit); - if (!newEditOrUndefined) { - return { edits: [] }; - } + const newEditOrUndefined = await view.setInput(edit); + if (!newEditOrUndefined) { + return { edits: [] }; + } + + // todo@joh/steVen add view state + // if (this._panelService.getLastActivePanelId() === BulkEditPanel.ID) { + // this._panelService.hideActivePanel(); + // } - // todo@joh/steVen add view state - // if (this._panelService.getLastActivePanelId() === BulkEditPanel.ID) { - // this._panelService.hideActivePanel(); - // } + return newEditOrUndefined; - return newEditOrUndefined; + } finally { + this._ctxEnabled.set(false); + } } } @@ -58,16 +71,17 @@ Registry.as(WorkbenchExtensions.Workbench).regi const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: BulkEditPane.ID, name: localize('panel', "Refactor Preview"), + hideIfEmpty: true, ctorDescriptor: { ctor: ViewPaneContainer, - arguments: [BulkEditPane.ID, '', { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }] + arguments: [BulkEditPane.ID, BulkEditPane.ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }] } }, ViewContainerLocation.Panel); Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ id: BulkEditPane.ID, name: localize('panel', "Refactor Preview"), - canToggleVisibility: false, + when: BulkEditPreviewContribution.ctxEnabled, ctorDescriptor: { ctor: BulkEditPane }, }], container); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index 1982c15c8d58f..e4b5a81d91c32 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -32,7 +32,7 @@ const enum State { export class BulkEditPanel extends ViewPane { - static readonly ID = 'BulkEditPanel'; + static readonly ID = 'refactorPreview'; private _tree!: WorkbenchAsyncDataTree; private _message!: HTMLSpanElement; From f1637084561f595f6c78734141471d41c55665e5 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Fri, 10 Jan 2020 08:34:22 +0100 Subject: [PATCH 120/315] Fix issue with breakingOffsetsCount being incorrect at the end --- .../editor/common/viewModel/characterHardWrappingLineMapper.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index 6d78f602fd254..4bfd8c100f0ef 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -197,6 +197,7 @@ function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterC // Add last segment breakingOffsets[breakingOffsetsCount] = prevBreakingOffsets[prevBreakingOffsets.length - 1]; breakingOffsetsVisibleColumn[breakingOffsetsCount] = prevBreakingOffsetsVisibleColumn[prevBreakingOffsets.length - 1]; + breakingOffsetsCount++; } break; } From 71206c77f1c4c3eb45867a6c50997f2e0a20d6a1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 10 Jan 2020 08:39:30 +0100 Subject: [PATCH 121/315] restore workbench layout/ux state when refactoring is done --- .../bulkEdit/browser/bulkEdit.contribution.ts | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts index dffe6a8715b37..8f42d383fde9d 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts @@ -15,6 +15,9 @@ import { localize } from 'vs/nls'; import { ViewPaneContainer, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { PaneCompositePanel } from 'vs/workbench/browser/panel'; import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; +import { BulkEditPreviewProvider } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; class BulkEditPreviewContribution { @@ -24,6 +27,7 @@ class BulkEditPreviewContribution { constructor( @IPanelService private _panelService: IPanelService, + @IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService, @IBulkEditService bulkEditService: IBulkEditService, @IContextKeyService contextKeyService: IContextKeyService, ) { @@ -33,6 +37,8 @@ class BulkEditPreviewContribution { private async _previewEdit(edit: WorkspaceEdit) { this._ctxEnabled.set(true); + const oldActivePanel = this._panelService.getActivePanel(); + try { let view: ViewPane | undefined; @@ -50,15 +56,32 @@ class BulkEditPreviewContribution { return { edits: [] }; } - // todo@joh/steVen add view state - // if (this._panelService.getLastActivePanelId() === BulkEditPanel.ID) { - // this._panelService.hideActivePanel(); - // } - return newEditOrUndefined; } finally { + // restore UX state + + // (1) hide refactor panel this._ctxEnabled.set(false); + + // (2) restore previous panel + if (oldActivePanel) { + this._panelService.openPanel(oldActivePanel.getId()); + } else { + this._panelService.hideActivePanel(); + } + + // (3) close preview editors + for (let group of this._editorGroupsService.groups) { + for (let input of group.editors) { + if (!group.isPinned(input) + && input instanceof DiffEditorInput + && input.modifiedInput.getResource()?.scheme === BulkEditPreviewProvider.Schema + ) { + group.closeEditor(input, { preserveFocus: true }); + } + } + } } } } From ec689c65de18e327d1a0dde7c2faa39ffd0cbfc2 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Fri, 10 Jan 2020 08:44:47 +0100 Subject: [PATCH 122/315] Remove unnecessary code --- .../characterHardWrappingLineMapper.ts | 55 +------------------ .../common/viewModel/splitLinesCollection.ts | 24 -------- 2 files changed, 1 insertion(+), 78 deletions(-) diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index 4bfd8c100f0ef..35d9381487f0c 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -315,63 +315,10 @@ function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterC breakingOffsetsVisibleColumn.length = breakingOffsetsCount; arrPool1 = previousBreakingData.breakOffsets; arrPool2 = previousBreakingData.breakingOffsetsVisibleColumn; - previousBreakingData.breakingColumn = firstLineBreakingColumn; previousBreakingData.breakOffsets = breakingOffsets; previousBreakingData.breakingOffsetsVisibleColumn = breakingOffsetsVisibleColumn; previousBreakingData.wrappedTextIndentLength = wrappedTextIndentLength; return previousBreakingData; - - // return new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); - - // const expected = createLineMapping(classifier, lineText, tabSize, firstLineBreakingColumn, columnsForFullWidthChar, hardWrappingIndent); - // const actual = new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); - // try { - // actual.assertEqual(expected); - // } catch (err) { - // console.log(`BREAKING!!`); - // console.log(err); - // console.log(` - // firstLineBreakingColumn: ${firstLineBreakingColumn} - - // previous breaks: ${JSON.stringify(prevBreakingOffsets)}, breakingOffsetsVisibleColumn: ${JSON.stringify(prevBreakingOffsetsVisibleColumn)} - // expected breaks: ${JSON.stringify(expected?.breakOffsets)}, breakingOffsetsVisibleColumn: ${JSON.stringify(expected?.breakingOffsetsVisibleColumn)} - // actual breaks: ${JSON.stringify(actual?.breakOffsets)}, breakingOffsetsVisibleColumn: ${JSON.stringify(actual?.breakingOffsetsVisibleColumn)} - - // previous str: ${toAnnotatedText(lineText, previousBreakingData)} - // expected str: ${toAnnotatedText(lineText, expected)} - // actual str: ${toAnnotatedText(lineText, actual)} - - // assertIncrementalLineMapping( - // factory, ${str(lineText)}, 4, - // ${previousBreakingData.breakingColumn}, ${str(toAnnotatedText(lineText, previousBreakingData))}, - // ${expected!.breakingColumn}, ${str(toAnnotatedText(lineText, expected))}, - // WrappingIndent.${hardWrappingIndent === WrappingIndent.None ? 'None' : hardWrappingIndent === WrappingIndent.Same ? 'Same' : hardWrappingIndent === WrappingIndent.Indent ? 'Indent' : 'DeepIndent'} - // ); - // `); - // function str(strr: string) { - // return `'${strr.replace(/\\/g, '\\\\').replace(/'/g, '\\\'')}'`; - // } - // function toAnnotatedText(text: string, lineBreakingData: LineBreakingData | null): string { - // // Insert line break markers again, according to algorithm - // let actualAnnotatedText = ''; - // if (lineBreakingData) { - // let previousLineIndex = 0; - // for (let i = 0, len = text.length; i < len; i++) { - // let r = LineBreakingData.getOutputPositionOfInputOffset(lineBreakingData.breakOffsets, i); - // if (previousLineIndex !== r.outputLineIndex) { - // previousLineIndex = r.outputLineIndex; - // actualAnnotatedText += '|'; - // } - // actualAnnotatedText += text.charAt(i); - // } - // } else { - // // No wrapping - // actualAnnotatedText = text; - // } - // return actualAnnotatedText; - // } - // } - // return actual; } function createLineMapping(classifier: WrappingCharacterClassifier, lineText: string, tabSize: number, firstLineBreakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): LineBreakingData | null { @@ -459,7 +406,7 @@ function createLineMapping(classifier: WrappingCharacterClassifier, lineText: st breakingOffsets[breakingOffsetsCount] = len; breakingOffsetsVisibleColumn[breakingOffsetsCount] = visibleColumn; - return new LineBreakingData(firstLineBreakingColumn, breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); + return new LineBreakingData(breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); } function computeCharWidth(charCode: number, visibleColumn: number, tabSize: number, columnsForFullWidthChar: number): number { diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 90f3c4c20d8ab..9f66266bc941c 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -28,35 +28,11 @@ export class OutputPosition { export class LineBreakingData { constructor( - public breakingColumn: number, public breakOffsets: number[], public breakingOffsetsVisibleColumn: number[], public wrappedTextIndentLength: number ) { } - assertEqual(other: LineBreakingData | null): void { - if (other === null) { - throw new Error(`x--unexpected--1`); - } - if (other.breakingColumn !== this.breakingColumn) { - throw new Error(`x--unexpected--2`); - } - if (other.wrappedTextIndentLength !== this.wrappedTextIndentLength) { - throw new Error(`x--unexpected--3`); - } - if (other.breakOffsets.length !== this.breakOffsets.length) { - throw new Error(`x--unexpected--4`); - } - for (let i = 0; i < this.breakOffsets.length; i++) { - if (this.breakOffsets[i] !== other.breakOffsets[i]) { - throw new Error(`x--unexpected--5`); - } - if (this.breakingOffsetsVisibleColumn[i] !== other.breakingOffsetsVisibleColumn[i]) { - throw new Error(`x--unexpected--6`); - } - } - } - public static getInputOffsetOfOutputPosition(breakOffsets: number[], outputLineIndex: number, outputOffset: number): number { if (outputLineIndex === 0) { return outputOffset; From afd58aa9b7be44934a067829196d38ba8b20f054 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Fri, 10 Jan 2020 08:45:04 +0100 Subject: [PATCH 123/315] Fix compilation --- .../editor/test/common/viewModel/splitLinesCollection.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts index 56f06a2a2bc6d..ba4228ce9a644 100644 --- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts +++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts @@ -781,7 +781,7 @@ function createLineMapping(breakingLengths: number[], breakingOffsetsVisibleColu for (let i = 0; i < breakingLengths.length; i++) { sums[i] = (i > 0 ? sums[i - 1] : 0) + breakingLengths[i]; } - return new LineBreakingData(0, sums, breakingOffsetsVisibleColumn, wrappedTextIndentWidth); + return new LineBreakingData(sums, breakingOffsetsVisibleColumn, wrappedTextIndentWidth); } function createModel(text: string): ISimpleModel { From 2dfe5bec96b494593796a75ec3806899d25a720c Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Fri, 10 Jan 2020 08:47:11 +0100 Subject: [PATCH 124/315] Fix tests --- .../common/viewModel/characterHardWrappingLineMapper.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts index ef61ca5b18353..db130c390b1b0 100644 --- a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts +++ b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts @@ -44,7 +44,8 @@ function toAnnotatedText(text: string, lineBreakingData: LineBreakingData | null function getLineBreakingData(factory: ILineMapperFactory, tabSize: number, breakAfter: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent, text: string, previousLineBreakingData: LineBreakingData | null): LineBreakingData | null { const lineMappingComputer = factory.createLineMappingComputer(tabSize, breakAfter, columnsForFullWidthChar, wrappingIndent); - lineMappingComputer.addRequest(text, previousLineBreakingData); + const previousLineBreakingDataClone = previousLineBreakingData ? new LineBreakingData(previousLineBreakingData.breakOffsets.slice(0), previousLineBreakingData.breakingOffsetsVisibleColumn.slice(0), previousLineBreakingData.wrappedTextIndentLength) : null; + lineMappingComputer.addRequest(text, previousLineBreakingDataClone); return lineMappingComputer.finalize()[0]; } From c9275df30c77078aa6562abe46bc547dc1907cfd Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 10 Jan 2020 08:52:22 +0100 Subject: [PATCH 125/315] fix BulkEditPane-ctor --- .../contrib/bulkEdit/browser/bulkEdit.contribution.ts | 2 +- src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts index 8f42d383fde9d..19e206bf2c689 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts @@ -9,7 +9,7 @@ import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } fr import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { WorkspaceEdit } from 'vs/editor/common/modes'; -import { BulkEditPanel as BulkEditPane } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPane'; +import { BulkEditPane } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPane'; import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry } from 'vs/workbench/common/views'; import { localize } from 'vs/nls'; import { ViewPaneContainer, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index e4b5a81d91c32..11ab40d84e266 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -24,13 +24,14 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; const enum State { Data = 'data', Message = 'message' } -export class BulkEditPanel extends ViewPane { +export class BulkEditPane extends ViewPane { static readonly ID = 'refactorPreview'; @@ -46,6 +47,7 @@ export class BulkEditPanel extends ViewPane { private _currentInput?: BulkFileOperations; constructor( + options: IViewletViewOptions, @IInstantiationService private readonly _instaService: IInstantiationService, @IEditorService private readonly _editorService: IEditorService, @ILabelService private readonly _labelService: ILabelService, @@ -56,7 +58,7 @@ export class BulkEditPanel extends ViewPane { @IContextKeyService contextKeyService: IContextKeyService ) { super( - { id: BulkEditPanel.ID, title: localize('title', "Refactor Preview") }, + options, keybindingService, contextMenuService, configurationService, contextKeyService ); } From 51fc8bf0c33c670791f1082533b8c500c2dc0c96 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Fri, 10 Jan 2020 00:04:30 -0800 Subject: [PATCH 126/315] Initial approach to search editor UI. --- .../search/browser/media/searchEditor.css | 165 +++++++ .../search/browser/patternInputWidget.ts | 3 +- .../search/browser/search.contribution.ts | 33 +- .../contrib/search/browser/searchActions.ts | 31 +- .../contrib/search/browser/searchEditor.ts | 401 ++++++++++++++++-- .../contrib/search/browser/searchView.ts | 2 +- .../contrib/search/browser/searchWidget.ts | 52 ++- .../contrib/search/common/constants.ts | 1 + .../services/search/common/search.ts | 1 + 9 files changed, 657 insertions(+), 32 deletions(-) create mode 100644 src/vs/workbench/contrib/search/browser/media/searchEditor.css diff --git a/src/vs/workbench/contrib/search/browser/media/searchEditor.css b/src/vs/workbench/contrib/search/browser/media/searchEditor.css new file mode 100644 index 0000000000000..c12e7737acc27 --- /dev/null +++ b/src/vs/workbench/contrib/search/browser/media/searchEditor.css @@ -0,0 +1,165 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.search-editor { + display: flex; + flex-direction: column; +} + +.search-editor .search-results { + flex: 1; +} + +.search-editor .query-container { + margin: 0px 12px 12px 2px; + padding-top: 6px; +} + +.search-editor .search-widget .toggle-replace-button { + position: absolute; + top: 0; + left: 0; + width: 16px; + height: 100%; + box-sizing: border-box; + background-position: center center; + background-repeat: no-repeat; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} + +.search-editor .search-widget .search-container, +.search-editor .search-widget .replace-container { + margin-left: 18px; + display: flex; + align-items: center; +} + +.search-editor .search-widget .monaco-findInput { + display: inline-block; + vertical-align: middle; + width: 100%; +} + +.search-editor .search-widget .monaco-inputbox > .wrapper { + height: 100%; +} + +.search-editor .search-widget .monaco-inputbox > .wrapper > .mirror, +.search-editor .search-widget .monaco-inputbox > .wrapper > textarea.input { + padding: 3px; + padding-left: 4px; +} + +.search-editor .search-widget .monaco-inputbox > .wrapper > .mirror { + max-height: 134px; +} + +/* NOTE: height is also used in searchWidget.ts as a constant*/ +.search-editor .search-widget .monaco-inputbox > .wrapper > textarea.input { + overflow: initial; + height: 24px; /* set initial height before measure */ +} + +.search-editor .monaco-inputbox > .wrapper > textarea.input { + scrollbar-width: none; /* Firefox: hide scrollbar */ +} + +.search-editor .monaco-inputbox > .wrapper > textarea.input::-webkit-scrollbar { + display: none; +} + +.search-editor .search-widget .context-lines-input { + display: none; +} + +.search-editor .search-widget.show-context .context-lines-input { + display: inherit; + margin-left: 5px; + margin-right: 2px; + max-width: 50px; +} + + +.search-editor .search-widget .replace-container { + margin-top: 6px; + position: relative; + display: inline-flex; +} + +.search-editor .search-widget .replace-input { + position: relative; + display: flex; + vertical-align: middle; + width: auto !important; + height: 25px; +} + +.search-editor .search-widget .replace-input > .controls { + position: absolute; + top: 3px; + right: 2px; +} + +.search-editor .search-widget .replace-container.disabled { + display: none; +} + +.search-editor .search-widget .replace-container .monaco-action-bar { + margin-left: 0; +} + +.search-editor .search-widget .replace-container .monaco-action-bar { + height: 25px; +} + +.search-editor .search-widget .replace-container .monaco-action-bar .action-item .codicon { + background-repeat: no-repeat; + width: 25px; + height: 25px; + margin-right: 0; + display: flex; + align-items: center; + justify-content: center; +} + +.search-editor .includes-excludes { + min-height: 1em; + position: relative; + margin: 0 0 0 17px; +} + +.search-editor .includes-excludes .expand { + position: absolute; + right: -2px; + cursor: pointer; + width: 25px; + height: 16px; + z-index: 2; /* Force it above the search results message, which has a negative top margin */ +} + +.search-editor .includes-excludes .file-types { + display: none; +} + +.search-editor .includes-excludes.expanded .file-types { + display: inherit; +} + +.search-editor .includes-excludes.expanded .file-types:last-child { + padding-bottom: 10px; +} + +.search-editor .includes-excludes.expanded h4 { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + padding: 4px 0 0; + margin: 0; + font-size: 11px; + font-weight: normal; +} diff --git a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts index 8b34c22cea70f..726ce4ee0b2e2 100644 --- a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts @@ -10,7 +10,7 @@ import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { IInputValidator, HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyCode } from 'vs/base/common/keyCodes'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler, attachCheckboxStyler } from 'vs/platform/theme/common/styler'; @@ -170,6 +170,7 @@ export class PatternInputWidget extends Widget { case KeyCode.Escape: this._onCancel.fire(); return; + case KeyCode.Tab: case KeyCode.Tab | KeyMod.Shift: return; default: if (this.searchConfig.searchOnType) { this._onCancel.fire(); diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 6a83637408eee..ab59270e380cd 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -39,7 +39,7 @@ import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition import { OpenAnythingHandler } from 'vs/workbench/contrib/search/browser/openAnythingHandler'; import { OpenSymbolHandler } from 'vs/workbench/contrib/search/browser/openSymbolHandler'; import { registerContributions as replaceContributions } from 'vs/workbench/contrib/search/browser/replaceContributions'; -import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, OpenResultsInEditorAction, RerunEditorSearchAction, RerunEditorSearchWithContextAction, ExpandAllAction } from 'vs/workbench/contrib/search/browser/searchActions'; +import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, OpenResultsInEditorAction, RerunEditorSearchAction, RerunEditorSearchWithContextAction, ExpandAllAction, OpenSearchEditorAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel'; import { SearchView, SearchViewPosition } from 'vs/workbench/contrib/search/browser/searchView'; import { registerContributions as searchWidgetContributions } from 'vs/workbench/contrib/search/browser/searchWidget'; @@ -56,6 +56,9 @@ import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/ex import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { assertType } from 'vs/base/common/types'; import { SearchViewPaneContainer } from 'vs/workbench/contrib/search/browser/searchViewlet'; +import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; +import { SearchEditor, SearchEditorInput } from 'vs/workbench/contrib/search/browser/searchEditor'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; registerSingleton(ISearchWorkbenchService, SearchWorkbenchService, true); registerSingleton(ISearchHistoryService, SearchHistoryService, true); @@ -647,6 +650,11 @@ registry.registerWorkbenchAction( 'Search: Open Results in Editor', category, ContextKeyExpr.and(Constants.EnableSearchEditorPreview)); +registry.registerWorkbenchAction( + SyncActionDescriptor.create(OpenSearchEditorAction, OpenSearchEditorAction.ID, OpenSearchEditorAction.LABEL), + 'Search: Open new Search Editor', category, + ContextKeyExpr.and(Constants.EnableSearchEditorPreview)); + const searchEditorCategory = nls.localize({ comment: ['The name of the tabbed search view'], key: 'searcheditor' }, "Search Editor"); registry.registerWorkbenchAction( SyncActionDescriptor.create(RerunEditorSearchAction, RerunEditorSearchAction.ID, RerunEditorSearchAction.LABEL, @@ -828,6 +836,17 @@ configurationRegistry.registerConfiguration({ default: false, description: nls.localize('search.enableSearchEditorPreview', "Experimental: When enabled, allows opening workspace search results in an editor.") }, + 'search.searchEditorPreview.doubleClickBehaviour': { + type: 'string', + enum: ['selectWord', 'goToLocation', 'openLocationToSide'], + default: 'goToLocation', + enumDescriptions: [ + nls.localize('search.searchEditorPreview.doubleClickBehaviour.selectWord', "Double clicking selects the word under the cursor."), + nls.localize('search.searchEditorPreview.doubleClickBehaviour.goToLocation', "Double clicking opens the result in the active editor group."), + nls.localize('search.searchEditorPreview.doubleClickBehaviour.openLocationToSide', "Double clicking opens the result in the editor group to the side, creating one if it does not yet exist."), + ], + markdownDescription: nls.localize('search.searchEditorPreview.doubleClickBehaviour', "Configure effect of double clicking a result in a Search Editor.\n\n `#search.enableSearchEditorPreview#` must be enabled for this setting to have an effect.") + }, 'search.sortOrder': { 'type': 'string', 'enum': [SearchSortOrder.Default, SearchSortOrder.FileNames, SearchSortOrder.Type, SearchSortOrder.Modified, SearchSortOrder.CountDescending, SearchSortOrder.CountAscending], @@ -872,3 +891,15 @@ MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { }, order: 2 }); + + +Registry.as(EditorExtensions.Editors).registerEditor( + EditorDescriptor.create( + SearchEditor, + SearchEditor.ID, + nls.localize('defaultPreferencesEditor', "Search Editor") + ), + [ + new SyncDescriptor(SearchEditorInput) + ] +); diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index 785f9f4575506..3be9ac8273743 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -29,7 +29,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { SearchViewPaneContainer } from 'vs/workbench/contrib/search/browser/searchViewlet'; import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel'; import { ITreeNavigator } from 'vs/base/browser/ui/tree/tree'; -import { createEditorFromSearchResult, refreshActiveEditorSearch } from 'vs/workbench/contrib/search/browser/searchEditor'; +import { createEditorFromSearchResult, refreshActiveEditorSearch, openNewSearchEditor } from 'vs/workbench/contrib/search/browser/searchEditor'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; @@ -508,6 +508,30 @@ export class CancelSearchAction extends Action { } } +export class OpenSearchEditorAction extends Action { + + static readonly ID: string = Constants.OpenNewEditorCommandId; + static readonly LABEL = nls.localize('search.openNewEditor', "Open new Search Editor"); + + constructor(id: string, label: string, + @IEditorService private editorService: IEditorService, + @IConfigurationService private configurationService: IConfigurationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + super(id, label); + } + + get enabled(): boolean { + return true; + } + + async run() { + if (this.configurationService.getValue('search').enableSearchEditorPreview) { + await openNewSearchEditor(this.editorService, this.instantiationService); + } + } +} + export class OpenResultsInEditorAction extends Action { static readonly ID: string = Constants.OpenInEditorCommandId; @@ -518,7 +542,8 @@ export class OpenResultsInEditorAction extends Action { @IPanelService private panelService: IPanelService, @ILabelService private labelService: ILabelService, @IEditorService private editorService: IEditorService, - @IConfigurationService private configurationService: IConfigurationService + @IConfigurationService private configurationService: IConfigurationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(id, label, 'codicon-go-to-file'); } @@ -535,7 +560,7 @@ export class OpenResultsInEditorAction extends Action { async run() { const searchView = getSearchView(this.viewletService, this.panelService); if (searchView && this.configurationService.getValue('search').enableSearchEditorPreview) { - await createEditorFromSearchResult(searchView.searchResult, searchView.searchIncludePattern.getValue(), searchView.searchExcludePattern.getValue(), this.labelService, this.editorService); + await createEditorFromSearchResult(searchView.searchResult, searchView.searchIncludePattern.getValue(), searchView.searchExcludePattern.getValue(), this.labelService, this.editorService, this.instantiationService); } } } diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index fda962982471b..83b746109a586 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -3,26 +3,356 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Match, searchMatchComparer, FileMatch, SearchResult, SearchModel } from 'vs/workbench/contrib/search/common/searchModel'; -import { repeat } from 'vs/base/common/strings'; -import { ILabelService } from 'vs/platform/label/common/label'; +import * as DOM from 'vs/base/browser/dom'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { coalesce, flatten } from 'vs/base/common/arrays'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { URI } from 'vs/base/common/uri'; -import { ITextQuery, IPatternInfo, ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import * as network from 'vs/base/common/network'; +import { repeat } from 'vs/base/common/strings'; +import { assertIsDefined } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import 'vs/css!./media/searchEditor'; +import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import type { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; -import { ITextModel, TrackedRangeStickiness, EndOfLinePreference } from 'vs/editor/common/model'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; -import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { EndOfLinePreference, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { localize } from 'vs/nls'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { searchEditorFindMatch, searchEditorFindMatchBorder } from 'vs/platform/theme/common/colorRegistry'; +import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; +import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; -import { localize } from 'vs/nls'; -import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ExcludePatternInputWidget, PatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget'; +import { SearchWidget } from 'vs/workbench/contrib/search/browser/searchWidget'; +import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; +import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search'; +import { FileMatch, Match, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IPatternInfo, ISearchConfigurationProperties, ITextQuery } from 'vs/workbench/services/search/common/search'; +import { Delayer } from 'vs/base/common/async'; + +const RESULT_LINE_REGEX = /^(\s+)(\d+)(:| )(\s+)(.*)$/; + +type SearchConfiguration = { + query: string, + includes: string, + excludes: string, + contextLines: number, + wholeWord: boolean, + caseSensitive: boolean, + regexp: boolean, + useIgnores: boolean +}; + +let searchEditorInputInstances = 0; +export class SearchEditorInput extends EditorInput { + static readonly ID: string = 'workbench.editorinputs.searchEditorInput'; + + public config: SearchConfiguration; + private instanceNumber: number = searchEditorInputInstances++; + + constructor( + config: SearchConfiguration | undefined, + @IModelService private readonly modelService: IModelService, + @IModeService private readonly modeService: IModeService, + ) { + super(); + + if (config === undefined) { + this.config = { query: '', includes: '', excludes: '', contextLines: 0, wholeWord: false, caseSensitive: false, regexp: false, useIgnores: true }; + } else { + this.config = config; + } + + const searchResultMode = this.modeService.create('search-result'); + this.modelService.createModel('', searchResultMode, this.getResource()); + } + + getTypeId(): string { + return SearchEditorInput.ID; + } + + getResource(): URI { + return URI.from({ scheme: 'code-search', fragment: `${this.instanceNumber}` }); + } + + getName(): string { + return this.config.query ? localize('searchTitle.withQuery', "Search: {0}", this.config.query) : localize('searchTitle', "Search"); + } + + setConfig(config: SearchConfiguration) { + this.config = config; + this._onDidChangeLabel.fire(); + } + + async resolve() { + return null; + } + + dispose() { + this.modelService.destroyModel(this.getResource()); + super.dispose(); + } +} + +export class SearchEditor extends BaseEditor { + static readonly ID: string = 'workbench.editor.searchEditor'; + + private queryEditorWidget!: SearchWidget; + private searchResultEditor!: CodeEditorWidget; + private queryEditorContainer!: HTMLElement; + private dimension?: DOM.Dimension; + private inputPatternIncludes!: PatternInputWidget; + private inputPatternExcludes!: ExcludePatternInputWidget; + private includesExcludesContainer!: HTMLElement; + private toggleQueryDetailsButton!: HTMLElement; + + private runSearchDelayer = new Delayer(300); + private pauseSearching: boolean = false; + + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService, + @IModelService private readonly modelService: IModelService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @ILabelService private readonly labelService: ILabelService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextViewService private readonly contextViewService: IContextViewService, + @ICommandService private readonly commandService: ICommandService, + ) { + super(SearchEditor.ID, telemetryService, themeService, storageService); + } + + createEditor(parent: HTMLElement) { + DOM.addClass(parent, 'search-editor'); + + // Query + this.queryEditorContainer = DOM.append(parent, DOM.$('.query-container')); + + this.queryEditorWidget = this._register(this.instantiationService.createInstance(SearchWidget, this.queryEditorContainer, { _hideReplaceToggle: true, showContextToggle: true })); + this._register(this.queryEditorWidget.onReplaceToggled(() => this.reLayout())); + this._register(this.queryEditorWidget.onDidHeightChange(() => this.reLayout())); + this.queryEditorWidget.onSearchSubmit(() => this.runSearch()); + this.queryEditorWidget.searchInput.onDidOptionChange(() => this.runSearch()); + this.queryEditorWidget.onDidToggleContext(() => this.runSearch()); + + // Includes/Excludes Dropdown + this.includesExcludesContainer = DOM.append(this.queryEditorContainer, DOM.$('.includes-excludes')); + // // Toggle query details button + this.toggleQueryDetailsButton = DOM.append(this.includesExcludesContainer, DOM.$('.expand.codicon.codicon-ellipsis', { tabindex: 0, role: 'button', title: localize('moreSearch', "Toggle Search Details") })); + this._register(DOM.addDisposableListener(this.toggleQueryDetailsButton, DOM.EventType.CLICK, e => { + DOM.EventHelper.stop(e); + this.toggleQueryDetails(); + })); + this._register(DOM.addDisposableListener(this.toggleQueryDetailsButton, DOM.EventType.KEY_UP, (e: KeyboardEvent) => { + const event = new StandardKeyboardEvent(e); + + if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { + DOM.EventHelper.stop(e); + this.toggleQueryDetails(); + } + })); + this._register(DOM.addDisposableListener(this.toggleQueryDetailsButton, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => { + const event = new StandardKeyboardEvent(e); + + if (event.equals(KeyMod.Shift | KeyCode.Tab)) { + if (this.queryEditorWidget.isReplaceActive()) { + this.queryEditorWidget.focusReplaceAllAction(); + } else { + this.queryEditorWidget.isReplaceShown() ? this.queryEditorWidget.replaceInput.focusOnPreserve() : this.queryEditorWidget.focusRegexAction(); + } + DOM.EventHelper.stop(e); + } + })); + + // // Includes + + const folderIncludesList = DOM.append(this.includesExcludesContainer, DOM.$('.file-types.includes')); + const filesToIncludeTitle = localize('searchScope.includes', "files to include"); + DOM.append(folderIncludesList, DOM.$('h4', undefined, filesToIncludeTitle)); + + this.inputPatternIncludes = this._register(this.instantiationService.createInstance(PatternInputWidget, folderIncludesList, this.contextViewService, { + ariaLabel: localize('label.includes', 'Search Include Patterns'), + })); + this.inputPatternIncludes.onSubmit(_triggeredOnType => this.runSearch()); + + // // Excludes + const excludesList = DOM.append(this.includesExcludesContainer, DOM.$('.file-types.excludes')); + const excludesTitle = localize('searchScope.excludes', "files to exclude"); + DOM.append(excludesList, DOM.$('h4', undefined, excludesTitle)); + + this.inputPatternExcludes = this._register(this.instantiationService.createInstance(ExcludePatternInputWidget, excludesList, this.contextViewService, { + ariaLabel: localize('label.excludes', 'Search Exclude Patterns'), + })); + + this.inputPatternExcludes.onSubmit(_triggeredOnType => this.runSearch()); + this.inputPatternExcludes.onChangeIgnoreBox(() => this.runSearch()); + + // Editor + const searchResultContainer = DOM.append(parent, DOM.$('.search-results')); + const configuration: IEditorOptions = this.configurationService.getValue('editor', { overrideIdentifier: 'search-result' }); + const options: ICodeEditorWidgetOptions = {}; + this.searchResultEditor = this._register(this.instantiationService.createInstance(CodeEditorWidget, searchResultContainer, configuration, options)); + this.searchResultEditor.onMouseUp(e => { + + if (e.event.detail === 2) { + const behaviour = this.configurationService.getValue('search').searchEditorPreview.doubleClickBehaviour; + const position = e.target.position; + if (position && behaviour !== 'selectWord') { + const line = this.searchResultEditor.getModel()?.getLineContent(position.lineNumber) ?? ''; + if (line.match(RESULT_LINE_REGEX)) { + this.searchResultEditor.setSelection(Range.fromPositions(position)); + this.commandService.executeCommand(behaviour === 'goToLocation' ? 'editor.action.goToDeclaration' : 'editor.action.openDeclarationToTheSide'); + } + } + } + }); + } + + private async runSearch() { + if (!this.pauseSearching) { + this.runSearchDelayer.trigger(() => this.doRunSearch()); + } + } + + private async doRunSearch() { + const startInput = this.input; + + const config: SearchConfiguration = { + caseSensitive: this.queryEditorWidget.searchInput.getCaseSensitive(), + contextLines: this.queryEditorWidget.contextLines(), + excludes: this.inputPatternExcludes.getValue(), + includes: this.inputPatternIncludes.getValue(), + query: this.queryEditorWidget.searchInput.getValue(), + regexp: this.queryEditorWidget.searchInput.getRegex(), + wholeWord: this.queryEditorWidget.searchInput.getWholeWords(), + useIgnores: this.inputPatternExcludes.useExcludesAndIgnoreFiles() + }; + + const content: IPatternInfo = { + pattern: config.query, + isRegExp: config.regexp, + isCaseSensitive: config.caseSensitive, + isWordMatch: config.wholeWord, + }; + + const options: ITextQueryBuilderOptions = { + _reason: 'searchEditor', + extraFileResources: this.instantiationService.invokeFunction(getOutOfWorkspaceEditorResources), + maxResults: 10000, + disregardIgnoreFiles: !config.useIgnores, + disregardExcludeSettings: !config.useIgnores, + excludePattern: config.excludes, + includePattern: config.includes, + previewOptions: { + matchLines: 1, + charsPerLine: 1000 + }, + afterContext: config.contextLines, + beforeContext: config.contextLines, + isSmartCase: this.configurationService.getValue('search').smartCase, + expandPatterns: true + }; + + const folderResources = this.contextService.getWorkspace().folders; + let query: ITextQuery; + try { + const queryBuilder = this.instantiationService.createInstance(QueryBuilder); + query = queryBuilder.text(content, folderResources.map(folder => folder.uri), options); + } + catch (err) { + return; + } + const searchModel = this.instantiationService.createInstance(SearchModel); + await searchModel.search(query); + if (this.input !== startInput) { + searchModel.dispose(); + return; + } + + (assertIsDefined(this._input) as SearchEditorInput).setConfig(config); + + const labelFormatter = (uri: URI): string => this.labelService.getUriLabel(uri, { relative: true }); + const results = serializeSearchResultForEditor(searchModel.searchResult, config.includes, config.excludes, config.contextLines, labelFormatter, false); + const textModel = assertIsDefined(this.searchResultEditor.getModel()); + textModel.setValue(results.text.join(lineDelimiter)); + textModel.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); + + searchModel.dispose(); + } + + layout(dimension: DOM.Dimension) { + this.dimension = dimension; + this.reLayout(); + } + + focusInput() { + this.queryEditorWidget.focus(); + } + + private reLayout() { + if (this.dimension) { + this.queryEditorWidget.setWidth(this.dimension.width - 28 /* container margin */); + this.searchResultEditor.layout({ height: this.dimension.height - DOM.getTotalHeight(this.queryEditorContainer), width: this.dimension.width }); + this.inputPatternExcludes.setWidth(this.dimension.width - 28 /* container margin */); + this.inputPatternIncludes.setWidth(this.dimension.width - 28 /* container margin */); + } + } + + async setInput(newInput: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { + if (!(newInput instanceof SearchEditorInput)) { return; } + this.pauseSearching = true; + // TODO: Manage model lifecycle in SearchEditorInput + const model = this.modelService.getModel(newInput.getResource()); + + this.searchResultEditor.setModel(model); + this.queryEditorWidget.setValue(newInput.config.query, true); + this.queryEditorWidget.searchInput.setCaseSensitive(newInput.config.caseSensitive); + this.queryEditorWidget.searchInput.setRegex(newInput.config.regexp); + this.queryEditorWidget.searchInput.setWholeWords(newInput.config.wholeWord); + this.queryEditorWidget.setContextLines(newInput.config.contextLines); + this.inputPatternExcludes.setValue(newInput.config.excludes); + this.inputPatternIncludes.setValue(newInput.config.includes); + this.inputPatternExcludes.setUseExcludesAndIgnoreFiles(newInput.config.useIgnores); + + this.focusInput(); + await super.setInput(newInput, options, token); + this.pauseSearching = false; + } + + toggleQueryDetails(): void { + const cls = 'expanded'; + const shouldShow = !DOM.hasClass(this.includesExcludesContainer, cls); + + if (shouldShow) { + this.toggleQueryDetailsButton.setAttribute('aria-expanded', 'true'); + DOM.addClass(this.includesExcludesContainer, cls); + } else { + this.toggleQueryDetailsButton.setAttribute('aria-expanded', 'false'); + DOM.removeClass(this.includesExcludesContainer, cls); + } + + this.reLayout(); + } + + getModel() { + return this.searchResultEditor.getModel(); + } +} // Using \r\n on Windows inserts an extra newline between results. const lineDelimiter = '\n'; @@ -221,8 +551,8 @@ const searchHeaderToContentPattern = (header: string[]): SearchHeader => { return query; }; -const serializeSearchResultForEditor = (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, contextLines: number, labelFormatter: (x: URI) => string): SearchResultSerialization => { - const header = contentPatternToSearchResultHeader(searchResult.query, rawIncludePattern, rawExcludePattern, contextLines); +const serializeSearchResultForEditor = (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, contextLines: number, labelFormatter: (x: URI) => string, includeHeader: boolean): SearchResultSerialization => { + const header = includeHeader ? contentPatternToSearchResultHeader(searchResult.query, rawIncludePattern, rawExcludePattern, contextLines) : []; const allResults = flattenSearchResultSerializations( flatten( @@ -290,20 +620,29 @@ export const refreshActiveEditorSearch = await searchModel.search(query); const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); - const results = serializeSearchResultForEditor(searchModel.searchResult, contentPattern.includes, contentPattern.excludes, contextLines, labelFormatter); + const results = serializeSearchResultForEditor(searchModel.searchResult, contentPattern.includes, contentPattern.excludes, contextLines, labelFormatter, false); textModel.setValue(results.text.join(lineDelimiter)); textModel.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); }; +export const openNewSearchEditor = + async (editorService: IEditorService, instantiationService: IInstantiationService) => { + await editorService.openEditor(instantiationService.createInstance(SearchEditorInput, undefined), { pinned: true }); + }; export const createEditorFromSearchResult = - async (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, labelService: ILabelService, editorService: IEditorService) => { - const searchTerm = searchResult.query?.contentPattern.pattern.replace(/[^\w-_. ]/g, '') || 'Search'; + async (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, labelService: ILabelService, editorService: IEditorService, instantiationService: IInstantiationService) => { + if (!searchResult.query) { + console.error('Expected searchResult.query to be defined. Got', searchResult); + return; + } + + const searchTerm = searchResult.query.contentPattern.pattern.replace(/[^\w-_. ]/g, '') || 'Search'; const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); - const results = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, 0, labelFormatter); + const results = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, 0, labelFormatter, false); const contents = results.text.join(lineDelimiter); let possible = { contents, @@ -312,6 +651,7 @@ export const createEditorFromSearchResult = }; let id = 0; + let existing = editorService.getOpened(possible); while (existing) { if (existing instanceof UntitledTextEditorInput) { @@ -325,10 +665,23 @@ export const createEditorFromSearchResult = existing = editorService.getOpened(possible); } - const editor = await editorService.openEditor(possible); - const control = editor?.getControl()!; - const model = control.getModel() as ITextModel; - + const editor = await editorService.openEditor( + instantiationService.createInstance( + SearchEditorInput, + { + query: searchResult.query.contentPattern.pattern, + regexp: !!searchResult.query.contentPattern.isRegExp, + caseSensitive: !!searchResult.query.contentPattern.isCaseSensitive, + wholeWord: !!searchResult.query.contentPattern.isWordMatch, + includes: rawIncludePattern, + excludes: rawExcludePattern, + contextLines: 0, + useIgnores: !searchResult.query.userDisabledExcludesAndIgnoreFiles, + }), + { pinned: true }) as SearchEditor; + + const model = assertIsDefined(editor.getModel()); + model.setValue(contents); model.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); }; diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 527fd051a1e1c..16ba94358caa0 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -1559,7 +1559,7 @@ export class SearchView extends ViewPane { this.messageDisposables.push(dom.addDisposableListener(openInEditorLink, dom.EventType.CLICK, (e: MouseEvent) => { dom.EventHelper.stop(e, false); - createEditorFromSearchResult(this.searchResult, this.searchIncludePattern.getValue(), this.searchExcludePattern.getValue(), this.labelService, this.editorService); + createEditorFromSearchResult(this.searchResult, this.searchIncludePattern.getValue(), this.searchExcludePattern.getValue(), this.labelService, this.editorService, this.instantiationService); })); } else { diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 77defd9110c5c..402f4313f1a10 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -9,7 +9,7 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Button, IButtonOptions } from 'vs/base/browser/ui/button/button'; import { FindInput, IFindInputOptions } from 'vs/base/browser/ui/findinput/findInput'; import { ReplaceInput } from 'vs/base/browser/ui/findinput/replaceInput'; -import { IMessage } from 'vs/base/browser/ui/inputbox/inputBox'; +import { IMessage, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { Widget } from 'vs/base/browser/ui/widget'; import { Action } from 'vs/base/common/actions'; import { Delayer } from 'vs/base/common/async'; @@ -34,6 +34,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { isMacintosh } from 'vs/base/common/platform'; +import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; /** Specified in searchview.css */ export const SingleLineInputHeight = 24; @@ -47,6 +48,8 @@ export interface ISearchWidgetOptions { searchHistory?: string[]; replaceHistory?: string[]; preserveCase?: boolean; + _hideReplaceToggle?: boolean; // TODO: Search Editor's replace experience + showContextToggle?: boolean; } class ReplaceAllAction extends Action { @@ -151,7 +154,13 @@ export class SearchWidget extends Widget { private _onDidHeightChange = this._register(new Emitter()); readonly onDidHeightChange: Event = this._onDidHeightChange.event; + private readonly _onDidToggleContext = new Emitter(); + readonly onDidToggleContext: Event = this._onDidToggleContext.event; + private temporarilySkipSearchOnChange = false; + private showContextCheckbox!: Checkbox; + private contextLinesInput!: InputBox; + private _contextLineInputDelayer: Delayer; constructor( container: HTMLElement, @@ -168,7 +177,10 @@ export class SearchWidget extends Widget { this.replaceActive = Constants.ReplaceActiveKey.bindTo(this.contextKeyService); this.searchInputBoxFocused = Constants.SearchInputBoxFocusedKey.bindTo(this.contextKeyService); this.replaceInputBoxFocused = Constants.ReplaceInputBoxFocusedKey.bindTo(this.contextKeyService); + this._replaceHistoryDelayer = new Delayer(500); + this._contextLineInputDelayer = new Delayer(300); + this._searchDelayer = this._register(new Delayer(this.searchConfiguration.searchOnTypeDebouncePeriod)); this.render(container, options); @@ -275,7 +287,9 @@ export class SearchWidget extends Widget { this.domNode = dom.append(container, dom.$('.search-widget')); this.domNode.style.position = 'relative'; - this.renderToggleReplaceButton(this.domNode); + if (!options._hideReplaceToggle) { + this.renderToggleReplaceButton(this.domNode); + } this.renderSearchInput(this.domNode, options); this.renderReplaceInput(this.domNode, options); @@ -356,6 +370,36 @@ export class SearchWidget extends Widget { this.ignoreGlobalFindBufferOnNextFocus = false; })); this._register(this.searchInputFocusTracker.onDidBlur(() => this.searchInputBoxFocused.set(false))); + + + this.showContextCheckbox = new Checkbox({ isChecked: false, title: nls.localize('showContext', "Show Context"), actionClassName: 'codicon-list-selection' }); + this._register(this.showContextCheckbox.onChange(() => { + dom.toggleClass(parent, 'show-context', this.showContextCheckbox.checked); + this._onDidToggleContext.fire(); + })); + + if (options.showContextToggle) { + this.contextLinesInput = new InputBox(searchInputContainer, this.contextViewService, { type: 'number' }); + dom.addClass(this.contextLinesInput.element, 'context-lines-input'); + this.contextLinesInput.value = '2'; + this._register(this.contextLinesInput.onDidChange(() => { + if (this.contextLinesInput.value.includes('-')) { + this.contextLinesInput.value = '0'; + } + this._contextLineInputDelayer.trigger(() => this._onDidToggleContext.fire()); + })); + dom.append(searchInputContainer, this.showContextCheckbox.domNode); + } + } + + public setContextLines(lines: number) { + if (!this.contextLinesInput) { return; } + if (lines === 0) { + this.showContextCheckbox.checked = false; + } else { + this.showContextCheckbox.checked = true; + this.contextLinesInput.value = '' + lines; + } } private renderReplaceInput(parent: HTMLElement, options: ISearchWidgetOptions): void { @@ -580,6 +624,10 @@ export class SearchWidget extends Widget { this._onSearchSubmit.fire(triggeredOnType); } + contextLines() { + return this.showContextCheckbox.checked ? +this.contextLinesInput.value : 0; + } + dispose(): void { this.setReplaceAllActionState(false); super.dispose(); diff --git a/src/vs/workbench/contrib/search/common/constants.ts b/src/vs/workbench/contrib/search/common/constants.ts index b7c72d5d5cd06..018bc7c02227e 100644 --- a/src/vs/workbench/contrib/search/common/constants.ts +++ b/src/vs/workbench/contrib/search/common/constants.ts @@ -16,6 +16,7 @@ export const CopyPathCommandId = 'search.action.copyPath'; export const CopyMatchCommandId = 'search.action.copyMatch'; export const CopyAllCommandId = 'search.action.copyAll'; export const OpenInEditorCommandId = 'search.action.openInEditor'; +export const OpenNewEditorCommandId = 'search.action.openNewEditor'; export const RerunEditorSearchCommandId = 'search.action.rerunEditorSearch'; export const RerunEditorSearchWithContextCommandId = 'search.action.rerunEditorSearchWithContext'; export const ClearSearchHistoryCommandId = 'search.action.clearHistory'; diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 21e77c7f90fae..99ba52ad15ff9 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -335,6 +335,7 @@ export interface ISearchConfigurationProperties { searchOnType: boolean; searchOnTypeDebouncePeriod: number; enableSearchEditorPreview: boolean; + searchEditorPreview: { doubleClickBehaviour: 'selectWord' | 'goToLocation' | 'openLocationToSide' }; searchEditorPreviewForceAbsolutePaths: boolean; sortOrder: SearchSortOrder; } From 0e57fa880a8a7c03b08a02f33902fe5c5d2991c1 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Fri, 10 Jan 2020 09:31:22 +0100 Subject: [PATCH 127/315] Renames --- .../editor/browser/widget/codeEditorWidget.ts | 4 +- ...pper.ts => monospaceLineBreaksComputer.ts} | 74 ++++---- .../common/viewModel/splitLinesCollection.ts | 122 ++++++------- .../editor/common/viewModel/viewModelImpl.ts | 26 +-- .../contrib/comment/lineCommentCommand.ts | 1 - .../test/browser/commands/sideEditing.test.ts | 4 +- .../test/browser/controller/cursor.test.ts | 16 +- .../controller/cursorMoveCommand.test.ts | 4 +- ...ts => monospaceLineBreaksComputer.test.ts} | 170 +++++++++--------- .../viewModel/splitLinesCollection.test.ts | 16 +- .../test/common/viewModel/testViewModel.ts | 4 +- 11 files changed, 220 insertions(+), 221 deletions(-) rename src/vs/editor/common/viewModel/{characterHardWrappingLineMapper.ts => monospaceLineBreaksComputer.ts} (81%) rename src/vs/editor/test/common/viewModel/{characterHardWrappingLineMapper.test.ts => monospaceLineBreaksComputer.test.ts} (50%) diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 00d0d4ad3d500..ddfaa481492be 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -50,7 +50,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; +import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; let EDITOR_ID = 0; @@ -1330,7 +1330,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE model.onBeforeAttached(); - const viewModel = new ViewModel(this._id, this._configuration, model, CharacterHardWrappingLineMapperFactory.create(this._configuration.options), (callback) => dom.scheduleAtNextAnimationFrame(callback)); + const viewModel = new ViewModel(this._id, this._configuration, model, MonospaceLineBreaksComputerFactory.create(this._configuration.options), (callback) => dom.scheduleAtNextAnimationFrame(callback)); listenersToRemove.push(model.onDidChangeDecorations((e) => this._onDidChangeModelDecorations.fire(e))); listenersToRemove.push(model.onDidChangeLanguage((e) => { diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts similarity index 81% rename from src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts rename to src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts index 35d9381487f0c..be52ca2265a7f 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts @@ -7,7 +7,7 @@ import { CharCode } from 'vs/base/common/charCode'; import * as strings from 'vs/base/common/strings'; import { WrappingIndent, IComputedEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions'; import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier'; -import { ILineMapperFactory, LineBreakingData, ILineMappingComputer } from 'vs/editor/common/viewModel/splitLinesCollection'; +import { ILineBreaksComputerFactory, LineBreakData, ILineBreaksComputer } from 'vs/editor/common/viewModel/splitLinesCollection'; const enum CharacterClass { NONE = 0, @@ -51,10 +51,10 @@ class WrappingCharacterClassifier extends CharacterClassifier { } } -export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactory { +export class MonospaceLineBreaksComputerFactory implements ILineBreaksComputerFactory { - public static create(options: IComputedEditorOptions): CharacterHardWrappingLineMapperFactory { - return new CharacterHardWrappingLineMapperFactory( + public static create(options: IComputedEditorOptions): MonospaceLineBreaksComputerFactory { + return new MonospaceLineBreaksComputerFactory( options.get(EditorOption.wordWrapBreakBeforeCharacters), options.get(EditorOption.wordWrapBreakAfterCharacters) ); @@ -66,26 +66,26 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor this.classifier = new WrappingCharacterClassifier(breakBeforeChars, breakAfterChars); } - public createLineMappingComputer(tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ILineMappingComputer { + public createLineBreaksComputer(tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ILineBreaksComputer { tabSize = tabSize | 0; //@perf wrappingColumn = +wrappingColumn; //@perf columnsForFullWidthChar = +columnsForFullWidthChar; //@perf let requests: string[] = []; - let previousBreakingData: (LineBreakingData | null)[] = []; + let previousBreakingData: (LineBreakData | null)[] = []; return { - addRequest: (lineText: string, previousLineBreakingData: LineBreakingData | null) => { + addRequest: (lineText: string, previousLineBreakData: LineBreakData | null) => { requests.push(lineText); - previousBreakingData.push(previousLineBreakingData); + previousBreakingData.push(previousLineBreakData); }, finalize: () => { - let result: (LineBreakingData | null)[] = []; + let result: (LineBreakData | null)[] = []; for (let i = 0, len = requests.length; i < len; i++) { - const previousLineBreakingData = previousBreakingData[i]; - if (previousLineBreakingData) { - result[i] = createLineMappingFromPreviousLineMapping(this.classifier, previousLineBreakingData, requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent); + const previousLineBreakData = previousBreakingData[i]; + if (previousLineBreakData) { + result[i] = createLineBreaksFromPreviousLineBreaks(this.classifier, previousLineBreakData, requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent); } else { - result[i] = createLineMapping(this.classifier, requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent); + result[i] = createLineBreaks(this.classifier, requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent); } } return result; @@ -96,8 +96,8 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor let arrPool1: number[] = []; let arrPool2: number[] = []; -function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterClassifier, previousBreakingData: LineBreakingData, lineText: string, tabSize: number, firstLineBreakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): LineBreakingData | null { - if (firstLineBreakingColumn === -1) { +function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterClassifier, previousBreakingData: LineBreakData, lineText: string, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): LineBreakData | null { + if (firstLineBreakColumn === -1) { return null; } @@ -107,16 +107,16 @@ function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterC } const prevBreakingOffsets = previousBreakingData.breakOffsets; - const prevBreakingOffsetsVisibleColumn = previousBreakingData.breakingOffsetsVisibleColumn; + const prevBreakingOffsetsVisibleColumn = previousBreakingData.breakOffsetsVisibleColumn; - const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakingColumn, columnsForFullWidthChar, hardWrappingIndent); - const wrappedLineBreakingColumn = firstLineBreakingColumn - wrappedTextIndentLength; + const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakColumn, columnsForFullWidthChar, wrappingIndent); + const wrappedLineBreakColumn = firstLineBreakColumn - wrappedTextIndentLength; let breakingOffsets: number[] = arrPool1; let breakingOffsetsVisibleColumn: number[] = arrPool2; let breakingOffsetsCount: number = 0; - let breakingColumn = firstLineBreakingColumn; + let breakingColumn = firstLineBreakColumn; const prevLen = prevBreakingOffsets.length; let prevIndex = 0; @@ -178,7 +178,7 @@ function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterC forcedBreakOffset = charStartOffset; forcedBreakOffsetVisibleColumn = visibleColumn - charWidth; - if (visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakingColumn) { + if (visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakColumn) { // Cannot break at `breakOffset` => reset it if it was set breakOffset = 0; } @@ -238,7 +238,7 @@ function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterC forcedBreakOffsetVisibleColumn = visibleColumn; } - if (visibleColumn <= breakingColumn - wrappedLineBreakingColumn) { + if (visibleColumn <= breakingColumn - wrappedLineBreakColumn) { // went too far! break; } @@ -256,7 +256,7 @@ function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterC } if (breakOffset !== 0) { - const remainingWidthOfNextLine = wrappedLineBreakingColumn - (forcedBreakOffsetVisibleColumn - breakOffsetVisibleColumn); + const remainingWidthOfNextLine = wrappedLineBreakColumn - (forcedBreakOffsetVisibleColumn - breakOffsetVisibleColumn); if (remainingWidthOfNextLine <= tabSize) { const charCodeAtForcedBreakOffset = lineText.charCodeAt(forcedBreakOffset); let charWidth: number; @@ -289,7 +289,7 @@ function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterC breakingOffsets[breakingOffsetsCount] = breakOffset; breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn; breakingOffsetsCount++; - breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakingColumn; + breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakColumn; while (prevIndex < 0 || (prevIndex < prevLen && prevBreakingOffsetsVisibleColumn[prevIndex] < breakOffsetVisibleColumn)) { prevIndex++; @@ -314,15 +314,15 @@ function createLineMappingFromPreviousLineMapping(classifier: WrappingCharacterC breakingOffsets.length = breakingOffsetsCount; breakingOffsetsVisibleColumn.length = breakingOffsetsCount; arrPool1 = previousBreakingData.breakOffsets; - arrPool2 = previousBreakingData.breakingOffsetsVisibleColumn; + arrPool2 = previousBreakingData.breakOffsetsVisibleColumn; previousBreakingData.breakOffsets = breakingOffsets; - previousBreakingData.breakingOffsetsVisibleColumn = breakingOffsetsVisibleColumn; + previousBreakingData.breakOffsetsVisibleColumn = breakingOffsetsVisibleColumn; previousBreakingData.wrappedTextIndentLength = wrappedTextIndentLength; return previousBreakingData; } -function createLineMapping(classifier: WrappingCharacterClassifier, lineText: string, tabSize: number, firstLineBreakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): LineBreakingData | null { - if (firstLineBreakingColumn === -1) { +function createLineBreaks(classifier: WrappingCharacterClassifier, lineText: string, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): LineBreakData | null { + if (firstLineBreakColumn === -1) { return null; } @@ -331,8 +331,8 @@ function createLineMapping(classifier: WrappingCharacterClassifier, lineText: st return null; } - const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakingColumn, columnsForFullWidthChar, hardWrappingIndent); - const wrappedLineBreakingColumn = firstLineBreakingColumn - wrappedTextIndentLength; + const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakColumn, columnsForFullWidthChar, wrappingIndent); + const wrappedLineBreakColumn = firstLineBreakColumn - wrappedTextIndentLength; let breakingOffsets: number[] = []; let breakingOffsetsVisibleColumn: number[] = []; @@ -340,7 +340,7 @@ function createLineMapping(classifier: WrappingCharacterClassifier, lineText: st let breakOffset = 0; let breakOffsetVisibleColumn = 0; - let breakingColumn = firstLineBreakingColumn; + let breakingColumn = firstLineBreakColumn; let prevCharCode = lineText.charCodeAt(0); let prevCharCodeClass = classifier.get(prevCharCode); let visibleColumn = computeCharWidth(prevCharCode, 0, tabSize, columnsForFullWidthChar); @@ -381,7 +381,7 @@ function createLineMapping(classifier: WrappingCharacterClassifier, lineText: st if (visibleColumn > breakingColumn) { // We need to break at least before character at `i`: - if (breakOffset === 0 || visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakingColumn) { + if (breakOffset === 0 || visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakColumn) { // Cannot break at `breakOffset`, must break at `i` breakOffset = charStartOffset; breakOffsetVisibleColumn = visibleColumn - charWidth; @@ -390,7 +390,7 @@ function createLineMapping(classifier: WrappingCharacterClassifier, lineText: st breakingOffsets[breakingOffsetsCount] = breakOffset; breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn; breakingOffsetsCount++; - breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakingColumn; + breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakColumn; breakOffset = 0; } @@ -406,7 +406,7 @@ function createLineMapping(classifier: WrappingCharacterClassifier, lineText: st breakingOffsets[breakingOffsetsCount] = len; breakingOffsetsVisibleColumn[breakingOffsetsCount] = visibleColumn; - return new LineBreakingData(breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); + return new LineBreakData(breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength); } function computeCharWidth(charCode: number, visibleColumn: number, tabSize: number, columnsForFullWidthChar: number): number { @@ -436,9 +436,9 @@ function canBreak(prevCharCodeClass: CharacterClass, charCodeClass: CharacterCla ); } -function computeWrappedTextIndentLength(lineText: string, tabSize: number, firstLineBreakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): number { +function computeWrappedTextIndentLength(lineText: string, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): number { let wrappedTextIndentLength = 0; - if (hardWrappingIndent !== WrappingIndent.None) { + if (wrappingIndent !== WrappingIndent.None) { const firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineText); if (firstNonWhitespaceIndex !== -1) { // Track existing indent @@ -449,14 +449,14 @@ function computeWrappedTextIndentLength(lineText: string, tabSize: number, first } // Increase indent of continuation lines, if desired - const numberOfAdditionalTabs = (hardWrappingIndent === WrappingIndent.DeepIndent ? 2 : hardWrappingIndent === WrappingIndent.Indent ? 1 : 0); + const numberOfAdditionalTabs = (wrappingIndent === WrappingIndent.DeepIndent ? 2 : wrappingIndent === WrappingIndent.Indent ? 1 : 0); for (let i = 0; i < numberOfAdditionalTabs; i++) { const charWidth = tabCharacterWidth(wrappedTextIndentLength, tabSize); wrappedTextIndentLength += charWidth; } // Force sticking to beginning of line if no character would fit except for the indentation - if (wrappedTextIndentLength + columnsForFullWidthChar > firstLineBreakingColumn) { + if (wrappedTextIndentLength + columnsForFullWidthChar > firstLineBreakColumn) { wrappedTextIndentLength = 0; } } diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 9f66266bc941c..726444f3b11e0 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -26,10 +26,10 @@ export class OutputPosition { } } -export class LineBreakingData { +export class LineBreakData { constructor( public breakOffsets: number[], - public breakingOffsetsVisibleColumn: number[], + public breakOffsetsVisibleColumn: number[], public wrappedTextIndentLength: number ) { } @@ -66,16 +66,16 @@ export class LineBreakingData { } } -export interface ILineMappingComputer { +export interface ILineBreaksComputer { /** - * Pass in previousLineBreakingData if the only difference is in breaking columns!!! + * Pass in `previousLineBreakData` if the only difference is in breaking columns!!! */ - addRequest(lineText: string, previousLineBreakingData: LineBreakingData | null): void; - finalize(): (LineBreakingData | null)[]; + addRequest(lineText: string, previousLineBreakData: LineBreakData | null): void; + finalize(): (LineBreakData | null)[]; } -export interface ILineMapperFactory { - createLineMappingComputer(tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ILineMappingComputer; +export interface ILineBreaksComputerFactory { + createLineBreaksComputer(tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ILineBreaksComputer; } export interface ISimpleModel { @@ -91,7 +91,7 @@ export interface ISplitLine { isVisible(): boolean; setVisible(isVisible: boolean): ISplitLine; - getLineBreakingData(): LineBreakingData | null; + getLineBreakData(): LineBreakData | null; getViewLineCount(): number; getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string; getViewLineLength(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number; @@ -113,11 +113,11 @@ export interface IViewModelLinesCollection extends IDisposable { getHiddenAreas(): Range[]; setHiddenAreas(_ranges: Range[]): boolean; - createLineMappingComputer(): ILineMappingComputer; + createLineBreaksComputer(): ILineBreaksComputer; onModelFlushed(): void; onModelLinesDeleted(versionId: number, fromLineNumber: number, toLineNumber: number): viewEvents.ViewLinesDeletedEvent | null; - onModelLinesInserted(versionId: number, fromLineNumber: number, toLineNumber: number, linesMappings: (LineBreakingData | null)[]): viewEvents.ViewLinesInsertedEvent | null; - onModelLineChanged(versionId: number, lineNumber: number, lineMapping: LineBreakingData | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null]; + onModelLinesInserted(versionId: number, fromLineNumber: number, toLineNumber: number, lineBreaks: (LineBreakData | null)[]): viewEvents.ViewLinesInsertedEvent | null; + onModelLineChanged(versionId: number, lineNumber: number, lineBreakData: LineBreakData | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null]; acceptVersionId(versionId: number): void; getViewLineCount(): number; @@ -277,11 +277,11 @@ export class SplitLinesCollection implements IViewModelLinesCollection { private prefixSumComputer!: LineNumberMapper; - private readonly linePositionMapperFactory: ILineMapperFactory; + private readonly linePositionMapperFactory: ILineBreaksComputerFactory; private hiddenAreasIds!: string[]; - constructor(model: ITextModel, linePositionMapperFactory: ILineMapperFactory, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent) { + constructor(model: ITextModel, linePositionMapperFactory: ILineBreaksComputerFactory, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent) { this.model = model; this._validModelVersionId = -1; this.tabSize = tabSize; @@ -301,7 +301,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return new CoordinatesConverter(this); } - private _constructLines(resetHiddenAreas: boolean, previousLineMapping: ((LineBreakingData | null)[]) | null): void { + private _constructLines(resetHiddenAreas: boolean, previousLineBreaks: ((LineBreakData | null)[]) | null): void { this.lines = []; if (resetHiddenAreas) { @@ -310,11 +310,11 @@ export class SplitLinesCollection implements IViewModelLinesCollection { let linesContent = this.model.getLinesContent(); const lineCount = linesContent.length; - const lineMappingComputer = this.linePositionMapperFactory.createLineMappingComputer(this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent); + const lineBreaksComputer = this.linePositionMapperFactory.createLineBreaksComputer(this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent); for (let i = 0; i < lineCount; i++) { - lineMappingComputer.addRequest(linesContent[i], previousLineMapping ? previousLineMapping[i] : null); + lineBreaksComputer.addRequest(linesContent[i], previousLineBreaks ? previousLineBreaks[i] : null); } - const lineMappings = lineMappingComputer.finalize(); + const linesBreaks = lineBreaksComputer.finalize(); let values: number[] = []; @@ -334,7 +334,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } let isInHiddenArea = (lineNumber >= hiddenAreaStart && lineNumber <= hiddenAreaEnd); - let line = createSplitLine(lineMappings[i], !isInHiddenArea); + let line = createSplitLine(linesBreaks[i], !isInHiddenArea); values[i] = line.getViewLineCount(); this.lines[i] = line; } @@ -481,21 +481,21 @@ export class SplitLinesCollection implements IViewModelLinesCollection { this.wrappingColumn = wrappingColumn; this.columnsForFullWidthChar = columnsForFullWidthChar; - let previousLineMapping: ((LineBreakingData | null)[]) | null = null; + let previousLineBreaks: ((LineBreakData | null)[]) | null = null; if (onlyWrappingColumnChanged) { - previousLineMapping = []; + previousLineBreaks = []; for (let i = 0, len = this.lines.length; i < len; i++) { - previousLineMapping[i] = this.lines[i].getLineBreakingData(); + previousLineBreaks[i] = this.lines[i].getLineBreakData(); } } - this._constructLines(/*resetHiddenAreas*/false, previousLineMapping); + this._constructLines(/*resetHiddenAreas*/false, previousLineBreaks); return true; } - public createLineMappingComputer(): ILineMappingComputer { - return this.linePositionMapperFactory.createLineMappingComputer(this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent); + public createLineBreaksComputer(): ILineBreaksComputer { + return this.linePositionMapperFactory.createLineBreaksComputer(this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent); } public onModelFlushed(): void { @@ -518,7 +518,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return new viewEvents.ViewLinesDeletedEvent(outputFromLineNumber, outputToLineNumber); } - public onModelLinesInserted(versionId: number, fromLineNumber: number, _toLineNumber: number, linesMappings: (LineBreakingData | null)[]): viewEvents.ViewLinesInsertedEvent | null { + public onModelLinesInserted(versionId: number, fromLineNumber: number, _toLineNumber: number, lineBreaks: (LineBreakData | null)[]): viewEvents.ViewLinesInsertedEvent | null { if (versionId <= this._validModelVersionId) { // Here we check for versionId in case the lines were reconstructed in the meantime. // We don't want to apply stale change events on top of a newer read model state. @@ -541,8 +541,8 @@ export class SplitLinesCollection implements IViewModelLinesCollection { let insertLines: ISplitLine[] = []; let insertPrefixSumValues: number[] = []; - for (let i = 0, len = linesMappings.length; i < len; i++) { - let line = createSplitLine(linesMappings[i], !isInHiddenArea); + for (let i = 0, len = lineBreaks.length; i < len; i++) { + let line = createSplitLine(lineBreaks[i], !isInHiddenArea); insertLines.push(line); let outputLineCount = line.getViewLineCount(); @@ -558,7 +558,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return new viewEvents.ViewLinesInsertedEvent(outputFromLineNumber, outputFromLineNumber + totalOutputLineCount - 1); } - public onModelLineChanged(versionId: number, lineNumber: number, lineMapping: LineBreakingData | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] { + public onModelLineChanged(versionId: number, lineNumber: number, lineBreakData: LineBreakData | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] { if (versionId <= this._validModelVersionId) { // Here we check for versionId in case the lines were reconstructed in the meantime. // We don't want to apply stale change events on top of a newer read model state. @@ -569,7 +569,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { let oldOutputLineCount = this.lines[lineIndex].getViewLineCount(); let isVisible = this.lines[lineIndex].isVisible(); - let line = createSplitLine(lineMapping, isVisible); + let line = createSplitLine(lineBreakData, isVisible); this.lines[lineIndex] = line; let newOutputLineCount = this.lines[lineIndex].getViewLineCount(); @@ -1019,7 +1019,7 @@ class VisibleIdentitySplitLine implements ISplitLine { return InvisibleIdentitySplitLine.INSTANCE; } - public getLineBreakingData(): LineBreakingData | null { + public getLineBreakData(): LineBreakData | null { return null; } @@ -1094,7 +1094,7 @@ class InvisibleIdentitySplitLine implements ISplitLine { return VisibleIdentitySplitLine.INSTANCE; } - public getLineBreakingData(): LineBreakingData | null { + public getLineBreakData(): LineBreakData | null { return null; } @@ -1141,11 +1141,11 @@ class InvisibleIdentitySplitLine implements ISplitLine { export class SplitLine implements ISplitLine { - private readonly _lineBreakingData: LineBreakingData; + private readonly _lineBreakData: LineBreakData; private _isVisible: boolean; - constructor(lineBreakingData: LineBreakingData, isVisible: boolean) { - this._lineBreakingData = lineBreakingData; + constructor(lineBreakData: LineBreakData, isVisible: boolean) { + this._lineBreakData = lineBreakData; this._isVisible = isVisible; } @@ -1158,26 +1158,26 @@ export class SplitLine implements ISplitLine { return this; } - public getLineBreakingData(): LineBreakingData | null { - return this._lineBreakingData; + public getLineBreakData(): LineBreakData | null { + return this._lineBreakData; } public getViewLineCount(): number { if (!this._isVisible) { return 0; } - return this._lineBreakingData.breakOffsets.length; + return this._lineBreakData.breakOffsets.length; } private getInputStartOffsetOfOutputLineIndex(outputLineIndex: number): number { - return LineBreakingData.getInputOffsetOfOutputPosition(this._lineBreakingData.breakOffsets, outputLineIndex, 0); + return LineBreakData.getInputOffsetOfOutputPosition(this._lineBreakData.breakOffsets, outputLineIndex, 0); } private getInputEndOffsetOfOutputLineIndex(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number { - if (outputLineIndex + 1 === this._lineBreakingData.breakOffsets.length) { + if (outputLineIndex + 1 === this._lineBreakData.breakOffsets.length) { return model.getLineMaxColumn(modelLineNumber) - 1; } - return LineBreakingData.getInputOffsetOfOutputPosition(this._lineBreakingData.breakOffsets, outputLineIndex + 1, 0); + return LineBreakData.getInputOffsetOfOutputPosition(this._lineBreakData.breakOffsets, outputLineIndex + 1, 0); } public getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string { @@ -1194,7 +1194,7 @@ export class SplitLine implements ISplitLine { }); if (outputLineIndex > 0) { - r = spaces(this._lineBreakingData.wrappedTextIndentLength) + r; + r = spaces(this._lineBreakData.wrappedTextIndentLength) + r; } return r; @@ -1209,7 +1209,7 @@ export class SplitLine implements ISplitLine { let r = endOffset - startOffset; if (outputLineIndex > 0) { - r = this._lineBreakingData.wrappedTextIndentLength + r; + r = this._lineBreakData.wrappedTextIndentLength + r; } return r; @@ -1220,7 +1220,7 @@ export class SplitLine implements ISplitLine { throw new Error('Not supported'); } if (outputLineIndex > 0) { - return this._lineBreakingData.wrappedTextIndentLength + 1; + return this._lineBreakData.wrappedTextIndentLength + 1; } return 1; } @@ -1248,21 +1248,21 @@ export class SplitLine implements ISplitLine { }); if (outputLineIndex > 0) { - lineContent = spaces(this._lineBreakingData.wrappedTextIndentLength) + lineContent; + lineContent = spaces(this._lineBreakData.wrappedTextIndentLength) + lineContent; } - let minColumn = (outputLineIndex > 0 ? this._lineBreakingData.wrappedTextIndentLength + 1 : 1); + let minColumn = (outputLineIndex > 0 ? this._lineBreakData.wrappedTextIndentLength + 1 : 1); let maxColumn = lineContent.length + 1; let continuesWithWrappedLine = (outputLineIndex + 1 < this.getViewLineCount()); let deltaStartIndex = 0; if (outputLineIndex > 0) { - deltaStartIndex = this._lineBreakingData.wrappedTextIndentLength; + deltaStartIndex = this._lineBreakData.wrappedTextIndentLength; } let lineTokens = model.getLineTokens(modelLineNumber); - const startVisibleColumn = (outputLineIndex === 0 ? 0 : this._lineBreakingData.breakingOffsetsVisibleColumn[outputLineIndex - 1]); + const startVisibleColumn = (outputLineIndex === 0 ? 0 : this._lineBreakData.breakOffsetsVisibleColumn[outputLineIndex - 1]); return new ViewLineData( lineContent, @@ -1295,25 +1295,25 @@ export class SplitLine implements ISplitLine { } let adjustedColumn = outputColumn - 1; if (outputLineIndex > 0) { - if (adjustedColumn < this._lineBreakingData.wrappedTextIndentLength) { + if (adjustedColumn < this._lineBreakData.wrappedTextIndentLength) { adjustedColumn = 0; } else { - adjustedColumn -= this._lineBreakingData.wrappedTextIndentLength; + adjustedColumn -= this._lineBreakData.wrappedTextIndentLength; } } - return LineBreakingData.getInputOffsetOfOutputPosition(this._lineBreakingData.breakOffsets, outputLineIndex, adjustedColumn) + 1; + return LineBreakData.getInputOffsetOfOutputPosition(this._lineBreakData.breakOffsets, outputLineIndex, adjustedColumn) + 1; } public getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number): Position { if (!this._isVisible) { throw new Error('Not supported'); } - let r = LineBreakingData.getOutputPositionOfInputOffset(this._lineBreakingData.breakOffsets, inputColumn - 1); + let r = LineBreakData.getOutputPositionOfInputOffset(this._lineBreakData.breakOffsets, inputColumn - 1); let outputLineIndex = r.outputLineIndex; let outputColumn = r.outputOffset + 1; if (outputLineIndex > 0) { - outputColumn += this._lineBreakingData.wrappedTextIndentLength; + outputColumn += this._lineBreakData.wrappedTextIndentLength; } // console.log('in -> out ' + deltaLineNumber + ',' + inputColumn + ' ===> ' + (deltaLineNumber+outputLineIndex) + ',' + outputColumn); @@ -1324,7 +1324,7 @@ export class SplitLine implements ISplitLine { if (!this._isVisible) { throw new Error('Not supported'); } - const r = LineBreakingData.getOutputPositionOfInputOffset(this._lineBreakingData.breakOffsets, inputColumn - 1); + const r = LineBreakData.getOutputPositionOfInputOffset(this._lineBreakData.breakOffsets, inputColumn - 1); return (deltaLineNumber + r.outputLineIndex); } } @@ -1342,15 +1342,15 @@ function _makeSpaces(count: number): string { return new Array(count + 1).join(' '); } -function createSplitLine(lineMapping: LineBreakingData | null, isVisible: boolean): ISplitLine { - if (lineMapping === null) { +function createSplitLine(lineBreakData: LineBreakData | null, isVisible: boolean): ISplitLine { + if (lineBreakData === null) { // No mapping needed if (isVisible) { return VisibleIdentitySplitLine.INSTANCE; } return InvisibleIdentitySplitLine.INSTANCE; } else { - return new SplitLine(lineMapping, isVisible); + return new SplitLine(lineBreakData, isVisible); } } @@ -1440,10 +1440,10 @@ export class IdentityLinesCollection implements IViewModelLinesCollection { return false; } - public createLineMappingComputer(): ILineMappingComputer { + public createLineBreaksComputer(): ILineBreaksComputer { let result: null[] = []; return { - addRequest: (lineText: string, previousLineBreakingData: LineBreakingData | null) => { + addRequest: (lineText: string, previousLineBreakData: LineBreakData | null) => { result.push(null); }, finalize: () => { @@ -1459,11 +1459,11 @@ export class IdentityLinesCollection implements IViewModelLinesCollection { return new viewEvents.ViewLinesDeletedEvent(fromLineNumber, toLineNumber); } - public onModelLinesInserted(_versionId: number, fromLineNumber: number, toLineNumber: number, linesMappings: (LineBreakingData | null)[]): viewEvents.ViewLinesInsertedEvent | null { + public onModelLinesInserted(_versionId: number, fromLineNumber: number, toLineNumber: number, lineBreaks: (LineBreakData | null)[]): viewEvents.ViewLinesInsertedEvent | null { return new viewEvents.ViewLinesInsertedEvent(fromLineNumber, toLineNumber); } - public onModelLineChanged(_versionId: number, lineNumber: number, lineMapping: LineBreakingData | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] { + public onModelLineChanged(_versionId: number, lineNumber: number, lineBreakData: LineBreakData | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] { return [false, new viewEvents.ViewLinesChangedEvent(lineNumber, lineNumber), null, null]; } diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 55100bc6c2377..718c9fc090e18 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -18,7 +18,7 @@ import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTokensColorTracker'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { ViewLayout } from 'vs/editor/common/viewLayout/viewLayout'; -import { IViewModelLinesCollection, IdentityLinesCollection, SplitLinesCollection, ILineMapperFactory } from 'vs/editor/common/viewModel/splitLinesCollection'; +import { IViewModelLinesCollection, IdentityLinesCollection, SplitLinesCollection, ILineBreaksComputerFactory } from 'vs/editor/common/viewModel/splitLinesCollection'; import { ICoordinatesConverter, IOverviewRulerDecorations, IViewModel, MinimapLinesRenderingData, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel'; import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations'; import { ITheme } from 'vs/platform/theme/common/themeService'; @@ -46,7 +46,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel editorId: number, configuration: editorCommon.IConfiguration, model: ITextModel, - lineMapperFactory: ILineMapperFactory, + lineMapperFactory: ILineBreaksComputerFactory, scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable ) { super(); @@ -197,23 +197,23 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel const versionId = e.versionId; // Do a first pass to compute line mappings, and a second pass to actually interpret them - const lineMappingComputer = this.lines.createLineMappingComputer(); + const lineBreaksComputer = this.lines.createLineBreaksComputer(); for (const change of changes) { switch (change.changeType) { case textModelEvents.RawContentChangedType.LinesInserted: { for (const line of change.detail) { - lineMappingComputer.addRequest(line, null); + lineBreaksComputer.addRequest(line, null); } break; } case textModelEvents.RawContentChangedType.LineChanged: { - lineMappingComputer.addRequest(change.detail, null); + lineBreaksComputer.addRequest(change.detail, null); break; } } } - const lineMappings = lineMappingComputer.finalize(); - let lineMappingsOffset = 0; + const lineBreaks = lineBreaksComputer.finalize(); + let lineBreaksOffset = 0; for (const change of changes) { @@ -236,10 +236,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel break; } case textModelEvents.RawContentChangedType.LinesInserted: { - const insertedLinesMappings = lineMappings.slice(lineMappingsOffset, lineMappingsOffset + change.detail.length); - lineMappingsOffset += change.detail.length; + const insertedLineBreaks = lineBreaks.slice(lineBreaksOffset, lineBreaksOffset + change.detail.length); + lineBreaksOffset += change.detail.length; - const linesInsertedEvent = this.lines.onModelLinesInserted(versionId, change.fromLineNumber, change.toLineNumber, insertedLinesMappings); + const linesInsertedEvent = this.lines.onModelLinesInserted(versionId, change.fromLineNumber, change.toLineNumber, insertedLineBreaks); if (linesInsertedEvent !== null) { eventsCollector.emit(linesInsertedEvent); this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber); @@ -248,10 +248,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel break; } case textModelEvents.RawContentChangedType.LineChanged: { - const changedLineMapping = lineMappings[lineMappingsOffset]; - lineMappingsOffset++; + const changedLineBreakData = lineBreaks[lineBreaksOffset]; + lineBreaksOffset++; - const [lineMappingChanged, linesChangedEvent, linesInsertedEvent, linesDeletedEvent] = this.lines.onModelLineChanged(versionId, change.lineNumber, changedLineMapping); + const [lineMappingChanged, linesChangedEvent, linesInsertedEvent, linesDeletedEvent] = this.lines.onModelLineChanged(versionId, change.lineNumber, changedLineBreakData); hadModelLineChangeThatChangedLineMapping = lineMappingChanged; if (linesChangedEvent) { eventsCollector.emit(linesChangedEvent); diff --git a/src/vs/editor/contrib/comment/lineCommentCommand.ts b/src/vs/editor/contrib/comment/lineCommentCommand.ts index 5c3716ed79cdb..2eecd5ae2c51e 100644 --- a/src/vs/editor/contrib/comment/lineCommentCommand.ts +++ b/src/vs/editor/contrib/comment/lineCommentCommand.ts @@ -381,7 +381,6 @@ export class LineCommentCommand implements editorCommon.ICommand { return res; } - // TODO@Alex -> duplicated in characterHardWrappingLineMapper private static nextVisibleColumn(currentVisibleColumn: number, tabSize: number, isTab: boolean, columnSize: number): number { if (isTab) { return currentVisibleColumn + (tabSize - (currentVisibleColumn % tabSize)); diff --git a/src/vs/editor/test/browser/commands/sideEditing.test.ts b/src/vs/editor/test/browser/commands/sideEditing.test.ts index 205a169c495b2..67ae4e4d1749b 100644 --- a/src/vs/editor/test/browser/commands/sideEditing.test.ts +++ b/src/vs/editor/test/browser/commands/sideEditing.test.ts @@ -14,7 +14,7 @@ import { TextModel } from 'vs/editor/common/model/textModel'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; -import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; +import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; function testCommand(lines: string[], selections: Selection[], edits: IIdentifiedSingleEditOperation[], expectedLines: string[], expectedSelections: Selection[]): void { withTestCodeEditor(lines, {}, (editor, cursor) => { @@ -201,7 +201,7 @@ suite('SideEditing', () => { function _runTest(selection: Selection, editRange: Range, editText: string, editForceMoveMarkers: boolean, expected: Selection, msg: string): void { const model = TextModel.createFromString(LINES.join('\n')); const config = new TestConfiguration({}); - const viewModel = new ViewModel(0, config, model, CharacterHardWrappingLineMapperFactory.create(config.options), null!); + const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!); const cursor = new Cursor(config, model, viewModel); cursor.setSelections('tests', [selection]); diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index 9031f4056de64..7d82c86f55e5f 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -25,7 +25,7 @@ import { IRelaxedTextModelCreationOptions, createTextModel } from 'vs/editor/tes import { MockMode } from 'vs/editor/test/common/mocks/mockMode'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules'; -import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; +import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; const H = Handler; @@ -153,7 +153,7 @@ suite('Editor Controller - Cursor', () => { thisModel = createTextModel(text); thisConfiguration = new TestConfiguration({}); - thisViewModel = new ViewModel(0, thisConfiguration, thisModel, CharacterHardWrappingLineMapperFactory.create(thisConfiguration.options), null!); + thisViewModel = new ViewModel(0, thisConfiguration, thisModel, MonospaceLineBreaksComputerFactory.create(thisConfiguration.options), null!); thisCursor = new Cursor(thisConfiguration, thisModel, thisViewModel); }); @@ -777,7 +777,7 @@ suite('Editor Controller - Cursor', () => { 'var newer = require("gulp-newer");', ].join('\n')); const config = new TestConfiguration({}); - const viewModel = new ViewModel(0, config, model, CharacterHardWrappingLineMapperFactory.create(config.options), null!); + const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!); const cursor = new Cursor(config, model, viewModel); moveTo(cursor, 1, 4, false); @@ -817,7 +817,7 @@ suite('Editor Controller - Cursor', () => { '', ].join('\n')); const config = new TestConfiguration({}); - const viewModel = new ViewModel(0, config, model, CharacterHardWrappingLineMapperFactory.create(config.options), null!); + const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!); const cursor = new Cursor(config, model, viewModel); moveTo(cursor, 10, 10, false); @@ -881,7 +881,7 @@ suite('Editor Controller - Cursor', () => { '', ].join('\n')); const config = new TestConfiguration({}); - const viewModel = new ViewModel(0, config, model, CharacterHardWrappingLineMapperFactory.create(config.options), null!); + const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!); const cursor = new Cursor(config, model, viewModel); moveTo(cursor, 10, 10, false); @@ -930,7 +930,7 @@ suite('Editor Controller - Cursor', () => { 'var newer = require("gulp-newer");', ].join('\n')); const config = new TestConfiguration({}); - const viewModel = new ViewModel(0, config, model, CharacterHardWrappingLineMapperFactory.create(config.options), null!); + const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!); const cursor = new Cursor(config, model, viewModel); moveTo(cursor, 1, 4, false); @@ -2075,7 +2075,7 @@ suite('Editor Controller - Regression tests', () => { wordWrap: 'wordWrapColumn', wordWrapColumn: 100 }); - const viewModel = new ViewModel(0, config, model, CharacterHardWrappingLineMapperFactory.create(config.options), null!); + const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!); const cursor = new Cursor(config, model, viewModel); moveTo(cursor, 1, 43, false); @@ -3835,7 +3835,7 @@ function usingCursor(opts: ICursorOpts, callback: (model: TextModel, cursor: Cur let model = createTextModel(opts.text.join('\n'), opts.modelOpts, opts.languageIdentifier); model.forceTokenization(model.getLineCount()); let config = new TestConfiguration(opts.editorOpts || {}); - let viewModel = new ViewModel(0, config, model, CharacterHardWrappingLineMapperFactory.create(config.options), null!); + let viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!); let cursor = new Cursor(config, model, viewModel); callback(model, cursor); diff --git a/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts b/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts index dad6b955cdaad..117cf54372d52 100644 --- a/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts +++ b/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts @@ -13,7 +13,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { TextModel } from 'vs/editor/common/model/textModel'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; -import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; +import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; suite('Cursor move command test', () => { @@ -33,7 +33,7 @@ suite('Cursor move command test', () => { thisModel = TextModel.createFromString(text); thisConfiguration = new TestConfiguration({}); - thisViewModel = new ViewModel(0, thisConfiguration, thisModel, CharacterHardWrappingLineMapperFactory.create(thisConfiguration.options), null!); + thisViewModel = new ViewModel(0, thisConfiguration, thisModel, MonospaceLineBreaksComputerFactory.create(thisConfiguration.options), null!); thisCursor = new Cursor(thisConfiguration, thisModel, thisViewModel); }); diff --git a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts b/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts similarity index 50% rename from src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts rename to src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts index db130c390b1b0..625c678ff4767 100644 --- a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts +++ b/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { WrappingIndent, EditorOptions } from 'vs/editor/common/config/editorOptions'; -import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; -import { ILineMapperFactory, LineBreakingData } from 'vs/editor/common/viewModel/splitLinesCollection'; +import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; +import { ILineBreaksComputerFactory, LineBreakData } from 'vs/editor/common/viewModel/splitLinesCollection'; function parseAnnotatedText(annotatedText: string): { text: string; indices: number[]; } { let text = ''; @@ -22,13 +22,13 @@ function parseAnnotatedText(annotatedText: string): { text: string; indices: num return { text: text, indices: indices }; } -function toAnnotatedText(text: string, lineBreakingData: LineBreakingData | null): string { +function toAnnotatedText(text: string, lineBreakData: LineBreakData | null): string { // Insert line break markers again, according to algorithm let actualAnnotatedText = ''; - if (lineBreakingData) { + if (lineBreakData) { let previousLineIndex = 0; for (let i = 0, len = text.length; i < len; i++) { - let r = LineBreakingData.getOutputPositionOfInputOffset(lineBreakingData.breakOffsets, i); + let r = LineBreakData.getOutputPositionOfInputOffset(lineBreakData.breakOffsets, i); if (previousLineIndex !== r.outputLineIndex) { previousLineIndex = r.outputLineIndex; actualAnnotatedText += '|'; @@ -42,142 +42,142 @@ function toAnnotatedText(text: string, lineBreakingData: LineBreakingData | null return actualAnnotatedText; } -function getLineBreakingData(factory: ILineMapperFactory, tabSize: number, breakAfter: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent, text: string, previousLineBreakingData: LineBreakingData | null): LineBreakingData | null { - const lineMappingComputer = factory.createLineMappingComputer(tabSize, breakAfter, columnsForFullWidthChar, wrappingIndent); - const previousLineBreakingDataClone = previousLineBreakingData ? new LineBreakingData(previousLineBreakingData.breakOffsets.slice(0), previousLineBreakingData.breakingOffsetsVisibleColumn.slice(0), previousLineBreakingData.wrappedTextIndentLength) : null; - lineMappingComputer.addRequest(text, previousLineBreakingDataClone); - return lineMappingComputer.finalize()[0]; +function getLineBreakData(factory: ILineBreaksComputerFactory, tabSize: number, breakAfter: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent, text: string, previousLineBreakData: LineBreakData | null): LineBreakData | null { + const lineBreaksComputer = factory.createLineBreaksComputer(tabSize, breakAfter, columnsForFullWidthChar, wrappingIndent); + const previousLineBreakDataClone = previousLineBreakData ? new LineBreakData(previousLineBreakData.breakOffsets.slice(0), previousLineBreakData.breakOffsetsVisibleColumn.slice(0), previousLineBreakData.wrappedTextIndentLength) : null; + lineBreaksComputer.addRequest(text, previousLineBreakDataClone); + return lineBreaksComputer.finalize()[0]; } -function assertLineMapping(factory: ILineMapperFactory, tabSize: number, breakAfter: number, annotatedText: string, wrappingIndent = WrappingIndent.None): LineBreakingData | null { +function assertLineBreaks(factory: ILineBreaksComputerFactory, tabSize: number, breakAfter: number, annotatedText: string, wrappingIndent = WrappingIndent.None): LineBreakData | null { // Create version of `annotatedText` with line break markers removed const text = parseAnnotatedText(annotatedText).text; - const lineBreakingData = getLineBreakingData(factory, tabSize, breakAfter, 2, wrappingIndent, text, null); - const actualAnnotatedText = toAnnotatedText(text, lineBreakingData); + const lineBreakData = getLineBreakData(factory, tabSize, breakAfter, 2, wrappingIndent, text, null); + const actualAnnotatedText = toAnnotatedText(text, lineBreakData); assert.equal(actualAnnotatedText, annotatedText); - return lineBreakingData; + return lineBreakData; } -suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { - test('CharacterHardWrappingLineMapper', () => { +suite('Editor ViewModel - MonospaceLineBreaksComputer', () => { + test('MonospaceLineBreaksComputer', () => { - let factory = new CharacterHardWrappingLineMapperFactory('(', '\t).'); + let factory = new MonospaceLineBreaksComputerFactory('(', '\t).'); // Empty string - assertLineMapping(factory, 4, 5, ''); + assertLineBreaks(factory, 4, 5, ''); // No wrapping if not necessary - assertLineMapping(factory, 4, 5, 'aaa'); - assertLineMapping(factory, 4, 5, 'aaaaa'); - assertLineMapping(factory, 4, -1, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'); + assertLineBreaks(factory, 4, 5, 'aaa'); + assertLineBreaks(factory, 4, 5, 'aaaaa'); + assertLineBreaks(factory, 4, -1, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'); // Acts like hard wrapping if no char found - assertLineMapping(factory, 4, 5, 'aaaaa|a'); + assertLineBreaks(factory, 4, 5, 'aaaaa|a'); // Honors wrapping character - assertLineMapping(factory, 4, 5, 'aaaaa|.'); - assertLineMapping(factory, 4, 5, 'aaaaa|a.|aaa.|aa'); - assertLineMapping(factory, 4, 5, 'aaaaa|a..|aaa.|aa'); - assertLineMapping(factory, 4, 5, 'aaaaa|a...|aaa.|aa'); - assertLineMapping(factory, 4, 5, 'aaaaa|a....|aaa.|aa'); + assertLineBreaks(factory, 4, 5, 'aaaaa|.'); + assertLineBreaks(factory, 4, 5, 'aaaaa|a.|aaa.|aa'); + assertLineBreaks(factory, 4, 5, 'aaaaa|a..|aaa.|aa'); + assertLineBreaks(factory, 4, 5, 'aaaaa|a...|aaa.|aa'); + assertLineBreaks(factory, 4, 5, 'aaaaa|a....|aaa.|aa'); // Honors tabs when computing wrapping position - assertLineMapping(factory, 4, 5, '\t'); - assertLineMapping(factory, 4, 5, '\t|aaa'); - assertLineMapping(factory, 4, 5, '\t|a\t|aa'); - assertLineMapping(factory, 4, 5, 'aa\ta'); - assertLineMapping(factory, 4, 5, 'aa\t|aa'); + assertLineBreaks(factory, 4, 5, '\t'); + assertLineBreaks(factory, 4, 5, '\t|aaa'); + assertLineBreaks(factory, 4, 5, '\t|a\t|aa'); + assertLineBreaks(factory, 4, 5, 'aa\ta'); + assertLineBreaks(factory, 4, 5, 'aa\t|aa'); // Honors wrapping before characters (& gives it priority) - assertLineMapping(factory, 4, 5, 'aaa.|aa'); - assertLineMapping(factory, 4, 5, 'aaa(.|aa'); + assertLineBreaks(factory, 4, 5, 'aaa.|aa'); + assertLineBreaks(factory, 4, 5, 'aaa(.|aa'); // Honors wrapping after characters (& gives it priority) - assertLineMapping(factory, 4, 5, 'aaa))|).aaa'); - assertLineMapping(factory, 4, 5, 'aaa))|).|aaaa'); - assertLineMapping(factory, 4, 5, 'aaa)|().|aaa'); - assertLineMapping(factory, 4, 5, 'aaa(|().|aaa'); - assertLineMapping(factory, 4, 5, 'aa.(|().|aaa'); - assertLineMapping(factory, 4, 5, 'aa.(.|).aaa'); + assertLineBreaks(factory, 4, 5, 'aaa))|).aaa'); + assertLineBreaks(factory, 4, 5, 'aaa))|).|aaaa'); + assertLineBreaks(factory, 4, 5, 'aaa)|().|aaa'); + assertLineBreaks(factory, 4, 5, 'aaa(|().|aaa'); + assertLineBreaks(factory, 4, 5, 'aa.(|().|aaa'); + assertLineBreaks(factory, 4, 5, 'aa.(.|).aaa'); }); - function assertIncrementalLineMapping(factory: ILineMapperFactory, text: string, tabSize: number, breakAfter1: number, annotatedText1: string, breakAfter2: number, annotatedText2: string, wrappingIndent = WrappingIndent.None): void { + function assertIncrementalLineBreaks(factory: ILineBreaksComputerFactory, text: string, tabSize: number, breakAfter1: number, annotatedText1: string, breakAfter2: number, annotatedText2: string, wrappingIndent = WrappingIndent.None): void { // sanity check the test assert.equal(text, parseAnnotatedText(annotatedText1).text); assert.equal(text, parseAnnotatedText(annotatedText2).text); // check that the direct mapping is ok for 1 - const directLineBreakingData1 = getLineBreakingData(factory, tabSize, breakAfter1, 2, wrappingIndent, text, null); - assert.equal(toAnnotatedText(text, directLineBreakingData1), annotatedText1); + const directLineBreakData1 = getLineBreakData(factory, tabSize, breakAfter1, 2, wrappingIndent, text, null); + assert.equal(toAnnotatedText(text, directLineBreakData1), annotatedText1); // check that the direct mapping is ok for 2 - const directLineBreakingData2 = getLineBreakingData(factory, tabSize, breakAfter2, 2, wrappingIndent, text, null); - assert.equal(toAnnotatedText(text, directLineBreakingData2), annotatedText2); + const directLineBreakData2 = getLineBreakData(factory, tabSize, breakAfter2, 2, wrappingIndent, text, null); + assert.equal(toAnnotatedText(text, directLineBreakData2), annotatedText2); // check that going from 1 to 2 is ok - const lineBreakingData2from1 = getLineBreakingData(factory, tabSize, breakAfter2, 2, wrappingIndent, text, directLineBreakingData1); - assert.equal(toAnnotatedText(text, lineBreakingData2from1), annotatedText2); - assert.deepEqual(lineBreakingData2from1, directLineBreakingData2); + const lineBreakData2from1 = getLineBreakData(factory, tabSize, breakAfter2, 2, wrappingIndent, text, directLineBreakData1); + assert.equal(toAnnotatedText(text, lineBreakData2from1), annotatedText2); + assert.deepEqual(lineBreakData2from1, directLineBreakData2); // check that going from 2 to 1 is ok - const lineBreakingData1from2 = getLineBreakingData(factory, tabSize, breakAfter1, 2, wrappingIndent, text, directLineBreakingData2); - assert.equal(toAnnotatedText(text, lineBreakingData1from2), annotatedText1); - assert.deepEqual(lineBreakingData1from2, directLineBreakingData1); + const lineBreakData1from2 = getLineBreakData(factory, tabSize, breakAfter1, 2, wrappingIndent, text, directLineBreakData2); + assert.equal(toAnnotatedText(text, lineBreakData1from2), annotatedText1); + assert.deepEqual(lineBreakData1from2, directLineBreakData1); } - test('CharacterHardWrappingLineMapper incremental 1', () => { + test('MonospaceLineBreaksComputer incremental 1', () => { - let factory = new CharacterHardWrappingLineMapperFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue); + let factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue); - assertIncrementalLineMapping( + assertIncrementalLineBreaks( factory, 'just some text and more', 4, 10, 'just some |text and |more', 15, 'just some text |and more' ); - assertIncrementalLineMapping( + assertIncrementalLineBreaks( factory, 'Cu scripserit suscipiantur eos, in affert pericula contentiones sed, cetero sanctus et pro. Ius vidit magna regione te, sit ei elaboraret liberavisse. Mundi verear eu mea, eam vero scriptorem in, vix in menandri assueverit. Natum definiebas cu vim. Vim doming vocibus efficiantur id. In indoctum deseruisse voluptatum vim, ad debitis verterem sed.', 4, 47, 'Cu scripserit suscipiantur eos, in affert |pericula contentiones sed, cetero sanctus et |pro. Ius vidit magna regione te, sit ei |elaboraret liberavisse. Mundi verear eu mea, |eam vero scriptorem in, vix in menandri |assueverit. Natum definiebas cu vim. Vim |doming vocibus efficiantur id. In indoctum |deseruisse voluptatum vim, ad debitis verterem |sed.', 142, 'Cu scripserit suscipiantur eos, in affert pericula contentiones sed, cetero sanctus et pro. Ius vidit magna regione te, sit ei elaboraret |liberavisse. Mundi verear eu mea, eam vero scriptorem in, vix in menandri assueverit. Natum definiebas cu vim. Vim doming vocibus efficiantur |id. In indoctum deseruisse voluptatum vim, ad debitis verterem sed.', ); - assertIncrementalLineMapping( + assertIncrementalLineBreaks( factory, 'An his legere persecuti, oblique delicata efficiantur ex vix, vel at graecis officiis maluisset. Et per impedit voluptua, usu discere maiorum at. Ut assum ornatus temporibus vis, an sea melius pericula. Ea dicunt oblique phaedrum nam, eu duo movet nobis. His melius facilis eu, vim malorum temporibus ne. Nec no sale regione, meliore civibus placerat id eam. Mea alii fabulas definitionem te, agam volutpat ad vis, et per bonorum nonumes repudiandae.', 4, 57, 'An his legere persecuti, oblique delicata efficiantur ex |vix, vel at graecis officiis maluisset. Et per impedit |voluptua, usu discere maiorum at. Ut assum ornatus |temporibus vis, an sea melius pericula. Ea dicunt |oblique phaedrum nam, eu duo movet nobis. His melius |facilis eu, vim malorum temporibus ne. Nec no sale |regione, meliore civibus placerat id eam. Mea alii |fabulas definitionem te, agam volutpat ad vis, et per |bonorum nonumes repudiandae.', 58, 'An his legere persecuti, oblique delicata efficiantur ex |vix, vel at graecis officiis maluisset. Et per impedit |voluptua, usu discere maiorum at. Ut assum ornatus |temporibus vis, an sea melius pericula. Ea dicunt oblique |phaedrum nam, eu duo movet nobis. His melius facilis eu, |vim malorum temporibus ne. Nec no sale regione, meliore |civibus placerat id eam. Mea alii fabulas definitionem te,| agam volutpat ad vis, et per bonorum nonumes repudiandae.' ); - assertIncrementalLineMapping( + assertIncrementalLineBreaks( factory, '\t\t"owner": "vscode",', 4, 14, '\t\t"owner|": |"vscod|e",', 16, '\t\t"owner":| |"vscode"|,', WrappingIndent.Same ); - assertIncrementalLineMapping( + assertIncrementalLineBreaks( factory, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇&👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬', 4, 51, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇&|👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬', 50, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇|&|👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬', WrappingIndent.Same ); - assertIncrementalLineMapping( + assertIncrementalLineBreaks( factory, '🐇👬&🌞🌖', 4, 5, '🐇👬&|🌞🌖', 4, '🐇👬|&|🌞🌖', WrappingIndent.Same ); - assertIncrementalLineMapping( + assertIncrementalLineBreaks( factory, '\t\tfunc(\'🌞🏇🍼🌞🏇🍼🐇&👬🌖🌞👬🌖🌞🏇🍼🐇👬\', WrappingIndent.Same);', 4, 26, '\t\tfunc|(\'🌞🏇🍼🌞🏇🍼🐇&|👬🌖🌞👬🌖🌞🏇🍼🐇|👬\', |WrappingIndent.|Same);', 27, '\t\tfunc|(\'🌞🏇🍼🌞🏇🍼🐇&|👬🌖🌞👬🌖🌞🏇🍼🐇|👬\', |WrappingIndent.|Same);', WrappingIndent.Same ); - assertIncrementalLineMapping( + assertIncrementalLineBreaks( factory, 'factory, "xtxtfunc(x"🌞🏇🍼🌞🏇🍼🐇&👬🌖🌞👬🌖🌞🏇🍼🐇👬x"', 4, 16, 'factory, |"xtxtfunc|(x"🌞🏇🍼🌞🏇🍼|🐇&|👬🌖🌞👬🌖🌞🏇🍼|🐇👬x"', 17, 'factory, |"xtxtfunc|(x"🌞🏇🍼🌞🏇🍼🐇|&👬🌖🌞👬🌖🌞🏇🍼|🐇👬x"', @@ -186,50 +186,50 @@ suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { }); - test('CharacterHardWrappingLineMapper - CJK and Kinsoku Shori', () => { - let factory = new CharacterHardWrappingLineMapperFactory('(', '\t)'); - assertLineMapping(factory, 4, 5, 'aa \u5b89|\u5b89'); - assertLineMapping(factory, 4, 5, '\u3042 \u5b89|\u5b89'); - assertLineMapping(factory, 4, 5, '\u3042\u3042|\u5b89\u5b89'); - assertLineMapping(factory, 4, 5, 'aa |\u5b89)\u5b89|\u5b89'); - assertLineMapping(factory, 4, 5, 'aa \u3042|\u5b89\u3042)|\u5b89'); - assertLineMapping(factory, 4, 5, 'aa |(\u5b89aa|\u5b89'); + test('MonospaceLineBreaksComputer - CJK and Kinsoku Shori', () => { + let factory = new MonospaceLineBreaksComputerFactory('(', '\t)'); + assertLineBreaks(factory, 4, 5, 'aa \u5b89|\u5b89'); + assertLineBreaks(factory, 4, 5, '\u3042 \u5b89|\u5b89'); + assertLineBreaks(factory, 4, 5, '\u3042\u3042|\u5b89\u5b89'); + assertLineBreaks(factory, 4, 5, 'aa |\u5b89)\u5b89|\u5b89'); + assertLineBreaks(factory, 4, 5, 'aa \u3042|\u5b89\u3042)|\u5b89'); + assertLineBreaks(factory, 4, 5, 'aa |(\u5b89aa|\u5b89'); }); - test('CharacterHardWrappingLineMapper - WrappingIndent.Same', () => { - let factory = new CharacterHardWrappingLineMapperFactory('', '\t '); - assertLineMapping(factory, 4, 38, ' *123456789012345678901234567890123456|7890', WrappingIndent.Same); + test('MonospaceLineBreaksComputer - WrappingIndent.Same', () => { + let factory = new MonospaceLineBreaksComputerFactory('', '\t '); + assertLineBreaks(factory, 4, 38, ' *123456789012345678901234567890123456|7890', WrappingIndent.Same); }); test('issue #16332: Scroll bar overlaying on top of text', () => { - let factory = new CharacterHardWrappingLineMapperFactory('', '\t '); - assertLineMapping(factory, 4, 24, 'a/ very/long/line/of/tex|t/that/expands/beyon|d/your/typical/line/|of/code/', WrappingIndent.Indent); + let factory = new MonospaceLineBreaksComputerFactory('', '\t '); + assertLineBreaks(factory, 4, 24, 'a/ very/long/line/of/tex|t/that/expands/beyon|d/your/typical/line/|of/code/', WrappingIndent.Indent); }); test('issue #35162: wrappingIndent not consistently working', () => { - let factory = new CharacterHardWrappingLineMapperFactory('', '\t '); - let mapper = assertLineMapping(factory, 4, 24, ' t h i s |i s |a l |o n |g l |i n |e', WrappingIndent.Indent); + let factory = new MonospaceLineBreaksComputerFactory('', '\t '); + let mapper = assertLineBreaks(factory, 4, 24, ' t h i s |i s |a l |o n |g l |i n |e', WrappingIndent.Indent); assert.equal(mapper!.wrappedTextIndentLength, ' '.length); }); test('issue #75494: surrogate pairs', () => { - let factory = new CharacterHardWrappingLineMapperFactory('\t', ' '); - assertLineMapping(factory, 4, 49, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼|🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼|🐇👬', WrappingIndent.Same); + let factory = new MonospaceLineBreaksComputerFactory('\t', ' '); + assertLineBreaks(factory, 4, 49, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼|🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼|🐇👬', WrappingIndent.Same); }); test('issue #75494: surrogate pairs overrun 1', () => { - const factory = new CharacterHardWrappingLineMapperFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue); - assertLineMapping(factory, 4, 4, '🐇👬|&|🌞🌖', WrappingIndent.Same); + const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue); + assertLineBreaks(factory, 4, 4, '🐇👬|&|🌞🌖', WrappingIndent.Same); }); test('issue #75494: surrogate pairs overrun 2', () => { - const factory = new CharacterHardWrappingLineMapperFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue); - assertLineMapping(factory, 4, 17, 'factory, |"xtxtfunc|(x"🌞🏇🍼🌞🏇🍼🐇|&👬🌖🌞👬🌖🌞🏇🍼|🐇👬x"', WrappingIndent.Same); + const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue); + assertLineBreaks(factory, 4, 17, 'factory, |"xtxtfunc|(x"🌞🏇🍼🌞🏇🍼🐇|&👬🌖🌞👬🌖🌞🏇🍼|🐇👬x"', WrappingIndent.Same); }); - test('CharacterHardWrappingLineMapper - WrappingIndent.DeepIndent', () => { - let factory = new CharacterHardWrappingLineMapperFactory('', '\t '); - let mapper = assertLineMapping(factory, 4, 26, ' W e A r e T e s t |i n g D e |e p I n d |e n t a t |i o n', WrappingIndent.DeepIndent); + test('MonospaceLineBreaksComputer - WrappingIndent.DeepIndent', () => { + let factory = new MonospaceLineBreaksComputerFactory('', '\t '); + let mapper = assertLineBreaks(factory, 4, 26, ' W e A r e T e s t |i n g D e |e p I n d |e n t a t |i o n', WrappingIndent.DeepIndent); assert.equal(mapper!.wrappedTextIndentLength, ' '.length); }); }); diff --git a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts index ba4228ce9a644..7209c90e881c0 100644 --- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts +++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts @@ -13,8 +13,8 @@ import { EndOfLinePreference } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import * as modes from 'vs/editor/common/modes'; import { NULL_STATE } from 'vs/editor/common/modes/nullMode'; -import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; -import { LineBreakingData, ISimpleModel, SplitLine, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection'; +import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; +import { LineBreakData, ISimpleModel, SplitLine, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection'; import { ViewLineData } from 'vs/editor/common/viewModel/viewModel'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -95,7 +95,7 @@ suite('Editor ViewModel - SplitLinesCollection', () => { const wordWrapBreakBeforeCharacters = config.options.get(EditorOption.wordWrapBreakBeforeCharacters); const wrappingIndent = config.options.get(EditorOption.wrappingIndent); - const hardWrappingLineMapperFactory = new CharacterHardWrappingLineMapperFactory( + const lineBreaksComputerFactory = new MonospaceLineBreaksComputerFactory( wordWrapBreakBeforeCharacters, wordWrapBreakAfterCharacters ); @@ -111,7 +111,7 @@ suite('Editor ViewModel - SplitLinesCollection', () => { const linesCollection = new SplitLinesCollection( model, - hardWrappingLineMapperFactory, + lineBreaksComputerFactory, model.getOptions().tabSize, wrappingInfo.wrappingColumn, fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth, @@ -747,7 +747,7 @@ suite('SplitLinesCollection', () => { const wordWrapBreakBeforeCharacters = configuration.options.get(EditorOption.wordWrapBreakBeforeCharacters); const wrappingIndent = configuration.options.get(EditorOption.wrappingIndent); - const factory = new CharacterHardWrappingLineMapperFactory( + const factory = new MonospaceLineBreaksComputerFactory( wordWrapBreakBeforeCharacters, wordWrapBreakAfterCharacters ); @@ -773,15 +773,15 @@ function pos(lineNumber: number, column: number): Position { } function createSplitLine(splitLengths: number[], breakingOffsetsVisibleColumn: number[], wrappedTextIndentWidth: number, isVisible: boolean = true): SplitLine { - return new SplitLine(createLineMapping(splitLengths, breakingOffsetsVisibleColumn, wrappedTextIndentWidth), isVisible); + return new SplitLine(createLineBreakData(splitLengths, breakingOffsetsVisibleColumn, wrappedTextIndentWidth), isVisible); } -function createLineMapping(breakingLengths: number[], breakingOffsetsVisibleColumn: number[], wrappedTextIndentWidth: number): LineBreakingData { +function createLineBreakData(breakingLengths: number[], breakingOffsetsVisibleColumn: number[], wrappedTextIndentWidth: number): LineBreakData { let sums: number[] = []; for (let i = 0; i < breakingLengths.length; i++) { sums[i] = (i > 0 ? sums[i - 1] : 0) + breakingLengths[i]; } - return new LineBreakingData(sums, breakingOffsetsVisibleColumn, wrappedTextIndentWidth); + return new LineBreakData(sums, breakingOffsetsVisibleColumn, wrappedTextIndentWidth); } function createModel(text: string): ISimpleModel { diff --git a/src/vs/editor/test/common/viewModel/testViewModel.ts b/src/vs/editor/test/common/viewModel/testViewModel.ts index 6513d075b5ecc..fffa9f05aa95d 100644 --- a/src/vs/editor/test/common/viewModel/testViewModel.ts +++ b/src/vs/editor/test/common/viewModel/testViewModel.ts @@ -7,7 +7,7 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { TextModel } from 'vs/editor/common/model/textModel'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; -import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; +import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; export function testViewModel(text: string[], options: IEditorOptions, callback: (viewModel: ViewModel, model: TextModel) => void): void { const EDITOR_ID = 1; @@ -16,7 +16,7 @@ export function testViewModel(text: string[], options: IEditorOptions, callback: let model = TextModel.createFromString(text.join('\n')); - let viewModel = new ViewModel(EDITOR_ID, configuration, model, CharacterHardWrappingLineMapperFactory.create(configuration.options), null!); + let viewModel = new ViewModel(EDITOR_ID, configuration, model, MonospaceLineBreaksComputerFactory.create(configuration.options), null!); callback(viewModel, model); From 6411ea680afeb8e853137bcf925e0cff990ab588 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 10 Jan 2020 10:41:00 +0100 Subject: [PATCH 128/315] add keyboard command to check/uncheck changes and to apply changes --- .../bulkEdit/browser/bulkEdit.contribution.ts | 58 +++++++++++++++---- .../contrib/bulkEdit/browser/bulkEditPane.ts | 31 ++++++++-- .../contrib/bulkEdit/browser/bulkEditTree.ts | 18 ++---- 3 files changed, 78 insertions(+), 29 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts index 19e206bf2c689..e87a536c82f1d 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts @@ -14,14 +14,29 @@ import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewCon import { localize } from 'vs/nls'; import { ViewPaneContainer, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { PaneCompositePanel } from 'vs/workbench/browser/panel'; -import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { RawContextKey, IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { BulkEditPreviewProvider } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; + +function getBulkEditPane(panelService: IPanelService): BulkEditPane | undefined { + let view: ViewPane | undefined; + const activePanel = panelService.openPanel(BulkEditPane.ID, true); + if (activePanel instanceof PaneCompositePanel) { + view = activePanel.getViewPaneContainer().getView(BulkEditPane.ID); + } + if (view instanceof BulkEditPane) { + return view; + } + return undefined; +} class BulkEditPreviewContribution { - static readonly ctxEnabled = new RawContextKey('refactorPreviewIsEnabled', false); + static readonly ctxEnabled = new RawContextKey('refactorPreview.enabled', false); private readonly _ctxEnabled: IContextKey; @@ -40,14 +55,8 @@ class BulkEditPreviewContribution { const oldActivePanel = this._panelService.getActivePanel(); try { - - let view: ViewPane | undefined; - const activePanel = this._panelService.openPanel(BulkEditPane.ID, true); - if (activePanel instanceof PaneCompositePanel) { - view = activePanel.getViewPaneContainer().getView(BulkEditPane.ID); - } - - if (!(view instanceof BulkEditPane)) { + const view = getBulkEditPane(this._panelService); + if (!view) { return edit; } @@ -86,11 +95,38 @@ class BulkEditPreviewContribution { } } +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'refactorPreview.apply', + weight: KeybindingWeight.WorkbenchContrib, + when: BulkEditPreviewContribution.ctxEnabled, + primary: KeyMod.Shift + KeyCode.Enter, + handler(accessor) { + const panelService = accessor.get(IPanelService); + const view = getBulkEditPane(panelService); + if (view) { + view.accept(); + } + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'refactorPreview.toggleCheckedState', + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(BulkEditPreviewContribution.ctxEnabled, WorkbenchListFocusContextKey), + primary: KeyCode.Space, + handler(accessor) { + const panelService = accessor.get(IPanelService); + const view = getBulkEditPane(panelService); + if (view) { + view.toggleChecked(); + } + } +}); + Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution( BulkEditPreviewContribution, LifecyclePhase.Ready ); - const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: BulkEditPane.ID, name: localize('panel', "Refactor Preview"), diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index 11ab40d84e266..8bdaed6bb8824 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -38,8 +38,8 @@ export class BulkEditPane extends ViewPane { private _tree!: WorkbenchAsyncDataTree; private _message!: HTMLSpanElement; - private readonly _acceptAction = new Action('ok', localize('ok', "Apply Refactoring"), 'codicon-check', false, async () => this._done(true)); - private readonly _discardAction = new Action('discard', localize('discard', "Discard"), 'codicon-trash', false, async () => this._done(false)); + private readonly _acceptAction = new Action('ok', localize('ok', "Apply Refactoring"), 'codicon-check', false, async () => this.accept()); + private readonly _discardAction = new Action('discard', localize('discard', "Discard"), 'codicon-trash', false, async () => this.discard()); private readonly _disposables = new DisposableStore(); private readonly _sessionDisposables = new DisposableStore(); @@ -149,9 +149,29 @@ export class BulkEditPane extends ViewPane { if (first instanceof FileElement) { this._tree.expand(first); } + + // refresh when check state changes + this._sessionDisposables.add(input.onDidChangeCheckedState(() => { + this._tree.updateChildren(); + })); }); } + accept(): void { + this._done(true); + } + + discard() { + this._done(false); + } + + toggleChecked() { + const [first] = this._tree.getFocus(); + if (first) { + first.edit.updateChecked(!first.edit.isChecked()); + } + } + private _done(accept: boolean): void { this._setState(State.Message); this._sessionDisposables.clear(); @@ -180,10 +200,9 @@ export class BulkEditPane extends ViewPane { rightResource: previewUri, label: localize('edt.title', "{0} (Refactor Preview)", this._labelService.getUriLabel(element.parent.uri)), options: { - selection: element.edit.edit.range - // preserveFocus, - // pinned, - // revealIfVisible: true + selection: element.edit.edit.range, + revealInCenterIfOutsideViewport: true, + preserveFocus: true } }); } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index c7d86541996f2..67e5cd70388a6 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -139,30 +139,26 @@ export class BulkEditIdentityProvider implements IIdentityProvider { - if (this._element) { - this._element.edit.updateChecked(_checkbox.checked); - } - })); this._disposables.add(attachCheckboxStyler(_checkbox, themeService)); } dispose(): void { - this._element = undefined; + this._localDisposables.dispose(); this._disposables.dispose(); this._checkbox.dispose(); this._label.dispose(); } set(element: FileElement, score: FuzzyScore | undefined) { - this._element = element; + this._localDisposables.clear(); + this._localDisposables.add(this._checkbox.onChange(() => element.edit.updateChecked(this._checkbox.checked))); this._checkbox.checked = element.edit.isChecked(); const extraClasses: string[] = []; @@ -243,9 +239,8 @@ class TextEditElementTemplate { set(element: TextEditElement) { this._localDisposables.clear(); this._localDisposables.add(this._checkbox.onChange(() => element.edit.updateChecked(this._checkbox.checked))); - this._localDisposables.add(element.edit.parent.parent.onDidChangeCheckedState(() => { - dom.toggleClass(this._checkbox.domNode, 'disabled', !element.edit.parent.isChecked()); - })); + this._checkbox.checked = element.edit.isChecked(); + dom.toggleClass(this._checkbox.domNode, 'disabled', !element.edit.parent.isChecked()); let value = ''; value += element.prefix; @@ -257,7 +252,6 @@ class TextEditElementTemplate { let insertHighlight: IHighlight = { start: selectHighlight.end, end: selectHighlight.end + element.inserting.length, extraClasses: 'insert' }; this._label.set(value, [selectHighlight, insertHighlight], undefined, true); - this._checkbox.checked = element.edit.isChecked(); } } From 9298d3b4b8ffc1c98f8c64a79915f8d15230a171 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Fri, 10 Jan 2020 10:48:53 +0100 Subject: [PATCH 129/315] Fixes #33366: Avoid breaking before a space --- .../viewModel/monospaceLineBreaksComputer.ts | 19 +++++++++++-------- .../monospaceLineBreaksComputer.test.ts | 7 ++++++- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts index be52ca2265a7f..dfec62fc04fe7 100644 --- a/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts +++ b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts @@ -165,7 +165,7 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar); } - if (canBreak(prevCharCodeClass, charCodeClass)) { + if (canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass)) { breakOffset = charStartOffset; breakOffsetVisibleColumn = visibleColumn; } @@ -243,7 +243,7 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla break; } - if (canBreak(prevCharCodeClass, charCodeClass)) { + if (canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass)) { breakOffset = charStartOffset; breakOffsetVisibleColumn = visibleColumn; break; @@ -370,7 +370,7 @@ function createLineBreaks(classifier: WrappingCharacterClassifier, lineText: str charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar); } - if (canBreak(prevCharCodeClass, charCodeClass)) { + if (canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass)) { breakOffset = charStartOffset; breakOffsetVisibleColumn = visibleColumn; } @@ -427,12 +427,15 @@ function tabCharacterWidth(visibleColumn: number, tabSize: number): number { * Kinsoku Shori : Don't break after a leading character, like an open bracket * Kinsoku Shori : Don't break before a trailing character, like a period */ -function canBreak(prevCharCodeClass: CharacterClass, charCodeClass: CharacterClass): boolean { +function canBreak(prevCharCode: number, prevCharCodeClass: CharacterClass, charCode: number, charCodeClass: CharacterClass): boolean { return ( - (prevCharCodeClass === CharacterClass.BREAK_AFTER) - || (prevCharCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && charCodeClass !== CharacterClass.BREAK_AFTER) - || (charCodeClass === CharacterClass.BREAK_BEFORE) - || (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && prevCharCodeClass !== CharacterClass.BREAK_BEFORE) + charCode !== CharCode.Space + && ( + (prevCharCodeClass === CharacterClass.BREAK_AFTER) + || (prevCharCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && charCodeClass !== CharacterClass.BREAK_AFTER) + || (charCodeClass === CharacterClass.BREAK_BEFORE) + || (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && prevCharCodeClass !== CharacterClass.BREAK_BEFORE) + ) ); } diff --git a/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts b/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts index 625c678ff4767..a95095bbc9933 100644 --- a/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts +++ b/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts @@ -146,7 +146,7 @@ suite('Editor ViewModel - MonospaceLineBreaksComputer', () => { assertIncrementalLineBreaks( factory, 'An his legere persecuti, oblique delicata efficiantur ex vix, vel at graecis officiis maluisset. Et per impedit voluptua, usu discere maiorum at. Ut assum ornatus temporibus vis, an sea melius pericula. Ea dicunt oblique phaedrum nam, eu duo movet nobis. His melius facilis eu, vim malorum temporibus ne. Nec no sale regione, meliore civibus placerat id eam. Mea alii fabulas definitionem te, agam volutpat ad vis, et per bonorum nonumes repudiandae.', 4, 57, 'An his legere persecuti, oblique delicata efficiantur ex |vix, vel at graecis officiis maluisset. Et per impedit |voluptua, usu discere maiorum at. Ut assum ornatus |temporibus vis, an sea melius pericula. Ea dicunt |oblique phaedrum nam, eu duo movet nobis. His melius |facilis eu, vim malorum temporibus ne. Nec no sale |regione, meliore civibus placerat id eam. Mea alii |fabulas definitionem te, agam volutpat ad vis, et per |bonorum nonumes repudiandae.', - 58, 'An his legere persecuti, oblique delicata efficiantur ex |vix, vel at graecis officiis maluisset. Et per impedit |voluptua, usu discere maiorum at. Ut assum ornatus |temporibus vis, an sea melius pericula. Ea dicunt oblique |phaedrum nam, eu duo movet nobis. His melius facilis eu, |vim malorum temporibus ne. Nec no sale regione, meliore |civibus placerat id eam. Mea alii fabulas definitionem te,| agam volutpat ad vis, et per bonorum nonumes repudiandae.' + 58, 'An his legere persecuti, oblique delicata efficiantur ex |vix, vel at graecis officiis maluisset. Et per impedit |voluptua, usu discere maiorum at. Ut assum ornatus |temporibus vis, an sea melius pericula. Ea dicunt oblique |phaedrum nam, eu duo movet nobis. His melius facilis eu, |vim malorum temporibus ne. Nec no sale regione, meliore |civibus placerat id eam. Mea alii fabulas definitionem |te, agam volutpat ad vis, et per bonorum nonumes |repudiandae.' ); assertIncrementalLineBreaks( @@ -232,4 +232,9 @@ suite('Editor ViewModel - MonospaceLineBreaksComputer', () => { let mapper = assertLineBreaks(factory, 4, 26, ' W e A r e T e s t |i n g D e |e p I n d |e n t a t |i o n', WrappingIndent.DeepIndent); assert.equal(mapper!.wrappedTextIndentLength, ' '.length); }); + + test('issue #33366: Word wrap algorithm behaves differently around punctuation', () => { + const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue); + assertLineBreaks(factory, 4, 23, 'this is a line of |text, text that sits |on a line', WrappingIndent.Same); + }); }); From 25275a298119c745628b402a8e1a70a0d8975489 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 10 Jan 2020 10:53:17 +0100 Subject: [PATCH 130/315] Fix port labeling and localhost candidate display --- .../remote/common/remoteExplorerService.ts | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index cfc38fbed3be4..fe35ca3b9f532 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -45,11 +45,15 @@ export interface Tunnel { closeable?: boolean; } -export function MakeAddress(host: string, port: number): string { +function ToLocalHost(host: string): string { if (host === '127.0.0.1') { host = 'localhost'; } - return host + ':' + port; + return host; +} + +export function MakeAddress(host: string, port: number): string { + return ToLocalHost(host) + ':' + port; } export class TunnelModel extends Disposable { @@ -201,7 +205,13 @@ export class TunnelModel extends Disposable { return; } if (this._candidateFinder) { - this._candidates = await this._candidateFinder(); + this._candidates = (await this._candidateFinder()).map(value => { + return { + host: ToLocalHost(value.host), + port: value.port, + detail: value.detail + }; + }); } } @@ -286,7 +296,9 @@ class RemoteExplorerService implements IRemoteExplorerService { } getEditableData(tunnelItem: ITunnelItem | undefined): IEditableData | undefined { - return (this._editable && (!tunnelItem || (this._editable.tunnelItem?.remotePort === tunnelItem.remotePort) && (this._editable.tunnelItem.remoteHost === tunnelItem.remoteHost))) ? + return (this._editable && + ((!tunnelItem && (tunnelItem === this._editable.tunnelItem)) || + (tunnelItem && (this._editable.tunnelItem?.remotePort === tunnelItem.remotePort) && (this._editable.tunnelItem.remoteHost === tunnelItem.remoteHost)))) ? this._editable.data : undefined; } From 0398618f452acd0bd9f057d1d9efacec75d6a3eb Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 10 Jan 2020 11:15:03 +0100 Subject: [PATCH 131/315] Allow to save readonly documents (fixes #22121) --- .../editor/common/services/resolverService.ts | 7 +- .../standalone/browser/simpleServices.ts | 4 + .../common/editor/resourceEditorInput.ts | 31 ++++++- .../contrib/output/browser/logViewer.ts | 10 ++- .../electron-browser/perfviewEditor.ts | 10 ++- .../common/preferencesEditorInput.ts | 11 ++- .../textfile/browser/textFileService.ts | 87 +++++++++++-------- .../electron-browser/nativeTextFileService.ts | 6 +- .../common/textModelResolverService.ts | 4 + .../workbench/test/workbenchTestServices.ts | 6 +- 10 files changed, 123 insertions(+), 53 deletions(-) diff --git a/src/vs/editor/common/services/resolverService.ts b/src/vs/editor/common/services/resolverService.ts index 77719683a2331..236bc5ef3ffd3 100644 --- a/src/vs/editor/common/services/resolverService.ts +++ b/src/vs/editor/common/services/resolverService.ts @@ -53,9 +53,14 @@ export interface ITextEditorModel extends IEditorModel { createSnapshot(this: ITextEditorModel): ITextSnapshot | null; /** - * Signals if this model is readonly or not. + * Signals if this model is readonly or not. */ isReadonly(): boolean; + + /** + * Figure out if this model is resolved or not. + */ + isResolved(): this is IResolvedTextEditorModel; } export interface IResolvedTextEditorModel extends ITextEditorModel { diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index b5180aa9a2a1e..409a1dfeb528a 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -80,6 +80,10 @@ export class SimpleModel implements IResolvedTextEditorModel { public dispose(): void { this._onDispose.fire(); } + + public isResolved(): boolean { + return true; + } } export interface IOpenEditorDelegate { diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index ee9605889c4cd..c7f8bbbcb3c45 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -3,12 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { EditorInput, ITextEditorModel, IModeSupport } from 'vs/workbench/common/editor'; +import { EditorInput, ITextEditorModel, IModeSupport, GroupIdentifier, isTextEditor } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { IReference } from 'vs/base/common/lifecycle'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; import { basename } from 'vs/base/common/resources'; +import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import type { IEditorViewState } from 'vs/editor/common/editorCommon'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; /** * A read-only text editor input whos contents are made of the provided resource that points to an existing @@ -26,7 +29,9 @@ export class ResourceEditorInput extends EditorInput implements IModeSupport { private description: string | undefined, private readonly resource: URI, private preferredMode: string | undefined, - @ITextModelService private readonly textModelResolverService: ITextModelService + @ITextModelService private readonly textModelResolverService: ITextModelService, + @ITextFileService private readonly textFileService: ITextFileService, + @IEditorService private readonly editorService: IEditorService ) { super(); @@ -104,6 +109,28 @@ export class ResourceEditorInput extends EditorInput implements IModeSupport { return model; } + async saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { + + // Preserve view state by opening the editor first. In addition + // this allows the user to review the contents of the editor. + let viewState: IEditorViewState | undefined = undefined; + const editor = await this.editorService.openEditor(this, undefined, group); + if (isTextEditor(editor)) { + viewState = editor.getViewState(); + } + + // Save as + const target = await this.textFileService.saveAs(this.resource, undefined, options); + if (!target) { + return false; // save cancelled + } + + // Open the target + await this.editorService.openEditor({ resource: target, options: { viewState, pinned: true } }, group); + + return true; + } + matches(otherInput: unknown): boolean { if (super.matches(otherInput) === true) { return true; diff --git a/src/vs/workbench/contrib/output/browser/logViewer.ts b/src/vs/workbench/contrib/output/browser/logViewer.ts index f672f55cac299..dbe437aff0c57 100644 --- a/src/vs/workbench/contrib/output/browser/logViewer.ts +++ b/src/vs/workbench/contrib/output/browser/logViewer.ts @@ -18,15 +18,19 @@ import { LOG_SCHEME } from 'vs/workbench/contrib/output/common/output'; import { IFileOutputChannelDescriptor } from 'vs/workbench/services/output/common/output'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; export class LogViewerInput extends ResourceEditorInput { static readonly ID = 'workbench.editorinputs.output'; - constructor(private readonly outputChannelDescriptor: IFileOutputChannelDescriptor, - @ITextModelService textModelResolverService: ITextModelService + constructor( + private readonly outputChannelDescriptor: IFileOutputChannelDescriptor, + @ITextModelService textModelResolverService: ITextModelService, + @ITextFileService textFileService: ITextFileService, + @IEditorService editorService: IEditorService ) { - super(basename(outputChannelDescriptor.file.path), dirname(outputChannelDescriptor.file.path), URI.from({ scheme: LOG_SCHEME, path: outputChannelDescriptor.id }), undefined, textModelResolverService); + super(basename(outputChannelDescriptor.file.path), dirname(outputChannelDescriptor.file.path), URI.from({ scheme: LOG_SCHEME, path: outputChannelDescriptor.id }), undefined, textModelResolverService, textFileService, editorService); } getTypeId(): string { diff --git a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts index c9452a33c103c..31ab953bff3ab 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts @@ -21,6 +21,8 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { writeTransientState } from 'vs/workbench/contrib/codeEditor/browser/toggleWordWrap'; import { mergeSort } from 'vs/base/common/arrays'; import product from 'vs/platform/product/common/product'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class PerfviewContrib { @@ -44,14 +46,18 @@ export class PerfviewInput extends ResourceEditorInput { static readonly Uri = URI.from({ scheme: 'perf', path: 'Startup Performance' }); constructor( - @ITextModelService textModelResolverService: ITextModelService + @ITextModelService textModelResolverService: ITextModelService, + @ITextFileService textFileService: ITextFileService, + @IEditorService editorService: IEditorService ) { super( localize('name', "Startup Performance"), undefined, PerfviewInput.Uri, undefined, - textModelResolverService + textModelResolverService, + textFileService, + editorService ); } diff --git a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts index 0b4e5a69d7932..501a673894f80 100644 --- a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts +++ b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts @@ -13,6 +13,8 @@ import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorIn import { KeybindingsEditorModel } from 'vs/workbench/services/preferences/common/keybindingsEditorModel'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class PreferencesEditorInput extends SideBySideEditorInput { static readonly ID: string = 'workbench.editorinputs.preferencesEditorInput'; @@ -28,10 +30,13 @@ export class PreferencesEditorInput extends SideBySideEditorInput { export class DefaultPreferencesEditorInput extends ResourceEditorInput { static readonly ID = 'workbench.editorinputs.defaultpreferences'; - constructor(defaultSettingsResource: URI, - @ITextModelService textModelResolverService: ITextModelService + constructor( + defaultSettingsResource: URI, + @ITextModelService textModelResolverService: ITextModelService, + @ITextFileService textFileService: ITextFileService, + @IEditorService editorService: IEditorService ) { - super(nls.localize('settingsEditorName', "Default Settings"), '', defaultSettingsResource, undefined, textModelResolverService); + super(nls.localize('settingsEditorName', "Default Settings"), '', defaultSettingsResource, undefined, textModelResolverService, textFileService, editorService); } getTypeId(): string { diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 93658cabb39e3..0c0d2cad011f5 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -9,7 +9,7 @@ import { Emitter, AsyncEmitter } from 'vs/base/common/event'; import * as platform from 'vs/base/common/platform'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, FileOperationWillRunEvent, FileOperationDidRunEvent, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; -import { SaveReason, IRevertOptions } from 'vs/workbench/common/editor'; +import { SaveReason, IRevertOptions, IEncodingSupport } from 'vs/workbench/common/editor'; import { ILifecycleService, ShutdownReason, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IFileService, FileOperationError, FileOperationResult, HotExitConfiguration, IFileStatWithMetadata, ICreateFileOptions, FileOperation } from 'vs/platform/files/common/files'; @@ -37,6 +37,7 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/tex import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; /** * The workbench file service implementation implements the raw file service spec and adds additional methods on top. @@ -76,7 +77,8 @@ export abstract class AbstractTextFileService extends Disposable implements ITex @IFileDialogService private readonly fileDialogService: IFileDialogService, @IEditorService private readonly editorService: IEditorService, @ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService, - @IFilesConfigurationService protected readonly filesConfigurationService: IFilesConfigurationService + @IFilesConfigurationService protected readonly filesConfigurationService: IFilesConfigurationService, + @ITextModelService private readonly textModelService: ITextModelService ) { super(); @@ -676,68 +678,77 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return this.getFileModels(resources).filter(model => model.isDirty()); } - async saveAs(resource: URI, targetResource?: URI, options?: ITextFileSaveOptions): Promise { + async saveAs(source: URI, target?: URI, options?: ITextFileSaveOptions): Promise { // Get to target resource - if (!targetResource) { - let dialogPath = resource; - if (resource.scheme === Schemas.untitled) { - dialogPath = this.suggestFileName(resource); + if (!target) { + let dialogPath = source; + if (source.scheme === Schemas.untitled) { + dialogPath = this.suggestFileName(source); } - targetResource = await this.promptForPath(resource, dialogPath, options ? options.availableFileSystems : undefined); + target = await this.promptForPath(source, dialogPath, options ? options.availableFileSystems : undefined); } - if (!targetResource) { + if (!target) { return; // user canceled } // Just save if target is same as models own resource - if (resource.toString() === targetResource.toString()) { - await this.save(resource, options); + if (source.toString() === target.toString()) { + await this.save(source, options); - return resource; + return source; } // Do it - return this.doSaveAs(resource, targetResource, options); + return this.doSaveAs(source, target, options); } - private async doSaveAs(resource: URI, target: URI, options?: ITextFileSaveOptions): Promise { + private async doSaveAs(source: URI, target: URI, options?: ITextFileSaveOptions): Promise { + let success = false; - // Retrieve text model from provided resource if any - let model: ITextFileEditorModel | UntitledTextEditorModel | undefined; - if (this.fileService.canHandleResource(resource)) { - model = this._models.get(resource); - } else if (resource.scheme === Schemas.untitled && this.untitledTextEditorService.exists(resource)) { - model = await this.untitledTextEditorService.createOrGet({ resource }).resolve(); + // If the source is an existing text file model, we can directly + // use that model to copy the contents to the target destination + const textFileModel = this._models.get(source); + if (textFileModel && textFileModel.isResolved()) { + success = await this.doSaveAsTextFile(textFileModel, source, target, options); } - // We have a model: Use it (can be null e.g. if this file is binary and not a text file or was never opened before) - let result: boolean; - if (model) { - result = await this.doSaveTextFileAs(model, resource, target, options); + // Otherwise if the source can be handled by the file service + // we can simply invoke the copy() function to save as + else if (this.fileService.canHandleResource(source)) { + await this.fileService.copy(source, target); + + success = true; } - // Otherwise we can only copy - else { - await this.fileService.copy(resource, target); + // Finally, if the source does not seem to be a file, we have to + // try to resolve a text model from the resource to get at the + // contents and additional meta data (e.g. encoding). + else if (this.textModelService.hasTextModelContentProvider(source.scheme)) { + const modelReference = await this.textModelService.createModelReference(source); + success = await this.doSaveAsTextFile(modelReference.object, source, target, options); - result = true; + modelReference.dispose(); // free up our use of the reference } - // Return early if the operation was not running - if (!result) { - return target; + // Revert the source if result is success + if (success) { + await this.revert(source); } - // Revert the source - await this.revert(resource); - return target; } - private async doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledTextEditorModel, resource: URI, target: URI, options?: ITextFileSaveOptions): Promise { + private async doSaveAsTextFile(sourceModel: IResolvedTextEditorModel, source: URI, target: URI, options?: ITextFileSaveOptions): Promise { + + // Find source encoding if any + let sourceModelEncoding: string | undefined = undefined; + const sourceModelWithEncodingSupport = (sourceModel as unknown as IEncodingSupport); + if (typeof sourceModelWithEncodingSupport.getEncoding === 'function') { + sourceModelEncoding = sourceModelWithEncodingSupport.getEncoding(); + } // Prefer an existing model if it is already loaded for the given target resource let targetExists: boolean = false; @@ -764,7 +775,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } } - targetModel = await this.models.loadOrCreate(target, { encoding: sourceModel.getEncoding(), mode }); + targetModel = await this.models.loadOrCreate(target, { encoding: sourceModelEncoding, mode }); } try { @@ -785,7 +796,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } // take over model value, encoding and mode (only if more specific) from source model - targetModel.updatePreferredEncoding(sourceModel.getEncoding()); + targetModel.updatePreferredEncoding(sourceModelEncoding); if (sourceModel.isResolved() && targetModel.isResolved()) { this.modelService.updateModel(targetModel.textEditorModel, createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot())); @@ -809,7 +820,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex ) { await this.fileService.del(target); - return this.doSaveTextFileAs(sourceModel, resource, target, options); + return this.doSaveAsTextFile(sourceModel, source, target, options); } throw error; diff --git a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index 5e34b6d4c826f..17eeb9d92286a 100644 --- a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -41,6 +41,7 @@ import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/d import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { assign } from 'vs/base/common/objects'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; export class NativeTextFileService extends AbstractTextFileService { @@ -62,9 +63,10 @@ export class NativeTextFileService extends AbstractTextFileService { @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @IElectronService private readonly electronService: IElectronService, @IProductService private readonly productService: IProductService, - @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService + @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, + @ITextModelService textModelService: ITextModelService ) { - super(contextService, fileService, untitledTextEditorService, lifecycleService, instantiationService, modeService, modelService, environmentService, notificationService, backupFileService, historyService, dialogService, fileDialogService, editorService, textResourceConfigurationService, filesConfigurationService); + super(contextService, fileService, untitledTextEditorService, lifecycleService, instantiationService, modeService, modelService, environmentService, notificationService, backupFileService, historyService, dialogService, fileDialogService, editorService, textResourceConfigurationService, filesConfigurationService, textModelService); } private _encoding: EncodingOracle | undefined; diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index 2b630820d7fe8..be9f2a32793fc 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -175,6 +175,10 @@ export class TextModelResolverService implements ITextModelService { } hasTextModelContentProvider(scheme: string): boolean { + if (scheme === network.Schemas.untitled || scheme === network.Schemas.inMemory) { + return true; // we handle untitled:// and inMemory:// within + } + return this.resourceModelCollection.hasTextModelContentProvider(scheme); } } diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 868e183d45c9d..c9b8e6f48d73d 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -213,7 +213,8 @@ export class TestTextFileService extends NativeTextFileService { @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @IElectronService electronService: IElectronService, @IProductService productService: IProductService, - @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService + @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, + @ITextModelService textModelService: ITextModelService ) { super( contextService, @@ -233,7 +234,8 @@ export class TestTextFileService extends NativeTextFileService { textResourceConfigurationService, electronService, productService, - filesConfigurationService + filesConfigurationService, + textModelService ); } From 8bbfbfff1921d2589ce7f5c279bd7a54d3ea5b08 Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 10 Jan 2020 11:18:01 +0100 Subject: [PATCH 132/315] fixes #88342 --- src/vs/workbench/contrib/files/browser/fileActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 87a82bf8b7c7a..3774785acbcf9 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -202,9 +202,9 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi let { message, detail } = getMoveToTrashMessage(distinctElements); detail += detail ? '\n' : ''; if (isWindows) { - detail += nls.localize('undoBin', "You can restore from the Recycle Bin."); + detail += distinctElements.length > 1 ? nls.localize('undoBinFiles', "You can restore these files from the Recycle Bin.") : nls.localize('undoBin', "You can restore this file from the Recycle Bin."); } else { - detail += nls.localize('undoTrash', "You can restore from the Trash."); + detail += distinctElements.length > 1 ? nls.localize('undoTrashFiles', "You can restore these files from the Trash.") : nls.localize('undoTrash', "You can restore this file from the Trash."); } confirmDeletePromise = dialogService.confirm({ From 13606845f5624de5f54fef6705c2c356b18cd199 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 10 Jan 2020 11:35:54 +0100 Subject: [PATCH 133/315] fix issue when having an empty text edit --- src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts index 5808f9a78fdc0..97140519b5da6 100644 --- a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts @@ -198,7 +198,7 @@ class BulkEditModel implements IDisposable { for (const edit of value) { if (makeMinimal) { const newEdits = await this._editorWorker.computeMoreMinimalEdits(edit.resource, edit.edits); - task.addEdit({ ...edit, edits: newEdits! }); + task.addEdit({ ...edit, edits: newEdits ?? edit.edits }); } else { task.addEdit(edit); From b4136b65c6919b97272d2c098cdb5a7f59f5b3dd Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 10 Jan 2020 11:49:31 +0100 Subject: [PATCH 134/315] use native checkbox for consistency --- .../contrib/bulkEdit/browser/bulkEdit.css | 15 ---- .../contrib/bulkEdit/browser/bulkEditPane.ts | 9 ++- .../contrib/bulkEdit/browser/bulkEditTree.ts | 76 ++++++++----------- 3 files changed, 38 insertions(+), 62 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css index e23be66097c3b..3cd76dc760f4b 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css @@ -28,24 +28,9 @@ } .monaco-workbench .bulk-edit-panel .monaco-tl-contents .edit-checkbox { - height: 16px; - width: 16px; - min-height: 16px; - min-width: 16px; align-self: center; - margin-right: 4px; - border: 1px solid transparent; - border-radius: 2px; } .monaco-workbench .bulk-edit-panel .monaco-tl-contents .edit-checkbox.disabled { opacity: .5; } - -.monaco-workbench .bulk-edit-panel .monaco-tl-contents .edit-checkbox.codicon[class*='codicon-'] { - font-size: 13px; -} - -.monaco-workbench .bulk-edit-panel .monaco-tl-contents .edit-checkbox.codicon:not(.checked)::before { - opacity: 0; -} diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index 8bdaed6bb8824..b9d1c6ca68b98 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -25,6 +25,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { ResourceLabels, IResourceLabelsContainer } from 'vs/workbench/browser/labels'; const enum State { Data = 'data', @@ -71,6 +72,12 @@ export class BulkEditPane extends ViewPane { protected renderBody(parent: HTMLElement): void { parent.classList.add('bulk-edit-panel', 'show-file-icons'); + const resourceLabels = this._instaService.createInstance( + ResourceLabels, + { onDidChangeVisibility: this.onDidChangeBodyVisibility } + ); + this._disposables.add(resourceLabels); + // tree const treeContainer = document.createElement('div'); treeContainer.className = 'tree'; @@ -81,7 +88,7 @@ export class BulkEditPane extends ViewPane { this._tree = this._instaService.createInstance( WorkbenchAsyncDataTree, this.id, treeContainer, new BulkEditDelegate(), - [this._instaService.createInstance(TextEditElementRenderer), this._instaService.createInstance(FileElementRenderer)], + [new TextEditElementRenderer(), new FileElementRenderer(resourceLabels)], this._instaService.createInstance(BulkEditDataSource), { identityProvider: new BulkEditIdentityProvider(), diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index 67e5cd70388a6..b83b83dc92b17 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -6,9 +6,8 @@ import { IAsyncDataSource, ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; -import { IResourceLabel, ResourceLabels, DEFAULT_LABELS_CONTAINER } from 'vs/workbench/browser/labels'; +import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; import { URI } from 'vs/base/common/uri'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { Range } from 'vs/editor/common/core/range'; @@ -18,9 +17,6 @@ import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { TextModel } from 'vs/editor/common/model/textModel'; import { BulkFileOperations, BulkFileOperation, BulkFileOperationType, BulkTextEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; import { localize } from 'vs/nls'; -import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { attachCheckboxStyler } from 'vs/platform/theme/common/styler'; import { FileKind } from 'vs/platform/files/common/files'; // --- VIEW MODEL @@ -141,24 +137,29 @@ class FileElementTemplate { private readonly _disposables = new DisposableStore(); private readonly _localDisposables = new DisposableStore(); - constructor( - private readonly _checkbox: Checkbox, - private readonly _label: IResourceLabel, - @IThemeService themeService: IThemeService - ) { - this._disposables.add(attachCheckboxStyler(_checkbox, themeService)); + private readonly _checkbox: HTMLInputElement; + private readonly _label: IResourceLabel; + + constructor(container: HTMLElement, resourceLabels: ResourceLabels) { + + this._checkbox = document.createElement('input'); + this._checkbox.className = 'edit-checkbox'; + this._checkbox.type = 'checkbox'; + this._checkbox.setAttribute('role', 'checkbox'); + container.appendChild(this._checkbox); + + this._label = resourceLabels.create(container, { supportHighlights: true }); } dispose(): void { this._localDisposables.dispose(); this._disposables.dispose(); - this._checkbox.dispose(); this._label.dispose(); } set(element: FileElement, score: FuzzyScore | undefined) { this._localDisposables.clear(); - this._localDisposables.add(this._checkbox.onChange(() => element.edit.updateChecked(this._checkbox.checked))); + this._localDisposables.add(dom.addDisposableListener(this._checkbox, 'change', (() => element.edit.updateChecked(this._checkbox.checked)))); this._checkbox.checked = element.edit.isChecked(); const extraClasses: string[] = []; @@ -187,24 +188,10 @@ export class FileElementRenderer implements ITreeRenderer, _index: number, template: FileElementTemplate): void { @@ -221,26 +208,30 @@ class TextEditElementTemplate { private readonly _disposables = new DisposableStore(); private readonly _localDisposables = new DisposableStore(); - constructor( - private readonly _checkbox: Checkbox, - private readonly _label: HighlightedLabel, - @IThemeService themeService: IThemeService - ) { + private readonly _checkbox: HTMLInputElement; + private readonly _label: HighlightedLabel; - this._disposables.add(attachCheckboxStyler(_checkbox, themeService)); + constructor(container: HTMLElement) { + this._checkbox = document.createElement('input'); + this._checkbox.className = 'edit-checkbox'; + this._checkbox.type = 'checkbox'; + this._checkbox.setAttribute('role', 'checkbox'); + container.appendChild(this._checkbox); + + this._label = new HighlightedLabel(container, false); + dom.addClass(this._label.element, 'textedit'); } dispose(): void { this._localDisposables.dispose(); this._disposables.dispose(); - this._checkbox.dispose(); } set(element: TextEditElement) { this._localDisposables.clear(); - this._localDisposables.add(this._checkbox.onChange(() => element.edit.updateChecked(this._checkbox.checked))); + this._localDisposables.add(dom.addDisposableListener(this._checkbox, 'change', () => element.edit.updateChecked(this._checkbox.checked))); this._checkbox.checked = element.edit.isChecked(); - dom.toggleClass(this._checkbox.domNode, 'disabled', !element.edit.parent.isChecked()); + dom.toggleClass(this._checkbox, 'disabled', !element.edit.parent.isChecked()); let value = ''; value += element.prefix; @@ -261,15 +252,8 @@ export class TextEditElementRenderer implements ITreeRenderer, _index: number, template: TextEditElementTemplate): void { From 416433886d16a6a05ace4c8b9edff2a34f1803f1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 10 Jan 2020 12:04:37 +0100 Subject: [PATCH 135/315] files - remove PENDING_AUTO_SAVE model state --- .../contrib/files/common/editors/fileEditorInput.ts | 6 ++++-- .../services/textfile/browser/browserTextFileService.ts | 2 +- .../services/textfile/common/textFileEditorModel.ts | 2 -- src/vs/workbench/services/textfile/common/textfiles.ts | 5 ----- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index b0a38ea9b9788..799f816c760a5 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -69,14 +69,16 @@ export class FileEditorInput extends TextEditorInput implements IFileEditorInput private registerListeners(): void { - // Model changes + // Dirty changes this._register(this.textFileService.models.onModelDirty(e => this.onDirtyStateChange(e))); this._register(this.textFileService.models.onModelSaveError(e => this.onDirtyStateChange(e))); this._register(this.textFileService.models.onModelSaved(e => this.onDirtyStateChange(e))); this._register(this.textFileService.models.onModelReverted(e => this.onDirtyStateChange(e))); - this._register(this.textFileService.models.onModelOrphanedChanged(e => this.onModelOrphanedChanged(e))); + + // Label changes this._register(this.labelService.onDidChangeFormatters(() => FileEditorInput.MEMOIZER.clear())); this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(() => FileEditorInput.MEMOIZER.clear())); + this._register(this.textFileService.models.onModelOrphanedChanged(e => this.onModelOrphanedChanged(e))); } private onDirtyStateChange(e: TextFileModelChangeEvent): void { diff --git a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts index 858552d31c3b9..37bbc4bc07066 100644 --- a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts +++ b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts @@ -26,7 +26,7 @@ export class BrowserTextFileService extends AbstractTextFileService { } private doBeforeShutdownSync(): boolean { - if (this.models.getAll().some(model => model.hasState(ModelState.PENDING_SAVE) || model.hasState(ModelState.PENDING_AUTO_SAVE))) { + if (this.models.getAll().some(model => model.hasState(ModelState.PENDING_SAVE))) { return true; // files are pending to be saved: veto } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 539c595282f29..a8458d9f09fe2 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -953,8 +953,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.inOrphanMode; case ModelState.PENDING_SAVE: return this.saveSequentializer.hasPendingSave(); - case ModelState.PENDING_AUTO_SAVE: - return !!this.autoSaveDisposable.value; case ModelState.SAVED: return !this.dirty; } diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index b7abec08cba5f..a61a80c21d406 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -257,11 +257,6 @@ export const enum ModelState { */ PENDING_SAVE, - /** - * A model is marked for being saved after a specific timeout. - */ - PENDING_AUTO_SAVE, - /** * A model is in conflict mode when changes cannot be saved because the * underlying file has changed. Models in conflict mode are always dirty. From 358b1ab5af5a61b40c1c06c4c2cff0c583f25dae Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Fri, 10 Jan 2020 16:38:17 +0530 Subject: [PATCH 136/315] skip webview tests for https://github.com/microsoft/vscode/issues/88415 --- .../vscode-api-tests/src/singlefolder-tests/webview.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts index e785f1d4afbe1..b59d91ff380c7 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts @@ -13,7 +13,8 @@ const webviewId = 'myWebview'; const testDocument = join(vscode.workspace.rootPath || '', './bower.json'); -suite('Webview tests', () => { +// TODO: Re-enable after https://github.com/microsoft/vscode/issues/88415 +suite.skip('Webview tests', () => { const disposables: vscode.Disposable[] = []; function _register(disposable: T) { From 68fadbd08ed01fce4c4c276232d8a8bfe8bbed9d Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 10 Jan 2020 12:40:32 +0100 Subject: [PATCH 137/315] prepare for label --- src/vs/base/browser/ui/splitview/paneview.ts | 4 ++-- src/vs/editor/browser/services/bulkEditService.ts | 1 + src/vs/editor/contrib/rename/rename.ts | 3 ++- .../contrib/bulkEdit/browser/bulkEdit.contribution.ts | 8 ++++---- src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css | 8 ++++---- src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts | 8 +++++--- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/vs/base/browser/ui/splitview/paneview.ts b/src/vs/base/browser/ui/splitview/paneview.ts index e50add0846a28..037ffdce27b9c 100644 --- a/src/vs/base/browser/ui/splitview/paneview.ts +++ b/src/vs/base/browser/ui/splitview/paneview.ts @@ -44,8 +44,8 @@ export abstract class Pane extends Disposable implements IView { private static readonly HEADER_SIZE = 22; readonly element: HTMLElement; - protected header!: HTMLElement; - protected body!: HTMLElement; + private header!: HTMLElement; + private body!: HTMLElement; protected _expanded: boolean; diff --git a/src/vs/editor/browser/services/bulkEditService.ts b/src/vs/editor/browser/services/bulkEditService.ts index 0aecb86edfaa6..d7d1ad7041790 100644 --- a/src/vs/editor/browser/services/bulkEditService.ts +++ b/src/vs/editor/browser/services/bulkEditService.ts @@ -15,6 +15,7 @@ export interface IBulkEditOptions { editor?: ICodeEditor; progress?: IProgress; showPreview?: boolean; + label?: string; } export interface IBulkEditResult { diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index 20ef921a6c123..9f3c10786f6c2 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -200,7 +200,8 @@ class RenameController implements IEditorContribution { this._bulkEditService.apply(renameResult, { editor: this.editor, - showPreview: inputFieldResult.wantsPreview + showPreview: inputFieldResult.wantsPreview, + label: nls.localize('label', "Renaming '{0}'", loc?.text) }).then(result => { if (result.ariaSummary) { alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", loc!.text, inputFieldResult.newName, result.ariaSummary)); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts index e87a536c82f1d..2e46f5687aa88 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts @@ -7,7 +7,7 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; +import { IBulkEditService, IBulkEditOptions } from 'vs/editor/browser/services/bulkEditService'; import { WorkspaceEdit } from 'vs/editor/common/modes'; import { BulkEditPane } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPane'; import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry } from 'vs/workbench/common/views'; @@ -46,11 +46,11 @@ class BulkEditPreviewContribution { @IBulkEditService bulkEditService: IBulkEditService, @IContextKeyService contextKeyService: IContextKeyService, ) { - bulkEditService.setPreviewHandler(edit => this._previewEdit(edit)); + bulkEditService.setPreviewHandler((edit, options) => this._previewEdit(edit, options)); this._ctxEnabled = BulkEditPreviewContribution.ctxEnabled.bindTo(contextKeyService); } - private async _previewEdit(edit: WorkspaceEdit) { + private async _previewEdit(edit: WorkspaceEdit, options?: IBulkEditOptions) { this._ctxEnabled.set(true); const oldActivePanel = this._panelService.getActivePanel(); @@ -60,7 +60,7 @@ class BulkEditPreviewContribution { return edit; } - const newEditOrUndefined = await view.setInput(edit); + const newEditOrUndefined = await view.setInput(edit, options?.label); if (!newEditOrUndefined) { return { edits: [] }; } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css index 3cd76dc760f4b..b83367eb5d16a 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css @@ -11,14 +11,14 @@ padding: 10px 20px } -.monaco-workbench .bulk-edit-panel[data-state="message"] .message, -.monaco-workbench .bulk-edit-panel[data-state="data"] .tree +.monaco-workbench .bulk-edit-panel [data-state="message"] .message, +.monaco-workbench .bulk-edit-panel [data-state="data"] .tree { display: inherit; } -.monaco-workbench .bulk-edit-panel[data-state="data"] .message, -.monaco-workbench .bulk-edit-panel[data-state="message"] .tree +.monaco-workbench .bulk-edit-panel [data-state="data"] .message, +.monaco-workbench .bulk-edit-panel [data-state="message"] .tree { display: none; } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index b9d1c6ca68b98..510aa68d0ac65 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -41,6 +41,7 @@ export class BulkEditPane extends ViewPane { private readonly _acceptAction = new Action('ok', localize('ok', "Apply Refactoring"), 'codicon-check', false, async () => this.accept()); private readonly _discardAction = new Action('discard', localize('discard', "Discard"), 'codicon-trash', false, async () => this.discard()); + private readonly _disposables = new DisposableStore(); private readonly _sessionDisposables = new DisposableStore(); @@ -62,6 +63,8 @@ export class BulkEditPane extends ViewPane { options, keybindingService, contextMenuService, configurationService, contextKeyService ); + + this.element.classList.add('bulk-edit-panel', 'show-file-icons'); } dispose(): void { @@ -70,7 +73,6 @@ export class BulkEditPane extends ViewPane { } protected renderBody(parent: HTMLElement): void { - parent.classList.add('bulk-edit-panel', 'show-file-icons'); const resourceLabels = this._instaService.createInstance( ResourceLabels, @@ -124,10 +126,10 @@ export class BulkEditPane extends ViewPane { } private _setState(state: State): void { - this.body.dataset['state'] = state; + this.element.dataset['state'] = state; } - async setInput(edit: WorkspaceEdit): Promise { + async setInput(edit: WorkspaceEdit, label?: string): Promise { this._setState(State.Data); this._sessionDisposables.clear(); From c5a432c6b64655990b67624c10f82f2d752a0504 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 10 Jan 2020 13:04:28 +0100 Subject: [PATCH 138/315] Fix #88293 --- src/vs/workbench/contrib/search/browser/replaceService.ts | 5 +++-- src/vs/workbench/contrib/search/common/searchModel.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/replaceService.ts b/src/vs/workbench/contrib/search/browser/replaceService.ts index bc7e14ab2e28f..4f96247686132 100644 --- a/src/vs/workbench/contrib/search/browser/replaceService.ts +++ b/src/vs/workbench/contrib/search/browser/replaceService.ts @@ -121,9 +121,10 @@ export class ReplaceService implements IReplaceService { revealIfVisible: true } }).then(editor => { + const input = editor?.input; const disposable = fileMatch.onDispose(() => { - if (editor && editor.input) { - editor.input.dispose(); + if (input) { + input.dispose(); } disposable.dispose(); }); diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index 1dbbad8b69086..7e0db5d1da43a 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -543,7 +543,7 @@ export class FolderMatch extends Disposable { replace(match: FileMatch): Promise { return this.replaceService.replace([match]).then(() => { - this.doRemove(match, false, true); + this.doRemove(match); }); } From 60f27207df1668f7c95d12d1abe4f051c8bba990 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 10 Jan 2020 13:57:35 +0100 Subject: [PATCH 139/315] #88293 remove matches after replace --- src/vs/workbench/contrib/search/common/searchModel.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index 7e0db5d1da43a..b0c40aa7ca666 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -549,9 +549,7 @@ export class FolderMatch extends Disposable { replaceAll(): Promise { const matches = this.matches(); - return this.replaceService.replace(matches).then(() => { - matches.forEach(match => this.doRemove(match, false, true)); - }); + return this.replaceService.replace(matches).then(() => this.doRemove(matches)); } matches(): FileMatch[] { From f82e3c0000250a9db08cc5db10a7ef859ba8eb4d Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 10 Jan 2020 15:13:59 +0100 Subject: [PATCH 140/315] don't apply code action when things have changed in the meantime --- src/vs/base/common/map.ts | 4 +- .../contrib/bulkEdit/browser/bulkEditPane.ts | 29 ++++- .../bulkEdit/browser/bulkEditPreview.ts | 12 +- .../bulkEdit/browser/bulkEditService.ts | 19 +-- .../services/bulkEdit/browser/conflicts.ts | 118 ++++++++++++++++++ 5 files changed, 156 insertions(+), 26 deletions(-) create mode 100644 src/vs/workbench/services/bulkEdit/browser/conflicts.ts diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 61d2436b04572..4f6b55c3fe547 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -454,8 +454,8 @@ export class ResourceMap { return this.map.delete(this.toKey(resource)); } - forEach(clb: (value: T) => void): void { - this.map.forEach(clb); + forEach(clb: (value: T, key: URI) => void): void { + this.map.forEach((value, index) => clb(value, URI.parse(index))); } values(): T[] { diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index 510aa68d0ac65..c042d0a3e7c52 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -26,6 +26,9 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { ResourceLabels, IResourceLabelsContainer } from 'vs/workbench/browser/labels'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import Severity from 'vs/base/common/severity'; +import { isFalsyOrEmpty } from 'vs/base/common/arrays'; const enum State { Data = 'data', @@ -54,6 +57,7 @@ export class BulkEditPane extends ViewPane { @IEditorService private readonly _editorService: IEditorService, @ILabelService private readonly _labelService: ILabelService, @ITextModelService private readonly _textModelService: ITextModelService, + @IDialogService private readonly _dialogService: IDialogService, @IKeybindingService keybindingService: IKeybindingService, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, @@ -140,8 +144,9 @@ export class BulkEditPane extends ViewPane { const input = await this._instaService.invokeFunction(BulkFileOperations.create, edit); const provider = this._instaService.createInstance(BulkEditPreviewProvider, input); - this._sessionDisposables.add(provider); + this._sessionDisposables.add(input); + this._currentInput = input; this._acceptAction.enabled = true; @@ -167,7 +172,21 @@ export class BulkEditPane extends ViewPane { } accept(): void { - this._done(true); + + const conflicts = this._currentInput?.conflicts.list(); + + if (isFalsyOrEmpty(conflicts)) { + this._done(true); + } + + let message: string; + if (conflicts!.length === 1) { + message = localize('conflict.1', "Cannot apply refactoring because '{0}' has changed in the meantime.", this._labelService.getUriLabel(conflicts![0], { relative: true })); + } else { + message = localize('conflict.N', "Cannot apply refactoring because {0} other files have changed in the meantime.", conflicts!.length); + } + + this._dialogService.show(Severity.Warning, message, []).finally(() => this._done(false)); } discard() { @@ -182,19 +201,19 @@ export class BulkEditPane extends ViewPane { } private _done(accept: boolean): void { - this._setState(State.Message); - this._sessionDisposables.clear(); if (this._currentResolve) { this._currentResolve(accept ? this._currentInput?.asWorkspaceEdit() : undefined); this._acceptAction.enabled = false; this._discardAction.enabled = false; + this._currentInput = undefined; } + this._setState(State.Message); + this._sessionDisposables.clear(); } private async _previewTextEditElement(element: TextEditElement): Promise { let leftResource: URI; - try { (await this._textModelService.createModelReference(element.parent.uri)).dispose(); leftResource = element.parent.uri; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts index 4f93c0b9ad1cb..0414f0d1a2702 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts @@ -17,6 +17,7 @@ import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiati import { IFileService } from 'vs/platform/files/common/files'; import { Emitter, Event } from 'vs/base/common/event'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; +import { ConflictDetector } from 'vs/workbench/services/bulkEdit/browser/conflicts'; class CheckedObject { @@ -88,10 +89,19 @@ export class BulkFileOperations { readonly fileOperations: BulkFileOperation[] = []; + readonly conflicts: ConflictDetector; + constructor( private readonly _bulkEdit: WorkspaceEdit, @IFileService private readonly _fileService: IFileService, - ) { } + @IInstantiationService instaService: IInstantiationService, + ) { + this.conflicts = instaService.createInstance(ConflictDetector, _bulkEdit); + } + + dispose(): void { + this.conflicts.dispose(); + } async _init() { const operationByResource = new Map(); diff --git a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts index 97140519b5da6..11136917170b2 100644 --- a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts @@ -25,25 +25,8 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; +import { Recording } from 'vs/workbench/services/bulkEdit/browser/conflicts'; -abstract class Recording { - - static start(fileService: IFileService): Recording { - - let _changes = new Set(); - let subscription = fileService.onAfterOperation(e => { - _changes.add(e.resource.toString()); - }); - - return { - stop() { return subscription.dispose(); }, - hasChanged(resource) { return _changes.has(resource.toString()); } - }; - } - - abstract stop(): void; - abstract hasChanged(resource: URI): boolean; -} type ValidationResult = { canApply: true } | { canApply: false, reason: URI }; diff --git a/src/vs/workbench/services/bulkEdit/browser/conflicts.ts b/src/vs/workbench/services/bulkEdit/browser/conflicts.ts new file mode 100644 index 0000000000000..6c9c2dc6c0aed --- /dev/null +++ b/src/vs/workbench/services/bulkEdit/browser/conflicts.ts @@ -0,0 +1,118 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IFileService } from 'vs/platform/files/common/files'; +import { URI } from 'vs/base/common/uri'; +import { WorkspaceEdit, isResourceTextEdit } from 'vs/editor/common/modes'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { ResourceMap } from 'vs/base/common/map'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Emitter, Event } from 'vs/base/common/event'; +import type { ITextModel } from 'vs/editor/common/model'; + +export abstract class Recording { + + static start(fileService: IFileService): Recording { + + let _changes = new Set(); + let subscription = fileService.onAfterOperation(e => { + _changes.add(e.resource.toString()); + }); + + return { + stop() { return subscription.dispose(); }, + hasChanged(resource) { return _changes.has(resource.toString()); } + }; + } + + abstract stop(): void; + abstract hasChanged(resource: URI): boolean; +} + +export class ConflictDetector { + + private readonly _conflicts = new ResourceMap(); + private readonly _changes = new ResourceMap(); + private readonly _disposables = new DisposableStore(); + + private readonly _onDidConflict = new Emitter(); + readonly onDidConflict: Event = this._onDidConflict.event; + + constructor( + workspaceEdit: WorkspaceEdit, + @IFileService fileService: IFileService, + @IModelService modelService: IModelService, + ) { + + const _workspaceEditResources = new ResourceMap(); + + for (let edit of workspaceEdit.edits) { + if (isResourceTextEdit(edit)) { + + _workspaceEditResources.set(edit.resource, true); + + if (typeof edit.modelVersionId === 'number') { + const model = modelService.getModel(edit.resource); + if (model && model.getVersionId() !== edit.modelVersionId) { + this._conflicts.set(edit.resource, true); + this._onDidConflict.fire(this); + } + } + + } else if (edit.newUri) { + _workspaceEditResources.set(edit.newUri, true); + + } else if (edit.oldUri) { + _workspaceEditResources.set(edit.oldUri, true); + } + } + + // listen to file changes + this._disposables.add(fileService.onFileChanges(e => { + for (let change of e.changes) { + + // change + this._changes.set(change.resource, true); + + // conflict + if (_workspaceEditResources.has(change.resource)) { + this._conflicts.set(change.resource, true); + this._onDidConflict.fire(this); + } + } + })); + + + // listen to model changes...? + const onDidChangeModel = (model: ITextModel) => { + // change + this._changes.set(model.uri, true); + + // conflict + if (_workspaceEditResources.has(model.uri)) { + this._conflicts.set(model.uri, true); + this._onDidConflict.fire(this); + } + }; + for (let model of modelService.getModels()) { + this._disposables.add(model.onDidChangeContent(() => onDidChangeModel(model))); + } + } + + dispose(): void { + this._disposables.dispose(); + this._onDidConflict.dispose(); + } + + list(): URI[] { + const result: URI[] = this._conflicts.keys(); + this._changes.forEach((_value, key) => { + if (!this._conflicts.has(key)) { + result.push(key); + } + }); + return result; + } +} From 3db53cb50daee101a0b3c7ece795ee415fd25ede Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 10 Jan 2020 15:14:53 +0100 Subject: [PATCH 141/315] use clear icon for discarding changes --- src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index c042d0a3e7c52..60c9879b90786 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -42,8 +42,8 @@ export class BulkEditPane extends ViewPane { private _tree!: WorkbenchAsyncDataTree; private _message!: HTMLSpanElement; - private readonly _acceptAction = new Action('ok', localize('ok', "Apply Refactoring"), 'codicon-check', false, async () => this.accept()); - private readonly _discardAction = new Action('discard', localize('discard', "Discard"), 'codicon-trash', false, async () => this.discard()); + private readonly _acceptAction = new Action('ok', localize('ok', "Apply Changes"), 'codicon-check', false, async () => this.accept()); + private readonly _discardAction = new Action('discard', localize('discard', "Discard Changes"), 'codicon-clear-all', false, async () => this.discard()); private readonly _disposables = new DisposableStore(); From db581614ab57d2a1eb8925c56b52f60bddcace1c Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 9 Jan 2020 15:25:57 +0100 Subject: [PATCH 142/315] Error while computing semantic tokens. Fixes #88366 --- .../server/src/modes/javascriptSemanticTokens.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts index fa615319354ef..7e4deafa262e7 100644 --- a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts +++ b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts @@ -20,7 +20,7 @@ export function getSemanticTokens(jsLanguageService: ts.LanguageService, current if (node.kind === ts.SyntaxKind.Identifier) { const symbol = typeChecker.getSymbolAtLocation(node); if (symbol) { - const decl = symbol.valueDeclaration || symbol.declarations[0]; + const decl = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0]; if (decl) { let typeIdx = tokenFromDeclarationMapping[decl.kind]; let modifierSet = 0; From 94ae236fe396ab819292cc00ceff4f3766d6c008 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 10 Jan 2020 15:19:54 +0100 Subject: [PATCH 143/315] Theme is not enabled after sync. Fixes #88364 --- .../themes/browser/workbenchThemeService.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index a914477d227ba..a3b675c68ccb0 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -192,6 +192,15 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration, tokenColorCustomizationConfiguration); + let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); + if (colorThemeSetting !== this.currentColorTheme.settingsId) { + const theme = await this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, undefined); + if (theme) { + this.setColorTheme(theme.id, undefined); + return; + } + } + if (this.currentColorTheme.isLoaded) { const themeData = await this.colorThemeStore.findThemeData(this.currentColorTheme.id); if (!themeData) { @@ -216,6 +225,15 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { iconThemeSettingSchema.enumDescriptions = [iconThemeSettingSchema.enumDescriptions![0], ...event.themes.map(t => t.description || '')]; configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration); + let iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); + if (iconThemeSetting !== this.currentIconTheme.settingsId) { + const theme = await this.iconThemeStore.findThemeBySettingsId(iconThemeSetting); + if (theme) { + this.setFileIconTheme(theme.id, undefined); + return; + } + } + if (this.currentIconTheme.isLoaded) { const theme = await this.iconThemeStore.findThemeData(this.currentIconTheme.id); if (!theme) { From cf0fd9a5e7aa19375dd7cc5b9606e1b7400ef91a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 10 Jan 2020 15:34:37 +0100 Subject: [PATCH 144/315] editors - save dirty working copy after delay (for #84672) --- .../browser/parts/editor/editorAutoSave.ts | 82 +++++++++---- .../files/test/browser/editorAutoSave.test.ts | 113 ++++++++++++++++++ .../textfile/common/textFileEditorModel.ts | 66 +--------- .../workingCopy/common/workingCopyService.ts | 14 ++- .../test/common/workingCopyService.test.ts | 5 + 5 files changed, 195 insertions(+), 85 deletions(-) create mode 100644 src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts diff --git a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts index cb0c402f835ce..c36cd20e41d8c 100644 --- a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts +++ b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts @@ -4,16 +4,22 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { Disposable, DisposableStore, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { IFilesConfigurationService, AutoSaveMode, IAutoSaveConfiguration } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { SaveReason, IEditorIdentifier, IEditorInput, GroupIdentifier } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; export class EditorAutoSave extends Disposable implements IWorkbenchContribution { + // Auto save: after delay + private autoSaveAfterDelay: number | undefined; + private readonly pendingAutoSavesAfterDelay = new Map(); + + // Auto save: focus change & window change private lastActiveEditor: IEditorInput | undefined = undefined; private lastActiveGroupId: GroupIdentifier | undefined = undefined; private lastActiveEditorControlDisposable = this._register(new DisposableStore()); @@ -22,17 +28,22 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, @IHostService private readonly hostService: IHostService, @IEditorService private readonly editorService: IEditorService, - @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService ) { super(); + // Figure out initial auto save config + this.onAutoSaveConfigurationChange(filesConfigurationService.getAutoSaveConfiguration(), false); + this.registerListeners(); } private registerListeners(): void { this._register(this.hostService.onDidChangeFocus(focused => this.onWindowFocusChange(focused))); this._register(this.editorService.onDidActiveEditorChange(() => this.onDidActiveEditorChange())); - this._register(this.filesConfigurationService.onAutoSaveConfigurationChange(() => this.onAutoSaveConfigurationChange())); + this._register(this.filesConfigurationService.onAutoSaveConfigurationChange(config => this.onAutoSaveConfigurationChange(config, true))); + this._register(this.workingCopyService.onDidChangeDirty(workingCopy => this.onDidWorkingCopyChangeDirty(workingCopy))); } private onWindowFocusChange(focused: boolean): void { @@ -85,24 +96,55 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution } } - private onAutoSaveConfigurationChange(): void { - let reason: SaveReason | undefined = undefined; - switch (this.filesConfigurationService.getAutoSaveMode()) { - case AutoSaveMode.ON_FOCUS_CHANGE: - reason = SaveReason.FOCUS_CHANGE; - break; - case AutoSaveMode.ON_WINDOW_CHANGE: - reason = SaveReason.WINDOW_CHANGE; - break; - case AutoSaveMode.AFTER_SHORT_DELAY: - case AutoSaveMode.AFTER_LONG_DELAY: - reason = SaveReason.AUTO; - break; - } + private onAutoSaveConfigurationChange(config: IAutoSaveConfiguration, fromEvent: boolean): void { + + // Update auto save after delay config + this.autoSaveAfterDelay = (typeof config.autoSaveDelay === 'number') && config.autoSaveDelay > 0 ? config.autoSaveDelay : undefined; // Trigger a save-all when auto save is enabled - if (reason) { - this.editorService.saveAll({ reason }); + if (fromEvent) { + let reason: SaveReason | undefined = undefined; + switch (this.filesConfigurationService.getAutoSaveMode()) { + case AutoSaveMode.ON_FOCUS_CHANGE: + reason = SaveReason.FOCUS_CHANGE; + break; + case AutoSaveMode.ON_WINDOW_CHANGE: + reason = SaveReason.WINDOW_CHANGE; + break; + case AutoSaveMode.AFTER_SHORT_DELAY: + case AutoSaveMode.AFTER_LONG_DELAY: + reason = SaveReason.AUTO; + break; + } + + if (reason) { + this.editorService.saveAll({ reason }); + } + } + } + + private onDidWorkingCopyChangeDirty(workingCopy: IWorkingCopy): void { + if (typeof this.autoSaveAfterDelay !== 'number') { + return; // auto save after delay must be enabled + } + + if (workingCopy.capabilities & WorkingCopyCapabilities.Untitled) { + return; // we never auto save untitled working copies + } + + // Clear any running auto save operation + dispose(this.pendingAutoSavesAfterDelay.get(workingCopy)); + this.pendingAutoSavesAfterDelay.delete(workingCopy); + + // Working copy got dirty - start auto save + if (workingCopy.isDirty()) { + const handle = setTimeout(() => { + if (workingCopy.isDirty()) { + workingCopy.save({ reason: SaveReason.AUTO }); + } + }, this.autoSaveAfterDelay); + + this.pendingAutoSavesAfterDelay.set(workingCopy, toDisposable(() => clearTimeout(handle))); } } } diff --git a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts new file mode 100644 index 0000000000000..68ba4c0060ddc --- /dev/null +++ b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts @@ -0,0 +1,113 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { Event } from 'vs/base/common/event'; +import { toResource } from 'vs/base/test/common/utils'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { workbenchInstantiationService, TestTextFileService, TestFileService, TestFilesConfigurationService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices'; +import { ITextFileService, IResolvedTextFileEditorModel, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { EditorInput } from 'vs/workbench/common/editor'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; +import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; +import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import { EditorAutoSave } from 'vs/workbench/browser/parts/editor/editorAutoSave'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; + +class ServiceAccessor { + constructor( + @IEditorService public editorService: IEditorService, + @IEditorGroupsService public editorGroupService: IEditorGroupsService, + @ITextFileService public textFileService: TestTextFileService, + @IFileService public fileService: TestFileService, + @IUntitledTextEditorService public untitledTextEditorService: IUntitledTextEditorService, + @IConfigurationService public configurationService: TestConfigurationService + ) { + } +} + +suite('EditorAutoSave', () => { + + let disposables: IDisposable[] = []; + + setup(() => { + disposables.push(Registry.as(EditorExtensions.Editors).registerEditor( + EditorDescriptor.create( + TextFileEditor, + TextFileEditor.ID, + 'Text File Editor' + ), + [new SyncDescriptor(FileEditorInput)] + )); + }); + + teardown(() => { + dispose(disposables); + disposables = []; + }); + + test('editor auto saves after short delay if configured', async function () { + const instantiationService = workbenchInstantiationService(); + + const configurationService = new TestConfigurationService(); + configurationService.setUserConfiguration('files', { autoSave: 'afterDelay', autoSaveDelay: 1 }); + instantiationService.stub(IConfigurationService, configurationService); + + instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService( + instantiationService.createInstance(MockContextKeyService), + configurationService, + TestEnvironmentService + )); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + instantiationService.stub(IEditorGroupsService, part); + + const editorService: EditorService = instantiationService.createInstance(EditorService); + instantiationService.stub(IEditorService, editorService); + + const accessor = instantiationService.createInstance(ServiceAccessor); + + const editorAutoSave = instantiationService.createInstance(EditorAutoSave); + + const resource = toResource.call(this, '/path/index.txt'); + + const model = await accessor.textFileService.models.loadOrCreate(resource) as IResolvedTextFileEditorModel; + + model.textEditorModel.setValue('Super Good'); + + assert.ok(model.isDirty()); + + await awaitModelSaved(model); + + assert.ok(!model.isDirty()); + + part.dispose(); + editorAutoSave.dispose(); + (accessor.textFileService.models).clear(); + (accessor.textFileService.models).dispose(); + }); + + function awaitModelSaved(model: ITextFileEditorModel): Promise { + return new Promise(c => { + Event.once(model.onDidChangeDirty)(c); + }); + } +}); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index a8458d9f09fe2..28149c0102591 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -24,13 +24,12 @@ import { RunOnceScheduler, timeout } from 'vs/base/common/async'; import { ITextBufferFactory } from 'vs/editor/common/model'; import { hash } from 'vs/base/common/hash'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { isEqual, isEqualOrParent, extname, basename, joinPath } from 'vs/base/common/resources'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Schemas } from 'vs/base/common/network'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { IFilesConfigurationService, IAutoSaveConfiguration } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; export interface IBackupMetaData { mtime: number; @@ -92,10 +91,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private lastResolvedFileStat: IFileStatWithMetadata | undefined; - private autoSaveAfterMillies: number | undefined; - private autoSaveAfterMilliesEnabled: boolean | undefined; - private readonly autoSaveDisposable = this._register(new MutableDisposable()); - private readonly saveSequentializer = new SaveSequentializer(); private lastSaveAttemptTime = 0; @@ -128,8 +123,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil ) { super(modelService, modeService); - this.updateAutoSaveConfiguration(filesConfigurationService.getAutoSaveConfiguration()); - // Make known to working copy service this._register(this.workingCopyService.registerWorkingCopy(this)); @@ -138,7 +131,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private registerListeners(): void { this._register(this.fileService.onFileChanges(e => this.onFileChanges(e))); - this._register(this.filesConfigurationService.onAutoSaveConfigurationChange(config => this.updateAutoSaveConfiguration(config))); this._register(this.filesConfigurationService.onFilesAssociationChange(e => this.onFilesAssociationChange())); this._register(this.onDidStateChange(e => this.onStateChange(e))); } @@ -206,13 +198,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } - private updateAutoSaveConfiguration(config: IAutoSaveConfiguration): void { - const autoSaveAfterMilliesEnabled = (typeof config.autoSaveDelay === 'number') && config.autoSaveDelay > 0; - - this.autoSaveAfterMilliesEnabled = autoSaveAfterMilliesEnabled; - this.autoSaveAfterMillies = autoSaveAfterMilliesEnabled ? config.autoSaveDelay : undefined; - } - private onFilesAssociationChange(): void { if (!this.isResolved()) { return; @@ -258,9 +243,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return false; } - // Cancel any running auto-save - this.autoSaveDisposable.clear(); - // Unset flags const wasDirty = this.dirty; const undo = this.setDirty(false); @@ -474,13 +456,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.createTextEditorModel(value, resource, this.preferredMode); // We restored a backup so we have to set the model as being dirty - // We also want to trigger auto save if it is enabled to simulate the exact same behaviour - // you would get if manually making the model dirty (fixes https://github.com/Microsoft/vscode/issues/16977) if (fromBackup) { this.doMakeDirty(); - if (this.autoSaveAfterMilliesEnabled) { - this.doAutoSave(this.versionId); - } } // Ensure we are not tracking a stale state @@ -536,9 +513,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // The contents changed as a matter of Undo and the version reached matches the saved one // In this case we clear the dirty flag and emit a SAVED event to indicate this state. - // Note: we currently only do this check when auto-save is turned off because there you see - // a dirty indicator that you want to get rid of when undoing to the saved version. - if (!this.autoSaveAfterMilliesEnabled && this.isResolved() && this.textEditorModel.getAlternativeVersionId() === this.bufferSavedVersionId) { + if (this.isResolved() && this.textEditorModel.getAlternativeVersionId() === this.bufferSavedVersionId) { this.logService.trace('onModelContentChanged() - model content changed back to last saved version', this.resource); // Clear flags @@ -559,15 +534,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Mark as dirty this.doMakeDirty(); - // Start auto save process unless we are in conflict resolution mode and unless it is disabled - if (this.autoSaveAfterMilliesEnabled) { - if (!this.inConflictMode) { - this.doAutoSave(this.versionId); - } else { - this.logService.trace('makeDirty() - prevented save because we are in conflict resolution mode', this.resource); - } - } - // Handle content change events this.contentChangeEventScheduler.schedule(); } @@ -593,27 +559,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } - private doAutoSave(versionId: number): void { - this.logService.trace(`doAutoSave() - enter for versionId ${versionId}`, this.resource); - - // Cancel any currently running auto saves to make this the one that succeeds - this.autoSaveDisposable.clear(); - - // Create new save timer and store it for disposal as needed - const handle = setTimeout(() => { - - // Clear the timeout now that we are running - this.autoSaveDisposable.clear(); - - // Only trigger save if the version id has not changed meanwhile - if (versionId === this.versionId) { - this.doSave(versionId, { reason: SaveReason.AUTO }); // Very important here to not return the promise because if the timeout promise is canceled it will bubble up the error otherwise - do not change - } - }, this.autoSaveAfterMillies); - - this.autoSaveDisposable.value = toDisposable(() => clearTimeout(handle)); - } - async save(options: ITextFileSaveOptions = Object.create(null)): Promise { if (!this.isResolved()) { return false; @@ -621,9 +566,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.logService.trace('save() - enter', this.resource); - // Cancel any currently running auto saves to make this the one that succeeds - this.autoSaveDisposable.clear(); - await this.doSave(this.versionId, options); return true; @@ -676,8 +618,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } // Push all edit operations to the undo stack so that the user has a chance to - // Ctrl+Z back to the saved version. We only do this when auto-save is turned off - if (!this.autoSaveAfterMilliesEnabled && this.isResolved()) { + // Ctrl+Z back to the saved version. + if (this.isResolved()) { this.textEditorModel.pushStackElement(); } diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts index 55d754bb392bd..d7a2b403a5f98 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts @@ -9,19 +9,25 @@ import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { Disposable, IDisposable, toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { TernarySearchTree } from 'vs/base/common/map'; +import { ISaveOptions } from 'vs/workbench/common/editor'; export const enum WorkingCopyCapabilities { /** * Signals that the working copy requires * additional input when saving, e.g. an - * associated path to save to. + * associated path to save to. */ Untitled = 1 << 1 } export interface IWorkingCopy { + readonly resource: URI; + + readonly capabilities: WorkingCopyCapabilities; + + //#region Dirty Tracking readonly onDidChangeDirty: Event; @@ -31,9 +37,11 @@ export interface IWorkingCopy { //#endregion - readonly resource: URI; + //#region Save - readonly capabilities: WorkingCopyCapabilities; + save(options?: ISaveOptions): Promise; + + //#endregion } export const IWorkingCopyService = createDecorator('workingCopyService'); diff --git a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts index a7797845a1755..4bac643a90d18 100644 --- a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts @@ -9,6 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { TestWorkingCopyService } from 'vs/workbench/test/workbenchTestServices'; +import type { ISaveOptions } from 'vs/workbench/common/editor'; suite('WorkingCopyService', () => { @@ -41,6 +42,10 @@ suite('WorkingCopyService', () => { return this.dirty; } + async save(options?: ISaveOptions): Promise { + return true; + } + dispose(): void { this._onDispose.fire(); From 4032ce7241853f261fa79b1f7c2acd6d689b6099 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 10 Jan 2020 15:37:37 +0100 Subject: [PATCH 145/315] polish --- .../src/features/semanticTokens.ts | 146 +++++++++--------- 1 file changed, 70 insertions(+), 76 deletions(-) diff --git a/extensions/typescript-language-features/src/features/semanticTokens.ts b/extensions/typescript-language-features/src/features/semanticTokens.ts index cc9a8ba582aae..1762360568785 100644 --- a/extensions/typescript-language-features/src/features/semanticTokens.ts +++ b/extensions/typescript-language-features/src/features/semanticTokens.ts @@ -7,35 +7,19 @@ import * as vscode from 'vscode'; import { ITypeScriptServiceClient, ExecConfig, ServerResponse } from '../typescriptService'; import * as Proto from '../protocol'; -enum TokenType { - 'class', - 'enum', - 'interface', - 'namespace', - 'typeParameter', - 'type', - 'parameter', - 'variable', - 'property', - 'constant', - 'function', - 'member', - _sentinel -} - +export function register(selector: vscode.DocumentSelector, client: ITypeScriptServiceClient) { + const provider = new SemanticTokensProvider(client); + return vscode.languages.registerSemanticTokensProvider(selector, provider, provider.getLegend()); -enum TokenModifier { - 'declaration', - 'static', - 'async', - _sentinel } +/* + * Prototype of a SemanticTokensProvider, relying on the experimental `encodedSemanticClassifications-full` request from the TypeScript server. + * As the results retured by the TypeScript server are limited, we also add a Typescript plugin (typescript-vscode-sh-plugin) to enrich the returned token. + */ class SemanticTokensProvider implements vscode.SemanticTokensProvider { - constructor( - private readonly client: ITypeScriptServiceClient - ) { + constructor(private readonly client: ITypeScriptServiceClient) { } getLegend(): vscode.SemanticTokensLegend { @@ -58,79 +42,91 @@ class SemanticTokensProvider implements vscode.SemanticTokensProvider { const versionBeforeRequest = document.version; - if (_options.ranges) { + const allTokenSpans: number[][] = []; - // const allArgs = _options.ranges.map(r => ({file, start: document.offsetAt(r.start), length: document.offsetAt(r.end) - document.offsetAt(r.start)})); + let requestArgs: ExperimentalProtocol.EncodedSemanticClassificationsRequestArgs[] = []; + if (_options.ranges) { + requestArgs = _options.ranges.map(r => { const start = document.offsetAt(r.start); const length = document.offsetAt(r.end) - start; return { file, start, length }; }); + requestArgs = requestArgs.sort((a1, a2) => a1.start - a2.start); + } else { + requestArgs = [{ file, start: 0, length: document.getText().length }]; // full file + } + for (const requestArg of requestArgs) { + const response = await (this.client as ExperimentalProtocol.IExtendedTypeScriptServiceClient).execute('encodedSemanticClassifications-full', requestArg, token); + if (response.type === 'response' && response.body) { + allTokenSpans.push(response.body.spans); + } else { + return null; + } } - const args: ExperimentalProtocol.EncodedSemanticClassificationsRequestArgs = { - file: file, - start: 0, - length: document.getText().length, - }; - - const response = await (this.client as ExperimentalProtocol.IExtendedTypeScriptServiceClient).execute('encodedSemanticClassifications-full', args, token); const versionAfterRequest = document.version; - if (versionBeforeRequest !== versionAfterRequest) { // A new request will come in soon... return null; } - if (response.type !== 'response') { - return null; - } - if (!response.body) { - return null; - } - const builder = new vscode.SemanticTokensBuilder(); - - const tsTokens = response.body.spans; - for (let i = 0, len = Math.floor(tsTokens.length / 3); i < len; i++) { - - const tsClassification = tsTokens[3 * i + 2]; - let tokenType = 0; - let tokenModifiers = 0; - if (tsClassification > 0xFF) { - // classifications as returned by the typescript-vscode-sh-plugin - tokenType = (tsClassification >> 8) - 1; - tokenModifiers = tsClassification & 0xFF; - } else { - tokenType = tokenTypeMap[tsClassification]; - if (tokenType === undefined) { - continue; + for (const tokenSpan of allTokenSpans) { + for (let i = 0, len = Math.floor(tokenSpan.length / 3); i < len; i++) { + + const tsClassification = tokenSpan[3 * i + 2]; + let tokenType = 0; + let tokenModifiers = 0; + if (tsClassification >= 0x100) { + // exendend classifications as returned by the typescript-vscode-sh-plugin + tokenType = (tsClassification >> 8) - 1; + tokenModifiers = tsClassification & 0xFF; + } else { + tokenType = tokenTypeMap[tsClassification]; + if (tokenType === undefined) { + continue; + } } - } - const offset = tsTokens[3 * i]; - const length = tsTokens[3 * i + 1]; + const offset = tokenSpan[3 * i]; + const length = tokenSpan[3 * i + 1]; - // we can use the document's range conversion methods because - // the result is at the same version as the document - const startPos = document.positionAt(offset); - const endPos = document.positionAt(offset + length); + // we can use the document's range conversion methods because the result is at the same version as the document + const startPos = document.positionAt(offset); + const endPos = document.positionAt(offset + length); - for (let line = startPos.line; line <= endPos.line; line++) { - const startCharacter = (line === startPos.line ? startPos.character : 0); - const endCharacter = (line === endPos.line ? endPos.character : document.lineAt(line).text.length); - builder.push(line, startCharacter, endCharacter - startCharacter, tokenType, tokenModifiers); + for (let line = startPos.line; line <= endPos.line; line++) { + const startCharacter = (line === startPos.line ? startPos.character : 0); + const endCharacter = (line === endPos.line ? endPos.character : document.lineAt(line).text.length); + builder.push(line, startCharacter, endCharacter - startCharacter, tokenType, tokenModifiers); + } } } - return new vscode.SemanticTokens(builder.build()); } +} +enum TokenType { + 'class', + 'enum', + 'interface', + 'namespace', + 'typeParameter', + 'type', + 'parameter', + 'variable', + 'property', + 'constant', + 'function', + 'member', + _sentinel } -export function register( - selector: vscode.DocumentSelector, - client: ITypeScriptServiceClient -) { - const provider = new SemanticTokensProvider(client); - return vscode.languages.registerSemanticTokensProvider(selector, provider, provider.getLegend()); +enum TokenModifier { + 'declaration', + 'static', + 'async', + _sentinel } +// mapping for the original ExperimentalProtocol.ClassificationType from TypeScript (only used when plugin is not available) + const tokenTypeMap: number[] = []; tokenTypeMap[ExperimentalProtocol.ClassificationType.className] = TokenType.class; tokenTypeMap[ExperimentalProtocol.ClassificationType.enumName] = TokenType.enum; @@ -221,5 +217,3 @@ export namespace ExperimentalProtocol { 'encodedSemanticClassifications-full': [ExperimentalProtocol.EncodedSemanticClassificationsRequestArgs, ExperimentalProtocol.EncodedSemanticClassificationsResponse]; } } - - From f1f652ccc191d40f06a84a4b468b9cf09f9406c6 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 10 Jan 2020 15:39:42 +0100 Subject: [PATCH 146/315] polish html-js sem highlighting --- .../src/modes/javascriptSemanticTokens.ts | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts index 7e4deafa262e7..9a1f394977a9a 100644 --- a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts +++ b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts @@ -55,35 +55,40 @@ export function getSemanticTokens(jsLanguageService: ts.LanguageService, current return resultTokens; } - -export function getSemanticTokenLegend() { - return { types: tokenTypes, modifiers: tokenModifiers }; +enum TokenType { + 'class', + 'enum', + 'interface', + 'namespace', + 'typeParameter', + 'type', + 'parameter', + 'variable', + 'property', + 'constant', + 'function', + 'member', + _sentinel } -const tokenTypes: string[] = ['class', 'enum', 'interface', 'namespace', 'typeParameter', 'type', 'parameter', 'variable', 'property', 'constant', 'function', 'member']; -const tokenModifiers: string[] = ['declaration', 'static', 'async']; - -const enum TokenType { - 'class' = 0, - 'enum' = 1, - 'interface' = 2, - 'namespace' = 3, - 'typeParameter' = 4, - 'type' = 5, - 'parameter' = 6, - 'variable' = 7, - 'property' = 8, - 'constant' = 9, - 'function' = 10, - 'member' = 11 +enum TokenModifier { + 'declaration', + 'static', + 'async', + _sentinel } - -const enum TokenModifier { - 'declaration' = 0x01, - 'static' = 0x02, - 'async' = 0x04, +export function getSemanticTokenLegend() { + const tokenTypes = []; + for (let i = 0; i < TokenType._sentinel; i++) { + tokenTypes.push(TokenType[i]); + } + const tokenModifiers = []; + for (let i = 0; i < TokenModifier._sentinel; i++) { + tokenModifiers.push(TokenModifier[i]); + } + return { types: tokenTypes, modifiers: tokenModifiers }; } const tokenFromDeclarationMapping: { [name: string]: TokenType } = { From 18ee991ed5961fc3c80c415ac7e6923323e381f2 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 10 Jan 2020 15:45:31 +0100 Subject: [PATCH 147/315] fix issue with change check --- .../workbench/contrib/bulkEdit/browser/bulkEditPane.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index 60c9879b90786..ee46ddb38ef10 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -28,7 +28,6 @@ import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewl import { ResourceLabels, IResourceLabelsContainer } from 'vs/workbench/browser/labels'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; -import { isFalsyOrEmpty } from 'vs/base/common/arrays'; const enum State { Data = 'data', @@ -175,15 +174,16 @@ export class BulkEditPane extends ViewPane { const conflicts = this._currentInput?.conflicts.list(); - if (isFalsyOrEmpty(conflicts)) { + if (!conflicts || conflicts.length === 0) { this._done(true); + return; } let message: string; - if (conflicts!.length === 1) { - message = localize('conflict.1', "Cannot apply refactoring because '{0}' has changed in the meantime.", this._labelService.getUriLabel(conflicts![0], { relative: true })); + if (conflicts.length === 1) { + message = localize('conflict.1', "Cannot apply refactoring because '{0}' has changed in the meantime.", this._labelService.getUriLabel(conflicts[0], { relative: true })); } else { - message = localize('conflict.N', "Cannot apply refactoring because {0} other files have changed in the meantime.", conflicts!.length); + message = localize('conflict.N', "Cannot apply refactoring because {0} other files have changed in the meantime.", conflicts.length); } this._dialogService.show(Severity.Warning, message, []).finally(() => this._done(false)); From 0ab6605afd1a9a6ca87d2d9bfca349b3f9ef6a8f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 10 Jan 2020 15:47:08 +0100 Subject: [PATCH 148/315] text files - tweak logging --- .../browser/parts/editor/editorAutoSave.ts | 10 ++++- .../textfile/common/textFileEditorModel.ts | 40 +++++++++---------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts index c36cd20e41d8c..3a434d37c5de4 100644 --- a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts +++ b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts @@ -12,6 +12,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { ILogService } from 'vs/platform/log/common/log'; export class EditorAutoSave extends Disposable implements IWorkbenchContribution { @@ -29,7 +30,8 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution @IHostService private readonly hostService: IHostService, @IEditorService private readonly editorService: IEditorService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, - @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, + @ILogService private readonly logService: ILogService ) { super(); @@ -88,6 +90,8 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution (reason === SaveReason.WINDOW_CHANGE && (mode === AutoSaveMode.ON_FOCUS_CHANGE || mode === AutoSaveMode.ON_WINDOW_CHANGE)) || (reason === SaveReason.FOCUS_CHANGE && mode === AutoSaveMode.ON_FOCUS_CHANGE) ) { + this.logService.trace(`[editor auto save] triggering auto save with reason ${reason}`); + if (editorIdentifier) { this.editorService.save(editorIdentifier, { reason }); } else { @@ -138,6 +142,8 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution // Working copy got dirty - start auto save if (workingCopy.isDirty()) { + this.logService.trace(`[editor auto save] starting auto save after ${this.autoSaveAfterDelay}ms`, workingCopy.resource.toString()); + const handle = setTimeout(() => { if (workingCopy.isDirty()) { workingCopy.save({ reason: SaveReason.AUTO }); @@ -145,6 +151,8 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution }, this.autoSaveAfterDelay); this.pendingAutoSavesAfterDelay.set(workingCopy, toDisposable(() => clearTimeout(handle))); + } else { + this.logService.trace(`[editor auto save] clearing auto save`, workingCopy.resource.toString()); } } } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 28149c0102591..afd56f642e9ff 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -273,13 +273,13 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } async load(options?: ILoadOptions): Promise { - this.logService.trace('load() - enter', this.resource); + this.logService.trace('[text file model] load() - enter', this.resource.toString()); // It is very important to not reload the model when the model is dirty. // We also only want to reload the model from the disk if no save is pending // to avoid data loss. if (this.dirty || this.saveSequentializer.hasPendingSave()) { - this.logService.trace('load() - exit - without loading because model is dirty or being saved', this.resource); + this.logService.trace('[text file model] load() - exit - without loading because model is dirty or being saved', this.resource.toString()); return this; } @@ -296,7 +296,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil try { return await this.loadFromBackup(backup, options); } catch (error) { - this.logService.error(error); // ignore error and continue to load as file below + this.logService.error('[text file model] load()', error); // ignore error and continue to load as file below } } } @@ -396,7 +396,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } private loadFromContent(content: ITextFileStreamContent, options?: ILoadOptions, fromBackup?: boolean): TextFileEditorModel { - this.logService.trace('load() - resolved content', this.resource); + this.logService.trace('[text file model] load() - resolved content', this.resource.toString()); // Update our resolved disk stat model this.updateLastResolvedFileStat({ @@ -450,7 +450,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } private doCreateTextModel(resource: URI, value: ITextBufferFactory, fromBackup: boolean): void { - this.logService.trace('load() - created text editor model', this.resource); + this.logService.trace('[text file model] load() - created text editor model', this.resource.toString()); // Create model this.createTextEditorModel(value, resource, this.preferredMode); @@ -470,7 +470,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } private doUpdateTextModel(value: ITextBufferFactory): void { - this.logService.trace('load() - updated text editor model', this.resource); + this.logService.trace('[text file model] load() - updated text editor model', this.resource.toString()); // Ensure we are not tracking a stale state this.setDirty(false); @@ -500,11 +500,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } private onModelContentChanged(): void { - this.logService.trace(`onModelContentChanged() - enter`, this.resource); + this.logService.trace(`[text file model] onModelContentChanged() - enter`, this.resource.toString()); // In any case increment the version id because it tracks the textual content state of the model at all times this.versionId++; - this.logService.trace(`onModelContentChanged() - new versionId ${this.versionId}`, this.resource); + this.logService.trace(`[text file model] onModelContentChanged() - new versionId ${this.versionId}`, this.resource.toString()); // Ignore if blocking model changes if (this.blockModelContentChange) { @@ -514,7 +514,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // The contents changed as a matter of Undo and the version reached matches the saved one // In this case we clear the dirty flag and emit a SAVED event to indicate this state. if (this.isResolved() && this.textEditorModel.getAlternativeVersionId() === this.bufferSavedVersionId) { - this.logService.trace('onModelContentChanged() - model content changed back to last saved version', this.resource); + this.logService.trace('[text file model] onModelContentChanged() - model content changed back to last saved version', this.resource.toString()); // Clear flags const wasDirty = this.dirty; @@ -529,7 +529,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return; } - this.logService.trace('onModelContentChanged() - model content changed and marked as dirty', this.resource); + this.logService.trace('[text file model] onModelContentChanged() - model content changed and marked as dirty', this.resource.toString()); // Mark as dirty this.doMakeDirty(); @@ -564,7 +564,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return false; } - this.logService.trace('save() - enter', this.resource); + this.logService.trace('[text file model] save() - enter', this.resource.toString()); await this.doSave(this.versionId, options); @@ -576,7 +576,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil options.reason = SaveReason.EXPLICIT; } - this.logService.trace(`doSave(${versionId}) - enter with versionId ' + versionId`, this.resource); + this.logService.trace(`[text file model] doSave(${versionId}) - enter with versionId ' + versionId`, this.resource.toString()); // Lookup any running pending save for this versionId and return it if found // @@ -584,7 +584,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // while the save was not yet finished to disk // if (this.saveSequentializer.hasPendingSave(versionId)) { - this.logService.trace(`doSave(${versionId}) - exit - found a pending save for versionId ${versionId}`, this.resource); + this.logService.trace(`[text file model] doSave(${versionId}) - exit - found a pending save for versionId ${versionId}`, this.resource.toString()); return this.saveSequentializer.pendingSave || Promise.resolve(); } @@ -597,7 +597,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Thus we avoid spawning multiple auto saves and only take the latest. // if ((!options.force && !this.dirty) || versionId !== this.versionId) { - this.logService.trace(`doSave(${versionId}) - exit - because not dirty and/or versionId is different (this.isDirty: ${this.dirty}, this.versionId: ${this.versionId})`, this.resource); + this.logService.trace(`[text file model] doSave(${versionId}) - exit - because not dirty and/or versionId is different (this.isDirty: ${this.dirty}, this.versionId: ${this.versionId})`, this.resource.toString()); return Promise.resolve(); } @@ -611,7 +611,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // while the first save has not returned yet. // if (this.saveSequentializer.hasPendingSave()) { - this.logService.trace(`doSave(${versionId}) - exit - because busy saving`, this.resource); + this.logService.trace(`[text file model] doSave(${versionId}) - exit - because busy saving`, this.resource.toString()); // Register this as the next upcoming save and return return this.saveSequentializer.setNext(() => this.doSave(this.versionId /* make sure to use latest version id here */, options)); @@ -679,7 +679,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Save to Disk // mark the save operation as currently pending with the versionId (it might have changed from a save participant triggering) - this.logService.trace(`doSave(${versionId}) - before write()`, this.resource); + this.logService.trace(`[text file model] doSave(${versionId}) - before write()`, this.resource.toString()); const lastResolvedFileStat = assertIsDefined(this.lastResolvedFileStat); return this.saveSequentializer.setPending(newVersionId, this.textFileService.write(lastResolvedFileStat.resource, this.createSnapshot(), { overwriteReadonly: options.overwriteReadonly, @@ -689,14 +689,14 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil etag: (options.ignoreModifiedSince || !this.filesConfigurationService.preventSaveConflicts(lastResolvedFileStat.resource)) ? ETAG_DISABLED : lastResolvedFileStat.etag, writeElevated: options.writeElevated }).then(stat => { - this.logService.trace(`doSave(${versionId}) - after write()`, this.resource); + this.logService.trace(`[text file model] doSave(${versionId}) - after write()`, this.resource.toString()); // Update dirty state unless model has changed meanwhile if (versionId === this.versionId) { - this.logService.trace(`doSave(${versionId}) - setting dirty to false because versionId did not change`, this.resource); + this.logService.trace(`[text file model] doSave(${versionId}) - setting dirty to false because versionId did not change`, this.resource.toString()); this.setDirty(false); } else { - this.logService.trace(`doSave(${versionId}) - not setting dirty to false because versionId did change meanwhile`, this.resource); + this.logService.trace(`[text file model] doSave(${versionId}) - not setting dirty to false because versionId did change meanwhile`, this.resource.toString()); } // Updated resolved stat with updated stat @@ -721,7 +721,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.telemetryService.publicLog2('filePUT', this.getTelemetryData(options.reason)); } }, error => { - this.logService.error(`doSave(${versionId}) - exit - resulted in a save error: ${error.toString()}`, this.resource); + this.logService.error(`[text file model] doSave(${versionId}) - exit - resulted in a save error: ${error.toString()}`, this.resource.toString()); // Flag as error state in the model this.inErrorMode = true; From 66e7c7a159b1f2dc13457b4af771c2bcc48dcb1d Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 10 Jan 2020 16:00:23 +0100 Subject: [PATCH 149/315] react to feedback --- src/vs/editor/browser/editorBrowser.ts | 5 +++-- src/vs/editor/browser/widget/codeEditorWidget.ts | 2 +- src/vs/editor/contrib/suggest/suggestWidget.ts | 4 ++-- src/vs/monaco.d.ts | 7 ------- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 4906fa6196ae2..cbbe9d1701449 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -321,9 +321,10 @@ export interface IOverviewRuler { /** * Editor aria options. + * @internal */ export interface IEditorAriaOptions { - activeDescendent: string | undefined; + activeDescendant: string | undefined; } /** @@ -700,7 +701,7 @@ export interface ICodeEditor extends editorCommon.IEditor { * Sets the editor aria options, primarily the active descendent. * @internal */ - setAria(options: IEditorAriaOptions): void; + setAriaOptions(options: IEditorAriaOptions): void; /** * @internal diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index d0257074276c9..4409ad38d284e 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -1311,7 +1311,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._modelData.view.render(true, forceRedraw); } - public setAria(options: editorBrowser.IEditorAriaOptions): void { + public setAriaOptions(options: editorBrowser.IEditorAriaOptions): void { if (!this._modelData || !this._modelData.hasRealView) { return; } diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 8a0b22cdb527b..f608af0238e8c 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -650,7 +650,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate Date: Fri, 10 Jan 2020 16:01:45 +0100 Subject: [PATCH 150/315] missed spelling --- src/vs/editor/browser/controller/textAreaHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index eff63581e434c..604fee296f2da 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -426,10 +426,10 @@ export class TextAreaHandler extends ViewPart { } public setAria(options: IEditorAriaOptions): void { - if (options.activeDescendent) { + if (options.activeDescendant) { this.textArea.setAttribute('aria-haspopup', 'true'); this.textArea.setAttribute('aria-autocomplete', 'list'); - this.textArea.setAttribute('aria-activedescendant', options.activeDescendent); + this.textArea.setAttribute('aria-activedescendant', options.activeDescendant); } else { this.textArea.setAttribute('aria-haspopup', 'false'); this.textArea.setAttribute('aria-autocomplete', 'both'); From 2470304e6d6aa81879a61ad5debb365ee68ce663 Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 10 Jan 2020 16:03:51 +0100 Subject: [PATCH 151/315] set aria options --- src/vs/editor/browser/controller/textAreaHandler.ts | 2 +- src/vs/editor/browser/view/viewImpl.ts | 4 ++-- src/vs/editor/browser/widget/codeEditorWidget.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 604fee296f2da..6ce7362aacc6f 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -425,7 +425,7 @@ export class TextAreaHandler extends ViewPart { return this._lastRenderPosition; } - public setAria(options: IEditorAriaOptions): void { + public setAriaOptions(options: IEditorAriaOptions): void { if (options.activeDescendant) { this.textArea.setAttribute('aria-haspopup', 'true'); this.textArea.setAttribute('aria-autocomplete', 'list'); diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 908dce7abb519..1af4947456b33 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -510,8 +510,8 @@ export class View extends ViewEventHandler { this._textAreaHandler.refreshFocusState(); } - public setAria(options: IEditorAriaOptions): void { - this._textAreaHandler.setAria(options); + public setAriaOptions(options: IEditorAriaOptions): void { + this._textAreaHandler.setAriaOptions(options); } public addContentWidget(widgetData: IContentWidgetData): void { diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 4409ad38d284e..9a9e71bf65ff6 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -1315,7 +1315,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE if (!this._modelData || !this._modelData.hasRealView) { return; } - this._modelData.view.setAria(options); + this._modelData.view.setAriaOptions(options); } public applyFontInfo(target: HTMLElement): void { From 703a2afcef92bcfb8596e136c56307675c9dedb0 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 10 Jan 2020 16:05:16 +0100 Subject: [PATCH 152/315] update typescript-vscode-sh-plugin --- extensions/typescript-language-features/package.json | 4 ++-- extensions/typescript-language-features/yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index df72fe3d01ec4..c63e6a90901b9 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -19,9 +19,9 @@ "jsonc-parser": "^2.1.1", "rimraf": "^2.6.3", "semver": "5.5.1", + "typescript-vscode-sh-plugin": "^0.3.0", "vscode-extension-telemetry": "0.1.1", - "vscode-nls": "^4.0.0", - "typescript-vscode-sh-plugin": "0.2.0" + "vscode-nls": "^4.0.0" }, "devDependencies": { "@types/node": "^12.11.7", diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index 6f2da650dd8f2..53bc5bf315d73 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -626,10 +626,10 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -typescript-vscode-sh-plugin@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.2.0.tgz#8f1b79f2bc5a7d225235bf2b9fffdec9499f00f7" - integrity sha512-jp3B45VPwBuJ7I7IshkNuxn92/DUPqFq6Y3wiNE64Mmws0UCkXKbTsDT8+CKt5rFFCQL7PUtApj6aQkN8OKfxw== +typescript-vscode-sh-plugin@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.3.0.tgz#a071fa28187259a04e3d6862adba41a6332106ff" + integrity sha512-pqPgYa1L64/2LnhP/tJz/+hUsbKzQRMGCdp6Z5Z/dhAdjChB/0WZvovbwDlApGWxOvNAG+oub9TXnJ1yT0WfXQ== uri-js@^4.2.2: version "4.2.2" From 59e12621ae035508335fa7ab5d7935e16deda9e0 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 10 Jan 2020 16:15:08 +0100 Subject: [PATCH 153/315] polish --- .../src/features/semanticTokens.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/typescript-language-features/src/features/semanticTokens.ts b/extensions/typescript-language-features/src/features/semanticTokens.ts index 1762360568785..fefb8e2271f5a 100644 --- a/extensions/typescript-language-features/src/features/semanticTokens.ts +++ b/extensions/typescript-language-features/src/features/semanticTokens.ts @@ -10,10 +10,9 @@ import * as Proto from '../protocol'; export function register(selector: vscode.DocumentSelector, client: ITypeScriptServiceClient) { const provider = new SemanticTokensProvider(client); return vscode.languages.registerSemanticTokensProvider(selector, provider, provider.getLegend()); - } -/* +/** * Prototype of a SemanticTokensProvider, relying on the experimental `encodedSemanticClassifications-full` request from the TypeScript server. * As the results retured by the TypeScript server are limited, we also add a Typescript plugin (typescript-vscode-sh-plugin) to enrich the returned token. */ @@ -49,7 +48,7 @@ class SemanticTokensProvider implements vscode.SemanticTokensProvider { requestArgs = _options.ranges.map(r => { const start = document.offsetAt(r.start); const length = document.offsetAt(r.end) - start; return { file, start, length }; }); requestArgs = requestArgs.sort((a1, a2) => a1.start - a2.start); } else { - requestArgs = [{ file, start: 0, length: document.getText().length }]; // full file + requestArgs = [{ file, start: 0, length: document.getText().length }]; // full document } for (const requestArg of requestArgs) { const response = await (this.client as ExperimentalProtocol.IExtendedTypeScriptServiceClient).execute('encodedSemanticClassifications-full', requestArg, token); @@ -102,6 +101,7 @@ class SemanticTokensProvider implements vscode.SemanticTokensProvider { } } +// Don't change TokenType and TokenModifier enums without adopting typescript-vscode-sh-plugin enum TokenType { 'class', 'enum', @@ -136,7 +136,7 @@ tokenTypeMap[ExperimentalProtocol.ClassificationType.typeParameterName] = TokenT tokenTypeMap[ExperimentalProtocol.ClassificationType.typeAliasName] = TokenType.type; tokenTypeMap[ExperimentalProtocol.ClassificationType.parameterName] = TokenType.parameter; -export namespace ExperimentalProtocol { +namespace ExperimentalProtocol { export interface IExtendedTypeScriptServiceClient { execute( From adb6281d0aea56c8e701714858c7fd593e030fc3 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 10 Jan 2020 16:38:32 +0100 Subject: [PATCH 154/315] fix html tests --- .../src/modes/javascriptSemanticTokens.ts | 53 +++++++++---------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts index 9a1f394977a9a..7e4deafa262e7 100644 --- a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts +++ b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts @@ -55,40 +55,35 @@ export function getSemanticTokens(jsLanguageService: ts.LanguageService, current return resultTokens; } -enum TokenType { - 'class', - 'enum', - 'interface', - 'namespace', - 'typeParameter', - 'type', - 'parameter', - 'variable', - 'property', - 'constant', - 'function', - 'member', - _sentinel + +export function getSemanticTokenLegend() { + return { types: tokenTypes, modifiers: tokenModifiers }; } -enum TokenModifier { - 'declaration', - 'static', - 'async', - _sentinel +const tokenTypes: string[] = ['class', 'enum', 'interface', 'namespace', 'typeParameter', 'type', 'parameter', 'variable', 'property', 'constant', 'function', 'member']; +const tokenModifiers: string[] = ['declaration', 'static', 'async']; + +const enum TokenType { + 'class' = 0, + 'enum' = 1, + 'interface' = 2, + 'namespace' = 3, + 'typeParameter' = 4, + 'type' = 5, + 'parameter' = 6, + 'variable' = 7, + 'property' = 8, + 'constant' = 9, + 'function' = 10, + 'member' = 11 } -export function getSemanticTokenLegend() { - const tokenTypes = []; - for (let i = 0; i < TokenType._sentinel; i++) { - tokenTypes.push(TokenType[i]); - } - const tokenModifiers = []; - for (let i = 0; i < TokenModifier._sentinel; i++) { - tokenModifiers.push(TokenModifier[i]); - } - return { types: tokenTypes, modifiers: tokenModifiers }; + +const enum TokenModifier { + 'declaration' = 0x01, + 'static' = 0x02, + 'async' = 0x04, } const tokenFromDeclarationMapping: { [name: string]: TokenType } = { From caa505d53c86596cf06768e624da761e1f50b168 Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 10 Jan 2020 16:39:19 +0100 Subject: [PATCH 155/315] debug editor contribution: move out logic to helper functions --- .../debug/browser/debugEditorContribution.ts | 259 +++++++++--------- 1 file changed, 130 insertions(+), 129 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 5f6e668169516..c22007db2f244 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -27,7 +27,7 @@ import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWid import { Position } from 'vs/editor/common/core/position'; import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; import { first } from 'vs/base/common/arrays'; -import { memoize } from 'vs/base/common/decorators'; +import { memoize, createMemoizer } from 'vs/base/common/decorators'; import { IEditorHoverOptions, EditorOption } from 'vs/editor/common/config/editorOptions'; import { CancellationToken } from 'vs/base/common/cancellation'; import { DebugHoverWidget } from 'vs/workbench/contrib/debug/browser/debugHover'; @@ -42,6 +42,125 @@ const MAX_NUM_INLINE_VALUES = 100; // JS Global scope can have 700+ entries. We const MAX_INLINE_DECORATOR_LENGTH = 150; // Max string length of each inline decorator when debugging. If exceeded ... is added const MAX_TOKENIZATION_LINE_LEN = 500; // If line is too long, then inline values for the line are skipped +function createInlineValueDecoration(lineNumber: number, contentText: string): IDecorationOptions { + // If decoratorText is too long, trim and add ellipses. This could happen for minified files with everything on a single line + if (contentText.length > MAX_INLINE_DECORATOR_LENGTH) { + contentText = contentText.substr(0, MAX_INLINE_DECORATOR_LENGTH) + '...'; + } + + return { + range: { + startLineNumber: lineNumber, + endLineNumber: lineNumber, + startColumn: Constants.MAX_SAFE_SMALL_INTEGER, + endColumn: Constants.MAX_SAFE_SMALL_INTEGER + }, + renderOptions: { + after: { + contentText, + backgroundColor: 'rgba(255, 200, 0, 0.2)', + margin: '10px' + }, + dark: { + after: { + color: 'rgba(255, 255, 255, 0.5)', + } + }, + light: { + after: { + color: 'rgba(0, 0, 0, 0.5)', + } + } + } + }; +} + +function createInlineValueDecorationsInsideRange(expressions: ReadonlyArray, range: Range, model: ITextModel, wordToLineNumbersMap: Map): IDecorationOptions[] { + const nameValueMap = new Map(); + for (let expr of expressions) { + nameValueMap.set(expr.name, expr.value); + // Limit the size of map. Too large can have a perf impact + if (nameValueMap.size >= MAX_NUM_INLINE_VALUES) { + break; + } + } + + const lineToNamesMap: Map = new Map(); + + // Compute unique set of names on each line + nameValueMap.forEach((_value, name) => { + const lineNumbers = wordToLineNumbersMap.get(name); + if (lineNumbers) { + for (let lineNumber of lineNumbers) { + if (range.containsPosition(new Position(lineNumber, 0))) { + if (!lineToNamesMap.has(lineNumber)) { + lineToNamesMap.set(lineNumber, []); + } + + if (lineToNamesMap.get(lineNumber)!.indexOf(name) === -1) { + lineToNamesMap.get(lineNumber)!.push(name); + } + } + } + } + }); + + const decorations: IDecorationOptions[] = []; + // Compute decorators for each line + lineToNamesMap.forEach((names, line) => { + const contentText = names.sort((first, second) => { + const content = model.getLineContent(line); + return content.indexOf(first) - content.indexOf(second); + }).map(name => `${name} = ${nameValueMap.get(name)}`).join(', '); + decorations.push(createInlineValueDecoration(line, contentText)); + }); + + return decorations; +} + +function getWordToLineNumbersMap(model: ITextModel | null): Map { + const result = new Map(); + if (!model) { + return result; + } + + // For every word in every line, map its ranges for fast lookup + for (let lineNumber = 1, len = model.getLineCount(); lineNumber <= len; ++lineNumber) { + const lineContent = model.getLineContent(lineNumber); + + // If line is too long then skip the line + if (lineContent.length > MAX_TOKENIZATION_LINE_LEN) { + continue; + } + + model.forceTokenization(lineNumber); + const lineTokens = model.getLineTokens(lineNumber); + for (let tokenIndex = 0, tokenCount = lineTokens.getCount(); tokenIndex < tokenCount; tokenIndex++) { + const tokenStartOffset = lineTokens.getStartOffset(tokenIndex); + const tokenEndOffset = lineTokens.getEndOffset(tokenIndex); + const tokenType = lineTokens.getStandardTokenType(tokenIndex); + const tokenStr = lineContent.substring(tokenStartOffset, tokenEndOffset); + + // Token is a word and not a comment + if (tokenType === StandardTokenType.Other) { + DEFAULT_WORD_REGEXP.lastIndex = 0; // We assume tokens will usually map 1:1 to words if they match + const wordMatch = DEFAULT_WORD_REGEXP.exec(tokenStr); + + if (wordMatch) { + const word = wordMatch[0]; + if (!result.has(word)) { + result.set(word, []); + } + + result.get(word)!.push(lineNumber); + } + } + } + } + + return result; +} + class DebugEditorContribution implements IDebugEditorContribution { private toDispose: IDisposable[]; @@ -49,8 +168,7 @@ class DebugEditorContribution implements IDebugEditorContribution { private nonDebugHoverPosition: Position | undefined; private hoverRange: Range | null = null; private mouseDown = false; - - private wordToLineNumbersMap: Map | undefined; + private static readonly MEMOIZER = createMemoizer(); private exceptionWidget: ExceptionWidget | undefined; @@ -95,7 +213,7 @@ class DebugEditorContribution implements IDebugEditorContribution { })); this.toDispose.push(this.editor.onKeyDown((e: IKeyboardEvent) => this.onKeyDown(e))); this.toDispose.push(this.editor.onDidChangeModelContent(() => { - this.wordToLineNumbersMap = undefined; + DebugEditorContribution.MEMOIZER.clear(); this.updateInlineValuesScheduler.schedule(); })); this.toDispose.push(this.editor.onDidChangeModel(async () => { @@ -107,7 +225,7 @@ class DebugEditorContribution implements IDebugEditorContribution { this.toggleExceptionWidget(); this.hideHoverWidget(); this.updateConfigurationWidgetVisibility(); - this.wordToLineNumbersMap = undefined; + DebugEditorContribution.MEMOIZER.clear(); await this.updateInlineValueDecorations(stackFrame); })); this.toDispose.push(this.editor.onDidScrollChange(() => this.hideHoverWidget)); @@ -118,6 +236,11 @@ class DebugEditorContribution implements IDebugEditorContribution { })); } + @DebugEditorContribution.MEMOIZER + private get wordToLineNumbersMap(): Map { + return getWordToLineNumbersMap(this.editor.getModel()); + } + private _applyHoverConfiguration(model: ITextModel, stackFrame: IStackFrame | undefined): void { if (stackFrame && model.uri.toString() === stackFrame.source.uri.toString()) { this.editor.updateOptions({ @@ -401,136 +524,14 @@ class DebugEditorContribution implements IDebugEditorContribution { range = range.setStartPosition(scope.range.startLineNumber, scope.range.startColumn); } - return this.createInlineValueDecorationsInsideRange(children, range, model); + return createInlineValueDecorationsInsideRange(children, range, model, this.wordToLineNumbersMap); })); + const allDecorations = decorationsPerScope.reduce((previous, current) => previous.concat(current), []); this.editor.setDecorations(INLINE_VALUE_DECORATION_KEY, allDecorations); } - private createInlineValueDecorationsInsideRange(expressions: ReadonlyArray, range: Range, model: ITextModel): IDecorationOptions[] { - const nameValueMap = new Map(); - for (let expr of expressions) { - nameValueMap.set(expr.name, expr.value); - // Limit the size of map. Too large can have a perf impact - if (nameValueMap.size >= MAX_NUM_INLINE_VALUES) { - break; - } - } - - const lineToNamesMap: Map = new Map(); - const wordToPositionsMap = this.getWordToPositionsMap(); - - // Compute unique set of names on each line - nameValueMap.forEach((value, name) => { - const positions = wordToPositionsMap.get(name); - if (positions) { - for (let position of positions) { - if (range.containsPosition(position)) { - if (!lineToNamesMap.has(position.lineNumber)) { - lineToNamesMap.set(position.lineNumber, []); - } - - if (lineToNamesMap.get(position.lineNumber)!.indexOf(name) === -1) { - lineToNamesMap.get(position.lineNumber)!.push(name); - } - } - } - } - }); - - const decorations: IDecorationOptions[] = []; - // Compute decorators for each line - lineToNamesMap.forEach((names, line) => { - const contentText = names.sort((first, second) => { - const content = model.getLineContent(line); - return content.indexOf(first) - content.indexOf(second); - }).map(name => `${name} = ${nameValueMap.get(name)}`).join(', '); - decorations.push(this.createInlineValueDecoration(line, contentText)); - }); - - return decorations; - } - - private createInlineValueDecoration(lineNumber: number, contentText: string): IDecorationOptions { - // If decoratorText is too long, trim and add ellipses. This could happen for minified files with everything on a single line - if (contentText.length > MAX_INLINE_DECORATOR_LENGTH) { - contentText = contentText.substr(0, MAX_INLINE_DECORATOR_LENGTH) + '...'; - } - - return { - range: { - startLineNumber: lineNumber, - endLineNumber: lineNumber, - startColumn: Constants.MAX_SAFE_SMALL_INTEGER, - endColumn: Constants.MAX_SAFE_SMALL_INTEGER - }, - renderOptions: { - after: { - contentText, - backgroundColor: 'rgba(255, 200, 0, 0.2)', - margin: '10px' - }, - dark: { - after: { - color: 'rgba(255, 255, 255, 0.5)', - } - }, - light: { - after: { - color: 'rgba(0, 0, 0, 0.5)', - } - } - } - }; - } - - private getWordToPositionsMap(): Map { - if (!this.wordToLineNumbersMap) { - this.wordToLineNumbersMap = new Map(); - const model = this.editor.getModel(); - if (!model) { - return this.wordToLineNumbersMap; - } - - // For every word in every line, map its ranges for fast lookup - for (let lineNumber = 1, len = model.getLineCount(); lineNumber <= len; ++lineNumber) { - const lineContent = model.getLineContent(lineNumber); - - // If line is too long then skip the line - if (lineContent.length > MAX_TOKENIZATION_LINE_LEN) { - continue; - } - - model.forceTokenization(lineNumber); - const lineTokens = model.getLineTokens(lineNumber); - for (let tokenIndex = 0, tokenCount = lineTokens.getCount(); tokenIndex < tokenCount; tokenIndex++) { - const tokenStartOffset = lineTokens.getStartOffset(tokenIndex); - const tokenEndOffset = lineTokens.getEndOffset(tokenIndex); - const tokenType = lineTokens.getStandardTokenType(tokenIndex); - const tokenStr = lineContent.substring(tokenStartOffset, tokenEndOffset); - - // Token is a word and not a comment - if (tokenType === StandardTokenType.Other) { - DEFAULT_WORD_REGEXP.lastIndex = 0; // We assume tokens will usually map 1:1 to words if they match - const wordMatch = DEFAULT_WORD_REGEXP.exec(tokenStr); - - if (wordMatch) { - const word = wordMatch[0]; - if (!this.wordToLineNumbersMap.has(word)) { - this.wordToLineNumbersMap.set(word, []); - } - - this.wordToLineNumbersMap.get(word)!.push(new Position(lineNumber, tokenStartOffset)); - } - } - } - } - } - - return this.wordToLineNumbersMap; - } - dispose(): void { if (this.hoverWidget) { this.hoverWidget.dispose(); From b145d332a9fbef337dba9cda215df866726c262b Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Fri, 10 Jan 2020 16:57:04 +0100 Subject: [PATCH 156/315] First cut for DOMLineBreaksComputer --- .../browser/view/domLineBreaksComputer.ts | 212 ++++++++++++++++++ .../editor/browser/widget/codeEditorWidget.ts | 14 +- .../viewModel/monospaceLineBreaksComputer.ts | 7 +- 3 files changed, 230 insertions(+), 3 deletions(-) create mode 100644 src/vs/editor/browser/view/domLineBreaksComputer.ts diff --git a/src/vs/editor/browser/view/domLineBreaksComputer.ts b/src/vs/editor/browser/view/domLineBreaksComputer.ts new file mode 100644 index 0000000000000..b22d6757f436d --- /dev/null +++ b/src/vs/editor/browser/view/domLineBreaksComputer.ts @@ -0,0 +1,212 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ILineBreaksComputerFactory, LineBreakData, ILineBreaksComputer } from 'vs/editor/common/viewModel/splitLinesCollection'; +import { IComputedEditorOptions, EditorOption, WrappingIndent } from 'vs/editor/common/config/editorOptions'; +import { FontInfo } from 'vs/editor/common/config/fontInfo'; +import { createStringBuilder, IStringBuilder } from 'vs/editor/common/core/stringBuilder'; +import { CharCode } from 'vs/base/common/charCode'; +import * as strings from 'vs/base/common/strings'; +import { Configuration } from 'vs/editor/browser/config/configuration'; + +export class DOMLineBreaksComputerFactory implements ILineBreaksComputerFactory { + + public static create(options: IComputedEditorOptions): DOMLineBreaksComputerFactory { + return new DOMLineBreaksComputerFactory( + options.get(EditorOption.fontInfo) + ); + } + + private _fontInfo: FontInfo; + + constructor(fontInfo: FontInfo) { + this._fontInfo = fontInfo; + } + + public createLineBreaksComputer(tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ILineBreaksComputer { + tabSize = tabSize | 0; //@perf + wrappingColumn = +wrappingColumn; //@perf + columnsForFullWidthChar = +columnsForFullWidthChar; //@perf + + let requests: string[] = []; + return { + addRequest: (lineText: string, previousLineBreakData: LineBreakData | null) => { + requests.push(lineText); + }, + finalize: () => { + return createLineBreaks(this._fontInfo, tabSize, wrappingColumn, wrappingIndent, requests); + } + }; + } +} + +function createLineBreaks(fontInfo: FontInfo, tabSize: number, firstLineBreakColumn: number, wrappingIndent: WrappingIndent, requests: string[]): (LineBreakData | null)[] { + const width = Math.round(firstLineBreakColumn * fontInfo.typicalHalfwidthCharacterWidth); + + const containerDomNode = document.createElement('div'); + Configuration.applyFontInfoSlow(containerDomNode, fontInfo); + containerDomNode.style.width = `${width}px`; + + const sb = createStringBuilder(10000); + const charOffsets: number[][] = []; + const visibleColumns: number[][] = []; + for (let i = 0; i < requests.length; i++) { + const r = renderLine(i, requests[i], tabSize, sb); + charOffsets[i] = r[0]; + visibleColumns[i] = r[1]; + } + containerDomNode.innerHTML = sb.build(); + + containerDomNode.style.position = 'absolute'; + containerDomNode.style.right = '0'; + containerDomNode.style.bottom = '0'; + containerDomNode.style.zIndex = '10000'; + document.body.appendChild(containerDomNode); + + let range = document.createRange(); + const lineDomNodes = Array.prototype.slice.call(containerDomNode.children, 0); + + let result: (LineBreakData | null)[] = []; + for (let i = 0; i < requests.length; i++) { + const lineDomNode = lineDomNodes[i]; + result[i] = readLineBreaks(range, lineDomNode, requests[i], charOffsets[i], visibleColumns[i]); + } + + document.body.removeChild(containerDomNode); + return result; +} + +function renderLine(lineIndex: number, lineContent: string, tabSize: number, sb: IStringBuilder): [number[], number[]] { + sb.appendASCIIString('

'); + // if (containsRTL) { + // sb.appendASCIIString('" dir="ltr'); + // } + + let visibleColumn = 0; + let charOffset = 0; + let charOffsets: number[] = []; + let visibleColumns: number[] = []; + + for (let charIndex = 0, len = lineContent.length; charIndex < len; charIndex++) { + charOffsets[charIndex] = charOffset; + visibleColumns[charIndex] = visibleColumn; + const charCode = lineContent.charCodeAt(charIndex); + let producedCharacters = 1; + let charWidth = 1; + switch (charCode) { + case CharCode.Tab: + producedCharacters = (tabSize - (visibleColumn % tabSize)); + charWidth = producedCharacters; + for (let space = 1; space <= producedCharacters; space++) { + sb.appendASCII(CharCode.Space); + // sb.write1(0xA0); //   + } + break; + + case CharCode.Space: + sb.appendASCII(CharCode.Space); + // sb.write1(0xA0); //   + break; + + case CharCode.LessThan: + sb.appendASCIIString('<'); + break; + + case CharCode.GreaterThan: + sb.appendASCIIString('>'); + break; + + case CharCode.Ampersand: + sb.appendASCIIString('&'); + break; + + case CharCode.Null: + sb.appendASCIIString('�'); + break; + + case CharCode.UTF8_BOM: + case CharCode.LINE_SEPARATOR_2028: + sb.write1(0xFFFD); + break; + + default: + if (strings.isFullWidthCharacter(charCode)) { + charWidth++; + } + // if (renderControlCharacters && charCode < 32) { + // sb.write1(9216 + charCode); + // } else { + sb.write1(charCode); + // } + } + + charOffset += producedCharacters; + visibleColumn += charWidth; + } + + charOffsets[lineContent.length] = charOffset; + visibleColumns[lineContent.length] = visibleColumn; + + sb.appendASCIIString('
'); + + return [charOffsets, visibleColumns]; +} + +function readLineBreaks(range: Range, lineDomNode: HTMLDivElement, lineContent: string, charOffsets: number[], visibleColumns: number[]): LineBreakData | null { + if (lineContent.length <= 1) { + return null; + } + const textContentNode = lineDomNode.firstChild!; + + const breakOffsets: number[] = []; + discoverBreaks(range, textContentNode, charOffsets, 0, null, lineContent.length - 1, null, breakOffsets); + + if (breakOffsets.length === 0) { + return null; + } + + breakOffsets.push(lineContent.length); + + const breakOffsetsVisibleColumn = []; + for (let i = 0, len = breakOffsets.length; i < len; i++) { + breakOffsetsVisibleColumn[i] = visibleColumns[breakOffsets[i]]; + } + + return new LineBreakData(breakOffsets, breakOffsetsVisibleColumn, 0); +} + +type MaybeRects = ClientRectList | DOMRectList | null; + +function discoverBreaks(range: Range, textContentNode: Node, charOffsets: number[], low: number, lowRects: MaybeRects, high: number, highRects: MaybeRects, result: number[]): void { + if (low === high) { + return; + } + + lowRects = lowRects || readClientRect(range, textContentNode, charOffsets[low], charOffsets[low + 1]); + highRects = highRects || readClientRect(range, textContentNode, charOffsets[high], charOffsets[high + 1]); + + if (Math.abs(lowRects[0].top - highRects[0].top) <= 0.1) { + // same line + return; + } + + // there is at least one line break between these two offsets + if (low + 1 === high) { + // the two characters are adjacent, so the line break must be exactly between them + result.push(high); + return; + } + + const mid = low + ((high - low) / 2) | 0; + const midRects = readClientRect(range, textContentNode, charOffsets[mid], charOffsets[mid + 1]); + discoverBreaks(range, textContentNode, charOffsets, low, lowRects, mid, midRects, result); + discoverBreaks(range, textContentNode, charOffsets, mid, midRects, high, highRects, result); +} + +function readClientRect(range: Range, textContentNode: Node, startOffset: number, endOffset: number): ClientRectList | DOMRectList { + range.setStart(textContentNode, startOffset); + range.setEnd(textContentNode, endOffset); + return range.getClientRects(); +} diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 8015e70e61074..d9ac4a31b0db4 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -51,8 +51,10 @@ import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/com import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { withNullAsUndefined } from 'vs/base/common/types'; import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; +import { DOMLineBreaksComputerFactory } from 'vs/editor/browser/view/domLineBreaksComputer'; let EDITOR_ID = 0; +const useDOMLineBreaksComputerFactory = false; export interface ICodeEditorWidgetOptions { /** @@ -1337,7 +1339,17 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE model.onBeforeAttached(); - const viewModel = new ViewModel(this._id, this._configuration, model, MonospaceLineBreaksComputerFactory.create(this._configuration.options), (callback) => dom.scheduleAtNextAnimationFrame(callback)); + const viewModel = new ViewModel( + this._id, + this._configuration, + model, + ( + useDOMLineBreaksComputerFactory + ? DOMLineBreaksComputerFactory.create(this._configuration.options) + : MonospaceLineBreaksComputerFactory.create(this._configuration.options) + ), + (callback) => dom.scheduleAtNextAnimationFrame(callback) + ); listenersToRemove.push(model.onDidChangeDecorations((e) => this._onDidChangeModelDecorations.fire(e))); listenersToRemove.push(model.onDidChangeLanguage((e) => { diff --git a/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts index dfec62fc04fe7..546464c263658 100644 --- a/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts +++ b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts @@ -51,6 +51,9 @@ class WrappingCharacterClassifier extends CharacterClassifier { } } +let arrPool1: number[] = []; +let arrPool2: number[] = []; + export class MonospaceLineBreaksComputerFactory implements ILineBreaksComputerFactory { public static create(options: IComputedEditorOptions): MonospaceLineBreaksComputerFactory { @@ -88,14 +91,14 @@ export class MonospaceLineBreaksComputerFactory implements ILineBreaksComputerFa result[i] = createLineBreaks(this.classifier, requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent); } } + arrPool1.length = 0; + arrPool2.length = 0; return result; } }; } } -let arrPool1: number[] = []; -let arrPool2: number[] = []; function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterClassifier, previousBreakingData: LineBreakData, lineText: string, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): LineBreakData | null { if (firstLineBreakColumn === -1) { return null; From c488a65d9788ee2e355b2bd09a76929e22f3fa6b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 10 Jan 2020 17:07:12 +0100 Subject: [PATCH 157/315] fix rename case, show file type operations --- .../contrib/bulkEdit/browser/bulkEdit.css | 7 +++ .../contrib/bulkEdit/browser/bulkEditPane.ts | 2 +- .../bulkEdit/browser/bulkEditPreview.ts | 17 +++++- .../contrib/bulkEdit/browser/bulkEditTree.ts | 56 +++++++++---------- 4 files changed, 52 insertions(+), 30 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css index b83367eb5d16a..52edbe56a36bc 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css @@ -34,3 +34,10 @@ .monaco-workbench .bulk-edit-panel .monaco-tl-contents .edit-checkbox.disabled { opacity: .5; } + +.monaco-workbench .bulk-edit-panel .monaco-tl-contents .details { + margin-left: .5em; + opacity: .7; + font-size: 0.9em; + white-space: pre +} diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index ee46ddb38ef10..c4456386b4f67 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -93,7 +93,7 @@ export class BulkEditPane extends ViewPane { this._tree = this._instaService.createInstance( WorkbenchAsyncDataTree, this.id, treeContainer, new BulkEditDelegate(), - [new TextEditElementRenderer(), new FileElementRenderer(resourceLabels)], + [new TextEditElementRenderer(), this._instaService.createInstance(FileElementRenderer, resourceLabels)], this._instaService.createInstance(BulkEditDataSource), { identityProvider: new BulkEditIdentityProvider(), diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts index 0414f0d1a2702..614c152f94a39 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts @@ -60,6 +60,7 @@ export class BulkFileOperation extends CheckedObject { type = BulkFileOperationType.None; textEdits: BulkTextEdit[] = []; originalEdits = new Map(); + newUri?: URI; constructor( readonly uri: URI, @@ -73,6 +74,9 @@ export class BulkFileOperation extends CheckedObject { this.originalEdits.set(index, edit); if (isResourceTextEdit(edit)) { this.textEdits = this.textEdits.concat(edit.edits.map(edit => new BulkTextEdit(this, edit, this._emitter))); + + } else if (type === BulkFileOperationType.Rename) { + this.newUri = edit.newUri; } } } @@ -105,6 +109,7 @@ export class BulkFileOperations { async _init() { const operationByResource = new Map(); + const newToOldUri = new Map(); for (let idx = 0; idx < this._bulkEdit.edits.length; idx++) { const edit = this._bulkEdit.edits[idx]; @@ -123,6 +128,9 @@ export class BulkFileOperations { // noop -> "soft" rename to something that already exists continue; } + // map newUri onto oldUri so that text-edit appear for + // the same file element + newToOldUri.set(edit.newUri.toString(), uri.toString()); } else if (edit.oldUri) { type = BulkFileOperationType.Delete; @@ -145,8 +153,15 @@ export class BulkFileOperations { continue; } - const key = uri.toString(); + let key = uri.toString(); let operation = operationByResource.get(key); + + // rename + if (!operation && newToOldUri.has(key)) { + key = newToOldUri.get(key)!; + operation = operationByResource.get(key); + } + if (!operation) { operation = new BulkFileOperation(uri, this); operationByResource.set(key, operation); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index b83b83dc92b17..0fbdcd2634579 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -16,29 +16,18 @@ import { ITextModel } from 'vs/editor/common/model'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { TextModel } from 'vs/editor/common/model/textModel'; import { BulkFileOperations, BulkFileOperation, BulkFileOperationType, BulkTextEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; -import { localize } from 'vs/nls'; import { FileKind } from 'vs/platform/files/common/files'; +import { localize } from 'vs/nls'; +import { ILabelService } from 'vs/platform/label/common/label'; // --- VIEW MODEL export class FileElement { - private static _typeLabels: Record = { - [BulkFileOperationType.Create]: localize('create', "create"), - [BulkFileOperationType.Delete]: localize('delete', "delete"), - [BulkFileOperationType.Rename]: localize('rename', "rename"), - [BulkFileOperationType.Create | BulkFileOperationType.Delete]: localize('createDelete', "create & delete"), - [BulkFileOperationType.Create | BulkFileOperationType.Rename]: localize('createRename', "create & rename"), - [BulkFileOperationType.Delete | BulkFileOperationType.Rename]: localize('deleteRename', "delete & rename"), - [BulkFileOperationType.Create | BulkFileOperationType.Delete | BulkFileOperationType.Rename]: localize('createRenameDelete', "create, rename, delete"), - }; - readonly uri: URI; - readonly typeLabel: string; constructor(readonly edit: BulkFileOperation) { this.uri = edit.uri; - this.typeLabel = FileElement._typeLabels[edit.type] || ''; } } @@ -139,8 +128,13 @@ class FileElementTemplate { private readonly _checkbox: HTMLInputElement; private readonly _label: IResourceLabel; + private readonly _details: HTMLSpanElement; - constructor(container: HTMLElement, resourceLabels: ResourceLabels) { + constructor( + container: HTMLElement, + resourceLabels: ResourceLabels, + @ILabelService private readonly _labelService: ILabelService, + ) { this._checkbox = document.createElement('input'); this._checkbox.className = 'edit-checkbox'; @@ -149,6 +143,10 @@ class FileElementTemplate { container.appendChild(this._checkbox); this._label = resourceLabels.create(container, { supportHighlights: true }); + + this._details = document.createElement('span'); + this._details.className = 'details'; + container.appendChild(this._details); } dispose(): void { @@ -162,23 +160,22 @@ class FileElementTemplate { this._localDisposables.add(dom.addDisposableListener(this._checkbox, 'change', (() => element.edit.updateChecked(this._checkbox.checked)))); this._checkbox.checked = element.edit.isChecked(); - const extraClasses: string[] = []; - if (element.edit.type & BulkFileOperationType.Create) { - extraClasses.push('create'); - } - if (element.edit.type & BulkFileOperationType.Delete) { - extraClasses.push('delete'); - } - if (element.edit.type & BulkFileOperationType.Rename) { - extraClasses.push('rename'); - } this._label.setFile(element.uri, { matches: createMatches(score), fileKind: FileKind.FILE, fileDecorations: { colors: true, badges: false }, - // parentCount: element.edit.textEdits.length || undefined, - extraClasses, }); + + // details + if (element.edit.type & BulkFileOperationType.Rename && element.edit.newUri) { + this._details.innerText = localize('detail.rename', "(renaming to {0})", this._labelService.getUriLabel(element.edit.newUri, { relative: true })); + } else if (element.edit.type & BulkFileOperationType.Create) { + this._details.innerText = localize('detail.create', "(creating)"); + } else if (element.edit.type & BulkFileOperationType.Create) { + this._details.innerText = localize('detail.del', "(deleting)"); + } else { + this._details.innerText = ''; + } } } @@ -188,10 +185,13 @@ export class FileElementRenderer implements ITreeRenderer, _index: number, template: FileElementTemplate): void { From f4f3c7ce570aa2ae36f1ed7ac0fc77ab6a5f3326 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 10 Jan 2020 17:10:31 +0100 Subject: [PATCH 158/315] ignore pinned state, close all editors --- .../contrib/bulkEdit/browser/bulkEdit.contribution.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts index 2e46f5687aa88..19f870dec791b 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts @@ -83,10 +83,7 @@ class BulkEditPreviewContribution { // (3) close preview editors for (let group of this._editorGroupsService.groups) { for (let input of group.editors) { - if (!group.isPinned(input) - && input instanceof DiffEditorInput - && input.modifiedInput.getResource()?.scheme === BulkEditPreviewProvider.Schema - ) { + if (input instanceof DiffEditorInput && input.modifiedInput.getResource()?.scheme === BulkEditPreviewProvider.Schema) { group.closeEditor(input, { preserveFocus: true }); } } From 0b42d1c01a3a9a0673fc84fd295c1698f01f51ca Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 10 Jan 2020 17:10:20 +0100 Subject: [PATCH 159/315] debug hover: find expression in stack frame tests --- .../contrib/debug/browser/debugHover.ts | 67 +++++++++---------- .../debug/test/browser/debugHover.test.ts | 65 ++++++++++++++++++ 2 files changed, 98 insertions(+), 34 deletions(-) create mode 100644 src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index 8e6e499016cee..618e02e8b37a0 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -14,7 +14,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IContentWidget, ICodeEditor, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IDebugService, IExpression, IExpressionContainer } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, IExpression, IExpressionContainer, IStackFrame } from 'vs/workbench/contrib/debug/common/debug'; import { Expression } from 'vs/workbench/contrib/debug/common/debugModel'; import { renderExpressionValue, replaceWhitespace } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; @@ -34,6 +34,34 @@ import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesV const $ = dom.$; const MAX_TREE_HEIGHT = 324; +async function doFindExpression(container: IExpressionContainer, namesToFind: string[]): Promise { + if (!container) { + return Promise.resolve(null); + } + + const children = await container.getChildren(); + // look for our variable in the list. First find the parents of the hovered variable if there are any. + const filtered = children.filter(v => namesToFind[0] === v.name); + if (filtered.length !== 1) { + return null; + } + + if (namesToFind.length === 1) { + return filtered[0]; + } else { + return doFindExpression(filtered[0], namesToFind.slice(1)); + } +} + +export async function findExpressionInStackFrame(stackFrame: IStackFrame, namesToFind: string[]): Promise { + const scopes = await stackFrame.getScopes(); + const nonExpensive = scopes.filter(s => !s.expensive); + const expressions = coalesce(await Promise.all(nonExpensive.map(scope => doFindExpression(scope, namesToFind)))); + + // only show if all expressions found have the same value + return expressions.length > 0 && expressions.every(e => e.value === expressions[0].value) ? expressions[0] : undefined; +} + export class DebugHoverWidget implements IContentWidget { static readonly ID = 'debug.hoverWidget'; @@ -166,7 +194,10 @@ export class DebugHoverWidget implements IContentWidget { expression = new Expression(matchingExpression); await expression.evaluate(session, this.debugService.getViewModel().focusedStackFrame, 'hover'); } else { - expression = await this.findExpressionInStackFrame(coalesce(matchingExpression.split('.').map(word => word.trim()))); + const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; + if (focusedStackFrame) { + expression = await findExpressionInStackFrame(focusedStackFrame, coalesce(matchingExpression.split('.').map(word => word.trim()))); + } } if (!expression || (expression instanceof Expression && !expression.available)) { @@ -186,38 +217,6 @@ export class DebugHoverWidget implements IContentWidget { className: 'hoverHighlight' }); - private async doFindExpression(container: IExpressionContainer, namesToFind: string[]): Promise { - if (!container) { - return Promise.resolve(null); - } - - const children = await container.getChildren(); - // look for our variable in the list. First find the parents of the hovered variable if there are any. - const filtered = children.filter(v => namesToFind[0] === v.name); - if (filtered.length !== 1) { - return null; - } - - if (namesToFind.length === 1) { - return filtered[0]; - } else { - return this.doFindExpression(filtered[0], namesToFind.slice(1)); - } - } - - private async findExpressionInStackFrame(namesToFind: string[]): Promise { - const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; - if (!focusedStackFrame) { - return undefined; - } - - const scopes = await focusedStackFrame.getScopes(); - const nonExpensive = scopes.filter(s => !s.expensive); - const expressions = coalesce(await Promise.all(nonExpensive.map(scope => this.doFindExpression(scope, namesToFind)))); - // only show if all expressions found have the same value - return expressions.length > 0 && expressions.every(e => e.value === expressions[0].value) ? expressions[0] : undefined; - } - private async doShow(position: Position, expression: IExpression, focus: boolean, forceValueHover = false): Promise { if (!this.domNode) { this.create(); diff --git a/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts new file mode 100644 index 0000000000000..258fe2a8f3cc0 --- /dev/null +++ b/src/vs/workbench/contrib/debug/test/browser/debugHover.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 { findExpressionInStackFrame } from 'vs/workbench/contrib/debug/browser/debugHover'; +import { createMockSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test'; +import { StackFrame, Thread, DebugModel, Scope, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; +import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; +import type { IScope, IExpression } from 'vs/workbench/contrib/debug/common/debug'; + +suite('Debug - Hover', () => { + test('find expression in stack frame', async () => { + const model = new DebugModel([], [], [], [], [], { isDirty: (e: any) => false }); + const session = createMockSession(model); + let stackFrame: StackFrame; + + const thread = new class extends Thread { + public getCallStack(): StackFrame[] { + return [stackFrame]; + } + }(session, 'mockthread', 1); + + const firstSource = new Source({ + name: 'internalModule.js', + path: 'a/b/c/d/internalModule.js', + sourceReference: 10, + }, 'aDebugSessionId'); + + let scope: Scope; + stackFrame = new class extends StackFrame { + getScopes(): Promise { + return Promise.resolve([scope]); + } + }(thread, 1, firstSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1); + + + let variableA: Variable; + let variableB: Variable; + scope = new class extends Scope { + getChildren(): Promise { + return Promise.resolve([variableA]); + } + }(stackFrame, 1, 'local', 1, false, 10, 10); + + variableA = new class extends Variable { + getChildren(): Promise { + return Promise.resolve([variableB]); + } + }(session, 1, scope, 2, 'A', 'A', undefined!, 0, 0, {}, 'string'); + variableB = new Variable(session, 1, scope, 2, 'B', 'A.B', undefined!, 0, 0, {}, 'string'); + + assert.equal(await findExpressionInStackFrame(stackFrame, ['A']), variableA); + assert.equal(await findExpressionInStackFrame(stackFrame, ['doesNotExist', 'no']), undefined); + assert.equal(await findExpressionInStackFrame(stackFrame, ['a']), undefined); + assert.equal(await findExpressionInStackFrame(stackFrame, ['B']), undefined); + assert.equal(await findExpressionInStackFrame(stackFrame, ['A', 'B']), variableB); + assert.equal(await findExpressionInStackFrame(stackFrame, ['A', 'C']), undefined); + + // We do not search in expensive scopes + scope.expensive = true; + assert.equal(await findExpressionInStackFrame(stackFrame, ['A']), undefined); + }); +}); From 5a50f25375058427df4c74bb2121fa2082f0a896 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 10 Jan 2020 08:30:25 -0800 Subject: [PATCH 160/315] Add "Don't sync this setting" action for settings For #84431 --- .../userDataSync/common/settingsSync.ts | 50 +++++----- .../preferences/browser/settingsTree.ts | 99 +++++++++++++------ 2 files changed, 96 insertions(+), 53 deletions(-) diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 09dd0a4647e17..5f21b275d5b38 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -183,7 +183,7 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer } if (hasRemoteChanged) { const formatUtils = await this.getFormattingOptions(); - const remoteContent = remoteUserData.content ? computeRemoteContent(content, remoteUserData.content, this.getIgnoredSettings(content), formatUtils) : content; + const remoteContent = remoteUserData.content ? computeRemoteContent(content, remoteUserData.content, getIgnoredSettings(this.configurationService, content), formatUtils) : content; this.logService.info('Settings: Updating remote settings'); const ref = await this.writeToRemote(remoteContent, remoteUserData.ref); remoteUserData = { ref, content }; @@ -242,7 +242,7 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer ) { this.logService.trace('Settings: Merging remote settings with local settings...'); const formatUtils = await this.getFormattingOptions(); - const result = merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null, this.getIgnoredSettings(), resolvedConflicts, formatUtils); + const result = merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null, getIgnoredSettings(this.configurationService), resolvedConflicts, formatUtils); // Sync only if there are changes if (result.hasChanges) { hasLocalChanged = result.mergeContent !== localContent; @@ -276,29 +276,6 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer return this._formattingOptions; } - private getIgnoredSettings(settingsContent?: string): string[] { - let value: string[] = []; - if (settingsContent) { - const setting = parse(settingsContent); - if (setting) { - value = setting['sync.ignoredSettings']; - } - } else { - value = this.configurationService.getValue('sync.ignoredSettings'); - } - const added: string[] = [], removed: string[] = []; - if (Array.isArray(value)) { - for (const key of value) { - if (startsWith(key, '-')) { - removed.push(key.substring(1)); - } else { - added.push(key); - } - } - } - return [...DEFAULT_IGNORED_SETTINGS, ...added].filter(setting => removed.indexOf(setting) === -1); - } - private async getLastSyncUserData(): Promise { try { const content = await this.fileService.readFile(this.lastSyncSettingsResource); @@ -335,3 +312,26 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer } } + +export function getIgnoredSettings(configurationService: IConfigurationService, settingsContent?: string): string[] { + let value: string[] = []; + if (settingsContent) { + const setting = parse(settingsContent); + if (setting) { + value = setting['sync.ignoredSettings']; + } + } else { + value = configurationService.getValue('sync.ignoredSettings'); + } + const added: string[] = [], removed: string[] = []; + if (Array.isArray(value)) { + for (const key of value) { + if (startsWith(key, '-')) { + removed.push(key.substring(1)); + } else { + added.push(key); + } + } + } + return [...DEFAULT_IGNORED_SETTINGS, ...added].filter(setting => removed.indexOf(setting) === -1); +} diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 226f584ad2d69..70cbb8822bee7 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -3,16 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { BrowserFeatures } from 'vs/base/browser/canIUse'; import * as DOM from 'vs/base/browser/dom'; -import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { alert as ariaAlert } from 'vs/base/browser/ui/aria/aria'; import { Button } from 'vs/base/browser/ui/button/button'; import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; -import { ListAriaRootRole, CachedListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { CachedListVirtualDelegate, ListAriaRootRole } from 'vs/base/browser/ui/list/list'; import { DefaultStyleController } from 'vs/base/browser/ui/list/listWidget'; import { ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; @@ -25,29 +26,29 @@ import { Color, RGBA } from 'vs/base/common/color'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { dispose, IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { isIOS } from 'vs/base/common/platform'; import { ISpliceable } from 'vs/base/common/sequence'; import { escapeRegExpCharacters, startsWith } from 'vs/base/common/strings'; +import { isArray } from 'vs/base/common/types'; import { localize } from 'vs/nls'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { errorForeground, focusBorder, foreground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, editorBackground } from 'vs/platform/theme/common/colorRegistry'; +import { editorBackground, errorForeground, focusBorder, foreground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry'; import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler, attachStyler } from 'vs/platform/theme/common/styler'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsSync'; import { ITOCEntry } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; import { ISettingsEditorViewState, settingKeyToDisplayFormat, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeNewExtensionsElement, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; -import { ListSettingWidget, IListChangeEvent, IListDataItem, settingsHeaderForeground, settingsNumberInputBackground, settingsNumberInputBorder, settingsNumberInputForeground, settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsSelectListBorder, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground, ExcludeSettingWidget } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; +import { ExcludeSettingWidget, IListChangeEvent, IListDataItem, ListSettingWidget, settingsHeaderForeground, settingsNumberInputBackground, settingsNumberInputBorder, settingsNumberInputForeground, settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsSelectListBorder, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; import { SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; -import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { isArray } from 'vs/base/common/types'; -import { BrowserFeatures } from 'vs/base/browser/canIUse'; -import { isIOS } from 'vs/base/common/platform'; +import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; const $ = DOM.$; @@ -203,6 +204,7 @@ interface ISettingItemTemplate extends IDisposableTemplate { deprecationWarningElement: HTMLElement; otherOverridesElement: HTMLElement; toolbar: ToolBar; + elementDisposables: IDisposable[]; } interface ISettingBoolItemTemplate extends ISettingItemTemplate { @@ -300,6 +302,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre // Put common injections back here constructor( private readonly settingActions: IAction[], + private readonly disposableActionFactory: (setting: ISetting) => IAction[], @IThemeService protected readonly _themeService: IThemeService, @IContextViewService protected readonly _contextViewService: IContextViewService, @IOpenerService protected readonly _openerService: IOpenerService, @@ -345,6 +348,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const template: ISettingItemTemplate = { toDispose, + elementDisposables: [], containerElement: container, categoryElement, @@ -393,7 +397,6 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const toolbar = new ToolBar(container, this._contextMenuService, { toggleMenuTitle }); - toolbar.setActions([], this.settingActions)(); const button = container.querySelector('.codicon-more'); if (button) { @@ -410,6 +413,9 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const element = node.element; template.context = element; template.toolbar.context = element; + const actions = this.disposableActionFactory(element.setting); + template.elementDisposables?.push(...actions); + template.toolbar.setActions([], [...this.settingActions, ...actions])(); const setting = element.setting; @@ -455,14 +461,15 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre DOM.append(template.otherOverridesElement, $('span', undefined, ')')); } - DOM.addStandardDisposableListener(view, DOM.EventType.CLICK, (e: IMouseEvent) => { - this._onDidClickOverrideElement.fire({ - targetKey: element.setting.key, - scope: element.overriddenScopeList[i] - }); - e.preventDefault(); - e.stopPropagation(); - }); + template.elementDisposables.push( + DOM.addStandardDisposableListener(view, DOM.EventType.CLICK, (e: IMouseEvent) => { + this._onDidClickOverrideElement.fire({ + targetKey: element.setting.key, + scope: element.overriddenScopeList[i] + }); + e.preventDefault(); + e.stopPropagation(); + })); } } @@ -472,7 +479,6 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre DOM.toggleClass(template.containerElement, 'is-deprecated', !!deprecationText); this.renderValue(element, template, onChange); - } private renderDescriptionMarkdown(element: SettingsTreeSettingElement, text: string, disposeables: DisposableStore): HTMLElement { @@ -563,6 +569,12 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre disposeTemplate(template: IDisposableTemplate): void { dispose(template.toDispose); } + + disposeElement(_element: ITreeNode, _index: number, template: IDisposableTemplate, _height: number | undefined): void { + if ((template as ISettingItemTemplate).elementDisposables) { + dispose((template as ISettingItemTemplate).elementDisposables); + } + } } export class SettingGroupRenderer implements ITreeRenderer { @@ -1113,6 +1125,7 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre const template: ISettingBoolItemTemplate = { toDispose: [toDispose], + elementDisposables: [], containerElement: container, categoryElement, @@ -1190,15 +1203,16 @@ export class SettingTreeRenderers { this._instantiationService.createInstance(CopySettingAsJSONAction), ]; + const actionFactory = (setting: ISetting) => [this._instantiationService.createInstance(StopSyncingSettingAction, setting)]; const settingRenderers = [ - this._instantiationService.createInstance(SettingBoolRenderer, this.settingActions), - this._instantiationService.createInstance(SettingNumberRenderer, this.settingActions), - this._instantiationService.createInstance(SettingBoolRenderer, this.settingActions), - this._instantiationService.createInstance(SettingArrayRenderer, this.settingActions), - this._instantiationService.createInstance(SettingComplexRenderer, this.settingActions), - this._instantiationService.createInstance(SettingTextRenderer, this.settingActions), - this._instantiationService.createInstance(SettingExcludeRenderer, this.settingActions), - this._instantiationService.createInstance(SettingEnumRenderer, this.settingActions), + this._instantiationService.createInstance(SettingBoolRenderer, this.settingActions, actionFactory), + this._instantiationService.createInstance(SettingNumberRenderer, this.settingActions, actionFactory), + this._instantiationService.createInstance(SettingBoolRenderer, this.settingActions, actionFactory), + this._instantiationService.createInstance(SettingArrayRenderer, this.settingActions, actionFactory), + this._instantiationService.createInstance(SettingComplexRenderer, this.settingActions, actionFactory), + this._instantiationService.createInstance(SettingTextRenderer, this.settingActions, actionFactory), + this._instantiationService.createInstance(SettingExcludeRenderer, this.settingActions, actionFactory), + this._instantiationService.createInstance(SettingEnumRenderer, this.settingActions, actionFactory), ]; this.onDidClickOverrideElement = Event.any(...settingRenderers.map(r => r.onDidClickOverrideElement)); @@ -1596,3 +1610,32 @@ class CopySettingAsJSONAction extends Action { return Promise.resolve(undefined); } } + +class StopSyncingSettingAction extends Action { + static readonly ID = 'settings.stopSyncingSetting'; + static readonly LABEL = localize('stopSyncingSetting', "Don't Sync This Setting"); + + constructor( + private readonly setting: ISetting, + @IConfigurationService private readonly configService: IConfigurationService, + ) { + super(StopSyncingSettingAction.ID, StopSyncingSettingAction.LABEL); + this.update(); + } + + update() { + const ignoredSettings = getIgnoredSettings(this.configService); + this.checked = ignoredSettings.includes(this.setting.key); + } + + async run(): Promise { + const currentValue = this.configService.getValue('sync.ignoredSettings'); + if (this.checked) { + this.configService.updateValue('sync.ignoredSettings', currentValue.filter(v => v !== this.setting.key)); + } else { + this.configService.updateValue('sync.ignoredSettings', [...currentValue, this.setting.key]); + } + + return Promise.resolve(undefined); + } +} From 175ee8056736b46e48b508ac416602a2d2212da5 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 10 Jan 2020 17:48:24 +0100 Subject: [PATCH 161/315] fix tests --- src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts index 9bb9292a4cdea..e408f21afd2e4 100644 --- a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts +++ b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts @@ -70,7 +70,11 @@ export class HighlightedLabel { htmlContent += '
'; pos = highlight.end; } - htmlContent += ``; + if (highlight.extraClasses) { + htmlContent += ``; + } else { + htmlContent += ``; + } const substring = this.text.substring(highlight.start, highlight.end); htmlContent += this.supportCodicons ? renderCodicons(escape(substring)) : escape(substring); htmlContent += ''; From 50a4f6e7f816ce997ee423f3297ab772b66ae8df Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 10 Jan 2020 18:04:00 +0100 Subject: [PATCH 162/315] debug: more unit tests for getStackFrameThreadAndSessionToFocus --- .../contrib/debug/browser/debugService.ts | 60 ++++++++-------- .../contrib/debug/browser/debugStatus.ts | 12 ++-- .../debug/test/browser/callStack.test.ts | 69 ++++++++++++++++++- .../debug/test/browser/debugHover.test.ts | 1 + 4 files changed, 106 insertions(+), 36 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 3009806109719..5f805dd778bd9 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -727,33 +727,8 @@ export class DebugService implements IDebugService { //---- focus management - async focusStackFrame(stackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession, explicit?: boolean): Promise { - if (!session) { - if (stackFrame || thread) { - session = stackFrame ? stackFrame.thread.session : thread!.session; - } else { - const sessions = this.model.getSessions(); - const stoppedSession = sessions.filter(s => s.state === State.Stopped).shift(); - session = stoppedSession || (sessions.length ? sessions[0] : undefined); - } - } - - if (!thread) { - if (stackFrame) { - thread = stackFrame.thread; - } else { - const threads = session ? session.getAllThreads() : undefined; - const stoppedThread = threads && threads.filter(t => t.stopped).shift(); - thread = stoppedThread || (threads && threads.length ? threads[0] : undefined); - } - } - - if (!stackFrame) { - if (thread) { - const callStack = thread.getCallStack(); - stackFrame = first(callStack, sf => !!(sf && sf.source && sf.source.available && sf.source.presentationHint !== 'deemphasize'), undefined); - } - } + async focusStackFrame(_stackFrame: IStackFrame | undefined, _thread?: IThread, _session?: IDebugSession, explicit?: boolean): Promise { + const { stackFrame, thread, session } = getStackFrameThreadAndSessionToFocus(this.model, _stackFrame, _thread, _session); if (stackFrame) { const editor = await stackFrame.openInEditor(this.editorService, true); @@ -1117,3 +1092,34 @@ export class DebugService implements IDebugService { }); } } + +export function getStackFrameThreadAndSessionToFocus(model: IDebugModel, stackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession): { stackFrame: IStackFrame | undefined, thread: IThread | undefined, session: IDebugSession | undefined } { + if (!session) { + if (stackFrame || thread) { + session = stackFrame ? stackFrame.thread.session : thread!.session; + } else { + const sessions = model.getSessions(); + const stoppedSession = sessions.filter(s => s.state === State.Stopped).shift(); + session = stoppedSession || (sessions.length ? sessions[0] : undefined); + } + } + + if (!thread) { + if (stackFrame) { + thread = stackFrame.thread; + } else { + const threads = session ? session.getAllThreads() : undefined; + const stoppedThread = threads && threads.filter(t => t.stopped).shift(); + thread = stoppedThread || (threads && threads.length ? threads[0] : undefined); + } + } + + if (!stackFrame) { + if (thread) { + const callStack = thread.getCallStack(); + stackFrame = first(callStack, sf => !!(sf && sf.source && sf.source.available && sf.source.presentationHint !== 'deemphasize'), undefined); + } + } + + return { session, thread, stackFrame }; +} diff --git a/src/vs/workbench/contrib/debug/browser/debugStatus.ts b/src/vs/workbench/contrib/debug/browser/debugStatus.ts index ac48ec84a4e45..8cfbf1702b8cb 100644 --- a/src/vs/workbench/contrib/debug/browser/debugStatus.ts +++ b/src/vs/workbench/contrib/debug/browser/debugStatus.ts @@ -10,7 +10,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IStatusbarEntry, IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/common/statusbar'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; - export class DebugStatusContribution implements IWorkbenchContribution { private showInStatusBar!: 'never' | 'always' | 'onFirstSessionStart'; @@ -56,20 +55,17 @@ export class DebugStatusContribution implements IWorkbenchContribution { })); } - private getText(): string { + private get entry(): IStatusbarEntry { + let text = ''; const manager = this.debugService.getConfigurationManager(); const name = manager.selectedConfiguration.name || ''; const nameAndLaunchPresent = name && manager.selectedConfiguration.launch; if (nameAndLaunchPresent) { - return '$(play) ' + (manager.getLaunches().length > 1 ? `${name} (${manager.selectedConfiguration.launch!.name})` : name); + text = '$(play) ' + (manager.getLaunches().length > 1 ? `${name} (${manager.selectedConfiguration.launch!.name})` : name); } - return ''; - } - - private get entry(): IStatusbarEntry { return { - text: this.getText(), + text: text, tooltip: nls.localize('selectAndStartDebug', "Select and start debug configuration"), command: 'workbench.action.debug.selectandstart' }; 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 8442cd4086c99..558aa88393d22 100644 --- a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts @@ -10,11 +10,12 @@ import { MockRawSession } from 'vs/workbench/contrib/debug/test/common/mockDebug import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; import { Range } from 'vs/editor/common/core/range'; -import { IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugSessionOptions, State } from 'vs/workbench/contrib/debug/common/debug'; import { NullOpenerService } from 'vs/platform/opener/common/opener'; import { createDecorationsForStackFrame } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution'; 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'; 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!); @@ -349,4 +350,70 @@ suite('Debug - CallStack', () => { contributedContext = getContextForContributedActions(session); assert.equal(contributedContext, session.getId()); }); + + test('focusStackFrameThreadAndSesion', () => { + const threadId1 = 1; + const threadName1 = 'firstThread'; + const threadId2 = 2; + const threadName2 = 'secondThread'; + const stoppedReason = 'breakpoint'; + + // Add the threads + const session = new class extends DebugSession { + 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!); + + const runningSession = createMockSession(model); + model.addSession(runningSession); + model.addSession(session); + + session['raw'] = rawSession; + + model.rawUpdate({ + sessionId: session.getId(), + threads: [{ + id: threadId1, + name: threadName1 + }] + }); + + // Stopped event with all threads stopped + model.rawUpdate({ + sessionId: session.getId(), + threads: [{ + id: threadId1, + name: threadName1 + }, { + id: threadId2, + name: threadName2 + }], + stoppedDetails: { + reason: stoppedReason, + threadId: 1, + allThreadsStopped: true + }, + }); + + const thread = session.getThread(threadId1)!; + const runningThread = session.getThread(threadId2); + + let toFocus = getStackFrameThreadAndSessionToFocus(model, undefined); + // Verify stopped session and stopped thread get focused + assert.deepEqual(toFocus, { stackFrame: undefined, thread: thread, session: session }); + + toFocus = getStackFrameThreadAndSessionToFocus(model, undefined, undefined, runningSession); + assert.deepEqual(toFocus, { stackFrame: undefined, thread: undefined, session: runningSession }); + + toFocus = getStackFrameThreadAndSessionToFocus(model, undefined, thread); + assert.deepEqual(toFocus, { stackFrame: undefined, thread: thread, session: session }); + + toFocus = getStackFrameThreadAndSessionToFocus(model, undefined, runningThread); + assert.deepEqual(toFocus, { stackFrame: undefined, thread: runningThread, session: session }); + + const stackFrame = new StackFrame(thread, 5, undefined!, 'stackframename2', undefined, undefined!, 1); + toFocus = getStackFrameThreadAndSessionToFocus(model, stackFrame); + assert.deepEqual(toFocus, { stackFrame: stackFrame, thread: thread, session: session }); + }); }); diff --git a/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts index 258fe2a8f3cc0..241c899eed6c1 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts @@ -51,6 +51,7 @@ suite('Debug - Hover', () => { }(session, 1, scope, 2, 'A', 'A', undefined!, 0, 0, {}, 'string'); variableB = new Variable(session, 1, scope, 2, 'B', 'A.B', undefined!, 0, 0, {}, 'string'); + assert.equal(await findExpressionInStackFrame(stackFrame, []), undefined); assert.equal(await findExpressionInStackFrame(stackFrame, ['A']), variableA); assert.equal(await findExpressionInStackFrame(stackFrame, ['doesNotExist', 'no']), undefined); assert.equal(await findExpressionInStackFrame(stackFrame, ['a']), undefined); From 98ba603bd8aa4021ec6ecf2b8bd44136577cd8d8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 10 Jan 2020 18:18:43 +0100 Subject: [PATCH 163/315] auto save - directly operate on working copies --- .../browser/parts/editor/editorAutoSave.ts | 14 +++++++++++--- .../workingCopy/common/workingCopyService.ts | 17 +++++++++++------ .../test/common/workingCopyService.test.ts | 4 ++++ 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts index 3a434d37c5de4..fd7bbf92a66f6 100644 --- a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts +++ b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts @@ -7,7 +7,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Disposable, DisposableStore, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { IFilesConfigurationService, AutoSaveMode, IAutoSaveConfiguration } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { SaveReason, IEditorIdentifier, IEditorInput, GroupIdentifier } from 'vs/workbench/common/editor'; +import { SaveReason, IEditorIdentifier, IEditorInput, GroupIdentifier, ISaveOptions } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { withNullAsUndefined } from 'vs/base/common/types'; @@ -95,7 +95,7 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution if (editorIdentifier) { this.editorService.save(editorIdentifier, { reason }); } else { - this.editorService.saveAll({ reason }); + this.saveAllDirty({ reason }); } } } @@ -122,11 +122,19 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution } if (reason) { - this.editorService.saveAll({ reason }); + this.saveAllDirty({ reason }); } } } + private saveAllDirty(options?: ISaveOptions): void { + Promise.all(this.workingCopyService.workingCopies.map(workingCopy => { + if (workingCopy.isDirty() && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled)) { + workingCopy.save(options); + } + })); + } + private onDidWorkingCopyChangeDirty(workingCopy: IWorkingCopy): void { if (typeof this.autoSaveAfterDelay !== 'number') { return; // auto save after delay must be enabled diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts index d7a2b403a5f98..e79533e733361 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts @@ -8,7 +8,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { Disposable, IDisposable, toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; -import { TernarySearchTree } from 'vs/base/common/map'; +import { TernarySearchTree, values } from 'vs/base/common/map'; import { ISaveOptions } from 'vs/workbench/common/editor'; export const enum WorkingCopyCapabilities { @@ -50,6 +50,7 @@ export interface IWorkingCopyService { _serviceBrand: undefined; + //#region Dirty Tracking readonly onDidChangeDirty: Event; @@ -65,6 +66,8 @@ export interface IWorkingCopyService { //#region Registry + readonly workingCopies: IWorkingCopy[]; + registerWorkingCopy(workingCopy: IWorkingCopy): IDisposable; //#endregion @@ -93,7 +96,7 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic } get hasDirty(): boolean { - for (const workingCopy of this.workingCopies) { + for (const workingCopy of this._workingCopies) { if (workingCopy.isDirty()) { return true; } @@ -105,7 +108,7 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic get dirtyCount(): number { let totalDirtyCount = 0; - for (const workingCopy of this.workingCopies) { + for (const workingCopy of this._workingCopies) { if (workingCopy.isDirty()) { totalDirtyCount++; } @@ -120,7 +123,9 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic //#region Registry private mapResourceToWorkingCopy = TernarySearchTree.forPaths>(); - private workingCopies = new Set(); + + get workingCopies(): IWorkingCopy[] { return values(this._workingCopies); } + private _workingCopies = new Set(); registerWorkingCopy(workingCopy: IWorkingCopy): IDisposable { const disposables = new DisposableStore(); @@ -134,7 +139,7 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic workingCopiesForResource.add(workingCopy); - this.workingCopies.add(workingCopy); + this._workingCopies.add(workingCopy); // Dirty Events disposables.add(workingCopy.onDidChangeDirty(() => this._onDidChangeDirty.fire(workingCopy))); @@ -156,7 +161,7 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic this.mapResourceToWorkingCopy.delete(workingCopy.resource.toString()); } - this.workingCopies.delete(workingCopy); + this._workingCopies.delete(workingCopy); // If copy is dirty, ensure to fire an event to signal the dirty change // (a disposed working copy cannot account for being dirty in our model) diff --git a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts index 4bac643a90d18..b929447efd956 100644 --- a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts @@ -61,6 +61,7 @@ suite('WorkingCopyService', () => { assert.equal(service.hasDirty, false); assert.equal(service.dirtyCount, 0); + assert.equal(service.workingCopies.length, 0); assert.equal(service.isDirty(URI.file('/')), false); // resource 1 @@ -68,6 +69,7 @@ suite('WorkingCopyService', () => { const copy1 = new TestWorkingCopy(resource1); const unregister1 = service.registerWorkingCopy(copy1); + assert.equal(service.workingCopies.length, 1); assert.equal(service.dirtyCount, 0); assert.equal(service.isDirty(resource1), false); assert.equal(service.hasDirty, false); @@ -90,6 +92,8 @@ suite('WorkingCopyService', () => { unregister1.dispose(); + assert.equal(service.workingCopies.length, 0); + // resource 2 const resource2 = URI.file('/some/folder/file-dirty.txt'); const copy2 = new TestWorkingCopy(resource2, true); From 76d0b308ededd3a706d26fb1ccc564428248cd29 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 10 Jan 2020 18:27:10 +0100 Subject: [PATCH 164/315] import :lipstick: --- src/vs/workbench/common/editor/resourceEditorInput.ts | 2 +- .../services/workingCopy/test/common/workingCopyService.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index c7f8bbbcb3c45..c97918ed39a00 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -10,7 +10,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; import { basename } from 'vs/base/common/resources'; import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import type { IEditorViewState } from 'vs/editor/common/editorCommon'; +import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; /** diff --git a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts index b929447efd956..ee32a8332624b 100644 --- a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { TestWorkingCopyService } from 'vs/workbench/test/workbenchTestServices'; -import type { ISaveOptions } from 'vs/workbench/common/editor'; +import { ISaveOptions } from 'vs/workbench/common/editor'; suite('WorkingCopyService', () => { From 35d37c78f24ac2ea44a78bc60eb914f6e3b5cd97 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 10 Jan 2020 19:12:56 +0100 Subject: [PATCH 165/315] #85216 Improve settings sync UX --- .../userDataSync/common/userDataSync.ts | 13 ++ .../userDataSync/browser/userDataSync.ts | 144 +++++++++++++++--- 2 files changed, 135 insertions(+), 22 deletions(-) diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index ca76015a8851c..6ee18e5c75662 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -28,6 +28,19 @@ export const DEFAULT_IGNORED_SETTINGS = [ 'sync.enableExtensions', ]; +export interface ISyncConfiguration { + sync: { + enable: boolean, + enableSettings: boolean, + enableKeybindings: boolean, + enableUIState: boolean, + enableExtensions: boolean, + keybindingsPerPlatform: boolean, + ignoredExtensions: string[], + ignoredSettings: string[] + } +} + export function registerConfiguration(): IDisposable { const ignoredSettingsSchemaId = 'vscode://schemas/ignoredSettings'; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 77789580a70e9..07264e336bd3c 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore, ISyncConfiguration } from 'vs/platform/userDataSync/common/userDataSync'; import { localize } from 'vs/nls'; import { Disposable, MutableDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -138,7 +138,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo const enabled = this.configurationService.getValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING); if (enabled) { if (this.authTokenService.status === AuthTokenStatus.SignedOut) { - const handle = this.notificationService.prompt(Severity.Info, localize('ask to sign in', "Please sign in with your {0} account to sync configuration across all your machines", this.userDataSyncStore!.account), + const handle = this.notificationService.prompt(Severity.Info, this.getSignInAndTurnOnDetailString(), [ { label: localize('Sign in', "Sign in"), @@ -179,25 +179,123 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } + private getTurnOnDetailString(): string { + const { enableSettings, enableKeybindings, enableExtensions, enableUIState } = this.configurationService.getValue<{ enableSettings: boolean, enableKeybindings: boolean, enableExtensions: boolean, enableUIState: boolean }>('sync'); + if (enableSettings && enableKeybindings && enableExtensions && enableUIState) { + return localize('turn on sync detail 1', "This will synchronize your settings, keybindings, extensions and other UI state (display language) across all your devices."); + } + if (enableSettings && enableKeybindings && enableExtensions) { + return localize('turn on sync detail 2', "This will synchronize your settings, keybindings and extensions across all your devices."); + } + if (enableSettings && enableKeybindings && enableUIState) { + return localize('turn on sync detail 3', "This will synchronize your settings, keybindings and other UI state (display language) across all your devices."); + } + if (enableSettings && enableExtensions && enableUIState) { + return localize('turn on sync detail 4', "This will synchronize your settings, extensions and other UI state (display language) across all your devices."); + } + if (enableSettings && enableKeybindings) { + return localize('turn on sync detail 5', "This will synchronize your settings and keybindings across all your devices."); + } + if (enableSettings && enableExtensions) { + return localize('turn on sync detail 6', "This will synchronize your settings and extensions across all your devices."); + } + if (enableSettings && enableUIState) { + return localize('turn on sync detail 7', "This will synchronize your settings and UI state (display language) across all your devices."); + } + if (enableKeybindings && enableExtensions) { + return localize('turn on sync detail 8', "This will synchronize your keybindings and extensions across all your devices."); + } + if (enableKeybindings && enableUIState) { + return localize('turn on sync detail 9', "This will synchronize your keybindings and UI state (display language) across all your devices."); + } + if (enableExtensions && enableUIState) { + return localize('turn on sync detail 10', "This will synchronize your extensions and UI state (display language) across all your devices."); + } + if (enableSettings) { + return localize('turn on sync detail 11', "This will synchronize your settings across all your devices."); + } + if (enableKeybindings) { + return localize('turn on sync detail 12', "This will synchronize your keybindings across all your devices."); + } + if (enableExtensions) { + return localize('turn on sync detail 13', "This will synchronize your extensions across all your devices."); + } + if (enableUIState) { + return localize('turn on sync detail 14', "This will synchronize your UI state (display language) across all your devices."); + } + return ''; + } + + private getSignInAndTurnOnDetailString(): string { + const { enableSettings, enableKeybindings, enableExtensions, enableUIState } = this.configurationService.getValue().sync; + if (enableSettings && enableKeybindings && enableExtensions && enableUIState) { + return localize('sign in and turn on sync detail 1', "Please sign in with your {0} account to synchronize your settings, keybindings, extensions and other UI state (display language) across all your devices.", this.userDataSyncStore!.account); + } + if (enableSettings && enableKeybindings && enableExtensions) { + return localize('sign in and turn on sync detail 2', "Please sign in with your {0} account to synchronize your settings, keybindings and extensions across all your devices.", this.userDataSyncStore!.account); + } + if (enableSettings && enableKeybindings && enableUIState) { + return localize('sign in and turn on sync detail 3', "Please sign in with your {0} account to synchronize your settings, keybindings and other UI state (display language) across all your devices.", this.userDataSyncStore!.account); + } + if (enableSettings && enableExtensions && enableUIState) { + return localize('sign in and turn on sync detail 4', "Please sign in with your {0} account to synchronize your settings, extensions and other UI state (display language) across all your devices.", this.userDataSyncStore!.account); + } + if (enableSettings && enableKeybindings) { + return localize('sign in and turn on sync detail 5', "Please sign in with your {0} account to synchronize your settings and keybindings across all your devices.", this.userDataSyncStore!.account); + } + if (enableSettings && enableExtensions) { + return localize('sign in and turn on sync detail 6', "Please sign in with your {0} account to synchronize your settings and extensions across all your devices.", this.userDataSyncStore!.account); + } + if (enableSettings && enableUIState) { + return localize('sign in and turn on sync detail 7', "Please sign in with your {0} account to synchronize your settings and UI state (display language) across all your devices.", this.userDataSyncStore!.account); + } + if (enableKeybindings && enableExtensions) { + return localize('sign in and turn on sync detail 8', "Please sign in with your {0} account to synchronize your keybindings and extensions across all your devices.", this.userDataSyncStore!.account); + } + if (enableKeybindings && enableUIState) { + return localize('sign in and turn on sync detail 9', "Please sign in with your {0} account to synchronize your keybindings and UI state (display language) across all your devices.", this.userDataSyncStore!.account); + } + if (enableExtensions && enableUIState) { + return localize('sign in and turn on sync detail 10', "Please sign in with your {0} account to synchronize your extensions and UI state (display language) across all your devices.", this.userDataSyncStore!.account); + } + if (enableSettings) { + return localize('sign in and turn on sync detail 11', "Please sign in with your {0} account to synchronize your settings across all your devices.", this.userDataSyncStore!.account); + } + if (enableKeybindings) { + return localize('sign in and turn on sync detail 12', "Please sign in with your {0} account to synchronize your keybindings across all your devices.", this.userDataSyncStore!.account); + } + if (enableExtensions) { + return localize('sign in and turn on sync detail 13', "Please sign in with your {0} account to synchronize your extensions across all your devices.", this.userDataSyncStore!.account); + } + if (enableUIState) { + return localize('sign in and turn on sync detail 14', "Please sign in with your {0} account to synchronize your UI state (display language) across all your devices.", this.userDataSyncStore!.account); + } + return ''; + } + private async turnOn(): Promise { - if (this.authTokenService.status === AuthTokenStatus.SignedOut) { - const result = await this.dialogService.confirm({ - type: 'info', - message: localize('sign in to account', "Sign in to {0}", this.userDataSyncStore!.name), - detail: localize('ask to sign in', "Please sign in with your {0} account to sync configuration across all your machines", this.userDataSyncStore!.account), - primaryButton: localize('Sign in', "Sign in") - }); - if (!result.confirmed) { - return; - } + const message = localize('turn on sync', "Turn on Sync"); + let detail: string, primaryButton: string; + if (this.authTokenService.status === AuthTokenStatus.SignedIn) { + detail = this.getTurnOnDetailString(); + primaryButton = localize('turn on sync', "Turn on Sync"); + } else { + detail = this.getSignInAndTurnOnDetailString(); + primaryButton = localize('sign in and turn on sync', "Sign in & Turn on Sync"); + } + const result = await this.dialogService.show(Severity.Info, message, [primaryButton, localize('cancel', "Cancel"), localize('configure', "Configure")], { detail, cancelId: 1 }); + switch (result.choice) { + case 1: return; + case 2: await this.configureSyncOptions(); return this.turnOn(); + } + if (this.authTokenService.status !== AuthTokenStatus.SignedIn) { await this.signIn(); } - await this.configureSyncOptions(); await this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, true); this.notificationService.info(localize('Sync Started', "Sync Started.")); } - private async configureSyncOptions(): Promise { + private async configureSyncOptions(): Promise { return new Promise((c, e) => { const disposables: DisposableStore = new DisposableStore(); const quickPick = this.quickInputService.createQuickPick(); @@ -222,15 +320,17 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }]; quickPick.items = items; quickPick.selectedItems = items.filter(item => this.configurationService.getValue(item.id)); - disposables.add(quickPick.onDidAccept(() => { - for (const item of items) { - const wasEnabled = this.configurationService.getValue(item.id); - const isEnabled = !!quickPick.selectedItems.filter(selected => selected.id === item.id)[0]; - if (wasEnabled !== isEnabled) { - this.configurationService.updateValue(item.id!, isEnabled); + disposables.add(quickPick.onDidAccept(async () => { + if (quickPick.selectedItems.length) { + for (const item of items) { + const wasEnabled = this.configurationService.getValue(item.id); + const isEnabled = !!quickPick.selectedItems.filter(selected => selected.id === item.id)[0]; + if (wasEnabled !== isEnabled) { + await this.configurationService.updateValue(item.id!, isEnabled); + } } + quickPick.hide(); } - quickPick.hide(); })); disposables.add(quickPick.onDidHide(() => { disposables.dispose(); @@ -333,7 +433,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: turnOnSyncCommandId, - title: localize('turn on sync', "Sync: Turn on sync...") + title: localize('turn on sync...', "Sync: Turn on sync...") }, when: turnOnSyncWhenContext, }); From 7879818f9c54aa63b9887ed96f53671d29bbc3fe Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Fri, 10 Jan 2020 10:17:45 -0800 Subject: [PATCH 166/315] Fix circular dependency issue --- .../search/browser/search.contribution.ts | 3 +- .../contrib/search/browser/searchActions.ts | 2 +- .../contrib/search/browser/searchEditor.ts | 416 +---------------- .../search/browser/searchEditorCommands.ts | 434 ++++++++++++++++++ .../contrib/search/browser/searchView.ts | 2 +- 5 files changed, 444 insertions(+), 413 deletions(-) create mode 100644 src/vs/workbench/contrib/search/browser/searchEditorCommands.ts diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index ab59270e380cd..2e499c9e06da7 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -57,7 +57,8 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { assertType } from 'vs/base/common/types'; import { SearchViewPaneContainer } from 'vs/workbench/contrib/search/browser/searchViewlet'; import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; -import { SearchEditor, SearchEditorInput } from 'vs/workbench/contrib/search/browser/searchEditor'; +import { SearchEditorInput } from 'vs/workbench/contrib/search/browser/searchEditorCommands'; +import { SearchEditor } from 'vs/workbench/contrib/search/browser/searchEditor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; registerSingleton(ISearchWorkbenchService, SearchWorkbenchService, true); diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index 3be9ac8273743..2839f77556873 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -29,7 +29,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { SearchViewPaneContainer } from 'vs/workbench/contrib/search/browser/searchViewlet'; import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel'; import { ITreeNavigator } from 'vs/base/browser/ui/tree/tree'; -import { createEditorFromSearchResult, refreshActiveEditorSearch, openNewSearchEditor } from 'vs/workbench/contrib/search/browser/searchEditor'; +import { createEditorFromSearchResult, refreshActiveEditorSearch, openNewSearchEditor } from 'vs/workbench/contrib/search/browser/searchEditorCommands'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index 83b746109a586..8e7e247f519b5 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -5,21 +5,16 @@ import * as DOM from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { coalesce, flatten } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import * as network from 'vs/base/common/network'; -import { repeat } from 'vs/base/common/strings'; import { assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/searchEditor'; -import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; import type { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; -import { EndOfLinePreference, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { TrackedRangeStickiness } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; import { localize } from 'vs/nls'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -28,84 +23,25 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ILabelService } from 'vs/platform/label/common/label'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { searchEditorFindMatch, searchEditorFindMatchBorder } from 'vs/platform/theme/common/colorRegistry'; -import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; -import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; import { ExcludePatternInputWidget, PatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget'; import { SearchWidget } from 'vs/workbench/contrib/search/browser/searchWidget'; import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search'; -import { FileMatch, Match, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { SearchModel } from 'vs/workbench/contrib/search/common/searchModel'; import { IPatternInfo, ISearchConfigurationProperties, ITextQuery } from 'vs/workbench/services/search/common/search'; import { Delayer } from 'vs/base/common/async'; +import { serializeSearchResultForEditor, SearchConfiguration, SearchEditorInput } from 'vs/workbench/contrib/search/browser/searchEditorCommands'; const RESULT_LINE_REGEX = /^(\s+)(\d+)(:| )(\s+)(.*)$/; -type SearchConfiguration = { - query: string, - includes: string, - excludes: string, - contextLines: number, - wholeWord: boolean, - caseSensitive: boolean, - regexp: boolean, - useIgnores: boolean -}; - -let searchEditorInputInstances = 0; -export class SearchEditorInput extends EditorInput { - static readonly ID: string = 'workbench.editorinputs.searchEditorInput'; - - public config: SearchConfiguration; - private instanceNumber: number = searchEditorInputInstances++; - - constructor( - config: SearchConfiguration | undefined, - @IModelService private readonly modelService: IModelService, - @IModeService private readonly modeService: IModeService, - ) { - super(); - - if (config === undefined) { - this.config = { query: '', includes: '', excludes: '', contextLines: 0, wholeWord: false, caseSensitive: false, regexp: false, useIgnores: true }; - } else { - this.config = config; - } - - const searchResultMode = this.modeService.create('search-result'); - this.modelService.createModel('', searchResultMode, this.getResource()); - } - - getTypeId(): string { - return SearchEditorInput.ID; - } - - getResource(): URI { - return URI.from({ scheme: 'code-search', fragment: `${this.instanceNumber}` }); - } - - getName(): string { - return this.config.query ? localize('searchTitle.withQuery', "Search: {0}", this.config.query) : localize('searchTitle', "Search"); - } - - setConfig(config: SearchConfiguration) { - this.config = config; - this._onDidChangeLabel.fire(); - } +// Using \r\n on Windows inserts an extra newline between results. +const lineDelimiter = '\n'; - async resolve() { - return null; - } - dispose() { - this.modelService.destroyModel(this.getResource()); - super.dispose(); - } -} export class SearchEditor extends BaseEditor { static readonly ID: string = 'workbench.editor.searchEditor'; @@ -353,343 +289,3 @@ export class SearchEditor extends BaseEditor { return this.searchResultEditor.getModel(); } } - -// Using \r\n on Windows inserts an extra newline between results. -const lineDelimiter = '\n'; - -const translateRangeLines = - (n: number) => - (range: Range) => - new Range(range.startLineNumber + n, range.startColumn, range.endLineNumber + n, range.endColumn); - -const matchToSearchResultFormat = (match: Match): { line: string, ranges: Range[], lineNumber: string }[] => { - const getLinePrefix = (i: number) => `${match.range().startLineNumber + i}`; - - const fullMatchLines = match.fullPreviewLines(); - const largestPrefixSize = fullMatchLines.reduce((largest, _, i) => Math.max(getLinePrefix(i).length, largest), 0); - - - const results: { line: string, ranges: Range[], lineNumber: string }[] = []; - - fullMatchLines - .forEach((sourceLine, i) => { - const lineNumber = getLinePrefix(i); - const paddingStr = repeat(' ', largestPrefixSize - lineNumber.length); - const prefix = ` ${lineNumber}: ${paddingStr}`; - const prefixOffset = prefix.length; - - const line = (prefix + sourceLine).replace(/\r?\n?$/, ''); - - const rangeOnThisLine = ({ start, end }: { start?: number; end?: number; }) => new Range(1, (start ?? 1) + prefixOffset, 1, (end ?? sourceLine.length + 1) + prefixOffset); - - const matchRange = match.range(); - const matchIsSingleLine = matchRange.startLineNumber === matchRange.endLineNumber; - - let lineRange; - if (matchIsSingleLine) { lineRange = (rangeOnThisLine({ start: matchRange.startColumn, end: matchRange.endColumn })); } - else if (i === 0) { lineRange = (rangeOnThisLine({ start: matchRange.startColumn })); } - else if (i === fullMatchLines.length - 1) { lineRange = (rangeOnThisLine({ end: matchRange.endColumn })); } - else { lineRange = (rangeOnThisLine({})); } - - results.push({ lineNumber: lineNumber, line, ranges: [lineRange] }); - }); - - return results; -}; - -type SearchResultSerialization = { text: string[], matchRanges: Range[] }; - -function fileMatchToSearchResultFormat(fileMatch: FileMatch, labelFormatter: (x: URI) => string): SearchResultSerialization { - const serializedMatches = flatten(fileMatch.matches() - .sort(searchMatchComparer) - .map(match => matchToSearchResultFormat(match))); - - const uriString = labelFormatter(fileMatch.resource); - let text: string[] = [`${uriString}:`]; - let matchRanges: Range[] = []; - - const targetLineNumberToOffset: Record = {}; - - const context: { line: string, lineNumber: number }[] = []; - fileMatch.context.forEach((line, lineNumber) => context.push({ line, lineNumber })); - context.sort((a, b) => a.lineNumber - b.lineNumber); - - let lastLine: number | undefined = undefined; - - const seenLines = new Set(); - serializedMatches.forEach(match => { - if (!seenLines.has(match.line)) { - while (context.length && context[0].lineNumber < +match.lineNumber) { - const { line, lineNumber } = context.shift()!; - if (lastLine !== undefined && lineNumber !== lastLine + 1) { - text.push(''); - } - text.push(` ${lineNumber} ${line}`); - lastLine = lineNumber; - } - - targetLineNumberToOffset[match.lineNumber] = text.length; - seenLines.add(match.line); - text.push(match.line); - lastLine = +match.lineNumber; - } - - matchRanges.push(...match.ranges.map(translateRangeLines(targetLineNumberToOffset[match.lineNumber]))); - }); - - while (context.length) { - const { line, lineNumber } = context.shift()!; - text.push(` ${lineNumber} ${line}`); - } - - return { text, matchRanges }; -} - -const flattenSearchResultSerializations = (serializations: SearchResultSerialization[]): SearchResultSerialization => { - let text: string[] = []; - let matchRanges: Range[] = []; - - serializations.forEach(serialized => { - serialized.matchRanges.map(translateRangeLines(text.length)).forEach(range => matchRanges.push(range)); - serialized.text.forEach(line => text.push(line)); - text.push(''); // new line - }); - - return { text, matchRanges }; -}; - -const contentPatternToSearchResultHeader = (pattern: ITextQuery | null, includes: string, excludes: string, contextLines: number): string[] => { - if (!pattern) { return []; } - - const removeNullFalseAndUndefined = (a: (T | null | false | undefined)[]) => a.filter(a => a !== false && a !== null && a !== undefined) as T[]; - - const escapeNewlines = (str: string) => str.replace(/\\/g, '\\\\').replace(/\n/g, '\\n'); - - return removeNullFalseAndUndefined([ - `# Query: ${escapeNewlines(pattern.contentPattern.pattern)}`, - - (pattern.contentPattern.isCaseSensitive || pattern.contentPattern.isWordMatch || pattern.contentPattern.isRegExp || pattern.userDisabledExcludesAndIgnoreFiles) - && `# Flags: ${coalesce([ - pattern.contentPattern.isCaseSensitive && 'CaseSensitive', - pattern.contentPattern.isWordMatch && 'WordMatch', - pattern.contentPattern.isRegExp && 'RegExp', - pattern.userDisabledExcludesAndIgnoreFiles && 'IgnoreExcludeSettings' - ]).join(' ')}`, - includes ? `# Including: ${includes}` : undefined, - excludes ? `# Excluding: ${excludes}` : undefined, - contextLines ? `# ContextLines: ${contextLines}` : undefined, - '' - ]); -}; - - -type SearchHeader = { - pattern: string; - flags: { - regex: boolean; - wholeWord: boolean; - caseSensitive: boolean; - ignoreExcludes: boolean; - }; - includes: string; - excludes: string; - context: number | undefined; -}; - -const searchHeaderToContentPattern = (header: string[]): SearchHeader => { - const query: SearchHeader = { - pattern: '', - flags: { regex: false, caseSensitive: false, ignoreExcludes: false, wholeWord: false }, - includes: '', - excludes: '', - context: undefined - }; - - const unescapeNewlines = (str: string) => { - let out = ''; - for (let i = 0; i < str.length; i++) { - if (str[i] === '\\') { - i++; - const escaped = str[i]; - - if (escaped === 'n') { - out += '\n'; - } - else if (escaped === '\\') { - out += '\\'; - } - else { - throw Error(localize('invalidQueryStringError', "All backslashes in Query string must be escaped (\\\\)")); - } - } else { - out += str[i]; - } - } - return out; - }; - const parseYML = /^# ([^:]*): (.*)$/; - for (const line of header) { - const parsed = parseYML.exec(line); - if (!parsed) { continue; } - const [, key, value] = parsed; - switch (key) { - case 'Query': query.pattern = unescapeNewlines(value); break; - case 'Including': query.includes = value; break; - case 'Excluding': query.excludes = value; break; - case 'ContextLines': query.context = +value; break; - case 'Flags': { - query.flags = { - regex: value.indexOf('RegExp') !== -1, - caseSensitive: value.indexOf('CaseSensitive') !== -1, - ignoreExcludes: value.indexOf('IgnoreExcludeSettings') !== -1, - wholeWord: value.indexOf('WordMatch') !== -1 - }; - } - } - } - - return query; -}; - -const serializeSearchResultForEditor = (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, contextLines: number, labelFormatter: (x: URI) => string, includeHeader: boolean): SearchResultSerialization => { - const header = includeHeader ? contentPatternToSearchResultHeader(searchResult.query, rawIncludePattern, rawExcludePattern, contextLines) : []; - const allResults = - flattenSearchResultSerializations( - flatten( - searchResult.folderMatches().sort(searchMatchComparer) - .map(folderMatch => folderMatch.matches().sort(searchMatchComparer) - .map(fileMatch => fileMatchToSearchResultFormat(fileMatch, labelFormatter))))); - - return { matchRanges: allResults.matchRanges.map(translateRangeLines(header.length)), text: header.concat(allResults.text) }; -}; - -export const refreshActiveEditorSearch = - async (contextLines: number | undefined, editorService: IEditorService, instantiationService: IInstantiationService, contextService: IWorkspaceContextService, labelService: ILabelService, configurationService: IConfigurationService) => { - const editorWidget = editorService.activeTextEditorWidget; - if (!isCodeEditor(editorWidget)) { - return; - } - - const textModel = editorWidget.getModel(); - if (!textModel) { return; } - - const header = textModel.getValueInRange(new Range(1, 1, 5, 1), EndOfLinePreference.LF) - .split(lineDelimiter) - .filter(line => line.indexOf('# ') === 0); - - const contentPattern = searchHeaderToContentPattern(header); - - const content: IPatternInfo = { - pattern: contentPattern.pattern, - isRegExp: contentPattern.flags.regex, - isCaseSensitive: contentPattern.flags.caseSensitive, - isWordMatch: contentPattern.flags.wholeWord - }; - - contextLines = contextLines ?? contentPattern.context ?? 0; - - const options: ITextQueryBuilderOptions = { - _reason: 'searchEditor', - extraFileResources: instantiationService.invokeFunction(getOutOfWorkspaceEditorResources), - maxResults: 10000, - disregardIgnoreFiles: contentPattern.flags.ignoreExcludes, - disregardExcludeSettings: contentPattern.flags.ignoreExcludes, - excludePattern: contentPattern.excludes, - includePattern: contentPattern.includes, - previewOptions: { - matchLines: 1, - charsPerLine: 1000 - }, - afterContext: contextLines, - beforeContext: contextLines, - isSmartCase: configurationService.getValue('search').smartCase, - expandPatterns: true - }; - - const folderResources = contextService.getWorkspace().folders; - - let query: ITextQuery; - try { - const queryBuilder = instantiationService.createInstance(QueryBuilder); - query = queryBuilder.text(content, folderResources.map(folder => folder.uri), options); - } catch (err) { - return; - } - - const searchModel = instantiationService.createInstance(SearchModel); - await searchModel.search(query); - - const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); - const results = serializeSearchResultForEditor(searchModel.searchResult, contentPattern.includes, contentPattern.excludes, contextLines, labelFormatter, false); - - textModel.setValue(results.text.join(lineDelimiter)); - textModel.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); - }; - -export const openNewSearchEditor = - async (editorService: IEditorService, instantiationService: IInstantiationService) => { - await editorService.openEditor(instantiationService.createInstance(SearchEditorInput, undefined), { pinned: true }); - }; - -export const createEditorFromSearchResult = - async (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, labelService: ILabelService, editorService: IEditorService, instantiationService: IInstantiationService) => { - if (!searchResult.query) { - console.error('Expected searchResult.query to be defined. Got', searchResult); - return; - } - - const searchTerm = searchResult.query.contentPattern.pattern.replace(/[^\w-_. ]/g, '') || 'Search'; - - const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); - - const results = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, 0, labelFormatter, false); - const contents = results.text.join(lineDelimiter); - let possible = { - contents, - mode: 'search-result', - resource: URI.from({ scheme: network.Schemas.untitled, path: searchTerm }) - }; - - let id = 0; - - let existing = editorService.getOpened(possible); - while (existing) { - if (existing instanceof UntitledTextEditorInput) { - const model = await existing.resolve(); - const existingContents = model.textEditorModel.getValue(EndOfLinePreference.LF); - if (existingContents === contents) { - break; - } - } - possible.resource = possible.resource.with({ path: searchTerm + '-' + ++id }); - existing = editorService.getOpened(possible); - } - - const editor = await editorService.openEditor( - instantiationService.createInstance( - SearchEditorInput, - { - query: searchResult.query.contentPattern.pattern, - regexp: !!searchResult.query.contentPattern.isRegExp, - caseSensitive: !!searchResult.query.contentPattern.isCaseSensitive, - wholeWord: !!searchResult.query.contentPattern.isWordMatch, - includes: rawIncludePattern, - excludes: rawExcludePattern, - contextLines: 0, - useIgnores: !searchResult.query.userDisabledExcludesAndIgnoreFiles, - }), - { pinned: true }) as SearchEditor; - - const model = assertIsDefined(editor.getModel()); - model.setValue(contents); - model.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); - }; - -registerThemingParticipant((theme, collector) => { - collector.addRule(`.monaco-editor .searchEditorFindMatch { background-color: ${theme.getColor(searchEditorFindMatch)}; }`); - - const findMatchHighlightBorder = theme.getColor(searchEditorFindMatchBorder); - if (findMatchHighlightBorder) { - collector.addRule(`.monaco-editor .searchEditorFindMatch { border: 1px ${theme.type === 'hc' ? 'dotted' : 'solid'} ${findMatchHighlightBorder}; box-sizing: border-box; }`); - } -}); diff --git a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts new file mode 100644 index 0000000000000..d6735a62c1254 --- /dev/null +++ b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts @@ -0,0 +1,434 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { coalesce, flatten } from 'vs/base/common/arrays'; +import * as network from 'vs/base/common/network'; +import { repeat } from 'vs/base/common/strings'; +import { assertIsDefined } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import 'vs/css!./media/searchEditor'; +import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { Range } from 'vs/editor/common/core/range'; +import { EndOfLinePreference, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { searchEditorFindMatch, searchEditorFindMatchBorder } from 'vs/platform/theme/common/colorRegistry'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; +import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search'; +import { FileMatch, Match, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IPatternInfo, ISearchConfigurationProperties, ITextQuery } from 'vs/workbench/services/search/common/search'; +import { EditorInput } from 'vs/workbench/common/editor'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { SearchEditor } from 'vs/workbench/contrib/search/browser/searchEditor'; + + +export type SearchConfiguration = { + query: string, + includes: string, + excludes: string, + contextLines: number, + wholeWord: boolean, + caseSensitive: boolean, + regexp: boolean, + useIgnores: boolean +}; + +let searchEditorInputInstances = 0; +export class SearchEditorInput extends EditorInput { + static readonly ID: string = 'workbench.editorinputs.searchEditorInput'; + + public config: SearchConfiguration; + private instanceNumber: number = searchEditorInputInstances++; + + constructor( + config: SearchConfiguration | undefined, + @IModelService private readonly modelService: IModelService, + @IModeService private readonly modeService: IModeService, + ) { + super(); + + if (config === undefined) { + this.config = { query: '', includes: '', excludes: '', contextLines: 0, wholeWord: false, caseSensitive: false, regexp: false, useIgnores: true }; + } else { + this.config = config; + } + + const searchResultMode = this.modeService.create('search-result'); + this.modelService.createModel('', searchResultMode, this.getResource()); + } + + getTypeId(): string { + return SearchEditorInput.ID; + } + + getResource(): URI { + return URI.from({ scheme: 'code-search', fragment: `${this.instanceNumber}` }); + } + + getName(): string { + return this.config.query ? localize('searchTitle.withQuery', "Search: {0}", this.config.query) : localize('searchTitle', "Search"); + } + + setConfig(config: SearchConfiguration) { + this.config = config; + this._onDidChangeLabel.fire(); + } + + async resolve() { + return null; + } + + dispose() { + this.modelService.destroyModel(this.getResource()); + super.dispose(); + } +} + +// Using \r\n on Windows inserts an extra newline between results. +const lineDelimiter = '\n'; + +const translateRangeLines = + (n: number) => + (range: Range) => + new Range(range.startLineNumber + n, range.startColumn, range.endLineNumber + n, range.endColumn); + +const matchToSearchResultFormat = (match: Match): { line: string, ranges: Range[], lineNumber: string }[] => { + const getLinePrefix = (i: number) => `${match.range().startLineNumber + i}`; + + const fullMatchLines = match.fullPreviewLines(); + const largestPrefixSize = fullMatchLines.reduce((largest, _, i) => Math.max(getLinePrefix(i).length, largest), 0); + + + const results: { line: string, ranges: Range[], lineNumber: string }[] = []; + + fullMatchLines + .forEach((sourceLine, i) => { + const lineNumber = getLinePrefix(i); + const paddingStr = repeat(' ', largestPrefixSize - lineNumber.length); + const prefix = ` ${lineNumber}: ${paddingStr}`; + const prefixOffset = prefix.length; + + const line = (prefix + sourceLine).replace(/\r?\n?$/, ''); + + const rangeOnThisLine = ({ start, end }: { start?: number; end?: number; }) => new Range(1, (start ?? 1) + prefixOffset, 1, (end ?? sourceLine.length + 1) + prefixOffset); + + const matchRange = match.range(); + const matchIsSingleLine = matchRange.startLineNumber === matchRange.endLineNumber; + + let lineRange; + if (matchIsSingleLine) { lineRange = (rangeOnThisLine({ start: matchRange.startColumn, end: matchRange.endColumn })); } + else if (i === 0) { lineRange = (rangeOnThisLine({ start: matchRange.startColumn })); } + else if (i === fullMatchLines.length - 1) { lineRange = (rangeOnThisLine({ end: matchRange.endColumn })); } + else { lineRange = (rangeOnThisLine({})); } + + results.push({ lineNumber: lineNumber, line, ranges: [lineRange] }); + }); + + return results; +}; + +type SearchResultSerialization = { text: string[], matchRanges: Range[] }; + +function fileMatchToSearchResultFormat(fileMatch: FileMatch, labelFormatter: (x: URI) => string): SearchResultSerialization { + const serializedMatches = flatten(fileMatch.matches() + .sort(searchMatchComparer) + .map(match => matchToSearchResultFormat(match))); + + const uriString = labelFormatter(fileMatch.resource); + let text: string[] = [`${uriString}:`]; + let matchRanges: Range[] = []; + + const targetLineNumberToOffset: Record = {}; + + const context: { line: string, lineNumber: number }[] = []; + fileMatch.context.forEach((line, lineNumber) => context.push({ line, lineNumber })); + context.sort((a, b) => a.lineNumber - b.lineNumber); + + let lastLine: number | undefined = undefined; + + const seenLines = new Set(); + serializedMatches.forEach(match => { + if (!seenLines.has(match.line)) { + while (context.length && context[0].lineNumber < +match.lineNumber) { + const { line, lineNumber } = context.shift()!; + if (lastLine !== undefined && lineNumber !== lastLine + 1) { + text.push(''); + } + text.push(` ${lineNumber} ${line}`); + lastLine = lineNumber; + } + + targetLineNumberToOffset[match.lineNumber] = text.length; + seenLines.add(match.line); + text.push(match.line); + lastLine = +match.lineNumber; + } + + matchRanges.push(...match.ranges.map(translateRangeLines(targetLineNumberToOffset[match.lineNumber]))); + }); + + while (context.length) { + const { line, lineNumber } = context.shift()!; + text.push(` ${lineNumber} ${line}`); + } + + return { text, matchRanges }; +} + +const flattenSearchResultSerializations = (serializations: SearchResultSerialization[]): SearchResultSerialization => { + let text: string[] = []; + let matchRanges: Range[] = []; + + serializations.forEach(serialized => { + serialized.matchRanges.map(translateRangeLines(text.length)).forEach(range => matchRanges.push(range)); + serialized.text.forEach(line => text.push(line)); + text.push(''); // new line + }); + + return { text, matchRanges }; +}; + +const contentPatternToSearchResultHeader = (pattern: ITextQuery | null, includes: string, excludes: string, contextLines: number): string[] => { + if (!pattern) { return []; } + + const removeNullFalseAndUndefined = (a: (T | null | false | undefined)[]) => a.filter(a => a !== false && a !== null && a !== undefined) as T[]; + + const escapeNewlines = (str: string) => str.replace(/\\/g, '\\\\').replace(/\n/g, '\\n'); + + return removeNullFalseAndUndefined([ + `# Query: ${escapeNewlines(pattern.contentPattern.pattern)}`, + + (pattern.contentPattern.isCaseSensitive || pattern.contentPattern.isWordMatch || pattern.contentPattern.isRegExp || pattern.userDisabledExcludesAndIgnoreFiles) + && `# Flags: ${coalesce([ + pattern.contentPattern.isCaseSensitive && 'CaseSensitive', + pattern.contentPattern.isWordMatch && 'WordMatch', + pattern.contentPattern.isRegExp && 'RegExp', + pattern.userDisabledExcludesAndIgnoreFiles && 'IgnoreExcludeSettings' + ]).join(' ')}`, + includes ? `# Including: ${includes}` : undefined, + excludes ? `# Excluding: ${excludes}` : undefined, + contextLines ? `# ContextLines: ${contextLines}` : undefined, + '' + ]); +}; + + +type SearchHeader = { + pattern: string; + flags: { + regex: boolean; + wholeWord: boolean; + caseSensitive: boolean; + ignoreExcludes: boolean; + }; + includes: string; + excludes: string; + context: number | undefined; +}; + +const searchHeaderToContentPattern = (header: string[]): SearchHeader => { + const query: SearchHeader = { + pattern: '', + flags: { regex: false, caseSensitive: false, ignoreExcludes: false, wholeWord: false }, + includes: '', + excludes: '', + context: undefined + }; + + const unescapeNewlines = (str: string) => { + let out = ''; + for (let i = 0; i < str.length; i++) { + if (str[i] === '\\') { + i++; + const escaped = str[i]; + + if (escaped === 'n') { + out += '\n'; + } + else if (escaped === '\\') { + out += '\\'; + } + else { + throw Error(localize('invalidQueryStringError', "All backslashes in Query string must be escaped (\\\\)")); + } + } else { + out += str[i]; + } + } + return out; + }; + const parseYML = /^# ([^:]*): (.*)$/; + for (const line of header) { + const parsed = parseYML.exec(line); + if (!parsed) { continue; } + const [, key, value] = parsed; + switch (key) { + case 'Query': query.pattern = unescapeNewlines(value); break; + case 'Including': query.includes = value; break; + case 'Excluding': query.excludes = value; break; + case 'ContextLines': query.context = +value; break; + case 'Flags': { + query.flags = { + regex: value.indexOf('RegExp') !== -1, + caseSensitive: value.indexOf('CaseSensitive') !== -1, + ignoreExcludes: value.indexOf('IgnoreExcludeSettings') !== -1, + wholeWord: value.indexOf('WordMatch') !== -1 + }; + } + } + } + + return query; +}; + +export const serializeSearchResultForEditor = (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, contextLines: number, labelFormatter: (x: URI) => string, includeHeader: boolean): SearchResultSerialization => { + const header = includeHeader ? contentPatternToSearchResultHeader(searchResult.query, rawIncludePattern, rawExcludePattern, contextLines) : []; + const allResults = + flattenSearchResultSerializations( + flatten( + searchResult.folderMatches().sort(searchMatchComparer) + .map(folderMatch => folderMatch.matches().sort(searchMatchComparer) + .map(fileMatch => fileMatchToSearchResultFormat(fileMatch, labelFormatter))))); + + return { matchRanges: allResults.matchRanges.map(translateRangeLines(header.length)), text: header.concat(allResults.text) }; +}; + +export const refreshActiveEditorSearch = + async (contextLines: number | undefined, editorService: IEditorService, instantiationService: IInstantiationService, contextService: IWorkspaceContextService, labelService: ILabelService, configurationService: IConfigurationService) => { + const editorWidget = editorService.activeTextEditorWidget; + if (!isCodeEditor(editorWidget)) { + return; + } + + const textModel = editorWidget.getModel(); + if (!textModel) { return; } + + const header = textModel.getValueInRange(new Range(1, 1, 5, 1), EndOfLinePreference.LF) + .split(lineDelimiter) + .filter(line => line.indexOf('# ') === 0); + + const contentPattern = searchHeaderToContentPattern(header); + + const content: IPatternInfo = { + pattern: contentPattern.pattern, + isRegExp: contentPattern.flags.regex, + isCaseSensitive: contentPattern.flags.caseSensitive, + isWordMatch: contentPattern.flags.wholeWord + }; + + contextLines = contextLines ?? contentPattern.context ?? 0; + + const options: ITextQueryBuilderOptions = { + _reason: 'searchEditor', + extraFileResources: instantiationService.invokeFunction(getOutOfWorkspaceEditorResources), + maxResults: 10000, + disregardIgnoreFiles: contentPattern.flags.ignoreExcludes, + disregardExcludeSettings: contentPattern.flags.ignoreExcludes, + excludePattern: contentPattern.excludes, + includePattern: contentPattern.includes, + previewOptions: { + matchLines: 1, + charsPerLine: 1000 + }, + afterContext: contextLines, + beforeContext: contextLines, + isSmartCase: configurationService.getValue('search').smartCase, + expandPatterns: true + }; + + const folderResources = contextService.getWorkspace().folders; + + let query: ITextQuery; + try { + const queryBuilder = instantiationService.createInstance(QueryBuilder); + query = queryBuilder.text(content, folderResources.map(folder => folder.uri), options); + } catch (err) { + return; + } + + const searchModel = instantiationService.createInstance(SearchModel); + await searchModel.search(query); + + const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); + const results = serializeSearchResultForEditor(searchModel.searchResult, contentPattern.includes, contentPattern.excludes, contextLines, labelFormatter, false); + + textModel.setValue(results.text.join(lineDelimiter)); + textModel.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); + }; + +export const openNewSearchEditor = + async (editorService: IEditorService, instantiationService: IInstantiationService) => { + await editorService.openEditor(instantiationService.createInstance(SearchEditorInput, undefined), { pinned: true }); + }; + +export const createEditorFromSearchResult = + async (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, labelService: ILabelService, editorService: IEditorService, instantiationService: IInstantiationService) => { + if (!searchResult.query) { + console.error('Expected searchResult.query to be defined. Got', searchResult); + return; + } + + const searchTerm = searchResult.query.contentPattern.pattern.replace(/[^\w-_. ]/g, '') || 'Search'; + + const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); + + const results = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, 0, labelFormatter, false); + const contents = results.text.join(lineDelimiter); + let possible = { + contents, + mode: 'search-result', + resource: URI.from({ scheme: network.Schemas.untitled, path: searchTerm }) + }; + + let id = 0; + + let existing = editorService.getOpened(possible); + while (existing) { + if (existing instanceof UntitledTextEditorInput) { + const model = await existing.resolve(); + const existingContents = model.textEditorModel.getValue(EndOfLinePreference.LF); + if (existingContents === contents) { + break; + } + } + possible.resource = possible.resource.with({ path: searchTerm + '-' + ++id }); + existing = editorService.getOpened(possible); + } + + const editor = await editorService.openEditor( + instantiationService.createInstance( + SearchEditorInput, + { + query: searchResult.query.contentPattern.pattern, + regexp: !!searchResult.query.contentPattern.isRegExp, + caseSensitive: !!searchResult.query.contentPattern.isCaseSensitive, + wholeWord: !!searchResult.query.contentPattern.isWordMatch, + includes: rawIncludePattern, + excludes: rawExcludePattern, + contextLines: 0, + useIgnores: !searchResult.query.userDisabledExcludesAndIgnoreFiles, + }), + { pinned: true }) as SearchEditor; + + const model = assertIsDefined(editor.getModel()); + model.setValue(contents); + model.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); + }; + +registerThemingParticipant((theme, collector) => { + collector.addRule(`.monaco-editor .searchEditorFindMatch { background-color: ${theme.getColor(searchEditorFindMatch)}; }`); + + const findMatchHighlightBorder = theme.getColor(searchEditorFindMatchBorder); + if (findMatchHighlightBorder) { + collector.addRule(`.monaco-editor .searchEditorFindMatch { border: 1px ${theme.type === 'hc' ? 'dotted' : 'solid'} ${findMatchHighlightBorder}; box-sizing: border-box; }`); + } +}); diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 16ba94358caa0..1a7005618f5f9 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -64,7 +64,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { MultiCursorSelectionController } from 'vs/editor/contrib/multicursor/multicursor'; import { Selection } from 'vs/editor/common/core/selection'; import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; -import { createEditorFromSearchResult } from 'vs/workbench/contrib/search/browser/searchEditor'; +import { createEditorFromSearchResult } from 'vs/workbench/contrib/search/browser/searchEditorCommands'; import { ILabelService } from 'vs/platform/label/common/label'; import { Color, RGBA } from 'vs/base/common/color'; From d2911afe36d9ebe8d70b401d74f3fdec6dfd9988 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 10 Jan 2020 11:28:24 -0800 Subject: [PATCH 167/315] Move handler into CustomEditorInfoStore --- .../customEditor/browser/customEditors.ts | 68 ++++++++++--------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 908c38979fc49..0336f929f0c44 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -30,6 +30,7 @@ import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { CustomFileEditorInput } from './customEditorInput'; +import { Emitter } from 'vs/base/common/event'; export const defaultEditorId = 'default'; const defaultEditorInfo = new CustomEditorInfo({ @@ -41,37 +42,57 @@ const defaultEditorInfo = new CustomEditorInfo({ priority: CustomEditorPriority.default, }); -export class CustomEditorInfoStore { - private readonly contributedEditors = new Map(); +export class CustomEditorInfoStore extends Disposable { - public clear() { - this.contributedEditors.clear(); + private readonly _contributedEditors = new Map(); + + constructor() { + super(); + + webviewEditorsExtensionPoint.setHandler(extensions => { + this._contributedEditors.clear(); + + for (const extension of extensions) { + for (const webviewEditorContribution of extension.value) { + this.add(new CustomEditorInfo({ + id: webviewEditorContribution.viewType, + displayName: webviewEditorContribution.displayName, + selector: webviewEditorContribution.selector || [], + priority: webviewEditorContribution.priority || CustomEditorPriority.default, + })); + } + } + this._onChange.fire(); + }); } + private readonly _onChange = this._register(new Emitter()); + public readonly onChange = this._onChange.event; + public get(viewType: string): CustomEditorInfo | undefined { return viewType === defaultEditorId ? defaultEditorInfo - : this.contributedEditors.get(viewType); + : this._contributedEditors.get(viewType); } - public add(info: CustomEditorInfo): void { - if (info.id === defaultEditorId || this.contributedEditors.has(info.id)) { - console.log(`Custom editor with id '${info.id}' already registered`); - return; - } - this.contributedEditors.set(info.id, info); + public getContributedEditors(resource: URI): readonly CustomEditorInfo[] { + return Array.from(this._contributedEditors.values()) + .filter(customEditor => customEditor.matches(resource)); } - public getContributedEditors(resource: URI): readonly CustomEditorInfo[] { - return Array.from(this.contributedEditors.values()).filter(customEditor => - customEditor.matches(resource)); + private add(info: CustomEditorInfo): void { + if (info.id === defaultEditorId || this._contributedEditors.has(info.id)) { + console.error(`Custom editor with id '${info.id}' already registered`); + return; + } + this._contributedEditors.set(info.id, info); } } export class CustomEditorService extends Disposable implements ICustomEditorService { _serviceBrand: any; - private readonly _editorInfoStore = new CustomEditorInfoStore(); + private readonly _editorInfoStore = this._register(new CustomEditorInfoStore()); private readonly _models: CustomEditorModelManager; @@ -94,26 +115,11 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ this._models = new CustomEditorModelManager(workingCopyService); - webviewEditorsExtensionPoint.setHandler(extensions => { - this._editorInfoStore.clear(); - - for (const extension of extensions) { - for (const webviewEditorContribution of extension.value) { - this._editorInfoStore.add(new CustomEditorInfo({ - id: webviewEditorContribution.viewType, - displayName: webviewEditorContribution.displayName, - selector: webviewEditorContribution.selector || [], - priority: webviewEditorContribution.priority || CustomEditorPriority.default, - })); - } - } - this.updateContexts(); - }); - this._hasCustomEditor = CONTEXT_HAS_CUSTOM_EDITORS.bindTo(contextKeyService); this._focusedCustomEditorIsEditable = CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE.bindTo(contextKeyService); this._webviewHasOwnEditFunctions = webviewHasOwnEditFunctionsContext.bindTo(contextKeyService); + this._register(this._editorInfoStore.onChange(() => this.updateContexts())); this._register(this.editorService.onDidActiveEditorChange(() => this.updateContexts())); this._register(fileService.onAfterOperation(e => { From 1b70625f8c7c3b8ae24e22c25d261c19f99746e0 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 10 Jan 2020 11:50:45 -0800 Subject: [PATCH 168/315] Use import type when importing `vscode` under src When the core references `vscode`, we only want to import the types and never generate a real import (which will fail to load). Use `import type` to better enforce this --- src/vs/workbench/api/common/apiCommands.ts | 2 +- src/vs/workbench/api/common/extHostApiCommands.ts | 2 +- src/vs/workbench/api/common/extHostClipboard.ts | 2 +- src/vs/workbench/api/common/extHostCodeInsets.ts | 2 +- src/vs/workbench/api/common/extHostCommands.ts | 2 +- src/vs/workbench/api/common/extHostComments.ts | 2 +- src/vs/workbench/api/common/extHostConfiguration.ts | 2 +- src/vs/workbench/api/common/extHostDebugService.ts | 2 +- src/vs/workbench/api/common/extHostDecorations.ts | 2 +- src/vs/workbench/api/common/extHostDiagnostics.ts | 2 +- src/vs/workbench/api/common/extHostDialogs.ts | 2 +- src/vs/workbench/api/common/extHostDocumentContentProviders.ts | 2 +- src/vs/workbench/api/common/extHostDocumentData.ts | 2 +- src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts | 2 +- src/vs/workbench/api/common/extHostDocuments.ts | 2 +- src/vs/workbench/api/common/extHostExtensionActivator.ts | 2 +- src/vs/workbench/api/common/extHostExtensionService.ts | 2 +- src/vs/workbench/api/common/extHostFileSystem.ts | 2 +- src/vs/workbench/api/common/extHostFileSystemEventService.ts | 2 +- src/vs/workbench/api/common/extHostLanguageFeatures.ts | 2 +- src/vs/workbench/api/common/extHostLanguages.ts | 2 +- src/vs/workbench/api/common/extHostMemento.ts | 2 +- src/vs/workbench/api/common/extHostMessageService.ts | 2 +- src/vs/workbench/api/common/extHostOutput.ts | 2 +- src/vs/workbench/api/common/extHostSCM.ts | 2 +- src/vs/workbench/api/common/extHostSearch.ts | 2 +- src/vs/workbench/api/common/extHostTask.ts | 2 +- src/vs/workbench/api/common/extHostTerminalService.ts | 2 +- src/vs/workbench/api/common/extHostTextEditor.ts | 2 +- src/vs/workbench/api/common/extHostTextEditors.ts | 2 +- src/vs/workbench/api/common/extHostTreeViews.ts | 2 +- src/vs/workbench/api/common/extHostTunnelService.ts | 2 +- src/vs/workbench/api/common/extHostTypeConverters.ts | 2 +- src/vs/workbench/api/common/extHostTypes.ts | 2 +- src/vs/workbench/api/common/extHostUrls.ts | 2 +- src/vs/workbench/api/common/extHostWebview.ts | 2 +- src/vs/workbench/api/common/extHostWorkspace.ts | 2 +- src/vs/workbench/api/common/shared/webview.ts | 2 +- src/vs/workbench/api/node/extHostDebugService.ts | 2 +- src/vs/workbench/api/node/extHostOutputService.ts | 2 +- src/vs/workbench/api/node/extHostSearch.ts | 2 +- src/vs/workbench/api/node/extHostTask.ts | 2 +- src/vs/workbench/api/node/extHostTerminalService.ts | 2 +- src/vs/workbench/api/node/extHostTunnelService.ts | 2 +- .../test/electron-browser/api/extHostApiCommands.test.ts | 2 +- .../electron-browser/api/extHostDocumentSaveParticipant.test.ts | 2 +- .../test/electron-browser/api/extHostLanguageFeatures.test.ts | 2 +- .../workbench/test/electron-browser/api/extHostSearch.test.ts | 2 +- .../workbench/test/electron-browser/api/extHostWebview.test.ts | 2 +- 49 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/vs/workbench/api/common/apiCommands.ts b/src/vs/workbench/api/common/apiCommands.ts index 01581c2c7c2bf..df619faf25ec8 100644 --- a/src/vs/workbench/api/common/apiCommands.ts +++ b/src/vs/workbench/api/common/apiCommands.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { URI } from 'vs/base/common/uri'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { CommandsRegistry, ICommandService, ICommandHandler } from 'vs/platform/commands/common/commands'; diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 312e9d1b4a92c..0435b0a59b6b2 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as types from 'vs/workbench/api/common/extHostTypes'; import { IRawColorInfo, IWorkspaceEditDto, ICallHierarchyItemDto, IIncomingCallDto, IOutgoingCallDto } from 'vs/workbench/api/common/extHost.protocol'; diff --git a/src/vs/workbench/api/common/extHostClipboard.ts b/src/vs/workbench/api/common/extHostClipboard.ts index a34125b22c810..2983f22bd5b9c 100644 --- a/src/vs/workbench/api/common/extHostClipboard.ts +++ b/src/vs/workbench/api/common/extHostClipboard.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IMainContext, MainContext, MainThreadClipboardShape } from 'vs/workbench/api/common/extHost.protocol'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; export class ExtHostClipboard implements vscode.Clipboard { diff --git a/src/vs/workbench/api/common/extHostCodeInsets.ts b/src/vs/workbench/api/common/extHostCodeInsets.ts index 769e4de2a7a06..8eabb9d74ab86 100644 --- a/src/vs/workbench/api/common/extHostCodeInsets.ts +++ b/src/vs/workbench/api/common/extHostCodeInsets.ts @@ -8,7 +8,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor'; import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { ExtHostEditorInsetsShape, MainThreadEditorInsetsShape } from './extHost.protocol'; import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; import { generateUuid } from 'vs/base/common/uuid'; diff --git a/src/vs/workbench/api/common/extHostCommands.ts b/src/vs/workbench/api/common/extHostCommands.ts index 67f9d79f2c807..73ac4858560d1 100644 --- a/src/vs/workbench/api/common/extHostCommands.ts +++ b/src/vs/workbench/api/common/extHostCommands.ts @@ -11,7 +11,7 @@ import { cloneAndChange } from 'vs/base/common/objects'; import { MainContext, MainThreadCommandsShape, ExtHostCommandsShape, ObjectIdentifier, ICommandDto } from './extHost.protocol'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import * as modes from 'vs/editor/common/modes'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { ILogService } from 'vs/platform/log/common/log'; import { revive } from 'vs/base/common/marshalling'; import { Range } from 'vs/editor/common/core/range'; diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 20e85761638af..e090c8d0736b0 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -15,7 +15,7 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import * as extHostTypeConverter from 'vs/workbench/api/common/extHostTypeConverters'; import * as types from 'vs/workbench/api/common/extHostTypes'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { ExtHostCommentsShape, IMainContext, MainContext, MainThreadCommentsShape, CommentThreadChanges } from './extHost.protocol'; import { ExtHostCommands } from './extHostCommands'; diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index ad9446b73948e..7b1766de9a40f 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -5,7 +5,7 @@ import { mixin, deepClone } from 'vs/base/common/objects'; import { Event, Emitter } from 'vs/base/common/event'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { ExtHostConfigurationShape, MainThreadConfigurationShape, IConfigurationInitData, MainContext } from './extHost.protocol'; import { ConfigurationTarget as ExtHostConfigurationTarget } from './extHostTypes'; diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index a3b0090c518ac..64eb6565b61e0 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -27,7 +27,7 @@ import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { ISignService } from 'vs/platform/sign/common/sign'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { withNullAsUndefined } from 'vs/base/common/types'; diff --git a/src/vs/workbench/api/common/extHostDecorations.ts b/src/vs/workbench/api/common/extHostDecorations.ts index 1888766e31feb..cccb18f5fa9b5 100644 --- a/src/vs/workbench/api/common/extHostDecorations.ts +++ b/src/vs/workbench/api/common/extHostDecorations.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { URI } from 'vs/base/common/uri'; import { MainContext, ExtHostDecorationsShape, MainThreadDecorationsShape, DecorationData, DecorationRequest, DecorationReply } from 'vs/workbench/api/common/extHost.protocol'; import { Disposable, Decoration } from 'vs/workbench/api/common/extHostTypes'; diff --git a/src/vs/workbench/api/common/extHostDiagnostics.ts b/src/vs/workbench/api/common/extHostDiagnostics.ts index e0b6831c5522d..9e06e6a75891b 100644 --- a/src/vs/workbench/api/common/extHostDiagnostics.ts +++ b/src/vs/workbench/api/common/extHostDiagnostics.ts @@ -6,7 +6,7 @@ import { localize } from 'vs/nls'; import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { URI, UriComponents } from 'vs/base/common/uri'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { MainContext, MainThreadDiagnosticsShape, ExtHostDiagnosticsShape, IMainContext } from './extHost.protocol'; import { DiagnosticSeverity } from './extHostTypes'; import * as converter from './extHostTypeConverters'; diff --git a/src/vs/workbench/api/common/extHostDialogs.ts b/src/vs/workbench/api/common/extHostDialogs.ts index 06fcd380275ef..393db6ff77a9b 100644 --- a/src/vs/workbench/api/common/extHostDialogs.ts +++ b/src/vs/workbench/api/common/extHostDialogs.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { URI } from 'vs/base/common/uri'; import { MainContext, MainThreadDiaglogsShape, IMainContext } from 'vs/workbench/api/common/extHost.protocol'; diff --git a/src/vs/workbench/api/common/extHostDocumentContentProviders.ts b/src/vs/workbench/api/common/extHostDocumentContentProviders.ts index 56fda7f3a9084..eba5e784576d6 100644 --- a/src/vs/workbench/api/common/extHostDocumentContentProviders.ts +++ b/src/vs/workbench/api/common/extHostDocumentContentProviders.ts @@ -7,7 +7,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/workbench/api/common/extHostTypes'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { MainContext, ExtHostDocumentContentProvidersShape, MainThreadDocumentContentProvidersShape, IMainContext } from './extHost.protocol'; import { ExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors'; import { Schemas } from 'vs/base/common/network'; diff --git a/src/vs/workbench/api/common/extHostDocumentData.ts b/src/vs/workbench/api/common/extHostDocumentData.ts index 1ff19e1998c35..cd871a8b382f7 100644 --- a/src/vs/workbench/api/common/extHostDocumentData.ts +++ b/src/vs/workbench/api/common/extHostDocumentData.ts @@ -11,7 +11,7 @@ import { MirrorTextModel } from 'vs/editor/common/model/mirrorTextModel'; import { ensureValidWordDefinition, getWordAtText } from 'vs/editor/common/model/wordHelper'; import { MainThreadDocumentsShape } from 'vs/workbench/api/common/extHost.protocol'; import { EndOfLine, Position, Range } from 'vs/workbench/api/common/extHostTypes'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { equals } from 'vs/base/common/arrays'; const _modeId2WordDefinition = new Map(); diff --git a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts index 63f885cc69acc..05c8e4bb11b72 100644 --- a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts +++ b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts @@ -12,7 +12,7 @@ import { TextEdit } from 'vs/workbench/api/common/extHostTypes'; import { Range, TextDocumentSaveReason, EndOfLine } from 'vs/workbench/api/common/extHostTypeConverters'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { SaveReason } from 'vs/workbench/common/editor'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { LinkedList } from 'vs/base/common/linkedList'; import { ILogService } from 'vs/platform/log/common/log'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; diff --git a/src/vs/workbench/api/common/extHostDocuments.ts b/src/vs/workbench/api/common/extHostDocuments.ts index 60a0e162e3623..d4406d5166a47 100644 --- a/src/vs/workbench/api/common/extHostDocuments.ts +++ b/src/vs/workbench/api/common/extHostDocuments.ts @@ -11,7 +11,7 @@ import { ExtHostDocumentsShape, IMainContext, MainContext, MainThreadDocumentsSh import { ExtHostDocumentData, setWordDefinitionFor } from 'vs/workbench/api/common/extHostDocumentData'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import * as TypeConverters from 'vs/workbench/api/common/extHostTypeConverters'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { assertIsDefined } from 'vs/base/common/types'; export class ExtHostDocuments implements ExtHostDocumentsShape { diff --git a/src/vs/workbench/api/common/extHostExtensionActivator.ts b/src/vs/workbench/api/common/extHostExtensionActivator.ts index efa750e4b6593..3a076946edd82 100644 --- a/src/vs/workbench/api/common/extHostExtensionActivator.ts +++ b/src/vs/workbench/api/common/extHostExtensionActivator.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { IDisposable } from 'vs/base/common/lifecycle'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index b0cb870778200..4fe9d010a9f05 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -20,7 +20,7 @@ import { ExtensionActivationError } from 'vs/workbench/services/extensions/commo import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { Schemas } from 'vs/base/common/network'; import { VSBuffer } from 'vs/base/common/buffer'; diff --git a/src/vs/workbench/api/common/extHostFileSystem.ts b/src/vs/workbench/api/common/extHostFileSystem.ts index 2e16c4f8bff4e..a721785d28c3b 100644 --- a/src/vs/workbench/api/common/extHostFileSystem.ts +++ b/src/vs/workbench/api/common/extHostFileSystem.ts @@ -5,7 +5,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { MainContext, IMainContext, ExtHostFileSystemShape, MainThreadFileSystemShape, IFileChangeDto } from './extHost.protocol'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import * as files from 'vs/platform/files/common/files'; import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; import { FileChangeType, FileSystemError } from 'vs/workbench/api/common/extHostTypes'; diff --git a/src/vs/workbench/api/common/extHostFileSystemEventService.ts b/src/vs/workbench/api/common/extHostFileSystemEventService.ts index 39bc81f6f8c52..0f93730c1312d 100644 --- a/src/vs/workbench/api/common/extHostFileSystemEventService.ts +++ b/src/vs/workbench/api/common/extHostFileSystemEventService.ts @@ -7,7 +7,7 @@ import { AsyncEmitter, Emitter, Event, IWaitUntil } from 'vs/base/common/event'; import { IRelativePattern, parse } from 'vs/base/common/glob'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, MainThreadTextEditorsShape, IResourceFileEditDto, IResourceTextEditDto } from './extHost.protocol'; import * as typeConverter from './extHostTypeConverters'; import { Disposable, WorkspaceEdit } from './extHostTypes'; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 737ed4be71402..af9a04c90f3c1 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -5,7 +5,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { mixin } from 'vs/base/common/objects'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits } from 'vs/workbench/api/common/extHostTypes'; import { ISingleEditOperation } from 'vs/editor/common/model'; diff --git a/src/vs/workbench/api/common/extHostLanguages.ts b/src/vs/workbench/api/common/extHostLanguages.ts index c6c3cc0c6e13a..1231fcb046963 100644 --- a/src/vs/workbench/api/common/extHostLanguages.ts +++ b/src/vs/workbench/api/common/extHostLanguages.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { MainContext, MainThreadLanguagesShape, IMainContext } from './extHost.protocol'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; export class ExtHostLanguages { diff --git a/src/vs/workbench/api/common/extHostMemento.ts b/src/vs/workbench/api/common/extHostMemento.ts index 103b8f6a785dc..c74a5cffe5faa 100644 --- a/src/vs/workbench/api/common/extHostMemento.ts +++ b/src/vs/workbench/api/common/extHostMemento.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { IDisposable } from 'vs/base/common/lifecycle'; import { ExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; diff --git a/src/vs/workbench/api/common/extHostMessageService.ts b/src/vs/workbench/api/common/extHostMessageService.ts index 31154ab1de9d0..fc383d9726530 100644 --- a/src/vs/workbench/api/common/extHostMessageService.ts +++ b/src/vs/workbench/api/common/extHostMessageService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import Severity from 'vs/base/common/severity'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { MainContext, MainThreadMessageServiceShape, MainThreadMessageOptions, IMainContext } from './extHost.protocol'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; diff --git a/src/vs/workbench/api/common/extHostOutput.ts b/src/vs/workbench/api/common/extHostOutput.ts index aeae1a0aef41c..a8bc2ddd474ce 100644 --- a/src/vs/workbench/api/common/extHostOutput.ts +++ b/src/vs/workbench/api/common/extHostOutput.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { MainContext, MainThreadOutputServiceShape, ExtHostOutputServiceShape } from './extHost.protocol'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 3b2943ff281f7..e27ae573bdd52 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -12,7 +12,7 @@ import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto } from './extHost.protocol'; import { sortedDiff, equals } from 'vs/base/common/arrays'; import { comparePaths } from 'vs/base/common/comparers'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { ISplice } from 'vs/base/common/sequence'; import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken } from 'vs/base/common/cancellation'; diff --git a/src/vs/workbench/api/common/extHostSearch.ts b/src/vs/workbench/api/common/extHostSearch.ts index adc7c29d6ec4d..7ed796455c5d7 100644 --- a/src/vs/workbench/api/common/extHostSearch.ts +++ b/src/vs/workbench/api/common/extHostSearch.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { ExtHostSearchShape, MainThreadSearchShape, MainContext } from '../common/extHost.protocol'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { FileSearchManager } from 'vs/workbench/services/search/common/fileSearchManager'; diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts index fc9f83e5d2ce1..106635b772c31 100644 --- a/src/vs/workbench/api/common/extHostTask.ts +++ b/src/vs/workbench/api/common/extHostTask.ts @@ -12,7 +12,7 @@ import { MainContext, MainThreadTaskShape, ExtHostTaskShape } from 'vs/workbench import * as types from 'vs/workbench/api/common/extHostTypes'; import { IExtHostWorkspaceProvider, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import * as tasks from '../common/shared/tasks'; import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index e08d1f50b85fa..f5635b83d23f5 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { Event, Emitter } from 'vs/base/common/event'; import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto, ITerminalDimensionsDto } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; diff --git a/src/vs/workbench/api/common/extHostTextEditor.ts b/src/vs/workbench/api/common/extHostTextEditor.ts index f57af9da4ef57..635a5c6c253b0 100644 --- a/src/vs/workbench/api/common/extHostTextEditor.ts +++ b/src/vs/workbench/api/common/extHostTextEditor.ts @@ -13,7 +13,7 @@ import { IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, MainT import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; import * as TypeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { EndOfLine, Position, Range, Selection, SnippetString, TextEditorLineNumbersStyle, TextEditorRevealType } from 'vs/workbench/api/common/extHostTypes'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { ILogService } from 'vs/platform/log/common/log'; export class TextEditorDecorationType implements vscode.TextEditorDecorationType { diff --git a/src/vs/workbench/api/common/extHostTextEditors.ts b/src/vs/workbench/api/common/extHostTextEditors.ts index d70ab97f9cf7b..4a61bf075360b 100644 --- a/src/vs/workbench/api/common/extHostTextEditors.ts +++ b/src/vs/workbench/api/common/extHostTextEditors.ts @@ -10,7 +10,7 @@ import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocum import { ExtHostTextEditor, TextEditorDecorationType } from 'vs/workbench/api/common/extHostTextEditor'; import * as TypeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { TextEditorSelectionChangeKind } from 'vs/workbench/api/common/extHostTypes'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; export class ExtHostEditors implements ExtHostEditorsShape { diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index 5fa7306fdf990..a17ec4889ca11 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { basename } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { Emitter, Event } from 'vs/base/common/event'; diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index f3e81eab7bd93..fe666a32b52cf 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -5,7 +5,7 @@ import { ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { RemoteTunnel, TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { IDisposable } from 'vs/base/common/lifecycle'; diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 90ee0b882cba7..093a3219eb5aa 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -10,7 +10,7 @@ import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { IDecorationOptions, IThemeDecorationRenderOptions, IDecorationRenderOptions, IContentDecorationRenderOptions } from 'vs/editor/common/editorCommon'; import { EndOfLineSequence, TrackedRangeStickiness } from 'vs/editor/common/model'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ProgressLocation as MainProgressLocation } from 'vs/platform/progress/common/progress'; import { SaveReason } from 'vs/workbench/common/editor'; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 5239da9499b21..eeb89627d6260 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -11,7 +11,7 @@ import { values } from 'vs/base/common/map'; import { startsWith } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files'; import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { escapeCodicons } from 'vs/base/common/codicons'; diff --git a/src/vs/workbench/api/common/extHostUrls.ts b/src/vs/workbench/api/common/extHostUrls.ts index 6b484fe0dec2e..6a0487c578e55 100644 --- a/src/vs/workbench/api/common/extHostUrls.ts +++ b/src/vs/workbench/api/common/extHostUrls.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { MainContext, IMainContext, ExtHostUrlsShape, MainThreadUrlsShape } from './extHost.protocol'; import { URI, UriComponents } from 'vs/base/common/uri'; import { toDisposable } from 'vs/base/common/lifecycle'; diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index 724b670d3400b..7c3cefab3348d 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -14,7 +14,7 @@ import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewEditorCapabilities, WebviewPanelHandle, WebviewPanelViewStateData } from './extHost.protocol'; import { Disposable as VSCodeDisposable } from './extHostTypes'; diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 4b5c0c1301f54..edf45abf30700 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -25,7 +25,7 @@ import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { Range, RelativePattern } from 'vs/workbench/api/common/extHostTypes'; import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder'; import { IRawFileMatch2, resultIsMatch } from 'vs/workbench/services/search/common/search'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { ExtHostWorkspaceShape, IWorkspaceData, MainContext, MainThreadMessageServiceShape, MainThreadWorkspaceShape } from './extHost.protocol'; export interface IExtHostWorkspaceProvider { diff --git a/src/vs/workbench/api/common/shared/webview.ts b/src/vs/workbench/api/common/shared/webview.ts index 6d5a52928f879..851a56985db22 100644 --- a/src/vs/workbench/api/common/shared/webview.ts +++ b/src/vs/workbench/api/common/shared/webview.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; export interface WebviewInitData { readonly isExtensionDevelopmentDebug: boolean; diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index a4c70ad11e0aa..ee2d2c45bb1e7 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import * as env from 'vs/base/common/platform'; import { DebugAdapterExecutable } from 'vs/workbench/api/common/extHostTypes'; import { ExecutableDebugAdapter, SocketDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter'; diff --git a/src/vs/workbench/api/node/extHostOutputService.ts b/src/vs/workbench/api/node/extHostOutputService.ts index 4e6d32c496728..c7da758e4cc62 100644 --- a/src/vs/workbench/api/node/extHostOutputService.ts +++ b/src/vs/workbench/api/node/extHostOutputService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { MainThreadOutputServiceShape } from '../common/extHost.protocol'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { URI } from 'vs/base/common/uri'; import { join } from 'vs/base/common/path'; import { OutputAppender } from 'vs/workbench/services/output/node/outputAppender'; diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index 3082bb7062551..497f971404b99 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -11,7 +11,7 @@ import { IFileQuery, IRawFileQuery, ISearchCompleteStats, isSerializedFileMatch, import { SearchService } from 'vs/workbench/services/search/node/rawSearchService'; import { RipgrepSearchProvider } from 'vs/workbench/services/search/node/ripgrepSearchProvider'; import { OutputChannel } from 'vs/workbench/services/search/node/ripgrepSearchUtils'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index d6d9a1ee84cdc..bcca770e837ad 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -9,7 +9,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { win32 } from 'vs/base/node/processes'; import * as types from 'vs/workbench/api/common/extHostTypes'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import * as tasks from '../common/shared/tasks'; import * as Objects from 'vs/base/common/objects'; import { ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostDebugService'; diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index f318235a519bb..8d2f73a92635b 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import product from 'vs/platform/product/common/product'; import * as os from 'os'; import { URI, UriComponents } from 'vs/base/common/uri'; diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts index a593299072661..58e07babca120 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -5,7 +5,7 @@ import { MainThreadTunnelServiceShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { URI } from 'vs/base/common/uri'; diff --git a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts index 7b4f95e3aa42a..06138aaba40cf 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts @@ -23,7 +23,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { MainContext, ExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import 'vs/workbench/contrib/search/browser/search.contribution'; import { NullLogService } from 'vs/platform/log/common/log'; diff --git a/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts b/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts index a219765f48be9..759da1073456a 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts @@ -11,7 +11,7 @@ import { MainThreadTextEditorsShape, IWorkspaceEditDto } from 'vs/workbench/api/ import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/common/extHostDocumentSaveParticipant'; import { SingleProxyRPCProtocol } from './testRPCProtocol'; import { SaveReason } from 'vs/workbench/common/editor'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { NullLogService } from 'vs/platform/log/common/log'; import { isResourceTextEdit, ResourceTextEdit } from 'vs/editor/common/modes'; diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index 9bb319fe7b717..232becb2a56a4 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -35,7 +35,7 @@ import { getDocumentFormattingEditsUntilResult, getDocumentRangeFormattingEditsU import { getLinks } from 'vs/editor/contrib/links/getLinks'; import { MainContext, ExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { NullLogService } from 'vs/platform/log/common/log'; import { ITextModel, EndOfLineSequence } from 'vs/editor/common/model'; diff --git a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts index d90cbe0425674..ab0b20cdd8683 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts @@ -16,7 +16,7 @@ import { NativeExtHostSearch } from 'vs/workbench/api/node/extHostSearch'; import { Range } from 'vs/workbench/api/common/extHostTypes'; import { IFileMatch, IFileQuery, IPatternInfo, IRawFileMatch2, ISearchCompleteStats, ISearchQuery, ITextQuery, QueryType, resultIsMatch } from 'vs/workbench/services/search/common/search'; import { TestRPCProtocol } from 'vs/workbench/test/electron-browser/api/testRPCProtocol'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { NullLogService } from 'vs/platform/log/common/log'; import { URITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; diff --git a/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts b/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts index 8febbdbdf9eb7..3828c1f781140 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts @@ -11,7 +11,7 @@ import { MainThreadWebviews } from 'vs/workbench/api/browser/mainThreadWebview'; import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; -import * as vscode from 'vscode'; +import type * as vscode from 'vscode'; import { SingleProxyRPCProtocol } from './testRPCProtocol'; suite('ExtHostWebview', () => { From 5c71a0b18b83b5fae09978770df7eb3813d9b076 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 10 Jan 2020 13:24:41 -0800 Subject: [PATCH 169/315] Update js/ts grammar --- .../syntaxes/JavaScript.tmLanguage.json | 63 ++++++++++++------- .../syntaxes/JavaScriptReact.tmLanguage.json | 63 ++++++++++++------- extensions/typescript-basics/cgmanifest.json | 2 +- .../syntaxes/TypeScript.tmLanguage.json | 63 ++++++++++++------- .../syntaxes/TypeScriptReact.tmLanguage.json | 63 ++++++++++++------- 5 files changed, 157 insertions(+), 97 deletions(-) diff --git a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json index 54169e925dab3..c18fb6cfef782 100644 --- a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json +++ b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/b7ef0941e38d56292d19ee7706a558dbff6c3a35", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/f065e7e88d1c20160c5ec92455aad99a1016284f", "name": "JavaScript (with React support)", "scopeName": "source.js", "patterns": [ @@ -1081,13 +1081,13 @@ }, "field-declaration": { "name": "meta.field.declaration.js", - "begin": "(?x)(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(\\#?[_$[:alpha:]][_$[:alnum:]]*)(?:(\\?)|(\\!))?(?=\\s*\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "meta.definition.property.js entity.name.function.js" @@ -1123,7 +1123,7 @@ }, { "name": "meta.definition.property.js variable.object.property.js", - "match": "[_$[:alpha:]][_$[:alnum:]]*" + "match": "\\#?[_$[:alpha:]][_$[:alnum:]]*" }, { "name": "keyword.operator.optional.js", @@ -1998,7 +1998,7 @@ "patterns": [ { "name": "meta.import-equals.external.js", - "begin": "(?\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", - "end": "(?<=\\))(?!(((([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", + "begin": "(?=(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", + "end": "(?<=\\))(?!(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", "patterns": [ { "name": "meta.function-call.js", - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))", "end": "(?=\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", "patterns": [ { @@ -2834,7 +2849,7 @@ }, { "name": "entity.name.function.js", - "match": "([_$[:alpha:]][_$[:alnum:]]*)" + "match": "(\\#?[_$[:alpha:]][_$[:alnum:]]*)" } ] }, @@ -3626,7 +3641,7 @@ } }, { - "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*([[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])", + "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(\\#?[[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])", "captures": { "1": { "name": "punctuation.accessor.js" @@ -3640,7 +3655,7 @@ } }, { - "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*([_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "punctuation.accessor.js" @@ -3670,7 +3685,7 @@ "match": "([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\\??\\.\\s*prototype\\b(?!\\$))" }, { - "match": "(?x)(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?x)(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(?:\n (\\#?[[:upper:]][_$[:digit:][:upper:]]*) |\n (\\#?[_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "punctuation.accessor.js" @@ -3687,7 +3702,7 @@ } }, { - "match": "(?x)(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?x)(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "variable.other.constant.object.js" diff --git a/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json b/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json index 9c5e4e15474be..29cd81e4ab369 100644 --- a/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json +++ b/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/b7ef0941e38d56292d19ee7706a558dbff6c3a35", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/f065e7e88d1c20160c5ec92455aad99a1016284f", "name": "JavaScript (with React support)", "scopeName": "source.js.jsx", "patterns": [ @@ -1081,13 +1081,13 @@ }, "field-declaration": { "name": "meta.field.declaration.js.jsx", - "begin": "(?x)(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(\\#?[_$[:alpha:]][_$[:alnum:]]*)(?:(\\?)|(\\!))?(?=\\s*\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "meta.definition.property.js.jsx entity.name.function.js.jsx" @@ -1123,7 +1123,7 @@ }, { "name": "meta.definition.property.js.jsx variable.object.property.js.jsx", - "match": "[_$[:alpha:]][_$[:alnum:]]*" + "match": "\\#?[_$[:alpha:]][_$[:alnum:]]*" }, { "name": "keyword.operator.optional.js.jsx", @@ -1998,7 +1998,7 @@ "patterns": [ { "name": "meta.import-equals.external.js.jsx", - "begin": "(?\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", - "end": "(?<=\\))(?!(((([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", + "begin": "(?=(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", + "end": "(?<=\\))(?!(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", "patterns": [ { "name": "meta.function-call.js.jsx", - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))", "end": "(?=\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", "patterns": [ { @@ -2834,7 +2849,7 @@ }, { "name": "entity.name.function.js.jsx", - "match": "([_$[:alpha:]][_$[:alnum:]]*)" + "match": "(\\#?[_$[:alpha:]][_$[:alnum:]]*)" } ] }, @@ -3626,7 +3641,7 @@ } }, { - "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*([[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])", + "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(\\#?[[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])", "captures": { "1": { "name": "punctuation.accessor.js.jsx" @@ -3640,7 +3655,7 @@ } }, { - "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*([_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "punctuation.accessor.js.jsx" @@ -3670,7 +3685,7 @@ "match": "([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\\??\\.\\s*prototype\\b(?!\\$))" }, { - "match": "(?x)(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?x)(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(?:\n (\\#?[[:upper:]][_$[:digit:][:upper:]]*) |\n (\\#?[_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "punctuation.accessor.js.jsx" @@ -3687,7 +3702,7 @@ } }, { - "match": "(?x)(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?x)(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "variable.other.constant.object.js.jsx" diff --git a/extensions/typescript-basics/cgmanifest.json b/extensions/typescript-basics/cgmanifest.json index 31f2e0c545a79..4136543a84f31 100644 --- a/extensions/typescript-basics/cgmanifest.json +++ b/extensions/typescript-basics/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "TypeScript-TmLanguage", "repositoryUrl": "https://github.com/Microsoft/TypeScript-TmLanguage", - "commitHash": "b7ef0941e38d56292d19ee7706a558dbff6c3a35" + "commitHash": "f065e7e88d1c20160c5ec92455aad99a1016284f" } }, "license": "MIT", diff --git a/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json b/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json index 437e205163f8d..4edc3b8285e6b 100644 --- a/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json +++ b/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/b7ef0941e38d56292d19ee7706a558dbff6c3a35", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/f065e7e88d1c20160c5ec92455aad99a1016284f", "name": "TypeScript", "scopeName": "source.ts", "patterns": [ @@ -1078,13 +1078,13 @@ }, "field-declaration": { "name": "meta.field.declaration.ts", - "begin": "(?x)(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(\\#?[_$[:alpha:]][_$[:alnum:]]*)(?:(\\?)|(\\!))?(?=\\s*\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?[\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "meta.definition.property.ts entity.name.function.ts" @@ -1120,7 +1120,7 @@ }, { "name": "meta.definition.property.ts variable.object.property.ts", - "match": "[_$[:alpha:]][_$[:alnum:]]*" + "match": "\\#?[_$[:alpha:]][_$[:alnum:]]*" }, { "name": "keyword.operator.optional.ts", @@ -1995,7 +1995,7 @@ "patterns": [ { "name": "meta.import-equals.external.ts", - "begin": "(?\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", - "end": "(?<=\\))(?!(((([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", + "begin": "(?=(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", + "end": "(?<=\\))(?!(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", "patterns": [ { "name": "meta.function-call.ts", - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))", "end": "(?=\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", "patterns": [ { @@ -2831,7 +2846,7 @@ }, { "name": "entity.name.function.ts", - "match": "([_$[:alpha:]][_$[:alnum:]]*)" + "match": "(\\#?[_$[:alpha:]][_$[:alnum:]]*)" } ] }, @@ -3675,7 +3690,7 @@ } }, { - "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*([[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])", + "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(\\#?[[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])", "captures": { "1": { "name": "punctuation.accessor.ts" @@ -3689,7 +3704,7 @@ } }, { - "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*([_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "punctuation.accessor.ts" @@ -3719,7 +3734,7 @@ "match": "([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\\??\\.\\s*prototype\\b(?!\\$))" }, { - "match": "(?x)(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?x)(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(?:\n (\\#?[[:upper:]][_$[:digit:][:upper:]]*) |\n (\\#?[_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "punctuation.accessor.ts" @@ -3736,7 +3751,7 @@ } }, { - "match": "(?x)(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?x)(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "variable.other.constant.object.ts" diff --git a/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json b/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json index c2c6c09a5a2a6..810cac1b2db59 100644 --- a/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json +++ b/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/b7ef0941e38d56292d19ee7706a558dbff6c3a35", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/f065e7e88d1c20160c5ec92455aad99a1016284f", "name": "TypeScriptReact", "scopeName": "source.tsx", "patterns": [ @@ -1081,13 +1081,13 @@ }, "field-declaration": { "name": "meta.field.declaration.tsx", - "begin": "(?x)(?)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", + "match": "(?x)(\\#?[_$[:alpha:]][_$[:alnum:]]*)(?:(\\?)|(\\!))?(?=\\s*\\s*\n# function assignment |\n(=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))) |\n(:\\s*(=>|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(<[^<>]*>)|[^<>(),=])+=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n ((<\\s*$)|([\\(]\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))) |\n # sure shot arrow functions even if => is on new line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\n [(]\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)? # typeparameters\n \\(\\s*(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*(([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\.\\.\\.\\s*[_$[:alpha:]]))([^()]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\)))*)?\\) # parameters\n (\\s*:\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<[^<>]+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)))", "captures": { "1": { "name": "meta.definition.property.tsx entity.name.function.tsx" @@ -1123,7 +1123,7 @@ }, { "name": "meta.definition.property.tsx variable.object.property.tsx", - "match": "[_$[:alpha:]][_$[:alnum:]]*" + "match": "\\#?[_$[:alpha:]][_$[:alnum:]]*" }, { "name": "keyword.operator.optional.tsx", @@ -1998,7 +1998,7 @@ "patterns": [ { "name": "meta.import-equals.external.tsx", - "begin": "(?\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", - "end": "(?<=\\))(?!(((([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", + "begin": "(?=(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", + "end": "(?<=\\))(?!(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", "patterns": [ { "name": "meta.function-call.tsx", - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))", "end": "(?=\\s*(?:(\\?\\.\\s*)|(\\!))?(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\])|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\()", "patterns": [ { @@ -2834,7 +2849,7 @@ }, { "name": "entity.name.function.tsx", - "match": "([_$[:alpha:]][_$[:alnum:]]*)" + "match": "(\\#?[_$[:alpha:]][_$[:alnum:]]*)" } ] }, @@ -3626,7 +3641,7 @@ } }, { - "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*([[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])", + "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(\\#?[[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])", "captures": { "1": { "name": "punctuation.accessor.tsx" @@ -3640,7 +3655,7 @@ } }, { - "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*([_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "punctuation.accessor.tsx" @@ -3670,7 +3685,7 @@ "match": "([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\\??\\.\\s*prototype\\b(?!\\$))" }, { - "match": "(?x)(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?x)(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(?:\n (\\#?[[:upper:]][_$[:digit:][:upper:]]*) |\n (\\#?[_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "punctuation.accessor.tsx" @@ -3687,7 +3702,7 @@ } }, { - "match": "(?x)(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?x)(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "variable.other.constant.object.tsx" From a0328d26aca420f9d0606b825f5a326dc6d0d398 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 10 Jan 2020 14:24:50 -0800 Subject: [PATCH 170/315] Add 'vscode.reopenWith' API command Fixes #88426 Add an api command that allows extensions to open/reopen a file with a specific we custom editor. Use this to allow re-opening a failed to load image as text/binary For #77131 --- extensions/image-preview/media/main.css | 6 +-- extensions/image-preview/media/main.js | 6 +++ extensions/image-preview/src/preview.ts | 11 ++++- src/vs/workbench/api/common/apiCommands.ts | 18 +++++++++ .../contrib/customEditor/browser/commands.ts | 40 +++++-------------- 5 files changed, 46 insertions(+), 35 deletions(-) diff --git a/extensions/image-preview/media/main.css b/extensions/image-preview/media/main.css index ff3e6d0f0d2e3..49a01b8d9694b 100644 --- a/extensions/image-preview/media/main.css +++ b/extensions/image-preview/media/main.css @@ -94,16 +94,16 @@ body img { } .loading-indicator, -.image-load-error-message { +.image-load-error { display: none; } .loading .loading-indicator, -.error .image-load-error-message { +.error .image-load-error { display: block; } -.image-load-error-message { +.image-load-error { margin: 1em; } diff --git a/extensions/image-preview/media/main.js b/extensions/image-preview/media/main.js index 3e2abf4ee988e..394511aa8ef8d 100644 --- a/extensions/image-preview/media/main.js +++ b/extensions/image-preview/media/main.js @@ -305,6 +305,12 @@ image.src = settings.src; + document.querySelector('.open-file-link').addEventListener('click', () => { + vscode.postMessage({ + type: 'reopen-as-text', + }); + }); + window.addEventListener('message', e => { switch (e.data.type) { case 'setScale': diff --git a/extensions/image-preview/src/preview.ts b/extensions/image-preview/src/preview.ts index dc2e6676245e9..4c21b052509bc 100644 --- a/extensions/image-preview/src/preview.ts +++ b/extensions/image-preview/src/preview.ts @@ -108,6 +108,12 @@ class Preview extends Disposable { this.update(); break; } + + case 'reopen-as-text': + { + vscode.commands.executeCommand('vscode.openWith', resource, 'default', webviewEditor.viewColumn); + break; + } } })); @@ -218,7 +224,10 @@ class Preview extends Disposable {
-
${localize('preview.imageLoadError', "An error occurred while loading the image")}
+
+

${localize('preview.imageLoadError', "An error occurred while loading the image.")}

+ ${localize('preview.imageLoadErrorLink', "Open file using VS Code's standard text/binary editor?")} +
`; diff --git a/src/vs/workbench/api/common/apiCommands.ts b/src/vs/workbench/api/common/apiCommands.ts index df619faf25ec8..d2c28563d4875 100644 --- a/src/vs/workbench/api/common/apiCommands.ts +++ b/src/vs/workbench/api/common/apiCommands.ts @@ -132,6 +132,24 @@ export class OpenAPICommand { } CommandsRegistry.registerCommand(OpenAPICommand.ID, adjustHandler(OpenAPICommand.execute)); +export class OpenWithAPICommand { + public static readonly ID = 'vscode.openWith'; + public static execute(executor: ICommandsExecutor, resource: URI, viewType: string, column?: vscode.ViewColumn): Promise { + let position: EditorViewColumn | undefined; + + if (typeof column === 'number') { + position = typeConverters.ViewColumn.from(column); + } + + return executor.executeCommand('_workbench.openWith', [ + resource, + viewType, + position + ]); + } +} +CommandsRegistry.registerCommand(OpenWithAPICommand.ID, adjustHandler(OpenWithAPICommand.execute)); + CommandsRegistry.registerCommand('_workbench.removeFromRecentlyOpened', function (accessor: ServicesAccessor, uri: URI) { const workspacesService = accessor.get(IWorkspacesService); return workspacesService.removeFromRecentlyOpened([uri]); diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index 1022aa8e3f8fb..5db57f251a4f8 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -3,59 +3,37 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { firstOrDefault } from 'vs/base/common/arrays'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { URI } from 'vs/base/common/uri'; import { Command } from 'vs/editor/browser/editorExtensions'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import * as nls from 'vs/nls'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IListService } from 'vs/platform/list/browser/listService'; +import { EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; import { IEditorCommandsContext } from 'vs/workbench/common/editor'; +import { CustomFileEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; import { defaultEditorId } from 'vs/workbench/contrib/customEditor/browser/customEditors'; import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_HAS_CUSTOM_EDITORS, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; -import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; -import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { CustomFileEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; const viewCategory = nls.localize('viewCategory', "View"); // #region Open With -const OPEN_WITH_COMMAND_ID = 'openWith'; -// const OPEN_WITH_TITLE = { value: nls.localize('openWith.title', 'Open With'), original: 'Open With' }; +CommandsRegistry.registerCommand('_workbench.openWith', (accessor: ServicesAccessor, args: [URI, string, EditorViewColumn]) => { + const customEditorService = accessor.get(ICustomEditorService); + const editorGroupService = accessor.get(IEditorGroupsService); -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: OPEN_WITH_COMMAND_ID, - weight: KeybindingWeight.WorkbenchContrib, - when: EditorContextKeys.focus.toNegated(), - handler: async (accessor: ServicesAccessor, resource: URI | object) => { - const editorService = accessor.get(IEditorService); - const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService, accessor.get(IExplorerService)); - const targetResource = firstOrDefault(resources); - if (!targetResource) { - return; - } - return accessor.get(ICustomEditorService).promptOpenWith(targetResource, undefined, undefined); - } + const [resource, viewType, position] = args; + const group = viewColumnToEditorGroup(editorGroupService, position); + customEditorService.openWith(resource, viewType, undefined, editorGroupService.getGroup(group)); }); -// MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { -// group: 'navigation', -// order: 20, -// command: { -// id: OPEN_WITH_COMMAND_ID, -// title: OPEN_WITH_TITLE, -// }, -// when: ResourceContextKey.Scheme.isEqualTo(Schemas.file) -// }); - // #endregion // #region Reopen With From f6f9931377b864e5f1a6ca8081921f726508ca75 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 10 Jan 2020 14:34:38 -0800 Subject: [PATCH 171/315] add launch.json settings to make js-debug faster --- .vscode/launch.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 6fa93e1c9c59c..15dcd01344ec0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -162,9 +162,12 @@ "urlFilter": "*workbench.html*", "runtimeArgs": [ "--inspect=5875", - "--no-cached-data" + "--no-cached-data", ], - "webRoot": "${workspaceFolder}" + "webRoot": "${workspaceFolder}", + // Settings for js-debug: + "pauseForSourceMap": false, + "outFiles": ["${workspaceFolder}/out/**/*.js"], }, { "type": "node", From 59213a4c2797e532c59bb9b1463684b688e34fb2 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Fri, 10 Jan 2020 16:20:59 -0800 Subject: [PATCH 172/315] Keep track of query includes/excludes expansion state --- .../contrib/search/browser/searchEditor.ts | 21 ++++++++++++------- .../search/browser/searchEditorCommands.ts | 6 ++++-- .../contrib/search/browser/searchWidget.ts | 5 ++--- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index 8e7e247f519b5..505ea541890be 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -57,6 +57,7 @@ export class SearchEditor extends BaseEditor { private runSearchDelayer = new Delayer(300); private pauseSearching: boolean = false; + private showingIncludesExcludes: boolean = false; constructor( @ITelemetryService telemetryService: ITelemetryService, @@ -82,7 +83,7 @@ export class SearchEditor extends BaseEditor { this.queryEditorWidget = this._register(this.instantiationService.createInstance(SearchWidget, this.queryEditorContainer, { _hideReplaceToggle: true, showContextToggle: true })); this._register(this.queryEditorWidget.onReplaceToggled(() => this.reLayout())); this._register(this.queryEditorWidget.onDidHeightChange(() => this.reLayout())); - this.queryEditorWidget.onSearchSubmit(() => this.runSearch()); + this.queryEditorWidget.onSearchSubmit(() => this.runSearch(true)); // onSearchSubmit has an internal delayer, so skip over ours. this.queryEditorWidget.searchInput.onDidOptionChange(() => this.runSearch()); this.queryEditorWidget.onDidToggleContext(() => this.runSearch()); @@ -92,14 +93,14 @@ export class SearchEditor extends BaseEditor { this.toggleQueryDetailsButton = DOM.append(this.includesExcludesContainer, DOM.$('.expand.codicon.codicon-ellipsis', { tabindex: 0, role: 'button', title: localize('moreSearch', "Toggle Search Details") })); this._register(DOM.addDisposableListener(this.toggleQueryDetailsButton, DOM.EventType.CLICK, e => { DOM.EventHelper.stop(e); - this.toggleQueryDetails(); + this.toggleIncludesExcludes(); })); this._register(DOM.addDisposableListener(this.toggleQueryDetailsButton, DOM.EventType.KEY_UP, (e: KeyboardEvent) => { const event = new StandardKeyboardEvent(e); if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { DOM.EventHelper.stop(e); - this.toggleQueryDetails(); + this.toggleIncludesExcludes(); } })); this._register(DOM.addDisposableListener(this.toggleQueryDetailsButton, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => { @@ -159,9 +160,9 @@ export class SearchEditor extends BaseEditor { }); } - private async runSearch() { + private async runSearch(instant = false) { if (!this.pauseSearching) { - this.runSearchDelayer.trigger(() => this.doRunSearch()); + this.runSearchDelayer.trigger(() => this.doRunSearch(), instant ? 0 : undefined); } } @@ -176,7 +177,8 @@ export class SearchEditor extends BaseEditor { query: this.queryEditorWidget.searchInput.getValue(), regexp: this.queryEditorWidget.searchInput.getRegex(), wholeWord: this.queryEditorWidget.searchInput.getWholeWords(), - useIgnores: this.inputPatternExcludes.useExcludesAndIgnoreFiles() + useIgnores: this.inputPatternExcludes.useExcludesAndIgnoreFiles(), + showIncludesExcludes: this.showingIncludesExcludes }; const content: IPatternInfo = { @@ -264,15 +266,16 @@ export class SearchEditor extends BaseEditor { this.inputPatternExcludes.setValue(newInput.config.excludes); this.inputPatternIncludes.setValue(newInput.config.includes); this.inputPatternExcludes.setUseExcludesAndIgnoreFiles(newInput.config.useIgnores); + this.toggleIncludesExcludes(newInput.config.showIncludesExcludes); this.focusInput(); await super.setInput(newInput, options, token); this.pauseSearching = false; } - toggleQueryDetails(): void { + private toggleIncludesExcludes(_shouldShow?: boolean): void { const cls = 'expanded'; - const shouldShow = !DOM.hasClass(this.includesExcludesContainer, cls); + const shouldShow = _shouldShow ?? !DOM.hasClass(this.includesExcludesContainer, cls); if (shouldShow) { this.toggleQueryDetailsButton.setAttribute('aria-expanded', 'true'); @@ -282,6 +285,8 @@ export class SearchEditor extends BaseEditor { DOM.removeClass(this.includesExcludesContainer, cls); } + this.showingIncludesExcludes = DOM.hasClass(this.includesExcludesContainer, cls); + this.reLayout(); } diff --git a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts index d6735a62c1254..8156667cbaad5 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts @@ -39,7 +39,8 @@ export type SearchConfiguration = { wholeWord: boolean, caseSensitive: boolean, regexp: boolean, - useIgnores: boolean + useIgnores: boolean, + showIncludesExcludes: boolean, }; let searchEditorInputInstances = 0; @@ -57,7 +58,7 @@ export class SearchEditorInput extends EditorInput { super(); if (config === undefined) { - this.config = { query: '', includes: '', excludes: '', contextLines: 0, wholeWord: false, caseSensitive: false, regexp: false, useIgnores: true }; + this.config = { query: '', includes: '', excludes: '', contextLines: 0, wholeWord: false, caseSensitive: false, regexp: false, useIgnores: true, showIncludesExcludes: false }; } else { this.config = config; } @@ -416,6 +417,7 @@ export const createEditorFromSearchResult = excludes: rawExcludePattern, contextLines: 0, useIgnores: !searchResult.query.userDisabledExcludesAndIgnoreFiles, + showIncludesExcludes: !!(rawExcludePattern || rawExcludePattern || searchResult.query.userDisabledExcludesAndIgnoreFiles) }), { pinned: true }) as SearchEditor; diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 402f4313f1a10..95f74747c9341 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -160,7 +160,6 @@ export class SearchWidget extends Widget { private temporarilySkipSearchOnChange = false; private showContextCheckbox!: Checkbox; private contextLinesInput!: InputBox; - private _contextLineInputDelayer: Delayer; constructor( container: HTMLElement, @@ -179,7 +178,6 @@ export class SearchWidget extends Widget { this.replaceInputBoxFocused = Constants.ReplaceInputBoxFocusedKey.bindTo(this.contextKeyService); this._replaceHistoryDelayer = new Delayer(500); - this._contextLineInputDelayer = new Delayer(300); this._searchDelayer = this._register(new Delayer(this.searchConfiguration.searchOnTypeDebouncePeriod)); this.render(container, options); @@ -386,7 +384,7 @@ export class SearchWidget extends Widget { if (this.contextLinesInput.value.includes('-')) { this.contextLinesInput.value = '0'; } - this._contextLineInputDelayer.trigger(() => this._onDidToggleContext.fire()); + this._onDidToggleContext.fire(); })); dom.append(searchInputContainer, this.showContextCheckbox.domNode); } @@ -400,6 +398,7 @@ export class SearchWidget extends Widget { this.showContextCheckbox.checked = true; this.contextLinesInput.value = '' + lines; } + dom.toggleClass(this.domNode, 'show-context', this.showContextCheckbox.checked); } private renderReplaceInput(parent: HTMLElement, options: ISearchWidgetOptions): void { From b9ad9c968a0786a2d13faf709903226e27e9c840 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Fri, 10 Jan 2020 17:56:28 -0800 Subject: [PATCH 173/315] Restore search editor configurations on reload --- .../search/browser/search.contribution.ts | 7 ++++++- .../search/browser/searchEditorCommands.ts | 17 +++++++++++++++-- .../contrib/search/browser/searchWidget.ts | 2 +- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 2e499c9e06da7..3a3383efb8eca 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -57,9 +57,10 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { assertType } from 'vs/base/common/types'; import { SearchViewPaneContainer } from 'vs/workbench/contrib/search/browser/searchViewlet'; import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; -import { SearchEditorInput } from 'vs/workbench/contrib/search/browser/searchEditorCommands'; +import { SearchEditorInput, SearchEditorInputFactory } from 'vs/workbench/contrib/search/browser/searchEditorCommands'; import { SearchEditor } from 'vs/workbench/contrib/search/browser/searchEditor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; registerSingleton(ISearchWorkbenchService, SearchWorkbenchService, true); registerSingleton(ISearchHistoryService, SearchHistoryService, true); @@ -904,3 +905,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( new SyncDescriptor(SearchEditorInput) ] ); + +Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory( + SearchEditorInput.ID, + SearchEditorInputFactory); diff --git a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts index 8156667cbaad5..6d9e6ee65ee57 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts @@ -25,7 +25,7 @@ import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/co import { FileMatch, Match, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IPatternInfo, ISearchConfigurationProperties, ITextQuery } from 'vs/workbench/services/search/common/search'; -import { EditorInput } from 'vs/workbench/common/editor'; +import { EditorInput, IEditorInputFactory } from 'vs/workbench/common/editor'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { SearchEditor } from 'vs/workbench/contrib/search/browser/searchEditor'; @@ -43,6 +43,19 @@ export type SearchConfiguration = { showIncludesExcludes: boolean, }; +export class SearchEditorInputFactory implements IEditorInputFactory { + + canSerialize() { return true; } + + serialize(input: SearchEditorInput) { + return JSON.stringify(input.config); + } + + deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): SearchEditorInput | undefined { + return instantiationService.createInstance(SearchEditorInput, JSON.parse(serializedEditorInput)); + } +} + let searchEditorInputInstances = 0; export class SearchEditorInput extends EditorInput { static readonly ID: string = 'workbench.editorinputs.searchEditorInput'; @@ -72,7 +85,7 @@ export class SearchEditorInput extends EditorInput { } getResource(): URI { - return URI.from({ scheme: 'code-search', fragment: `${this.instanceNumber}` }); + return URI.from({ scheme: 'untitled', authority: 'search-editor', path: this.config.query, fragment: `${this.instanceNumber}` }); } getName(): string { diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 95f74747c9341..30b5c34eb4ab7 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -452,8 +452,8 @@ export class SearchWidget extends Widget { } setValue(value: string, skipSearchOnChange: boolean) { - this.searchInput.setValue(value); this.temporarilySkipSearchOnChange = skipSearchOnChange || this.temporarilySkipSearchOnChange; + this.searchInput.setValue(value); } setReplaceAllActionState(enabled: boolean): void { From 4fb03413db8e1df7bae16eeb2d6604579167bcc7 Mon Sep 17 00:00:00 2001 From: Fabien Launay Date: Sat, 11 Jan 2020 11:08:15 +0900 Subject: [PATCH 174/315] Fix word repetition in lazy.test.ts comment (#88464) --- src/vs/base/test/common/lazy.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/test/common/lazy.test.ts b/src/vs/base/test/common/lazy.test.ts index c6a1655513fbe..04d4a2569858b 100644 --- a/src/vs/base/test/common/lazy.test.ts +++ b/src/vs/base/test/common/lazy.test.ts @@ -47,7 +47,7 @@ suite('Lazy', () => { assert.deepEqual(innerLazy.getValue(), [1, 11]); }); - test('map should should handle error values', () => { + test('map should handle error values', () => { let outer = 0; let inner = 10; const outerLazy = new Lazy(() => { throw new Error(`${++outer}`); }); From cb4ef36e6d489f74a1346fa89cdabe727bcd6027 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 10 Jan 2020 16:24:55 -0800 Subject: [PATCH 175/315] Make sure we restore the previous editor when switching back to it --- src/vs/workbench/api/common/apiCommands.ts | 10 +++-- .../contrib/customEditor/browser/commands.ts | 7 ++-- .../customEditor/browser/customEditors.ts | 41 ++++++++++++------- 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/api/common/apiCommands.ts b/src/vs/workbench/api/common/apiCommands.ts index d2c28563d4875..69fc23a9af155 100644 --- a/src/vs/workbench/api/common/apiCommands.ts +++ b/src/vs/workbench/api/common/apiCommands.ts @@ -134,16 +134,20 @@ CommandsRegistry.registerCommand(OpenAPICommand.ID, adjustHandler(OpenAPICommand export class OpenWithAPICommand { public static readonly ID = 'vscode.openWith'; - public static execute(executor: ICommandsExecutor, resource: URI, viewType: string, column?: vscode.ViewColumn): Promise { + public static execute(executor: ICommandsExecutor, resource: URI, viewType: string, columnOrOptions?: vscode.ViewColumn | vscode.TextDocumentShowOptions): Promise { + let options: ITextEditorOptions | undefined; let position: EditorViewColumn | undefined; - if (typeof column === 'number') { - position = typeConverters.ViewColumn.from(column); + if (typeof columnOrOptions === 'number') { + position = typeConverters.ViewColumn.from(columnOrOptions); + } else if (typeof columnOrOptions !== 'undefined') { + options = typeConverters.TextEditorOptions.from(columnOrOptions); } return executor.executeCommand('_workbench.openWith', [ resource, viewType, + options, position ]); } diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index 5db57f251a4f8..6814383e70b81 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -20,18 +20,19 @@ import { defaultEditorId } from 'vs/workbench/contrib/customEditor/browser/custo import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_HAS_CUSTOM_EDITORS, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import type { ITextEditorOptions } from 'vs/platform/editor/common/editor'; const viewCategory = nls.localize('viewCategory', "View"); // #region Open With -CommandsRegistry.registerCommand('_workbench.openWith', (accessor: ServicesAccessor, args: [URI, string, EditorViewColumn]) => { +CommandsRegistry.registerCommand('_workbench.openWith', (accessor: ServicesAccessor, args: [URI, string, ITextEditorOptions | undefined, EditorViewColumn | undefined]) => { const customEditorService = accessor.get(ICustomEditorService); const editorGroupService = accessor.get(IEditorGroupsService); - const [resource, viewType, position] = args; + const [resource, viewType, options, position] = args; const group = viewColumnToEditorGroup(editorGroupService, position); - customEditorService.openWith(resource, viewType, undefined, editorGroupService.getGroup(group)); + customEditorService.openWith(resource, viewType, options, editorGroupService.getGroup(group)); }); // #endregion diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 0336f929f0c44..25ce3d2fc38e1 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -240,23 +240,25 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ options?: IEditorOptions, group?: IEditorGroup ): Promise { - if (group) { - const existingEditors = group.editors.filter(editor => editor.getResource() && isEqual(editor.getResource(), resource)); - if (existingEditors.length) { - const existing = existingEditors[0]; - if (!input.matches(existing)) { - await this.editorService.replaceEditors([{ - editor: existing, - replacement: input, - options: options ? EditorOptions.create(options) : undefined, - }], group); - - if (existing instanceof CustomFileEditorInput) { - existing.dispose(); - } + const targetGroup = group || this.editorGroupService.activeGroup; + + // Try to replace existing editors for resource + const existingEditors = targetGroup.editors.filter(editor => editor.getResource() && isEqual(editor.getResource(), resource)); + if (existingEditors.length) { + const existing = existingEditors[0]; + if (!input.matches(existing)) { + await this.editorService.replaceEditors([{ + editor: existing, + replacement: input, + options: options ? EditorOptions.create(options) : undefined, + }], targetGroup); + + if (existing instanceof CustomFileEditorInput) { + existing.dispose(); } } } + return this.editorService.openEditor(input, options, group); } @@ -345,6 +347,17 @@ export class CustomEditorContribution implements IWorkbenchContribution { options: ITextEditorOptions | undefined, group: IEditorGroup ): IOpenEditorOverride | undefined { + // Check to see if there already an editor for the resource in the group. + // If there is, we want to open that instead of creating a new editor. + // This ensures that we preserve whatever state the editor was previously in + // when the user switches back to it. + const existingEditorForResource = group.editors.find(editor => isEqual(resource, editor.getResource())); + if (existingEditorForResource) { + return { + override: this.editorService.openEditor(existingEditorForResource, { ...options, ignoreOverrides: true }, group) + }; + } + const userConfiguredEditors = this.customEditorService.getUserConfiguredCustomEditors(resource); if (userConfiguredEditors.length) { return { From 8520f74c08cd24ee6525df7c930ea24eb2f7334c Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 10 Jan 2020 18:17:31 -0800 Subject: [PATCH 176/315] Implement a default saveAs for readonly custom editors Fixes #88414 --- .../workbench/api/browser/mainThreadWebview.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index d0fe331c5f508..bac1b9b6ba2df 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -12,6 +12,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import * as modes from 'vs/editor/common/modes'; import { localize } from 'vs/nls'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IFileService } from 'vs/platform/files/common/files'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IProductService } from 'vs/platform/product/common/productService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -107,6 +108,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma @IProductService private readonly _productService: IProductService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @IWebviewWorkbenchService private readonly _webviewWorkbenchService: IWebviewWorkbenchService, + @IFileService private readonly _fileService: IFileService, ) { super(); @@ -319,7 +321,8 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._customEditorModels.set(model, { referenceCount: 1 }); const capabilitiesSet = new Set(capabilities); - if (capabilitiesSet.has(extHostProtocol.WebviewEditorCapabilities.Editable)) { + const isEditable = capabilitiesSet.has(extHostProtocol.WebviewEditorCapabilities.Editable); + if (isEditable) { model.onUndo(edits => { this._proxy.$undoEdits(resource, viewType, edits.map(x => x.data)); }); @@ -335,10 +338,17 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma e.waitUntil(this._proxy.$onSave(resource.toJSON(), viewType)); }); - model.onWillSaveAs(e => { - e.waitUntil(this._proxy.$onSaveAs(e.resource.toJSON(), viewType, e.targetResource.toJSON())); - }); } + + // Save as should always be implemented even if the model is readonly + model.onWillSaveAs(e => { + if (isEditable) { + e.waitUntil(this._proxy.$onSaveAs(e.resource.toJSON(), viewType, e.targetResource.toJSON())); + } else { + // Since the editor is readonly, just copy the file over + e.waitUntil(this._fileService.copy(e.resource, e.targetResource, false /* overwrite */)); + } + }); return model; } From 22fad2a49ccc70f0ea07c6ef9b23bb25f2e8695d Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 10 Jan 2020 18:19:34 -0800 Subject: [PATCH 177/315] Update VS Code build with TS 3.8 beta --- build/package.json | 2 +- build/yarn.lock | 10 +++++----- package.json | 2 +- yarn.lock | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/build/package.json b/build/package.json index 33c254aafe1d2..77cfdaf721b86 100644 --- a/build/package.json +++ b/build/package.json @@ -43,7 +43,7 @@ "minimist": "^1.2.0", "request": "^2.85.0", "terser": "4.3.8", - "typescript": "^3.8.0-dev.20200108", + "typescript": " 3.8.0-beta", "vsce": "1.48.0", "vscode-telemetry-extractor": "^1.5.4", "xml2js": "^0.4.17" diff --git a/build/yarn.lock b/build/yarn.lock index 38c1cbec7d941..6b7c3ea006800 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -2453,16 +2453,16 @@ typed-rest-client@^0.9.0: tunnel "0.0.4" underscore "1.8.3" +"typescript@ 3.8.0-beta": + version "3.8.0-beta" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.0-beta.tgz#acdcaf9f24c7e20b1ff0a6329d1e8d63691e2e13" + integrity sha512-mQEmQUJg0CQBhf/GSVnGscKv/jrKsrLxE01AhdjYmBNoXX2Iah3i38ufxXByXacK6Fc5Nr9oMz7MjpjgddiknA== + typescript@^3.0.1: version "3.5.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== -typescript@^3.8.0-dev.20200108: - version "3.8.0-dev.20200108" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.0-dev.20200108.tgz#ca3a4d950cd19112d80758be779fb07d577e49bc" - integrity sha512-SD3VEYUUrDGc0djorpi0zVdmVwmvuaSHta18WP3sS9X0HC7eA4izdjj07pVUc99IBpBw55ljUATm5vkNdvxX6w== - typical@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" diff --git a/package.json b/package.json index 11d823edf02f3..922d877c743bc 100644 --- a/package.json +++ b/package.json @@ -144,7 +144,7 @@ "sinon": "^1.17.2", "source-map": "^0.4.4", "ts-loader": "^4.4.2", - "typescript": "^3.8.0-dev.20200108", + "typescript": "3.8.0-beta", "typescript-formatter": "7.1.0", "underscore": "^1.8.2", "vinyl": "^2.0.0", diff --git a/yarn.lock b/yarn.lock index 36fee127c849e..38a8d55738f7e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9262,16 +9262,16 @@ typescript-formatter@7.1.0: commandpost "^1.0.0" editorconfig "^0.15.0" +typescript@3.8.0-beta: + version "3.8.0-beta" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.0-beta.tgz#acdcaf9f24c7e20b1ff0a6329d1e8d63691e2e13" + integrity sha512-mQEmQUJg0CQBhf/GSVnGscKv/jrKsrLxE01AhdjYmBNoXX2Iah3i38ufxXByXacK6Fc5Nr9oMz7MjpjgddiknA== + typescript@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q= -typescript@^3.8.0-dev.20200108: - version "3.8.0-dev.20200108" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.0-dev.20200108.tgz#ca3a4d950cd19112d80758be779fb07d577e49bc" - integrity sha512-SD3VEYUUrDGc0djorpi0zVdmVwmvuaSHta18WP3sS9X0HC7eA4izdjj07pVUc99IBpBw55ljUATm5vkNdvxX6w== - uc.micro@^1.0.1, uc.micro@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192" From ad970cc1b323e9ea8b9d422016a2adb6c890e3f3 Mon Sep 17 00:00:00 2001 From: Fabien Launay Date: Sun, 12 Jan 2020 01:43:47 +0900 Subject: [PATCH 178/315] Fix word repetition in dialogs.ts comment (#88466) --- src/vs/platform/dialogs/common/dialogs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index 68f8ac9c6d416..47ee6ba4c6113 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -230,7 +230,7 @@ export interface IFileDialogService { pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise; /** - * Shows a save file file dialog and save the file at the chosen file URI. + * Shows a save file dialog and save the file at the chosen file URI. */ pickFileToSave(options: ISaveDialogOptions): Promise; From 71df4b20ded9dc724b67817ce941b1550b3f8690 Mon Sep 17 00:00:00 2001 From: Fabien Launay Date: Sun, 12 Jan 2020 01:44:13 +0900 Subject: [PATCH 179/315] Fix word repetition in tokenClassificationExtensionPoint.ts error message (#88468) --- .../services/themes/common/tokenClassificationExtensionPoint.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/themes/common/tokenClassificationExtensionPoint.ts b/src/vs/workbench/services/themes/common/tokenClassificationExtensionPoint.ts index 77f6a8586c999..d9687a653639a 100644 --- a/src/vs/workbench/services/themes/common/tokenClassificationExtensionPoint.ts +++ b/src/vs/workbench/services/themes/common/tokenClassificationExtensionPoint.ts @@ -228,7 +228,7 @@ export class TokenClassificationExtensionPoints { if (contribution.scopes) { if ((!Array.isArray(contribution.scopes) || contribution.scopes.some(s => typeof s !== 'string'))) { - collector.error(nls.localize('invalid.scopes', "If defined, 'configuration.tokenStyleDefaults.scopes' must must be an array or strings")); + collector.error(nls.localize('invalid.scopes', "If defined, 'configuration.tokenStyleDefaults.scopes' must be an array or strings")); continue; } tokenStyleDefault.scopesToProbe = [contribution.scopes]; From b7c1982aeef05160133d52c9274cb76e2d231b9a Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Sun, 12 Jan 2020 02:39:01 -0500 Subject: [PATCH 180/315] Fixes #88242 --- .../contrib/debug/browser/baseDebugView.ts | 121 +++++++++--------- .../debug/browser/watchExpressionsView.ts | 11 +- 2 files changed, 74 insertions(+), 58 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index c40e105ffefed..160c2c8f3cdac 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -9,7 +9,7 @@ import { Expression, Variable, ExpressionContainer } from 'vs/workbench/contrib/ import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInputValidationOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -18,6 +18,7 @@ import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabe import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel'; +import { once } from 'vs/base/common/functional'; export const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; export const twistiePixels = 20; @@ -137,8 +138,7 @@ export interface IExpressionTemplateData { name: HTMLSpanElement; value: HTMLSpanElement; inputBoxContainer: HTMLElement; - enableInputBox(options: IInputBoxOptions): void; - toDispose: IDisposable[]; + toDispose: IDisposable; label: HighlightedLabel; } @@ -159,77 +159,84 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer { - name.style.display = 'none'; - value.style.display = 'none'; - inputBoxContainer.style.display = 'initial'; - - const inputBox = new InputBox(inputBoxContainer, this.contextViewService, options); - const styler = attachInputBoxStyler(inputBox, this.themeService); - - inputBox.value = replaceWhitespace(options.initialValue); - inputBox.focus(); - inputBox.select(); - - let disposed = false; - toDispose.push(inputBox); - toDispose.push(styler); - - const wrapUp = (renamed: boolean) => { - if (!disposed) { - disposed = true; - this.debugService.getViewModel().setSelectedExpression(undefined); - options.onFinish(inputBox.value, renamed); - - // need to remove the input box since this template will be reused. - inputBoxContainer.removeChild(inputBox.element); - name.style.display = 'initial'; - value.style.display = 'initial'; - inputBoxContainer.style.display = 'none'; - dispose(toDispose); - } - }; - - toDispose.push(dom.addStandardDisposableListener(inputBox.inputElement, 'keydown', (e: IKeyboardEvent) => { - const isEscape = e.equals(KeyCode.Escape); - const isEnter = e.equals(KeyCode.Enter); - if (isEscape || isEnter) { - e.preventDefault(); - e.stopPropagation(); - wrapUp(isEnter); - } - })); - toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'blur', () => { - wrapUp(true); - })); - toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'click', e => { - // Do not expand / collapse selected elements - e.preventDefault(); - e.stopPropagation(); - })); - }; - return { expression, name, value, label, enableInputBox, inputBoxContainer, toDispose }; + return { expression, name, value, label, inputBoxContainer, toDispose: Disposable.None }; } renderElement(node: ITreeNode, index: number, data: IExpressionTemplateData): void { + data.toDispose.dispose(); + data.toDispose = Disposable.None; const { element } = node; if (element === this.debugService.getViewModel().getSelectedExpression() || (element instanceof Variable && element.errorMessage)) { const options = this.getInputBoxOptions(element); if (options) { - data.enableInputBox(options); + data.toDispose = this.renderInputBox(data.name, data.value, data.inputBoxContainer, options); return; } } this.renderExpression(element, data, createMatches(node.filterData)); } + renderInputBox(nameElement: HTMLElement, valueElement: HTMLElement, inputBoxContainer: HTMLElement, options: IInputBoxOptions): IDisposable { + nameElement.style.display = 'none'; + valueElement.style.display = 'none'; + inputBoxContainer.style.display = 'initial'; + + const inputBox = new InputBox(inputBoxContainer, this.contextViewService, options); + const styler = attachInputBoxStyler(inputBox, this.themeService); + + inputBox.value = replaceWhitespace(options.initialValue); + inputBox.focus(); + inputBox.select(); + + const done = once((success: boolean, finishEditing: boolean) => { + nameElement.style.display = 'initial'; + valueElement.style.display = 'initial'; + inputBoxContainer.style.display = 'none'; + const value = inputBox.value; + dispose(toDispose); + + if (finishEditing) { + this.debugService.getViewModel().setSelectedExpression(undefined); + options.onFinish(value, success); + } + }); + + const toDispose = [ + inputBox, + dom.addStandardDisposableListener(inputBox.inputElement, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => { + const isEscape = e.equals(KeyCode.Escape); + const isEnter = e.equals(KeyCode.Enter); + if (isEscape || isEnter) { + e.preventDefault(); + e.stopPropagation(); + done(isEnter, true); + } + }), + dom.addDisposableListener(inputBox.inputElement, dom.EventType.BLUR, (e) => { + done(true, true); + }), + dom.addDisposableListener(inputBox.inputElement, dom.EventType.CLICK, e => { + // Do not expand / collapse selected elements + e.preventDefault(); + e.stopPropagation(); + }), + styler + ]; + + return toDisposable(() => { + done(false, false); + }); + } + protected abstract renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void; protected abstract getInputBoxOptions(expression: IExpression): IInputBoxOptions | undefined; + disposeElement(node: ITreeNode, index: number, templateData: IExpressionTemplateData): void { + templateData.toDispose.dispose(); + } + disposeTemplate(templateData: IExpressionTemplateData): void { - dispose(templateData.toDispose); + templateData.toDispose.dispose(); } } diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 0e2f8fd6df454..7ee2d0fcaca3d 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -69,7 +69,16 @@ export class WatchExpressionsView extends ViewPane { ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'watchAriaTreeLabel' }, "Debug Watch Expressions"), accessibilityProvider: new WatchExpressionsAccessibilityProvider(), identityProvider: { getId: (element: IExpression) => element.getId() }, - keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IExpression) => e }, + keyboardNavigationLabelProvider: { + getKeyboardNavigationLabel: (e: IExpression) => { + if (e === this.debugService.getViewModel().getSelectedExpression()) { + // Don't filter input box + return undefined; + } + + return e; + } + }, dnd: new WatchExpressionsDragAndDrop(this.debugService), overrideStyles: { listBackground: SIDE_BAR_BACKGROUND From df4a71a8708089380e25ba9f51bf2aa15226c3a8 Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Sun, 12 Jan 2020 02:50:09 -0500 Subject: [PATCH 181/315] :lipstick: --- src/vs/workbench/contrib/debug/browser/baseDebugView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 160c2c8f3cdac..3d6eafae60a49 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -213,7 +213,7 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer { + dom.addDisposableListener(inputBox.inputElement, dom.EventType.BLUR, () => { done(true, true); }), dom.addDisposableListener(inputBox.inputElement, dom.EventType.CLICK, e => { From e6651b90f937e9874aeba717cb3724190e01a34f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 12 Jan 2020 11:11:41 +0100 Subject: [PATCH 182/315] custom editors - implement auto save and backup creation over working copy events (#84672) --- src/vs/platform/files/common/files.ts | 2 - .../browser/parts/editor/editorAutoSave.ts | 73 ++++++-- src/vs/workbench/common/editor.ts | 6 +- .../common/editor/untitledTextEditorInput.ts | 14 +- .../common/editor/untitledTextEditorModel.ts | 30 +-- .../backup/common/backup.contribution.ts | 8 +- .../backup/common/backupModelTracker.ts | 85 --------- .../contrib/backup/common/backupTracker.ts | 124 +++++++++++++ .../electron-browser/backupRestorer.test.ts | 8 +- .../electron-browser/backupTracker.test.ts | 174 ++++++++++++++++++ .../customEditor/common/customEditorModel.ts | 19 ++ .../common/customEditorModelManager.ts | 2 +- .../test/browser/fileEditorTracker.test.ts | 37 ++-- .../backupFileService.test.ts | 31 +++- .../editor/test/browser/editorService.test.ts | 114 +++--------- .../test/browser/editorsObserver.test.ts | 84 +++------ .../common/filesConfigurationService.ts | 4 +- .../textfile/browser/textFileService.ts | 59 +++--- .../textfile/common/textFileEditorModel.ts | 105 +++++------ .../common/textFileEditorModelManager.ts | 27 +-- .../services/textfile/common/textfiles.ts | 10 +- .../textfile/test/textFileEditorModel.test.ts | 42 ++++- .../test/textFileEditorModelManager.test.ts | 27 --- .../common/untitledTextEditorService.ts | 21 --- .../workingCopy/common/workingCopyService.ts | 51 ++++- .../test/common/workingCopyService.test.ts | 38 ++++ .../common/editor/untitledTextEditor.test.ts | 62 +++---- 27 files changed, 720 insertions(+), 537 deletions(-) delete mode 100644 src/vs/workbench/contrib/backup/common/backupModelTracker.ts create mode 100644 src/vs/workbench/contrib/backup/common/backupTracker.ts create mode 100644 src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index a45eb2016cfa7..86252c893899b 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -787,8 +787,6 @@ export const HotExitConfiguration = { ON_EXIT_AND_WINDOW_CLOSE: 'onExitAndWindowClose' }; -export const CONTENT_CHANGE_EVENT_BUFFER_DELAY = 1000; - export const FILES_ASSOCIATIONS_CONFIG = 'files.associations'; export const FILES_EXCLUDE_CONFIG = 'files.exclude'; diff --git a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts index fd7bbf92a66f6..6be968e338f4a 100644 --- a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts +++ b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts @@ -45,7 +45,12 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution this._register(this.hostService.onDidChangeFocus(focused => this.onWindowFocusChange(focused))); this._register(this.editorService.onDidActiveEditorChange(() => this.onDidActiveEditorChange())); this._register(this.filesConfigurationService.onAutoSaveConfigurationChange(config => this.onAutoSaveConfigurationChange(config, true))); - this._register(this.workingCopyService.onDidChangeDirty(workingCopy => this.onDidWorkingCopyChangeDirty(workingCopy))); + + // Working Copy events + this._register(this.workingCopyService.onDidRegister(c => this.onDidRegister(c))); + this._register(this.workingCopyService.onDidUnregister(c => this.onDidUnregister(c))); + this._register(this.workingCopyService.onDidChangeDirty(c => this.onDidChangeDirty(c))); + this._register(this.workingCopyService.onDidChangeContent(c => this.onDidChangeContent(c))); } private onWindowFocusChange(focused: boolean): void { @@ -128,14 +133,34 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution } private saveAllDirty(options?: ISaveOptions): void { - Promise.all(this.workingCopyService.workingCopies.map(workingCopy => { + for (const workingCopy of this.workingCopyService.workingCopies) { if (workingCopy.isDirty() && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled)) { workingCopy.save(options); } - })); + } + } + + private onDidRegister(workingCopy: IWorkingCopy): void { + this.scheduleAutoSave(workingCopy); + } + + private onDidUnregister(workingCopy: IWorkingCopy): void { + this.discardAutoSave(workingCopy); + } + + private onDidChangeDirty(workingCopy: IWorkingCopy): void { + if (!workingCopy.isDirty()) { + this.discardAutoSave(workingCopy); + } + } + + private onDidChangeContent(workingCopy: IWorkingCopy): void { + if (workingCopy.isDirty()) { + this.scheduleAutoSave(workingCopy); + } } - private onDidWorkingCopyChangeDirty(workingCopy: IWorkingCopy): void { + private scheduleAutoSave(workingCopy: IWorkingCopy): void { if (typeof this.autoSaveAfterDelay !== 'number') { return; // auto save after delay must be enabled } @@ -145,22 +170,34 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution } // Clear any running auto save operation - dispose(this.pendingAutoSavesAfterDelay.get(workingCopy)); - this.pendingAutoSavesAfterDelay.delete(workingCopy); + this.discardAutoSave(workingCopy); - // Working copy got dirty - start auto save - if (workingCopy.isDirty()) { - this.logService.trace(`[editor auto save] starting auto save after ${this.autoSaveAfterDelay}ms`, workingCopy.resource.toString()); + this.logService.trace(`[editor auto save] scheduling auto save after ${this.autoSaveAfterDelay}ms`, workingCopy.resource.toString()); - const handle = setTimeout(() => { - if (workingCopy.isDirty()) { - workingCopy.save({ reason: SaveReason.AUTO }); - } - }, this.autoSaveAfterDelay); + // Schedule new auto save + const handle = setTimeout(() => { - this.pendingAutoSavesAfterDelay.set(workingCopy, toDisposable(() => clearTimeout(handle))); - } else { - this.logService.trace(`[editor auto save] clearing auto save`, workingCopy.resource.toString()); - } + // Clear disposable + this.pendingAutoSavesAfterDelay.delete(workingCopy); + + // Save if dirty + if (workingCopy.isDirty()) { + this.logService.trace(`[editor auto save] running auto save`, workingCopy.resource.toString()); + + workingCopy.save({ reason: SaveReason.AUTO }); + } + }, this.autoSaveAfterDelay); + + // Keep in map for disposal as needed + this.pendingAutoSavesAfterDelay.set(workingCopy, toDisposable(() => { + this.logService.trace(`[editor auto save] clearing pending auto save`, workingCopy.resource.toString()); + + clearTimeout(handle); + })); + } + + private discardAutoSave(workingCopy: IWorkingCopy): void { + dispose(this.pendingAutoSavesAfterDelay.get(workingCopy)); + this.pendingAutoSavesAfterDelay.delete(workingCopy); } } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 92b1874ffcf68..6a55d4bd697d7 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -317,7 +317,7 @@ export interface ISaveOptions { context?: SaveContext; /** - * Forces to load the contents of the working copy + * Forces to save the contents of the working copy * again even if the working copy is not dirty. */ force?: boolean; @@ -571,10 +571,6 @@ export abstract class TextEditorInput extends EditorInput { } async save(groupId: GroupIdentifier, options?: ITextFileSaveOptions): Promise { - if (this.isReadonly()) { - return false; // return early if editor is readonly - } - return this.textFileService.save(this.resource, options); } diff --git a/src/vs/workbench/common/editor/untitledTextEditorInput.ts b/src/vs/workbench/common/editor/untitledTextEditorInput.ts index cd3091c508111..2366bcf133b8d 100644 --- a/src/vs/workbench/common/editor/untitledTextEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledTextEditorInput.ts @@ -28,9 +28,6 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin private static readonly MEMOIZER = createMemoizer(); - private readonly _onDidModelChangeContent = this._register(new Emitter()); - readonly onDidModelChangeContent = this._onDidModelChangeContent.event; - private readonly _onDidModelChangeEncoding = this._register(new Emitter()); readonly onDidModelChangeEncoding = this._onDidModelChangeEncoding.event; @@ -147,6 +144,8 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin } isDirty(): boolean { + + // Always trust the model first if existing if (this.cachedModel) { return this.cachedModel.isDirty(); } @@ -156,7 +155,13 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin return false; } - // untitled files with an associated path or associated resource + // A input with initial value is always dirty + if (this.initialValue && this.initialValue.length > 0) { + return true; + } + + // A input with associated path is always dirty because it is the intent + // of the user to create a new file at that location through saving return this.hasAssociatedFilePath; } @@ -274,7 +279,6 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin const model = this._register(this.instantiationService.createInstance(UntitledTextEditorModel, this.preferredMode, this.resource, this.hasAssociatedFilePath, this.initialValue, this.preferredEncoding)); // re-emit some events from the model - this._register(model.onDidChangeContent(() => this._onDidModelChangeContent.fire())); this._register(model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); this._register(model.onDidChangeEncoding(() => this._onDidModelChangeEncoding.fire())); diff --git a/src/vs/workbench/common/editor/untitledTextEditorModel.ts b/src/vs/workbench/common/editor/untitledTextEditorModel.ts index a34992acbc991..a110d1f782f83 100644 --- a/src/vs/workbench/common/editor/untitledTextEditorModel.ts +++ b/src/vs/workbench/common/editor/untitledTextEditorModel.ts @@ -6,11 +6,9 @@ import { IEncodingSupport, ISaveOptions } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { URI } from 'vs/base/common/uri'; -import { CONTENT_CHANGE_EVENT_BUFFER_DELAY } from 'vs/platform/files/common/files'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { Event, Emitter } from 'vs/base/common/event'; -import { RunOnceScheduler } from 'vs/base/common/async'; +import { Emitter } from 'vs/base/common/event'; import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backup/common/backup'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; import { ITextBufferFactory } from 'vs/editor/common/model'; @@ -21,22 +19,19 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile export class UntitledTextEditorModel extends BaseTextEditorModel implements IEncodingSupport, IWorkingCopy { - static DEFAULT_CONTENT_CHANGE_BUFFER_DELAY = CONTENT_CHANGE_EVENT_BUFFER_DELAY; + private readonly _onDidChangeContent = this._register(new Emitter()); + readonly onDidChangeContent = this._onDidChangeContent.event; - private readonly _onDidChangeContent: Emitter = this._register(new Emitter()); - readonly onDidChangeContent: Event = this._onDidChangeContent.event; + private readonly _onDidChangeDirty = this._register(new Emitter()); + readonly onDidChangeDirty = this._onDidChangeDirty.event; - private readonly _onDidChangeDirty: Emitter = this._register(new Emitter()); - readonly onDidChangeDirty: Event = this._onDidChangeDirty.event; - - private readonly _onDidChangeEncoding: Emitter = this._register(new Emitter()); - readonly onDidChangeEncoding: Event = this._onDidChangeEncoding.event; + private readonly _onDidChangeEncoding = this._register(new Emitter()); + readonly onDidChangeEncoding = this._onDidChangeEncoding.event; readonly capabilities = WorkingCopyCapabilities.Untitled; private dirty = false; private versionId = 0; - private readonly contentChangeEventScheduler = this._register(new RunOnceScheduler(() => this._onDidChangeContent.fire(), UntitledTextEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY)); private configuredEncoding: string | undefined; constructor( @@ -124,18 +119,13 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IEnc async revert(): Promise { this.setDirty(false); - // Handle content change event buffered - this.contentChangeEventScheduler.schedule(); - return true; } - backup(): Promise { + async backup(): Promise { if (this.isResolved()) { return this.backupFileService.backupResource(this.resource, this.createSnapshot(), this.versionId); } - - return Promise.resolve(); } hasBackup(): boolean { @@ -204,8 +194,8 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IEnc this.setDirty(true); } - // Handle content change event buffered - this.contentChangeEventScheduler.schedule(); + // Emit as event + this._onDidChangeContent.fire(); } isReadonly(): boolean { diff --git a/src/vs/workbench/contrib/backup/common/backup.contribution.ts b/src/vs/workbench/contrib/backup/common/backup.contribution.ts index f3ac6e3a0bdc0..9800a1c947246 100644 --- a/src/vs/workbench/contrib/backup/common/backup.contribution.ts +++ b/src/vs/workbench/contrib/backup/common/backup.contribution.ts @@ -5,12 +5,12 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { BackupModelTracker } from 'vs/workbench/contrib/backup/common/backupModelTracker'; +import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker'; import { BackupRestorer } from 'vs/workbench/contrib/backup/common/backupRestorer'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -// Register Backup Model Tracker -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(BackupModelTracker, LifecyclePhase.Starting); +// Register Backup Tracker +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(BackupTracker, LifecyclePhase.Starting); // Register Backup Restorer -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(BackupRestorer, LifecyclePhase.Starting); \ No newline at end of file +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(BackupRestorer, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/backup/common/backupModelTracker.ts b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts deleted file mode 100644 index 2f44e59ad78cd..0000000000000 --- a/src/vs/workbench/contrib/backup/common/backupModelTracker.ts +++ /dev/null @@ -1,85 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { URI as Uri } from 'vs/base/common/uri'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { ITextFileService, TextFileModelChangeEvent, StateChange } from 'vs/workbench/services/textfile/common/textfiles'; -import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { CONTENT_CHANGE_EVENT_BUFFER_DELAY } from 'vs/platform/files/common/files'; -import { IFilesConfigurationService, IAutoSaveConfiguration } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; - -const AUTO_SAVE_AFTER_DELAY_DISABLED_TIME = CONTENT_CHANGE_EVENT_BUFFER_DELAY + 500; - -export class BackupModelTracker extends Disposable implements IWorkbenchContribution { - - private configuredAutoSaveAfterDelay = false; - - constructor( - @IBackupFileService private readonly backupFileService: IBackupFileService, - @ITextFileService private readonly textFileService: ITextFileService, - @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, - @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService - ) { - super(); - - this.registerListeners(); - } - - private registerListeners() { - - // Listen for text file model changes - this._register(this.textFileService.models.onModelContentChanged(e => this.onTextFileModelChanged(e))); - this._register(this.textFileService.models.onModelSaved(e => this.discardBackup(e.resource))); - this._register(this.textFileService.models.onModelDisposed(e => this.discardBackup(e))); - - // Listen for untitled model changes - this._register(this.untitledTextEditorService.onDidCreate(e => this.onUntitledModelCreated(e))); - this._register(this.untitledTextEditorService.onDidChangeContent(e => this.onUntitledModelChanged(e))); - this._register(this.untitledTextEditorService.onDidDisposeModel(e => this.discardBackup(e))); - - // Listen to auto save config changes - this._register(this.filesConfigurationService.onAutoSaveConfigurationChange(c => this.onAutoSaveConfigurationChange(c))); - } - - private onAutoSaveConfigurationChange(configuration: IAutoSaveConfiguration): void { - this.configuredAutoSaveAfterDelay = typeof configuration.autoSaveDelay === 'number' && configuration.autoSaveDelay < AUTO_SAVE_AFTER_DELAY_DISABLED_TIME; - } - - private onTextFileModelChanged(event: TextFileModelChangeEvent): void { - if (event.kind === StateChange.REVERTED) { - // This must proceed even if auto save after delay is configured in order to clean up - // any backups made before the config change - this.discardBackup(event.resource); - } else if (event.kind === StateChange.CONTENT_CHANGE) { - // Do not backup when auto save after delay is configured - if (!this.configuredAutoSaveAfterDelay) { - const model = this.textFileService.models.get(event.resource); - if (model) { - model.backup(); - } - } - } - } - - private onUntitledModelCreated(resource: Uri): void { - if (this.untitledTextEditorService.isDirty(resource)) { - this.untitledTextEditorService.createOrGet({ resource }).resolve().then(model => model.backup()); - } - } - - private onUntitledModelChanged(resource: Uri): void { - if (this.untitledTextEditorService.isDirty(resource)) { - this.untitledTextEditorService.createOrGet({ resource }).resolve().then(model => model.backup()); - } else { - this.discardBackup(resource); - } - } - - private discardBackup(resource: Uri): void { - this.backupFileService.discardResourceBackup(resource); - } -} diff --git a/src/vs/workbench/contrib/backup/common/backupTracker.ts b/src/vs/workbench/contrib/backup/common/backupTracker.ts new file mode 100644 index 0000000000000..8496b31a7cb85 --- /dev/null +++ b/src/vs/workbench/contrib/backup/common/backupTracker.ts @@ -0,0 +1,124 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IFilesConfigurationService, IAutoSaveConfiguration } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { ILogService } from 'vs/platform/log/common/log'; + +export class BackupTracker extends Disposable implements IWorkbenchContribution { + + // Disable backup for when a short auto-save delay is configured with + // the rationale that the auto save will trigger a save periodically + // anway and thus creating frequent backups is not useful + // + // This will only apply to working copies that are not untitled where + // auto save is actually saving. + private static DISABLE_BACKUP_AUTO_SAVE_THRESHOLD = 1500; + + // Delay creation of backups when content changes to avoid too much + // load on the backup service when the user is typing into the editor + protected static BACKUP_FROM_CONTENT_CHANGE_DELAY = 1000; + + private backupsDisabledForAutoSaveables = false; + + private readonly pendingBackups = new Map(); + + constructor( + @IBackupFileService private readonly backupFileService: IBackupFileService, + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, + @ILogService private readonly logService: ILogService + ) { + super(); + + // Figure out initial auto save config + this.onAutoSaveConfigurationChange(filesConfigurationService.getAutoSaveConfiguration()); + + this.registerListeners(); + } + + private registerListeners() { + + // Working Copy events + this._register(this.workingCopyService.onDidRegister(c => this.onDidRegister(c))); + this._register(this.workingCopyService.onDidUnregister(c => this.onDidUnregister(c))); + this._register(this.workingCopyService.onDidChangeDirty(c => this.onDidChangeDirty(c))); + this._register(this.workingCopyService.onDidChangeContent(c => this.onDidChangeContent(c))); + + // Listen to auto save config changes + this._register(this.filesConfigurationService.onAutoSaveConfigurationChange(c => this.onAutoSaveConfigurationChange(c))); + } + + private onDidRegister(workingCopy: IWorkingCopy): void { + this.scheduleBackup(workingCopy); + } + + private onDidUnregister(workingCopy: IWorkingCopy): void { + this.discardBackup(workingCopy); + } + + private onDidChangeDirty(workingCopy: IWorkingCopy): void { + if (!workingCopy.isDirty()) { + this.discardBackup(workingCopy); + } + } + + private onDidChangeContent(workingCopy: IWorkingCopy): void { + if (workingCopy.isDirty()) { + this.scheduleBackup(workingCopy); + } + } + + private onAutoSaveConfigurationChange(configuration: IAutoSaveConfiguration): void { + this.backupsDisabledForAutoSaveables = typeof configuration.autoSaveDelay === 'number' && configuration.autoSaveDelay < BackupTracker.DISABLE_BACKUP_AUTO_SAVE_THRESHOLD; + } + + private scheduleBackup(workingCopy: IWorkingCopy): void { + if (this.backupsDisabledForAutoSaveables && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled)) { + return; // skip if auto save is enabled with a short delay + } + + // Clear any running backup operation + dispose(this.pendingBackups.get(workingCopy)); + this.pendingBackups.delete(workingCopy); + + this.logService.trace(`[backup tracker] scheduling backup`, workingCopy.resource.toString()); + + // Schedule new backup + const handle = setTimeout(() => { + + // Clear disposable + this.pendingBackups.delete(workingCopy); + + // Backup if dirty + if (workingCopy.isDirty()) { + this.logService.trace(`[backup tracker] running backup`, workingCopy.resource.toString()); + + workingCopy.backup(); + } + }, BackupTracker.BACKUP_FROM_CONTENT_CHANGE_DELAY); + + // Keep in map for disposal as needed + this.pendingBackups.set(workingCopy, toDisposable(() => { + this.logService.trace(`[backup tracker] clearing pending backup`, workingCopy.resource.toString()); + + clearTimeout(handle); + })); + } + + private discardBackup(workingCopy: IWorkingCopy): void { + this.logService.trace(`[backup tracker] discarding backup`, workingCopy.resource.toString()); + + // Clear any running backup operation + dispose(this.pendingBackups.get(workingCopy)); + this.pendingBackups.delete(workingCopy); + + // Forward to backup file service + this.backupFileService.discardResourceBackup(workingCopy.resource); + } +} diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts index cc5d10a70a4ef..e3fa6b8c44ea9 100644 --- a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts @@ -14,7 +14,7 @@ import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { DefaultEndOfLine } from 'vs/editor/common/model'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { hashPath } from 'vs/workbench/services/backup/node/backupFileService'; -import { BackupModelTracker } from 'vs/workbench/contrib/backup/common/backupModelTracker'; +import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker'; import { TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; @@ -60,7 +60,7 @@ class ServiceAccessor { } } -suite('BackupModelRestorer', () => { +suite('BackupRestorer', () => { let accessor: ServiceAccessor; let disposables: IDisposable[] = []; @@ -111,7 +111,9 @@ suite('BackupModelRestorer', () => { accessor = instantiationService.createInstance(ServiceAccessor); - const tracker = instantiationService.createInstance(BackupModelTracker); + await part.whenRestored; + + const tracker = instantiationService.createInstance(BackupTracker); const restorer = instantiationService.createInstance(TestBackupRestorer); // Backup 2 normal files and 2 untitled file diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts new file mode 100644 index 0000000000000..945cb8352de5b --- /dev/null +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts @@ -0,0 +1,174 @@ +/*--------------------------------------------------------------------------------------------- + * 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 * as platform from 'vs/base/common/platform'; +import * as os from 'os'; +import * as path from 'vs/base/common/path'; +import * as pfs from 'vs/base/node/pfs'; +import { URI } from 'vs/base/common/uri'; +import { getRandomTestPath } from 'vs/base/test/node/testUtils'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { hashPath } from 'vs/workbench/services/backup/node/backupFileService'; +import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker'; +import { TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { EditorInput } from 'vs/workbench/common/editor'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; +import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { NodeTestBackupFileService } from 'vs/workbench/services/backup/test/electron-browser/backupFileService.test'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { toResource } from 'vs/base/test/common/utils'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { ILogService } from 'vs/platform/log/common/log'; + +const userdataDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backuprestorer'); +const backupHome = path.join(userdataDir, 'Backups'); +const workspacesJsonPath = path.join(backupHome, 'workspaces.json'); + +const workspaceResource = URI.file(platform.isWindows ? 'c:\\workspace' : '/workspace'); +const workspaceBackupPath = path.join(backupHome, hashPath(workspaceResource)); + +class ServiceAccessor { + constructor( + @ITextFileService public textFileService: TestTextFileService, + @IUntitledTextEditorService public untitledTextEditorService: IUntitledTextEditorService, + @IEditorService public editorService: IEditorService, + @IBackupFileService public backupFileService: NodeTestBackupFileService + ) { + } +} + +class TestBackupTracker extends BackupTracker { + + constructor( + @IBackupFileService backupFileService: IBackupFileService, + @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, + @IWorkingCopyService workingCopyService: IWorkingCopyService, + @ILogService logService: ILogService + ) { + super(backupFileService, filesConfigurationService, workingCopyService, logService); + + // Reduce timeout for tests + BackupTracker.BACKUP_FROM_CONTENT_CHANGE_DELAY = 10; + } +} + +suite('BackupTracker', () => { + let accessor: ServiceAccessor; + + let disposables: IDisposable[] = []; + + setup(async () => { + disposables.push(Registry.as(EditorExtensions.Editors).registerEditor( + EditorDescriptor.create( + TextFileEditor, + TextFileEditor.ID, + 'Text File Editor' + ), + [new SyncDescriptor(FileEditorInput)] + )); + + // Delete any existing backups completely and then re-create it. + await pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); + await pfs.mkdirp(backupHome); + + return pfs.writeFile(workspacesJsonPath, ''); + }); + + teardown(async () => { + dispose(disposables); + disposables = []; + + (accessor.textFileService.models).clear(); + (accessor.textFileService.models).dispose(); + accessor.untitledTextEditorService.revertAll(); + + return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); + }); + + async function createTracker(): Promise<[ServiceAccessor, EditorPart, BackupTracker]> { + const backupFileService = new NodeTestBackupFileService(workspaceBackupPath); + const instantiationService = workbenchInstantiationService(); + instantiationService.stub(IBackupFileService, backupFileService); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + instantiationService.stub(IEditorGroupsService, part); + + const editorService: EditorService = instantiationService.createInstance(EditorService); + instantiationService.stub(IEditorService, editorService); + + accessor = instantiationService.createInstance(ServiceAccessor); + + await part.whenRestored; + + const tracker = instantiationService.createInstance(TestBackupTracker); + + return [accessor, part, tracker]; + } + + test('Track backups (untitled)', async function () { + this.timeout(20000); + + const [accessor, part, tracker] = await createTracker(); + + const untitledEditor = accessor.untitledTextEditorService.createOrGet(); + await accessor.editorService.openEditor(untitledEditor, { pinned: true }); + + const untitledModel = await untitledEditor.resolve(); + untitledModel.textEditorModel.setValue('Super Good'); + + await accessor.backupFileService.joinBackupResource(); + + assert.equal(accessor.backupFileService.hasBackupSync(untitledEditor.getResource()), true); + + untitledModel.dispose(); + + await accessor.backupFileService.joinDiscardBackup(); + + assert.equal(accessor.backupFileService.hasBackupSync(untitledEditor.getResource()), false); + + part.dispose(); + tracker.dispose(); + }); + + test('Track backups (file)', async function () { + this.timeout(20000); + + const [accessor, part, tracker] = await createTracker(); + + const resource = toResource.call(this, '/path/index.txt'); + await accessor.editorService.openEditor({ resource, options: { pinned: true } }); + + const fileModel = accessor.textFileService.models.get(resource); + fileModel?.textEditorModel?.setValue('Super Good'); + + await accessor.backupFileService.joinBackupResource(); + + assert.equal(accessor.backupFileService.hasBackupSync(resource), true); + + fileModel?.dispose(); + + await accessor.backupFileService.joinDiscardBackup(); + + assert.equal(accessor.backupFileService.hasBackupSync(resource), false); + + part.dispose(); + tracker.dispose(); + }); +}); diff --git a/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts b/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts index e4913f6962cee..6f7de36e6b3d0 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts @@ -39,6 +39,9 @@ export class CustomEditorModel extends Disposable implements ICustomEditorModel protected readonly _onDidChangeDirty: Emitter = this._register(new Emitter()); readonly onDidChangeDirty: Event = this._onDidChangeDirty.event; + protected readonly _onDidChangeContent: Emitter = this._register(new Emitter()); + readonly onDidChangeContent: Event = this._onDidChangeContent.event; + //#endregion protected readonly _onUndo = this._register(new Emitter()); @@ -62,12 +65,21 @@ export class CustomEditorModel extends Disposable implements ICustomEditorModel this._currentEditIndex = this._edits.length - 1; this.updateDirty(); this._onApplyEdit.fire([edit]); + this.updateContentChanged(); } private updateDirty() { + // TODO@matt this should to be more fine grained and avoid + // emitting events if there was no change actually this._onDidChangeDirty.fire(); } + private updateContentChanged() { + // TODO@matt revisit that this method is being called correctly + // on each case of content change within the custom editor + this._onDidChangeContent.fire(); + } + public async save(_options?: ISaveOptions): Promise { const untils: Promise[] = []; const handler: CustomEditorSaveEvent = { @@ -125,6 +137,7 @@ export class CustomEditorModel extends Disposable implements ICustomEditorModel this._currentEditIndex = this._savePoint; this._edits.splice(this._currentEditIndex + 1, this._edits.length - this._currentEditIndex); this.updateDirty(); + this.updateContentChanged(); return true; } @@ -139,6 +152,7 @@ export class CustomEditorModel extends Disposable implements ICustomEditorModel this._onUndo.fire([{ data: undoneEdit }]); this.updateDirty(); + this.updateContentChanged(); } public redo() { @@ -153,5 +167,10 @@ export class CustomEditorModel extends Disposable implements ICustomEditorModel this._onApplyEdit.fire([{ data: redoneEdit }]); this.updateDirty(); + this.updateContentChanged(); + } + + public async backup(): Promise { + //TODO@matt forward to extension } } diff --git a/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts b/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts index b97897df8c4dd..62271532b12d3 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditorModelManager.ts @@ -29,7 +29,7 @@ export class CustomEditorModelManager implements ICustomEditorModelManager { const model = new CustomEditorModel(resource); const disposables = new DisposableStore(); - this._workingCopyService.registerWorkingCopy(model); + disposables.add(this._workingCopyService.registerWorkingCopy(model)); this._models.set(this.key(resource, viewType), { model, disposables }); return model; } diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts index 8c06d19a919cc..f0362d48c0f04 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts @@ -83,7 +83,7 @@ suite('Files - FileEditorTracker', () => { (accessor.textFileService.models).dispose(); }); - test('dirty text file model opens as editor', async function () { + async function createTracker(): Promise<[EditorPart, ServiceAccessor, FileEditorTracker]> { const instantiationService = workbenchInstantiationService(); const part = instantiationService.createInstance(EditorPart); @@ -97,18 +97,26 @@ suite('Files - FileEditorTracker', () => { const accessor = instantiationService.createInstance(ServiceAccessor); + await part.whenRestored; + const tracker = instantiationService.createInstance(FileEditorTracker); + return [part, accessor, tracker]; + } + + test('dirty text file model opens as editor', async function () { + const [part, accessor, tracker] = await createTracker(); + const resource = toResource.call(this, '/path/index.txt'); - assert.ok(!editorService.isOpen({ resource })); + assert.ok(!accessor.editorService.isOpen({ resource })); const model = await accessor.textFileService.models.loadOrCreate(resource) as IResolvedTextFileEditorModel; model.textEditorModel.setValue('Super Good'); - await awaitEditorOpening(editorService); - assert.ok(editorService.isOpen({ resource })); + await awaitEditorOpening(accessor.editorService); + assert.ok(accessor.editorService.isOpen({ resource })); part.dispose(); tracker.dispose(); @@ -117,30 +125,17 @@ suite('Files - FileEditorTracker', () => { }); test('dirty untitled text file model opens as editor', async function () { - const instantiationService = workbenchInstantiationService(); - - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - instantiationService.stub(IEditorGroupsService, part); - - const editorService: EditorService = instantiationService.createInstance(EditorService); - instantiationService.stub(IEditorService, editorService); - - const accessor = instantiationService.createInstance(ServiceAccessor); - - const tracker = instantiationService.createInstance(FileEditorTracker); + const [part, accessor, tracker] = await createTracker(); const untitledEditor = accessor.untitledTextEditorService.createOrGet(); const model = await untitledEditor.resolve(); - assert.ok(!editorService.isOpen(untitledEditor)); + assert.ok(!accessor.editorService.isOpen(untitledEditor)); model.textEditorModel.setValue('Super Good'); - await awaitEditorOpening(editorService); - assert.ok(editorService.isOpen(untitledEditor)); + await awaitEditorOpening(accessor.editorService); + assert.ok(accessor.editorService.isOpen(untitledEditor)); part.dispose(); tracker.dispose(); diff --git a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts index f49293fd2ab16..bd0644729e80d 100644 --- a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts +++ b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts @@ -14,7 +14,7 @@ import { URI } from 'vs/base/common/uri'; import { BackupFilesModel } from 'vs/workbench/services/backup/common/backupFileService'; import { TextModel, createTextBufferFactory } from 'vs/editor/common/model/textModel'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; -import { DefaultEndOfLine } from 'vs/editor/common/model'; +import { DefaultEndOfLine, ITextSnapshot } from 'vs/editor/common/model'; import { Schemas } from 'vs/base/common/network'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { FileService } from 'vs/platform/files/common/fileService'; @@ -57,6 +57,9 @@ export class NodeTestBackupFileService extends BackupFileService { readonly fileService: IFileService; + private backupResourceJoiners: Function[]; + private discardBackupJoiners: Function[]; + constructor(workspaceBackupPath: string) { const environmentService = new TestBackupEnvironmentService(workspaceBackupPath); const fileService = new FileService(new NullLogService()); @@ -67,11 +70,37 @@ export class NodeTestBackupFileService extends BackupFileService { super(environmentService, fileService); this.fileService = fileService; + this.backupResourceJoiners = []; + this.discardBackupJoiners = []; } toBackupResource(resource: URI): URI { return super.toBackupResource(resource); } + + joinBackupResource(): Promise { + return new Promise(resolve => this.backupResourceJoiners.push(resolve)); + } + + async backupResource(resource: URI, content: ITextSnapshot, versionId?: number, meta?: any): Promise { + await super.backupResource(resource, content, versionId, meta); + + while (this.backupResourceJoiners.length) { + this.backupResourceJoiners.pop()!(); + } + } + + joinDiscardBackup(): Promise { + return new Promise(resolve => this.discardBackupJoiners.push(resolve)); + } + + async discardResourceBackup(resource: URI): Promise { + await super.discardResourceBackup(resource); + + while (this.discardBackupJoiners.length) { + this.discardBackupJoiners.pop()!(); + } + } } suite('BackupFileService', () => { diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index fe74fba0aea33..67ded3c54dce0 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -14,14 +14,12 @@ import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService import { EditorService, DelegatingEditorService } from 'vs/workbench/services/editor/browser/editorService'; import { IEditorGroup, IEditorGroupsService, GroupDirection, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; -import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { timeout } from 'vs/base/common/async'; import { toResource } from 'vs/base/test/common/utils'; import { IFileService } from 'vs/platform/files/common/files'; @@ -32,6 +30,7 @@ import { NullFileSystemProvider } from 'vs/platform/files/test/common/nullFileSy import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; const TEST_EDITOR_ID = 'MyTestEditorForEditorService'; const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorService'; @@ -120,16 +119,23 @@ suite('EditorService', () => { disposables = []; }); - test('basics', async () => { - const partInstantiator = workbenchInstantiationService(); + function createEditorService(): [EditorPart, EditorService, IInstantiationService] { + const instantiationService = workbenchInstantiationService(); - const part = partInstantiator.createInstance(EditorPart); + const part = instantiationService.createInstance(EditorPart); part.create(document.createElement('div')); part.layout(400, 300); - const testInstantiationService = partInstantiator.createChild(new ServiceCollection([IEditorGroupsService, part])); + instantiationService.stub(IEditorGroupsService, part); - const service: EditorServiceImpl = testInstantiationService.createInstance(EditorService); + const editorService = instantiationService.createInstance(EditorService); + instantiationService.stub(IEditorService, editorService); + + return [part, editorService, instantiationService]; + } + + test('basics', async () => { + const [part, service, testInstantiationService] = createEditorService(); let input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-basics')); let otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-basics')); @@ -212,15 +218,7 @@ suite('EditorService', () => { }); test('openEditors() / replaceEditors()', async () => { - const partInstantiator = workbenchInstantiationService(); - - const part = partInstantiator.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - const testInstantiationService = partInstantiator.createChild(new ServiceCollection([IEditorGroupsService, part])); - - const service: IEditorService = testInstantiationService.createInstance(EditorService); + const [part, service, testInstantiationService] = createEditorService(); const input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-openEditors')); const otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-openEditors')); @@ -242,7 +240,7 @@ suite('EditorService', () => { test('caching', function () { const instantiationService = workbenchInstantiationService(); - const service: EditorService = instantiationService.createInstance(EditorService); + const service = instantiationService.createInstance(EditorService); // Cached Input (Files) const fileResource1 = toResource.call(this, '/foo/bar/cache1.js'); @@ -291,7 +289,7 @@ suite('EditorService', () => { test('createInput', async function () { const instantiationService = workbenchInstantiationService(); - const service: EditorService = instantiationService.createInstance(EditorService); + const service = instantiationService.createInstance(EditorService); const mode = 'create-input-test'; ModesRegistry.registerLanguage({ @@ -395,15 +393,7 @@ suite('EditorService', () => { }); test('close editor does not dispose when editor opened in other group', async () => { - const partInstantiator = workbenchInstantiationService(); - - const part = partInstantiator.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - const testInstantiationService = partInstantiator.createChild(new ServiceCollection([IEditorGroupsService, part])); - - const service: IEditorService = testInstantiationService.createInstance(EditorService); + const [part, service, testInstantiationService] = createEditorService(); const input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-close1')); @@ -432,15 +422,7 @@ suite('EditorService', () => { }); test('open to the side', async () => { - const partInstantiator = workbenchInstantiationService(); - - const part = partInstantiator.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - const testInstantiationService = partInstantiator.createChild(new ServiceCollection([IEditorGroupsService, part])); - - const service: IEditorService = testInstantiationService.createInstance(EditorService); + const [part, service, testInstantiationService] = createEditorService(); const input1 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource1-openside')); const input2 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-openside')); @@ -466,15 +448,7 @@ suite('EditorService', () => { }); test('editor group activation', async () => { - const partInstantiator = workbenchInstantiationService(); - - const part = partInstantiator.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - const testInstantiationService = partInstantiator.createChild(new ServiceCollection([IEditorGroupsService, part])); - - const service: IEditorService = testInstantiationService.createInstance(EditorService); + const [part, service, testInstantiationService] = createEditorService(); const input1 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource1-openside')); const input2 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-openside')); @@ -509,15 +483,7 @@ suite('EditorService', () => { }); test('active editor change / visible editor change events', async function () { - const partInstantiator = workbenchInstantiationService(); - - const part = partInstantiator.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - const testInstantiationService = partInstantiator.createChild(new ServiceCollection([IEditorGroupsService, part])); - - const service: EditorServiceImpl = testInstantiationService.createInstance(EditorService); + const [part, service, testInstantiationService] = createEditorService(); let input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); let otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); @@ -737,15 +703,7 @@ suite('EditorService', () => { }); test('two active editor change events when opening editor to the side', async function () { - const partInstantiator = workbenchInstantiationService(); - - const part = partInstantiator.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - const testInstantiationService = partInstantiator.createChild(new ServiceCollection([IEditorGroupsService, part])); - - const service: EditorServiceImpl = testInstantiationService.createInstance(EditorService); + const [part, service, testInstantiationService] = createEditorService(); let input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); @@ -782,15 +740,7 @@ suite('EditorService', () => { }); test('activeTextEditorWidget / activeTextEditorMode', async () => { - const partInstantiator = workbenchInstantiationService(); - - const part = partInstantiator.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - const testInstantiationService = partInstantiator.createChild(new ServiceCollection([IEditorGroupsService, part])); - - const service: EditorServiceImpl = testInstantiationService.createInstance(EditorService); + const [part, service] = createEditorService(); await part.whenRestored; @@ -805,15 +755,7 @@ suite('EditorService', () => { }); test('openEditor returns NULL when opening fails or is inactive', async function () { - const partInstantiator = workbenchInstantiationService(); - - const part = partInstantiator.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - const testInstantiationService = partInstantiator.createChild(new ServiceCollection([IEditorGroupsService, part])); - - const service: EditorServiceImpl = testInstantiationService.createInstance(EditorService); + const [part, service, testInstantiationService] = createEditorService(); const input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); const otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-inactive')); @@ -835,15 +777,7 @@ suite('EditorService', () => { }); test('save, saveAll, revertAll', async function () { - const partInstantiator = workbenchInstantiationService(); - - const part = partInstantiator.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - const testInstantiationService = partInstantiator.createChild(new ServiceCollection([IEditorGroupsService, part])); - - const service: IEditorService = testInstantiationService.createInstance(EditorService); + const [part, service, testInstantiationService] = createEditorService(); const input1 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource1-openside')); input1.dirty = true; diff --git a/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts b/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts index 97bb769736a92..1dae9361931b8 100644 --- a/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts @@ -106,9 +106,9 @@ suite('EditorsObserver', function () { disposables = []; }); - - test('basics (single group)', async () => { + async function createPart(): Promise { const instantiationService = workbenchInstantiationService(); + instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); const part = instantiationService.createInstance(EditorPart); part.create(document.createElement('div')); @@ -116,8 +116,20 @@ suite('EditorsObserver', function () { await part.whenRestored; + return part; + } + + async function createEditorObserver(): Promise<[EditorPart, EditorsObserver]> { + const part = await createPart(); + const observer = new EditorsObserver(part, new TestStorageService()); + return [part, observer]; + } + + test('basics (single group)', async () => { + const [part, observer] = await createEditorObserver(); + let observerChangeListenerCalled = false; const listener = observer.onDidChange(() => { observerChangeListenerCalled = true; @@ -183,18 +195,10 @@ suite('EditorsObserver', function () { }); test('basics (multi group)', async () => { - const instantiationService = workbenchInstantiationService(); - - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - await part.whenRestored; + const [part, observer] = await createEditorObserver(); const rootGroup = part.activeGroup; - const observer = new EditorsObserver(part, new TestStorageService()); - let currentEditorsMRU = observer.editors; assert.equal(currentEditorsMRU.length, 0); @@ -252,15 +256,7 @@ suite('EditorsObserver', function () { }); test('copy group', async () => { - const instantiationService = workbenchInstantiationService(); - - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - await part.whenRestored; - - const observer = new EditorsObserver(part, new TestStorageService()); + const [part, observer] = await createEditorObserver(); const input1 = new EditorsObserverTestEditorInput(URI.parse('foo://bar1')); const input2 = new EditorsObserverTestEditorInput(URI.parse('foo://bar2')); @@ -303,14 +299,7 @@ suite('EditorsObserver', function () { }); test('initial editors are part of observer and state is persisted & restored (single group)', async () => { - const instantiationService = workbenchInstantiationService(); - instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); - - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - await part.whenRestored; + const part = await createPart(); const rootGroup = part.activeGroup; @@ -353,13 +342,7 @@ suite('EditorsObserver', function () { }); test('initial editors are part of observer (multi group)', async () => { - const instantiationService = workbenchInstantiationService(); - - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - await part.whenRestored; + const part = await createPart(); const rootGroup = part.activeGroup; @@ -404,14 +387,7 @@ suite('EditorsObserver', function () { }); test('observer does not restore editors that cannot be serialized', async () => { - const instantiationService = workbenchInstantiationService(); - instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); - - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - await part.whenRestored; + const part = await createPart(); const rootGroup = part.activeGroup; @@ -440,18 +416,9 @@ suite('EditorsObserver', function () { }); test('observer closes editors when limit reached (across all groups)', async () => { - const instantiationService = workbenchInstantiationService(); - - instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); - - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - + const part = await createPart(); part.enforcePartOptions({ limit: { enabled: true, value: 3 } }); - await part.whenRestored; - const storage = new TestStorageService(); const observer = new EditorsObserver(part, storage); @@ -501,18 +468,9 @@ suite('EditorsObserver', function () { }); test('observer closes editors when limit reached (in group)', async () => { - const instantiationService = workbenchInstantiationService(); - - instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); - - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - + const part = await createPart(); part.enforcePartOptions({ limit: { enabled: true, value: 3, perEditorGroup: true } }); - await part.whenRestored; - const storage = new TestStorageService(); const observer = new EditorsObserver(part, storage); diff --git a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts index 63481a2e16222..4d60e10c853d6 100644 --- a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts +++ b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts @@ -41,10 +41,10 @@ export interface IFilesConfigurationService { readonly onAutoSaveConfigurationChange: Event; - getAutoSaveMode(): AutoSaveMode; - getAutoSaveConfiguration(): IAutoSaveConfiguration; + getAutoSaveMode(): AutoSaveMode; + toggleAutoSave(): Promise; //#endregion diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 0c0d2cad011f5..3c0ece2ce4356 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -8,8 +8,8 @@ import { URI } from 'vs/base/common/uri'; import { Emitter, AsyncEmitter } from 'vs/base/common/event'; import * as platform from 'vs/base/common/platform'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, FileOperationWillRunEvent, FileOperationDidRunEvent, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; -import { SaveReason, IRevertOptions, IEncodingSupport } from 'vs/workbench/common/editor'; +import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, ITextFileEditorModelManager, ITextFileEditorModel, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, FileOperationWillRunEvent, FileOperationDidRunEvent, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { IRevertOptions, IEncodingSupport } from 'vs/workbench/common/editor'; import { ILifecycleService, ShutdownReason, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IFileService, FileOperationError, FileOperationResult, HotExitConfiguration, IFileStatWithMetadata, ICreateFileOptions, FileOperation } from 'vs/platform/files/common/files'; @@ -482,19 +482,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex //#region save/revert async save(resource: URI, options?: ITextFileSaveOptions): Promise { - - // Run a forced save if we detect the file is not dirty so that save participants can still run - if (options?.force && this.fileService.canHandleResource(resource) && !this.isDirty(resource)) { - const model = this._models.get(resource); - if (model) { - options.reason = SaveReason.EXPLICIT; - - await model.save(options); - - return !model.isDirty(); - } - } - return !(await this.saveAll([resource], options)).results.some(result => result.error); } @@ -502,21 +489,29 @@ export abstract class AbstractTextFileService extends Disposable implements ITex saveAll(resources: URI[], options?: ITextFileSaveOptions): Promise; saveAll(arg1?: boolean | URI[], options?: ITextFileSaveOptions): Promise { - // get all dirty - let toSave: URI[] = []; + // Extract the resources to save + let resourcesToSave: URI[] = []; if (Array.isArray(arg1)) { - toSave = this.getDirty(arg1); + // if specific resources are given, we consider even + // non-dirty ones if options.force is provided + if (options?.force) { + resourcesToSave = arg1; + } else { + resourcesToSave = this.getDirty(arg1); + } } else { - toSave = this.getDirty(); + // if no resources are given, we only consider dirty + // resources even if options.force is provided + resourcesToSave = this.getDirty(); } // split up between files and untitled const filesToSave: URI[] = []; const untitledToSave: URI[] = []; - toSave.forEach(resourceToSave => { + resourcesToSave.forEach(resourceToSave => { if ((Array.isArray(arg1) || arg1 === true /* includeUntitled */) && resourceToSave.scheme === Schemas.untitled) { untitledToSave.push(resourceToSave); - } else { + } else if (this.fileService.canHandleResource(resourceToSave)) { filesToSave.push(resourceToSave); } }); @@ -630,23 +625,17 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } private async doSaveAllFiles(resources?: URI[], options: ITextFileSaveOptions = Object.create(null)): Promise { - const dirtyFileModels = this.getDirtyFileModels(Array.isArray(resources) ? resources : undefined /* Save All */) - .filter(model => { - if ((model.hasState(ModelState.CONFLICT) || model.hasState(ModelState.ERROR)) && (options.reason === SaveReason.AUTO || options.reason === SaveReason.FOCUS_CHANGE || options.reason === SaveReason.WINDOW_CHANGE)) { - return false; // if model is in save conflict or error, do not save unless save reason is explicit or not provided at all - } - - return true; - }); + const fileModelsToSave = this.getFileModels(resources); const mapResourceToResult = new ResourceMap(); - dirtyFileModels.forEach(dirtyModel => { - mapResourceToResult.set(dirtyModel.resource, { - source: dirtyModel.resource - }); - }); + for (const fileModelToSave of fileModelsToSave) { + mapResourceToResult.set(fileModelToSave.resource, { source: fileModelToSave.resource }); + } + + // Save all in parallel + await Promise.all(fileModelsToSave.map(async model => { - await Promise.all(dirtyFileModels.map(async model => { + // Save with options await model.save(options); // If model is still dirty, mark the resulting operation as error diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index afd56f642e9ff..53f5f8a3738da 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -8,19 +8,19 @@ import { Emitter } from 'vs/base/common/event'; import { guessMimeTypes } from 'vs/base/common/mime'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { URI } from 'vs/base/common/uri'; -import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; +import { assertIsDefined } from 'vs/base/common/types'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ITextFileService, ModelState, ITextFileEditorModel, ISaveErrorHandler, ISaveParticipant, StateChange, ITextFileStreamContent, ILoadOptions, LoadReason, IResolvedTextFileEditorModel, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { EncodingMode, IRevertOptions, SaveReason } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IFileService, FileOperationError, FileOperationResult, CONTENT_CHANGE_EVENT_BUFFER_DELAY, FileChangesEvent, FileChangeType, IFileStatWithMetadata, ETAG_DISABLED, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; +import { IFileService, FileOperationError, FileOperationResult, FileChangesEvent, FileChangeType, IFileStatWithMetadata, ETAG_DISABLED, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { RunOnceScheduler, timeout } from 'vs/base/common/async'; +import { timeout } from 'vs/base/common/async'; import { ITextBufferFactory } from 'vs/editor/common/model'; import { hash } from 'vs/base/common/hash'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -60,9 +60,6 @@ type TelemetryData = { */ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFileEditorModel { - static DEFAULT_CONTENT_CHANGE_BUFFER_DELAY = CONTENT_CHANGE_EVENT_BUFFER_DELAY; - static DEFAULT_ORPHANED_CHANGE_BUFFER_DELAY = 100; - static WHITELIST_JSON = ['package.json', 'package-lock.json', 'tsconfig.json', 'jsconfig.json', 'bower.json', '.eslintrc.json', 'tslint.json', 'composer.json']; static WHITELIST_WORKSPACE_JSON = ['settings.json', 'extensions.json', 'tasks.json', 'launch.json']; @@ -72,11 +69,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private static saveParticipant: ISaveParticipant | null; static setSaveParticipant(handler: ISaveParticipant | null): void { TextFileEditorModel.saveParticipant = handler; } - private readonly _onDidContentChange = this._register(new Emitter()); - readonly onDidContentChange = this._onDidContentChange.event; + private readonly _onDidChangeContent = this._register(new Emitter()); + readonly onDidChangeContent = this._onDidChangeContent.event; - private readonly _onDidStateChange = this._register(new Emitter()); - readonly onDidStateChange = this._onDidStateChange.event; + private readonly _onDidChangeState = this._register(new Emitter()); + readonly onDidChangeState = this._onDidChangeState.event; private readonly _onDidChangeDirty = this._register(new Emitter()); readonly onDidChangeDirty = this._onDidChangeDirty.event; @@ -94,9 +91,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private readonly saveSequentializer = new SaveSequentializer(); private lastSaveAttemptTime = 0; - private readonly contentChangeEventScheduler = this._register(new RunOnceScheduler(() => this._onDidContentChange.fire(StateChange.CONTENT_CHANGE), TextFileEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY)); - private readonly orphanedChangeEventScheduler = this._register(new RunOnceScheduler(() => this._onDidStateChange.fire(StateChange.ORPHANED_CHANGE), TextFileEditorModel.DEFAULT_ORPHANED_CHANGE_BUFFER_DELAY)); - private dirty = false; private inConflictMode = false; private inOrphanMode = false; @@ -132,18 +126,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private registerListeners(): void { this._register(this.fileService.onFileChanges(e => this.onFileChanges(e))); this._register(this.filesConfigurationService.onFilesAssociationChange(e => this.onFilesAssociationChange())); - this._register(this.onDidStateChange(e => this.onStateChange(e))); - } - - private onStateChange(e: StateChange): void { - if (e === StateChange.REVERTED) { - - // Cancel any content change event promises as they are no longer valid. - this.contentChangeEventScheduler.cancel(); - - // Refire state change reverted events as content change events - this._onDidContentChange.fire(StateChange.REVERTED); - } } private async onFileChanges(e: FileChangesEvent): Promise { @@ -194,7 +176,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private setOrphaned(orphaned: boolean): void { if (this.inOrphanMode !== orphaned) { this.inOrphanMode = orphaned; - this.orphanedChangeEventScheduler.schedule(); + this._onDidChangeState.fire(StateChange.ORPHANED_CHANGE); } } @@ -262,7 +244,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } // Emit file change event - this._onDidStateChange.fire(StateChange.REVERTED); + this._onDidChangeState.fire(StateChange.REVERTED); // Emit dirty change event if (wasDirty) { @@ -419,7 +401,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil if (this.preferredEncoding) { this.updatePreferredEncoding(this.contentEncoding); // make sure to reflect the real encoding of the file (never out of sync) } else if (oldEncoding !== this.contentEncoding) { - this._onDidStateChange.fire(StateChange.ENCODING); + this._onDidChangeState.fire(StateChange.ENCODING); } // Update Existing Model @@ -522,20 +504,18 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Emit event if (wasDirty) { - this._onDidStateChange.fire(StateChange.REVERTED); + this._onDidChangeState.fire(StateChange.REVERTED); this._onDidChangeDirty.fire(); } + } else { + this.logService.trace('[text file model] onModelContentChanged() - model content changed and marked as dirty', this.resource.toString()); - return; + // Mark as dirty + this.doMakeDirty(); } - this.logService.trace('[text file model] onModelContentChanged() - model content changed and marked as dirty', this.resource.toString()); - - // Mark as dirty - this.doMakeDirty(); - - // Handle content change events - this.contentChangeEventScheduler.schedule(); + // Emit as event + this._onDidChangeContent.fire(); } makeDirty(): void { @@ -554,7 +534,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Emit as Event if we turned dirty if (!wasDirty) { - this._onDidStateChange.fire(StateChange.DIRTY); + this._onDidChangeState.fire(StateChange.DIRTY); this._onDidChangeDirty.fire(); } } @@ -564,19 +544,37 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return false; } + if (this.isReadonly()) { + this.logService.trace('[text file model] save() - ignoring request for readonly resource', this.resource.toString()); + + return false; // if model is readonly we do not attempt to save at all + } + + if ( + (this.hasState(ModelState.CONFLICT) || this.hasState(ModelState.ERROR)) && + (options.reason === SaveReason.AUTO || options.reason === SaveReason.FOCUS_CHANGE || options.reason === SaveReason.WINDOW_CHANGE) + ) { + this.logService.trace('[text file model] save() - ignoring auto save request for model that is in conflict or error', this.resource.toString()); + + return false; // if model is in save conflict or error, do not save unless save reason is explicit + } + this.logService.trace('[text file model] save() - enter', this.resource.toString()); - await this.doSave(this.versionId, options); + await this.doSave(options); + + this.logService.trace('[text file model] save() - exit', this.resource.toString()); return true; } - private doSave(versionId: number, options: ITextFileSaveOptions): Promise { - if (isUndefinedOrNull(options.reason)) { + private doSave(options: ITextFileSaveOptions): Promise { + if (typeof options.reason !== 'number') { options.reason = SaveReason.EXPLICIT; } - this.logService.trace(`[text file model] doSave(${versionId}) - enter with versionId ' + versionId`, this.resource.toString()); + let versionId = this.versionId; + this.logService.trace(`[text file model] doSave(${versionId}) - enter with versionId ${versionId}`, this.resource.toString()); // Lookup any running pending save for this versionId and return it if found // @@ -589,14 +587,10 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.saveSequentializer.pendingSave || Promise.resolve(); } - // Return early if not dirty (unless forced) or version changed meanwhile + // Return early if not dirty (unless forced) // - // Scenario A: user invoked save action even though the model is not dirty - // Scenario B: auto save was triggered for a certain change by the user but meanwhile the user changed - // the contents and the version for which auto save was started is no longer the latest. - // Thus we avoid spawning multiple auto saves and only take the latest. - // - if ((!options.force && !this.dirty) || versionId !== this.versionId) { + // Scenario: user invoked save action even though the model is not dirty + if (!options.force && !this.dirty) { this.logService.trace(`[text file model] doSave(${versionId}) - exit - because not dirty and/or versionId is different (this.isDirty: ${this.dirty}, this.versionId: ${this.versionId})`, this.resource.toString()); return Promise.resolve(); @@ -614,7 +608,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.logService.trace(`[text file model] doSave(${versionId}) - exit - because busy saving`, this.resource.toString()); // Register this as the next upcoming save and return - return this.saveSequentializer.setNext(() => this.doSave(this.versionId /* make sure to use latest version id here */, options)); + return this.saveSequentializer.setNext(() => this.doSave(options)); } // Push all edit operations to the undo stack so that the user has a chance to @@ -702,11 +696,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Updated resolved stat with updated stat this.updateLastResolvedFileStat(stat); - // Cancel any content change event promises as they are no longer valid - this.contentChangeEventScheduler.cancel(); - // Emit Events - this._onDidStateChange.fire(StateChange.SAVED); + this._onDidChangeState.fire(StateChange.SAVED); this._onDidChangeDirty.fire(); // Telemetry @@ -735,7 +726,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.onSaveError(error); // Emit as event - this._onDidStateChange.fire(StateChange.SAVE_ERROR); + this._onDidChangeState.fire(StateChange.SAVE_ERROR); })); })); } @@ -809,7 +800,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.updateLastResolvedFileStat(stat); // Emit File Saved Event - this._onDidStateChange.fire(StateChange.SAVED); + this._onDidChangeState.fire(StateChange.SAVED); }, error => onUnexpectedError(error) /* just log any error but do not notify the user since the file was not dirty */)); } @@ -957,7 +948,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.preferredEncoding = encoding; // Emit - this._onDidStateChange.fire(StateChange.ENCODING); + this._onDidChangeState.fire(StateChange.ENCODING); } private isNewEncoding(encoding: string | undefined): boolean { diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index d7116508ca07b..deb34c132fbb9 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -18,12 +18,6 @@ import { onUnexpectedError } from 'vs/base/common/errors'; export class TextFileEditorModelManager extends Disposable implements ITextFileEditorModelManager { - private readonly _onModelDisposed = this._register(new Emitter()); - readonly onModelDisposed = this._onModelDisposed.event; - - private readonly _onModelContentChanged = this._register(new Emitter()); - readonly onModelContentChanged = this._onModelContentChanged.event; - private readonly _onModelDirty = this._register(new Emitter()); readonly onModelDirty = this._onModelDirty.event; @@ -69,15 +63,6 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return this._onModelsSaved; } - private _onModelsReverted: Event> | undefined; - get onModelsReverted(): Event> { - if (!this._onModelsReverted) { - this._onModelsReverted = this.debounce(this.onModelReverted); - } - - return this._onModelsReverted; - } - private readonly mapResourceToDisposeListener = new ResourceMap(); private readonly mapResourceToStateChangeListener = new ResourceMap(); private readonly mapResourceToModelContentChangeListener = new ResourceMap(); @@ -183,7 +168,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE modelPromise = model.load(options); // Install state change listener - this.mapResourceToStateChangeListener.set(resource, model.onDidStateChange(state => { + this.mapResourceToStateChangeListener.set(resource, model.onDidChangeState(state => { const event = new TextFileModelChangeEvent(newModel, state); switch (state) { case StateChange.DIRTY: @@ -206,11 +191,6 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE break; } })); - - // Install model content change listener - this.mapResourceToModelContentChangeListener.set(resource, model.onDidContentChange(e => { - this._onModelContentChanged.fire(new TextFileModelChangeEvent(newModel, e)); - })); } // Store pending loads to avoid race conditions @@ -281,10 +261,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // store in cache but remove when model gets disposed this.mapResourceToModel.set(resource, model); - this.mapResourceToDisposeListener.set(resource, model.onDispose(() => { - this.remove(resource); - this._onModelDisposed.fire(resource); - })); + this.mapResourceToDisposeListener.set(resource, model.onDispose(() => this.remove(resource))); } remove(resource: URI): void { diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index a61a80c21d406..8a0439d45de47 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -277,12 +277,10 @@ export const enum ModelState { export const enum StateChange { DIRTY, - SAVING, SAVE_ERROR, SAVED, REVERTED, ENCODING, - CONTENT_CHANGE, ORPHANED_CHANGE } @@ -379,20 +377,17 @@ export interface IModelLoadOrCreateOptions { export interface ITextFileEditorModelManager { - readonly onModelDisposed: Event; - readonly onModelContentChanged: Event; readonly onModelEncodingChanged: Event; + readonly onModelOrphanedChanged: Event; readonly onModelDirty: Event; readonly onModelSaveError: Event; readonly onModelSaved: Event; readonly onModelReverted: Event; - readonly onModelOrphanedChanged: Event; readonly onModelsDirty: Event; readonly onModelsSaveError: Event; readonly onModelsSaved: Event; - readonly onModelsReverted: Event; get(resource: URI): ITextFileEditorModel | undefined; @@ -430,8 +425,7 @@ export interface ILoadOptions { export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport, IModeSupport, IWorkingCopy { - readonly onDidContentChange: Event; - readonly onDidStateChange: Event; + readonly onDidChangeState: Event; hasState(state: ModelState): boolean; diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts index 3d8c90ba25086..5d0f0212cadfd 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts @@ -52,6 +52,34 @@ suite('Files - TextFileEditorModel', () => { accessor.fileService.setContent(content); }); + test('basic events', async function () { + const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + + await model.load(); + + let onDidChangeContentCounter = 0; + model.onDidChangeContent(() => onDidChangeContentCounter++); + + let onDidChangeDirtyCounter = 0; + model.onDidChangeDirty(() => onDidChangeDirtyCounter++); + + model.textEditorModel?.setValue('bar'); + + assert.equal(onDidChangeContentCounter, 1); + assert.equal(onDidChangeDirtyCounter, 1); + + model.textEditorModel?.setValue('foo'); + + assert.equal(onDidChangeContentCounter, 2); + assert.equal(onDidChangeDirtyCounter, 1); + + await model.revert(); + + assert.equal(onDidChangeDirtyCounter, 2); + + model.dispose(); + }); + test('save', async function () { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); @@ -67,7 +95,7 @@ suite('Files - TextFileEditorModel', () => { assert.equal(accessor.workingCopyService.isDirty(model.resource), true); let savedEvent = false; - model.onDidStateChange(e => { + model.onDidChangeState(e => { if (e === StateChange.SAVED) { savedEvent = true; } @@ -104,7 +132,7 @@ suite('Files - TextFileEditorModel', () => { await model.load(); let savedEvent = false; - model.onDidStateChange(e => { + model.onDidChangeState(e => { if (e === StateChange.SAVED) { savedEvent = true; } @@ -178,7 +206,7 @@ suite('Files - TextFileEditorModel', () => { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index.txt'), 'utf8', undefined); assert.ok(model.hasState(ModelState.SAVED)); - model.onDidStateChange(e => { + model.onDidChangeState(e => { assert.ok(e !== StateChange.DIRTY && e !== StateChange.SAVED); }); @@ -206,7 +234,7 @@ suite('Files - TextFileEditorModel', () => { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - model.onDidStateChange(e => { + model.onDidChangeState(e => { if (e === StateChange.REVERTED) { eventCounter++; } @@ -243,7 +271,7 @@ suite('Files - TextFileEditorModel', () => { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - model.onDidStateChange(e => { + model.onDidChangeState(e => { if (e === StateChange.REVERTED) { eventCounter++; } @@ -303,7 +331,7 @@ suite('Files - TextFileEditorModel', () => { await model.revert({ soft: true }); assert.ok(!model.isDirty()); - model.onDidStateChange(e => { + model.onDidChangeState(e => { if (e === StateChange.DIRTY) { eventCounter++; } @@ -388,7 +416,7 @@ suite('Files - TextFileEditorModel', () => { let eventCounter = 0; const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - model.onDidStateChange(e => { + model.onDidChangeState(e => { if (e === StateChange.SAVED) { assert.equal(snapshotToString(model.createSnapshot()!), 'bar'); assert.ok(!model.isDirty()); diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts index f4db513644fc3..4145c7f6f8fac 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts @@ -136,9 +136,6 @@ suite('Files - TextFileEditorModelManager', () => { }); test('events', async function () { - TextFileEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY = 0; - TextFileEditorModel.DEFAULT_ORPHANED_CHANGE_BUFFER_DELAY = 0; - const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); const resource1 = toResource.call(this, '/path/index.txt'); @@ -148,8 +145,6 @@ suite('Files - TextFileEditorModelManager', () => { let revertedCounter = 0; let savedCounter = 0; let encodingCounter = 0; - let disposeCounter = 0; - let contentCounter = 0; manager.onModelDirty(e => { if (e.resource.toString() === resource1.toString()) { @@ -175,16 +170,6 @@ suite('Files - TextFileEditorModelManager', () => { } }); - manager.onModelContentChanged(e => { - if (e.resource.toString() === resource1.toString()) { - contentCounter++; - } - }); - - manager.onModelDisposed(e => { - disposeCounter++; - }); - const model1 = await manager.loadOrCreate(resource1, { encoding: 'utf8' }); accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource: resource1, type: FileChangeType.DELETED }])); accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource: resource1, type: FileChangeType.ADDED }])); @@ -199,7 +184,6 @@ suite('Files - TextFileEditorModelManager', () => { await model1.save(); model1.dispose(); model2.dispose(); - assert.equal(disposeCounter, 2); await model1.revert(); assert.equal(dirtyCounter, 2); @@ -207,8 +191,6 @@ suite('Files - TextFileEditorModelManager', () => { assert.equal(savedCounter, 1); assert.equal(encodingCounter, 2); - await timeout(10); - assert.equal(contentCounter, 2); model1.dispose(); model2.dispose(); assert.ok(!accessor.modelService.getModel(resource1)); @@ -222,21 +204,13 @@ suite('Files - TextFileEditorModelManager', () => { const resource2 = toResource.call(this, '/path/other.txt'); let dirtyCounter = 0; - let revertedCounter = 0; let savedCounter = 0; - TextFileEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY = 0; - manager.onModelsDirty(e => { dirtyCounter += e.length; assert.equal(e[0].resource.toString(), resource1.toString()); }); - manager.onModelsReverted(e => { - revertedCounter += e.length; - assert.equal(e[0].resource.toString(), resource1.toString()); - }); - manager.onModelsSaved(e => { savedCounter += e.length; assert.equal(e[0].resource.toString(), resource1.toString()); @@ -257,7 +231,6 @@ suite('Files - TextFileEditorModelManager', () => { await model1.revert(); await timeout(20); assert.equal(dirtyCounter, 2); - assert.equal(revertedCounter, 1); assert.equal(savedCounter, 1); model1.dispose(); model2.dispose(); diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts index 9131aa8def527..42c6848278a15 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts @@ -30,16 +30,6 @@ export interface IUntitledTextEditorService { _serviceBrand: undefined; - /** - * Events for when untitled text editors are created. - */ - readonly onDidCreate: Event; - - /** - * Events for when untitled text editors content changes (e.g. any keystroke). - */ - readonly onDidChangeContent: Event; - /** * Events for when untitled text editors change (e.g. getting dirty, saved or reverted). */ @@ -113,12 +103,6 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe private mapResourceToInput = new ResourceMap(); private mapResourceToAssociatedFilePath = new ResourceMap(); - private readonly _onDidCreate = this._register(new Emitter()); - readonly onDidCreate = this._onDidCreate.event; - - private readonly _onDidChangeContent = this._register(new Emitter()); - readonly onDidChangeContent = this._onDidChangeContent.event; - private readonly _onDidChangeDirty = this._register(new Emitter()); readonly onDidChangeDirty = this._onDidChangeDirty.event; @@ -246,7 +230,6 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe const input = this.instantiationService.createInstance(UntitledTextEditorInput, untitledResource, !!hasAssociatedFilePath, mode, initialValue, encoding); - const contentListener = input.onDidModelChangeContent(() => this._onDidChangeContent.fire(untitledResource)); const dirtyListener = input.onDidChangeDirty(() => this._onDidChangeDirty.fire(untitledResource)); const encodingListener = input.onDidModelChangeEncoding(() => this._onDidChangeEncoding.fire(untitledResource)); const disposeListener = input.onDispose(() => this._onDidDisposeModel.fire(untitledResource)); @@ -256,7 +239,6 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe onceDispose(() => { this.mapResourceToInput.delete(input.getResource()); this.mapResourceToAssociatedFilePath.delete(input.getResource()); - contentListener.dispose(); dirtyListener.dispose(); encodingListener.dispose(); disposeListener.dispose(); @@ -265,9 +247,6 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe // Add to cache this.mapResourceToInput.set(untitledResource, input); - // Signal new untitled as event - this._onDidCreate.fire(untitledResource); - return input; } diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts index e79533e733361..4c12fbb093fb0 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts @@ -28,19 +28,28 @@ export interface IWorkingCopy { readonly capabilities: WorkingCopyCapabilities; - //#region Dirty Tracking + //#region Events readonly onDidChangeDirty: Event; + readonly onDidChangeContent: Event; + + //#endregion + + + //#region Dirty Tracking + isDirty(): boolean; //#endregion - //#region Save + //#region Save / Backup save(options?: ISaveOptions): Promise; + backup(): Promise; + //#endregion } @@ -51,10 +60,21 @@ export interface IWorkingCopyService { _serviceBrand: undefined; - //#region Dirty Tracking + //#region Events + + readonly onDidRegister: Event; + + readonly onDidUnregister: Event; readonly onDidChangeDirty: Event; + readonly onDidChangeContent: Event; + + //#endregion + + + //#region Dirty Tracking + readonly dirtyCount: number; readonly hasDirty: boolean; @@ -77,11 +97,25 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic _serviceBrand: undefined; - //#region Dirty Tracking + //#region Events + + private readonly _onDidRegister = this._register(new Emitter()); + readonly onDidRegister = this._onDidRegister.event; + + private readonly _onDidUnregister = this._register(new Emitter()); + readonly onDidUnregister = this._onDidUnregister.event; private readonly _onDidChangeDirty = this._register(new Emitter()); readonly onDidChangeDirty = this._onDidChangeDirty.event; + private readonly _onDidChangeContent = this._register(new Emitter()); + readonly onDidChangeContent = this._onDidChangeContent.event; + + //#endregion + + + //#region Dirty Tracking + isDirty(resource: URI): boolean { const workingCopies = this.mapResourceToWorkingCopy.get(resource.toString()); if (workingCopies) { @@ -141,8 +175,12 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic this._workingCopies.add(workingCopy); - // Dirty Events + // Wire in Events + disposables.add(workingCopy.onDidChangeContent(() => this._onDidChangeContent.fire(workingCopy))); disposables.add(workingCopy.onDidChangeDirty(() => this._onDidChangeDirty.fire(workingCopy))); + + // Send some initial events + this._onDidRegister.fire(workingCopy); if (workingCopy.isDirty()) { this._onDidChangeDirty.fire(workingCopy); } @@ -150,6 +188,9 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic return toDisposable(() => { this.unregisterWorkingCopy(workingCopy); dispose(disposables); + + // Signal as event + this._onDidUnregister.fire(workingCopy); }); } diff --git a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts index ee32a8332624b..908c24a2251bf 100644 --- a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts @@ -18,6 +18,9 @@ suite('WorkingCopyService', () => { private readonly _onDidChangeDirty = this._register(new Emitter()); readonly onDidChangeDirty = this._onDidChangeDirty.event; + private readonly _onDidChangeContent = this._register(new Emitter()); + readonly onDidChangeContent = this._onDidChangeContent.event; + private readonly _onDispose = this._register(new Emitter()); readonly onDispose = this._onDispose.event; @@ -38,6 +41,10 @@ suite('WorkingCopyService', () => { } } + setContent(content: string): void { + this._onDidChangeContent.fire(); + } + isDirty(): boolean { return this.dirty; } @@ -46,6 +53,8 @@ suite('WorkingCopyService', () => { return true; } + async backup(): Promise { } + dispose(): void { this._onDispose.fire(); @@ -59,6 +68,15 @@ suite('WorkingCopyService', () => { const onDidChangeDirty: IWorkingCopy[] = []; service.onDidChangeDirty(copy => onDidChangeDirty.push(copy)); + const onDidChangeContent: IWorkingCopy[] = []; + service.onDidChangeContent(copy => onDidChangeContent.push(copy)); + + const onDidRegister: IWorkingCopy[] = []; + service.onDidRegister(copy => onDidRegister.push(copy)); + + const onDidUnregister: IWorkingCopy[] = []; + service.onDidUnregister(copy => onDidUnregister.push(copy)); + assert.equal(service.hasDirty, false); assert.equal(service.dirtyCount, 0); assert.equal(service.workingCopies.length, 0); @@ -70,6 +88,9 @@ suite('WorkingCopyService', () => { const unregister1 = service.registerWorkingCopy(copy1); assert.equal(service.workingCopies.length, 1); + assert.equal(service.workingCopies[0], copy1); + assert.equal(onDidRegister.length, 1); + assert.equal(onDidRegister[0], copy1); assert.equal(service.dirtyCount, 0); assert.equal(service.isDirty(resource1), false); assert.equal(service.hasDirty, false); @@ -82,6 +103,11 @@ suite('WorkingCopyService', () => { assert.equal(onDidChangeDirty.length, 1); assert.equal(onDidChangeDirty[0], copy1); + copy1.setContent('foo'); + + assert.equal(onDidChangeContent.length, 1); + assert.equal(onDidChangeContent[0], copy1); + copy1.setDirty(false); assert.equal(service.dirtyCount, 0); @@ -92,6 +118,8 @@ suite('WorkingCopyService', () => { unregister1.dispose(); + assert.equal(onDidUnregister.length, 1); + assert.equal(onDidUnregister[0], copy1); assert.equal(service.workingCopies.length, 0); // resource 2 @@ -99,6 +127,8 @@ suite('WorkingCopyService', () => { const copy2 = new TestWorkingCopy(resource2, true); const unregister2 = service.registerWorkingCopy(copy2); + assert.equal(onDidRegister.length, 2); + assert.equal(onDidRegister[1], copy2); assert.equal(service.dirtyCount, 1); assert.equal(service.isDirty(resource2), true); assert.equal(service.hasDirty, true); @@ -106,7 +136,15 @@ suite('WorkingCopyService', () => { assert.equal(onDidChangeDirty.length, 3); assert.equal(onDidChangeDirty[2], copy2); + copy2.setContent('foo'); + + assert.equal(onDidChangeContent.length, 2); + assert.equal(onDidChangeContent[1], copy2); + unregister2.dispose(); + + assert.equal(onDidUnregister.length, 2); + assert.equal(onDidUnregister[1], copy2); assert.equal(service.dirtyCount, 0); assert.equal(service.hasDirty, false); assert.equal(onDidChangeDirty.length, 4); diff --git a/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts index c9ca35dc1440d..ca9bba1e27be1 100644 --- a/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts +++ b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts @@ -10,11 +10,9 @@ import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbe import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { workbenchInstantiationService, TestEditorService } from 'vs/workbench/test/workbenchTestServices'; -import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; -import { timeout } from 'vs/base/common/async'; import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; @@ -56,20 +54,11 @@ suite('Workbench untitled text editors', () => { assert.equal(service.getAll().length, 0); - let createdResources: URI[] = []; - const createListener = service.onDidCreate(resource => { - createdResources.push(resource); - }); - const input1 = service.createOrGet(); assert.equal(input1, service.createOrGet(input1.getResource())); assert.ok(service.exists(input1.getResource())); assert.ok(!service.exists(URI.file('testing'))); - assert.equal(createdResources.length, 1); - assert.equal(createdResources[0].toString(), input1.getResource()); - - createListener.dispose(); const input2 = service.createOrGet(); @@ -122,17 +111,18 @@ suite('Workbench untitled text editors', () => { model.textEditorModel.setValue('foo bar'); }); - test('Untitled with associated resource', () => { + test('Untitled with associated resource is dirty', () => { const service = accessor.untitledTextEditorService; const file = URI.file(join('C:\\', '/foo/file.txt')); const untitled = service.createOrGet(file); assert.ok(service.hasAssociatedFilePath(untitled.getResource())); + assert.equal(untitled.isDirty(), true); untitled.dispose(); }); - test('Untitled no longer dirty when content gets empty', async () => { + test('Untitled no longer dirty when content gets empty (not with associated resource)', async () => { const service = accessor.untitledTextEditorService; const workingCopyService = accessor.workingCopyService; const input = service.createOrGet(); @@ -203,21 +193,23 @@ suite('Workbench untitled text editors', () => { test('Untitled with initial content is dirty', async () => { const service = accessor.untitledTextEditorService; - const input = service.createOrGet(undefined, undefined, 'Hello World'); const workingCopyService = accessor.workingCopyService; + const untitled = service.createOrGet(undefined, undefined, 'Hello World'); + assert.equal(untitled.isDirty(), true); + let onDidChangeDirty: IWorkingCopy | undefined = undefined; const listener = workingCopyService.onDidChangeDirty(copy => { onDidChangeDirty = copy; }); // dirty - const model = await input.resolve(); + const model = await untitled.resolve(); assert.ok(model.isDirty()); assert.equal(workingCopyService.dirtyCount, 1); assert.equal(onDidChangeDirty, model); - input.dispose(); + untitled.dispose(); listener.dispose(); }); @@ -309,41 +301,47 @@ suite('Workbench untitled text editors', () => { input.dispose(); }); - test('onDidChangeContent event', async () => { + test('onDidChangeContent event', async function () { const service = accessor.untitledTextEditorService; const input = service.createOrGet(); - UntitledTextEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY = 0; - let counter = 0; - service.onDidChangeContent(r => { - counter++; - assert.equal(r.toString(), input.getResource().toString()); - }); - const model = await input.resolve(); + model.onDidChangeContent(() => counter++); + model.textEditorModel.setValue('foo'); - assert.equal(counter, 0, 'Dirty model should not trigger event immediately'); - await timeout(3); assert.equal(counter, 1, 'Dirty model should trigger event'); model.textEditorModel.setValue('bar'); - await timeout(3); assert.equal(counter, 2, 'Content change when dirty should trigger event'); model.textEditorModel.setValue(''); - await timeout(3); assert.equal(counter, 3, 'Manual revert should trigger event'); model.textEditorModel.setValue('foo'); - await timeout(3); assert.equal(counter, 4, 'Dirty model should trigger event'); - model.revert(); - await timeout(3); - assert.equal(counter, 5, 'Revert should trigger event'); + input.dispose(); + }); + + test('onDidChangeDirty event', async function () { + const service = accessor.untitledTextEditorService; + const input = service.createOrGet(); + + let counter = 0; + + const model = await input.resolve(); + model.onDidChangeDirty(() => counter++); + + model.textEditorModel.setValue('foo'); + + assert.equal(counter, 1, 'Dirty model should trigger event'); + model.textEditorModel.setValue('bar'); + + assert.equal(counter, 1, 'Another change does not fire event'); + input.dispose(); }); From 02cce060b6f653c0b444533c3873624ad00fc41b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 12 Jan 2020 12:22:05 +0100 Subject: [PATCH 183/315] preventSaveConflicts - allow as language setting --- .../contrib/files/browser/files.contribution.ts | 2 +- .../common/filesConfigurationService.ts | 11 +++++++---- .../services/textfile/common/textFileEditorModel.ts | 4 +++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 39744d2ae4ac3..aedef12163ed8 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -326,7 +326,7 @@ configurationRegistry.registerConfiguration({ 'type': 'boolean', 'description': nls.localize('files.preventSaveConflicts', "When enabled, will prevent to save a file that has been changed since it was last edited. Instead, a diff editor is provided to compare the changes and accept or revert them. This setting should only be disabled if you frequently encounter save conflict errors and may result in data loss if used without caution."), 'default': true, - 'scope': ConfigurationScope.RESOURCE + 'scope': ConfigurationScope.RESOURCE_LANGUAGE }, 'files.simpleDialog.enable': { 'type': 'boolean', diff --git a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts index 4d60e10c853d6..0df4d59fa9b70 100644 --- a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts +++ b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts @@ -14,6 +14,7 @@ import { isUndefinedOrNull } from 'vs/base/common/types'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { equals } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; +import { isWeb } from 'vs/base/common/platform'; export const AutoSaveAfterShortDelayContext = new RawContextKey('autoSaveAfterShortDelayContext', false); @@ -55,13 +56,15 @@ export interface IFilesConfigurationService { readonly hotExitConfiguration: string | undefined; - preventSaveConflicts(resource: URI): boolean; + preventSaveConflicts(resource: URI, language: string): boolean; } export class FilesConfigurationService extends Disposable implements IFilesConfigurationService { _serviceBrand: undefined; + private static DEFAULT_AUTO_SAVE_MODE = isWeb ? AutoSaveConfiguration.AFTER_DELAY : AutoSaveConfiguration.OFF; + private readonly _onAutoSaveConfigurationChange = this._register(new Emitter()); readonly onAutoSaveConfigurationChange = this._onAutoSaveConfigurationChange.event; @@ -110,7 +113,7 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi protected onFilesConfigurationChange(configuration: IFilesConfiguration): void { // Auto Save - const autoSaveMode = configuration?.files?.autoSave || AutoSaveConfiguration.OFF; + const autoSaveMode = configuration?.files?.autoSave || FilesConfigurationService.DEFAULT_AUTO_SAVE_MODE; switch (autoSaveMode) { case AutoSaveConfiguration.AFTER_DELAY: this.configuredAutoSaveDelay = configuration?.files?.autoSaveDelay; @@ -207,8 +210,8 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi return this.currentHotExitConfig; } - preventSaveConflicts(resource: URI): boolean { - return this.configurationService.getValue('files.preventSaveConflicts', { resource }); + preventSaveConflicts(resource: URI, language: string): boolean { + return this.configurationService.getValue('files.preventSaveConflicts', { resource, overrideIdentifier: language }); } } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 53f5f8a3738da..19ade6f51be74 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -680,7 +680,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil overwriteEncoding: options.overwriteEncoding, mtime: lastResolvedFileStat.mtime, encoding: this.getEncoding(), - etag: (options.ignoreModifiedSince || !this.filesConfigurationService.preventSaveConflicts(lastResolvedFileStat.resource)) ? ETAG_DISABLED : lastResolvedFileStat.etag, + etag: (options.ignoreModifiedSince || !this.filesConfigurationService.preventSaveConflicts(lastResolvedFileStat.resource, this.getMode())) ? ETAG_DISABLED : lastResolvedFileStat.etag, writeElevated: options.writeElevated }).then(stat => { this.logService.trace(`[text file model] doSave(${versionId}) - after write()`, this.resource.toString()); @@ -891,6 +891,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } + getMode(this: IResolvedTextFileEditorModel): string; + getMode(): string | undefined; getMode(): string | undefined { if (this.textEditorModel) { return this.textEditorModel.getModeId(); From e15873eae8d398394dcb6bf85c22447e4283dc23 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sun, 12 Jan 2020 18:29:17 +0100 Subject: [PATCH 184/315] #85216 Add configure sync command --- .../contrib/userDataSync/browser/userDataSync.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 07264e336bd3c..9c9dd463da78d 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -552,5 +552,15 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }; CommandsRegistry.registerCommand(signOutMenuItem.command.id, () => this.signOut()); MenuRegistry.appendMenuItem(MenuId.CommandPalette, signOutMenuItem); + + const configureSyncCommandId = 'workbench.userData.actions.configureSync'; + CommandsRegistry.registerCommand(configureSyncCommandId, () => this.configureSyncOptions()); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: configureSyncCommandId, + title: localize('configure sync', "Sync: Configure") + }, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`)), + }); } } From 478f9019ce4c33e3ceb3c0b924607ffd8e953270 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sun, 12 Jan 2020 18:38:35 +0100 Subject: [PATCH 185/315] #85216 Add confirmation while turning off --- .../contrib/userDataSync/browser/userDataSync.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 9c9dd463da78d..666725b992295 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -278,10 +278,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo let detail: string, primaryButton: string; if (this.authTokenService.status === AuthTokenStatus.SignedIn) { detail = this.getTurnOnDetailString(); - primaryButton = localize('turn on sync', "Turn on Sync"); + primaryButton = localize('turn on', "Turn on"); } else { detail = this.getSignInAndTurnOnDetailString(); - primaryButton = localize('sign in and turn on sync', "Sign in & Turn on Sync"); + primaryButton = localize('sign in and turn on sync', "Sign in & Turn on"); } const result = await this.dialogService.show(Severity.Info, message, [primaryButton, localize('cancel', "Cancel"), localize('configure', "Configure")], { detail, cancelId: 1 }); switch (result.choice) { @@ -341,7 +341,15 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private async turnOff(): Promise { - await this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, false); + const result = await this.dialogService.confirm({ + type: 'info', + message: localize('turn off sync confirmation', "Turn off Sync"), + detail: localize('turn off sync detail', "Your settings, keybindings, extensions and more will no longer be synced."), + primaryButton: localize('turn off', "Turn off") + }); + if (result.confirmed) { + await this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, false); + } } private async signIn(): Promise { From 22e4d75bd07b1ec7362c69da9dda7667eae95374 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sun, 12 Jan 2020 20:45:44 +0100 Subject: [PATCH 186/315] Fix #86096 --- .../userDataSync/common/userDataSync.ts | 7 ++-- .../common/userDataSyncService.ts | 35 ++++++++++++------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 6ee18e5c75662..cb042269bc3db 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -167,9 +167,10 @@ export interface IGlobalState { } export const enum SyncSource { - Settings = 1, - Keybindings, - Extensions + Settings = 'Settings', + Keybindings = 'Keybindings', + Extensions = 'Extensions', + UIState = 'UI State' } export const enum SyncStatus { diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 1bf9f49040d68..7d13861b945ef 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource, ISettingsSyncService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; @@ -13,6 +13,7 @@ import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; import { KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync'; import { GlobalStateSynchroniser } from 'vs/platform/userDataSync/common/globalStateSync'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; export class UserDataSyncService extends Disposable implements IUserDataSyncService { @@ -39,6 +40,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ @IInstantiationService private readonly instantiationService: IInstantiationService, @IAuthTokenService private readonly authTokenService: IAuthTokenService, @ISettingsSyncService private readonly settingsSynchroniser: ISettingsSyncService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, ) { super(); this.keybindingsSynchroniser = this._register(this.instantiationService.createInstance(KeybindingsSynchroniser)); @@ -62,8 +64,12 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ throw new Error('Not Authenticated. Please sign in to start sync.'); } for (const synchroniser of this.synchronisers) { - if (!await synchroniser.sync(_continue)) { - return false; + try { + if (!await synchroniser.sync(_continue)) { + return false; + } + } catch (e) { + this.logService.error(`${this.getSyncSource(synchroniser)}: ${toErrorMessage(e)}`); } } return true; @@ -108,15 +114,20 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } private computeConflictsSource(): SyncSource | null { - const source = this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts)[0]; - if (source) { - if (source instanceof SettingsSynchroniser) { - return SyncSource.Settings; - } - if (source instanceof KeybindingsSynchroniser) { - return SyncSource.Keybindings; - } + const synchroniser = this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts)[0]; + return synchroniser ? this.getSyncSource(synchroniser) : null; + } + + private getSyncSource(synchroniser: ISynchroniser): SyncSource { + if (synchroniser instanceof SettingsSynchroniser) { + return SyncSource.Settings; + } + if (synchroniser instanceof KeybindingsSynchroniser) { + return SyncSource.Keybindings; + } + if (synchroniser instanceof ExtensionsSynchroniser) { + return SyncSource.Extensions; } - return null; + return SyncSource.UIState; } } From 528202e34f9b98ba92d07a00d743847b9bed7b12 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 13 Jan 2020 08:37:02 +0100 Subject: [PATCH 187/315] #85216 methods to know if synced before --- .../userDataSync/common/extensionsSync.ts | 10 +++++++ .../userDataSync/common/globalStateSync.ts | 10 +++++++ .../userDataSync/common/keybindingsSync.ts | 10 +++++++ .../userDataSync/common/settingsSync.ts | 10 +++++++ .../userDataSync/common/userDataSync.ts | 2 ++ .../userDataSync/common/userDataSyncIpc.ts | 4 +++ .../common/userDataSyncService.ts | 30 +++++++++++++++++++ .../electron-browser/settingsSyncService.ts | 8 +++++ .../electron-browser/userDataSyncService.ts | 8 +++++ 9 files changed, 92 insertions(+) diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 43d86e6f26ea4..98beb932ceda6 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -108,6 +108,16 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser stop(): void { } + async hasPreviouslySynced(): Promise { + const lastSyncData = await this.getLastSyncUserData(); + return !!lastSyncData; + } + + async hasRemote(): Promise { + const remoteUserData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, null); + return remoteUserData.content !== null; + } + removeExtension(identifier: IExtensionIdentifier): Promise { return this.replaceQueue.queue(async () => { const remoteData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, null); diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index ff5ce0d5ae9e3..5677baaf1f14a 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -85,6 +85,16 @@ export class GlobalStateSynchroniser extends Disposable implements ISynchroniser stop(): void { } + async hasPreviouslySynced(): Promise { + const lastSyncData = await this.getLastSyncUserData(); + return !!lastSyncData; + } + + async hasRemote(): Promise { + const remoteUserData = await this.userDataSyncStoreService.read(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, null); + return remoteUserData.content !== null; + } + private async doSync(): Promise { const lastSyncData = await this.getLastSyncUserData(); const lastSyncGlobalState = lastSyncData && lastSyncData.content ? JSON.parse(lastSyncData.content) : null; diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 4735db867821f..1b92064f97207 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -128,6 +128,16 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser this.setStatus(SyncStatus.Idle); } + async hasPreviouslySynced(): Promise { + const lastSyncData = await this.getLastSyncUserData(); + return !!lastSyncData; + } + + async hasRemote(): Promise { + const remoteUserData = await this.userDataSyncStoreService.read(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, null); + return remoteUserData.content !== null; + } + private async continueSync(): Promise { if (this.status !== SyncStatus.HasConflicts) { return false; diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 5f21b275d5b38..89dc8c0ae81bf 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -117,6 +117,16 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer this.setStatus(SyncStatus.Idle); } + async hasPreviouslySynced(): Promise { + const lastSyncData = await this.getLastSyncUserData(); + return !!lastSyncData; + } + + async hasRemote(): Promise { + const remoteUserData = await this.userDataSyncStoreService.read(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, null); + return remoteUserData.content !== null; + } + async resolveConflicts(resolvedConflicts: { key: string, value: any | undefined }[]): Promise { if (this.status === SyncStatus.HasConflicts) { this.syncPreviewResultPromise!.cancel(); diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index cb042269bc3db..3558afcb8e838 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -186,6 +186,8 @@ export interface ISynchroniser { readonly onDidChangeLocal: Event; sync(_continue?: boolean): Promise; stop(): void; + hasPreviouslySynced(): Promise + hasRemote(): Promise; } export const IUserDataSyncService = createDecorator('IUserDataSyncService'); diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index bcd53cae1b700..05f4d99128f59 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -29,6 +29,8 @@ export class UserDataSyncChannel implements IServerChannel { case 'getConflictsSource': return Promise.resolve(this.service.conflictsSource); case 'removeExtension': return this.service.removeExtension(args[0]); case 'stop': this.service.stop(); return Promise.resolve(); + case 'hasPreviouslySynced': return this.service.hasPreviouslySynced(); + case 'hasRemote': return this.service.hasRemote(); } throw new Error('Invalid call'); } @@ -53,6 +55,8 @@ export class SettingsSyncChannel implements IServerChannel { case '_getInitialStatus': return Promise.resolve(this.service.status); case '_getInitialConflicts': return Promise.resolve(this.service.conflicts); case 'stop': this.service.stop(); return Promise.resolve(); + case 'hasPreviouslySynced': return this.service.hasPreviouslySynced(); + case 'hasRemote': return this.service.hasRemote(); case 'resolveConflicts': return this.service.resolveConflicts(args[0]); } throw new Error('Invalid call'); diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 7d13861b945ef..5a23f9d01c4ab 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -84,6 +84,36 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } } + async hasPreviouslySynced(): Promise { + if (!this.userDataSyncStoreService.userDataSyncStore) { + throw new Error('Not enabled'); + } + if (this.authTokenService.status === AuthTokenStatus.SignedOut) { + throw new Error('Not Authenticated. Please sign in to start sync.'); + } + for (const synchroniser of this.synchronisers) { + if (await synchroniser.hasPreviouslySynced()) { + return true; + } + } + return false; + } + + async hasRemote(): Promise { + if (!this.userDataSyncStoreService.userDataSyncStore) { + throw new Error('Not enabled'); + } + if (this.authTokenService.status === AuthTokenStatus.SignedOut) { + throw new Error('Not Authenticated. Please sign in to start sync.'); + } + for (const synchroniser of this.synchronisers) { + if (await synchroniser.hasRemote()) { + return true; + } + } + return false; + } + removeExtension(identifier: IExtensionIdentifier): Promise { return this.extensionsSynchroniser.removeExtension(identifier); } diff --git a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts index 0a2adc53f3a97..51c3a999ff652 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts @@ -53,6 +53,14 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ this.channel.call('stop'); } + hasPreviouslySynced(): Promise { + return this.channel.call('hasPreviouslySynced'); + } + + hasRemote(): Promise { + return this.channel.call('hasRemote'); + } + resolveConflicts(conflicts: { key: string, value: any | undefined }[]): Promise { return this.channel.call('resolveConflicts', [conflicts]); } diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index 8a0bf37d888cf..fe09e70eec638 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -46,6 +46,14 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.channel.call('stop'); } + hasPreviouslySynced(): Promise { + return this.channel.call('hasPreviouslySynced'); + } + + hasRemote(): Promise { + return this.channel.call('hasRemote'); + } + removeExtension(identifier: IExtensionIdentifier): Promise { return this.channel.call('removeExtension', [identifier]); } From b84d08be70d1efe55d6097d3badcc03297a27635 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 13 Jan 2020 10:04:48 +0100 Subject: [PATCH 188/315] options - make sure text editor options are proper --- src/vs/platform/editor/common/editor.ts | 6 +- src/vs/workbench/common/editor.ts | 107 +++++++----------- .../test/common/editor/editorOptions.test.ts | 39 ------- 3 files changed, 46 insertions(+), 106 deletions(-) delete mode 100644 src/vs/workbench/test/common/editor/editorOptions.test.ts diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index 3185e8c9f9626..fcbef52e0f33d 100644 --- a/src/vs/platform/editor/common/editor.ts +++ b/src/vs/platform/editor/common/editor.ts @@ -220,15 +220,15 @@ export interface ITextEditorOptions extends IEditorOptions { /** * Text editor selection. */ - selection?: ITextEditorSelection; + readonly selection?: ITextEditorSelection; /** * Text editor view state. */ - viewState?: object; + readonly viewState?: object; /** * Option to scroll vertically or horizontally as necessary and reveal a range centered vertically only if it lies outside the viewport. */ - revealInCenterIfOutsideViewport?: boolean; + readonly revealInCenterIfOutsideViewport?: boolean; } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 6a55d4bd697d7..21cbe96e150e6 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -5,11 +5,11 @@ import { Event, Emitter } from 'vs/base/common/event'; import { assign } from 'vs/base/common/objects'; -import { isUndefinedOrNull, withNullAsUndefined, assertIsDefined } from 'vs/base/common/types'; +import { withNullAsUndefined, assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IEditor as ICodeEditor, IEditorViewState, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; -import { IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceInput, IResourceInput, EditorActivation, EditorOpenContext } from 'vs/platform/editor/common/editor'; +import { IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceInput, IResourceInput, EditorActivation, EditorOpenContext, ITextEditorSelection } from 'vs/platform/editor/common/editor'; import { IInstantiationService, IConstructorSignature0, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -24,6 +24,7 @@ import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/te import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { isEqual } from 'vs/base/common/resources'; import { IPanel } from 'vs/workbench/common/panel'; +import { IRange } from 'vs/editor/common/core/range'; export const DirtyWorkingCopiesContext = new RawContextKey('dirtyWorkingCopies', false); export const ActiveEditorContext = new RawContextKey('activeEditor', null); @@ -983,14 +984,22 @@ export class EditorOptions implements IEditorOptions { /** * Base Text Editor Options. */ -export class TextEditorOptions extends EditorOptions { - private startLineNumber: number | undefined; - private startColumn: number | undefined; - private endLineNumber: number | undefined; - private endColumn: number | undefined; +export class TextEditorOptions extends EditorOptions implements ITextEditorOptions { - private revealInCenterIfOutsideViewport: boolean | undefined; - private editorViewState: IEditorViewState | undefined; + /** + * Text editor selection. + */ + selection: ITextEditorSelection | undefined; + + /** + * Text editor view state. + */ + editorViewState: IEditorViewState | undefined; + + /** + * Option to scroll vertically or horizontally as necessary and reveal a range centered vertically only if it lies outside the viewport. + */ + revealInCenterIfOutsideViewport: boolean | undefined; static from(input?: IBaseResourceInput): TextEditorOptions | undefined { if (!input || !input.options) { @@ -1017,8 +1026,12 @@ export class TextEditorOptions extends EditorOptions { super.overwrite(options); if (options.selection) { - const selection = options.selection; - this.selection(selection.startLineNumber, selection.startColumn, selection.endLineNumber, selection.endColumn); + this.selection = { + startLineNumber: options.selection.startLineNumber, + startColumn: options.selection.startColumn, + endLineNumber: options.selection.endLineNumber ?? options.selection.startLineNumber, + endColumn: options.selection.endColumn ?? options.selection.startColumn + }; } if (options.viewState) { @@ -1036,19 +1049,7 @@ export class TextEditorOptions extends EditorOptions { * Returns if this options object has objects defined for the editor. */ hasOptionsDefined(): boolean { - return !!this.editorViewState || (!isUndefinedOrNull(this.startLineNumber) && !isUndefinedOrNull(this.startColumn)); - } - - /** - * Tells the editor to set show the given selection when the editor is being opened. - */ - selection(startLineNumber: number, startColumn: number, endLineNumber: number = startLineNumber, endColumn: number = startColumn): EditorOptions { - this.startLineNumber = startLineNumber; - this.startColumn = startColumn; - this.endLineNumber = endLineNumber; - this.endColumn = endColumn; - - return this; + return !!this.editorViewState || !!this.revealInCenterIfOutsideViewport || !!this.selection; } /** @@ -1069,12 +1070,6 @@ export class TextEditorOptions extends EditorOptions { * @return if something was applied */ apply(editor: ICodeEditor, scrollType: ScrollType): boolean { - - // View state - return this.applyViewState(editor, scrollType); - } - - private applyViewState(editor: ICodeEditor, scrollType: ScrollType): boolean { let gotApplied = false; // First try viewstate @@ -1084,36 +1079,20 @@ export class TextEditorOptions extends EditorOptions { } // Otherwise check for selection - else if (!isUndefinedOrNull(this.startLineNumber) && !isUndefinedOrNull(this.startColumn)) { - - // Select - if (!isUndefinedOrNull(this.endLineNumber) && !isUndefinedOrNull(this.endColumn)) { - const range = { - startLineNumber: this.startLineNumber, - startColumn: this.startColumn, - endLineNumber: this.endLineNumber, - endColumn: this.endColumn - }; - editor.setSelection(range); - if (this.revealInCenterIfOutsideViewport) { - editor.revealRangeInCenterIfOutsideViewport(range, scrollType); - } else { - editor.revealRangeInCenter(range, scrollType); - } - } + else if (this.selection) { + const range: IRange = { + startLineNumber: this.selection.startLineNumber, + startColumn: this.selection.startColumn, + endLineNumber: this.selection.endLineNumber ?? this.selection.startLineNumber, + endColumn: this.selection.endColumn ?? this.selection.startColumn + }; - // Reveal - else { - const pos = { - lineNumber: this.startLineNumber, - column: this.startColumn - }; - editor.setPosition(pos); - if (this.revealInCenterIfOutsideViewport) { - editor.revealPositionInCenterIfOutsideViewport(pos, scrollType); - } else { - editor.revealPositionInCenter(pos, scrollType); - } + editor.setSelection(range); + + if (this.revealInCenterIfOutsideViewport) { + editor.revealRangeInCenterIfOutsideViewport(range, scrollType); + } else { + editor.revealRangeInCenter(range, scrollType); } gotApplied = true; @@ -1319,13 +1298,13 @@ export async function pathsToEditors(paths: IPathData[] | undefined, fileService const exists = (typeof path.exists === 'boolean') ? path.exists : await fileService.exists(resource); - const options: ITextEditorOptions = { pinned: true }; - if (exists && typeof path.lineNumber === 'number') { - options.selection = { + const options: ITextEditorOptions = (exists && typeof path.lineNumber === 'number') ? { + selection: { startLineNumber: path.lineNumber, startColumn: path.columnNumber || 1 - }; - } + }, + pinned: true + } : { pinned: true }; let input: IResourceInput | IUntitledTextResourceInput; if (!exists) { diff --git a/src/vs/workbench/test/common/editor/editorOptions.test.ts b/src/vs/workbench/test/common/editor/editorOptions.test.ts deleted file mode 100644 index eb6f754080897..0000000000000 --- a/src/vs/workbench/test/common/editor/editorOptions.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { EditorOptions, TextEditorOptions } from 'vs/workbench/common/editor'; - -suite('Workbench editor options', () => { - - test('EditorOptions', () => { - let options = new EditorOptions(); - - assert(!options.preserveFocus); - options.preserveFocus = true; - assert(options.preserveFocus); - assert(!options.forceReload); - options.forceReload = true; - assert(options.forceReload); - - options = new EditorOptions(); - options.forceReload = true; - }); - - test('TextEditorOptions', () => { - let options = new TextEditorOptions(); - let otherOptions = new TextEditorOptions(); - - assert(!options.hasOptionsDefined()); - options.selection(1, 1, 2, 2); - assert(options.hasOptionsDefined()); - - otherOptions.selection(1, 1, 2, 2); - - options = new TextEditorOptions(); - options.forceReload = true; - options.selection(1, 1, 2, 2); - }); -}); \ No newline at end of file From 561f5215d73a99203a4e0bd6ea7e856ff869984b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 13 Jan 2020 10:16:40 +0100 Subject: [PATCH 189/315] for rename-file, show the new name more prominent, #77728 --- .../contrib/bulkEdit/browser/bulkEditTree.ts | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index 0fbdcd2634579..03746a0a7bcaf 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -160,21 +160,34 @@ class FileElementTemplate { this._localDisposables.add(dom.addDisposableListener(this._checkbox, 'change', (() => element.edit.updateChecked(this._checkbox.checked)))); this._checkbox.checked = element.edit.isChecked(); - this._label.setFile(element.uri, { - matches: createMatches(score), - fileKind: FileKind.FILE, - fileDecorations: { colors: true, badges: false }, - }); - - // details if (element.edit.type & BulkFileOperationType.Rename && element.edit.newUri) { - this._details.innerText = localize('detail.rename', "(renaming to {0})", this._labelService.getUriLabel(element.edit.newUri, { relative: true })); - } else if (element.edit.type & BulkFileOperationType.Create) { - this._details.innerText = localize('detail.create', "(creating)"); - } else if (element.edit.type & BulkFileOperationType.Create) { - this._details.innerText = localize('detail.del', "(deleting)"); + // rename: NEW NAME (old name) + this._label.setFile(element.edit.newUri, { + matches: createMatches(score), + fileKind: FileKind.FILE, + fileDecorations: { colors: true, badges: false }, + }); + + this._details.innerText = localize( + 'detail.rename', "(renaming from {0})", + this._labelService.getUriLabel(element.uri, { relative: true }) + ); + } else { - this._details.innerText = ''; + // create, delete, edit: NAME + this._label.setFile(element.uri, { + matches: createMatches(score), + fileKind: FileKind.FILE, + fileDecorations: { colors: true, badges: false }, + }); + + if (element.edit.type & BulkFileOperationType.Create) { + this._details.innerText = localize('detail.create', "(creating)"); + } else if (element.edit.type & BulkFileOperationType.Create) { + this._details.innerText = localize('detail.del', "(deleting)"); + } else { + this._details.innerText = ''; + } } } } From 5e758c10c969d668730f036abfc557d9cfb62b87 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 13 Jan 2020 10:17:36 +0100 Subject: [PATCH 190/315] fix compile errors --- .../contrib/search/browser/openSymbolHandler.ts | 7 ++----- src/vs/workbench/services/history/browser/history.ts | 10 +++------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts index 0f44a755c545f..3f01dba4e1d1d 100644 --- a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts @@ -119,14 +119,11 @@ class SymbolEntry extends EditorQuickOpenEntry { const input: IResourceInput = { resource: this.bearing.location.uri, options: { - pinned: !this.configurationService.getValue().workbench.editor.enablePreviewFromQuickOpen + pinned: !this.configurationService.getValue().workbench.editor.enablePreviewFromQuickOpen, + selection: this.bearing.location.range ? Range.collapseToStart(this.bearing.location.range) : undefined } }; - if (this.bearing.location.range) { - input.options!.selection = Range.collapseToStart(this.bearing.location.range); - } - return input; } diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index 02ca4db033bee..5557d08727d0d 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -354,15 +354,11 @@ export class HistoryService extends Disposable implements IHistoryService { private doNavigate(location: IStackEntry): Promise { const options: ITextEditorOptions = { - revealIfOpened: true // support to navigate across editor groups + revealIfOpened: true, // support to navigate across editor groups, + selection: location.selection, + revealInCenterIfOutsideViewport: !!location.selection }; - // Support selection and minimize scrolling by setting revealInCenterIfOutsideViewport - if (location.selection) { - options.selection = location.selection; - options.revealInCenterIfOutsideViewport = true; - } - if (location.input instanceof EditorInput) { return this.editorService.openEditor(location.input, options); } From f36e0c13c68564d5aa1a34bdb6b92ba8fe417ffc Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 13 Jan 2020 10:43:20 +0100 Subject: [PATCH 191/315] fix #88310 --- src/vs/workbench/api/common/extHostDocuments.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/common/extHostDocuments.ts b/src/vs/workbench/api/common/extHostDocuments.ts index d4406d5166a47..4e8bb4d125277 100644 --- a/src/vs/workbench/api/common/extHostDocuments.ts +++ b/src/vs/workbench/api/common/extHostDocuments.ts @@ -13,6 +13,7 @@ import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocum import * as TypeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import type * as vscode from 'vscode'; import { assertIsDefined } from 'vs/base/common/types'; +import { deepFreeze } from 'vs/base/common/objects'; export class ExtHostDocuments implements ExtHostDocumentsShape { @@ -144,7 +145,7 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { } data._acceptIsDirty(isDirty); data.onEvents(events); - this._onDidChangeDocument.fire({ + this._onDidChangeDocument.fire(deepFreeze({ document: data.document, contentChanges: events.changes.map((change) => { return { @@ -154,7 +155,7 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { text: change.text }; }) - }); + })); } public setWordDefinitionFor(modeId: string, wordDefinition: RegExp | undefined): void { From 94742854c255977b0806097c68b4a80883caf0c7 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Mon, 13 Jan 2020 10:59:18 +0100 Subject: [PATCH 192/315] Minor tweaks --- .../keybinding/browser/keybindingService.ts | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index e5387e3260d94..8bf8e567fe274 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -44,7 +44,7 @@ import { IKeymapService } from 'vs/workbench/services/keybinding/common/keymapIn import { getDispatchConfig } from 'vs/workbench/services/keybinding/common/dispatchConfig'; import { isArray } from 'vs/base/common/types'; import { INavigatorWithKeyboard, IKeyboard } from 'vs/workbench/services/keybinding/browser/navigatorKeyboard'; -import { ScanCode, ScanCodeUtils, IMMUTABLE_CODE_TO_KEY_CODE, IMMUTABLE_KEY_CODE_TO_CODE } from 'vs/base/common/scanCode'; +import { ScanCode, ScanCodeUtils, IMMUTABLE_CODE_TO_KEY_CODE } from 'vs/base/common/scanCode'; import { flatten } from 'vs/base/common/arrays'; import { BrowserFeatures, KeyboardSupport } from 'vs/base/browser/canIUse'; @@ -578,33 +578,38 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { } const code = ScanCodeUtils.toEnum(event.code); - if (NUMPAD_PRINTABLE_SCANCODES.indexOf(code) === -1) { - const keycode = IMMUTABLE_CODE_TO_KEY_CODE[code]; - if (keycode !== -1) { - // https://github.com/microsoft/vscode/issues/74934 - return false; - } - // consult the KeyboardMapperFactory to check the given event for - // a printable value. - const mapping = this.keymapService.getRawKeyboardMapping(); - if (!mapping) { - return false; - } - const keyInfo = mapping[event.code]; - if (!keyInfo) { - return false; - } - if (!keyInfo.value || /\s/.test(keyInfo.value)) { - return false; - } - } else { - const immutableScanCode = IMMUTABLE_KEY_CODE_TO_CODE[event.keyCode]; - if (code !== immutableScanCode) { - // NumLock is disabled - return false; + if (NUMPAD_PRINTABLE_SCANCODES.indexOf(code) !== -1) { + // This is a numpad key that might produce a printable character based on NumLock. + // Let's check if NumLock is on or off based on the event's keyCode. + // e.g. + // - when NumLock is off, ScanCode.Numpad4 produces KeyCode.LeftArrow + // - when NumLock is on, ScanCode.Numpad4 produces KeyCode.NUMPAD_4 + // However, ScanCode.NumpadAdd always produces KeyCode.NUMPAD_ADD + if (event.keyCode === IMMUTABLE_CODE_TO_KEY_CODE[code]) { + // NumLock is on or this is /, *, -, + on the numpad + return true; } + return false; } + const keycode = IMMUTABLE_CODE_TO_KEY_CODE[code]; + if (keycode !== -1) { + // https://github.com/microsoft/vscode/issues/74934 + return false; + } + // consult the KeyboardMapperFactory to check the given event for + // a printable value. + const mapping = this.keymapService.getRawKeyboardMapping(); + if (!mapping) { + return false; + } + const keyInfo = mapping[event.code]; + if (!keyInfo) { + return false; + } + if (!keyInfo.value || /\s/.test(keyInfo.value)) { + return false; + } return true; } } From dd54a1439d65cf08090487ef03d6d8b93dd9ae1a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 13 Jan 2020 11:22:58 +0100 Subject: [PATCH 193/315] debt - move text file model telemetry into telemetry contribution --- .../browser/telemetry.contribution.ts | 124 +++++++++++++++++- .../textfile/common/textFileEditorModel.ts | 112 +--------------- .../common/textFileEditorModelManager.ts | 6 + .../services/textfile/common/textfiles.ts | 2 + 4 files changed, 131 insertions(+), 113 deletions(-) diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index 35c131834e026..eca81528ce4ec 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -19,20 +19,46 @@ import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry'; import { configurationTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { ITextFileService, TextFileModelChangeEvent } from 'vs/workbench/services/textfile/common/textfiles'; +import { extname, basename, isEqual, isEqualOrParent, joinPath } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { Schemas } from 'vs/base/common/network'; +import { guessMimeTypes } from 'vs/base/common/mime'; +import { hash } from 'vs/base/common/hash'; + +type TelemetryData = { + mimeType: string; + ext: string; + path: number; + reason?: number; + whitelistedjson?: string; +}; + +type FileTelemetryDataFragment = { + mimeType: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + ext: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + path: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + reason?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + whitelistedjson?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; +}; export class TelemetryContribution extends Disposable implements IWorkbenchContribution { + private static WHITELIST_JSON = ['package.json', 'package-lock.json', 'tsconfig.json', 'jsconfig.json', 'bower.json', '.eslintrc.json', 'tslint.json', 'composer.json']; + private static WHITELIST_WORKSPACE_JSON = ['settings.json', 'extensions.json', 'tasks.json', 'launch.json']; + constructor( - @ITelemetryService telemetryService: ITelemetryService, - @IWorkspaceContextService contextService: IWorkspaceContextService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IActivityBarService activityBarService: IActivityBarService, @ILifecycleService lifecycleService: ILifecycleService, @IEditorService editorService: IEditorService, @IKeybindingService keybindingsService: IKeybindingService, @IWorkbenchThemeService themeService: IWorkbenchThemeService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IConfigurationService configurationService: IConfigurationService, - @IViewletService viewletService: IViewletService + @IViewletService viewletService: IViewletService, + @ITextFileService textFileService: ITextFileService, ) { super(); @@ -45,6 +71,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr outerHeight: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; outerWidth: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; }; + type WorkspaceLoadClassification = { userAgent: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; emptyWorkbench: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; @@ -59,6 +86,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr restoredEditors: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; startupKind: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; }; + type WorkspaceLoadEvent = { userAgent: string; windowSize: { innerHeight: number, innerWidth: number, outerHeight: number, outerWidth: number }; @@ -73,6 +101,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr restoredEditors: number; startupKind: StartupKind; }; + telemetryService.publicLog2('workspaceLoad', { userAgent: navigator.userAgent, windowSize: { innerHeight: window.innerHeight, innerWidth: window.innerWidth, outerHeight: window.outerHeight, outerWidth: window.outerWidth }, @@ -94,9 +123,94 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr // Configuration Telemetry this._register(configurationTelemetry(telemetryService, configurationService)); + // Files Telemetry + this._register(textFileService.models.onModelLoaded(e => this.onTextFileModelLoaded(e))); + this._register(textFileService.models.onModelSaved(e => this.onTextFileModelSaved(e))); + // Lifecycle this._register(lifecycleService.onShutdown(() => this.dispose())); } + + private onTextFileModelLoaded(event: TextFileModelChangeEvent): void { + const settingsType = this.getTypeIfSettings(event.resource); + if (settingsType) { + type SettingsReadClassification = { + settingsType: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + }; + + this.telemetryService.publicLog2<{ settingsType: string }, SettingsReadClassification>('settingsRead', { settingsType }); // Do not log read to user settings.json and .vscode folder as a fileGet event as it ruins our JSON usage data + } else { + type FileGetClassification = {} & FileTelemetryDataFragment; + + this.telemetryService.publicLog2('fileGet', this.getTelemetryData(event.resource)); + } + } + + private onTextFileModelSaved(event: TextFileModelChangeEvent): void { + const settingsType = this.getTypeIfSettings(event.resource); + if (settingsType) { + type SettingsWrittenClassification = { + settingsType: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + }; + this.telemetryService.publicLog2<{ settingsType: string }, SettingsWrittenClassification>('settingsWritten', { settingsType }); // Do not log write to user settings.json and .vscode folder as a filePUT event as it ruins our JSON usage data + } else { + type FilePutClassfication = {} & FileTelemetryDataFragment; + this.telemetryService.publicLog2('filePUT', this.getTelemetryData(event.resource)); + } + } + + private getTypeIfSettings(resource: URI): string { + if (extname(resource) !== '.json') { + return ''; + } + + // Check for global settings file + if (isEqual(resource, this.environmentService.settingsResource)) { + return 'global-settings'; + } + + // Check for keybindings file + if (isEqual(resource, this.environmentService.keybindingsResource)) { + return 'keybindings'; + } + + // Check for snippets + if (isEqualOrParent(resource, joinPath(this.environmentService.userRoamingDataHome, 'snippets'))) { + return 'snippets'; + } + + // Check for workspace settings file + const folders = this.contextService.getWorkspace().folders; + for (const folder of folders) { + if (isEqualOrParent(resource, folder.toResource('.vscode'))) { + const filename = basename(resource); + if (TelemetryContribution.WHITELIST_WORKSPACE_JSON.indexOf(filename) > -1) { + return `.vscode/${filename}`; + } + } + } + + return ''; + } + + private getTelemetryData(resource: URI, reason?: number): TelemetryData { + const ext = extname(resource); + const fileName = basename(resource); + const path = resource.scheme === Schemas.file ? resource.fsPath : resource.path; + const telemetryData = { + mimeType: guessMimeTypes(resource).join(', '), + ext, + path: hash(path), + reason, + whitelistedjson: undefined as string | undefined + }; + + if (ext === '.json' && TelemetryContribution.WHITELIST_JSON.indexOf(fileName) > -1) { + telemetryData['whitelistedjson'] = fileName; + } + + return telemetryData; + } } -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TelemetryContribution, LifecyclePhase.Restored); \ No newline at end of file +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TelemetryContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 19ade6f51be74..22c6313771ff2 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -5,13 +5,10 @@ import * as nls from 'vs/nls'; import { Emitter } from 'vs/base/common/event'; -import { guessMimeTypes } from 'vs/base/common/mime'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { URI } from 'vs/base/common/uri'; import { assertIsDefined } from 'vs/base/common/types'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ITextFileService, ModelState, ITextFileEditorModel, ISaveErrorHandler, ISaveParticipant, StateChange, ITextFileStreamContent, ILoadOptions, LoadReason, IResolvedTextFileEditorModel, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ModelState, ITextFileEditorModel, ISaveErrorHandler, ISaveParticipant, StateChange, ITextFileStreamContent, ILoadOptions, IResolvedTextFileEditorModel, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { EncodingMode, IRevertOptions, SaveReason } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; @@ -19,15 +16,12 @@ import { IFileService, FileOperationError, FileOperationResult, FileChangesEvent import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { timeout } from 'vs/base/common/async'; import { ITextBufferFactory } from 'vs/editor/common/model'; -import { hash } from 'vs/base/common/hash'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ILogService } from 'vs/platform/log/common/log'; -import { isEqual, isEqualOrParent, extname, basename, joinPath } from 'vs/base/common/resources'; +import { isEqual, basename } from 'vs/base/common/resources'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { Schemas } from 'vs/base/common/network'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; @@ -39,30 +33,11 @@ export interface IBackupMetaData { orphaned: boolean; } -type FileTelemetryDataFragment = { - mimeType: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - ext: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - path: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - reason?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - whitelistedjson?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; -}; - -type TelemetryData = { - mimeType: string; - ext: string; - path: number; - reason?: number; - whitelistedjson?: string; -}; - /** * The text file editor model listens to changes to its underlying code editor model and saves these changes through the file service back to the disk. */ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFileEditorModel { - static WHITELIST_JSON = ['package.json', 'package-lock.json', 'tsconfig.json', 'jsconfig.json', 'bower.json', '.eslintrc.json', 'tslint.json', 'composer.json']; - static WHITELIST_WORKSPACE_JSON = ['settings.json', 'extensions.json', 'tasks.json', 'launch.json']; - private static saveErrorHandler: ISaveErrorHandler; static setSaveErrorHandler(handler: ISaveErrorHandler): void { TextFileEditorModel.saveErrorHandler = handler; } @@ -106,11 +81,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil @IModelService modelService: IModelService, @IFileService private readonly fileService: IFileService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @ITelemetryService private readonly telemetryService: ITelemetryService, @ITextFileService private readonly textFileService: ITextFileService, @IBackupFileService private readonly backupFileService: IBackupFileService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @ILogService private readonly logService: ILogService, @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService @@ -414,19 +386,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.doCreateTextModel(content.resource, content.value, !!fromBackup); } - // Telemetry: We log the fileGet telemetry event after the model has been loaded to ensure a good mimetype - const settingsType = this.getTypeIfSettings(); - if (settingsType) { - type SettingsReadClassification = { - settingsType: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - }; - - this.telemetryService.publicLog2<{ settingsType: string }, SettingsReadClassification>('settingsRead', { settingsType }); // Do not log read to user settings.json and .vscode folder as a fileGet event as it ruins our JSON usage data - } else { - type FileGetClassification = {} & FileTelemetryDataFragment; - - this.telemetryService.publicLog2('fileGet', this.getTelemetryData(options?.reason ?? LoadReason.OTHER)); - } + // Emit as event + this._onDidChangeState.fire(StateChange.LOADED); return this; } @@ -699,18 +660,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Emit Events this._onDidChangeState.fire(StateChange.SAVED); this._onDidChangeDirty.fire(); - - // Telemetry - const settingsType = this.getTypeIfSettings(); - if (settingsType) { - type SettingsWrittenClassification = { - settingsType: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - }; - this.telemetryService.publicLog2<{ settingsType: string }, SettingsWrittenClassification>('settingsWritten', { settingsType }); // Do not log write to user settings.json and .vscode folder as a filePUT event as it ruins our JSON usage data - } else { - type FilePutClassfication = {} & FileTelemetryDataFragment; - this.telemetryService.publicLog2('filePUT', this.getTelemetryData(options.reason)); - } }, error => { this.logService.error(`[text file model] doSave(${versionId}) - exit - resulted in a save error: ${error.toString()}`, this.resource.toString()); @@ -731,59 +680,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil })); } - private getTypeIfSettings(): string { - if (extname(this.resource) !== '.json') { - return ''; - } - - // Check for global settings file - if (isEqual(this.resource, this.environmentService.settingsResource)) { - return 'global-settings'; - } - - // Check for keybindings file - if (isEqual(this.resource, this.environmentService.keybindingsResource)) { - return 'keybindings'; - } - - // Check for snippets - if (isEqualOrParent(this.resource, joinPath(this.environmentService.userRoamingDataHome, 'snippets'))) { - return 'snippets'; - } - - // Check for workspace settings file - const folders = this.contextService.getWorkspace().folders; - for (const folder of folders) { - if (isEqualOrParent(this.resource, folder.toResource('.vscode'))) { - const filename = basename(this.resource); - if (TextFileEditorModel.WHITELIST_WORKSPACE_JSON.indexOf(filename) > -1) { - return `.vscode/${filename}`; - } - } - } - - return ''; - } - - private getTelemetryData(reason: number | undefined): TelemetryData { - const ext = extname(this.resource); - const fileName = basename(this.resource); - const path = this.resource.scheme === Schemas.file ? this.resource.fsPath : this.resource.path; - const telemetryData = { - mimeType: guessMimeTypes(this.resource).join(', '), - ext, - path: hash(path), - reason, - whitelistedjson: undefined as string | undefined - }; - - if (ext === '.json' && TextFileEditorModel.WHITELIST_JSON.indexOf(fileName) > -1) { - telemetryData['whitelistedjson'] = fileName; - } - - return telemetryData; - } - private doTouch(versionId: number): Promise { if (!this.isResolved()) { return Promise.resolve(); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index deb34c132fbb9..523f371578a0d 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -18,6 +18,9 @@ import { onUnexpectedError } from 'vs/base/common/errors'; export class TextFileEditorModelManager extends Disposable implements ITextFileEditorModelManager { + private readonly _onModelLoaded = this._register(new Emitter()); + readonly onModelLoaded = this._onModelLoaded.event; + private readonly _onModelDirty = this._register(new Emitter()); readonly onModelDirty = this._onModelDirty.event; @@ -171,6 +174,9 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE this.mapResourceToStateChangeListener.set(resource, model.onDidChangeState(state => { const event = new TextFileModelChangeEvent(newModel, state); switch (state) { + case StateChange.LOADED: + this._onModelLoaded.fire(event); + break; case StateChange.DIRTY: this._onModelDirty.fire(event); break; diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 8a0439d45de47..c5efc94245d24 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -276,6 +276,7 @@ export const enum ModelState { } export const enum StateChange { + LOADED, DIRTY, SAVE_ERROR, SAVED, @@ -380,6 +381,7 @@ export interface ITextFileEditorModelManager { readonly onModelEncodingChanged: Event; readonly onModelOrphanedChanged: Event; + readonly onModelLoaded: Event; readonly onModelDirty: Event; readonly onModelSaveError: Event; readonly onModelSaved: Event; From bc0f81bd0545af1eb0282870a95b0f156811b48e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 13 Jan 2020 11:42:13 +0100 Subject: [PATCH 194/315] debt - cleanup file model a bit --- .../textfile/common/saveSequenzializer.ts | 95 +++++++++++++++ .../textfile/common/textFileEditorModel.ts | 110 ++---------------- .../textfile/test/saveSequenzializer.test.ts | 90 ++++++++++++++ .../textfile/test/textFileEditorModel.test.ts | 81 +------------ .../test/textFileEditorModelManager.test.ts | 11 ++ .../common/textResourcePropertiesService.ts | 0 src/vs/workbench/workbench.common.main.ts | 2 +- 7 files changed, 206 insertions(+), 183 deletions(-) create mode 100644 src/vs/workbench/services/textfile/common/saveSequenzializer.ts create mode 100644 src/vs/workbench/services/textfile/test/saveSequenzializer.test.ts rename src/vs/workbench/services/{textfile => textresourceProperties}/common/textResourcePropertiesService.ts (100%) diff --git a/src/vs/workbench/services/textfile/common/saveSequenzializer.ts b/src/vs/workbench/services/textfile/common/saveSequenzializer.ts new file mode 100644 index 0000000000000..2b7cc0c828209 --- /dev/null +++ b/src/vs/workbench/services/textfile/common/saveSequenzializer.ts @@ -0,0 +1,95 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +interface IPendingSave { + versionId: number; + promise: Promise; +} + +interface ISaveOperation { + promise: Promise; + promiseResolve: () => void; + promiseReject: (error: Error) => void; + run: () => Promise; +} + +export class SaveSequentializer { + private _pendingSave?: IPendingSave; + private _nextSave?: ISaveOperation; + + hasPendingSave(versionId?: number): boolean { + if (!this._pendingSave) { + return false; + } + + if (typeof versionId === 'number') { + return this._pendingSave.versionId === versionId; + } + + return !!this._pendingSave; + } + + get pendingSave(): Promise | undefined { + return this._pendingSave ? this._pendingSave.promise : undefined; + } + + setPending(versionId: number, promise: Promise): Promise { + this._pendingSave = { versionId, promise }; + + promise.then(() => this.donePending(versionId), () => this.donePending(versionId)); + + return promise; + } + + private donePending(versionId: number): void { + if (this._pendingSave && versionId === this._pendingSave.versionId) { + + // only set pending to done if the promise finished that is associated with that versionId + this._pendingSave = undefined; + + // schedule the next save now that we are free if we have any + this.triggerNextSave(); + } + } + + private triggerNextSave(): void { + if (this._nextSave) { + const saveOperation = this._nextSave; + this._nextSave = undefined; + + // Run next save and complete on the associated promise + saveOperation.run().then(saveOperation.promiseResolve, saveOperation.promiseReject); + } + } + + setNext(run: () => Promise): Promise { + + // this is our first next save, so we create associated promise with it + // so that we can return a promise that completes when the save operation + // has completed. + if (!this._nextSave) { + let promiseResolve: () => void; + let promiseReject: (error: Error) => void; + const promise = new Promise((resolve, reject) => { + promiseResolve = resolve; + promiseReject = reject; + }); + + this._nextSave = { + run, + promise, + promiseResolve: promiseResolve!, + promiseReject: promiseReject! + }; + } + + // we have a previous next save, just overwrite it + else { + this._nextSave.run = run; + } + + return this._nextSave.promise; + } +} diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 22c6313771ff2..8293240037e26 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -13,7 +13,6 @@ import { EncodingMode, IRevertOptions, SaveReason } from 'vs/workbench/common/ed import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IFileService, FileOperationError, FileOperationResult, FileChangesEvent, FileChangeType, IFileStatWithMetadata, ETAG_DISABLED, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { timeout } from 'vs/base/common/async'; @@ -24,8 +23,9 @@ import { isEqual, basename } from 'vs/base/common/resources'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { SaveSequentializer } from 'vs/workbench/services/textfile/common/saveSequenzializer'; -export interface IBackupMetaData { +interface IBackupMetaData { mtime: number; ctime: number; size: number; @@ -80,7 +80,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil @IModeService modeService: IModeService, @IModelService modelService: IModelService, @IFileService private readonly fileService: IFileService, - @IInstantiationService private readonly instantiationService: IInstantiationService, @ITextFileService private readonly textFileService: ITextFileService, @IBackupFileService private readonly backupFileService: IBackupFileService, @ILogService private readonly logService: ILogService, @@ -755,7 +754,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Prepare handler if (!TextFileEditorModel.saveErrorHandler) { - TextFileEditorModel.setSaveErrorHandler(this.instantiationService.createInstance(DefaultSaveErrorHandler)); + const notificationService = this.notificationService; + TextFileEditorModel.setSaveErrorHandler({ + onSaveError(error: Error, model: TextFileEditorModel): void { + notificationService.error(nls.localize('genericSaveError', "Failed to save '{0}': {1}", basename(model.resource), toErrorMessage(error, false))); + } + }); } // Handle @@ -887,102 +891,4 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } -interface IPendingSave { - versionId: number; - promise: Promise; -} - -interface ISaveOperation { - promise: Promise; - promiseResolve: () => void; - promiseReject: (error: Error) => void; - run: () => Promise; -} - -export class SaveSequentializer { - private _pendingSave?: IPendingSave; - private _nextSave?: ISaveOperation; - - hasPendingSave(versionId?: number): boolean { - if (!this._pendingSave) { - return false; - } - - if (typeof versionId === 'number') { - return this._pendingSave.versionId === versionId; - } - - return !!this._pendingSave; - } - - get pendingSave(): Promise | undefined { - return this._pendingSave ? this._pendingSave.promise : undefined; - } - - setPending(versionId: number, promise: Promise): Promise { - this._pendingSave = { versionId, promise }; - - promise.then(() => this.donePending(versionId), () => this.donePending(versionId)); - - return promise; - } - - private donePending(versionId: number): void { - if (this._pendingSave && versionId === this._pendingSave.versionId) { - - // only set pending to done if the promise finished that is associated with that versionId - this._pendingSave = undefined; - - // schedule the next save now that we are free if we have any - this.triggerNextSave(); - } - } - - private triggerNextSave(): void { - if (this._nextSave) { - const saveOperation = this._nextSave; - this._nextSave = undefined; - - // Run next save and complete on the associated promise - saveOperation.run().then(saveOperation.promiseResolve, saveOperation.promiseReject); - } - } - - setNext(run: () => Promise): Promise { - - // this is our first next save, so we create associated promise with it - // so that we can return a promise that completes when the save operation - // has completed. - if (!this._nextSave) { - let promiseResolve: () => void; - let promiseReject: (error: Error) => void; - const promise = new Promise((resolve, reject) => { - promiseResolve = resolve; - promiseReject = reject; - }); - - this._nextSave = { - run, - promise, - promiseResolve: promiseResolve!, - promiseReject: promiseReject! - }; - } - - // we have a previous next save, just overwrite it - else { - this._nextSave.run = run; - } - - return this._nextSave.promise; - } -} - -class DefaultSaveErrorHandler implements ISaveErrorHandler { - - constructor(@INotificationService private readonly notificationService: INotificationService) { } - onSaveError(error: Error, model: TextFileEditorModel): void { - this.notificationService.error(nls.localize('genericSaveError', "Failed to save '{0}': {1}", basename(model.resource), toErrorMessage(error, false))); - } -} diff --git a/src/vs/workbench/services/textfile/test/saveSequenzializer.test.ts b/src/vs/workbench/services/textfile/test/saveSequenzializer.test.ts new file mode 100644 index 0000000000000..c85a38bd8b321 --- /dev/null +++ b/src/vs/workbench/services/textfile/test/saveSequenzializer.test.ts @@ -0,0 +1,90 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { timeout } from 'vs/base/common/async'; +import { SaveSequentializer } from 'vs/workbench/services/textfile/common/saveSequenzializer'; + +suite('Files - SaveSequentializer', () => { + + test('SaveSequentializer - pending basics', async function () { + const sequentializer = new SaveSequentializer(); + + assert.ok(!sequentializer.hasPendingSave()); + assert.ok(!sequentializer.hasPendingSave(2323)); + assert.ok(!sequentializer.pendingSave); + + // pending removes itself after done + await sequentializer.setPending(1, Promise.resolve()); + assert.ok(!sequentializer.hasPendingSave()); + assert.ok(!sequentializer.hasPendingSave(1)); + assert.ok(!sequentializer.pendingSave); + + // pending removes itself after done (use timeout) + sequentializer.setPending(2, timeout(1)); + assert.ok(sequentializer.hasPendingSave()); + assert.ok(sequentializer.hasPendingSave(2)); + assert.ok(!sequentializer.hasPendingSave(1)); + assert.ok(sequentializer.pendingSave); + + await timeout(2); + assert.ok(!sequentializer.hasPendingSave()); + assert.ok(!sequentializer.hasPendingSave(2)); + assert.ok(!sequentializer.pendingSave); + }); + + test('SaveSequentializer - pending and next (finishes instantly)', async function () { + const sequentializer = new SaveSequentializer(); + + let pendingDone = false; + sequentializer.setPending(1, timeout(1).then(() => { pendingDone = true; return; })); + + // next finishes instantly + let nextDone = false; + const res = sequentializer.setNext(() => Promise.resolve(null).then(() => { nextDone = true; return; })); + + await res; + assert.ok(pendingDone); + assert.ok(nextDone); + }); + + test('SaveSequentializer - pending and next (finishes after timeout)', async function () { + const sequentializer = new SaveSequentializer(); + + let pendingDone = false; + sequentializer.setPending(1, timeout(1).then(() => { pendingDone = true; return; })); + + // next finishes after timeout + let nextDone = false; + const res = sequentializer.setNext(() => timeout(1).then(() => { nextDone = true; return; })); + + await res; + assert.ok(pendingDone); + assert.ok(nextDone); + }); + + test('SaveSequentializer - pending and multiple next (last one wins)', async function () { + const sequentializer = new SaveSequentializer(); + + let pendingDone = false; + sequentializer.setPending(1, timeout(1).then(() => { pendingDone = true; return; })); + + // next finishes after timeout + let firstDone = false; + let firstRes = sequentializer.setNext(() => timeout(2).then(() => { firstDone = true; return; })); + + let secondDone = false; + let secondRes = sequentializer.setNext(() => timeout(3).then(() => { secondDone = true; return; })); + + let thirdDone = false; + let thirdRes = sequentializer.setNext(() => timeout(4).then(() => { thirdDone = true; return; })); + + await Promise.all([firstRes, secondRes, thirdRes]); + assert.ok(pendingDone); + assert.ok(!firstDone); + assert.ok(!secondDone); + assert.ok(thirdDone); + }); +}); diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts index 5d0f0212cadfd..a3b647521f7f7 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { EncodingMode } from 'vs/workbench/common/editor'; -import { TextFileEditorModel, SaveSequentializer } from 'vs/workbench/services/textfile/common/textFileEditorModel'; +import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { ITextFileService, ModelState, StateChange, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { workbenchInstantiationService, TestTextFileService, createFileInput, TestFileService } from 'vs/workbench/test/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; @@ -475,83 +475,4 @@ suite('Files - TextFileEditorModel', () => { await model.save(); model.dispose(); }); - - test('SaveSequentializer - pending basics', async function () { - const sequentializer = new SaveSequentializer(); - - assert.ok(!sequentializer.hasPendingSave()); - assert.ok(!sequentializer.hasPendingSave(2323)); - assert.ok(!sequentializer.pendingSave); - - // pending removes itself after done - await sequentializer.setPending(1, Promise.resolve()); - assert.ok(!sequentializer.hasPendingSave()); - assert.ok(!sequentializer.hasPendingSave(1)); - assert.ok(!sequentializer.pendingSave); - - // pending removes itself after done (use timeout) - sequentializer.setPending(2, timeout(1)); - assert.ok(sequentializer.hasPendingSave()); - assert.ok(sequentializer.hasPendingSave(2)); - assert.ok(!sequentializer.hasPendingSave(1)); - assert.ok(sequentializer.pendingSave); - - await timeout(2); - assert.ok(!sequentializer.hasPendingSave()); - assert.ok(!sequentializer.hasPendingSave(2)); - assert.ok(!sequentializer.pendingSave); - }); - - test('SaveSequentializer - pending and next (finishes instantly)', async function () { - const sequentializer = new SaveSequentializer(); - - let pendingDone = false; - sequentializer.setPending(1, timeout(1).then(() => { pendingDone = true; return; })); - - // next finishes instantly - let nextDone = false; - const res = sequentializer.setNext(() => Promise.resolve(null).then(() => { nextDone = true; return; })); - - await res; - assert.ok(pendingDone); - assert.ok(nextDone); - }); - - test('SaveSequentializer - pending and next (finishes after timeout)', async function () { - const sequentializer = new SaveSequentializer(); - - let pendingDone = false; - sequentializer.setPending(1, timeout(1).then(() => { pendingDone = true; return; })); - - // next finishes after timeout - let nextDone = false; - const res = sequentializer.setNext(() => timeout(1).then(() => { nextDone = true; return; })); - - await res; - assert.ok(pendingDone); - assert.ok(nextDone); - }); - - test('SaveSequentializer - pending and multiple next (last one wins)', async function () { - const sequentializer = new SaveSequentializer(); - - let pendingDone = false; - sequentializer.setPending(1, timeout(1).then(() => { pendingDone = true; return; })); - - // next finishes after timeout - let firstDone = false; - let firstRes = sequentializer.setNext(() => timeout(2).then(() => { firstDone = true; return; })); - - let secondDone = false; - let secondRes = sequentializer.setNext(() => timeout(3).then(() => { secondDone = true; return; })); - - let thirdDone = false; - let thirdRes = sequentializer.setNext(() => timeout(4).then(() => { thirdDone = true; return; })); - - await Promise.all([firstRes, secondRes, thirdRes]); - assert.ok(pendingDone); - assert.ok(!firstDone); - assert.ok(!secondDone); - assert.ok(thirdDone); - }); }); diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts index 4145c7f6f8fac..8c9e2e98670bd 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts @@ -141,11 +141,18 @@ suite('Files - TextFileEditorModelManager', () => { const resource1 = toResource.call(this, '/path/index.txt'); const resource2 = toResource.call(this, '/path/other.txt'); + let loadedCounter = 0; let dirtyCounter = 0; let revertedCounter = 0; let savedCounter = 0; let encodingCounter = 0; + manager.onModelLoaded(e => { + if (e.resource.toString() === resource1.toString()) { + loadedCounter++; + } + }); + manager.onModelDirty(e => { if (e.resource.toString() === resource1.toString()) { dirtyCounter++; @@ -171,10 +178,14 @@ suite('Files - TextFileEditorModelManager', () => { }); const model1 = await manager.loadOrCreate(resource1, { encoding: 'utf8' }); + assert.equal(loadedCounter, 1); + accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource: resource1, type: FileChangeType.DELETED }])); accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource: resource1, type: FileChangeType.ADDED }])); const model2 = await manager.loadOrCreate(resource2, { encoding: 'utf8' }); + assert.equal(loadedCounter, 2); + model1.textEditorModel!.setValue('changed'); model1.updatePreferredEncoding('utf16'); diff --git a/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts b/src/vs/workbench/services/textresourceProperties/common/textResourcePropertiesService.ts similarity index 100% rename from src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts rename to src/vs/workbench/services/textresourceProperties/common/textResourcePropertiesService.ts diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index c437848af7efe..ab49435a19ba0 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -71,7 +71,7 @@ import 'vs/workbench/services/history/browser/history'; import 'vs/workbench/services/activity/browser/activityService'; import 'vs/workbench/services/keybinding/browser/keybindingService'; import 'vs/workbench/services/untitled/common/untitledTextEditorService'; -import 'vs/workbench/services/textfile/common/textResourcePropertiesService'; +import 'vs/workbench/services/textresourceProperties/common/textResourcePropertiesService'; import 'vs/workbench/services/mode/common/workbenchModeService'; import 'vs/workbench/services/commands/common/commandService'; import 'vs/workbench/services/themes/browser/workbenchThemeService'; From 0d9ebc47d3f1c5059f379b0572b27b828a81122b Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 13 Jan 2020 12:06:24 +0100 Subject: [PATCH 195/315] add setting for semantic highlighting --- src/vs/editor/common/config/editorOptions.ts | 55 ++++++++++++++++++- .../common/services/modelServiceImpl.ts | 52 +++++++++++++++--- src/vs/monaco.d.ts | 11 ++++ 3 files changed, 109 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 94af6b10d1b55..990d885089a13 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -1536,6 +1536,55 @@ class EditorHover extends BaseEditorOption>; + +class EditorSemanticHighlighting extends BaseEditorOption { + + constructor() { + const defaults: EditorSemanticHighlightingOptions = { + enabled: true + }; + super( + EditorOption.semanticHighlighting, 'semanticHighlighting', defaults, + { + 'editor.semanticHighlighting.enabled': { + type: 'boolean', + default: defaults.enabled, + description: nls.localize('semanticHighlighting.enabled', "Controls whether the semanticHighlighting is shown for the languages that support it.") + } + } + ); + } + + public validate(_input: any): EditorSemanticHighlightingOptions { + if (typeof _input !== 'object') { + return this.defaultValue; + } + const input = _input as IEditorSemanticHighlightingOptions; + return { + enabled: EditorBooleanOption.boolean(input.enabled, this.defaultValue.enabled) + }; + } +} + +//#endregion + //#region layoutInfo /** @@ -2823,7 +2872,7 @@ class EditorSuggest extends BaseEditorOption this._updateModelOptions()); this._updateModelOptions(); - this._register(new SemanticColoringFeature(this, themeService, logService)); + this._register(new SemanticColoringFeature(this, themeService, configurationService, logService)); } private static _readModelOptions(config: IRawConfig, isForSimpleWidget: boolean): ITextModelCreationOptions { @@ -442,20 +442,56 @@ export interface ILineSequence { } class SemanticColoringFeature extends Disposable { + + private static readonly SETTING_ID = 'editor.semanticHighlighting'; + private _watchers: Record; private _semanticStyling: SemanticStyling; + private _configurationService: IConfigurationService; - constructor(modelService: IModelService, themeService: IThemeService, logService: ILogService) { + constructor(modelService: IModelService, themeService: IThemeService, configurationService: IConfigurationService, logService: ILogService) { super(); + this._configurationService = configurationService; this._watchers = Object.create(null); this._semanticStyling = this._register(new SemanticStyling(themeService, logService)); - this._register(modelService.onModelAdded((model) => { + + const isSemanticColoringEnabled = (model: ITextModel) => { + return configurationService.getValue(SemanticColoringFeature.SETTING_ID, { overrideIdentifier: model.getLanguageIdentifier().language, resource: model.uri }).enabled; + }; + const register = (model: ITextModel) => { this._watchers[model.uri.toString()] = new ModelSemanticColoring(model, themeService, this._semanticStyling); + }; + const deregister = (model: ITextModel, modelSemanticColoring: ModelSemanticColoring) => { + modelSemanticColoring.dispose(); + delete this._watchers[model.uri.toString()]; + }; + this._register(modelService.onModelAdded((model) => { + if (isSemanticColoringEnabled(model)) { + register(model); + } })); this._register(modelService.onModelRemoved((model) => { - this._watchers[model.uri.toString()].dispose(); - delete this._watchers[model.uri.toString()]; + const curr = this._watchers[model.uri.toString()]; + if (curr) { + deregister(model, curr); + } })); + this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(SemanticColoringFeature.SETTING_ID)) { + for (let model of modelService.getModels()) { + const curr = this._watchers[model.uri.toString()]; + if (isSemanticColoringEnabled(model)) { + if (!curr) { + register(model); + } + } else { + if (curr) { + deregister(model, curr); + } + } + } + } + }); } } @@ -685,7 +721,6 @@ class ModelSemanticColoring extends Disposable { } public dispose(): void { - this._isDisposed = true; if (this._currentResponse) { this._currentResponse.dispose(); this._currentResponse = null; @@ -694,6 +729,9 @@ class ModelSemanticColoring extends Disposable { this._currentRequestCancellationTokenSource.cancel(); this._currentRequestCancellationTokenSource = null; } + this._setSemanticTokens(null, null, null, []); + this._isDisposed = true; + super.dispose(); } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index ace85852ace2c..a58453dca5e9a 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3088,6 +3088,17 @@ declare namespace monaco.editor { sticky?: boolean; } + /** + * Configuration options for semantic highlighting + */ + export interface IEditorSemanticHighlightingOptions { + /** + * Enable semantic highlighting. + * Defaults to true. + */ + enabled?: boolean; + } + /** * A description for the overview ruler position. */ From 9c4aa6d903f1ad9075dfbd886e7b0efbfc9c406a Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 13 Jan 2020 11:54:26 +0100 Subject: [PATCH 196/315] :lipstick: --- src/vs/editor/contrib/suggest/suggest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/suggest/suggest.ts b/src/vs/editor/contrib/suggest/suggest.ts index 72ca092ea5e7c..f79dbe4a54cbd 100644 --- a/src/vs/editor/contrib/suggest/suggest.ts +++ b/src/vs/editor/contrib/suggest/suggest.ts @@ -30,6 +30,7 @@ export class CompletionItem { _brand!: 'ISuggestionItem'; readonly resolve: (token: CancellationToken) => Promise; + isResolved: boolean = false; // readonly editStart: IPosition; @@ -44,7 +45,6 @@ export class CompletionItem { // sorting, filtering score: FuzzyScore = FuzzyScore.Default; distance: number = 0; - isResolved: boolean = false; idx?: number; word?: string; From 0dfc47f4ef3352c8faa73aa0fa7a1414e7134351 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 13 Jan 2020 12:19:08 +0100 Subject: [PATCH 197/315] use set instead of array --- .../configuration/common/configurationRegistry.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index b62c02f4000ce..61590f38fc499 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -11,6 +11,7 @@ import * as types from 'vs/base/common/types'; import * as strings from 'vs/base/common/strings'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { values } from 'vs/base/common/map'; export const Extensions = { Configuration: 'base.contributions.configuration' @@ -157,7 +158,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { private readonly configurationProperties: { [qualifiedKey: string]: IJSONSchema }; private readonly excludedConfigurationProperties: { [qualifiedKey: string]: IJSONSchema }; private readonly resourceLanguageSettingsSchema: IJSONSchema; - private readonly overrideIdentifiers: string[] = []; + private readonly overrideIdentifiers = new Set(); private overridePropertyPattern: string; private readonly _onDidSchemaChange = new Emitter(); @@ -290,7 +291,10 @@ class ConfigurationRegistry implements IConfigurationRegistry { } public registerOverrideIdentifiers(overrideIdentifiers: string[]): void { - this.overrideIdentifiers.push(...overrideIdentifiers); + for (const overrideIdentifier of overrideIdentifiers) { + this.overrideIdentifiers.add(overrideIdentifier); + } + this.updateOverridePropertyPatternKey(); } @@ -420,7 +424,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { } private computeOverridePropertyPattern(): string { - return this.overrideIdentifiers.length ? OVERRIDE_PATTERN_WITH_SUBSTITUTION.replace('${0}', this.overrideIdentifiers.map(identifier => strings.createRegExp(identifier, false).source).join('|')) : OVERRIDE_PROPERTY; + return this.overrideIdentifiers.size > 0 ? OVERRIDE_PATTERN_WITH_SUBSTITUTION.replace('${0}', values(this.overrideIdentifiers).map(identifier => strings.createRegExp(identifier, false).source).join('|')) : OVERRIDE_PROPERTY; } } From e69cc0760c1090cb826c6e59542c551b3e2432d6 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 13 Jan 2020 12:26:42 +0100 Subject: [PATCH 198/315] update telemetry message with index of changed field #87187 --- .../api/common/extHostLanguageFeatures.ts | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index af9a04c90f3c1..adfbebe622ab8 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -826,23 +826,27 @@ class SuggestAdapter { type BlameExtension = { extensionId: string; - kind: string + kind: string; + index: string; }; type BlameExtensionMeta = { extensionId: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' }; kind: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' }; + index: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' }; }; - if (!this._didWarnMust && _mustNotChange !== SuggestAdapter._mustNotChangeHash(resolvedItem)) { + let _mustNotChangeIndex = !this._didWarnMust && SuggestAdapter._mustNotChangeDiff(_mustNotChange, resolvedItem); + if (typeof _mustNotChangeIndex === 'string') { this._logService.warn(`[${this._extensionId.value}] INVALID result from 'resolveCompletionItem', extension MUST NOT change any of: label, sortText, filterText, insertText, or textEdit`); - this._telemetry.$publicLog2('resolveCompletionItem/invalid', { extensionId: this._extensionId.value, kind: 'must' }); + this._telemetry.$publicLog2('resolveCompletionItem/invalid', { extensionId: this._extensionId.value, kind: 'must', index: _mustNotChangeIndex }); this._didWarnMust = true; } - if (!this._didWarnShould && _mayNotChange !== SuggestAdapter._mayNotChangeHash(resolvedItem)) { + let _mayNotChangeIndex = !this._didWarnShould && SuggestAdapter._mayNotChangeDiff(_mayNotChange, resolvedItem); + if (typeof _mayNotChangeIndex === 'string') { this._logService.info(`[${this._extensionId.value}] UNSAVE result from 'resolveCompletionItem', extension SHOULD NOT change any of: additionalTextEdits, or command`); - this._telemetry.$publicLog2('resolveCompletionItem/invalid', { extensionId: this._extensionId.value, kind: 'should' }); + this._telemetry.$publicLog2('resolveCompletionItem/invalid', { extensionId: this._extensionId.value, kind: 'should', index: _mayNotChangeIndex }); this._didWarnShould = true; } @@ -941,14 +945,43 @@ class SuggestAdapter { } private static _mustNotChangeHash(item: vscode.CompletionItem) { - const args = [item.label, item.sortText, item.filterText, item.insertText, item.range, item.range2]; - const res = JSON.stringify(args); + const res = JSON.stringify([item.label, item.sortText, item.filterText, item.insertText, item.range, item.range2]); return res; } + private static _mustNotChangeDiff(hash: string, item: vscode.CompletionItem): string | void { + const thisArr = [item.label, item.sortText, item.filterText, item.insertText, item.range, item.range2]; + const thisHash = JSON.stringify(thisArr); + if (hash === thisHash) { + return; + } + const arr = JSON.parse(hash); + for (let i = 0; i < 6; i++) { + if (JSON.stringify(arr[i] !== JSON.stringify(thisArr[i]))) { + return i.toString(); + } + } + return 'unknown'; + } + private static _mayNotChangeHash(item: vscode.CompletionItem) { return JSON.stringify([item.additionalTextEdits, item.command]); } + + private static _mayNotChangeDiff(hash: string, item: vscode.CompletionItem): string | void { + const thisArr = [item.additionalTextEdits, item.command]; + const thisHash = JSON.stringify(thisArr); + if (hash === thisHash) { + return; + } + const arr = JSON.parse(hash); + for (let i = 0; i < 6; i++) { + if (JSON.stringify(arr[i] !== JSON.stringify(thisArr[i]))) { + return i.toString(); + } + } + return 'unknown'; + } } class SignatureHelpAdapter { From 0fe69bd5298193a3bf0687a2347f81c7fcee1755 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 13 Jan 2020 12:42:45 +0100 Subject: [PATCH 199/315] TS semantic highlighting: check api version --- .../src/features/semanticTokens.ts | 8 ++++++++ extensions/typescript-language-features/src/utils/api.ts | 1 + 2 files changed, 9 insertions(+) diff --git a/extensions/typescript-language-features/src/features/semanticTokens.ts b/extensions/typescript-language-features/src/features/semanticTokens.ts index fefb8e2271f5a..f9f489321a286 100644 --- a/extensions/typescript-language-features/src/features/semanticTokens.ts +++ b/extensions/typescript-language-features/src/features/semanticTokens.ts @@ -6,8 +6,16 @@ import * as vscode from 'vscode'; import { ITypeScriptServiceClient, ExecConfig, ServerResponse } from '../typescriptService'; import * as Proto from '../protocol'; +import { VersionDependentRegistration } from '../utils/dependentRegistration'; +import API from '../utils/api'; + +const minTypeScriptVersion = API.v370; export function register(selector: vscode.DocumentSelector, client: ITypeScriptServiceClient) { + return new VersionDependentRegistration(client, minTypeScriptVersion, () => { + const provider = new SemanticTokensProvider(client); + return vscode.languages.registerSemanticTokensProvider(selector, provider, provider.getLegend()); + }); const provider = new SemanticTokensProvider(client); return vscode.languages.registerSemanticTokensProvider(selector, provider, provider.getLegend()); } diff --git a/extensions/typescript-language-features/src/utils/api.ts b/extensions/typescript-language-features/src/utils/api.ts index 0fa41cb01a0b8..8f181f14b6586 100644 --- a/extensions/typescript-language-features/src/utils/api.ts +++ b/extensions/typescript-language-features/src/utils/api.ts @@ -31,6 +31,7 @@ export default class API { public static readonly v340 = API.fromSimpleString('3.4.0'); public static readonly v345 = API.fromSimpleString('3.4.5'); public static readonly v350 = API.fromSimpleString('3.5.0'); + public static readonly v370 = API.fromSimpleString('3.7.0'); public static readonly v380 = API.fromSimpleString('3.8.0'); public static fromVersionString(versionString: string): API { From 4c9c6780c7a04c2992669cdb06f16042a4a4a8f4 Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Mon, 13 Jan 2020 12:48:37 +0100 Subject: [PATCH 200/315] Fix #88541 --- src/vs/editor/contrib/parameterHints/parameterHints.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/parameterHints/parameterHints.css b/src/vs/editor/contrib/parameterHints/parameterHints.css index b8745f45d7238..89bf6462e7fa3 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHints.css +++ b/src/vs/editor/contrib/parameterHints/parameterHints.css @@ -54,7 +54,7 @@ white-space: initial; } -.monaco-editor .parameter-hints-widget .docs .markdown-docs p code { +.monaco-editor .parameter-hints-widget .docs .markdown-docs code { font-family: var(--monaco-monospace-font); } From 224f983016eb313799f7c3b23e223dbbb596b329 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 13 Jan 2020 12:50:13 +0100 Subject: [PATCH 201/315] text files - drop debounced bulk events --- .../experiments/common/experimentService.ts | 12 ++-- .../browser/editors/fileEditorTracker.ts | 43 ++++++++----- .../browser/languageSurveys.contribution.ts | 16 +++-- .../common/textFileEditorModelManager.ts | 44 +------------ .../services/textfile/common/textfiles.ts | 4 -- .../test/textFileEditorModelManager.test.ts | 63 +++---------------- 6 files changed, 54 insertions(+), 128 deletions(-) diff --git a/src/vs/workbench/contrib/experiments/common/experimentService.ts b/src/vs/workbench/contrib/experiments/common/experimentService.ts index f49e4ac868b21..7f40caaa3cafc 100644 --- a/src/vs/workbench/contrib/experiments/common/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/common/experimentService.ts @@ -14,12 +14,13 @@ import { language } from 'vs/base/common/platform'; import { Disposable } from 'vs/base/common/lifecycle'; import { match } from 'vs/base/common/glob'; import { IRequestService, asJson } from 'vs/platform/request/common/request'; -import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, StateChange, TextFileModelChangeEvent } from 'vs/workbench/services/textfile/common/textfiles'; import { CancellationToken } from 'vs/base/common/cancellation'; import { distinct } from 'vs/base/common/arrays'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { IProductService } from 'vs/platform/product/common/productService'; import { IWorkspaceTagsService } from 'vs/workbench/contrib/tags/common/workspaceTags'; +import { RunOnceWorker } from 'vs/base/common/async'; export const enum ExperimentState { Evaluating, @@ -402,11 +403,13 @@ export class ExperimentService extends Disposable implements IExperimentService return ExperimentState.Run; } - const onSaveHandler = this.textFileService.models.onModelsSaved(e => { + // Process model-save event every 250ms to reduce load + const onModelsSavedWorker = this._register(new RunOnceWorker(e => { const date = new Date().toDateString(); const latestExperimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {}); if (latestExperimentState.state !== ExperimentState.Evaluating) { onSaveHandler.dispose(); + onModelsSavedWorker.dispose(); return; } e.forEach(async event => { @@ -444,8 +447,9 @@ export class ExperimentService extends Disposable implements IExperimentService this.fireRunExperiment(processedExperiment); } } - }); - this._register(onSaveHandler); + }, 250)); + + const onSaveHandler = this._register(this.textFileService.models.onModelSaved(e => onModelsSavedWorker.work(e))); return ExperimentState.Evaluating; }); } diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index bebcb9fcf9976..f5f9d80fa3be4 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -7,7 +7,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { URI } from 'vs/base/common/uri'; import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration, SideBySideEditor as SideBySideEditorChoice } from 'vs/workbench/common/editor'; -import { ITextFileService, TextFileModelChangeEvent, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; @@ -26,6 +26,7 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { isEqualOrParent, joinPath } from 'vs/base/common/resources'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import { Schemas } from 'vs/base/common/network'; export class FileEditorTracker extends Disposable implements IWorkbenchContribution { @@ -60,9 +61,9 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut this._register(this.fileService.onFileChanges(e => this.onFileChanges(e))); // Ensure dirty text file and untitled models are always opened as editors - this._register(this.textFileService.models.onModelsDirty(e => this.ensureDirtyTextFilesAreOpened(e))); - this._register(this.textFileService.models.onModelsSaveError(e => this.ensureDirtyTextFilesAreOpened(e))); - this._register(this.untitledTextEditorService.onDidChangeDirty(e => this.ensureDirtyUntitledTextFilesAreOpenedWorker.work(e))); + this._register(this.textFileService.models.onModelDirty(e => this.ensureDirtyFilesAreOpenedWorker.work(e.resource))); + this._register(this.textFileService.models.onModelSaveError(e => this.ensureDirtyFilesAreOpenedWorker.work(e.resource))); + this._register(this.untitledTextEditorService.onDidChangeDirty(e => this.ensureDirtyFilesAreOpenedWorker.work(e))); // Out of workspace file watchers this._register(this.editorService.onDidVisibleEditorsChange(() => this.onDidVisibleEditorsChange())); @@ -277,22 +278,30 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut //#region Text File: Ensure every dirty text and untitled file is opened in an editor - private readonly ensureDirtyUntitledTextFilesAreOpenedWorker = this._register(new RunOnceWorker(units => this.ensureDirtyUntitledTextFilesAreOpened(units), 250)); + private readonly ensureDirtyFilesAreOpenedWorker = this._register(new RunOnceWorker(units => this.ensureDirtyFilesAreOpened(units), 250)); - private ensureDirtyTextFilesAreOpened(events: ReadonlyArray): void { - this.doEnsureDirtyFilesAreOpened(distinct(events.filter(({ resource }) => { - const model = this.textFileService.models.get(resource); + private ensureDirtyFilesAreOpened(resources: URI[]): void { + this.doEnsureDirtyFilesAreOpened(distinct(resources.filter(resource => { + if (resource.scheme === Schemas.untitled) { + if (!this.untitledTextEditorService.isDirty(resource)) { + return false; // untitled must be dirty + } + } else { + const model = this.textFileService.models.get(resource); + if (!model) { + return false; // only for text file models + } - return model?.hasState(ModelState.DIRTY) && // model must be dirty - !model.hasState(ModelState.PENDING_SAVE) && // model should not be saving currently - !this.editorService.isOpen({ resource }); // model is not currently opened as editor - }).map(event => event.resource), resource => resource.toString())); - } + if (!model?.hasState(ModelState.DIRTY) || model.hasState(ModelState.PENDING_SAVE)) { + return false; // model must be dirty and not being saved + } + } - private ensureDirtyUntitledTextFilesAreOpened(resources: URI[]): void { - this.doEnsureDirtyFilesAreOpened(distinct(resources.filter(resource => { - return this.untitledTextEditorService.isDirty(resource) && // untitled must be dirty - !this.editorService.isOpen({ resource }); // untitled is not currently opened as editor + if (this.editorService.isOpen({ resource })) { + return false; // model must not be opened already + } + + return true; }), resource => resource.toString())); } diff --git a/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts b/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts index e25d5601d7c3b..a4242c6958d83 100644 --- a/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts +++ b/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts @@ -13,12 +13,14 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { ISurveyData, IProductService } from 'vs/platform/product/common/productService'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; -import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, StateChange, TextFileModelChangeEvent } from 'vs/workbench/services/textfile/common/textfiles'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; import { platform } from 'vs/base/common/process'; +import { RunOnceWorker } from 'vs/base/common/async'; +import { Disposable } from 'vs/base/common/lifecycle'; -class LanguageSurvey { +class LanguageSurvey extends Disposable { constructor( data: ISurveyData, @@ -30,6 +32,8 @@ class LanguageSurvey { openerService: IOpenerService, productService: IProductService ) { + super(); + const SESSION_COUNT_KEY = `${data.surveyId}.sessionCount`; const LAST_SESSION_DATE_KEY = `${data.surveyId}.lastSessionDate`; const SKIP_VERSION_KEY = `${data.surveyId}.skipVersion`; @@ -45,7 +49,9 @@ class LanguageSurvey { const date = new Date().toDateString(); if (storageService.getNumber(EDITED_LANGUAGE_COUNT_KEY, StorageScope.GLOBAL, 0) < data.editCount) { - textFileService.models.onModelsSaved(e => { + + // Process model-save event every 250ms to reduce load + const onModelsSavedWorker = this._register(new RunOnceWorker(e => { e.forEach(event => { if (event.kind === StateChange.SAVED) { const model = modelService.getModel(event.resource); @@ -56,7 +62,9 @@ class LanguageSurvey { } } }); - }); + }, 250)); + + this._register(textFileService.models.onModelSaved(e => onModelsSavedWorker.work(e))); } const lastSessionDate = storageService.get(LAST_SESSION_DATE_KEY, StorageScope.GLOBAL, new Date(0).toDateString()); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 523f371578a0d..0db12b9d2fd02 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { dispose, IDisposable, Disposable } from 'vs/base/common/lifecycle'; @@ -39,33 +39,6 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE private readonly _onModelOrphanedChanged = this._register(new Emitter()); readonly onModelOrphanedChanged = this._onModelOrphanedChanged.event; - private _onModelsDirty: Event> | undefined; - get onModelsDirty(): Event> { - if (!this._onModelsDirty) { - this._onModelsDirty = this.debounce(this.onModelDirty); - } - - return this._onModelsDirty; - } - - private _onModelsSaveError: Event> | undefined; - get onModelsSaveError(): Event> { - if (!this._onModelsSaveError) { - this._onModelsSaveError = this.debounce(this.onModelSaveError); - } - - return this._onModelsSaveError; - } - - private _onModelsSaved: Event> | undefined; - get onModelsSaved(): Event> { - if (!this._onModelsSaved) { - this._onModelsSaved = this.debounce(this.onModelSaved); - } - - return this._onModelsSaved; - } - private readonly mapResourceToDisposeListener = new ResourceMap(); private readonly mapResourceToStateChangeListener = new ResourceMap(); private readonly mapResourceToModelContentChangeListener = new ResourceMap(); @@ -116,21 +89,6 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE } } - private debounce(event: Event): Event { - return Event.debounce(event, (prev, cur) => { - if (!prev) { - prev = [cur]; - } else { - prev.push(cur); - } - return prev; - }, this.debounceDelay()); - } - - protected debounceDelay(): number { - return 250; - } - get(resource: URI): ITextFileEditorModel | undefined { return this.mapResourceToModel.get(resource); } diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index c5efc94245d24..92056c53d84ef 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -387,10 +387,6 @@ export interface ITextFileEditorModelManager { readonly onModelSaved: Event; readonly onModelReverted: Event; - readonly onModelsDirty: Event; - readonly onModelsSaveError: Event; - readonly onModelsSaved: Event; - get(resource: URI): ITextFileEditorModel | undefined; getAll(resource?: URI): ITextFileEditorModel[]; diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts index 8c9e2e98670bd..da4fbc06caf8d 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts @@ -11,17 +11,9 @@ import { workbenchInstantiationService, TestFileService } from 'vs/workbench/tes import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { IFileService, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { timeout } from 'vs/base/common/async'; import { toResource } from 'vs/base/test/common/utils'; import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; -export class TestTextFileEditorModelManager extends TextFileEditorModelManager { - - protected debounceDelay(): number { - return 10; - } -} - class ServiceAccessor { constructor( @IFileService public fileService: TestFileService, @@ -41,7 +33,7 @@ suite('Files - TextFileEditorModelManager', () => { }); test('add, remove, clear, get, getAll', function () { - const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); + const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager); const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8', undefined); const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8', undefined); @@ -96,7 +88,7 @@ suite('Files - TextFileEditorModelManager', () => { }); test('loadOrCreate', async () => { - const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); + const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager); const resource = URI.file('/test.html'); const encoding = 'utf8'; @@ -116,7 +108,7 @@ suite('Files - TextFileEditorModelManager', () => { }); test('removed from cache when model disposed', function () { - const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); + const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager); const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8', undefined); const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8', undefined); @@ -136,7 +128,7 @@ suite('Files - TextFileEditorModelManager', () => { }); test('events', async function () { - const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); + const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager); const resource1 = toResource.call(this, '/path/index.txt'); const resource2 = toResource.call(this, '/path/other.txt'); @@ -208,49 +200,8 @@ suite('Files - TextFileEditorModelManager', () => { assert.ok(!accessor.modelService.getModel(resource2)); }); - test('events debounced', async function () { - const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); - - const resource1 = toResource.call(this, '/path/index.txt'); - const resource2 = toResource.call(this, '/path/other.txt'); - - let dirtyCounter = 0; - let savedCounter = 0; - - manager.onModelsDirty(e => { - dirtyCounter += e.length; - assert.equal(e[0].resource.toString(), resource1.toString()); - }); - - manager.onModelsSaved(e => { - savedCounter += e.length; - assert.equal(e[0].resource.toString(), resource1.toString()); - }); - - const model1 = await manager.loadOrCreate(resource1, { encoding: 'utf8' }); - const model2 = await manager.loadOrCreate(resource2, { encoding: 'utf8' }); - model1.textEditorModel!.setValue('changed'); - model1.updatePreferredEncoding('utf16'); - - await model1.revert(); - model1.textEditorModel!.setValue('changed again'); - - await model1.save(); - model1.dispose(); - model2.dispose(); - - await model1.revert(); - await timeout(20); - assert.equal(dirtyCounter, 2); - assert.equal(savedCounter, 1); - model1.dispose(); - model2.dispose(); - assert.ok(!accessor.modelService.getModel(resource1)); - assert.ok(!accessor.modelService.getModel(resource2)); - }); - test('disposing model takes it out of the manager', async function () { - const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); + const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager); const resource = toResource.call(this, '/path/index_something.txt'); @@ -262,7 +213,7 @@ suite('Files - TextFileEditorModelManager', () => { }); test('dispose prevents dirty model from getting disposed', async function () { - const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); + const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager); const resource = toResource.call(this, '/path/index_something.txt'); @@ -282,7 +233,7 @@ suite('Files - TextFileEditorModelManager', () => { id: mode, }); - const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); + const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager); const resource = toResource.call(this, '/path/index_something.txt'); From 9918d87cb69befa421cd2019ac51f74cc644c233 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 13 Jan 2020 13:01:11 +0100 Subject: [PATCH 202/315] Fix #88402 --- .../api/browser/viewsExtensionPoint.ts | 5 +-- .../browser/parts/views/viewPaneContainer.ts | 2 +- .../browser/workbench.contribution.ts | 4 +-- src/vs/workbench/common/views.ts | 5 +-- .../bulkEdit/browser/bulkEdit.contribution.ts | 11 ++++--- .../debug/browser/debug.contribution.ts | 15 ++++----- .../browser/extensions.contribution.ts | 2 +- .../extensions/browser/extensionsViewlet.ts | 31 ++++++++++--------- .../contrib/files/browser/explorerViewlet.ts | 9 +++--- .../markers/browser/markers.contribution.ts | 5 +-- .../outline/browser/outline.contribution.ts | 3 +- .../contrib/remote/browser/remote.ts | 7 +++-- .../contrib/remote/browser/tunnelView.ts | 5 +-- .../workbench/contrib/scm/browser/mainPane.ts | 5 +-- .../contrib/scm/browser/repositoryPane.ts | 5 +-- .../contrib/scm/browser/scm.contribution.ts | 3 +- .../search/browser/search.contribution.ts | 4 +-- .../test/browser/parts/views/views.test.ts | 3 +- 18 files changed, 69 insertions(+), 55 deletions(-) diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index e721bb2426701..65bb4afb1dd9e 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -36,6 +36,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; export interface IUserFriendlyViewsContainerDescriptor { id: string; @@ -332,7 +333,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { viewContainer = this.viewContainersRegistry.registerViewContainer({ id, name: title, extensionId, - ctorDescriptor: { ctor: CustomViewPaneContainer }, + ctorDescriptor: new SyncDescriptor(CustomViewPaneContainer), hideIfEmpty: true, icon, }, ViewContainerLocation.Sidebar); @@ -416,7 +417,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { const viewDescriptor = { id: item.id, name: item.name, - ctorDescriptor: { ctor: CustomTreeViewPane }, + ctorDescriptor: new SyncDescriptor(CustomTreeViewPane), when: ContextKeyExpr.deserialize(item.when), canToggleVisibility: true, collapsed: this.showCollapsed(container), diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index fc7f3528c07d3..95095030c4848 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -492,7 +492,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewPane { - return (this.instantiationService as any).createInstance(viewDescriptor.ctorDescriptor.ctor, ...(viewDescriptor.ctorDescriptor.arguments || []), options) as ViewPane; + return (this.instantiationService as any).createInstance(viewDescriptor.ctorDescriptor.ctor, ...(viewDescriptor.ctorDescriptor.staticArguments || []), options) as ViewPane; } getView(id: string): ViewPane | undefined { diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index c14125bea1736..f2a68f351d3d0 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -402,7 +402,7 @@ import { URI } from 'vs/base/common/uri'; @IExtensionService extensionService: IExtensionService, @IWorkspaceContextService contextService: IWorkspaceContextService ) { - super(viewContainer.id, (instantiationService as any).createInstance(viewContainer.ctorDescriptor!.ctor, ...(viewContainer.ctorDescriptor!.arguments || [])), telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + super(viewContainer.id, (instantiationService as any).createInstance(viewContainer.ctorDescriptor!.ctor, ...(viewContainer.ctorDescriptor!.staticArguments || [])), telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); } } Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( @@ -429,7 +429,7 @@ import { URI } from 'vs/base/common/uri'; @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService ) { - super(viewContainer.id, (instantiationService as any).createInstance(viewContainer.ctorDescriptor!.ctor, ...(viewContainer.ctorDescriptor!.arguments || [])), telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, layoutService, configurationService); + super(viewContainer.id, (instantiationService as any).createInstance(viewContainer.ctorDescriptor!.ctor, ...(viewContainer.ctorDescriptor!.staticArguments || [])), telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, layoutService, configurationService); } } const viewletDescriptor = ViewletDescriptor.create( diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 15ee4827de841..174df04dff654 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -19,6 +19,7 @@ import { IAction } from 'vs/base/common/actions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { flatten } from 'vs/base/common/arrays'; import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test'; export const FocusedViewContext = new RawContextKey('focusedView', ''); @@ -39,7 +40,7 @@ export interface IViewContainerDescriptor { readonly name: string; - readonly ctorDescriptor: { ctor: new (...args: any[]) => IViewPaneContainer, arguments?: any[] }; + readonly ctorDescriptor: SyncDescriptor; readonly icon?: string | URI; @@ -166,7 +167,7 @@ export interface IViewDescriptor { readonly name: string; - readonly ctorDescriptor: { ctor: any, arguments?: any[] }; + readonly ctorDescriptor: SyncDescriptor; readonly when?: ContextKeyExpr; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts index 19f870dec791b..2a705730bae14 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts @@ -21,6 +21,7 @@ import { BulkEditPreviewProvider } from 'vs/workbench/contrib/bulkEdit/browser/b import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; function getBulkEditPane(panelService: IPanelService): BulkEditPane | undefined { let view: ViewPane | undefined; @@ -128,16 +129,16 @@ const container = Registry.as(ViewContainerExtensions.V id: BulkEditPane.ID, name: localize('panel', "Refactor Preview"), hideIfEmpty: true, - ctorDescriptor: { - ctor: ViewPaneContainer, - arguments: [BulkEditPane.ID, BulkEditPane.ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }] - } + ctorDescriptor: new SyncDescriptor( + ViewPaneContainer, + [BulkEditPane.ID, BulkEditPane.ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }] + ) }, ViewContainerLocation.Panel); Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ id: BulkEditPane.ID, name: localize('panel', "Refactor Preview"), when: BulkEditPreviewContribution.ctxEnabled, - ctorDescriptor: { ctor: BulkEditPane }, + ctorDescriptor: new SyncDescriptor(BulkEditPane), }], container); diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 7626a1780ce3d..3874102dd89b5 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -52,6 +52,7 @@ import { DebugViewPaneContainer } from 'vs/workbench/contrib/debug/browser/debug import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { CallStackEditorContribution } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution'; import { BreakpointEditorContribution } from 'vs/workbench/contrib/debug/browser/breakpointEditorContribution'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; class OpenDebugViewletAction extends ShowViewletAction { public static readonly ID = VIEWLET_ID; @@ -85,7 +86,7 @@ class OpenDebugPanelAction extends TogglePanelAction { const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: nls.localize('runAndDebug', "Run and Debug"), - ctorDescriptor: { ctor: DebugViewPaneContainer }, + ctorDescriptor: new SyncDescriptor(DebugViewPaneContainer), icon: 'codicon-debug-alt', order: 3 }, ViewContainerLocation.Sidebar); @@ -109,12 +110,12 @@ Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor // Register default debug views const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); -viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), ctorDescriptor: { ctor: VariablesView }, order: 10, weight: 40, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); -viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctorDescriptor: { ctor: WatchExpressionsView }, order: 20, weight: 10, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); -viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctorDescriptor: { ctor: CallStackView }, order: 30, weight: 30, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); -viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctorDescriptor: { ctor: BreakpointsView }, order: 40, weight: 20, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); -viewsRegistry.registerViews([{ id: StartView.ID, name: StartView.LABEL, ctorDescriptor: { ctor: StartView }, order: 10, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], viewContainer); -viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), ctorDescriptor: { ctor: LoadedScriptsView }, order: 35, weight: 5, canToggleVisibility: true, collapsed: true, when: ContextKeyExpr.and(CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer); +viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), ctorDescriptor: new SyncDescriptor(VariablesView), order: 10, weight: 40, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); +viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctorDescriptor: new SyncDescriptor(WatchExpressionsView), order: 20, weight: 10, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); +viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctorDescriptor: new SyncDescriptor(CallStackView), order: 30, weight: 30, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); +viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctorDescriptor: new SyncDescriptor(BreakpointsView), order: 40, weight: 20, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); +viewsRegistry.registerViews([{ id: StartView.ID, name: StartView.LABEL, ctorDescriptor: new SyncDescriptor(StartView), order: 10, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], viewContainer); +viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), ctorDescriptor: new SyncDescriptor(LoadedScriptsView), order: 35, weight: 5, canToggleVisibility: true, collapsed: true, when: ContextKeyExpr.and(CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer); registerCommands(); diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 80f565cbd2d50..6550640590af2 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -82,7 +82,7 @@ Registry.as(ViewContainerExtensions.ViewContainersRegis { id: VIEWLET_ID, name: localize('extensions', "Extensions"), - ctorDescriptor: { ctor: ExtensionsViewPaneContainer }, + ctorDescriptor: new SyncDescriptor(ExtensionsViewPaneContainer), icon: 'codicon-extensions', order: 4 }, ViewContainerLocation.Sidebar); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index eb05d9ef19aa4..9accf3727af69 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -57,6 +57,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { RemoteNameContext } from 'vs/workbench/browser/contextkeys'; import { ILabelService } from 'vs/platform/label/common/label'; import { MementoObject } from 'vs/workbench/common/memento'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; const NonEmptyWorkspaceContext = new RawContextKey('nonEmptyWorkspace', false); const DefaultViewsContext = new RawContextKey('defaultExtensionViews', true); @@ -128,7 +129,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctorDescriptor: { ctor: ExtensionsListView }, + ctorDescriptor: new SyncDescriptor(ExtensionsListView), when: ContextKeyExpr.and(ContextKeyExpr.has('searchMarketplaceExtensions')), weight: 100 }; @@ -141,7 +142,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctorDescriptor: { ctor: EnabledExtensionsView }, + ctorDescriptor: new SyncDescriptor(EnabledExtensionsView), when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteNameContext.isEqualTo('')), weight: 40, canToggleVisibility: true, @@ -156,7 +157,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctorDescriptor: { ctor: DisabledExtensionsView }, + ctorDescriptor: new SyncDescriptor(DisabledExtensionsView), when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteNameContext.isEqualTo('')), weight: 10, canToggleVisibility: true, @@ -172,7 +173,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctorDescriptor: { ctor: ExtensionsListView }, + ctorDescriptor: new SyncDescriptor(ExtensionsListView), when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.not('hasInstalledExtensions')), weight: 60, order: 1 @@ -193,19 +194,19 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return [{ id: `extensions.${server.authority}.installed`, get name() { return getInstalledViewName(); }, - ctorDescriptor: { ctor: ServerExtensionsView, arguments: [server, EventOf.map(onDidChangeServerLabel, () => getInstalledViewName())] }, + ctorDescriptor: new SyncDescriptor(ServerExtensionsView, [server, EventOf.map(onDidChangeServerLabel, () => getInstalledViewName())]), when: ContextKeyExpr.and(ContextKeyExpr.has('searchInstalledExtensions')), weight: 100 }, { id: `extensions.${server.authority}.outdated`, get name() { return getOutdatedViewName(); }, - ctorDescriptor: { ctor: ServerExtensionsView, arguments: [server, EventOf.map(onDidChangeServerLabel, () => getOutdatedViewName())] }, + ctorDescriptor: new SyncDescriptor(ServerExtensionsView, [server, EventOf.map(onDidChangeServerLabel, () => getOutdatedViewName())]), when: ContextKeyExpr.and(ContextKeyExpr.has('searchOutdatedExtensions')), weight: 100 }, { id: `extensions.${server.authority}.default`, get name() { return getInstalledViewName(); }, - ctorDescriptor: { ctor: ServerExtensionsView, arguments: [server, EventOf.map(onDidChangeServerLabel, () => getInstalledViewName())] }, + ctorDescriptor: new SyncDescriptor(ServerExtensionsView, [server, EventOf.map(onDidChangeServerLabel, () => getInstalledViewName())]), when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteNameContext.notEqualsTo('')), weight: 40, order: 1 @@ -220,7 +221,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctorDescriptor: { ctor: DefaultRecommendedExtensionsView }, + ctorDescriptor: new SyncDescriptor(DefaultRecommendedExtensionsView), when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('defaultRecommendedExtensions')), weight: 40, order: 2, @@ -235,7 +236,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctorDescriptor: { ctor: RecommendedExtensionsView }, + ctorDescriptor: new SyncDescriptor(RecommendedExtensionsView), when: ContextKeyExpr.has('recommendedExtensions'), weight: 50, order: 2 @@ -249,7 +250,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctorDescriptor: { ctor: WorkspaceRecommendedExtensionsView }, + ctorDescriptor: new SyncDescriptor(WorkspaceRecommendedExtensionsView), when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions'), ContextKeyExpr.has('nonEmptyWorkspace')), weight: 50, order: 1 @@ -261,7 +262,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctorDescriptor: { ctor: EnabledExtensionsView }, + ctorDescriptor: new SyncDescriptor(EnabledExtensionsView), when: ContextKeyExpr.and(ContextKeyExpr.has('searchEnabledExtensions')), weight: 40, order: 1 @@ -273,7 +274,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctorDescriptor: { ctor: DisabledExtensionsView }, + ctorDescriptor: new SyncDescriptor(DisabledExtensionsView), when: ContextKeyExpr.and(ContextKeyExpr.has('searchDisabledExtensions')), weight: 10, order: 3, @@ -286,7 +287,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctorDescriptor: { ctor: BuiltInExtensionsView }, + ctorDescriptor: new SyncDescriptor(BuiltInExtensionsView), when: ContextKeyExpr.has('searchBuiltInExtensions'), weight: 100 }; @@ -297,7 +298,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctorDescriptor: { ctor: BuiltInThemesExtensionsView }, + ctorDescriptor: new SyncDescriptor(BuiltInThemesExtensionsView), when: ContextKeyExpr.has('searchBuiltInExtensions'), weight: 100 }; @@ -308,7 +309,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctorDescriptor: { ctor: BuiltInBasicsExtensionsView }, + ctorDescriptor: new SyncDescriptor(BuiltInBasicsExtensionsView), when: ContextKeyExpr.has('searchBuiltInExtensions'), weight: 100 }; diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 94d591094b697..82d603d63fbbb 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -33,6 +33,7 @@ import { ViewPane, ViewPaneContainer } from 'vs/workbench/browser/parts/views/vi import { KeyChord, KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution { @@ -103,7 +104,7 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor return { id: OpenEditorsView.ID, name: OpenEditorsView.NAME, - ctorDescriptor: { ctor: OpenEditorsView }, + ctorDescriptor: new SyncDescriptor(OpenEditorsView), order: 0, when: OpenEditorsVisibleContext, canToggleVisibility: true, @@ -118,7 +119,7 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor return { id: EmptyView.ID, name: EmptyView.NAME, - ctorDescriptor: { ctor: EmptyView }, + ctorDescriptor: new SyncDescriptor(EmptyView), order: 1, canToggleVisibility: true, }; @@ -128,7 +129,7 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor return { id: ExplorerView.ID, name: localize('folders', "Folders"), - ctorDescriptor: { ctor: ExplorerView }, + ctorDescriptor: new SyncDescriptor(ExplorerView), order: 1, canToggleVisibility: false }; @@ -247,7 +248,7 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { export const VIEW_CONTAINER: ViewContainer = Registry.as(Extensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: localize('explore', "Explorer"), - ctorDescriptor: { ctor: ExplorerViewPaneContainer }, + ctorDescriptor: new SyncDescriptor(ExplorerViewPaneContainer), icon: 'codicon-files', order: 0 }, ViewContainerLocation.Sidebar); diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index 1baf6e1c4f9c6..bc3a48211150c 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -32,6 +32,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry } from 'vs/workbench/common/views'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; registerSingleton(IMarkersWorkbenchService, MarkersWorkbenchService, false); @@ -110,7 +111,7 @@ class ToggleMarkersPanelAction extends TogglePanelAction { const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: Constants.MARKERS_PANEL_ID, name: Messages.MARKERS_PANEL_TITLE_PROBLEMS, - ctorDescriptor: { ctor: ViewPaneContainer, arguments: [Constants.MARKERS_PANEL_ID, Constants.MARKERS_PANEL_STORAGE_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }] }, + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [Constants.MARKERS_PANEL_ID, Constants.MARKERS_PANEL_STORAGE_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), focusCommand: { id: ToggleMarkersPanelAction.ID, keybindings: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_M @@ -122,7 +123,7 @@ Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews id: Constants.MARKERS_VIEW_ID, name: Messages.MARKERS_PANEL_TITLE_PROBLEMS, canToggleVisibility: false, - ctorDescriptor: { ctor: MarkersView }, + ctorDescriptor: new SyncDescriptor(MarkersView), }], VIEW_CONTAINER); // workbench diff --git a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts index d3d712fe18a53..d95ee1360d0cd 100644 --- a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts +++ b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts @@ -10,13 +10,14 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { OutlineConfigKeys, OutlineViewId } from 'vs/editor/contrib/documentSymbols/outline'; import { VIEW_CONTAINER } from 'vs/workbench/contrib/files/browser/explorerViewlet'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; // import './outlineNavigation'; const _outlineDesc = { id: OutlineViewId, name: localize('name', "Outline"), - ctorDescriptor: { ctor: OutlinePane }, + ctorDescriptor: new SyncDescriptor(OutlinePane), canToggleVisibility: true, hideByDefault: false, collapsed: true, diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 3edc85814644e..27ce85e8cb3a1 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -54,6 +54,7 @@ import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list 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'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; export interface HelpInformation { extensionDescription: IExtensionDescription; @@ -406,13 +407,13 @@ class HelpPanel extends ViewPane { class HelpPanelDescriptor implements IViewDescriptor { readonly id = HelpPanel.ID; readonly name = HelpPanel.TITLE; - readonly ctorDescriptor: { ctor: any, arguments?: any[] }; + readonly ctorDescriptor: SyncDescriptor; readonly canToggleVisibility = true; readonly hideByDefault = false; readonly workspace = true; constructor(viewModel: IViewModel) { - this.ctorDescriptor = { ctor: HelpPanel, arguments: [viewModel] }; + this.ctorDescriptor = new SyncDescriptor(HelpPanel, [viewModel]); } } @@ -521,7 +522,7 @@ Registry.as(Extensions.ViewContainersRegistry).register { id: VIEWLET_ID, name: nls.localize('remote.explorer', "Remote Explorer"), - ctorDescriptor: { ctor: RemoteViewPaneContainer }, + ctorDescriptor: new SyncDescriptor(RemoteViewPaneContainer), hideIfEmpty: true, viewOrderDelegate: { getOrder: (group?: string) => { diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index ec5fedf10f9e1..c88132b8b2c42 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -38,6 +38,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { URI } from 'vs/base/common/uri'; import { RemoteTunnel } from 'vs/platform/remote/common/tunnel'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; export const forwardedPortsViewEnabled = new RawContextKey('forwardedPortsViewEnabled', false); @@ -574,7 +575,7 @@ export class TunnelPanel extends ViewPane { export class TunnelPanelDescriptor implements IViewDescriptor { readonly id = TunnelPanel.ID; readonly name = TunnelPanel.TITLE; - readonly ctorDescriptor: { ctor: any, arguments?: any[] }; + readonly ctorDescriptor: SyncDescriptor; readonly canToggleVisibility = true; readonly hideByDefault = false; readonly workspace = true; @@ -582,7 +583,7 @@ export class TunnelPanelDescriptor implements IViewDescriptor { readonly remoteAuthority?: string | string[]; constructor(viewModel: ITunnelViewModel, environmentService: IWorkbenchEnvironmentService) { - this.ctorDescriptor = { ctor: TunnelPanel, arguments: [viewModel] }; + this.ctorDescriptor = new SyncDescriptor(TunnelPanel, [viewModel]); this.remoteAuthority = environmentService.configuration.remoteAuthority ? environmentService.configuration.remoteAuthority.split('+')[0] : undefined; } } diff --git a/src/vs/workbench/contrib/scm/browser/mainPane.ts b/src/vs/workbench/contrib/scm/browser/mainPane.ts index d5ec661f87b84..f44e13e5e9ca7 100644 --- a/src/vs/workbench/contrib/scm/browser/mainPane.ts +++ b/src/vs/workbench/contrib/scm/browser/mainPane.ts @@ -31,6 +31,7 @@ import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewDescriptor } from 'vs/workbench/common/views'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; export interface ISpliceEvent { index: number; @@ -320,7 +321,7 @@ export class MainPaneDescriptor implements IViewDescriptor { readonly id = MainPane.ID; readonly name = MainPane.TITLE; - readonly ctorDescriptor: { ctor: any, arguments?: any[] }; + readonly ctorDescriptor: SyncDescriptor; readonly canToggleVisibility = true; readonly hideByDefault = false; readonly order = -1000; @@ -328,6 +329,6 @@ export class MainPaneDescriptor implements IViewDescriptor { readonly when = ContextKeyExpr.or(ContextKeyExpr.equals('config.scm.alwaysShowProviders', true), ContextKeyExpr.and(ContextKeyExpr.notEquals('scm.providerCount', 0), ContextKeyExpr.notEquals('scm.providerCount', 1))); constructor(viewModel: IViewModel) { - this.ctorDescriptor = { ctor: MainPane, arguments: [viewModel] }; + this.ctorDescriptor = new SyncDescriptor(MainPane, [viewModel]); } } diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts index ac0040072c67e..95626a88b2ca8 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -54,6 +54,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { Hasher } from 'vs/base/common/hash'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; type TreeElement = ISCMResourceGroup | IResourceNode | ISCMResource; @@ -957,7 +958,7 @@ export class RepositoryViewDescriptor implements IViewDescriptor { readonly id: string; readonly name: string; - readonly ctorDescriptor: { ctor: any, arguments?: any[] }; + readonly ctorDescriptor: SyncDescriptor; readonly canToggleVisibility = true; readonly order = -500; readonly workspace = true; @@ -970,6 +971,6 @@ export class RepositoryViewDescriptor implements IViewDescriptor { this.id = `scm:repository:${hasher.value}`; this.name = repository.provider.rootUri ? basename(repository.provider.rootUri) : repository.provider.label; - this.ctorDescriptor = { ctor: RepositoryPane, arguments: [repository] }; + this.ctorDescriptor = new SyncDescriptor(RepositoryPane, [repository]); } } diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 73e1959661d2e..fd371ac79b05d 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -25,6 +25,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { SCMService } from 'vs/workbench/contrib/scm/common/scmService'; import { IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views'; import { SCMViewPaneContainer } from 'vs/workbench/contrib/scm/browser/scmViewlet'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; class OpenSCMViewletAction extends ShowViewletAction { @@ -42,7 +43,7 @@ Registry.as(WorkbenchExtensions.Workbench) Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: localize('source control', "Source Control"), - ctorDescriptor: { ctor: SCMViewPaneContainer }, + ctorDescriptor: new SyncDescriptor(SCMViewPaneContainer), icon: 'codicon-source-control', order: 2 }, ViewContainerLocation.Sidebar); diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 3a3383efb8eca..3e698a8a74cfb 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -512,7 +512,7 @@ class ShowAllSymbolsAction extends Action { const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: nls.localize('name', "Search"), - ctorDescriptor: { ctor: SearchViewPaneContainer }, + ctorDescriptor: new SyncDescriptor(SearchViewPaneContainer), hideIfEmpty: true, icon: 'codicon-search', order: 1 @@ -550,7 +550,7 @@ class RegisterSearchViewContribution implements IWorkbenchContribution { } } else { Registry.as(PanelExtensions.Panels).deregisterPanel(PANEL_ID); - viewsRegistry.registerViews([{ id: VIEW_ID, name: nls.localize('search', "Search"), ctorDescriptor: { ctor: SearchView, arguments: [SearchViewPosition.SideBar] }, canToggleVisibility: false }], viewContainer); + viewsRegistry.registerViews([{ id: VIEW_ID, name: nls.localize('search', "Search"), ctorDescriptor: new SyncDescriptor(SearchView, [SearchViewPosition.SideBar]), canToggleVisibility: false }], viewContainer); if (open) { viewletService.openViewlet(VIEWLET_ID); } diff --git a/src/vs/workbench/test/browser/parts/views/views.test.ts b/src/vs/workbench/test/browser/parts/views/views.test.ts index 794ff35443bf3..a5244e4de17c9 100644 --- a/src/vs/workbench/test/browser/parts/views/views.test.ts +++ b/src/vs/workbench/test/browser/parts/views/views.test.ts @@ -14,8 +14,9 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; import sinon = require('sinon'); +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: { ctor: {} } }, ViewContainerLocation.Sidebar); +const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); class ViewDescriptorSequence { From 238617a847b378eeefbd95fc2e9cd585737837c3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 13 Jan 2020 13:02:41 +0100 Subject: [PATCH 203/315] debt - drop TextFileModelChangeEvent --- .../api/browser/mainThreadDocuments.ts | 24 +++++++------- .../browser/parts/editor/editorStatus.ts | 2 +- .../experiments/common/experimentService.ts | 13 ++++---- .../browser/editors/fileEditorTracker.ts | 6 ++-- .../editors/textFileSaveErrorHandler.ts | 4 +-- .../files/common/editors/fileEditorInput.ts | 20 +++++------ .../browser/languageSurveys.contribution.ts | 20 +++++------ .../browser/telemetry.contribution.ts | 18 +++++----- .../common/textFileEditorModelManager.ts | 33 +++++++++---------- .../services/textfile/common/textfiles.ts | 30 ++++------------- .../test/textFileEditorModelManager.test.ts | 20 +++++------ 11 files changed, 85 insertions(+), 105 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index c54c261302807..3568bd30704af 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -15,7 +15,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/browser/mainThreadDocumentsAndEditors'; import { ExtHostContext, ExtHostDocumentsShape, IExtHostContext, MainThreadDocumentsShape } from 'vs/workbench/api/common/extHost.protocol'; import { ITextEditorModel } from 'vs/workbench/common/editor'; -import { ITextFileService, TextFileModelChangeEvent } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { toLocalResource } from 'vs/base/common/resources'; @@ -104,19 +104,19 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { this._toDispose.add(this._modelReferenceCollection); this._toDispose.add(modelService.onModelModeChanged(this._onModelModeChanged, this)); - this._toDispose.add(textFileService.models.onModelSaved(e => { - if (this._shouldHandleFileEvent(e)) { - this._proxy.$acceptModelSaved(e.resource); + this._toDispose.add(textFileService.models.onModelSaved(m => { + if (this._shouldHandleFileEvent(m.resource)) { + this._proxy.$acceptModelSaved(m.resource); } })); - this._toDispose.add(textFileService.models.onModelReverted(e => { - if (this._shouldHandleFileEvent(e)) { - this._proxy.$acceptDirtyStateChanged(e.resource, false); + this._toDispose.add(textFileService.models.onModelReverted(m => { + if (this._shouldHandleFileEvent(m.resource)) { + this._proxy.$acceptDirtyStateChanged(m.resource, false); } })); - this._toDispose.add(textFileService.models.onModelDirty(e => { - if (this._shouldHandleFileEvent(e)) { - this._proxy.$acceptDirtyStateChanged(e.resource, true); + this._toDispose.add(textFileService.models.onModelDirty(m => { + if (this._shouldHandleFileEvent(m.resource)) { + this._proxy.$acceptDirtyStateChanged(m.resource, true); } })); @@ -131,8 +131,8 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { this._toDispose.dispose(); } - private _shouldHandleFileEvent(e: TextFileModelChangeEvent): boolean { - const model = this._modelService.getModel(e.resource); + private _shouldHandleFileEvent(resource: URI): boolean { + const model = this._modelService.getModel(resource); return !!model && shouldSynchronizeModel(model); } diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index a8909037e516d..d832143cc0427 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -314,7 +314,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { private registerListeners(): void { this._register(this.editorService.onDidActiveEditorChange(() => this.updateStatusBar())); this._register(this.untitledTextEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r))); - this._register(this.textFileService.models.onModelEncodingChanged(e => this.onResourceEncodingChange((e.resource)))); + this._register(this.textFileService.models.onModelEncodingChanged(m => this.onResourceEncodingChange((m.resource)))); this._register(TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange())); } diff --git a/src/vs/workbench/contrib/experiments/common/experimentService.ts b/src/vs/workbench/contrib/experiments/common/experimentService.ts index 7f40caaa3cafc..f0eccb33be31d 100644 --- a/src/vs/workbench/contrib/experiments/common/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/common/experimentService.ts @@ -14,7 +14,7 @@ import { language } from 'vs/base/common/platform'; import { Disposable } from 'vs/base/common/lifecycle'; import { match } from 'vs/base/common/glob'; import { IRequestService, asJson } from 'vs/platform/request/common/request'; -import { ITextFileService, StateChange, TextFileModelChangeEvent } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { CancellationToken } from 'vs/base/common/cancellation'; import { distinct } from 'vs/base/common/arrays'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; @@ -404,7 +404,7 @@ export class ExperimentService extends Disposable implements IExperimentService } // Process model-save event every 250ms to reduce load - const onModelsSavedWorker = this._register(new RunOnceWorker(e => { + const onModelsSavedWorker = this._register(new RunOnceWorker(models => { const date = new Date().toDateString(); const latestExperimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {}); if (latestExperimentState.state !== ExperimentState.Evaluating) { @@ -412,9 +412,8 @@ export class ExperimentService extends Disposable implements IExperimentService onModelsSavedWorker.dispose(); return; } - e.forEach(async event => { - if (event.kind !== StateChange.SAVED - || latestExperimentState.state !== ExperimentState.Evaluating + models.forEach(async model => { + if (latestExperimentState.state !== ExperimentState.Evaluating || date === latestExperimentState.lastEditedDate || (typeof latestExperimentState.editCount === 'number' && latestExperimentState.editCount >= fileEdits.minEditCount) ) { @@ -424,7 +423,7 @@ export class ExperimentService extends Disposable implements IExperimentService let workspaceCheck = true; if (typeof fileEdits.filePathPattern === 'string') { - filePathCheck = match(fileEdits.filePathPattern, event.resource.fsPath); + filePathCheck = match(fileEdits.filePathPattern, model.resource.fsPath); } if (Array.isArray(fileEdits.workspaceIncludes) && fileEdits.workspaceIncludes.length) { const tags = await this.workspaceTagsService.getTags(); @@ -449,7 +448,7 @@ export class ExperimentService extends Disposable implements IExperimentService } }, 250)); - const onSaveHandler = this._register(this.textFileService.models.onModelSaved(e => onModelsSavedWorker.work(e))); + const onSaveHandler = this._register(this.textFileService.models.onModelSaved(m => onModelsSavedWorker.work(m))); return ExperimentState.Evaluating; }); } diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index f5f9d80fa3be4..183bae2faa913 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -61,9 +61,9 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut this._register(this.fileService.onFileChanges(e => this.onFileChanges(e))); // Ensure dirty text file and untitled models are always opened as editors - this._register(this.textFileService.models.onModelDirty(e => this.ensureDirtyFilesAreOpenedWorker.work(e.resource))); - this._register(this.textFileService.models.onModelSaveError(e => this.ensureDirtyFilesAreOpenedWorker.work(e.resource))); - this._register(this.untitledTextEditorService.onDidChangeDirty(e => this.ensureDirtyFilesAreOpenedWorker.work(e))); + this._register(this.textFileService.models.onModelDirty(m => this.ensureDirtyFilesAreOpenedWorker.work(m.resource))); + this._register(this.textFileService.models.onModelSaveError(m => this.ensureDirtyFilesAreOpenedWorker.work(m.resource))); + this._register(this.untitledTextEditorService.onDidChangeDirty(r => this.ensureDirtyFilesAreOpenedWorker.work(r))); // Out of workspace file watchers this._register(this.editorService.onDidVisibleEditorsChange(() => this.onDidVisibleEditorsChange())); diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts index cebc296f37790..e94d9a4d23df4 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts @@ -71,8 +71,8 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa } private registerListeners(): void { - this._register(this.textFileService.models.onModelSaved(e => this.onFileSavedOrReverted(e.resource))); - this._register(this.textFileService.models.onModelReverted(e => this.onFileSavedOrReverted(e.resource))); + this._register(this.textFileService.models.onModelSaved(m => this.onFileSavedOrReverted(m.resource))); + this._register(this.textFileService.models.onModelReverted(m => this.onFileSavedOrReverted(m.resource))); this._register(this.editorService.onDidActiveEditorChange(() => this.onActiveEditorChanged())); } diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index 799f816c760a5..f6f2db7029299 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -11,7 +11,7 @@ import { EncodingMode, IFileEditorInput, ITextEditorModel, Verbosity, TextEditor import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; import { FileOperationError, FileOperationResult, IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; -import { ITextFileService, ModelState, TextFileModelChangeEvent, LoadReason, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ModelState, LoadReason, TextFileOperationError, TextFileOperationResult, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IReference } from 'vs/base/common/lifecycle'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -70,25 +70,25 @@ export class FileEditorInput extends TextEditorInput implements IFileEditorInput private registerListeners(): void { // Dirty changes - this._register(this.textFileService.models.onModelDirty(e => this.onDirtyStateChange(e))); - this._register(this.textFileService.models.onModelSaveError(e => this.onDirtyStateChange(e))); - this._register(this.textFileService.models.onModelSaved(e => this.onDirtyStateChange(e))); - this._register(this.textFileService.models.onModelReverted(e => this.onDirtyStateChange(e))); + this._register(this.textFileService.models.onModelDirty(m => this.onDirtyStateChange(m))); + this._register(this.textFileService.models.onModelSaveError(m => this.onDirtyStateChange(m))); + this._register(this.textFileService.models.onModelSaved(m => this.onDirtyStateChange(m))); + this._register(this.textFileService.models.onModelReverted(m => this.onDirtyStateChange(m))); // Label changes this._register(this.labelService.onDidChangeFormatters(() => FileEditorInput.MEMOIZER.clear())); this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(() => FileEditorInput.MEMOIZER.clear())); - this._register(this.textFileService.models.onModelOrphanedChanged(e => this.onModelOrphanedChanged(e))); + this._register(this.textFileService.models.onModelOrphanedChanged(model => this.onModelOrphanedChanged(model))); } - private onDirtyStateChange(e: TextFileModelChangeEvent): void { - if (e.resource.toString() === this.resource.toString()) { + private onDirtyStateChange(model: ITextFileEditorModel): void { + if (model.resource.toString() === this.resource.toString()) { this._onDidChangeDirty.fire(); } } - private onModelOrphanedChanged(e: TextFileModelChangeEvent): void { - if (e.resource.toString() === this.resource.toString()) { + private onModelOrphanedChanged(model: ITextFileEditorModel): void { + if (model.resource.toString() === this.resource.toString()) { FileEditorInput.MEMOIZER.clear(); this._onDidChangeLabel.fire(); } diff --git a/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts b/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts index a4242c6958d83..738a131dd1872 100644 --- a/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts +++ b/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts @@ -13,7 +13,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { ISurveyData, IProductService } from 'vs/platform/product/common/productService'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; -import { ITextFileService, StateChange, TextFileModelChangeEvent } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; import { platform } from 'vs/base/common/process'; @@ -51,20 +51,18 @@ class LanguageSurvey extends Disposable { if (storageService.getNumber(EDITED_LANGUAGE_COUNT_KEY, StorageScope.GLOBAL, 0) < data.editCount) { // Process model-save event every 250ms to reduce load - const onModelsSavedWorker = this._register(new RunOnceWorker(e => { - e.forEach(event => { - if (event.kind === StateChange.SAVED) { - const model = modelService.getModel(event.resource); - if (model && model.getModeId() === data.languageId && date !== storageService.get(EDITED_LANGUAGE_DATE_KEY, StorageScope.GLOBAL)) { - const editedCount = storageService.getNumber(EDITED_LANGUAGE_COUNT_KEY, StorageScope.GLOBAL, 0) + 1; - storageService.store(EDITED_LANGUAGE_COUNT_KEY, editedCount, StorageScope.GLOBAL); - storageService.store(EDITED_LANGUAGE_DATE_KEY, date, StorageScope.GLOBAL); - } + const onModelsSavedWorker = this._register(new RunOnceWorker(models => { + models.forEach(m => { + const model = modelService.getModel(m.resource); + if (model && model.getModeId() === data.languageId && date !== storageService.get(EDITED_LANGUAGE_DATE_KEY, StorageScope.GLOBAL)) { + const editedCount = storageService.getNumber(EDITED_LANGUAGE_COUNT_KEY, StorageScope.GLOBAL, 0) + 1; + storageService.store(EDITED_LANGUAGE_COUNT_KEY, editedCount, StorageScope.GLOBAL); + storageService.store(EDITED_LANGUAGE_DATE_KEY, date, StorageScope.GLOBAL); } }); }, 250)); - this._register(textFileService.models.onModelSaved(e => onModelsSavedWorker.work(e))); + this._register(textFileService.models.onModelSaved(m => onModelsSavedWorker.work(m))); } const lastSessionDate = storageService.get(LAST_SESSION_DATE_KEY, StorageScope.GLOBAL, new Date(0).toDateString()); diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index eca81528ce4ec..0682ad8239a8e 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -19,7 +19,7 @@ import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry'; import { configurationTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { ITextFileService, TextFileModelChangeEvent } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { extname, basename, isEqual, isEqualOrParent, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; @@ -124,15 +124,15 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr this._register(configurationTelemetry(telemetryService, configurationService)); // Files Telemetry - this._register(textFileService.models.onModelLoaded(e => this.onTextFileModelLoaded(e))); - this._register(textFileService.models.onModelSaved(e => this.onTextFileModelSaved(e))); + this._register(textFileService.models.onModelLoaded(m => this.onTextFileModelLoaded(m))); + this._register(textFileService.models.onModelSaved(m => this.onTextFileModelSaved(m))); // Lifecycle this._register(lifecycleService.onShutdown(() => this.dispose())); } - private onTextFileModelLoaded(event: TextFileModelChangeEvent): void { - const settingsType = this.getTypeIfSettings(event.resource); + private onTextFileModelLoaded(model: ITextFileEditorModel): void { + const settingsType = this.getTypeIfSettings(model.resource); if (settingsType) { type SettingsReadClassification = { settingsType: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; @@ -142,12 +142,12 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr } else { type FileGetClassification = {} & FileTelemetryDataFragment; - this.telemetryService.publicLog2('fileGet', this.getTelemetryData(event.resource)); + this.telemetryService.publicLog2('fileGet', this.getTelemetryData(model.resource)); } } - private onTextFileModelSaved(event: TextFileModelChangeEvent): void { - const settingsType = this.getTypeIfSettings(event.resource); + private onTextFileModelSaved(model: ITextFileEditorModel): void { + const settingsType = this.getTypeIfSettings(model.resource); if (settingsType) { type SettingsWrittenClassification = { settingsType: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; @@ -155,7 +155,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr this.telemetryService.publicLog2<{ settingsType: string }, SettingsWrittenClassification>('settingsWritten', { settingsType }); // Do not log write to user settings.json and .vscode folder as a filePUT event as it ruins our JSON usage data } else { type FilePutClassfication = {} & FileTelemetryDataFragment; - this.telemetryService.publicLog2('filePUT', this.getTelemetryData(event.resource)); + this.telemetryService.publicLog2('filePUT', this.getTelemetryData(model.resource)); } } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 0db12b9d2fd02..95f1ae3322374 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -7,7 +7,7 @@ import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { dispose, IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { ITextFileEditorModel, ITextFileEditorModelManager, TextFileModelChangeEvent, StateChange, IModelLoadOrCreateOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileEditorModel, ITextFileEditorModelManager, StateChange, IModelLoadOrCreateOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ResourceMap } from 'vs/base/common/map'; @@ -18,25 +18,25 @@ import { onUnexpectedError } from 'vs/base/common/errors'; export class TextFileEditorModelManager extends Disposable implements ITextFileEditorModelManager { - private readonly _onModelLoaded = this._register(new Emitter()); + private readonly _onModelLoaded = this._register(new Emitter()); readonly onModelLoaded = this._onModelLoaded.event; - private readonly _onModelDirty = this._register(new Emitter()); + private readonly _onModelDirty = this._register(new Emitter()); readonly onModelDirty = this._onModelDirty.event; - private readonly _onModelSaveError = this._register(new Emitter()); + private readonly _onModelSaveError = this._register(new Emitter()); readonly onModelSaveError = this._onModelSaveError.event; - private readonly _onModelSaved = this._register(new Emitter()); + private readonly _onModelSaved = this._register(new Emitter()); readonly onModelSaved = this._onModelSaved.event; - private readonly _onModelReverted = this._register(new Emitter()); + private readonly _onModelReverted = this._register(new Emitter()); readonly onModelReverted = this._onModelReverted.event; - private readonly _onModelEncodingChanged = this._register(new Emitter()); + private readonly _onModelEncodingChanged = this._register(new Emitter()); readonly onModelEncodingChanged = this._onModelEncodingChanged.event; - private readonly _onModelOrphanedChanged = this._register(new Emitter()); + private readonly _onModelOrphanedChanged = this._register(new Emitter()); readonly onModelOrphanedChanged = this._onModelOrphanedChanged.event; private readonly mapResourceToDisposeListener = new ResourceMap(); @@ -130,28 +130,27 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // Install state change listener this.mapResourceToStateChangeListener.set(resource, model.onDidChangeState(state => { - const event = new TextFileModelChangeEvent(newModel, state); switch (state) { case StateChange.LOADED: - this._onModelLoaded.fire(event); + this._onModelLoaded.fire(newModel); break; case StateChange.DIRTY: - this._onModelDirty.fire(event); + this._onModelDirty.fire(newModel); break; case StateChange.SAVE_ERROR: - this._onModelSaveError.fire(event); + this._onModelSaveError.fire(newModel); break; case StateChange.SAVED: - this._onModelSaved.fire(event); + this._onModelSaved.fire(newModel); break; case StateChange.REVERTED: - this._onModelReverted.fire(event); + this._onModelReverted.fire(newModel); break; case StateChange.ENCODING: - this._onModelEncodingChanged.fire(event); + this._onModelEncodingChanged.fire(newModel); break; case StateChange.ORPHANED_CHANGE: - this._onModelOrphanedChanged.fire(event); + this._onModelOrphanedChanged.fire(newModel); break; } })); @@ -168,7 +167,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // Model can be dirty if a backup was restored, so we make sure to have this event delivered if (resolvedModel.isDirty()) { - this._onModelDirty.fire(new TextFileModelChangeEvent(resolvedModel, StateChange.DIRTY)); + this._onModelDirty.fire(resolvedModel); } // Remove from pending loads diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 92056c53d84ef..561aa04380bc5 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -285,22 +285,6 @@ export const enum StateChange { ORPHANED_CHANGE } -export class TextFileModelChangeEvent { - private _resource: URI; - - constructor(model: ITextFileEditorModel, private _kind: StateChange) { - this._resource = model.resource; - } - - get resource(): URI { - return this._resource; - } - - get kind(): StateChange { - return this._kind; - } -} - export interface ITextFileOperationResult { results: IResult[]; } @@ -378,14 +362,14 @@ export interface IModelLoadOrCreateOptions { export interface ITextFileEditorModelManager { - readonly onModelEncodingChanged: Event; - readonly onModelOrphanedChanged: Event; + readonly onModelEncodingChanged: Event; + readonly onModelOrphanedChanged: Event; - readonly onModelLoaded: Event; - readonly onModelDirty: Event; - readonly onModelSaveError: Event; - readonly onModelSaved: Event; - readonly onModelReverted: Event; + readonly onModelLoaded: Event; + readonly onModelDirty: Event; + readonly onModelSaveError: Event; + readonly onModelSaved: Event; + readonly onModelReverted: Event; get(resource: URI): ITextFileEditorModel | undefined; diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts index da4fbc06caf8d..4d5ee1fcee79a 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts @@ -139,32 +139,32 @@ suite('Files - TextFileEditorModelManager', () => { let savedCounter = 0; let encodingCounter = 0; - manager.onModelLoaded(e => { - if (e.resource.toString() === resource1.toString()) { + manager.onModelLoaded(model => { + if (model.resource.toString() === resource1.toString()) { loadedCounter++; } }); - manager.onModelDirty(e => { - if (e.resource.toString() === resource1.toString()) { + manager.onModelDirty(model => { + if (model.resource.toString() === resource1.toString()) { dirtyCounter++; } }); - manager.onModelReverted(e => { - if (e.resource.toString() === resource1.toString()) { + manager.onModelReverted(model => { + if (model.resource.toString() === resource1.toString()) { revertedCounter++; } }); - manager.onModelSaved(e => { - if (e.resource.toString() === resource1.toString()) { + manager.onModelSaved(model => { + if (model.resource.toString() === resource1.toString()) { savedCounter++; } }); - manager.onModelEncodingChanged(e => { - if (e.resource.toString() === resource1.toString()) { + manager.onModelEncodingChanged(model => { + if (model.resource.toString() === resource1.toString()) { encodingCounter++; } }); From 02d39920cd494f55d793c431a6e7af3a91f2d4b5 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Mon, 13 Jan 2020 11:35:27 +0100 Subject: [PATCH 204/315] Better handling of multiple spaces when using the DOM for line breaks --- .../browser/view/domLineBreaksComputer.ts | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/browser/view/domLineBreaksComputer.ts b/src/vs/editor/browser/view/domLineBreaksComputer.ts index b22d6757f436d..6ec11f3db8892 100644 --- a/src/vs/editor/browser/view/domLineBreaksComputer.ts +++ b/src/vs/editor/browser/view/domLineBreaksComputer.ts @@ -84,15 +84,18 @@ function renderLine(lineIndex: number, lineContent: string, tabSize: number, sb: // sb.appendASCIIString('" dir="ltr'); // } + const len = lineContent.length; let visibleColumn = 0; let charOffset = 0; let charOffsets: number[] = []; let visibleColumns: number[] = []; + let nextCharCode = (0 < len ? lineContent.charCodeAt(0) : CharCode.Null); - for (let charIndex = 0, len = lineContent.length; charIndex < len; charIndex++) { + for (let charIndex = 0; charIndex < len; charIndex++) { charOffsets[charIndex] = charOffset; visibleColumns[charIndex] = visibleColumn; - const charCode = lineContent.charCodeAt(charIndex); + const charCode = nextCharCode; + nextCharCode = (charIndex + 1 < len ? lineContent.charCodeAt(charIndex + 1) : CharCode.Null); let producedCharacters = 1; let charWidth = 1; switch (charCode) { @@ -100,14 +103,20 @@ function renderLine(lineIndex: number, lineContent: string, tabSize: number, sb: producedCharacters = (tabSize - (visibleColumn % tabSize)); charWidth = producedCharacters; for (let space = 1; space <= producedCharacters; space++) { - sb.appendASCII(CharCode.Space); - // sb.write1(0xA0); //   + if (space < producedCharacters) { + sb.write1(0xA0); //   + } else { + sb.appendASCII(CharCode.Space); + } } break; case CharCode.Space: - sb.appendASCII(CharCode.Space); - // sb.write1(0xA0); //   + if (nextCharCode === CharCode.Space) { + sb.write1(0xA0); //   + } else { + sb.appendASCII(CharCode.Space); + } break; case CharCode.LessThan: From c6dec6ef12f74cdb4998beb63fcc0b44d9aa1edb Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Mon, 13 Jan 2020 12:34:48 +0100 Subject: [PATCH 205/315] Add support for WrappingIndent.Same in DOMLineBreaksComputer --- .../browser/view/domLineBreaksComputer.ts | 116 ++++++++++++++---- 1 file changed, 95 insertions(+), 21 deletions(-) diff --git a/src/vs/editor/browser/view/domLineBreaksComputer.ts b/src/vs/editor/browser/view/domLineBreaksComputer.ts index 6ec11f3db8892..66d89506b8c93 100644 --- a/src/vs/editor/browser/view/domLineBreaksComputer.ts +++ b/src/vs/editor/browser/view/domLineBreaksComputer.ts @@ -36,26 +36,82 @@ export class DOMLineBreaksComputerFactory implements ILineBreaksComputerFactory requests.push(lineText); }, finalize: () => { - return createLineBreaks(this._fontInfo, tabSize, wrappingColumn, wrappingIndent, requests); + return createLineBreaks(requests, this._fontInfo, tabSize, wrappingColumn, wrappingIndent); } }; } } -function createLineBreaks(fontInfo: FontInfo, tabSize: number, firstLineBreakColumn: number, wrappingIndent: WrappingIndent, requests: string[]): (LineBreakData | null)[] { - const width = Math.round(firstLineBreakColumn * fontInfo.typicalHalfwidthCharacterWidth); +function createLineBreaks(requests: string[], fontInfo: FontInfo, tabSize: number, firstLineBreakColumn: number, wrappingIndent: WrappingIndent): (LineBreakData | null)[] { + if (firstLineBreakColumn === -1) { + const result: null[] = []; + for (let i = 0, len = requests.length; i < len; i++) { + result[i] = null; + } + return result; + } + + const overallWidth = Math.round(firstLineBreakColumn * fontInfo.typicalHalfwidthCharacterWidth); + + // Cannot respect WrappingIndent.Indent and WrappingIndent.DeepIndent because that would require + // two dom layouts, in order to first set the width of the first line, and then set the width of the wrapped lines + if (wrappingIndent === WrappingIndent.Indent || wrappingIndent === WrappingIndent.DeepIndent) { + wrappingIndent = WrappingIndent.Same; + } const containerDomNode = document.createElement('div'); Configuration.applyFontInfoSlow(containerDomNode, fontInfo); - containerDomNode.style.width = `${width}px`; const sb = createStringBuilder(10000); - const charOffsets: number[][] = []; - const visibleColumns: number[][] = []; + const firstNonWhitespaceIndices: number[] = []; + const wrappedTextIndentLengths: number[] = []; + const renderLineContents: string[] = []; + const allCharOffsets: number[][] = []; + const allVisibleColumns: number[][] = []; for (let i = 0; i < requests.length; i++) { - const r = renderLine(i, requests[i], tabSize, sb); - charOffsets[i] = r[0]; - visibleColumns[i] = r[1]; + const lineContent = requests[i]; + + let firstNonWhitespaceIndex = 0; + let wrappedTextIndentLength = 0; + let width = overallWidth; + + if (wrappingIndent !== WrappingIndent.None) { + firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineContent); + if (firstNonWhitespaceIndex === -1) { + // all whitespace line + firstNonWhitespaceIndex = 0; + + } else { + // Track existing indent + + for (let i = 0; i < firstNonWhitespaceIndex; i++) { + const charWidth = ( + lineContent.charCodeAt(i) === CharCode.Tab + ? (tabSize - (wrappedTextIndentLength % tabSize)) + : 1 + ); + wrappedTextIndentLength += charWidth; + } + + const indentWidth = Math.ceil(fontInfo.spaceWidth * wrappedTextIndentLength); + + // Force sticking to beginning of line if no character would fit except for the indentation + if (indentWidth + fontInfo.typicalFullwidthCharacterWidth > overallWidth) { + firstNonWhitespaceIndex = 0; + wrappedTextIndentLength = 0; + } else { + width = overallWidth - indentWidth; + } + } + } + + const renderLineContent = lineContent.substr(firstNonWhitespaceIndex); + const tmp = renderLine(renderLineContent, wrappedTextIndentLength, tabSize, width, sb); + firstNonWhitespaceIndices[i] = firstNonWhitespaceIndex; + wrappedTextIndentLengths[i] = wrappedTextIndentLength; + renderLineContents[i] = renderLineContent; + allCharOffsets[i] = tmp[0]; + allVisibleColumns[i] = tmp[1]; } containerDomNode.innerHTML = sb.build(); @@ -71,21 +127,45 @@ function createLineBreaks(fontInfo: FontInfo, tabSize: number, firstLineBreakCol let result: (LineBreakData | null)[] = []; for (let i = 0; i < requests.length; i++) { const lineDomNode = lineDomNodes[i]; - result[i] = readLineBreaks(range, lineDomNode, requests[i], charOffsets[i], visibleColumns[i]); + const breakOffsets: number[] | null = readLineBreaks(range, lineDomNode, renderLineContents[i], allCharOffsets[i]); + if (breakOffsets === null) { + result[i] = null; + continue; + } + + const firstNonWhitespaceIndex = firstNonWhitespaceIndices[i]; + const wrappedTextIndentLength = wrappedTextIndentLengths[i]; + const visibleColumns = allVisibleColumns[i]; + + const breakOffsetsVisibleColumn: number[] = []; + for (let j = 0, len = breakOffsets.length; j < len; j++) { + breakOffsetsVisibleColumn[j] = visibleColumns[breakOffsets[j]]; + } + + if (firstNonWhitespaceIndex !== 0) { + // All break offsets are relative to the renderLineContent, make them absolute again + for (let j = 0, len = breakOffsets.length; j < len; j++) { + breakOffsets[j] += firstNonWhitespaceIndex; + } + } + + result[i] = new LineBreakData(breakOffsets, breakOffsetsVisibleColumn, wrappedTextIndentLength); } document.body.removeChild(containerDomNode); return result; } -function renderLine(lineIndex: number, lineContent: string, tabSize: number, sb: IStringBuilder): [number[], number[]] { - sb.appendASCIIString('
'); +function renderLine(lineContent: string, initialVisibleColumn: number, tabSize: number, width: number, sb: IStringBuilder): [number[], number[]] { + sb.appendASCIIString('
'); // if (containsRTL) { // sb.appendASCIIString('" dir="ltr'); // } const len = lineContent.length; - let visibleColumn = 0; + let visibleColumn = initialVisibleColumn; let charOffset = 0; let charOffsets: number[] = []; let visibleColumns: number[] = []; @@ -163,7 +243,7 @@ function renderLine(lineIndex: number, lineContent: string, tabSize: number, sb: return [charOffsets, visibleColumns]; } -function readLineBreaks(range: Range, lineDomNode: HTMLDivElement, lineContent: string, charOffsets: number[], visibleColumns: number[]): LineBreakData | null { +function readLineBreaks(range: Range, lineDomNode: HTMLDivElement, lineContent: string, charOffsets: number[]): number[] | null { if (lineContent.length <= 1) { return null; } @@ -177,13 +257,7 @@ function readLineBreaks(range: Range, lineDomNode: HTMLDivElement, lineContent: } breakOffsets.push(lineContent.length); - - const breakOffsetsVisibleColumn = []; - for (let i = 0, len = breakOffsets.length; i < len; i++) { - breakOffsetsVisibleColumn[i] = visibleColumns[breakOffsets[i]]; - } - - return new LineBreakData(breakOffsets, breakOffsetsVisibleColumn, 0); + return breakOffsets; } type MaybeRects = ClientRectList | DOMRectList | null; From 314d0e12a90c1c98cece1528da6f94865f1490cc Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Mon, 13 Jan 2020 12:58:40 +0100 Subject: [PATCH 206/315] Introduce editor.wrappingAlgorithm --- .../browser/view/domLineBreaksComputer.ts | 4 +-- .../editor/browser/widget/codeEditorWidget.ts | 8 ++--- src/vs/editor/common/config/editorOptions.ts | 17 +++++++++++ .../common/viewModel/splitLinesCollection.ts | 29 +++++++++++++++---- .../editor/common/viewModel/viewModelImpl.ts | 6 ++-- .../test/browser/commands/sideEditing.test.ts | 3 +- .../test/browser/controller/cursor.test.ts | 21 +++++++++----- .../controller/cursorMoveCommand.test.ts | 3 +- .../viewModel/splitLinesCollection.test.ts | 15 ++++------ .../test/common/viewModel/testViewModel.ts | 9 +++--- src/vs/monaco.d.ts | 5 ++++ 11 files changed, 78 insertions(+), 42 deletions(-) diff --git a/src/vs/editor/browser/view/domLineBreaksComputer.ts b/src/vs/editor/browser/view/domLineBreaksComputer.ts index 66d89506b8c93..11db5d6849334 100644 --- a/src/vs/editor/browser/view/domLineBreaksComputer.ts +++ b/src/vs/editor/browser/view/domLineBreaksComputer.ts @@ -116,9 +116,7 @@ function createLineBreaks(requests: string[], fontInfo: FontInfo, tabSize: numbe containerDomNode.innerHTML = sb.build(); containerDomNode.style.position = 'absolute'; - containerDomNode.style.right = '0'; - containerDomNode.style.bottom = '0'; - containerDomNode.style.zIndex = '10000'; + containerDomNode.style.top = '10000'; document.body.appendChild(containerDomNode); let range = document.createRange(); diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index d9ac4a31b0db4..6d584cb146cea 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -54,7 +54,6 @@ import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/m import { DOMLineBreaksComputerFactory } from 'vs/editor/browser/view/domLineBreaksComputer'; let EDITOR_ID = 0; -const useDOMLineBreaksComputerFactory = false; export interface ICodeEditorWidgetOptions { /** @@ -1343,11 +1342,8 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._id, this._configuration, model, - ( - useDOMLineBreaksComputerFactory - ? DOMLineBreaksComputerFactory.create(this._configuration.options) - : MonospaceLineBreaksComputerFactory.create(this._configuration.options) - ), + DOMLineBreaksComputerFactory.create(this._configuration.options), + MonospaceLineBreaksComputerFactory.create(this._configuration.options), (callback) => dom.scheduleAtNextAnimationFrame(callback) ); diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 00308b411ea75..056e4ba7a0d1d 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -262,6 +262,11 @@ export interface IEditorOptions { * Defaults to 'same' in vscode and to 'none' in monaco-editor. */ wrappingIndent?: 'none' | 'same' | 'indent' | 'deepIndent'; + /** + * Controls the wrapping algorithm to use. + * Defaults to 'monospace'. + */ + wrappingAlgorithm?: 'monospace' | 'dom'; /** * Configure word wrapping characters. A break will be introduced before these characters. * Defaults to '([{‘“〈《「『【〔([{「£¥$£¥++'. @@ -3158,6 +3163,7 @@ export const enum EditorOption { wordWrapColumn, wordWrapMinified, wrappingIndent, + wrappingAlgorithm, // Leave these at the end (because they have dependencies!) editorClassName, @@ -3722,6 +3728,17 @@ export const EditorOptions = { description: nls.localize('wrappingIndent', "Controls the indentation of wrapped lines."), } )), + wrappingAlgorithm: register(new EditorStringEnumOption( + EditorOption.wrappingAlgorithm, 'wrappingAlgorithm', + 'monospace', ['monospace', 'dom'], + { + enumDescriptions: [ + nls.localize('wrappingAlgorithm.monospace', "Assumes that all characters are of the same width. This is a fast algorithm."), + nls.localize('wrappingAlgorithm.dom', "Delegates wrapping points computation to the DOM. This is a slow algorithm, that might cause freezes for large files.") + ], + description: nls.localize('wrappingAlgorithm', "Controls the algorithm that computes wrapping points.") + } + )), // Leave these at the end (because they have dependencies!) editorClassName: register(new EditorClassName()), diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 726444f3b11e0..37a9e6a02c66d 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -264,11 +264,16 @@ class LineNumberMapper { } } +const usDOMLineBreaksComputerFactory = false; + export class SplitLinesCollection implements IViewModelLinesCollection { private readonly model: ITextModel; private _validModelVersionId: number; + private readonly _domLineBreaksComputerFactory: ILineBreaksComputerFactory; + private readonly _monospaceLineBreaksComputerFactory: ILineBreaksComputerFactory; + private wrappingColumn: number; private columnsForFullWidthChar: number; private wrappingIndent: WrappingIndent; @@ -277,18 +282,25 @@ export class SplitLinesCollection implements IViewModelLinesCollection { private prefixSumComputer!: LineNumberMapper; - private readonly linePositionMapperFactory: ILineBreaksComputerFactory; - private hiddenAreasIds!: string[]; - constructor(model: ITextModel, linePositionMapperFactory: ILineBreaksComputerFactory, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent) { + constructor( + model: ITextModel, + domLineBreaksComputerFactory: ILineBreaksComputerFactory, + monospaceLineBreaksComputerFactory: ILineBreaksComputerFactory, + tabSize: number, + wrappingColumn: number, + columnsForFullWidthChar: number, + wrappingIndent: WrappingIndent + ) { this.model = model; this._validModelVersionId = -1; + this._domLineBreaksComputerFactory = domLineBreaksComputerFactory; + this._monospaceLineBreaksComputerFactory = monospaceLineBreaksComputerFactory; this.tabSize = tabSize; this.wrappingColumn = wrappingColumn; this.columnsForFullWidthChar = columnsForFullWidthChar; this.wrappingIndent = wrappingIndent; - this.linePositionMapperFactory = linePositionMapperFactory; this._constructLines(/*resetHiddenAreas*/true, null); } @@ -310,7 +322,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { let linesContent = this.model.getLinesContent(); const lineCount = linesContent.length; - const lineBreaksComputer = this.linePositionMapperFactory.createLineBreaksComputer(this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent); + const lineBreaksComputer = this.createLineBreaksComputer(); for (let i = 0; i < lineCount; i++) { lineBreaksComputer.addRequest(linesContent[i], previousLineBreaks ? previousLineBreaks[i] : null); } @@ -495,7 +507,12 @@ export class SplitLinesCollection implements IViewModelLinesCollection { } public createLineBreaksComputer(): ILineBreaksComputer { - return this.linePositionMapperFactory.createLineBreaksComputer(this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent); + const lineBreaksComputerFactory = ( + usDOMLineBreaksComputerFactory + ? this._domLineBreaksComputerFactory + : this._monospaceLineBreaksComputerFactory + ); + return lineBreaksComputerFactory.createLineBreaksComputer(this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent); } public onModelFlushed(): void { diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 718c9fc090e18..2e9c57986ddec 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -46,7 +46,8 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel editorId: number, configuration: editorCommon.IConfiguration, model: ITextModel, - lineMapperFactory: ILineBreaksComputerFactory, + domLineBreaksComputerFactory: ILineBreaksComputerFactory, + monospaceLineBreaksComputerFactory: ILineBreaksComputerFactory, scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable ) { super(); @@ -72,7 +73,8 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.lines = new SplitLinesCollection( this.model, - lineMapperFactory, + domLineBreaksComputerFactory, + monospaceLineBreaksComputerFactory, this.model.getOptions().tabSize, wrappingInfo.wrappingColumn, fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth, diff --git a/src/vs/editor/test/browser/commands/sideEditing.test.ts b/src/vs/editor/test/browser/commands/sideEditing.test.ts index 67ae4e4d1749b..540771d0c72a7 100644 --- a/src/vs/editor/test/browser/commands/sideEditing.test.ts +++ b/src/vs/editor/test/browser/commands/sideEditing.test.ts @@ -201,7 +201,8 @@ suite('SideEditing', () => { function _runTest(selection: Selection, editRange: Range, editText: string, editForceMoveMarkers: boolean, expected: Selection, msg: string): void { const model = TextModel.createFromString(LINES.join('\n')); const config = new TestConfiguration({}); - const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!); + const monospaceLineBreaksComputerFactory = MonospaceLineBreaksComputerFactory.create(config.options); + const viewModel = new ViewModel(0, config, model, monospaceLineBreaksComputerFactory, monospaceLineBreaksComputerFactory, null!); const cursor = new Cursor(config, model, viewModel); cursor.setSelections('tests', [selection]); diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index 7d82c86f55e5f..7b9cafb8c9509 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -12,7 +12,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { TokenizationResult2 } from 'vs/editor/common/core/token'; -import { Handler, ICommand, ICursorStateComputerData, IEditOperationBuilder } from 'vs/editor/common/editorCommon'; +import { Handler, ICommand, ICursorStateComputerData, IEditOperationBuilder, IConfiguration } from 'vs/editor/common/editorCommon'; import { EndOfLinePreference, EndOfLineSequence, ITextModel } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import { IState, ITokenizationSupport, LanguageIdentifier, TokenizationRegistry } from 'vs/editor/common/modes'; @@ -131,6 +131,11 @@ function assertCursor(cursor: Cursor, what: Position | Selection | Selection[]): assert.deepEqual(actual, expected); } +function createViewModel(configuration: IConfiguration, model: ITextModel): ViewModel { + const monospaceLineBreaksComputerFactory = MonospaceLineBreaksComputerFactory.create(configuration.options); + return new ViewModel(0, configuration, model, monospaceLineBreaksComputerFactory, monospaceLineBreaksComputerFactory, null!); +} + suite('Editor Controller - Cursor', () => { const LINE1 = ' \tMy First Line\t '; const LINE2 = '\tMy Second Line'; @@ -153,7 +158,7 @@ suite('Editor Controller - Cursor', () => { thisModel = createTextModel(text); thisConfiguration = new TestConfiguration({}); - thisViewModel = new ViewModel(0, thisConfiguration, thisModel, MonospaceLineBreaksComputerFactory.create(thisConfiguration.options), null!); + thisViewModel = createViewModel(thisConfiguration, thisModel); thisCursor = new Cursor(thisConfiguration, thisModel, thisViewModel); }); @@ -777,7 +782,7 @@ suite('Editor Controller - Cursor', () => { 'var newer = require("gulp-newer");', ].join('\n')); const config = new TestConfiguration({}); - const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!); + const viewModel = createViewModel(config, model); const cursor = new Cursor(config, model, viewModel); moveTo(cursor, 1, 4, false); @@ -817,7 +822,7 @@ suite('Editor Controller - Cursor', () => { '', ].join('\n')); const config = new TestConfiguration({}); - const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!); + const viewModel = createViewModel(config, model); const cursor = new Cursor(config, model, viewModel); moveTo(cursor, 10, 10, false); @@ -881,7 +886,7 @@ suite('Editor Controller - Cursor', () => { '', ].join('\n')); const config = new TestConfiguration({}); - const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!); + const viewModel = createViewModel(config, model); const cursor = new Cursor(config, model, viewModel); moveTo(cursor, 10, 10, false); @@ -930,7 +935,7 @@ suite('Editor Controller - Cursor', () => { 'var newer = require("gulp-newer");', ].join('\n')); const config = new TestConfiguration({}); - const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!); + const viewModel = createViewModel(config, model); const cursor = new Cursor(config, model, viewModel); moveTo(cursor, 1, 4, false); @@ -2075,7 +2080,7 @@ suite('Editor Controller - Regression tests', () => { wordWrap: 'wordWrapColumn', wordWrapColumn: 100 }); - const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!); + const viewModel = createViewModel(config, model); const cursor = new Cursor(config, model, viewModel); moveTo(cursor, 1, 43, false); @@ -3835,7 +3840,7 @@ function usingCursor(opts: ICursorOpts, callback: (model: TextModel, cursor: Cur let model = createTextModel(opts.text.join('\n'), opts.modelOpts, opts.languageIdentifier); model.forceTokenization(model.getLineCount()); let config = new TestConfiguration(opts.editorOpts || {}); - let viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!); + let viewModel = createViewModel(config, model); let cursor = new Cursor(config, model, viewModel); callback(model, cursor); diff --git a/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts b/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts index 117cf54372d52..36f2f54dc231a 100644 --- a/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts +++ b/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts @@ -33,7 +33,8 @@ suite('Cursor move command test', () => { thisModel = TextModel.createFromString(text); thisConfiguration = new TestConfiguration({}); - thisViewModel = new ViewModel(0, thisConfiguration, thisModel, MonospaceLineBreaksComputerFactory.create(thisConfiguration.options), null!); + const monospaceLineBreaksComputerFactory = MonospaceLineBreaksComputerFactory.create(thisConfiguration.options); + thisViewModel = new ViewModel(0, thisConfiguration, thisModel, monospaceLineBreaksComputerFactory, monospaceLineBreaksComputerFactory, null!); thisCursor = new Cursor(thisConfiguration, thisModel, thisViewModel); }); diff --git a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts index 7209c90e881c0..0f6be3855ad07 100644 --- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts +++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts @@ -19,7 +19,6 @@ import { ViewLineData } from 'vs/editor/common/viewModel/viewModel'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; - suite('Editor ViewModel - SplitLinesCollection', () => { test('SplitLine', () => { let model1 = createModel('My First LineMy Second LineAnd another one'); @@ -95,10 +94,7 @@ suite('Editor ViewModel - SplitLinesCollection', () => { const wordWrapBreakBeforeCharacters = config.options.get(EditorOption.wordWrapBreakBeforeCharacters); const wrappingIndent = config.options.get(EditorOption.wrappingIndent); - const lineBreaksComputerFactory = new MonospaceLineBreaksComputerFactory( - wordWrapBreakBeforeCharacters, - wordWrapBreakAfterCharacters - ); + const lineBreaksComputerFactory = new MonospaceLineBreaksComputerFactory(wordWrapBreakBeforeCharacters, wordWrapBreakAfterCharacters); const model = TextModel.createFromString([ 'int main() {', @@ -112,6 +108,7 @@ suite('Editor ViewModel - SplitLinesCollection', () => { const linesCollection = new SplitLinesCollection( model, lineBreaksComputerFactory, + lineBreaksComputerFactory, model.getOptions().tabSize, wrappingInfo.wrappingColumn, fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth, @@ -747,14 +744,12 @@ suite('SplitLinesCollection', () => { const wordWrapBreakBeforeCharacters = configuration.options.get(EditorOption.wordWrapBreakBeforeCharacters); const wrappingIndent = configuration.options.get(EditorOption.wrappingIndent); - const factory = new MonospaceLineBreaksComputerFactory( - wordWrapBreakBeforeCharacters, - wordWrapBreakAfterCharacters - ); + const lineBreaksComputerFactory = new MonospaceLineBreaksComputerFactory(wordWrapBreakBeforeCharacters, wordWrapBreakAfterCharacters); const linesCollection = new SplitLinesCollection( model, - factory, + lineBreaksComputerFactory, + lineBreaksComputerFactory, model.getOptions().tabSize, wrappingInfo.wrappingColumn, fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth, diff --git a/src/vs/editor/test/common/viewModel/testViewModel.ts b/src/vs/editor/test/common/viewModel/testViewModel.ts index fffa9f05aa95d..b316d88b00494 100644 --- a/src/vs/editor/test/common/viewModel/testViewModel.ts +++ b/src/vs/editor/test/common/viewModel/testViewModel.ts @@ -12,11 +12,10 @@ import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/m export function testViewModel(text: string[], options: IEditorOptions, callback: (viewModel: ViewModel, model: TextModel) => void): void { const EDITOR_ID = 1; - let configuration = new TestConfiguration(options); - - let model = TextModel.createFromString(text.join('\n')); - - let viewModel = new ViewModel(EDITOR_ID, configuration, model, MonospaceLineBreaksComputerFactory.create(configuration.options), null!); + const configuration = new TestConfiguration(options); + const model = TextModel.createFromString(text.join('\n')); + const monospaceLineBreaksComputerFactory = MonospaceLineBreaksComputerFactory.create(configuration.options); + const viewModel = new ViewModel(EDITOR_ID, configuration, model, monospaceLineBreaksComputerFactory, monospaceLineBreaksComputerFactory, null!); callback(viewModel, model); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 5e2f5a1564248..95d91f163b904 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2707,6 +2707,11 @@ declare namespace monaco.editor { * Defaults to 'same' in vscode and to 'none' in monaco-editor. */ wrappingIndent?: 'none' | 'same' | 'indent' | 'deepIndent'; + /** + * Controls the wrapping algorithm to use. + * Defaults to 'monospace'. + */ + wrappingAlgorithm?: 'monospace' | 'dom'; /** * Configure word wrapping characters. A break will be introduced before these characters. * Defaults to '([{‘“〈《「『【〔([{「£¥$£¥++'. From 53125e31f0953a83b1e8da711a0f9fbab2fd4bfc Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Mon, 13 Jan 2020 14:51:04 +0100 Subject: [PATCH 207/315] Pass in entire fontInfo when computing line breaks --- .../browser/view/domLineBreaksComputer.ts | 18 ++++------- .../editor/browser/widget/codeEditorWidget.ts | 2 +- .../viewModel/monospaceLineBreaksComputer.ts | 5 ++-- .../common/viewModel/splitLinesCollection.ts | 30 +++++++++++-------- .../editor/common/viewModel/viewModelImpl.ts | 4 +-- .../monospaceLineBreaksComputer.test.ts | 18 ++++++++++- .../viewModel/splitLinesCollection.test.ts | 4 +-- 7 files changed, 48 insertions(+), 33 deletions(-) diff --git a/src/vs/editor/browser/view/domLineBreaksComputer.ts b/src/vs/editor/browser/view/domLineBreaksComputer.ts index 11db5d6849334..ab567c36c9fe6 100644 --- a/src/vs/editor/browser/view/domLineBreaksComputer.ts +++ b/src/vs/editor/browser/view/domLineBreaksComputer.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ILineBreaksComputerFactory, LineBreakData, ILineBreaksComputer } from 'vs/editor/common/viewModel/splitLinesCollection'; -import { IComputedEditorOptions, EditorOption, WrappingIndent } from 'vs/editor/common/config/editorOptions'; +import { WrappingIndent } from 'vs/editor/common/config/editorOptions'; import { FontInfo } from 'vs/editor/common/config/fontInfo'; import { createStringBuilder, IStringBuilder } from 'vs/editor/common/core/stringBuilder'; import { CharCode } from 'vs/base/common/charCode'; @@ -13,22 +13,16 @@ import { Configuration } from 'vs/editor/browser/config/configuration'; export class DOMLineBreaksComputerFactory implements ILineBreaksComputerFactory { - public static create(options: IComputedEditorOptions): DOMLineBreaksComputerFactory { - return new DOMLineBreaksComputerFactory( - options.get(EditorOption.fontInfo) - ); + public static create(): DOMLineBreaksComputerFactory { + return new DOMLineBreaksComputerFactory(); } - private _fontInfo: FontInfo; - - constructor(fontInfo: FontInfo) { - this._fontInfo = fontInfo; + constructor() { } - public createLineBreaksComputer(tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ILineBreaksComputer { + public createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent): ILineBreaksComputer { tabSize = tabSize | 0; //@perf wrappingColumn = +wrappingColumn; //@perf - columnsForFullWidthChar = +columnsForFullWidthChar; //@perf let requests: string[] = []; return { @@ -36,7 +30,7 @@ export class DOMLineBreaksComputerFactory implements ILineBreaksComputerFactory requests.push(lineText); }, finalize: () => { - return createLineBreaks(requests, this._fontInfo, tabSize, wrappingColumn, wrappingIndent); + return createLineBreaks(requests, fontInfo, tabSize, wrappingColumn, wrappingIndent); } }; } diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 6d584cb146cea..2f8f5cf4f98d2 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -1342,7 +1342,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._id, this._configuration, model, - DOMLineBreaksComputerFactory.create(this._configuration.options), + DOMLineBreaksComputerFactory.create(), MonospaceLineBreaksComputerFactory.create(this._configuration.options), (callback) => dom.scheduleAtNextAnimationFrame(callback) ); diff --git a/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts index 546464c263658..97a3c4a7655e8 100644 --- a/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts +++ b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts @@ -8,6 +8,7 @@ import * as strings from 'vs/base/common/strings'; import { WrappingIndent, IComputedEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions'; import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier'; import { ILineBreaksComputerFactory, LineBreakData, ILineBreaksComputer } from 'vs/editor/common/viewModel/splitLinesCollection'; +import { FontInfo } from 'vs/editor/common/config/fontInfo'; const enum CharacterClass { NONE = 0, @@ -69,10 +70,9 @@ export class MonospaceLineBreaksComputerFactory implements ILineBreaksComputerFa this.classifier = new WrappingCharacterClassifier(breakBeforeChars, breakAfterChars); } - public createLineBreaksComputer(tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ILineBreaksComputer { + public createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent): ILineBreaksComputer { tabSize = tabSize | 0; //@perf wrappingColumn = +wrappingColumn; //@perf - columnsForFullWidthChar = +columnsForFullWidthChar; //@perf let requests: string[] = []; let previousBreakingData: (LineBreakData | null)[] = []; @@ -82,6 +82,7 @@ export class MonospaceLineBreaksComputerFactory implements ILineBreaksComputerFa previousBreakingData.push(previousLineBreakData); }, finalize: () => { + const columnsForFullWidthChar = fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth; //@perf let result: (LineBreakData | null)[] = []; for (let i = 0, len = requests.length; i < len; i++) { const previousLineBreakData = previousBreakingData[i]; diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 37a9e6a02c66d..bee048ac90af6 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -15,6 +15,7 @@ import { PrefixSumIndexOfResult } from 'vs/editor/common/viewModel/prefixSumComp import { ICoordinatesConverter, IOverviewRulerDecorations, ViewLineData } from 'vs/editor/common/viewModel/viewModel'; import { ITheme } from 'vs/platform/theme/common/themeService'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { FontInfo } from 'vs/editor/common/config/fontInfo'; export class OutputPosition { outputLineIndex: number; @@ -75,7 +76,7 @@ export interface ILineBreaksComputer { } export interface ILineBreaksComputerFactory { - createLineBreaksComputer(tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ILineBreaksComputer; + createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent): ILineBreaksComputer; } export interface ISimpleModel { @@ -108,7 +109,7 @@ export interface ISplitLine { export interface IViewModelLinesCollection extends IDisposable { createCoordinatesConverter(): ICoordinatesConverter; - setWrappingSettings(wrappingIndent: WrappingIndent, wrappingColumn: number, columnsForFullWidthChar: number): boolean; + setWrappingSettings(fontInfo: FontInfo, wrappingColumn: number, wrappingIndent: WrappingIndent): boolean; setTabSize(newTabSize: number): boolean; getHiddenAreas(): Range[]; setHiddenAreas(_ranges: Range[]): boolean; @@ -274,10 +275,10 @@ export class SplitLinesCollection implements IViewModelLinesCollection { private readonly _domLineBreaksComputerFactory: ILineBreaksComputerFactory; private readonly _monospaceLineBreaksComputerFactory: ILineBreaksComputerFactory; + private fontInfo: FontInfo; + private tabSize: number; private wrappingColumn: number; - private columnsForFullWidthChar: number; private wrappingIndent: WrappingIndent; - private tabSize: number; private lines!: ISplitLine[]; private prefixSumComputer!: LineNumberMapper; @@ -288,18 +289,18 @@ export class SplitLinesCollection implements IViewModelLinesCollection { model: ITextModel, domLineBreaksComputerFactory: ILineBreaksComputerFactory, monospaceLineBreaksComputerFactory: ILineBreaksComputerFactory, + fontInfo: FontInfo, tabSize: number, wrappingColumn: number, - columnsForFullWidthChar: number, wrappingIndent: WrappingIndent ) { this.model = model; this._validModelVersionId = -1; this._domLineBreaksComputerFactory = domLineBreaksComputerFactory; this._monospaceLineBreaksComputerFactory = monospaceLineBreaksComputerFactory; + this.fontInfo = fontInfo; this.tabSize = tabSize; this.wrappingColumn = wrappingColumn; - this.columnsForFullWidthChar = columnsForFullWidthChar; this.wrappingIndent = wrappingIndent; this._constructLines(/*resetHiddenAreas*/true, null); @@ -482,16 +483,19 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return true; } - public setWrappingSettings(wrappingIndent: WrappingIndent, wrappingColumn: number, columnsForFullWidthChar: number): boolean { - if (this.wrappingIndent === wrappingIndent && this.wrappingColumn === wrappingColumn && this.columnsForFullWidthChar === columnsForFullWidthChar) { + public setWrappingSettings(fontInfo: FontInfo, wrappingColumn: number, wrappingIndent: WrappingIndent): boolean { + const equalFontInfo = this.fontInfo.equals(fontInfo); + const equalWrappingColumn = (this.wrappingColumn === wrappingColumn); + const equalWrappingIndent = (this.wrappingIndent === wrappingIndent); + if (equalFontInfo && equalWrappingColumn && equalWrappingIndent) { return false; } - const onlyWrappingColumnChanged = (this.wrappingIndent === wrappingIndent && this.wrappingColumn !== wrappingColumn && this.columnsForFullWidthChar === columnsForFullWidthChar); + const onlyWrappingColumnChanged = (equalFontInfo && !equalWrappingColumn && equalWrappingIndent); - this.wrappingIndent = wrappingIndent; + this.fontInfo = fontInfo; this.wrappingColumn = wrappingColumn; - this.columnsForFullWidthChar = columnsForFullWidthChar; + this.wrappingIndent = wrappingIndent; let previousLineBreaks: ((LineBreakData | null)[]) | null = null; if (onlyWrappingColumnChanged) { @@ -512,7 +516,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { ? this._domLineBreaksComputerFactory : this._monospaceLineBreaksComputerFactory ); - return lineBreaksComputerFactory.createLineBreaksComputer(this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent); + return lineBreaksComputerFactory.createLineBreaksComputer(this.fontInfo, this.tabSize, this.wrappingColumn, this.wrappingIndent); } public onModelFlushed(): void { @@ -1453,7 +1457,7 @@ export class IdentityLinesCollection implements IViewModelLinesCollection { return false; } - public setWrappingSettings(_wrappingIndent: WrappingIndent, _wrappingColumn: number, _columnsForFullWidthChar: number): boolean { + public setWrappingSettings(_fontInfo: FontInfo, _wrappingColumn: number, _wrappingIndent: WrappingIndent): boolean { return false; } diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 2e9c57986ddec..f6b5d5eeb4f92 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -75,9 +75,9 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel this.model, domLineBreaksComputerFactory, monospaceLineBreaksComputerFactory, + fontInfo, this.model.getOptions().tabSize, wrappingInfo.wrappingColumn, - fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth, wrappingIndent ); } @@ -157,7 +157,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel const fontInfo = options.get(EditorOption.fontInfo); const wrappingIndent = options.get(EditorOption.wrappingIndent); - if (this.lines.setWrappingSettings(wrappingIndent, wrappingInfo.wrappingColumn, fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth)) { + if (this.lines.setWrappingSettings(fontInfo, wrappingInfo.wrappingColumn, wrappingIndent)) { eventsCollector.emit(new viewEvents.ViewFlushedEvent()); eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent()); eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent()); diff --git a/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts b/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts index a95095bbc9933..b0bacb0768bf9 100644 --- a/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts +++ b/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import { WrappingIndent, EditorOptions } from 'vs/editor/common/config/editorOptions'; import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; import { ILineBreaksComputerFactory, LineBreakData } from 'vs/editor/common/viewModel/splitLinesCollection'; +import { FontInfo } from 'vs/editor/common/config/fontInfo'; function parseAnnotatedText(annotatedText: string): { text: string; indices: number[]; } { let text = ''; @@ -43,7 +44,22 @@ function toAnnotatedText(text: string, lineBreakData: LineBreakData | null): str } function getLineBreakData(factory: ILineBreaksComputerFactory, tabSize: number, breakAfter: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent, text: string, previousLineBreakData: LineBreakData | null): LineBreakData | null { - const lineBreaksComputer = factory.createLineBreaksComputer(tabSize, breakAfter, columnsForFullWidthChar, wrappingIndent); + const fontInfo = new FontInfo({ + zoomLevel: 0, + fontFamily: 'testFontFamily', + fontWeight: 'normal', + fontSize: 14, + fontFeatureSettings: '', + lineHeight: 19, + letterSpacing: 0, + isMonospace: true, + typicalHalfwidthCharacterWidth: 7, + typicalFullwidthCharacterWidth: 14, + canUseHalfwidthRightwardsArrow: true, + spaceWidth: 7, + maxDigitWidth: 7 + }, false); + const lineBreaksComputer = factory.createLineBreaksComputer(fontInfo, tabSize, breakAfter, wrappingIndent); const previousLineBreakDataClone = previousLineBreakData ? new LineBreakData(previousLineBreakData.breakOffsets.slice(0), previousLineBreakData.breakOffsetsVisibleColumn.slice(0), previousLineBreakData.wrappedTextIndentLength) : null; lineBreaksComputer.addRequest(text, previousLineBreakDataClone); return lineBreaksComputer.finalize()[0]; diff --git a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts index 0f6be3855ad07..53234a03a3a11 100644 --- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts +++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts @@ -109,9 +109,9 @@ suite('Editor ViewModel - SplitLinesCollection', () => { model, lineBreaksComputerFactory, lineBreaksComputerFactory, + fontInfo, model.getOptions().tabSize, wrappingInfo.wrappingColumn, - fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth, wrappingIndent ); @@ -750,9 +750,9 @@ suite('SplitLinesCollection', () => { model, lineBreaksComputerFactory, lineBreaksComputerFactory, + fontInfo, model.getOptions().tabSize, wrappingInfo.wrappingColumn, - fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth, wrappingIndent ); From 519ceb6283a884a82dcabc536ecb8a9bc125adbc Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Mon, 13 Jan 2020 15:00:08 +0100 Subject: [PATCH 208/315] Respect editor.wrappingAlgorithm --- src/vs/editor/common/config/editorOptions.ts | 3 ++- .../common/viewModel/splitLinesCollection.ts | 21 +++++++++++-------- .../editor/common/viewModel/viewModelImpl.ts | 9 +++++--- .../viewModel/splitLinesCollection.test.ts | 2 ++ 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 056e4ba7a0d1d..2bcf40b9399d5 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -3730,7 +3730,8 @@ export const EditorOptions = { )), wrappingAlgorithm: register(new EditorStringEnumOption( EditorOption.wrappingAlgorithm, 'wrappingAlgorithm', - 'monospace', ['monospace', 'dom'], + 'monospace' as 'monospace' | 'dom', + ['monospace', 'dom'] as const, { enumDescriptions: [ nls.localize('wrappingAlgorithm.monospace', "Assumes that all characters are of the same width. This is a fast algorithm."), diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index bee048ac90af6..dde0d12c37ad9 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -109,7 +109,7 @@ export interface ISplitLine { export interface IViewModelLinesCollection extends IDisposable { createCoordinatesConverter(): ICoordinatesConverter; - setWrappingSettings(fontInfo: FontInfo, wrappingColumn: number, wrappingIndent: WrappingIndent): boolean; + setWrappingSettings(fontInfo: FontInfo, wrappingAlgorithm: 'monospace' | 'dom', wrappingColumn: number, wrappingIndent: WrappingIndent): boolean; setTabSize(newTabSize: number): boolean; getHiddenAreas(): Range[]; setHiddenAreas(_ranges: Range[]): boolean; @@ -265,8 +265,6 @@ class LineNumberMapper { } } -const usDOMLineBreaksComputerFactory = false; - export class SplitLinesCollection implements IViewModelLinesCollection { private readonly model: ITextModel; @@ -279,6 +277,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { private tabSize: number; private wrappingColumn: number; private wrappingIndent: WrappingIndent; + private wrappingAlgorithm: 'monospace' | 'dom'; private lines!: ISplitLine[]; private prefixSumComputer!: LineNumberMapper; @@ -291,8 +290,9 @@ export class SplitLinesCollection implements IViewModelLinesCollection { monospaceLineBreaksComputerFactory: ILineBreaksComputerFactory, fontInfo: FontInfo, tabSize: number, + wrappingAlgorithm: 'monospace' | 'dom', wrappingColumn: number, - wrappingIndent: WrappingIndent + wrappingIndent: WrappingIndent, ) { this.model = model; this._validModelVersionId = -1; @@ -300,6 +300,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { this._monospaceLineBreaksComputerFactory = monospaceLineBreaksComputerFactory; this.fontInfo = fontInfo; this.tabSize = tabSize; + this.wrappingAlgorithm = wrappingAlgorithm; this.wrappingColumn = wrappingColumn; this.wrappingIndent = wrappingIndent; @@ -483,17 +484,19 @@ export class SplitLinesCollection implements IViewModelLinesCollection { return true; } - public setWrappingSettings(fontInfo: FontInfo, wrappingColumn: number, wrappingIndent: WrappingIndent): boolean { + public setWrappingSettings(fontInfo: FontInfo, wrappingAlgorithm: 'monospace' | 'dom', wrappingColumn: number, wrappingIndent: WrappingIndent): boolean { const equalFontInfo = this.fontInfo.equals(fontInfo); + const equalWrappingAlgorithm = (this.wrappingAlgorithm === wrappingAlgorithm); const equalWrappingColumn = (this.wrappingColumn === wrappingColumn); const equalWrappingIndent = (this.wrappingIndent === wrappingIndent); - if (equalFontInfo && equalWrappingColumn && equalWrappingIndent) { + if (equalFontInfo && equalWrappingAlgorithm && equalWrappingColumn && equalWrappingIndent) { return false; } - const onlyWrappingColumnChanged = (equalFontInfo && !equalWrappingColumn && equalWrappingIndent); + const onlyWrappingColumnChanged = (equalFontInfo && equalWrappingAlgorithm && !equalWrappingColumn && equalWrappingIndent); this.fontInfo = fontInfo; + this.wrappingAlgorithm = wrappingAlgorithm; this.wrappingColumn = wrappingColumn; this.wrappingIndent = wrappingIndent; @@ -512,7 +515,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { public createLineBreaksComputer(): ILineBreaksComputer { const lineBreaksComputerFactory = ( - usDOMLineBreaksComputerFactory + this.wrappingAlgorithm === 'dom' ? this._domLineBreaksComputerFactory : this._monospaceLineBreaksComputerFactory ); @@ -1457,7 +1460,7 @@ export class IdentityLinesCollection implements IViewModelLinesCollection { return false; } - public setWrappingSettings(_fontInfo: FontInfo, _wrappingColumn: number, _wrappingIndent: WrappingIndent): boolean { + public setWrappingSettings(_fontInfo: FontInfo, _wrappingAlgorithm: 'monospace' | 'dom', _wrappingColumn: number, _wrappingIndent: WrappingIndent): boolean { return false; } diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index f6b5d5eeb4f92..6bba786da592d 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -67,8 +67,9 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel } else { const options = this.configuration.options; - const wrappingInfo = options.get(EditorOption.wrappingInfo); const fontInfo = options.get(EditorOption.fontInfo); + const wrappingAlgorithm = options.get(EditorOption.wrappingAlgorithm); + const wrappingInfo = options.get(EditorOption.wrappingInfo); const wrappingIndent = options.get(EditorOption.wrappingIndent); this.lines = new SplitLinesCollection( @@ -77,6 +78,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel monospaceLineBreaksComputerFactory, fontInfo, this.model.getOptions().tabSize, + wrappingAlgorithm, wrappingInfo.wrappingColumn, wrappingIndent ); @@ -153,11 +155,12 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel let restorePreviousViewportStart = false; const options = this.configuration.options; - const wrappingInfo = options.get(EditorOption.wrappingInfo); const fontInfo = options.get(EditorOption.fontInfo); + const wrappingAlgorithm = options.get(EditorOption.wrappingAlgorithm); + const wrappingInfo = options.get(EditorOption.wrappingInfo); const wrappingIndent = options.get(EditorOption.wrappingIndent); - if (this.lines.setWrappingSettings(fontInfo, wrappingInfo.wrappingColumn, wrappingIndent)) { + if (this.lines.setWrappingSettings(fontInfo, wrappingAlgorithm, wrappingInfo.wrappingColumn, wrappingIndent)) { eventsCollector.emit(new viewEvents.ViewFlushedEvent()); eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent()); eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent()); diff --git a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts index 53234a03a3a11..7bd6c592366a3 100644 --- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts +++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts @@ -111,6 +111,7 @@ suite('Editor ViewModel - SplitLinesCollection', () => { lineBreaksComputerFactory, fontInfo, model.getOptions().tabSize, + 'monospace', wrappingInfo.wrappingColumn, wrappingIndent ); @@ -752,6 +753,7 @@ suite('SplitLinesCollection', () => { lineBreaksComputerFactory, fontInfo, model.getOptions().tabSize, + 'monospace', wrappingInfo.wrappingColumn, wrappingIndent ); From ebfcd2a3a80cb3585000d143e0f38ac3a8d02db6 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 13 Jan 2020 15:21:40 +0100 Subject: [PATCH 209/315] directly add override identifier property schema --- .../common/configurationRegistry.ts | 41 +++++-------------- 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 61590f38fc499..728bb7e66bfec 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -8,10 +8,8 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { Registry } from 'vs/platform/registry/common/platform'; import * as types from 'vs/base/common/types'; -import * as strings from 'vs/base/common/strings'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { values } from 'vs/base/common/map'; export const Extensions = { Configuration: 'base.contributions.configuration' @@ -159,7 +157,6 @@ class ConfigurationRegistry implements IConfigurationRegistry { private readonly excludedConfigurationProperties: { [qualifiedKey: string]: IJSONSchema }; private readonly resourceLanguageSettingsSchema: IJSONSchema; private readonly overrideIdentifiers = new Set(); - private overridePropertyPattern: string; private readonly _onDidSchemaChange = new Emitter(); readonly onDidSchemaChange: Event = this._onDidSchemaChange.event; @@ -177,7 +174,6 @@ class ConfigurationRegistry implements IConfigurationRegistry { this.resourceLanguageSettingsSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown editor configuration setting', allowTrailingCommas: true, allowComments: true }; this.configurationProperties = {}; this.excludedConfigurationProperties = {}; - this.overridePropertyPattern = this.computeOverridePropertyPattern(); contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema); } @@ -394,42 +390,27 @@ class ConfigurationRegistry implements IConfigurationRegistry { } private updateOverridePropertyPatternKey(): void { - let patternProperties: IJSONSchema = allSettings.patternProperties[this.overridePropertyPattern]; - if (!patternProperties) { - patternProperties = { + for (const overrideIdentifier of this.overrideIdentifiers) { + const overrideIdentifierProperty = `[${overrideIdentifier}]`; + const resourceLanguagePropertiesSchema: IJSONSchema = { type: 'object', description: nls.localize('overrideSettings.defaultDescription', "Configure editor settings to be overridden for a language."), errorMessage: 'Unknown Identifier. Use language identifiers', - $ref: resourceLanguageSettingsSchemaId + $ref: resourceLanguageSettingsSchemaId, + default: this.defaultOverridesConfigurationNode.properties![overrideIdentifierProperty]?.default }; + allSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema; + applicationSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema; + machineSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema; + machineOverridableSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema; + windowSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema; + resourceSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema; } - - delete allSettings.patternProperties[this.overridePropertyPattern]; - delete applicationSettings.patternProperties[this.overridePropertyPattern]; - delete machineSettings.patternProperties[this.overridePropertyPattern]; - delete machineOverridableSettings.patternProperties[this.overridePropertyPattern]; - delete windowSettings.patternProperties[this.overridePropertyPattern]; - delete resourceSettings.patternProperties[this.overridePropertyPattern]; - - this.overridePropertyPattern = this.computeOverridePropertyPattern(); - - allSettings.patternProperties[this.overridePropertyPattern] = patternProperties; - applicationSettings.patternProperties[this.overridePropertyPattern] = patternProperties; - machineSettings.patternProperties[this.overridePropertyPattern] = patternProperties; - machineOverridableSettings.patternProperties[this.overridePropertyPattern] = patternProperties; - windowSettings.patternProperties[this.overridePropertyPattern] = patternProperties; - resourceSettings.patternProperties[this.overridePropertyPattern] = patternProperties; - this._onDidSchemaChange.fire(); } - - private computeOverridePropertyPattern(): string { - return this.overrideIdentifiers.size > 0 ? OVERRIDE_PATTERN_WITH_SUBSTITUTION.replace('${0}', values(this.overrideIdentifiers).map(identifier => strings.createRegExp(identifier, false).source).join('|')) : OVERRIDE_PROPERTY; - } } const OVERRIDE_PROPERTY = '\\[.*\\]$'; -const OVERRIDE_PATTERN_WITH_SUBSTITUTION = '\\[(${0})\\]$'; export const OVERRIDE_PROPERTY_PATTERN = new RegExp(OVERRIDE_PROPERTY); export function getDefaultValue(type: string | string[] | undefined): any { From 06a9d488fb9a38171a4ea0bb3679b1a44125395e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 13 Jan 2020 15:46:19 +0100 Subject: [PATCH 210/315] fix compilation --- src/vs/platform/configuration/common/configurationRegistry.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 728bb7e66bfec..bad49e688da37 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -10,6 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import * as types from 'vs/base/common/types'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { values } from 'vs/base/common/map'; export const Extensions = { Configuration: 'base.contributions.configuration' @@ -390,7 +391,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { } private updateOverridePropertyPatternKey(): void { - for (const overrideIdentifier of this.overrideIdentifiers) { + for (const overrideIdentifier of values(this.overrideIdentifiers)) { const overrideIdentifierProperty = `[${overrideIdentifier}]`; const resourceLanguagePropertiesSchema: IJSONSchema = { type: 'object', From ad41028d8f23204093a994edd00fac2d3d33499b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 13 Jan 2020 15:49:15 +0100 Subject: [PATCH 211/315] debt - drop untyped events in favor of typed (text files) --- .../api/browser/mainThreadDocuments.ts | 6 +- .../browser/parts/editor/editorStatus.ts | 2 +- .../experiments/common/experimentService.ts | 2 +- .../browser/editors/fileEditorTracker.ts | 4 +- .../editors/textFileSaveErrorHandler.ts | 4 +- .../files/common/editors/fileEditorInput.ts | 10 +- .../browser/languageSurveys.contribution.ts | 2 +- .../browser/telemetry.contribution.ts | 4 +- .../textfile/common/textFileEditorModel.ts | 44 +++++--- .../common/textFileEditorModelManager.ts | 100 +++++++----------- .../services/textfile/common/textfiles.ts | 33 +++--- .../textfile/test/textFileEditorModel.test.ts | 50 ++++----- .../test/textFileEditorModelManager.test.ts | 14 +-- .../api/mainThreadDocumentsAndEditors.test.ts | 6 +- .../api/mainThreadEditors.test.ts | 6 +- 15 files changed, 133 insertions(+), 154 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index 3568bd30704af..9189cebfb1e8c 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -104,17 +104,17 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { this._toDispose.add(this._modelReferenceCollection); this._toDispose.add(modelService.onModelModeChanged(this._onModelModeChanged, this)); - this._toDispose.add(textFileService.models.onModelSaved(m => { + this._toDispose.add(textFileService.models.onDidSave(m => { if (this._shouldHandleFileEvent(m.resource)) { this._proxy.$acceptModelSaved(m.resource); } })); - this._toDispose.add(textFileService.models.onModelReverted(m => { + this._toDispose.add(textFileService.models.onDidRevert(m => { if (this._shouldHandleFileEvent(m.resource)) { this._proxy.$acceptDirtyStateChanged(m.resource, false); } })); - this._toDispose.add(textFileService.models.onModelDirty(m => { + this._toDispose.add(textFileService.models.onDidChangeDirty(m => { if (this._shouldHandleFileEvent(m.resource)) { this._proxy.$acceptDirtyStateChanged(m.resource, true); } diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index d832143cc0427..90644f3691f20 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -314,7 +314,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { private registerListeners(): void { this._register(this.editorService.onDidActiveEditorChange(() => this.updateStatusBar())); this._register(this.untitledTextEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r))); - this._register(this.textFileService.models.onModelEncodingChanged(m => this.onResourceEncodingChange((m.resource)))); + this._register(this.textFileService.models.onDidChangeEncoding(m => this.onResourceEncodingChange((m.resource)))); this._register(TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange())); } diff --git a/src/vs/workbench/contrib/experiments/common/experimentService.ts b/src/vs/workbench/contrib/experiments/common/experimentService.ts index f0eccb33be31d..65937cf4908c4 100644 --- a/src/vs/workbench/contrib/experiments/common/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/common/experimentService.ts @@ -448,7 +448,7 @@ export class ExperimentService extends Disposable implements IExperimentService } }, 250)); - const onSaveHandler = this._register(this.textFileService.models.onModelSaved(m => onModelsSavedWorker.work(m))); + const onSaveHandler = this._register(this.textFileService.models.onDidSave(m => onModelsSavedWorker.work(m))); return ExperimentState.Evaluating; }); } diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index 183bae2faa913..591e7bf7b8043 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -61,8 +61,8 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut this._register(this.fileService.onFileChanges(e => this.onFileChanges(e))); // Ensure dirty text file and untitled models are always opened as editors - this._register(this.textFileService.models.onModelDirty(m => this.ensureDirtyFilesAreOpenedWorker.work(m.resource))); - this._register(this.textFileService.models.onModelSaveError(m => this.ensureDirtyFilesAreOpenedWorker.work(m.resource))); + this._register(this.textFileService.models.onDidChangeDirty(m => this.ensureDirtyFilesAreOpenedWorker.work(m.resource))); + this._register(this.textFileService.models.onDidSaveError(m => this.ensureDirtyFilesAreOpenedWorker.work(m.resource))); this._register(this.untitledTextEditorService.onDidChangeDirty(r => this.ensureDirtyFilesAreOpenedWorker.work(r))); // Out of workspace file watchers diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts index e94d9a4d23df4..7a0d530facb3a 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts @@ -71,8 +71,8 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa } private registerListeners(): void { - this._register(this.textFileService.models.onModelSaved(m => this.onFileSavedOrReverted(m.resource))); - this._register(this.textFileService.models.onModelReverted(m => this.onFileSavedOrReverted(m.resource))); + this._register(this.textFileService.models.onDidSave(m => this.onFileSavedOrReverted(m.resource))); + this._register(this.textFileService.models.onDidRevert(m => this.onFileSavedOrReverted(m.resource))); this._register(this.editorService.onDidActiveEditorChange(() => this.onActiveEditorChanged())); } diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index f6f2db7029299..59af8f75b0045 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -70,15 +70,15 @@ export class FileEditorInput extends TextEditorInput implements IFileEditorInput private registerListeners(): void { // Dirty changes - this._register(this.textFileService.models.onModelDirty(m => this.onDirtyStateChange(m))); - this._register(this.textFileService.models.onModelSaveError(m => this.onDirtyStateChange(m))); - this._register(this.textFileService.models.onModelSaved(m => this.onDirtyStateChange(m))); - this._register(this.textFileService.models.onModelReverted(m => this.onDirtyStateChange(m))); + this._register(this.textFileService.models.onDidChangeDirty(m => this.onDirtyStateChange(m))); + this._register(this.textFileService.models.onDidSaveError(m => this.onDirtyStateChange(m))); + this._register(this.textFileService.models.onDidSave(m => this.onDirtyStateChange(m))); + this._register(this.textFileService.models.onDidRevert(m => this.onDirtyStateChange(m))); // Label changes this._register(this.labelService.onDidChangeFormatters(() => FileEditorInput.MEMOIZER.clear())); this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(() => FileEditorInput.MEMOIZER.clear())); - this._register(this.textFileService.models.onModelOrphanedChanged(model => this.onModelOrphanedChanged(model))); + this._register(this.textFileService.models.onDidChangeOrphaned(model => this.onModelOrphanedChanged(model))); } private onDirtyStateChange(model: ITextFileEditorModel): void { diff --git a/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts b/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts index 738a131dd1872..4cba8e2b8184c 100644 --- a/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts +++ b/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts @@ -62,7 +62,7 @@ class LanguageSurvey extends Disposable { }); }, 250)); - this._register(textFileService.models.onModelSaved(m => onModelsSavedWorker.work(m))); + this._register(textFileService.models.onDidSave(m => onModelsSavedWorker.work(m))); } const lastSessionDate = storageService.get(LAST_SESSION_DATE_KEY, StorageScope.GLOBAL, new Date(0).toDateString()); diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index 0682ad8239a8e..de4f62d26e9fc 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -124,8 +124,8 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr this._register(configurationTelemetry(telemetryService, configurationService)); // Files Telemetry - this._register(textFileService.models.onModelLoaded(m => this.onTextFileModelLoaded(m))); - this._register(textFileService.models.onModelSaved(m => this.onTextFileModelSaved(m))); + this._register(textFileService.models.onDidLoad(m => this.onTextFileModelLoaded(m))); + this._register(textFileService.models.onDidSave(m => this.onTextFileModelSaved(m))); // Lifecycle this._register(lifecycleService.onShutdown(() => this.dispose())); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 8293240037e26..bce63d972b3e6 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -8,7 +8,7 @@ import { Emitter } from 'vs/base/common/event'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { URI } from 'vs/base/common/uri'; import { assertIsDefined } from 'vs/base/common/types'; -import { ITextFileService, ModelState, ITextFileEditorModel, ISaveErrorHandler, ISaveParticipant, StateChange, ITextFileStreamContent, ILoadOptions, IResolvedTextFileEditorModel, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ModelState, ITextFileEditorModel, ISaveErrorHandler, ISaveParticipant, ITextFileStreamContent, ILoadOptions, IResolvedTextFileEditorModel, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { EncodingMode, IRevertOptions, SaveReason } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; @@ -44,15 +44,34 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private static saveParticipant: ISaveParticipant | null; static setSaveParticipant(handler: ISaveParticipant | null): void { TextFileEditorModel.saveParticipant = handler; } + //#region Events + private readonly _onDidChangeContent = this._register(new Emitter()); readonly onDidChangeContent = this._onDidChangeContent.event; - private readonly _onDidChangeState = this._register(new Emitter()); - readonly onDidChangeState = this._onDidChangeState.event; + private readonly _onDidLoad = this._register(new Emitter()); + readonly onDidLoad = this._onDidLoad.event; + + private readonly _onDidSaveError = this._register(new Emitter()); + readonly onDidSaveError = this._onDidSaveError.event; + + private readonly _onDidSave = this._register(new Emitter()); + readonly onDidSave = this._onDidSave.event; + + private readonly _onDidRevert = this._register(new Emitter()); + readonly onDidRevert = this._onDidRevert.event; + + private readonly _onDidChangeEncoding = this._register(new Emitter()); + readonly onDidChangeEncoding = this._onDidChangeEncoding.event; + + private readonly _onDidChangeOrphaned = this._register(new Emitter()); + readonly onDidChangeOrphaned = this._onDidChangeOrphaned.event; private readonly _onDidChangeDirty = this._register(new Emitter()); readonly onDidChangeDirty = this._onDidChangeDirty.event; + //#endregion + readonly capabilities = 0; private contentEncoding: string | undefined; // encoding as reported from disk @@ -147,7 +166,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private setOrphaned(orphaned: boolean): void { if (this.inOrphanMode !== orphaned) { this.inOrphanMode = orphaned; - this._onDidChangeState.fire(StateChange.ORPHANED_CHANGE); + this._onDidChangeOrphaned.fire(); } } @@ -215,7 +234,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } // Emit file change event - this._onDidChangeState.fire(StateChange.REVERTED); + this._onDidRevert.fire(); // Emit dirty change event if (wasDirty) { @@ -372,7 +391,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil if (this.preferredEncoding) { this.updatePreferredEncoding(this.contentEncoding); // make sure to reflect the real encoding of the file (never out of sync) } else if (oldEncoding !== this.contentEncoding) { - this._onDidChangeState.fire(StateChange.ENCODING); + this._onDidChangeEncoding.fire(); } // Update Existing Model @@ -386,7 +405,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } // Emit as event - this._onDidChangeState.fire(StateChange.LOADED); + this._onDidLoad.fire(); return this; } @@ -464,7 +483,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Emit event if (wasDirty) { - this._onDidChangeState.fire(StateChange.REVERTED); + this._onDidRevert.fire(); this._onDidChangeDirty.fire(); } } else { @@ -494,7 +513,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Emit as Event if we turned dirty if (!wasDirty) { - this._onDidChangeState.fire(StateChange.DIRTY); this._onDidChangeDirty.fire(); } } @@ -657,7 +675,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.updateLastResolvedFileStat(stat); // Emit Events - this._onDidChangeState.fire(StateChange.SAVED); + this._onDidSave.fire(); this._onDidChangeDirty.fire(); }, error => { this.logService.error(`[text file model] doSave(${versionId}) - exit - resulted in a save error: ${error.toString()}`, this.resource.toString()); @@ -674,7 +692,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.onSaveError(error); // Emit as event - this._onDidChangeState.fire(StateChange.SAVE_ERROR); + this._onDidSaveError.fire(); })); })); } @@ -695,7 +713,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.updateLastResolvedFileStat(stat); // Emit File Saved Event - this._onDidChangeState.fire(StateChange.SAVED); + this._onDidSave.fire(); }, error => onUnexpectedError(error) /* just log any error but do not notify the user since the file was not dirty */)); } @@ -850,7 +868,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.preferredEncoding = encoding; // Emit - this._onDidChangeState.fire(StateChange.ENCODING); + this._onDidChangeEncoding.fire(); } private isNewEncoding(encoding: string | undefined): boolean { diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 95f1ae3322374..f772a62df09a5 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -6,8 +6,8 @@ import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { dispose, IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { ITextFileEditorModel, ITextFileEditorModelManager, StateChange, IModelLoadOrCreateOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { dispose, IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { ITextFileEditorModel, ITextFileEditorModelManager, IModelLoadOrCreateOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ResourceMap } from 'vs/base/common/map'; @@ -18,31 +18,30 @@ import { onUnexpectedError } from 'vs/base/common/errors'; export class TextFileEditorModelManager extends Disposable implements ITextFileEditorModelManager { - private readonly _onModelLoaded = this._register(new Emitter()); - readonly onModelLoaded = this._onModelLoaded.event; + private readonly _onDidLoad = this._register(new Emitter()); + readonly onDidLoad = this._onDidLoad.event; - private readonly _onModelDirty = this._register(new Emitter()); - readonly onModelDirty = this._onModelDirty.event; + private readonly _onDidChangeDirty = this._register(new Emitter()); + readonly onDidChangeDirty = this._onDidChangeDirty.event; - private readonly _onModelSaveError = this._register(new Emitter()); - readonly onModelSaveError = this._onModelSaveError.event; + private readonly _onDidSaveError = this._register(new Emitter()); + readonly onDidSaveError = this._onDidSaveError.event; - private readonly _onModelSaved = this._register(new Emitter()); - readonly onModelSaved = this._onModelSaved.event; + private readonly _onDidSave = this._register(new Emitter()); + readonly onDidSave = this._onDidSave.event; - private readonly _onModelReverted = this._register(new Emitter()); - readonly onModelReverted = this._onModelReverted.event; + private readonly _onDidRevert = this._register(new Emitter()); + readonly onDidRevert = this._onDidRevert.event; - private readonly _onModelEncodingChanged = this._register(new Emitter()); - readonly onModelEncodingChanged = this._onModelEncodingChanged.event; + private readonly _onDidChangeEncoding = this._register(new Emitter()); + readonly onDidChangeEncoding = this._onDidChangeEncoding.event; - private readonly _onModelOrphanedChanged = this._register(new Emitter()); - readonly onModelOrphanedChanged = this._onModelOrphanedChanged.event; + private readonly _onDidChangeOrphaned = this._register(new Emitter()); + readonly onDidChangeOrphaned = this._onDidChangeOrphaned.event; - private readonly mapResourceToDisposeListener = new ResourceMap(); - private readonly mapResourceToStateChangeListener = new ResourceMap(); - private readonly mapResourceToModelContentChangeListener = new ResourceMap(); private readonly mapResourceToModel = new ResourceMap(); + private readonly mapResourceToModelListeners = new ResourceMap(); + private readonly mapResourceToDisposeListener = new ResourceMap(); private readonly mapResourceToPendingModelLoaders = new ResourceMap>(); private readonly modelLoadQueue = new ResourceQueue(); @@ -128,32 +127,17 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE const newModel = model = this.instantiationService.createInstance(TextFileEditorModel, resource, options ? options.encoding : undefined, options ? options.mode : undefined); modelPromise = model.load(options); - // Install state change listener - this.mapResourceToStateChangeListener.set(resource, model.onDidChangeState(state => { - switch (state) { - case StateChange.LOADED: - this._onModelLoaded.fire(newModel); - break; - case StateChange.DIRTY: - this._onModelDirty.fire(newModel); - break; - case StateChange.SAVE_ERROR: - this._onModelSaveError.fire(newModel); - break; - case StateChange.SAVED: - this._onModelSaved.fire(newModel); - break; - case StateChange.REVERTED: - this._onModelReverted.fire(newModel); - break; - case StateChange.ENCODING: - this._onModelEncodingChanged.fire(newModel); - break; - case StateChange.ORPHANED_CHANGE: - this._onModelOrphanedChanged.fire(newModel); - break; - } - })); + // Install model listeners + const listeners = new DisposableStore(); + listeners.add(model.onDidLoad(() => this._onDidLoad.fire(newModel))); + listeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(newModel))); + listeners.add(model.onDidSaveError(() => this._onDidSaveError.fire(newModel))); + listeners.add(model.onDidSave(() => this._onDidSave.fire(newModel))); + listeners.add(model.onDidRevert(() => this._onDidRevert.fire(newModel))); + listeners.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(newModel))); + listeners.add(model.onDidChangeOrphaned(() => this._onDidChangeOrphaned.fire(newModel))); + + this.mapResourceToModelListeners.set(resource, listeners); } // Store pending loads to avoid race conditions @@ -167,7 +151,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // Model can be dirty if a backup was restored, so we make sure to have this event delivered if (resolvedModel.isDirty()) { - this._onModelDirty.fire(resolvedModel); + this._onDidChangeDirty.fire(resolvedModel); } // Remove from pending loads @@ -236,16 +220,10 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE this.mapResourceToDisposeListener.delete(resource); } - const stateChangeListener = this.mapResourceToStateChangeListener.get(resource); - if (stateChangeListener) { - dispose(stateChangeListener); - this.mapResourceToStateChangeListener.delete(resource); - } - - const modelContentChangeListener = this.mapResourceToModelContentChangeListener.get(resource); - if (modelContentChangeListener) { - dispose(modelContentChangeListener); - this.mapResourceToModelContentChangeListener.delete(resource); + const modelListener = this.mapResourceToModelListeners.get(resource); + if (modelListener) { + dispose(modelListener); + this.mapResourceToModelListeners.delete(resource); } } @@ -259,13 +237,9 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE this.mapResourceToDisposeListener.forEach(l => l.dispose()); this.mapResourceToDisposeListener.clear(); - // dispose the state change listeners - this.mapResourceToStateChangeListener.forEach(l => l.dispose()); - this.mapResourceToStateChangeListener.clear(); - - // dispose the model content change listeners - this.mapResourceToModelContentChangeListener.forEach(l => l.dispose()); - this.mapResourceToModelContentChangeListener.clear(); + // dispose the model change listeners + this.mapResourceToModelListeners.forEach(l => l.dispose()); + this.mapResourceToModelListeners.clear(); } disposeModel(model: TextFileEditorModel): void { diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 561aa04380bc5..f32f2fd796c84 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -275,16 +275,6 @@ export const enum ModelState { ERROR } -export const enum StateChange { - LOADED, - DIRTY, - SAVE_ERROR, - SAVED, - REVERTED, - ENCODING, - ORPHANED_CHANGE -} - export interface ITextFileOperationResult { results: IResult[]; } @@ -362,14 +352,13 @@ export interface IModelLoadOrCreateOptions { export interface ITextFileEditorModelManager { - readonly onModelEncodingChanged: Event; - readonly onModelOrphanedChanged: Event; - - readonly onModelLoaded: Event; - readonly onModelDirty: Event; - readonly onModelSaveError: Event; - readonly onModelSaved: Event; - readonly onModelReverted: Event; + readonly onDidLoad: Event; + readonly onDidChangeDirty: Event; + readonly onDidSaveError: Event; + readonly onDidSave: Event; + readonly onDidRevert: Event; + readonly onDidChangeEncoding: Event; + readonly onDidChangeOrphaned: Event; get(resource: URI): ITextFileEditorModel | undefined; @@ -407,7 +396,13 @@ export interface ILoadOptions { export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport, IModeSupport, IWorkingCopy { - readonly onDidChangeState: Event; + readonly onDidChangeContent: Event; + readonly onDidLoad: Event; + readonly onDidSaveError: Event; + readonly onDidSave: Event; + readonly onDidRevert: Event; + readonly onDidChangeEncoding: Event; + readonly onDidChangeOrphaned: Event; hasState(state: ModelState): boolean; diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts index a3b647521f7f7..4fac27a7c43c2 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { EncodingMode } from 'vs/workbench/common/editor'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { ITextFileService, ModelState, StateChange, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ModelState, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { workbenchInstantiationService, TestTextFileService, createFileInput, TestFileService } from 'vs/workbench/test/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; @@ -95,10 +95,8 @@ suite('Files - TextFileEditorModel', () => { assert.equal(accessor.workingCopyService.isDirty(model.resource), true); let savedEvent = false; - model.onDidChangeState(e => { - if (e === StateChange.SAVED) { - savedEvent = true; - } + model.onDidSave(e => { + savedEvent = true; }); let workingCopyEvent = false; @@ -132,10 +130,8 @@ suite('Files - TextFileEditorModel', () => { await model.load(); let savedEvent = false; - model.onDidChangeState(e => { - if (e === StateChange.SAVED) { - savedEvent = true; - } + model.onDidSave(e => { + savedEvent = true; }); let workingCopyEvent = false; @@ -206,8 +202,12 @@ suite('Files - TextFileEditorModel', () => { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index.txt'), 'utf8', undefined); assert.ok(model.hasState(ModelState.SAVED)); - model.onDidChangeState(e => { - assert.ok(e !== StateChange.DIRTY && e !== StateChange.SAVED); + model.onDidSave(e => { + assert.fail(); + }); + + model.onDidChangeDirty(e => { + assert.fail(); }); await model.load(); @@ -234,10 +234,8 @@ suite('Files - TextFileEditorModel', () => { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - model.onDidChangeState(e => { - if (e === StateChange.REVERTED) { - eventCounter++; - } + model.onDidRevert(e => { + eventCounter++; }); let workingCopyEvent = false; @@ -271,10 +269,8 @@ suite('Files - TextFileEditorModel', () => { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - model.onDidChangeState(e => { - if (e === StateChange.REVERTED) { - eventCounter++; - } + model.onDidRevert(e => { + eventCounter++; }); let workingCopyEvent = false; @@ -331,10 +327,8 @@ suite('Files - TextFileEditorModel', () => { await model.revert({ soft: true }); assert.ok(!model.isDirty()); - model.onDidChangeState(e => { - if (e === StateChange.DIRTY) { - eventCounter++; - } + model.onDidChangeDirty(e => { + eventCounter++; }); let workingCopyEvent = false; @@ -416,12 +410,10 @@ suite('Files - TextFileEditorModel', () => { let eventCounter = 0; const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - model.onDidChangeState(e => { - if (e === StateChange.SAVED) { - assert.equal(snapshotToString(model.createSnapshot()!), 'bar'); - assert.ok(!model.isDirty()); - eventCounter++; - } + model.onDidSave(e => { + assert.equal(snapshotToString(model.createSnapshot()!), 'bar'); + assert.ok(!model.isDirty()); + eventCounter++; }); TextFileEditorModel.setSaveParticipant({ diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts index 4d5ee1fcee79a..b59f1e7b12ede 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts @@ -127,7 +127,7 @@ suite('Files - TextFileEditorModelManager', () => { model3.dispose(); }); - test('events', async function () { + test('pasero events', async function () { const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager); const resource1 = toResource.call(this, '/path/index.txt'); @@ -139,31 +139,31 @@ suite('Files - TextFileEditorModelManager', () => { let savedCounter = 0; let encodingCounter = 0; - manager.onModelLoaded(model => { + manager.onDidLoad(model => { if (model.resource.toString() === resource1.toString()) { loadedCounter++; } }); - manager.onModelDirty(model => { + manager.onDidChangeDirty(model => { if (model.resource.toString() === resource1.toString()) { dirtyCounter++; } }); - manager.onModelReverted(model => { + manager.onDidRevert(model => { if (model.resource.toString() === resource1.toString()) { revertedCounter++; } }); - manager.onModelSaved(model => { + manager.onDidSave(model => { if (model.resource.toString() === resource1.toString()) { savedCounter++; } }); - manager.onModelEncodingChanged(model => { + manager.onDidChangeEncoding(model => { if (model.resource.toString() === resource1.toString()) { encodingCounter++; } @@ -189,7 +189,7 @@ suite('Files - TextFileEditorModelManager', () => { model2.dispose(); await model1.revert(); - assert.equal(dirtyCounter, 2); + assert.equal(dirtyCounter, 4); assert.equal(revertedCounter, 1); assert.equal(savedCounter, 1); assert.equal(encodingCounter, 2); diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts index 78c73141ae01f..12d00d9f7330d 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts @@ -49,9 +49,9 @@ suite('MainThreadDocumentsAndEditors', () => { textFileService = new class extends mock() { isDirty() { return false; } models = { - onModelSaved: Event.None, - onModelReverted: Event.None, - onModelDirty: Event.None, + onDidSave: Event.None, + onDidRevert: Event.None, + onDidChangeDirty: Event.None }; }; const workbenchEditorService = new TestEditorService(); diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts index 84705671d3f80..8e7888ba8ce67 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts @@ -73,9 +73,9 @@ suite('MainThreadEditors', () => { return Promise.resolve(Object.create(null)); } models = { - onModelSaved: Event.None, - onModelReverted: Event.None, - onModelDirty: Event.None, + onDidSave: Event.None, + onDidRevert: Event.None, + onDidChangeDirty: Event.None }; }; const workbenchEditorService = new TestEditorService(); From 420fa189244b1e40d2299265592f79fdee6cfba4 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 13 Jan 2020 15:54:10 +0100 Subject: [PATCH 212/315] :lipstick: --- extensions/git/src/fileSystemProvider.ts | 31 +++++++++++++----------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/extensions/git/src/fileSystemProvider.ts b/extensions/git/src/fileSystemProvider.ts index 9687dbf59de23..2799b95bda966 100644 --- a/extensions/git/src/fileSystemProvider.ts +++ b/extensions/git/src/fileSystemProvider.ts @@ -18,6 +18,21 @@ interface CacheRow { const THREE_MINUTES = 1000 * 60 * 3; const FIVE_MINUTES = 1000 * 60 * 5; +function sanitizeRef(ref: string, path: string, repository: Repository): string { + if (ref === '~') { + const fileUri = Uri.file(path); + const uriString = fileUri.toString(); + const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString); + return indexStatus ? '' : 'HEAD'; + } + + if (/^~\d$/.test(ref)) { + return `:${ref[1]}`; + } + + return ref; +} + export class GitFileSystemProvider implements FileSystemProvider { private _onDidChangeFile = new EventEmitter(); @@ -126,7 +141,7 @@ export class GitFileSystemProvider implements FileSystemProvider { let size = 0; try { - const details = await repository.getObjectDetails(this.fixRef(ref, path, repository), path); + const details = await repository.getObjectDetails(sanitizeRef(ref, path, repository), path); size = details.size; } catch { // noop @@ -173,7 +188,7 @@ export class GitFileSystemProvider implements FileSystemProvider { this.cache.set(uri.toString(), cacheValue); try { - return await repository.buffer(this.fixRef(ref, path, repository), path); + return await repository.buffer(sanitizeRef(ref, path, repository), path); } catch (err) { return new Uint8Array(0); } @@ -194,16 +209,4 @@ export class GitFileSystemProvider implements FileSystemProvider { dispose(): void { this.disposables.forEach(d => d.dispose()); } - - private fixRef(ref: string, path: string, repository: Repository): string { - if (ref === '~') { - const fileUri = Uri.file(path); - const uriString = fileUri.toString(); - const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString); - return indexStatus ? '' : 'HEAD'; - } else if (/^~\d$/.test(ref)) { - return `:${ref[1]}`; - } - return ref; - } } From 76c72ffc947fa8dfe9180409f3b204541e05b10b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 13 Jan 2020 16:01:05 +0100 Subject: [PATCH 213/315] telemetry - restore back load/save reason for files --- .../api/browser/mainThreadDocuments.ts | 6 +++--- .../experiments/common/experimentService.ts | 2 +- .../editors/textFileSaveErrorHandler.ts | 2 +- .../files/common/editors/fileEditorInput.ts | 2 +- .../browser/languageSurveys.contribution.ts | 2 +- .../browser/telemetry.contribution.ts | 18 +++++++++--------- .../textfile/common/textFileEditorModel.ts | 16 ++++++++-------- .../common/textFileEditorModelManager.ts | 10 +++++----- .../services/textfile/common/textfiles.ts | 18 ++++++++++++++---- .../test/textFileEditorModelManager.test.ts | 4 ++-- 10 files changed, 45 insertions(+), 35 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index 9189cebfb1e8c..8b624063795da 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -104,9 +104,9 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { this._toDispose.add(this._modelReferenceCollection); this._toDispose.add(modelService.onModelModeChanged(this._onModelModeChanged, this)); - this._toDispose.add(textFileService.models.onDidSave(m => { - if (this._shouldHandleFileEvent(m.resource)) { - this._proxy.$acceptModelSaved(m.resource); + this._toDispose.add(textFileService.models.onDidSave(e => { + if (this._shouldHandleFileEvent(e.model.resource)) { + this._proxy.$acceptModelSaved(e.model.resource); } })); this._toDispose.add(textFileService.models.onDidRevert(m => { diff --git a/src/vs/workbench/contrib/experiments/common/experimentService.ts b/src/vs/workbench/contrib/experiments/common/experimentService.ts index 65937cf4908c4..226b389f3f5e7 100644 --- a/src/vs/workbench/contrib/experiments/common/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/common/experimentService.ts @@ -448,7 +448,7 @@ export class ExperimentService extends Disposable implements IExperimentService } }, 250)); - const onSaveHandler = this._register(this.textFileService.models.onDidSave(m => onModelsSavedWorker.work(m))); + const onSaveHandler = this._register(this.textFileService.models.onDidSave(e => onModelsSavedWorker.work(e.model))); return ExperimentState.Evaluating; }); } diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts index 7a0d530facb3a..02b32c67006c7 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts @@ -71,7 +71,7 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa } private registerListeners(): void { - this._register(this.textFileService.models.onDidSave(m => this.onFileSavedOrReverted(m.resource))); + this._register(this.textFileService.models.onDidSave(e => this.onFileSavedOrReverted(e.model.resource))); this._register(this.textFileService.models.onDidRevert(m => this.onFileSavedOrReverted(m.resource))); this._register(this.editorService.onDidActiveEditorChange(() => this.onActiveEditorChanged())); } diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index 59af8f75b0045..e21bfa9fec346 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -72,7 +72,7 @@ export class FileEditorInput extends TextEditorInput implements IFileEditorInput // Dirty changes this._register(this.textFileService.models.onDidChangeDirty(m => this.onDirtyStateChange(m))); this._register(this.textFileService.models.onDidSaveError(m => this.onDirtyStateChange(m))); - this._register(this.textFileService.models.onDidSave(m => this.onDirtyStateChange(m))); + this._register(this.textFileService.models.onDidSave(e => this.onDirtyStateChange(e.model))); this._register(this.textFileService.models.onDidRevert(m => this.onDirtyStateChange(m))); // Label changes diff --git a/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts b/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts index 4cba8e2b8184c..e196f2c4ecd36 100644 --- a/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts +++ b/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts @@ -62,7 +62,7 @@ class LanguageSurvey extends Disposable { }); }, 250)); - this._register(textFileService.models.onDidSave(m => onModelsSavedWorker.work(m))); + this._register(textFileService.models.onDidSave(e => onModelsSavedWorker.work(e.model))); } const lastSessionDate = storageService.get(LAST_SESSION_DATE_KEY, StorageScope.GLOBAL, new Date(0).toDateString()); diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index de4f62d26e9fc..a01ba225296d0 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -19,7 +19,7 @@ import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry'; import { configurationTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { ITextFileService, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ITextFileModelSaveEvent, ITextFileModelLoadEvent } from 'vs/workbench/services/textfile/common/textfiles'; import { extname, basename, isEqual, isEqualOrParent, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; @@ -124,15 +124,15 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr this._register(configurationTelemetry(telemetryService, configurationService)); // Files Telemetry - this._register(textFileService.models.onDidLoad(m => this.onTextFileModelLoaded(m))); - this._register(textFileService.models.onDidSave(m => this.onTextFileModelSaved(m))); + this._register(textFileService.models.onDidLoad(e => this.onTextFileModelLoaded(e))); + this._register(textFileService.models.onDidSave(e => this.onTextFileModelSaved(e))); // Lifecycle this._register(lifecycleService.onShutdown(() => this.dispose())); } - private onTextFileModelLoaded(model: ITextFileEditorModel): void { - const settingsType = this.getTypeIfSettings(model.resource); + private onTextFileModelLoaded(e: ITextFileModelLoadEvent): void { + const settingsType = this.getTypeIfSettings(e.model.resource); if (settingsType) { type SettingsReadClassification = { settingsType: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; @@ -142,12 +142,12 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr } else { type FileGetClassification = {} & FileTelemetryDataFragment; - this.telemetryService.publicLog2('fileGet', this.getTelemetryData(model.resource)); + this.telemetryService.publicLog2('fileGet', this.getTelemetryData(e.model.resource, e.reason)); } } - private onTextFileModelSaved(model: ITextFileEditorModel): void { - const settingsType = this.getTypeIfSettings(model.resource); + private onTextFileModelSaved(e: ITextFileModelSaveEvent): void { + const settingsType = this.getTypeIfSettings(e.model.resource); if (settingsType) { type SettingsWrittenClassification = { settingsType: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; @@ -155,7 +155,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr this.telemetryService.publicLog2<{ settingsType: string }, SettingsWrittenClassification>('settingsWritten', { settingsType }); // Do not log write to user settings.json and .vscode folder as a filePUT event as it ruins our JSON usage data } else { type FilePutClassfication = {} & FileTelemetryDataFragment; - this.telemetryService.publicLog2('filePUT', this.getTelemetryData(model.resource)); + this.telemetryService.publicLog2('filePUT', this.getTelemetryData(e.model.resource, e.reason)); } } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index bce63d972b3e6..7abf65e410c9b 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -8,7 +8,7 @@ import { Emitter } from 'vs/base/common/event'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { URI } from 'vs/base/common/uri'; import { assertIsDefined } from 'vs/base/common/types'; -import { ITextFileService, ModelState, ITextFileEditorModel, ISaveErrorHandler, ISaveParticipant, ITextFileStreamContent, ILoadOptions, IResolvedTextFileEditorModel, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ModelState, ITextFileEditorModel, ISaveErrorHandler, ISaveParticipant, ITextFileStreamContent, ILoadOptions, IResolvedTextFileEditorModel, ITextFileSaveOptions, LoadReason } from 'vs/workbench/services/textfile/common/textfiles'; import { EncodingMode, IRevertOptions, SaveReason } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; @@ -49,13 +49,13 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private readonly _onDidChangeContent = this._register(new Emitter()); readonly onDidChangeContent = this._onDidChangeContent.event; - private readonly _onDidLoad = this._register(new Emitter()); + private readonly _onDidLoad = this._register(new Emitter()); readonly onDidLoad = this._onDidLoad.event; private readonly _onDidSaveError = this._register(new Emitter()); readonly onDidSaveError = this._onDidSaveError.event; - private readonly _onDidSave = this._register(new Emitter()); + private readonly _onDidSave = this._register(new Emitter()); readonly onDidSave = this._onDidSave.event; private readonly _onDidRevert = this._register(new Emitter()); @@ -405,7 +405,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } // Emit as event - this._onDidLoad.fire(); + this._onDidLoad.fire(options?.reason ?? LoadReason.OTHER); return this; } @@ -637,7 +637,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // - the model is not in orphan mode (because in that case we know the file does not exist on disk) // - the model version did not change due to save participants running if (options.force && !this.dirty && !this.inOrphanMode && options.reason === SaveReason.EXPLICIT && versionId === newVersionId) { - return this.doTouch(newVersionId); + return this.doTouch(newVersionId, options.reason); } // update versionId with its new value (if pre-save changes happened) @@ -675,7 +675,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.updateLastResolvedFileStat(stat); // Emit Events - this._onDidSave.fire(); + this._onDidSave.fire(options.reason ?? SaveReason.EXPLICIT); this._onDidChangeDirty.fire(); }, error => { this.logService.error(`[text file model] doSave(${versionId}) - exit - resulted in a save error: ${error.toString()}`, this.resource.toString()); @@ -697,7 +697,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil })); } - private doTouch(versionId: number): Promise { + private doTouch(versionId: number, reason: SaveReason): Promise { if (!this.isResolved()) { return Promise.resolve(); } @@ -713,7 +713,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.updateLastResolvedFileStat(stat); // Emit File Saved Event - this._onDidSave.fire(); + this._onDidSave.fire(reason); }, error => onUnexpectedError(error) /* just log any error but do not notify the user since the file was not dirty */)); } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index f772a62df09a5..4fbc86a0d1ffc 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -7,7 +7,7 @@ import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { dispose, IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { ITextFileEditorModel, ITextFileEditorModelManager, IModelLoadOrCreateOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileEditorModel, ITextFileEditorModelManager, IModelLoadOrCreateOptions, ITextFileModelLoadEvent, ITextFileModelSaveEvent } from 'vs/workbench/services/textfile/common/textfiles'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ResourceMap } from 'vs/base/common/map'; @@ -18,7 +18,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; export class TextFileEditorModelManager extends Disposable implements ITextFileEditorModelManager { - private readonly _onDidLoad = this._register(new Emitter()); + private readonly _onDidLoad = this._register(new Emitter()); readonly onDidLoad = this._onDidLoad.event; private readonly _onDidChangeDirty = this._register(new Emitter()); @@ -27,7 +27,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE private readonly _onDidSaveError = this._register(new Emitter()); readonly onDidSaveError = this._onDidSaveError.event; - private readonly _onDidSave = this._register(new Emitter()); + private readonly _onDidSave = this._register(new Emitter()); readonly onDidSave = this._onDidSave.event; private readonly _onDidRevert = this._register(new Emitter()); @@ -129,10 +129,10 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // Install model listeners const listeners = new DisposableStore(); - listeners.add(model.onDidLoad(() => this._onDidLoad.fire(newModel))); + listeners.add(model.onDidLoad(reason => this._onDidLoad.fire({ model: newModel, reason }))); listeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(newModel))); listeners.add(model.onDidSaveError(() => this._onDidSaveError.fire(newModel))); - listeners.add(model.onDidSave(() => this._onDidSave.fire(newModel))); + listeners.add(model.onDidSave(reason => this._onDidSave.fire({ model: newModel, reason }))); listeners.add(model.onDidRevert(() => this._onDidRevert.fire(newModel))); listeners.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(newModel))); listeners.add(model.onDidChangeOrphaned(() => this._onDidChangeOrphaned.fire(newModel))); diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index f32f2fd796c84..89ce41bdbaa77 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -350,12 +350,22 @@ export interface IModelLoadOrCreateOptions { allowBinary?: boolean; } +export interface ITextFileModelSaveEvent { + model: ITextFileEditorModel; + reason: SaveReason; +} + +export interface ITextFileModelLoadEvent { + model: ITextFileEditorModel; + reason: LoadReason; +} + export interface ITextFileEditorModelManager { - readonly onDidLoad: Event; + readonly onDidLoad: Event; readonly onDidChangeDirty: Event; readonly onDidSaveError: Event; - readonly onDidSave: Event; + readonly onDidSave: Event; readonly onDidRevert: Event; readonly onDidChangeEncoding: Event; readonly onDidChangeOrphaned: Event; @@ -397,9 +407,9 @@ export interface ILoadOptions { export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport, IModeSupport, IWorkingCopy { readonly onDidChangeContent: Event; - readonly onDidLoad: Event; + readonly onDidLoad: Event; readonly onDidSaveError: Event; - readonly onDidSave: Event; + readonly onDidSave: Event; readonly onDidRevert: Event; readonly onDidChangeEncoding: Event; readonly onDidChangeOrphaned: Event; diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts index b59f1e7b12ede..3eab2ccf1aaf3 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts @@ -139,7 +139,7 @@ suite('Files - TextFileEditorModelManager', () => { let savedCounter = 0; let encodingCounter = 0; - manager.onDidLoad(model => { + manager.onDidLoad(({ model }) => { if (model.resource.toString() === resource1.toString()) { loadedCounter++; } @@ -157,7 +157,7 @@ suite('Files - TextFileEditorModelManager', () => { } }); - manager.onDidSave(model => { + manager.onDidSave(({ model }) => { if (model.resource.toString() === resource1.toString()) { savedCounter++; } From a62805844ef4b2c0bf22b069bcf136ac064feded Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 13 Jan 2020 07:45:08 -0800 Subject: [PATCH 214/315] Expose IExtHostTerminalService.getDefaultShellArgs internally Fixes #88280 --- src/vs/workbench/api/common/extHostTerminalService.ts | 6 ++++++ src/vs/workbench/api/node/extHostTerminalService.ts | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index f5635b83d23f5..6314faeadf4f7 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -32,6 +32,7 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape { createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal; attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): void; getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string; + getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string; } export const IExtHostTerminalService = createDecorator('IExtHostTerminalService'); @@ -321,6 +322,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ public abstract createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal; public abstract createTerminalFromOptions(options: vscode.TerminalOptions): vscode.Terminal; public abstract getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string; + public abstract getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string; public abstract $spawnExtHostProcess(id: number, shellLaunchConfigDto: IShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise; public abstract $requestAvailableShells(): Promise; public abstract $requestDefaultShellAndArgs(useAutomationShell: boolean): Promise; @@ -595,6 +597,10 @@ export class WorkerExtHostTerminalService extends BaseExtHostTerminalService { throw new Error('Not implemented'); } + public getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string { + throw new Error('Not implemented'); + } + public $spawnExtHostProcess(id: number, shellLaunchConfigDto: IShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise { throw new Error('Not implemented'); } diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 8d2f73a92635b..1eda346278690 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -78,7 +78,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { ); } - private _getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string { + public getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string { const fetchSetting = (key: string): { userValue: string | string[] | undefined, value: string | string[] | undefined, defaultValue: string | string[] | undefined } => { const setting = configProvider .getConfiguration(key.substr(0, key.lastIndexOf('.'))) @@ -137,7 +137,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { const configProvider = await this._extHostConfiguration.getConfigProvider(); if (!shellLaunchConfig.executable) { shellLaunchConfig.executable = this.getDefaultShell(false, configProvider); - shellLaunchConfig.args = this._getDefaultShellArgs(false, configProvider); + shellLaunchConfig.args = this.getDefaultShellArgs(false, configProvider); } else { if (this._variableResolver) { shellLaunchConfig.executable = this._variableResolver.resolve(this._lastActiveWorkspace, shellLaunchConfig.executable); @@ -208,7 +208,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { const configProvider = await this._extHostConfiguration.getConfigProvider(); return Promise.resolve({ shell: this.getDefaultShell(useAutomationShell, configProvider), - args: this._getDefaultShellArgs(useAutomationShell, configProvider) + args: this.getDefaultShellArgs(useAutomationShell, configProvider) }); } From 2a3dc1edace0e365e79019de0fd7c7f0eb10d51c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 13 Jan 2020 16:59:08 +0100 Subject: [PATCH 215/315] fix tests --- src/vs/workbench/api/browser/mainThreadDocuments.ts | 7 +------ .../textfile/test/textFileEditorModelManager.test.ts | 12 +++++++++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index 8b624063795da..7be61f10e1032 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -109,14 +109,9 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { this._proxy.$acceptModelSaved(e.model.resource); } })); - this._toDispose.add(textFileService.models.onDidRevert(m => { - if (this._shouldHandleFileEvent(m.resource)) { - this._proxy.$acceptDirtyStateChanged(m.resource, false); - } - })); this._toDispose.add(textFileService.models.onDidChangeDirty(m => { if (this._shouldHandleFileEvent(m.resource)) { - this._proxy.$acceptDirtyStateChanged(m.resource, true); + this._proxy.$acceptDirtyStateChanged(m.resource, m.isDirty()); } })); diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts index 3eab2ccf1aaf3..8a27ddcad4279 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts @@ -134,7 +134,8 @@ suite('Files - TextFileEditorModelManager', () => { const resource2 = toResource.call(this, '/path/other.txt'); let loadedCounter = 0; - let dirtyCounter = 0; + let gotDirtyCounter = 0; + let gotNonDirtyCounter = 0; let revertedCounter = 0; let savedCounter = 0; let encodingCounter = 0; @@ -147,7 +148,11 @@ suite('Files - TextFileEditorModelManager', () => { manager.onDidChangeDirty(model => { if (model.resource.toString() === resource1.toString()) { - dirtyCounter++; + if (model.isDirty()) { + gotDirtyCounter++; + } else { + gotNonDirtyCounter++; + } } }); @@ -189,7 +194,8 @@ suite('Files - TextFileEditorModelManager', () => { model2.dispose(); await model1.revert(); - assert.equal(dirtyCounter, 4); + assert.equal(gotDirtyCounter, 2); + assert.equal(gotNonDirtyCounter, 2); assert.equal(revertedCounter, 1); assert.equal(savedCounter, 1); assert.equal(encodingCounter, 2); From 08c04a487dd392561e8bf74bc9294135aaf7d8ba Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 13 Jan 2020 17:05:59 +0100 Subject: [PATCH 216/315] add accessibility provider for refactor preview, #88554 --- .../contrib/bulkEdit/browser/bulkEditPane.ts | 3 +- .../contrib/bulkEdit/browser/bulkEditTree.ts | 69 ++++++++++++++++++- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index c4456386b4f67..3f3d562d5d1b7 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -6,7 +6,7 @@ import 'vs/css!./bulkEdit'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { WorkspaceEdit } from 'vs/editor/common/modes'; -import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; +import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; import { FuzzyScore } from 'vs/base/common/filters'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; @@ -96,6 +96,7 @@ export class BulkEditPane extends ViewPane { [new TextEditElementRenderer(), this._instaService.createInstance(FileElementRenderer, resourceLabels)], this._instaService.createInstance(BulkEditDataSource), { + accessibilityProvider: this._instaService.createInstance(BulkEditAccessibilityProvider), identityProvider: new BulkEditIdentityProvider(), expandOnlyOnTwistieClick: true } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index 03746a0a7bcaf..188c05694422e 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -19,6 +19,7 @@ import { BulkFileOperations, BulkFileOperation, BulkFileOperationType, BulkTextE import { FileKind } from 'vs/platform/files/common/files'; import { localize } from 'vs/nls'; import { ILabelService } from 'vs/platform/label/common/label'; +import type { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; // --- VIEW MODEL @@ -106,6 +107,72 @@ export class BulkEditDataSource implements IAsyncDataSource { + + constructor(@ILabelService private readonly _labelService: ILabelService) { } + + getAriaLabel(element: BulkEditElement): string | null { + if (element instanceof FileElement) { + if (element.edit.textEdits.length > 0) { + if (element.edit.type & BulkFileOperationType.Rename && element.edit.newUri) { + return localize( + 'area.renameAndEdit', "Renaming {0} to {1}, also making text edits", + this._labelService.getUriLabel(element.edit.uri, { relative: true }), this._labelService.getUriLabel(element.edit.newUri, { relative: true }) + ); + + } else if (element.edit.type & BulkFileOperationType.Create) { + return localize( + 'area.createAndEdit', "Creating {0}, also making text edits", + this._labelService.getUriLabel(element.edit.uri, { relative: true }) + ); + + } else if (element.edit.type & BulkFileOperationType.Delete) { + return localize( + 'area.deleteAndEdit', "Deleting {0}, also making text edits", + this._labelService.getUriLabel(element.edit.uri, { relative: true }), + ); + } + } else { + if (element.edit.type & BulkFileOperationType.Rename && element.edit.newUri) { + return localize( + 'area.rename', "Renaming {0} to {1}", + this._labelService.getUriLabel(element.edit.uri, { relative: true }), this._labelService.getUriLabel(element.edit.newUri, { relative: true }) + ); + + } else if (element.edit.type & BulkFileOperationType.Create) { + return localize( + 'area.create', "Creating {0}", + this._labelService.getUriLabel(element.edit.uri, { relative: true }) + ); + + } else if (element.edit.type & BulkFileOperationType.Delete) { + return localize( + 'area.delete', "Deleting {0}", + this._labelService.getUriLabel(element.edit.uri, { relative: true }), + ); + } + } + } + + if (element instanceof TextEditElement) { + if (element.selecting.length > 0 && element.inserting.length > 0) { + // edit: replace + return localize('aria.replace', "line {0}, replacing {1} with {0}", element.edit.edit.range.startLineNumber, element.selecting, element.inserting); + } else if (element.selecting.length > 0 && element.inserting.length === 0) { + // edit: delete + return localize('aria.del', "line {0}, removing {1}", element.edit.edit.range.startLineNumber, element.selecting); + } else if (element.selecting.length === 0 && element.inserting.length > 0) { + // edit: insert + return localize('aria.insert', "line {0}, inserting {1}", element.edit.edit.range.startLineNumber, element.selecting); + } + } + + return null; + } +} + // --- IDENT export class BulkEditIdentityProvider implements IIdentityProvider { @@ -183,7 +250,7 @@ class FileElementTemplate { if (element.edit.type & BulkFileOperationType.Create) { this._details.innerText = localize('detail.create', "(creating)"); - } else if (element.edit.type & BulkFileOperationType.Create) { + } else if (element.edit.type & BulkFileOperationType.Delete) { this._details.innerText = localize('detail.del', "(deleting)"); } else { this._details.innerText = ''; From 9bd40be50e1fb343b4653e56cca498d313c9a543 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 13 Jan 2020 17:16:23 +0100 Subject: [PATCH 217/315] fix #88560 --- src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index 3f3d562d5d1b7..13e60a3218abe 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -98,7 +98,8 @@ export class BulkEditPane extends ViewPane { { accessibilityProvider: this._instaService.createInstance(BulkEditAccessibilityProvider), identityProvider: new BulkEditIdentityProvider(), - expandOnlyOnTwistieClick: true + expandOnlyOnTwistieClick: true, + multipleSelectionSupport: false } ); From 0fde639a9e0a80ba81d2bfc6d13e89ce4dc243a2 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 13 Jan 2020 17:35:53 +0100 Subject: [PATCH 218/315] message polish for #88552 --- src/vs/editor/contrib/rename/renameInputField.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/rename/renameInputField.ts b/src/vs/editor/contrib/rename/renameInputField.ts index f70ed3c15d1d6..dd036ba5df6f8 100644 --- a/src/vs/editor/contrib/rename/renameInputField.ts +++ b/src/vs/editor/contrib/rename/renameInputField.ts @@ -81,7 +81,7 @@ export class RenameInputField implements IContentWidget { const updateLabel = () => { const [accept, preview] = this._acceptKeybindings; this._keybindingService.lookupKeybinding(accept); - this._label!.innerText = localize('label', "Press {0} to Rename, {1} to Preview", this._keybindingService.lookupKeybinding(accept)?.getLabel(), this._keybindingService.lookupKeybinding(preview)?.getLabel()); + this._label!.innerText = localize('label', "{0} to Rename, {1} to Preview", this._keybindingService.lookupKeybinding(accept)?.getLabel(), this._keybindingService.lookupKeybinding(preview)?.getLabel()); }; updateLabel(); this._disposables.add(this._keybindingService.onDidUpdateKeybindings(updateLabel)); From 517e4391c2381838274acf428aaa4807c4465fa8 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 13 Jan 2020 16:06:55 +0100 Subject: [PATCH 219/315] readonly semantic highlighting --- .../src/modes/javascriptSemanticTokens.ts | 40 +++++++++++++++---- .../server/src/test/semanticTokens.test.ts | 34 ++++++++++++---- .../common/tokenClassificationRegistry.ts | 1 + 3 files changed, 61 insertions(+), 14 deletions(-) diff --git a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts index 7e4deafa262e7..fdf22b3cd7cdf 100644 --- a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts +++ b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts @@ -20,9 +20,10 @@ export function getSemanticTokens(jsLanguageService: ts.LanguageService, current if (node.kind === ts.SyntaxKind.Identifier) { const symbol = typeChecker.getSymbolAtLocation(node); if (symbol) { - const decl = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0]; - if (decl) { - let typeIdx = tokenFromDeclarationMapping[decl.kind]; + let typeIdx = classifySymbol(symbol); + + if (typeIdx !== undefined) { + let modifierSet = 0; if (node.parent) { const parentTypeIdx = tokenFromDeclarationMapping[node.parent.kind]; @@ -30,16 +31,19 @@ export function getSemanticTokens(jsLanguageService: ts.LanguageService, current modifierSet = TokenModifier.declaration; } } - const modifiers = ts.getCombinedModifierFlags(decl); + const decl = symbol.valueDeclaration; + const modifiers = decl ? ts.getCombinedModifierFlags(decl) : 0; + const nodeFlags = decl ? ts.getCombinedNodeFlags(decl) : 0; if (modifiers & ts.ModifierFlags.Static) { modifierSet |= TokenModifier.static; } if (modifiers & ts.ModifierFlags.Async) { modifierSet |= TokenModifier.async; } - if (typeIdx !== undefined) { - resultTokens.push({ start: currentTextDocument.positionAt(node.getStart()), length: node.getWidth(), typeIdx, modifierSet }); + if ((modifiers & ts.ModifierFlags.Readonly) || (nodeFlags & ts.NodeFlags.Const)) { + modifierSet |= TokenModifier.readonly; } + resultTokens.push({ start: currentTextDocument.positionAt(node.getStart()), length: node.getWidth(), typeIdx, modifierSet }); } } } @@ -55,6 +59,27 @@ export function getSemanticTokens(jsLanguageService: ts.LanguageService, current return resultTokens; } +function classifySymbol(symbol: ts.Symbol) { + + const flags = symbol.getFlags(); + if (flags & ts.SymbolFlags.Class) { + return TokenType.class; + } else if (flags & ts.SymbolFlags.Enum) { + return TokenType.enum; + } else if (flags & ts.SymbolFlags.TypeAlias) { + return TokenType.type; + } else if (flags & ts.SymbolFlags.Type) { + if (flags & ts.SymbolFlags.Interface) { + return TokenType.interface; + } if (flags & ts.SymbolFlags.TypeParameter) { + return TokenType.typeParameter; + } + } + const decl = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0]; + return tokenFromDeclarationMapping[decl.kind]; +} + + export function getSemanticTokenLegend() { return { types: tokenTypes, modifiers: tokenModifiers }; @@ -62,7 +87,7 @@ export function getSemanticTokenLegend() { const tokenTypes: string[] = ['class', 'enum', 'interface', 'namespace', 'typeParameter', 'type', 'parameter', 'variable', 'property', 'constant', 'function', 'member']; -const tokenModifiers: string[] = ['declaration', 'static', 'async']; +const tokenModifiers: string[] = ['declaration', 'static', 'async', 'readonly']; const enum TokenType { 'class' = 0, @@ -84,6 +109,7 @@ const enum TokenModifier { 'declaration' = 0x01, 'static' = 0x02, 'async' = 0x04, + 'readonly' = 0x08, } const tokenFromDeclarationMapping: { [name: string]: TokenType } = { diff --git a/extensions/html-language-features/server/src/test/semanticTokens.test.ts b/extensions/html-language-features/server/src/test/semanticTokens.test.ts index 0408b3ccdbf02..389fa3496f266 100644 --- a/extensions/html-language-features/server/src/test/semanticTokens.test.ts +++ b/extensions/html-language-features/server/src/test/semanticTokens.test.ts @@ -55,7 +55,7 @@ suite('HTML Semantic Tokens', () => { /*2*/'', + /*8*/'', + /*9*/'', + ]; + assertTokens(input, [ + t(3, 8, 1, 'variable.declaration.readonly'), + t(4, 8, 1, 'class.declaration'), t(4, 28, 1, 'property.declaration.static.readonly'), t(4, 42, 3, 'property.declaration.static'), t(4, 47, 3, 'interface'), + t(6, 8, 1, 'variable.declaration.readonly'), t(6, 12, 1, 'variable.readonly'), t(6, 16, 1, 'class'), t(6, 18, 1, 'property.static.readonly'), t(6, 22, 1, 'class'), t(6, 24, 3, 'property.static'), t(6, 28, 6, 'property.readonly'), ]); }); @@ -154,9 +174,9 @@ suite('HTML Semantic Tokens', () => { /*9*/'', ]; assertTokens(input, [ - t(3, 7, 5, 'type.declaration'), t(3, 15, 3, 'variable') /* to investiagte */, + t(3, 7, 5, 'type.declaration'), t(3, 15, 3, 'interface') /* to investiagte */, t(4, 11, 1, 'function.declaration'), t(4, 13, 1, 'typeParameter.declaration'), t(4, 23, 5, 'type'), t(4, 30, 1, 'parameter.declaration'), t(4, 33, 1, 'typeParameter'), t(4, 47, 1, 'typeParameter'), - t(5, 12, 1, 'typeParameter'), t(5, 29, 3, 'variable'), t(5, 41, 5, 'type'), + t(5, 12, 1, 'typeParameter'), t(5, 29, 3, 'interface'), t(5, 41, 5, 'type'), ]); }); diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index 1f387335ec2e1..7076d918348c7 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -400,6 +400,7 @@ function registerDefaultClassifications(): void { tokenClassificationRegistry.registerTokenModifier('deprecated', nls.localize('deprecated', "Style to use for symbols that are deprecated."), undefined); tokenClassificationRegistry.registerTokenModifier('modification', nls.localize('modification', "Style to use for write accesses."), undefined); tokenClassificationRegistry.registerTokenModifier('async', nls.localize('async', "Style to use for symbols that are async."), undefined); + tokenClassificationRegistry.registerTokenModifier('readonly', nls.localize('readonly', "Style to use for symbols that are readonly."), undefined); } export function getTokenClassificationRegistry(): ITokenClassificationRegistry { From ae8fee7ba8c1c303fb3cd0ecdee176ac39975404 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 13 Jan 2020 17:49:58 +0100 Subject: [PATCH 220/315] finish test --- .../server/src/modes/javascriptSemanticTokens.ts | 2 +- .../server/src/test/semanticTokens.test.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts index fdf22b3cd7cdf..3c7b620ab8c74 100644 --- a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts +++ b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts @@ -40,7 +40,7 @@ export function getSemanticTokens(jsLanguageService: ts.LanguageService, current if (modifiers & ts.ModifierFlags.Async) { modifierSet |= TokenModifier.async; } - if ((modifiers & ts.ModifierFlags.Readonly) || (nodeFlags & ts.NodeFlags.Const)) { + if ((modifiers & ts.ModifierFlags.Readonly) || (nodeFlags & ts.NodeFlags.Const) || (symbol.getFlags() & ts.SymbolFlags.EnumMember)) { modifierSet |= TokenModifier.readonly; } resultTokens.push({ start: currentTextDocument.positionAt(node.getStart()), length: node.getWidth(), typeIdx, modifierSet }); diff --git a/extensions/html-language-features/server/src/test/semanticTokens.test.ts b/extensions/html-language-features/server/src/test/semanticTokens.test.ts index 389fa3496f266..384d1467d0b7c 100644 --- a/extensions/html-language-features/server/src/test/semanticTokens.test.ts +++ b/extensions/html-language-features/server/src/test/semanticTokens.test.ts @@ -155,6 +155,7 @@ suite('HTML Semantic Tokens', () => { assertTokens(input, [ t(3, 8, 1, 'variable.declaration.readonly'), t(4, 8, 1, 'class.declaration'), t(4, 28, 1, 'property.declaration.static.readonly'), t(4, 42, 3, 'property.declaration.static'), t(4, 47, 3, 'interface'), + t(5, 13, 1, 'enum.declaration'), t(5, 17, 1, 'property.declaration.readonly'), t(5, 24, 1, 'property.declaration.readonly'), t(5, 28, 1, 'property.readonly'), t(6, 8, 1, 'variable.declaration.readonly'), t(6, 12, 1, 'variable.readonly'), t(6, 16, 1, 'class'), t(6, 18, 1, 'property.static.readonly'), t(6, 22, 1, 'class'), t(6, 24, 3, 'property.static'), t(6, 28, 6, 'property.readonly'), ]); }); From 7da3ea70a58706081c56d8fb6f76649511087637 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 13 Jan 2020 09:15:36 -0800 Subject: [PATCH 221/315] has -> had --- src/vs/workbench/contrib/terminal/browser/terminalPanel.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts index 2a0408ff55633..2c685281dc968 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts @@ -97,13 +97,13 @@ export class TerminalPanel extends Panel { this._register(this.onDidChangeVisibility(visible => { if (visible) { - const hasTerminals = this._terminalService.terminalInstances.length > 0; - if (!hasTerminals) { + const hadTerminals = this._terminalService.terminalInstances.length > 0; + if (!hadTerminals) { this._terminalService.createTerminal(); } this._updateFont(); this._updateTheme(); - if (hasTerminals) { + if (hadTerminals) { this._terminalService.getActiveTab()?.setVisible(visible); } } From 91283c1351bc86f3da1aa42ae3f559ee07acc391 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Mon, 13 Jan 2020 10:15:40 -0800 Subject: [PATCH 222/315] Enhance views service (#87505) Add view-moving functionality to viewsservice which has been renamed to viewdescriptorservice. also includes outline view moving --- .../parts/activitybar/activitybarPart.ts | 10 +- .../browser/parts/panel/panelPart.ts | 8 +- src/vs/workbench/browser/parts/views/views.ts | 261 +++++++++++++----- src/vs/workbench/common/views.ts | 29 ++ .../outline/browser/outline.contribution.ts | 77 +++++- .../quickopen/browser/viewPickerHandler.ts | 5 +- .../test/browser/parts/views/views.test.ts | 30 +- 7 files changed, 317 insertions(+), 103 deletions(-) diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 29ac27a90b665..9cdf544c79230 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -26,7 +26,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { URI, UriComponents } from 'vs/base/common/uri'; import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity } from 'vs/workbench/browser/parts/compositeBarActions'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; -import { IViewsService, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewDescriptorCollection } from 'vs/workbench/common/views'; +import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewDescriptorCollection } from 'vs/workbench/common/views'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; @@ -88,7 +88,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { @IThemeService themeService: IThemeService, @IStorageService private readonly storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, - @IViewsService private readonly viewsService: IViewsService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService, @@ -189,7 +189,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (viewletDescriptor) { const viewContainer = this.getViewContainer(viewletDescriptor.id); if (viewContainer?.hideIfEmpty) { - const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); + const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); if (viewDescriptors?.activeViewDescriptors.length === 0) { this.hideComposite(viewletDescriptor.id); // Update the composite bar by hiding } @@ -410,7 +410,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.enableCompositeActions(viewlet); const viewContainer = this.getViewContainer(viewlet.id); if (viewContainer?.hideIfEmpty) { - const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); + const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); if (viewDescriptors) { this.onDidChangeActiveViews(viewlet, viewDescriptors); this.viewletDisposables.set(viewlet.id, viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(viewlet, viewDescriptors))); @@ -551,7 +551,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (viewlet) { const views: { when: string | undefined }[] = []; if (viewContainer) { - const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); + const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); if (viewDescriptors) { for (const { when } of viewDescriptors.allViewDescriptors) { views.push({ when: when ? when.serialize() : undefined }); diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 354466a8da051..c381e6021cd6e 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -33,7 +33,7 @@ import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/con import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService, IViewDescriptorCollection } from 'vs/workbench/common/views'; +import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, IViewDescriptorCollection } from 'vs/workbench/common/views'; interface ICachedPanel { id: string; @@ -98,9 +98,9 @@ export class PanelPart extends CompositePart implements IPanelService { @IKeybindingService keybindingService: IKeybindingService, @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IExtensionService private readonly extensionService: IExtensionService, - @IViewsService private readonly viewsService: IViewsService, ) { super( notificationService, @@ -184,7 +184,7 @@ export class PanelPart extends CompositePart implements IPanelService { this.enableCompositeActions(panel); const viewContainer = this.getViewContainer(panel.id); if (viewContainer?.hideIfEmpty) { - const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); + const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); if (viewDescriptors) { this.onDidChangeActiveViews(panel, viewDescriptors); this.panelDisposables.set(panel.id, viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(panel, viewDescriptors))); @@ -295,7 +295,7 @@ export class PanelPart extends CompositePart implements IPanelService { if (panelDescriptor) { const viewContainer = this.getViewContainer(panelDescriptor.id); if (viewContainer?.hideIfEmpty) { - const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); + const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); if (viewDescriptors?.activeViewDescriptors.length === 0) { this.hideComposite(panelDescriptor.id); // Update the composite bar by hiding } diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index a3b9148647abf..91ca9c36bd059 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/views'; import { Disposable, IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IViewsService, IViewsViewlet, ViewContainer, IViewDescriptor, IViewContainersRegistry, Extensions as ViewExtensions, IView, IViewDescriptorCollection, IViewsRegistry } from 'vs/workbench/common/views'; +import { IViewDescriptorService, ViewContainer, IViewDescriptor, IViewContainersRegistry, Extensions as ViewExtensions, IView, IViewDescriptorCollection, IViewsRegistry, ViewContainerLocation, IViewsService } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -21,6 +21,8 @@ import { values } from 'vs/base/common/map'; import { IFileIconTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { toggleClass, addClass } from 'vs/base/browser/dom'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IPaneComposite } from 'vs/workbench/common/panecomposite'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; function filterViewRegisterEvent(container: ViewContainer, event: Event<{ viewContainer: ViewContainer, views: IViewDescriptor[]; }>): Event { return Event.chain(event) @@ -93,14 +95,14 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl constructor( container: ViewContainer, - @IContextKeyService private readonly contextKeyService: IContextKeyService + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService ) { super(); - const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); - const onRelevantViewsRegistered = filterViewRegisterEvent(container, viewsRegistry.onViewsRegistered); + const onRelevantViewsRegistered = filterViewRegisterEvent(container, this.viewDescriptorService.onViewsRegistered); this._register(onRelevantViewsRegistered(this.onViewsRegistered, this)); - const onRelevantViewsMoved = filterViewMoveEvent(container, viewsRegistry.onDidChangeContainer); + const onRelevantViewsMoved = filterViewMoveEvent(container, this.viewDescriptorService.onDidChangeContainer); this._register(onRelevantViewsMoved(({ added, removed }) => { if (isNonEmptyArray(added)) { this.onViewsRegistered(added); @@ -110,13 +112,14 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl } })); - const onRelevantViewsDeregistered = filterViewRegisterEvent(container, viewsRegistry.onViewsDeregistered); + const onRelevantViewsDeregistered = filterViewRegisterEvent(container, this.viewDescriptorService.onViewsDeregistered); this._register(onRelevantViewsDeregistered(this.onViewsDeregistered, this)); const onRelevantContextChange = Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys)); this._register(onRelevantContextChange(this.onContextChanged, this)); - this.onViewsRegistered(viewsRegistry.getViews(container)); + + this.onViewsRegistered(this.viewDescriptorService.getViews(container)); } private onViewsRegistered(viewDescriptors: IViewDescriptor[]): void { @@ -249,7 +252,7 @@ export class ContributableViewsModel extends Disposable { constructor( container: ViewContainer, - viewsService: IViewsService, + viewsService: IViewDescriptorService, protected viewStates = new Map(), ) { super(); @@ -487,13 +490,13 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { constructor( container: ViewContainer, viewletStateStorageId: string, - @IViewsService viewsService: IViewsService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IStorageService storageService: IStorageService, ) { const globalViewsStateStorageId = `${viewletStateStorageId}.hidden`; const viewStates = PersistentContributableViewsModel.loadViewsStates(viewletStateStorageId, globalViewsStateStorageId, storageService); - super(container, viewsService, viewStates); + super(container, viewDescriptorService, viewStates); this.workspaceViewsStateStorageId = viewletStateStorageId; this.globalViewsStateStorageId = globalViewsStateStorageId; @@ -624,45 +627,170 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { } } -export class ViewsService extends Disposable implements IViewsService { +export class ViewOpenerService extends Disposable implements IViewsService { _serviceBrand: undefined; - private readonly viewDescriptorCollections: Map; + private readonly viewContainersRegistry: IViewContainersRegistry; private readonly viewDisposable: Map; + + constructor( + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IPanelService private readonly panelService: IPanelService, + @IViewletService private readonly viewletService: IViewletService + ) { + super(); + + this.viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); + this.viewDisposable = new Map(); + + this._register(toDisposable(() => { + this.viewDisposable.forEach(disposable => disposable.dispose()); + this.viewDisposable.clear(); + })); + + this._register(this.viewDescriptorService.onViewsRegistered(({ views, viewContainer }) => this.onViewsRegistered(views, viewContainer))); + this._register(this.viewDescriptorService.onViewsDeregistered(({ views, viewContainer }) => this.onViewsDeregistered(views, viewContainer))); + } + + private onViewsRegistered(views: IViewDescriptor[], container: ViewContainer): void { + const location = this.viewContainersRegistry.getViewContainerLocation(container); + if (location === undefined) { + return; + } + + const composite = this.getComposite(container.id, location); + for (const viewDescriptor of views) { + const disposables = new DisposableStore(); + const command: ICommandAction = { + id: viewDescriptor.focusCommand ? viewDescriptor.focusCommand.id : `${viewDescriptor.id}.focus`, + title: { original: `Focus on ${viewDescriptor.name} View`, value: localize('focus view', "Focus on {0} View", viewDescriptor.name) }, + category: composite ? composite.name : localize('view category', "View"), + }; + const when = ContextKeyExpr.has(`${viewDescriptor.id}.active`); + + disposables.add(CommandsRegistry.registerCommand(command.id, () => this.openView(viewDescriptor.id, true))); + + disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command, + when + })); + + if (viewDescriptor.focusCommand && viewDescriptor.focusCommand.keybindings) { + KeybindingsRegistry.registerKeybindingRule({ + id: command.id, + when, + weight: KeybindingWeight.WorkbenchContrib, + primary: viewDescriptor.focusCommand.keybindings.primary, + secondary: viewDescriptor.focusCommand.keybindings.secondary, + linux: viewDescriptor.focusCommand.keybindings.linux, + mac: viewDescriptor.focusCommand.keybindings.mac, + win: viewDescriptor.focusCommand.keybindings.win + }); + } + + this.viewDisposable.set(viewDescriptor, disposables); + } + } + + private onViewsDeregistered(views: IViewDescriptor[], container: ViewContainer): void { + for (const view of views) { + const disposable = this.viewDisposable.get(view); + if (disposable) { + disposable.dispose(); + this.viewDisposable.delete(view); + } + } + } + + + private async openComposite(compositeId: string, location: ViewContainerLocation, focus?: boolean): Promise { + if (location === ViewContainerLocation.Sidebar) { + return this.viewletService.openViewlet(compositeId, focus); + } else if (location === ViewContainerLocation.Panel) { + return this.panelService.openPanel(compositeId, focus) as IPaneComposite; + } + return undefined; + } + + private getComposite(compositeId: string, location: ViewContainerLocation): { id: string, name: string } | undefined { + if (location === ViewContainerLocation.Sidebar) { + return this.viewletService.getViewlet(compositeId); + } else if (location === ViewContainerLocation.Panel) { + return this.panelService.getPanel(compositeId); + } + + return undefined; + } + + async openView(id: string, focus: boolean): Promise { + const viewContainer = this.viewDescriptorService.getViewContainer(id); + if (viewContainer) { + const location = this.viewContainersRegistry.getViewContainerLocation(viewContainer); + const compositeDescriptor = this.getComposite(viewContainer.id, location!); + if (compositeDescriptor) { + const paneComposite = await this.openComposite(compositeDescriptor.id, location!, focus) as IPaneComposite | undefined; + if (paneComposite && paneComposite.openView) { + return paneComposite.openView(id, focus); + } + } + } + + return null; + } +} + +export class ViewDescriptorService extends Disposable implements IViewDescriptorService { + + _serviceBrand: undefined; + + private readonly _onViewsRegistered: Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }>()); + readonly onViewsRegistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._onViewsRegistered.event; + + private readonly _onViewsDeregistered: Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }>()); + readonly onViewsDeregistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._onViewsDeregistered.event; + + private readonly _onDidChangeContainer: Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>()); + readonly onDidChangeContainer: Event<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = this._onDidChangeContainer.event; + + private readonly viewDescriptorCollections: Map; + private readonly viewContainers: Map; private readonly activeViewContextKeys: Map>; + private readonly viewsRegistry: IViewsRegistry; + private readonly viewContainersRegistry: IViewContainersRegistry; + constructor( - @IViewletService private readonly viewletService: IViewletService, @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(); this.viewDescriptorCollections = new Map(); - this.viewDisposable = new Map(); + this.viewContainers = new Map(); this.activeViewContextKeys = new Map>(); - const viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); - const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); - viewContainersRegistry.all.forEach(viewContainer => { - this.onDidRegisterViews(viewContainer, viewsRegistry.getViews(viewContainer)); + this.viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); + this.viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); + this.viewContainersRegistry.all.forEach(viewContainer => { + this.onDidRegisterViews(viewContainer, this.viewsRegistry.getViews(viewContainer)); this.onDidRegisterViewContainer(viewContainer); }); - this._register(viewsRegistry.onViewsRegistered(({ views, viewContainer }) => this.onDidRegisterViews(viewContainer, views))); - this._register(viewsRegistry.onViewsDeregistered(({ views }) => this.onDidDeregisterViews(views))); - this._register(viewsRegistry.onDidChangeContainer(({ views, to }) => { this.onDidDeregisterViews(views); this.onDidRegisterViews(to, views); })); - this._register(toDisposable(() => { - this.viewDisposable.forEach(disposable => disposable.dispose()); - this.viewDisposable.clear(); - })); - this._register(viewContainersRegistry.onDidRegister(({ viewContainer }) => this.onDidRegisterViewContainer(viewContainer))); - this._register(viewContainersRegistry.onDidDeregister(({ viewContainer }) => this.onDidDeregisterViewContainer(viewContainer))); + this._register(this.viewsRegistry.onViewsRegistered(({ views, viewContainer }) => this.onDidRegisterViews(viewContainer, views))); + this._register(this.viewsRegistry.onViewsDeregistered(({ views, viewContainer }) => this.onDidDeregisterViews(viewContainer, views))); + this._register(this.viewsRegistry.onDidChangeContainer(({ views, from, to }) => { this.onDidDeregisterViews(from, views); this.onDidRegisterViews(to, views); this._onDidChangeContainer.fire({ views, from, to }); })); + + this._register(this.viewContainersRegistry.onDidRegister(({ viewContainer }) => this.onDidRegisterViewContainer(viewContainer))); + this._register(this.viewContainersRegistry.onDidDeregister(({ viewContainer }) => this.onDidDeregisterViewContainer(viewContainer))); this._register(toDisposable(() => { this.viewDescriptorCollections.forEach(({ disposable }) => disposable.dispose()); this.viewDescriptorCollections.clear(); })); } + getViewContainer(viewId: string): ViewContainer | null { + return this.viewContainers.get(viewId) ?? null; + } + getViewDescriptors(container: ViewContainer): IViewDescriptorCollection | null { const registeredViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).get(container.id); if (registeredViewContainer) { @@ -677,24 +805,28 @@ export class ViewsService extends Disposable implements IViewsService { return null; } - async openView(id: string, focus: boolean): Promise { - const viewContainer = Registry.as(ViewExtensions.ViewsRegistry).getViewContainer(id); - if (viewContainer) { - const viewletDescriptor = this.viewletService.getViewlet(viewContainer.id); - if (viewletDescriptor) { - const viewlet = await this.viewletService.openViewlet(viewletDescriptor.id, focus) as IViewsViewlet | null; - if (viewlet && viewlet.openView) { - return viewlet.openView(id, focus); - } - } + moveViews(views: IViewDescriptor[], viewContainer: ViewContainer): void { + if (!views.length) { + return; } - return null; + const from = this.viewContainers.get(views[0].id); + const to = viewContainer; + + if (from && to && from !== to) { + this.onDidDeregisterViews(from, views); + this.onDidRegisterViews(viewContainer, views); + this._onDidChangeContainer.fire({ views, from, to }); + } + } + + getViews(container: ViewContainer): IViewDescriptor[] { + return this.viewsRegistry.getViews(container); } private onDidRegisterViewContainer(viewContainer: ViewContainer): void { const disposables = new DisposableStore(); - const viewDescriptorCollection = disposables.add(new ViewDescriptorCollection(viewContainer, this.contextKeyService)); + const viewDescriptorCollection = disposables.add(new ViewDescriptorCollection(viewContainer, this.contextKeyService, this)); this.onDidChangeActiveViews({ added: viewDescriptorCollection.activeViewDescriptors, removed: [] }); viewDescriptorCollection.onDidChangeActiveViews(changed => this.onDidChangeActiveViews(changed), this, disposables); @@ -716,48 +848,24 @@ export class ViewsService extends Disposable implements IViewsService { } private onDidRegisterViews(container: ViewContainer, views: IViewDescriptor[]): void { - const viewlet = this.viewletService.getViewlet(container.id); - for (const viewDescriptor of views) { - const disposables = new DisposableStore(); - const command: ICommandAction = { - id: viewDescriptor.focusCommand ? viewDescriptor.focusCommand.id : `${viewDescriptor.id}.focus`, - title: { original: `Focus on ${viewDescriptor.name} View`, value: localize('focus view', "Focus on {0} View", viewDescriptor.name) }, - category: viewlet ? viewlet.name : localize('view category', "View"), - }; - const when = ContextKeyExpr.has(`${viewDescriptor.id}.active`); - - disposables.add(CommandsRegistry.registerCommand(command.id, () => this.openView(viewDescriptor.id, true))); - - disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command, - when - })); - - if (viewDescriptor.focusCommand && viewDescriptor.focusCommand.keybindings) { - KeybindingsRegistry.registerKeybindingRule({ - id: command.id, - when, - weight: KeybindingWeight.WorkbenchContrib, - primary: viewDescriptor.focusCommand.keybindings.primary, - secondary: viewDescriptor.focusCommand.keybindings.secondary, - linux: viewDescriptor.focusCommand.keybindings.linux, - mac: viewDescriptor.focusCommand.keybindings.mac, - win: viewDescriptor.focusCommand.keybindings.win - }); - } + const location = this.viewContainersRegistry.getViewContainerLocation(container); + if (location === undefined) { + return; + } - this.viewDisposable.set(viewDescriptor, disposables); + for (const viewDescriptor of views) { + this.viewContainers.set(viewDescriptor.id, container); } + + this._onViewsRegistered.fire({ views, viewContainer: container }); } - private onDidDeregisterViews(views: IViewDescriptor[]): void { + private onDidDeregisterViews(container: ViewContainer, views: IViewDescriptor[]): void { for (const view of views) { - const disposable = this.viewDisposable.get(view); - if (disposable) { - disposable.dispose(); - this.viewDisposable.delete(view); - } + this.viewContainers.delete(view.id); } + + this._onViewsDeregistered.fire({ views, viewContainer: container }); } private getOrCreateActiveViewContextKey(viewDescriptor: IViewDescriptor): IContextKey { @@ -784,4 +892,5 @@ export function createFileIconThemableTreeContainerScope(container: HTMLElement, return themeService.onDidFileIconThemeChange(onDidChangeFileIconTheme); } -registerSingleton(IViewsService, ViewsService); +registerSingleton(IViewDescriptorService, ViewDescriptorService); +registerSingleton(IViewsService, ViewOpenerService); diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 174df04dff654..72f90b88cd6da 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -100,6 +100,11 @@ export interface IViewContainersRegistry { * Returns all view containers in the given location */ getViewContainers(location: ViewContainerLocation): ViewContainer[]; + + /** + * Returns the view container location + */ + getViewContainerLocation(container: ViewContainer): ViewContainerLocation | undefined; } interface ViewOrderDelegate { @@ -157,6 +162,10 @@ class ViewContainersRegistryImpl extends Disposable implements IViewContainersRe getViewContainers(location: ViewContainerLocation): ViewContainer[] { return [...(this.viewContainers.get(location) || [])]; } + + getViewContainerLocation(container: ViewContainer): ViewContainerLocation | undefined { + return keys(this.viewContainers).filter(location => this.getViewContainers(location).filter(viewContainer => viewContainer.id === container.id).length > 0)[0]; + } } Registry.add(Extensions.ViewContainersRegistry, new ViewContainersRegistryImpl()); @@ -336,14 +345,34 @@ export interface IViewsViewlet extends IViewlet { } +export const IViewDescriptorService = createDecorator('viewDescriptorService'); export const IViewsService = createDecorator('viewsService'); + export interface IViewsService { _serviceBrand: undefined; openView(id: string, focus?: boolean): Promise; +} + + +export interface IViewDescriptorService { + + _serviceBrand: undefined; + + readonly onViewsRegistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }>; + + readonly onViewsDeregistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }>; + + readonly onDidChangeContainer: Event<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>; + + moveViews(views: IViewDescriptor[], viewContainer: ViewContainer): void; + + getViews(container: ViewContainer): IViewDescriptor[]; getViewDescriptors(container: ViewContainer): IViewDescriptorCollection | null; + + getViewContainer(viewId: string): ViewContainer | null; } // Custom views diff --git a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts index d95ee1360d0cd..bbc5693905b31 100644 --- a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts +++ b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts @@ -4,16 +4,56 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { IViewsRegistry, IViewDescriptor, Extensions as ViewExtensions } from 'vs/workbench/common/views'; +import { IViewsRegistry, IViewDescriptor, Extensions as ViewExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; import { OutlinePane } from './outlinePane'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { OutlineConfigKeys, OutlineViewId } from 'vs/editor/contrib/documentSymbols/outline'; import { VIEW_CONTAINER } from 'vs/workbench/contrib/files/browser/explorerViewlet'; +import { Action } from 'vs/base/common/actions'; +import { IWorkbenchActionRegistry, Extensions as ActionsExtensions } from 'vs/workbench/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; // import './outlineNavigation'; +export const PANEL_ID = 'panel.view.outline'; + +export class OutlineViewPaneContainer extends ViewPaneContainer { + constructor( + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ITelemetryService telemetryService: ITelemetryService, + @IWorkspaceContextService protected contextService: IWorkspaceContextService, + @IStorageService protected storageService: IStorageService, + @IConfigurationService configurationService: IConfigurationService, + @IInstantiationService protected instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService, + ) { + super(PANEL_ID, `${PANEL_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + } +} + +export const VIEW_CONTAINER_PANEL: ViewContainer = + Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ + id: PANEL_ID, + ctorDescriptor: new SyncDescriptor(OutlineViewPaneContainer), + name: localize('name', "Outline"), + hideIfEmpty: true + }, ViewContainerLocation.Panel); + + const _outlineDesc = { id: OutlineViewId, name: localize('name', "Outline"), @@ -28,6 +68,41 @@ const _outlineDesc = { Registry.as(ViewExtensions.ViewsRegistry).registerViews([_outlineDesc], VIEW_CONTAINER); +let inPanel = false; + +export class ToggleOutlinePositionAction extends Action { + + static ID = 'outline.view.togglePosition'; + static LABEL = 'Toggle Outline View Position'; + + constructor( + id: string, + label: string, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IViewsService private readonly viewsService: IViewsService + ) { + super(id, label, '', true); + } + + async run(): Promise { + if (!inPanel) { + this.viewDescriptorService.moveViews([_outlineDesc], VIEW_CONTAINER_PANEL); + this.viewsService.openView(OutlineViewId, true); + inPanel = true; + } else { + this.viewDescriptorService.moveViews([_outlineDesc], VIEW_CONTAINER); + this.viewsService.openView(OutlineViewId, true); + + inPanel = false; + } + + } +} + +Registry.as(ActionsExtensions.WorkbenchActions) + .registerWorkbenchAction(SyncActionDescriptor.create(ToggleOutlinePositionAction, ToggleOutlinePositionAction.ID, ToggleOutlinePositionAction.LABEL), 'Show Release Notes'); + + Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ 'id': 'outline', 'order': 117, diff --git a/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts b/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts index 587dc075b247b..eefa445672855 100644 --- a/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts @@ -16,7 +16,7 @@ import { Action } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { fuzzyContains, stripWildcards } from 'vs/base/common/strings'; import { matchesFuzzy } from 'vs/base/common/filters'; -import { IViewsRegistry, ViewContainer, IViewsService, IViewContainersRegistry, Extensions as ViewExtensions } from 'vs/workbench/common/views'; +import { IViewsRegistry, ViewContainer, IViewDescriptorService, IViewContainersRegistry, Extensions as ViewExtensions, IViewsService } from 'vs/workbench/common/views'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -70,6 +70,7 @@ export class ViewPickerHandler extends QuickOpenHandler { constructor( @IViewletService private readonly viewletService: IViewletService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @IViewsService private readonly viewsService: IViewsService, @IOutputService private readonly outputService: IOutputService, @ITerminalService private readonly terminalService: ITerminalService, @@ -197,7 +198,7 @@ export class ViewPickerHandler extends QuickOpenHandler { private hasToShowViewlet(viewlet: ViewletDescriptor): boolean { const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).get(viewlet.id); if (viewContainer?.hideIfEmpty) { - const viewsCollection = this.viewsService.getViewDescriptors(viewContainer); + const viewsCollection = this.viewDescriptorService.getViewDescriptors(viewContainer); return !!viewsCollection && viewsCollection.activeViewDescriptors.length > 0; } return true; diff --git a/src/vs/workbench/test/browser/parts/views/views.test.ts b/src/vs/workbench/test/browser/parts/views/views.test.ts index a5244e4de17c9..49502d7bec95e 100644 --- a/src/vs/workbench/test/browser/parts/views/views.test.ts +++ b/src/vs/workbench/test/browser/parts/views/views.test.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ContributableViewsModel, ViewsService, IViewState } from 'vs/workbench/browser/parts/views/views'; -import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { ContributableViewsModel, ViewDescriptorService, IViewState } from 'vs/workbench/browser/parts/views/views'; +import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { move } from 'vs/base/common/arrays'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -38,14 +38,14 @@ class ViewDescriptorSequence { suite('ContributableViewsModel', () => { - let viewsService: IViewsService; + let viewDescriptorService: IViewDescriptorService; let contextKeyService: IContextKeyService; setup(() => { const instantiationService: TestInstantiationService = workbenchInstantiationService(); contextKeyService = instantiationService.createInstance(ContextKeyService); instantiationService.stub(IContextKeyService, contextKeyService); - viewsService = instantiationService.createInstance(ViewsService); + viewDescriptorService = instantiationService.createInstance(ViewDescriptorService); }); teardown(() => { @@ -53,12 +53,12 @@ suite('ContributableViewsModel', () => { }); test('empty model', function () { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); assert.equal(model.visibleViewDescriptors.length, 0); }); test('register/unregister', () => { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); const seq = new ViewDescriptorSequence(model); assert.equal(model.visibleViewDescriptors.length, 0); @@ -84,7 +84,7 @@ suite('ContributableViewsModel', () => { }); test('when contexts', async function () { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); const seq = new ViewDescriptorSequence(model); assert.equal(model.visibleViewDescriptors.length, 0); @@ -128,7 +128,7 @@ suite('ContributableViewsModel', () => { }); test('when contexts - multiple', async function () { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); const seq = new ViewDescriptorSequence(model); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' }; @@ -151,7 +151,7 @@ suite('ContributableViewsModel', () => { }); test('when contexts - multiple 2', async function () { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); const seq = new ViewDescriptorSequence(model); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', when: ContextKeyExpr.equals('showview1', true) }; @@ -174,7 +174,7 @@ suite('ContributableViewsModel', () => { }); test('setVisible', () => { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); const seq = new ViewDescriptorSequence(model); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', canToggleVisibility: true }; @@ -219,7 +219,7 @@ suite('ContributableViewsModel', () => { }); test('move', () => { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); const seq = new ViewDescriptorSequence(model); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' }; @@ -250,7 +250,7 @@ suite('ContributableViewsModel', () => { test('view states', async function () { const viewStates = new Map(); viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); - const model = new ContributableViewsModel(container, viewsService, viewStates); + const model = new ContributableViewsModel(container, viewDescriptorService, viewStates); const seq = new ViewDescriptorSequence(model); assert.equal(model.visibleViewDescriptors.length, 0); @@ -270,7 +270,7 @@ suite('ContributableViewsModel', () => { test('view states and when contexts', async function () { const viewStates = new Map(); viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); - const model = new ContributableViewsModel(container, viewsService, viewStates); + const model = new ContributableViewsModel(container, viewDescriptorService, viewStates); const seq = new ViewDescriptorSequence(model); assert.equal(model.visibleViewDescriptors.length, 0); @@ -300,7 +300,7 @@ suite('ContributableViewsModel', () => { test('view states and when contexts multiple views', async function () { const viewStates = new Map(); viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); - const model = new ContributableViewsModel(container, viewsService, viewStates); + const model = new ContributableViewsModel(container, viewDescriptorService, viewStates); const seq = new ViewDescriptorSequence(model); assert.equal(model.visibleViewDescriptors.length, 0); @@ -344,7 +344,7 @@ suite('ContributableViewsModel', () => { }); test('remove event is not triggered if view was hidden and removed', async function () { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); const seq = new ViewDescriptorSequence(model); const viewDescriptor: IViewDescriptor = { From 4e7b4bc3269438d3df80d1dd5944d15eb265f4af Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Mon, 13 Jan 2020 10:40:38 -0800 Subject: [PATCH 223/315] missed rename for viewsservice --- src/vs/workbench/browser/parts/views/views.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 91ca9c36bd059..b2911ed666303 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -627,7 +627,7 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { } } -export class ViewOpenerService extends Disposable implements IViewsService { +export class ViewsService extends Disposable implements IViewsService { _serviceBrand: undefined; @@ -893,4 +893,4 @@ export function createFileIconThemableTreeContainerScope(container: HTMLElement, } registerSingleton(IViewDescriptorService, ViewDescriptorService); -registerSingleton(IViewsService, ViewOpenerService); +registerSingleton(IViewsService, ViewsService); From 18f0a5bd2164993acbf12c9eba5a7e826da07f5c Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 13 Jan 2020 19:57:17 +0100 Subject: [PATCH 224/315] add reaonly support, update typescript-vscode-sh-plugin version --- extensions/typescript-language-features/package.json | 4 ++-- .../src/features/semanticTokens.ts | 2 ++ extensions/typescript-language-features/yarn.lock | 8 ++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index c63e6a90901b9..34b17851a94b6 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -19,14 +19,14 @@ "jsonc-parser": "^2.1.1", "rimraf": "^2.6.3", "semver": "5.5.1", - "typescript-vscode-sh-plugin": "^0.3.0", + "typescript-vscode-sh-plugin": "^0.4.0", "vscode-extension-telemetry": "0.1.1", "vscode-nls": "^4.0.0" }, "devDependencies": { "@types/node": "^12.11.7", - "@types/semver": "^5.5.0", "@types/rimraf": "2.0.2", + "@types/semver": "^5.5.0", "vscode": "^1.1.10" }, "scripts": { diff --git a/extensions/typescript-language-features/src/features/semanticTokens.ts b/extensions/typescript-language-features/src/features/semanticTokens.ts index f9f489321a286..de3f11e81d7c3 100644 --- a/extensions/typescript-language-features/src/features/semanticTokens.ts +++ b/extensions/typescript-language-features/src/features/semanticTokens.ts @@ -23,6 +23,7 @@ export function register(selector: vscode.DocumentSelector, client: ITypeScriptS /** * Prototype of a SemanticTokensProvider, relying on the experimental `encodedSemanticClassifications-full` request from the TypeScript server. * As the results retured by the TypeScript server are limited, we also add a Typescript plugin (typescript-vscode-sh-plugin) to enrich the returned token. + * See https://github.com/aeschli/typescript-vscode-sh-plugin. */ class SemanticTokensProvider implements vscode.SemanticTokensProvider { @@ -130,6 +131,7 @@ enum TokenModifier { 'declaration', 'static', 'async', + 'readonly', _sentinel } diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index 53bc5bf315d73..d3455708d2638 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -626,10 +626,10 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -typescript-vscode-sh-plugin@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.3.0.tgz#a071fa28187259a04e3d6862adba41a6332106ff" - integrity sha512-pqPgYa1L64/2LnhP/tJz/+hUsbKzQRMGCdp6Z5Z/dhAdjChB/0WZvovbwDlApGWxOvNAG+oub9TXnJ1yT0WfXQ== +typescript-vscode-sh-plugin@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.4.0.tgz#1b3661f41970a3003099cb3785f7788b5703b751" + integrity sha512-UzqwDMoo/O5asQmUwaiW29cJG/5YzS0EK/4UyyB6UGLNY65CX9/CTp1RHYBmqdh7wq7M0PDqZ8QS1dOWEYnPuQ== uri-js@^4.2.2: version "4.2.2" From 8d1456e210a67f271924cecd68912a086562b9c7 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Mon, 13 Jan 2020 10:58:53 -0800 Subject: [PATCH 225/315] Fix search editor URI malform error --- src/vs/workbench/contrib/search/browser/searchEditorCommands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts index 6d9e6ee65ee57..07177d95ea8da 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts @@ -85,7 +85,7 @@ export class SearchEditorInput extends EditorInput { } getResource(): URI { - return URI.from({ scheme: 'untitled', authority: 'search-editor', path: this.config.query, fragment: `${this.instanceNumber}` }); + return URI.from({ scheme: 'search-editor', fragment: `${this.instanceNumber}` }); } getName(): string { From 5e1dbb10964c3606b2b887bb21566ef70a131042 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 13 Jan 2020 11:47:25 -0800 Subject: [PATCH 226/315] fix(debug): don't insert task boundaries between events (#88459) * fix(debug): don't insert task boundaries between events See discussion in https://github.com/microsoft/vscode-js-debug/issues/206. This PR adjusts logic such that we only assert task boundaries around requests and responses, rather than around every single event. I believe this will solve the primary case where misordering can happen, as given in the existing unit test and described more verbosely in the doc comment in this PR. A more conservative approach would be to only omit the boundary between events of the same type. That would be safer, but I browsing through the code I didn't see any cases where it looked like we could get tripped up by bucketing here (e.g. cases where we resolve a deferred promise in an event handler). Seems to fix the performance issue for me. * fixup! add unit tests for ordering --- .../debug/common/abstractDebugAdapter.ts | 102 ++++++++++++------ .../debug/test/common/rawDebugSession.test.ts | 52 +++++++++ 2 files changed, 121 insertions(+), 33 deletions(-) create mode 100644 src/vs/workbench/contrib/debug/test/common/rawDebugSession.test.ts diff --git a/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts b/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts index f060ba6c665c0..93a76d52c1efc 100644 --- a/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts +++ b/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts @@ -5,20 +5,19 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IDebugAdapter } from 'vs/workbench/contrib/debug/common/debug'; -import { timeout, Queue } from 'vs/base/common/async'; +import { timeout } from 'vs/base/common/async'; /** * Abstract implementation of the low level API for a debug adapter. * Missing is how this API communicates with the debug adapter. */ export abstract class AbstractDebugAdapter implements IDebugAdapter { - private sequence: number; private pendingRequests = new Map void>(); private requestCallback: ((request: DebugProtocol.Request) => void) | undefined; private eventCallback: ((request: DebugProtocol.Event) => void) | undefined; private messageCallback: ((message: DebugProtocol.ProtocolMessage) => void) | undefined; - private readonly queue = new Queue(); + private queue: DebugProtocol.ProtocolMessage[] = []; protected readonly _onError = new Emitter(); protected readonly _onExit = new Emitter(); @@ -64,8 +63,7 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { sendResponse(response: DebugProtocol.Response): void { if (response.seq > 0) { this._onError.fire(new Error(`attempt to send more than one response for command ${response.command}`)); - } - else { + } else { this.internalSend('response', response); } } @@ -107,35 +105,73 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { acceptMessage(message: DebugProtocol.ProtocolMessage): void { if (this.messageCallback) { this.messageCallback(message); + } else { + this.queue.push(message); + if (this.queue.length === 1) { + // first item = need to start processing loop + this.processQueue(); + } } - else { - this.queue.queue(() => { - switch (message.type) { - case 'event': - if (this.eventCallback) { - this.eventCallback(message); - } - break; - case 'request': - if (this.requestCallback) { - this.requestCallback(message); - } - break; - case 'response': - const response = message; - const clb = this.pendingRequests.get(response.request_seq); - if (clb) { - this.pendingRequests.delete(response.request_seq); - clb(response); - } - break; - } + } + + /** + * Returns whether we should insert a timeout between processing messageA + * and messageB. Artificially queueing protocol messages guarantees that any + * microtasks for previous message finish before next message is processed. + * This is essential ordering when using promises anywhere along the call path. + * + * For example, take the following, where `chooseAndSendGreeting` returns + * a person name and then emits a greeting event: + * + * ``` + * let person: string; + * adapter.onGreeting(() => console.log('hello', person)); + * person = await adapter.chooseAndSendGreeting(); + * ``` + * + * Because the event is dispatched synchronously, it may fire before person + * is assigned if they're processed in the same task. Inserting a task + * boundary avoids this issue. + */ + protected needsTaskBoundaryBetween(messageA: DebugProtocol.ProtocolMessage, messageB: DebugProtocol.ProtocolMessage) { + return messageA.type !== 'event' || messageB.type !== 'event'; + } - // Artificially queueing protocol messages guarantees that any microtasks for - // previous message finish before next message is processed. This is essential - // to guarantee ordering when using promises anywhere along the call path. - return timeout(0); - }); + /** + * Reads and dispatches items from the queue until it is empty. + */ + private async processQueue() { + let message: DebugProtocol.ProtocolMessage | undefined; + while (this.queue.length) { + if (!message || this.needsTaskBoundaryBetween(this.queue[0], message)) { + await timeout(0); + } + + message = this.queue.shift(); + if (!message) { + return; // may have been disposed of + } + + switch (message.type) { + case 'event': + if (this.eventCallback) { + this.eventCallback(message); + } + break; + case 'request': + if (this.requestCallback) { + this.requestCallback(message); + } + break; + case 'response': + const response = message; + const clb = this.pendingRequests.get(response.request_seq); + if (clb) { + this.pendingRequests.delete(response.request_seq); + clb(response); + } + break; + } } } @@ -172,6 +208,6 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { } dispose(): void { - this.queue.dispose(); + this.queue = []; } } diff --git a/src/vs/workbench/contrib/debug/test/common/rawDebugSession.test.ts b/src/vs/workbench/contrib/debug/test/common/rawDebugSession.test.ts new file mode 100644 index 0000000000000..16c02f6cb0277 --- /dev/null +++ b/src/vs/workbench/contrib/debug/test/common/rawDebugSession.test.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { MockDebugAdapter } from 'vs/workbench/contrib/debug/test/common/mockDebug'; +import { timeout } from 'vs/base/common/async'; + +suite('Debug - AbstractDebugAdapter', () => { + suite('event ordering', () => { + let adapter: MockDebugAdapter; + let output: string[]; + setup(() => { + adapter = new MockDebugAdapter(); + output = []; + adapter.onEvent(ev => { + output.push((ev as DebugProtocol.OutputEvent).body.output); + Promise.resolve().then(() => output.push('--end microtask--')); + }); + }); + + const evaluate = async (expression: string) => { + await new Promise(resolve => adapter.sendRequest('evaluate', { expression }, resolve)); + output.push(`=${expression}`); + Promise.resolve().then(() => output.push('--end microtask--')); + }; + + test('inserts task boundary before response', async () => { + await evaluate('before.foo'); + await timeout(0); + + assert.deepStrictEqual(output, ['before.foo', '--end microtask--', '=before.foo', '--end microtask--']); + }); + + test('inserts task boundary after response', async () => { + await evaluate('after.foo'); + await timeout(0); + + assert.deepStrictEqual(output, ['=after.foo', '--end microtask--', 'after.foo', '--end microtask--']); + }); + + test('does not insert boundaries between events', async () => { + adapter.sendEventBody('output', { output: 'a' }); + adapter.sendEventBody('output', { output: 'b' }); + adapter.sendEventBody('output', { output: 'c' }); + await timeout(0); + + assert.deepStrictEqual(output, ['a', 'b', 'c', '--end microtask--', '--end microtask--', '--end microtask--']); + }); + }); +}); From a5f90a56850809fc9e1a55a351c147597e102238 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 13 Jan 2020 21:06:57 +0100 Subject: [PATCH 227/315] polish html sem highlighting --- .../server/src/modes/javascriptSemanticTokens.ts | 4 ++-- .../server/src/test/semanticTokens.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts index 3c7b620ab8c74..38a87159a327f 100644 --- a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts +++ b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts @@ -17,7 +17,7 @@ export function getSemanticTokens(jsLanguageService: ts.LanguageService, current const typeChecker = program.getTypeChecker(); function visit(node: ts.Node) { - if (node.kind === ts.SyntaxKind.Identifier) { + if (ts.isIdentifier(node)) { const symbol = typeChecker.getSymbolAtLocation(node); if (symbol) { let typeIdx = classifySymbol(symbol); @@ -76,7 +76,7 @@ function classifySymbol(symbol: ts.Symbol) { } } const decl = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0]; - return tokenFromDeclarationMapping[decl.kind]; + return decl && tokenFromDeclarationMapping[decl.kind]; } diff --git a/extensions/html-language-features/server/src/test/semanticTokens.test.ts b/extensions/html-language-features/server/src/test/semanticTokens.test.ts index 384d1467d0b7c..ea3777e84e90b 100644 --- a/extensions/html-language-features/server/src/test/semanticTokens.test.ts +++ b/extensions/html-language-features/server/src/test/semanticTokens.test.ts @@ -147,7 +147,7 @@ suite('HTML Semantic Tokens', () => { /*3*/' const f = 9;', /*4*/' class A { static readonly t = 9; static url: URL; }', /*5*/' const enum E { A = 9, B = A + 1 }', - /*6*/' const x = f + A.t + A.url.origin;', + /*6*/' console.log(f + A.t + A.url.origin);', /*7*/'', /*8*/'', /*9*/'', @@ -156,7 +156,7 @@ suite('HTML Semantic Tokens', () => { t(3, 8, 1, 'variable.declaration.readonly'), t(4, 8, 1, 'class.declaration'), t(4, 28, 1, 'property.declaration.static.readonly'), t(4, 42, 3, 'property.declaration.static'), t(4, 47, 3, 'interface'), t(5, 13, 1, 'enum.declaration'), t(5, 17, 1, 'property.declaration.readonly'), t(5, 24, 1, 'property.declaration.readonly'), t(5, 28, 1, 'property.readonly'), - t(6, 8, 1, 'variable.declaration.readonly'), t(6, 12, 1, 'variable.readonly'), t(6, 16, 1, 'class'), t(6, 18, 1, 'property.static.readonly'), t(6, 22, 1, 'class'), t(6, 24, 3, 'property.static'), t(6, 28, 6, 'property.readonly'), + t(6, 2, 7, 'variable'), t(6, 10, 3, 'member'), t(6, 14, 1, 'variable.readonly'), t(6, 18, 1, 'class'), t(6, 20, 1, 'property.static.readonly'), t(6, 24, 1, 'class'), t(6, 26, 3, 'property.static'), t(6, 30, 6, 'property.readonly'), ]); }); From fc57a1421df4ec323e36d1b8c29789300998fe80 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 13 Jan 2020 21:09:37 +0100 Subject: [PATCH 228/315] update typescript-language-features --- extensions/typescript-language-features/package.json | 2 +- extensions/typescript-language-features/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 34b17851a94b6..3ff2f98b8e44e 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -19,7 +19,7 @@ "jsonc-parser": "^2.1.1", "rimraf": "^2.6.3", "semver": "5.5.1", - "typescript-vscode-sh-plugin": "^0.4.0", + "typescript-vscode-sh-plugin": "^0.4.1", "vscode-extension-telemetry": "0.1.1", "vscode-nls": "^4.0.0" }, diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index d3455708d2638..98feebef484c3 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -626,10 +626,10 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -typescript-vscode-sh-plugin@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.4.0.tgz#1b3661f41970a3003099cb3785f7788b5703b751" - integrity sha512-UzqwDMoo/O5asQmUwaiW29cJG/5YzS0EK/4UyyB6UGLNY65CX9/CTp1RHYBmqdh7wq7M0PDqZ8QS1dOWEYnPuQ== +typescript-vscode-sh-plugin@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.4.1.tgz#6982958419ca760c0add761dd1d5f398815bee8c" + integrity sha512-IPsz/FNuk9HsjaEOsJJxGkiKGK5E4muZtjntp/BDWYZVYOj3R8JtX6++pQ0ftMOcZwxjKxeBmnS1FSUdVMifSw== uri-js@^4.2.2: version "4.2.2" From fbb4fbcd4ba862cade0bbf39632a8b7a88aadef9 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 13 Jan 2020 21:06:05 +0100 Subject: [PATCH 229/315] fix issue with accessibility provider, #88554 --- .../contrib/bulkEdit/browser/bulkEditTree.ts | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index 188c05694422e..bbd92f7b31a4f 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -118,38 +118,44 @@ export class BulkEditAccessibilityProvider implements IAccessibilityProvider 0) { if (element.edit.type & BulkFileOperationType.Rename && element.edit.newUri) { return localize( - 'area.renameAndEdit', "Renaming {0} to {1}, also making text edits", + 'aria.renameAndEdit', "Renaming {0} to {1}, also making text edits", this._labelService.getUriLabel(element.edit.uri, { relative: true }), this._labelService.getUriLabel(element.edit.newUri, { relative: true }) ); } else if (element.edit.type & BulkFileOperationType.Create) { return localize( - 'area.createAndEdit', "Creating {0}, also making text edits", + 'aria.createAndEdit', "Creating {0}, also making text edits", this._labelService.getUriLabel(element.edit.uri, { relative: true }) ); } else if (element.edit.type & BulkFileOperationType.Delete) { return localize( - 'area.deleteAndEdit', "Deleting {0}, also making text edits", + 'aria.deleteAndEdit', "Deleting {0}, also making text edits", + this._labelService.getUriLabel(element.edit.uri, { relative: true }), + ); + } else { + return localize( + 'aria.editOnly', "{0}, making text edits", this._labelService.getUriLabel(element.edit.uri, { relative: true }), ); } + } else { if (element.edit.type & BulkFileOperationType.Rename && element.edit.newUri) { return localize( - 'area.rename', "Renaming {0} to {1}", + 'aria.rename', "Renaming {0} to {1}", this._labelService.getUriLabel(element.edit.uri, { relative: true }), this._labelService.getUriLabel(element.edit.newUri, { relative: true }) ); } else if (element.edit.type & BulkFileOperationType.Create) { return localize( - 'area.create', "Creating {0}", + 'aria.create', "Creating {0}", this._labelService.getUriLabel(element.edit.uri, { relative: true }) ); } else if (element.edit.type & BulkFileOperationType.Delete) { return localize( - 'area.delete', "Deleting {0}", + 'aria.delete', "Deleting {0}", this._labelService.getUriLabel(element.edit.uri, { relative: true }), ); } @@ -159,7 +165,7 @@ export class BulkEditAccessibilityProvider implements IAccessibilityProvider 0 && element.inserting.length > 0) { // edit: replace - return localize('aria.replace', "line {0}, replacing {1} with {0}", element.edit.edit.range.startLineNumber, element.selecting, element.inserting); + return localize('aria.replace', "line {0}, replacing {1} with {2}", element.edit.edit.range.startLineNumber, element.selecting, element.inserting); } else if (element.selecting.length > 0 && element.inserting.length === 0) { // edit: delete return localize('aria.del', "line {0}, removing {1}", element.edit.edit.range.startLineNumber, element.selecting); From 0bf6738abe5d7009c5d1ab4ef1a3d10c792e05b1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 13 Jan 2020 21:17:21 +0100 Subject: [PATCH 230/315] add AriaProvider, #88553 --- .../contrib/bulkEdit/browser/bulkEditPane.ts | 3 ++- .../contrib/bulkEdit/browser/bulkEditTree.ts | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index 13e60a3218abe..d0310fcc72724 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -6,7 +6,7 @@ import 'vs/css!./bulkEdit'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { WorkspaceEdit } from 'vs/editor/common/modes'; -import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; +import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider, BulkEditAriaProvider } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; import { FuzzyScore } from 'vs/base/common/filters'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; @@ -97,6 +97,7 @@ export class BulkEditPane extends ViewPane { this._instaService.createInstance(BulkEditDataSource), { accessibilityProvider: this._instaService.createInstance(BulkEditAccessibilityProvider), + ariaProvider: new BulkEditAriaProvider(), identityProvider: new BulkEditIdentityProvider(), expandOnlyOnTwistieClick: true, multipleSelectionSupport: false diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index bbd92f7b31a4f..2488030bcfa80 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -20,6 +20,7 @@ import { FileKind } from 'vs/platform/files/common/files'; import { localize } from 'vs/nls'; import { ILabelService } from 'vs/platform/label/common/label'; import type { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import type { IAriaProvider } from 'vs/base/browser/ui/list/listView'; // --- VIEW MODEL @@ -192,6 +193,21 @@ export class BulkEditIdentityProvider implements IIdentityProvider { + + getSetSize(_element: BulkEditElement, _index: number, listLength: number): number { + return listLength; + } + + getPosInSet(_element: BulkEditElement, index: number): number { + return index; + } + + getRole?(_element: BulkEditElement): string { + return 'checkbox'; + } +} + // --- RENDERER class FileElementTemplate { From 27546bcb564e0f7246bd9fa47887c0883c5757e4 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 13 Jan 2020 21:35:22 +0100 Subject: [PATCH 231/315] fix test failure --- src/vs/editor/common/services/modelServiceImpl.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 9e0c8349ade70..1d60306a4cc9e 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -456,7 +456,8 @@ class SemanticColoringFeature extends Disposable { this._semanticStyling = this._register(new SemanticStyling(themeService, logService)); const isSemanticColoringEnabled = (model: ITextModel) => { - return configurationService.getValue(SemanticColoringFeature.SETTING_ID, { overrideIdentifier: model.getLanguageIdentifier().language, resource: model.uri }).enabled; + const options = configurationService.getValue(SemanticColoringFeature.SETTING_ID, { overrideIdentifier: model.getLanguageIdentifier().language, resource: model.uri }); + return options && options.enabled; }; const register = (model: ITextModel) => { this._watchers[model.uri.toString()] = new ModelSemanticColoring(model, themeService, this._semanticStyling); From 137c78d711a6d701a928c29241ac50f89000e41a Mon Sep 17 00:00:00 2001 From: Konstantin Solomatov Date: Tue, 7 Jan 2020 12:26:06 -0800 Subject: [PATCH 232/315] Code cleanup --- .../contrib/terminal/browser/terminalPanel.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts index 0cec6b4b0c6dd..cfa67e91d8f04 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts @@ -97,19 +97,14 @@ export class TerminalPanel extends Panel { this._register(this.onDidChangeVisibility(visible => { if (visible) { - if (this._terminalService.terminalInstances.length > 0) { - this._updateFont(); - this._updateTheme(); + const hasTerminals = this._terminalService.terminalInstances.length > 0; + if (!hasTerminals) { + this._terminalService.createTerminal(); + } + this._updateFont(); + this._updateTheme(); + if (!hasTerminals) { this._terminalService.getActiveTab()?.setVisible(visible); - } else { - // Check if instances were already restored as part of workbench restore - if (this._terminalService.terminalInstances.length === 0) { - this._terminalService.createTerminal(); - } - if (this._terminalService.terminalInstances.length > 0) { - this._updateFont(); - this._updateTheme(); - } } } })); From fb9a7f0398045d869d9ad3a87559875dca9cab40 Mon Sep 17 00:00:00 2001 From: Konstantin Solomatov Date: Tue, 7 Jan 2020 13:03:11 -0800 Subject: [PATCH 233/315] Fix incorrect negation --- src/vs/workbench/contrib/terminal/browser/terminalPanel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts index cfa67e91d8f04..2a0408ff55633 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts @@ -103,7 +103,7 @@ export class TerminalPanel extends Panel { } this._updateFont(); this._updateTheme(); - if (!hasTerminals) { + if (hasTerminals) { this._terminalService.getActiveTab()?.setVisible(visible); } } From 115dde7d6529b7b214805e47c2cc0e263b57342d Mon Sep 17 00:00:00 2001 From: kieferrm Date: Thu, 9 Jan 2020 20:04:07 -0800 Subject: [PATCH 234/315] issue flow configuration --- .github/feature-requests.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/feature-requests.yml diff --git a/.github/feature-requests.yml b/.github/feature-requests.yml new file mode 100644 index 0000000000000..18055b844862d --- /dev/null +++ b/.github/feature-requests.yml @@ -0,0 +1,34 @@ +{ + typeLabel: { + name: 'feature-request' + }, + candidateMilestone: { + number: 107, + name: 'Backlog Candidates' + }, + approvedMilestone: { + number: 8, + name: 'Backlog' + }, + onLabeled: { + delay: 60, + perform: true + }, + onCandidateMilestoned: { + candidatesComment: "This feature request is now a candidate for our backlog. The community has 60 days to upvote the issue. If it receives 20 upvotes we will move it to our backlog. If not, we will close it. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding!", + perform: true + }, + onMonitorUpvotes: { + upvoteThreshold: 20, + acceptanceComment: ":slightly_smiling_face: This feature request received a sufficient number of community upvotes and we moved it to our backlog. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding!", + perform: true + }, + onMonitorDaysOnCandidateMilestone: { + daysOnMilestone: 60, + warningPeriod: 10, + numberOfCommentsToPreventAutomaticRejection: 20, + rejectionComment: ":slightly_frowning_face: In the last 60 days, this feature request has received less than 20 community upvotes and we closed it. Still a big Thank You to you for taking the time to create this issue! To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding!", + warningComment: "This feature request has not yet received the 20 community upvotes it takes to make to our backlog. 10 days to go. To learn more about how we handle feature requests, please see our [documentation](https://aka.ms/vscode-issue-lifecycle).\n\nHappy Coding", + perform: true + } +} From fe572bbb59003c036f4708bf45b82abf3d6b692c Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Sun, 12 Jan 2020 02:39:01 -0500 Subject: [PATCH 235/315] Fixes #88242 --- .../contrib/debug/browser/baseDebugView.ts | 121 +++++++++--------- .../debug/browser/watchExpressionsView.ts | 11 +- 2 files changed, 74 insertions(+), 58 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index c40e105ffefed..160c2c8f3cdac 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -9,7 +9,7 @@ import { Expression, Variable, ExpressionContainer } from 'vs/workbench/contrib/ import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInputValidationOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -18,6 +18,7 @@ import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabe import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel'; +import { once } from 'vs/base/common/functional'; export const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; export const twistiePixels = 20; @@ -137,8 +138,7 @@ export interface IExpressionTemplateData { name: HTMLSpanElement; value: HTMLSpanElement; inputBoxContainer: HTMLElement; - enableInputBox(options: IInputBoxOptions): void; - toDispose: IDisposable[]; + toDispose: IDisposable; label: HighlightedLabel; } @@ -159,77 +159,84 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer { - name.style.display = 'none'; - value.style.display = 'none'; - inputBoxContainer.style.display = 'initial'; - - const inputBox = new InputBox(inputBoxContainer, this.contextViewService, options); - const styler = attachInputBoxStyler(inputBox, this.themeService); - - inputBox.value = replaceWhitespace(options.initialValue); - inputBox.focus(); - inputBox.select(); - - let disposed = false; - toDispose.push(inputBox); - toDispose.push(styler); - - const wrapUp = (renamed: boolean) => { - if (!disposed) { - disposed = true; - this.debugService.getViewModel().setSelectedExpression(undefined); - options.onFinish(inputBox.value, renamed); - - // need to remove the input box since this template will be reused. - inputBoxContainer.removeChild(inputBox.element); - name.style.display = 'initial'; - value.style.display = 'initial'; - inputBoxContainer.style.display = 'none'; - dispose(toDispose); - } - }; - - toDispose.push(dom.addStandardDisposableListener(inputBox.inputElement, 'keydown', (e: IKeyboardEvent) => { - const isEscape = e.equals(KeyCode.Escape); - const isEnter = e.equals(KeyCode.Enter); - if (isEscape || isEnter) { - e.preventDefault(); - e.stopPropagation(); - wrapUp(isEnter); - } - })); - toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'blur', () => { - wrapUp(true); - })); - toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'click', e => { - // Do not expand / collapse selected elements - e.preventDefault(); - e.stopPropagation(); - })); - }; - return { expression, name, value, label, enableInputBox, inputBoxContainer, toDispose }; + return { expression, name, value, label, inputBoxContainer, toDispose: Disposable.None }; } renderElement(node: ITreeNode, index: number, data: IExpressionTemplateData): void { + data.toDispose.dispose(); + data.toDispose = Disposable.None; const { element } = node; if (element === this.debugService.getViewModel().getSelectedExpression() || (element instanceof Variable && element.errorMessage)) { const options = this.getInputBoxOptions(element); if (options) { - data.enableInputBox(options); + data.toDispose = this.renderInputBox(data.name, data.value, data.inputBoxContainer, options); return; } } this.renderExpression(element, data, createMatches(node.filterData)); } + renderInputBox(nameElement: HTMLElement, valueElement: HTMLElement, inputBoxContainer: HTMLElement, options: IInputBoxOptions): IDisposable { + nameElement.style.display = 'none'; + valueElement.style.display = 'none'; + inputBoxContainer.style.display = 'initial'; + + const inputBox = new InputBox(inputBoxContainer, this.contextViewService, options); + const styler = attachInputBoxStyler(inputBox, this.themeService); + + inputBox.value = replaceWhitespace(options.initialValue); + inputBox.focus(); + inputBox.select(); + + const done = once((success: boolean, finishEditing: boolean) => { + nameElement.style.display = 'initial'; + valueElement.style.display = 'initial'; + inputBoxContainer.style.display = 'none'; + const value = inputBox.value; + dispose(toDispose); + + if (finishEditing) { + this.debugService.getViewModel().setSelectedExpression(undefined); + options.onFinish(value, success); + } + }); + + const toDispose = [ + inputBox, + dom.addStandardDisposableListener(inputBox.inputElement, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => { + const isEscape = e.equals(KeyCode.Escape); + const isEnter = e.equals(KeyCode.Enter); + if (isEscape || isEnter) { + e.preventDefault(); + e.stopPropagation(); + done(isEnter, true); + } + }), + dom.addDisposableListener(inputBox.inputElement, dom.EventType.BLUR, (e) => { + done(true, true); + }), + dom.addDisposableListener(inputBox.inputElement, dom.EventType.CLICK, e => { + // Do not expand / collapse selected elements + e.preventDefault(); + e.stopPropagation(); + }), + styler + ]; + + return toDisposable(() => { + done(false, false); + }); + } + protected abstract renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void; protected abstract getInputBoxOptions(expression: IExpression): IInputBoxOptions | undefined; + disposeElement(node: ITreeNode, index: number, templateData: IExpressionTemplateData): void { + templateData.toDispose.dispose(); + } + disposeTemplate(templateData: IExpressionTemplateData): void { - dispose(templateData.toDispose); + templateData.toDispose.dispose(); } } diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 0e2f8fd6df454..7ee2d0fcaca3d 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -69,7 +69,16 @@ export class WatchExpressionsView extends ViewPane { ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'watchAriaTreeLabel' }, "Debug Watch Expressions"), accessibilityProvider: new WatchExpressionsAccessibilityProvider(), identityProvider: { getId: (element: IExpression) => element.getId() }, - keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IExpression) => e }, + keyboardNavigationLabelProvider: { + getKeyboardNavigationLabel: (e: IExpression) => { + if (e === this.debugService.getViewModel().getSelectedExpression()) { + // Don't filter input box + return undefined; + } + + return e; + } + }, dnd: new WatchExpressionsDragAndDrop(this.debugService), overrideStyles: { listBackground: SIDE_BAR_BACKGROUND From 1e061ff1d4a5d98a53184fb0909f7244d1bb0a3d Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Sun, 12 Jan 2020 02:50:09 -0500 Subject: [PATCH 236/315] :lipstick: --- src/vs/workbench/contrib/debug/browser/baseDebugView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 160c2c8f3cdac..3d6eafae60a49 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -213,7 +213,7 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer { + dom.addDisposableListener(inputBox.inputElement, dom.EventType.BLUR, () => { done(true, true); }), dom.addDisposableListener(inputBox.inputElement, dom.EventType.CLICK, e => { From 39af627463e7eecfb5c59149ed67bb1afda3564a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 13 Jan 2020 09:15:36 -0800 Subject: [PATCH 237/315] has -> had --- src/vs/workbench/contrib/terminal/browser/terminalPanel.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts index 2a0408ff55633..2c685281dc968 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts @@ -97,13 +97,13 @@ export class TerminalPanel extends Panel { this._register(this.onDidChangeVisibility(visible => { if (visible) { - const hasTerminals = this._terminalService.terminalInstances.length > 0; - if (!hasTerminals) { + const hadTerminals = this._terminalService.terminalInstances.length > 0; + if (!hadTerminals) { this._terminalService.createTerminal(); } this._updateFont(); this._updateTheme(); - if (hasTerminals) { + if (hadTerminals) { this._terminalService.getActiveTab()?.setVisible(visible); } } From 16b75e33070d7c1e04c2e4af1e2f54bce7dfa41e Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Mon, 13 Jan 2020 10:15:40 -0800 Subject: [PATCH 238/315] Enhance views service (#87505) Add view-moving functionality to viewsservice which has been renamed to viewdescriptorservice. also includes outline view moving --- .../parts/activitybar/activitybarPart.ts | 10 +- .../browser/parts/panel/panelPart.ts | 8 +- src/vs/workbench/browser/parts/views/views.ts | 261 +++++++++++++----- src/vs/workbench/common/views.ts | 29 ++ .../outline/browser/outline.contribution.ts | 77 +++++- .../quickopen/browser/viewPickerHandler.ts | 5 +- .../test/browser/parts/views/views.test.ts | 30 +- 7 files changed, 317 insertions(+), 103 deletions(-) diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 29ac27a90b665..9cdf544c79230 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -26,7 +26,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { URI, UriComponents } from 'vs/base/common/uri'; import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity } from 'vs/workbench/browser/parts/compositeBarActions'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; -import { IViewsService, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewDescriptorCollection } from 'vs/workbench/common/views'; +import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewDescriptorCollection } from 'vs/workbench/common/views'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; @@ -88,7 +88,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { @IThemeService themeService: IThemeService, @IStorageService private readonly storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, - @IViewsService private readonly viewsService: IViewsService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService, @@ -189,7 +189,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (viewletDescriptor) { const viewContainer = this.getViewContainer(viewletDescriptor.id); if (viewContainer?.hideIfEmpty) { - const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); + const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); if (viewDescriptors?.activeViewDescriptors.length === 0) { this.hideComposite(viewletDescriptor.id); // Update the composite bar by hiding } @@ -410,7 +410,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.enableCompositeActions(viewlet); const viewContainer = this.getViewContainer(viewlet.id); if (viewContainer?.hideIfEmpty) { - const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); + const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); if (viewDescriptors) { this.onDidChangeActiveViews(viewlet, viewDescriptors); this.viewletDisposables.set(viewlet.id, viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(viewlet, viewDescriptors))); @@ -551,7 +551,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (viewlet) { const views: { when: string | undefined }[] = []; if (viewContainer) { - const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); + const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); if (viewDescriptors) { for (const { when } of viewDescriptors.allViewDescriptors) { views.push({ when: when ? when.serialize() : undefined }); diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 354466a8da051..c381e6021cd6e 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -33,7 +33,7 @@ import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/con import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService, IViewDescriptorCollection } from 'vs/workbench/common/views'; +import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, IViewDescriptorCollection } from 'vs/workbench/common/views'; interface ICachedPanel { id: string; @@ -98,9 +98,9 @@ export class PanelPart extends CompositePart implements IPanelService { @IKeybindingService keybindingService: IKeybindingService, @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IExtensionService private readonly extensionService: IExtensionService, - @IViewsService private readonly viewsService: IViewsService, ) { super( notificationService, @@ -184,7 +184,7 @@ export class PanelPart extends CompositePart implements IPanelService { this.enableCompositeActions(panel); const viewContainer = this.getViewContainer(panel.id); if (viewContainer?.hideIfEmpty) { - const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); + const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); if (viewDescriptors) { this.onDidChangeActiveViews(panel, viewDescriptors); this.panelDisposables.set(panel.id, viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(panel, viewDescriptors))); @@ -295,7 +295,7 @@ export class PanelPart extends CompositePart implements IPanelService { if (panelDescriptor) { const viewContainer = this.getViewContainer(panelDescriptor.id); if (viewContainer?.hideIfEmpty) { - const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); + const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); if (viewDescriptors?.activeViewDescriptors.length === 0) { this.hideComposite(panelDescriptor.id); // Update the composite bar by hiding } diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index a3b9148647abf..91ca9c36bd059 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/views'; import { Disposable, IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IViewsService, IViewsViewlet, ViewContainer, IViewDescriptor, IViewContainersRegistry, Extensions as ViewExtensions, IView, IViewDescriptorCollection, IViewsRegistry } from 'vs/workbench/common/views'; +import { IViewDescriptorService, ViewContainer, IViewDescriptor, IViewContainersRegistry, Extensions as ViewExtensions, IView, IViewDescriptorCollection, IViewsRegistry, ViewContainerLocation, IViewsService } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -21,6 +21,8 @@ import { values } from 'vs/base/common/map'; import { IFileIconTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { toggleClass, addClass } from 'vs/base/browser/dom'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IPaneComposite } from 'vs/workbench/common/panecomposite'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; function filterViewRegisterEvent(container: ViewContainer, event: Event<{ viewContainer: ViewContainer, views: IViewDescriptor[]; }>): Event { return Event.chain(event) @@ -93,14 +95,14 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl constructor( container: ViewContainer, - @IContextKeyService private readonly contextKeyService: IContextKeyService + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService ) { super(); - const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); - const onRelevantViewsRegistered = filterViewRegisterEvent(container, viewsRegistry.onViewsRegistered); + const onRelevantViewsRegistered = filterViewRegisterEvent(container, this.viewDescriptorService.onViewsRegistered); this._register(onRelevantViewsRegistered(this.onViewsRegistered, this)); - const onRelevantViewsMoved = filterViewMoveEvent(container, viewsRegistry.onDidChangeContainer); + const onRelevantViewsMoved = filterViewMoveEvent(container, this.viewDescriptorService.onDidChangeContainer); this._register(onRelevantViewsMoved(({ added, removed }) => { if (isNonEmptyArray(added)) { this.onViewsRegistered(added); @@ -110,13 +112,14 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl } })); - const onRelevantViewsDeregistered = filterViewRegisterEvent(container, viewsRegistry.onViewsDeregistered); + const onRelevantViewsDeregistered = filterViewRegisterEvent(container, this.viewDescriptorService.onViewsDeregistered); this._register(onRelevantViewsDeregistered(this.onViewsDeregistered, this)); const onRelevantContextChange = Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys)); this._register(onRelevantContextChange(this.onContextChanged, this)); - this.onViewsRegistered(viewsRegistry.getViews(container)); + + this.onViewsRegistered(this.viewDescriptorService.getViews(container)); } private onViewsRegistered(viewDescriptors: IViewDescriptor[]): void { @@ -249,7 +252,7 @@ export class ContributableViewsModel extends Disposable { constructor( container: ViewContainer, - viewsService: IViewsService, + viewsService: IViewDescriptorService, protected viewStates = new Map(), ) { super(); @@ -487,13 +490,13 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { constructor( container: ViewContainer, viewletStateStorageId: string, - @IViewsService viewsService: IViewsService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IStorageService storageService: IStorageService, ) { const globalViewsStateStorageId = `${viewletStateStorageId}.hidden`; const viewStates = PersistentContributableViewsModel.loadViewsStates(viewletStateStorageId, globalViewsStateStorageId, storageService); - super(container, viewsService, viewStates); + super(container, viewDescriptorService, viewStates); this.workspaceViewsStateStorageId = viewletStateStorageId; this.globalViewsStateStorageId = globalViewsStateStorageId; @@ -624,45 +627,170 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { } } -export class ViewsService extends Disposable implements IViewsService { +export class ViewOpenerService extends Disposable implements IViewsService { _serviceBrand: undefined; - private readonly viewDescriptorCollections: Map; + private readonly viewContainersRegistry: IViewContainersRegistry; private readonly viewDisposable: Map; + + constructor( + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IPanelService private readonly panelService: IPanelService, + @IViewletService private readonly viewletService: IViewletService + ) { + super(); + + this.viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); + this.viewDisposable = new Map(); + + this._register(toDisposable(() => { + this.viewDisposable.forEach(disposable => disposable.dispose()); + this.viewDisposable.clear(); + })); + + this._register(this.viewDescriptorService.onViewsRegistered(({ views, viewContainer }) => this.onViewsRegistered(views, viewContainer))); + this._register(this.viewDescriptorService.onViewsDeregistered(({ views, viewContainer }) => this.onViewsDeregistered(views, viewContainer))); + } + + private onViewsRegistered(views: IViewDescriptor[], container: ViewContainer): void { + const location = this.viewContainersRegistry.getViewContainerLocation(container); + if (location === undefined) { + return; + } + + const composite = this.getComposite(container.id, location); + for (const viewDescriptor of views) { + const disposables = new DisposableStore(); + const command: ICommandAction = { + id: viewDescriptor.focusCommand ? viewDescriptor.focusCommand.id : `${viewDescriptor.id}.focus`, + title: { original: `Focus on ${viewDescriptor.name} View`, value: localize('focus view', "Focus on {0} View", viewDescriptor.name) }, + category: composite ? composite.name : localize('view category', "View"), + }; + const when = ContextKeyExpr.has(`${viewDescriptor.id}.active`); + + disposables.add(CommandsRegistry.registerCommand(command.id, () => this.openView(viewDescriptor.id, true))); + + disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command, + when + })); + + if (viewDescriptor.focusCommand && viewDescriptor.focusCommand.keybindings) { + KeybindingsRegistry.registerKeybindingRule({ + id: command.id, + when, + weight: KeybindingWeight.WorkbenchContrib, + primary: viewDescriptor.focusCommand.keybindings.primary, + secondary: viewDescriptor.focusCommand.keybindings.secondary, + linux: viewDescriptor.focusCommand.keybindings.linux, + mac: viewDescriptor.focusCommand.keybindings.mac, + win: viewDescriptor.focusCommand.keybindings.win + }); + } + + this.viewDisposable.set(viewDescriptor, disposables); + } + } + + private onViewsDeregistered(views: IViewDescriptor[], container: ViewContainer): void { + for (const view of views) { + const disposable = this.viewDisposable.get(view); + if (disposable) { + disposable.dispose(); + this.viewDisposable.delete(view); + } + } + } + + + private async openComposite(compositeId: string, location: ViewContainerLocation, focus?: boolean): Promise { + if (location === ViewContainerLocation.Sidebar) { + return this.viewletService.openViewlet(compositeId, focus); + } else if (location === ViewContainerLocation.Panel) { + return this.panelService.openPanel(compositeId, focus) as IPaneComposite; + } + return undefined; + } + + private getComposite(compositeId: string, location: ViewContainerLocation): { id: string, name: string } | undefined { + if (location === ViewContainerLocation.Sidebar) { + return this.viewletService.getViewlet(compositeId); + } else if (location === ViewContainerLocation.Panel) { + return this.panelService.getPanel(compositeId); + } + + return undefined; + } + + async openView(id: string, focus: boolean): Promise { + const viewContainer = this.viewDescriptorService.getViewContainer(id); + if (viewContainer) { + const location = this.viewContainersRegistry.getViewContainerLocation(viewContainer); + const compositeDescriptor = this.getComposite(viewContainer.id, location!); + if (compositeDescriptor) { + const paneComposite = await this.openComposite(compositeDescriptor.id, location!, focus) as IPaneComposite | undefined; + if (paneComposite && paneComposite.openView) { + return paneComposite.openView(id, focus); + } + } + } + + return null; + } +} + +export class ViewDescriptorService extends Disposable implements IViewDescriptorService { + + _serviceBrand: undefined; + + private readonly _onViewsRegistered: Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }>()); + readonly onViewsRegistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._onViewsRegistered.event; + + private readonly _onViewsDeregistered: Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }>()); + readonly onViewsDeregistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._onViewsDeregistered.event; + + private readonly _onDidChangeContainer: Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>()); + readonly onDidChangeContainer: Event<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = this._onDidChangeContainer.event; + + private readonly viewDescriptorCollections: Map; + private readonly viewContainers: Map; private readonly activeViewContextKeys: Map>; + private readonly viewsRegistry: IViewsRegistry; + private readonly viewContainersRegistry: IViewContainersRegistry; + constructor( - @IViewletService private readonly viewletService: IViewletService, @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(); this.viewDescriptorCollections = new Map(); - this.viewDisposable = new Map(); + this.viewContainers = new Map(); this.activeViewContextKeys = new Map>(); - const viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); - const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); - viewContainersRegistry.all.forEach(viewContainer => { - this.onDidRegisterViews(viewContainer, viewsRegistry.getViews(viewContainer)); + this.viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); + this.viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); + this.viewContainersRegistry.all.forEach(viewContainer => { + this.onDidRegisterViews(viewContainer, this.viewsRegistry.getViews(viewContainer)); this.onDidRegisterViewContainer(viewContainer); }); - this._register(viewsRegistry.onViewsRegistered(({ views, viewContainer }) => this.onDidRegisterViews(viewContainer, views))); - this._register(viewsRegistry.onViewsDeregistered(({ views }) => this.onDidDeregisterViews(views))); - this._register(viewsRegistry.onDidChangeContainer(({ views, to }) => { this.onDidDeregisterViews(views); this.onDidRegisterViews(to, views); })); - this._register(toDisposable(() => { - this.viewDisposable.forEach(disposable => disposable.dispose()); - this.viewDisposable.clear(); - })); - this._register(viewContainersRegistry.onDidRegister(({ viewContainer }) => this.onDidRegisterViewContainer(viewContainer))); - this._register(viewContainersRegistry.onDidDeregister(({ viewContainer }) => this.onDidDeregisterViewContainer(viewContainer))); + this._register(this.viewsRegistry.onViewsRegistered(({ views, viewContainer }) => this.onDidRegisterViews(viewContainer, views))); + this._register(this.viewsRegistry.onViewsDeregistered(({ views, viewContainer }) => this.onDidDeregisterViews(viewContainer, views))); + this._register(this.viewsRegistry.onDidChangeContainer(({ views, from, to }) => { this.onDidDeregisterViews(from, views); this.onDidRegisterViews(to, views); this._onDidChangeContainer.fire({ views, from, to }); })); + + this._register(this.viewContainersRegistry.onDidRegister(({ viewContainer }) => this.onDidRegisterViewContainer(viewContainer))); + this._register(this.viewContainersRegistry.onDidDeregister(({ viewContainer }) => this.onDidDeregisterViewContainer(viewContainer))); this._register(toDisposable(() => { this.viewDescriptorCollections.forEach(({ disposable }) => disposable.dispose()); this.viewDescriptorCollections.clear(); })); } + getViewContainer(viewId: string): ViewContainer | null { + return this.viewContainers.get(viewId) ?? null; + } + getViewDescriptors(container: ViewContainer): IViewDescriptorCollection | null { const registeredViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).get(container.id); if (registeredViewContainer) { @@ -677,24 +805,28 @@ export class ViewsService extends Disposable implements IViewsService { return null; } - async openView(id: string, focus: boolean): Promise { - const viewContainer = Registry.as(ViewExtensions.ViewsRegistry).getViewContainer(id); - if (viewContainer) { - const viewletDescriptor = this.viewletService.getViewlet(viewContainer.id); - if (viewletDescriptor) { - const viewlet = await this.viewletService.openViewlet(viewletDescriptor.id, focus) as IViewsViewlet | null; - if (viewlet && viewlet.openView) { - return viewlet.openView(id, focus); - } - } + moveViews(views: IViewDescriptor[], viewContainer: ViewContainer): void { + if (!views.length) { + return; } - return null; + const from = this.viewContainers.get(views[0].id); + const to = viewContainer; + + if (from && to && from !== to) { + this.onDidDeregisterViews(from, views); + this.onDidRegisterViews(viewContainer, views); + this._onDidChangeContainer.fire({ views, from, to }); + } + } + + getViews(container: ViewContainer): IViewDescriptor[] { + return this.viewsRegistry.getViews(container); } private onDidRegisterViewContainer(viewContainer: ViewContainer): void { const disposables = new DisposableStore(); - const viewDescriptorCollection = disposables.add(new ViewDescriptorCollection(viewContainer, this.contextKeyService)); + const viewDescriptorCollection = disposables.add(new ViewDescriptorCollection(viewContainer, this.contextKeyService, this)); this.onDidChangeActiveViews({ added: viewDescriptorCollection.activeViewDescriptors, removed: [] }); viewDescriptorCollection.onDidChangeActiveViews(changed => this.onDidChangeActiveViews(changed), this, disposables); @@ -716,48 +848,24 @@ export class ViewsService extends Disposable implements IViewsService { } private onDidRegisterViews(container: ViewContainer, views: IViewDescriptor[]): void { - const viewlet = this.viewletService.getViewlet(container.id); - for (const viewDescriptor of views) { - const disposables = new DisposableStore(); - const command: ICommandAction = { - id: viewDescriptor.focusCommand ? viewDescriptor.focusCommand.id : `${viewDescriptor.id}.focus`, - title: { original: `Focus on ${viewDescriptor.name} View`, value: localize('focus view', "Focus on {0} View", viewDescriptor.name) }, - category: viewlet ? viewlet.name : localize('view category', "View"), - }; - const when = ContextKeyExpr.has(`${viewDescriptor.id}.active`); - - disposables.add(CommandsRegistry.registerCommand(command.id, () => this.openView(viewDescriptor.id, true))); - - disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command, - when - })); - - if (viewDescriptor.focusCommand && viewDescriptor.focusCommand.keybindings) { - KeybindingsRegistry.registerKeybindingRule({ - id: command.id, - when, - weight: KeybindingWeight.WorkbenchContrib, - primary: viewDescriptor.focusCommand.keybindings.primary, - secondary: viewDescriptor.focusCommand.keybindings.secondary, - linux: viewDescriptor.focusCommand.keybindings.linux, - mac: viewDescriptor.focusCommand.keybindings.mac, - win: viewDescriptor.focusCommand.keybindings.win - }); - } + const location = this.viewContainersRegistry.getViewContainerLocation(container); + if (location === undefined) { + return; + } - this.viewDisposable.set(viewDescriptor, disposables); + for (const viewDescriptor of views) { + this.viewContainers.set(viewDescriptor.id, container); } + + this._onViewsRegistered.fire({ views, viewContainer: container }); } - private onDidDeregisterViews(views: IViewDescriptor[]): void { + private onDidDeregisterViews(container: ViewContainer, views: IViewDescriptor[]): void { for (const view of views) { - const disposable = this.viewDisposable.get(view); - if (disposable) { - disposable.dispose(); - this.viewDisposable.delete(view); - } + this.viewContainers.delete(view.id); } + + this._onViewsDeregistered.fire({ views, viewContainer: container }); } private getOrCreateActiveViewContextKey(viewDescriptor: IViewDescriptor): IContextKey { @@ -784,4 +892,5 @@ export function createFileIconThemableTreeContainerScope(container: HTMLElement, return themeService.onDidFileIconThemeChange(onDidChangeFileIconTheme); } -registerSingleton(IViewsService, ViewsService); +registerSingleton(IViewDescriptorService, ViewDescriptorService); +registerSingleton(IViewsService, ViewOpenerService); diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 174df04dff654..72f90b88cd6da 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -100,6 +100,11 @@ export interface IViewContainersRegistry { * Returns all view containers in the given location */ getViewContainers(location: ViewContainerLocation): ViewContainer[]; + + /** + * Returns the view container location + */ + getViewContainerLocation(container: ViewContainer): ViewContainerLocation | undefined; } interface ViewOrderDelegate { @@ -157,6 +162,10 @@ class ViewContainersRegistryImpl extends Disposable implements IViewContainersRe getViewContainers(location: ViewContainerLocation): ViewContainer[] { return [...(this.viewContainers.get(location) || [])]; } + + getViewContainerLocation(container: ViewContainer): ViewContainerLocation | undefined { + return keys(this.viewContainers).filter(location => this.getViewContainers(location).filter(viewContainer => viewContainer.id === container.id).length > 0)[0]; + } } Registry.add(Extensions.ViewContainersRegistry, new ViewContainersRegistryImpl()); @@ -336,14 +345,34 @@ export interface IViewsViewlet extends IViewlet { } +export const IViewDescriptorService = createDecorator('viewDescriptorService'); export const IViewsService = createDecorator('viewsService'); + export interface IViewsService { _serviceBrand: undefined; openView(id: string, focus?: boolean): Promise; +} + + +export interface IViewDescriptorService { + + _serviceBrand: undefined; + + readonly onViewsRegistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }>; + + readonly onViewsDeregistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }>; + + readonly onDidChangeContainer: Event<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>; + + moveViews(views: IViewDescriptor[], viewContainer: ViewContainer): void; + + getViews(container: ViewContainer): IViewDescriptor[]; getViewDescriptors(container: ViewContainer): IViewDescriptorCollection | null; + + getViewContainer(viewId: string): ViewContainer | null; } // Custom views diff --git a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts index d95ee1360d0cd..bbc5693905b31 100644 --- a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts +++ b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts @@ -4,16 +4,56 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { IViewsRegistry, IViewDescriptor, Extensions as ViewExtensions } from 'vs/workbench/common/views'; +import { IViewsRegistry, IViewDescriptor, Extensions as ViewExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; import { OutlinePane } from './outlinePane'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { OutlineConfigKeys, OutlineViewId } from 'vs/editor/contrib/documentSymbols/outline'; import { VIEW_CONTAINER } from 'vs/workbench/contrib/files/browser/explorerViewlet'; +import { Action } from 'vs/base/common/actions'; +import { IWorkbenchActionRegistry, Extensions as ActionsExtensions } from 'vs/workbench/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; // import './outlineNavigation'; +export const PANEL_ID = 'panel.view.outline'; + +export class OutlineViewPaneContainer extends ViewPaneContainer { + constructor( + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ITelemetryService telemetryService: ITelemetryService, + @IWorkspaceContextService protected contextService: IWorkspaceContextService, + @IStorageService protected storageService: IStorageService, + @IConfigurationService configurationService: IConfigurationService, + @IInstantiationService protected instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService, + ) { + super(PANEL_ID, `${PANEL_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + } +} + +export const VIEW_CONTAINER_PANEL: ViewContainer = + Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ + id: PANEL_ID, + ctorDescriptor: new SyncDescriptor(OutlineViewPaneContainer), + name: localize('name', "Outline"), + hideIfEmpty: true + }, ViewContainerLocation.Panel); + + const _outlineDesc = { id: OutlineViewId, name: localize('name', "Outline"), @@ -28,6 +68,41 @@ const _outlineDesc = { Registry.as(ViewExtensions.ViewsRegistry).registerViews([_outlineDesc], VIEW_CONTAINER); +let inPanel = false; + +export class ToggleOutlinePositionAction extends Action { + + static ID = 'outline.view.togglePosition'; + static LABEL = 'Toggle Outline View Position'; + + constructor( + id: string, + label: string, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IViewsService private readonly viewsService: IViewsService + ) { + super(id, label, '', true); + } + + async run(): Promise { + if (!inPanel) { + this.viewDescriptorService.moveViews([_outlineDesc], VIEW_CONTAINER_PANEL); + this.viewsService.openView(OutlineViewId, true); + inPanel = true; + } else { + this.viewDescriptorService.moveViews([_outlineDesc], VIEW_CONTAINER); + this.viewsService.openView(OutlineViewId, true); + + inPanel = false; + } + + } +} + +Registry.as(ActionsExtensions.WorkbenchActions) + .registerWorkbenchAction(SyncActionDescriptor.create(ToggleOutlinePositionAction, ToggleOutlinePositionAction.ID, ToggleOutlinePositionAction.LABEL), 'Show Release Notes'); + + Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ 'id': 'outline', 'order': 117, diff --git a/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts b/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts index 587dc075b247b..eefa445672855 100644 --- a/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts @@ -16,7 +16,7 @@ import { Action } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { fuzzyContains, stripWildcards } from 'vs/base/common/strings'; import { matchesFuzzy } from 'vs/base/common/filters'; -import { IViewsRegistry, ViewContainer, IViewsService, IViewContainersRegistry, Extensions as ViewExtensions } from 'vs/workbench/common/views'; +import { IViewsRegistry, ViewContainer, IViewDescriptorService, IViewContainersRegistry, Extensions as ViewExtensions, IViewsService } from 'vs/workbench/common/views'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -70,6 +70,7 @@ export class ViewPickerHandler extends QuickOpenHandler { constructor( @IViewletService private readonly viewletService: IViewletService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @IViewsService private readonly viewsService: IViewsService, @IOutputService private readonly outputService: IOutputService, @ITerminalService private readonly terminalService: ITerminalService, @@ -197,7 +198,7 @@ export class ViewPickerHandler extends QuickOpenHandler { private hasToShowViewlet(viewlet: ViewletDescriptor): boolean { const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).get(viewlet.id); if (viewContainer?.hideIfEmpty) { - const viewsCollection = this.viewsService.getViewDescriptors(viewContainer); + const viewsCollection = this.viewDescriptorService.getViewDescriptors(viewContainer); return !!viewsCollection && viewsCollection.activeViewDescriptors.length > 0; } return true; diff --git a/src/vs/workbench/test/browser/parts/views/views.test.ts b/src/vs/workbench/test/browser/parts/views/views.test.ts index a5244e4de17c9..49502d7bec95e 100644 --- a/src/vs/workbench/test/browser/parts/views/views.test.ts +++ b/src/vs/workbench/test/browser/parts/views/views.test.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ContributableViewsModel, ViewsService, IViewState } from 'vs/workbench/browser/parts/views/views'; -import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { ContributableViewsModel, ViewDescriptorService, IViewState } from 'vs/workbench/browser/parts/views/views'; +import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { move } from 'vs/base/common/arrays'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -38,14 +38,14 @@ class ViewDescriptorSequence { suite('ContributableViewsModel', () => { - let viewsService: IViewsService; + let viewDescriptorService: IViewDescriptorService; let contextKeyService: IContextKeyService; setup(() => { const instantiationService: TestInstantiationService = workbenchInstantiationService(); contextKeyService = instantiationService.createInstance(ContextKeyService); instantiationService.stub(IContextKeyService, contextKeyService); - viewsService = instantiationService.createInstance(ViewsService); + viewDescriptorService = instantiationService.createInstance(ViewDescriptorService); }); teardown(() => { @@ -53,12 +53,12 @@ suite('ContributableViewsModel', () => { }); test('empty model', function () { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); assert.equal(model.visibleViewDescriptors.length, 0); }); test('register/unregister', () => { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); const seq = new ViewDescriptorSequence(model); assert.equal(model.visibleViewDescriptors.length, 0); @@ -84,7 +84,7 @@ suite('ContributableViewsModel', () => { }); test('when contexts', async function () { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); const seq = new ViewDescriptorSequence(model); assert.equal(model.visibleViewDescriptors.length, 0); @@ -128,7 +128,7 @@ suite('ContributableViewsModel', () => { }); test('when contexts - multiple', async function () { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); const seq = new ViewDescriptorSequence(model); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' }; @@ -151,7 +151,7 @@ suite('ContributableViewsModel', () => { }); test('when contexts - multiple 2', async function () { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); const seq = new ViewDescriptorSequence(model); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', when: ContextKeyExpr.equals('showview1', true) }; @@ -174,7 +174,7 @@ suite('ContributableViewsModel', () => { }); test('setVisible', () => { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); const seq = new ViewDescriptorSequence(model); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', canToggleVisibility: true }; @@ -219,7 +219,7 @@ suite('ContributableViewsModel', () => { }); test('move', () => { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); const seq = new ViewDescriptorSequence(model); const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' }; @@ -250,7 +250,7 @@ suite('ContributableViewsModel', () => { test('view states', async function () { const viewStates = new Map(); viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); - const model = new ContributableViewsModel(container, viewsService, viewStates); + const model = new ContributableViewsModel(container, viewDescriptorService, viewStates); const seq = new ViewDescriptorSequence(model); assert.equal(model.visibleViewDescriptors.length, 0); @@ -270,7 +270,7 @@ suite('ContributableViewsModel', () => { test('view states and when contexts', async function () { const viewStates = new Map(); viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); - const model = new ContributableViewsModel(container, viewsService, viewStates); + const model = new ContributableViewsModel(container, viewDescriptorService, viewStates); const seq = new ViewDescriptorSequence(model); assert.equal(model.visibleViewDescriptors.length, 0); @@ -300,7 +300,7 @@ suite('ContributableViewsModel', () => { test('view states and when contexts multiple views', async function () { const viewStates = new Map(); viewStates.set('view1', { visibleGlobal: false, collapsed: false, visibleWorkspace: undefined }); - const model = new ContributableViewsModel(container, viewsService, viewStates); + const model = new ContributableViewsModel(container, viewDescriptorService, viewStates); const seq = new ViewDescriptorSequence(model); assert.equal(model.visibleViewDescriptors.length, 0); @@ -344,7 +344,7 @@ suite('ContributableViewsModel', () => { }); test('remove event is not triggered if view was hidden and removed', async function () { - const model = new ContributableViewsModel(container, viewsService); + const model = new ContributableViewsModel(container, viewDescriptorService); const seq = new ViewDescriptorSequence(model); const viewDescriptor: IViewDescriptor = { From 85903e849feed2304687a7fd616a26cd5d86b2dd Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Mon, 13 Jan 2020 10:40:38 -0800 Subject: [PATCH 239/315] missed rename for viewsservice --- src/vs/workbench/browser/parts/views/views.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 91ca9c36bd059..b2911ed666303 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -627,7 +627,7 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { } } -export class ViewOpenerService extends Disposable implements IViewsService { +export class ViewsService extends Disposable implements IViewsService { _serviceBrand: undefined; @@ -893,4 +893,4 @@ export function createFileIconThemableTreeContainerScope(container: HTMLElement, } registerSingleton(IViewDescriptorService, ViewDescriptorService); -registerSingleton(IViewsService, ViewOpenerService); +registerSingleton(IViewsService, ViewsService); From 9fa325deec33aa93c359e2e2e529c008596712ce Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Mon, 13 Jan 2020 10:58:53 -0800 Subject: [PATCH 240/315] Fix search editor URI malform error --- src/vs/workbench/contrib/search/browser/searchEditorCommands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts index 6d9e6ee65ee57..07177d95ea8da 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts @@ -85,7 +85,7 @@ export class SearchEditorInput extends EditorInput { } getResource(): URI { - return URI.from({ scheme: 'untitled', authority: 'search-editor', path: this.config.query, fragment: `${this.instanceNumber}` }); + return URI.from({ scheme: 'search-editor', fragment: `${this.instanceNumber}` }); } getName(): string { From 1de4563f26184c98d3297e8d058380a7258b50b4 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 13 Jan 2020 11:47:25 -0800 Subject: [PATCH 241/315] fix(debug): don't insert task boundaries between events (#88459) * fix(debug): don't insert task boundaries between events See discussion in https://github.com/microsoft/vscode-js-debug/issues/206. This PR adjusts logic such that we only assert task boundaries around requests and responses, rather than around every single event. I believe this will solve the primary case where misordering can happen, as given in the existing unit test and described more verbosely in the doc comment in this PR. A more conservative approach would be to only omit the boundary between events of the same type. That would be safer, but I browsing through the code I didn't see any cases where it looked like we could get tripped up by bucketing here (e.g. cases where we resolve a deferred promise in an event handler). Seems to fix the performance issue for me. * fixup! add unit tests for ordering --- .../debug/common/abstractDebugAdapter.ts | 102 ++++++++++++------ .../debug/test/common/rawDebugSession.test.ts | 52 +++++++++ 2 files changed, 121 insertions(+), 33 deletions(-) create mode 100644 src/vs/workbench/contrib/debug/test/common/rawDebugSession.test.ts diff --git a/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts b/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts index f060ba6c665c0..93a76d52c1efc 100644 --- a/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts +++ b/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts @@ -5,20 +5,19 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IDebugAdapter } from 'vs/workbench/contrib/debug/common/debug'; -import { timeout, Queue } from 'vs/base/common/async'; +import { timeout } from 'vs/base/common/async'; /** * Abstract implementation of the low level API for a debug adapter. * Missing is how this API communicates with the debug adapter. */ export abstract class AbstractDebugAdapter implements IDebugAdapter { - private sequence: number; private pendingRequests = new Map void>(); private requestCallback: ((request: DebugProtocol.Request) => void) | undefined; private eventCallback: ((request: DebugProtocol.Event) => void) | undefined; private messageCallback: ((message: DebugProtocol.ProtocolMessage) => void) | undefined; - private readonly queue = new Queue(); + private queue: DebugProtocol.ProtocolMessage[] = []; protected readonly _onError = new Emitter(); protected readonly _onExit = new Emitter(); @@ -64,8 +63,7 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { sendResponse(response: DebugProtocol.Response): void { if (response.seq > 0) { this._onError.fire(new Error(`attempt to send more than one response for command ${response.command}`)); - } - else { + } else { this.internalSend('response', response); } } @@ -107,35 +105,73 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { acceptMessage(message: DebugProtocol.ProtocolMessage): void { if (this.messageCallback) { this.messageCallback(message); + } else { + this.queue.push(message); + if (this.queue.length === 1) { + // first item = need to start processing loop + this.processQueue(); + } } - else { - this.queue.queue(() => { - switch (message.type) { - case 'event': - if (this.eventCallback) { - this.eventCallback(message); - } - break; - case 'request': - if (this.requestCallback) { - this.requestCallback(message); - } - break; - case 'response': - const response = message; - const clb = this.pendingRequests.get(response.request_seq); - if (clb) { - this.pendingRequests.delete(response.request_seq); - clb(response); - } - break; - } + } + + /** + * Returns whether we should insert a timeout between processing messageA + * and messageB. Artificially queueing protocol messages guarantees that any + * microtasks for previous message finish before next message is processed. + * This is essential ordering when using promises anywhere along the call path. + * + * For example, take the following, where `chooseAndSendGreeting` returns + * a person name and then emits a greeting event: + * + * ``` + * let person: string; + * adapter.onGreeting(() => console.log('hello', person)); + * person = await adapter.chooseAndSendGreeting(); + * ``` + * + * Because the event is dispatched synchronously, it may fire before person + * is assigned if they're processed in the same task. Inserting a task + * boundary avoids this issue. + */ + protected needsTaskBoundaryBetween(messageA: DebugProtocol.ProtocolMessage, messageB: DebugProtocol.ProtocolMessage) { + return messageA.type !== 'event' || messageB.type !== 'event'; + } - // Artificially queueing protocol messages guarantees that any microtasks for - // previous message finish before next message is processed. This is essential - // to guarantee ordering when using promises anywhere along the call path. - return timeout(0); - }); + /** + * Reads and dispatches items from the queue until it is empty. + */ + private async processQueue() { + let message: DebugProtocol.ProtocolMessage | undefined; + while (this.queue.length) { + if (!message || this.needsTaskBoundaryBetween(this.queue[0], message)) { + await timeout(0); + } + + message = this.queue.shift(); + if (!message) { + return; // may have been disposed of + } + + switch (message.type) { + case 'event': + if (this.eventCallback) { + this.eventCallback(message); + } + break; + case 'request': + if (this.requestCallback) { + this.requestCallback(message); + } + break; + case 'response': + const response = message; + const clb = this.pendingRequests.get(response.request_seq); + if (clb) { + this.pendingRequests.delete(response.request_seq); + clb(response); + } + break; + } } } @@ -172,6 +208,6 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { } dispose(): void { - this.queue.dispose(); + this.queue = []; } } diff --git a/src/vs/workbench/contrib/debug/test/common/rawDebugSession.test.ts b/src/vs/workbench/contrib/debug/test/common/rawDebugSession.test.ts new file mode 100644 index 0000000000000..16c02f6cb0277 --- /dev/null +++ b/src/vs/workbench/contrib/debug/test/common/rawDebugSession.test.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { MockDebugAdapter } from 'vs/workbench/contrib/debug/test/common/mockDebug'; +import { timeout } from 'vs/base/common/async'; + +suite('Debug - AbstractDebugAdapter', () => { + suite('event ordering', () => { + let adapter: MockDebugAdapter; + let output: string[]; + setup(() => { + adapter = new MockDebugAdapter(); + output = []; + adapter.onEvent(ev => { + output.push((ev as DebugProtocol.OutputEvent).body.output); + Promise.resolve().then(() => output.push('--end microtask--')); + }); + }); + + const evaluate = async (expression: string) => { + await new Promise(resolve => adapter.sendRequest('evaluate', { expression }, resolve)); + output.push(`=${expression}`); + Promise.resolve().then(() => output.push('--end microtask--')); + }; + + test('inserts task boundary before response', async () => { + await evaluate('before.foo'); + await timeout(0); + + assert.deepStrictEqual(output, ['before.foo', '--end microtask--', '=before.foo', '--end microtask--']); + }); + + test('inserts task boundary after response', async () => { + await evaluate('after.foo'); + await timeout(0); + + assert.deepStrictEqual(output, ['=after.foo', '--end microtask--', 'after.foo', '--end microtask--']); + }); + + test('does not insert boundaries between events', async () => { + adapter.sendEventBody('output', { output: 'a' }); + adapter.sendEventBody('output', { output: 'b' }); + adapter.sendEventBody('output', { output: 'c' }); + await timeout(0); + + assert.deepStrictEqual(output, ['a', 'b', 'c', '--end microtask--', '--end microtask--', '--end microtask--']); + }); + }); +}); From 7ef5ef6e952e4eaeb32d0258612f08fbb439b849 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 13 Jan 2020 21:06:57 +0100 Subject: [PATCH 242/315] polish html sem highlighting --- .../server/src/modes/javascriptSemanticTokens.ts | 4 ++-- .../server/src/test/semanticTokens.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts index 3c7b620ab8c74..38a87159a327f 100644 --- a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts +++ b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts @@ -17,7 +17,7 @@ export function getSemanticTokens(jsLanguageService: ts.LanguageService, current const typeChecker = program.getTypeChecker(); function visit(node: ts.Node) { - if (node.kind === ts.SyntaxKind.Identifier) { + if (ts.isIdentifier(node)) { const symbol = typeChecker.getSymbolAtLocation(node); if (symbol) { let typeIdx = classifySymbol(symbol); @@ -76,7 +76,7 @@ function classifySymbol(symbol: ts.Symbol) { } } const decl = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0]; - return tokenFromDeclarationMapping[decl.kind]; + return decl && tokenFromDeclarationMapping[decl.kind]; } diff --git a/extensions/html-language-features/server/src/test/semanticTokens.test.ts b/extensions/html-language-features/server/src/test/semanticTokens.test.ts index 384d1467d0b7c..ea3777e84e90b 100644 --- a/extensions/html-language-features/server/src/test/semanticTokens.test.ts +++ b/extensions/html-language-features/server/src/test/semanticTokens.test.ts @@ -147,7 +147,7 @@ suite('HTML Semantic Tokens', () => { /*3*/' const f = 9;', /*4*/' class A { static readonly t = 9; static url: URL; }', /*5*/' const enum E { A = 9, B = A + 1 }', - /*6*/' const x = f + A.t + A.url.origin;', + /*6*/' console.log(f + A.t + A.url.origin);', /*7*/'', /*8*/'', /*9*/'', @@ -156,7 +156,7 @@ suite('HTML Semantic Tokens', () => { t(3, 8, 1, 'variable.declaration.readonly'), t(4, 8, 1, 'class.declaration'), t(4, 28, 1, 'property.declaration.static.readonly'), t(4, 42, 3, 'property.declaration.static'), t(4, 47, 3, 'interface'), t(5, 13, 1, 'enum.declaration'), t(5, 17, 1, 'property.declaration.readonly'), t(5, 24, 1, 'property.declaration.readonly'), t(5, 28, 1, 'property.readonly'), - t(6, 8, 1, 'variable.declaration.readonly'), t(6, 12, 1, 'variable.readonly'), t(6, 16, 1, 'class'), t(6, 18, 1, 'property.static.readonly'), t(6, 22, 1, 'class'), t(6, 24, 3, 'property.static'), t(6, 28, 6, 'property.readonly'), + t(6, 2, 7, 'variable'), t(6, 10, 3, 'member'), t(6, 14, 1, 'variable.readonly'), t(6, 18, 1, 'class'), t(6, 20, 1, 'property.static.readonly'), t(6, 24, 1, 'class'), t(6, 26, 3, 'property.static'), t(6, 30, 6, 'property.readonly'), ]); }); From 80c6acdf312c6486e12c9629602d9783734db2b7 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 13 Jan 2020 21:06:05 +0100 Subject: [PATCH 243/315] fix issue with accessibility provider, #88554 --- .../contrib/bulkEdit/browser/bulkEditTree.ts | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index 188c05694422e..bbd92f7b31a4f 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -118,38 +118,44 @@ export class BulkEditAccessibilityProvider implements IAccessibilityProvider 0) { if (element.edit.type & BulkFileOperationType.Rename && element.edit.newUri) { return localize( - 'area.renameAndEdit', "Renaming {0} to {1}, also making text edits", + 'aria.renameAndEdit', "Renaming {0} to {1}, also making text edits", this._labelService.getUriLabel(element.edit.uri, { relative: true }), this._labelService.getUriLabel(element.edit.newUri, { relative: true }) ); } else if (element.edit.type & BulkFileOperationType.Create) { return localize( - 'area.createAndEdit', "Creating {0}, also making text edits", + 'aria.createAndEdit', "Creating {0}, also making text edits", this._labelService.getUriLabel(element.edit.uri, { relative: true }) ); } else if (element.edit.type & BulkFileOperationType.Delete) { return localize( - 'area.deleteAndEdit', "Deleting {0}, also making text edits", + 'aria.deleteAndEdit', "Deleting {0}, also making text edits", + this._labelService.getUriLabel(element.edit.uri, { relative: true }), + ); + } else { + return localize( + 'aria.editOnly', "{0}, making text edits", this._labelService.getUriLabel(element.edit.uri, { relative: true }), ); } + } else { if (element.edit.type & BulkFileOperationType.Rename && element.edit.newUri) { return localize( - 'area.rename', "Renaming {0} to {1}", + 'aria.rename', "Renaming {0} to {1}", this._labelService.getUriLabel(element.edit.uri, { relative: true }), this._labelService.getUriLabel(element.edit.newUri, { relative: true }) ); } else if (element.edit.type & BulkFileOperationType.Create) { return localize( - 'area.create', "Creating {0}", + 'aria.create', "Creating {0}", this._labelService.getUriLabel(element.edit.uri, { relative: true }) ); } else if (element.edit.type & BulkFileOperationType.Delete) { return localize( - 'area.delete', "Deleting {0}", + 'aria.delete', "Deleting {0}", this._labelService.getUriLabel(element.edit.uri, { relative: true }), ); } @@ -159,7 +165,7 @@ export class BulkEditAccessibilityProvider implements IAccessibilityProvider 0 && element.inserting.length > 0) { // edit: replace - return localize('aria.replace', "line {0}, replacing {1} with {0}", element.edit.edit.range.startLineNumber, element.selecting, element.inserting); + return localize('aria.replace', "line {0}, replacing {1} with {2}", element.edit.edit.range.startLineNumber, element.selecting, element.inserting); } else if (element.selecting.length > 0 && element.inserting.length === 0) { // edit: delete return localize('aria.del', "line {0}, removing {1}", element.edit.edit.range.startLineNumber, element.selecting); From 1ea4d14894bec4809ed21132c8c3d8346e79deb7 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 13 Jan 2020 21:17:21 +0100 Subject: [PATCH 244/315] add AriaProvider, #88553 --- .../contrib/bulkEdit/browser/bulkEditPane.ts | 3 ++- .../contrib/bulkEdit/browser/bulkEditTree.ts | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index 13e60a3218abe..d0310fcc72724 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -6,7 +6,7 @@ import 'vs/css!./bulkEdit'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { WorkspaceEdit } from 'vs/editor/common/modes'; -import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; +import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider, BulkEditAriaProvider } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; import { FuzzyScore } from 'vs/base/common/filters'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; @@ -97,6 +97,7 @@ export class BulkEditPane extends ViewPane { this._instaService.createInstance(BulkEditDataSource), { accessibilityProvider: this._instaService.createInstance(BulkEditAccessibilityProvider), + ariaProvider: new BulkEditAriaProvider(), identityProvider: new BulkEditIdentityProvider(), expandOnlyOnTwistieClick: true, multipleSelectionSupport: false diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index bbd92f7b31a4f..2488030bcfa80 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -20,6 +20,7 @@ import { FileKind } from 'vs/platform/files/common/files'; import { localize } from 'vs/nls'; import { ILabelService } from 'vs/platform/label/common/label'; import type { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import type { IAriaProvider } from 'vs/base/browser/ui/list/listView'; // --- VIEW MODEL @@ -192,6 +193,21 @@ export class BulkEditIdentityProvider implements IIdentityProvider { + + getSetSize(_element: BulkEditElement, _index: number, listLength: number): number { + return listLength; + } + + getPosInSet(_element: BulkEditElement, index: number): number { + return index; + } + + getRole?(_element: BulkEditElement): string { + return 'checkbox'; + } +} + // --- RENDERER class FileElementTemplate { From 2bbe9c533005e97f163ed38e1cfe85ea23787aeb Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 13 Jan 2020 12:47:04 -0800 Subject: [PATCH 245/315] Only override editor opening for resource that have potential custom editors For #88525 --- .../contrib/customEditor/browser/customEditors.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 25ce3d2fc38e1..bc76a68485d57 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -347,9 +347,15 @@ export class CustomEditorContribution implements IWorkbenchContribution { options: ITextEditorOptions | undefined, group: IEditorGroup ): IOpenEditorOverride | undefined { + const userConfiguredEditors = this.customEditorService.getUserConfiguredCustomEditors(resource); + const contributedEditors = this.customEditorService.getContributedCustomEditors(resource); + if (!userConfiguredEditors.length && !contributedEditors.length) { + return; + } + // Check to see if there already an editor for the resource in the group. // If there is, we want to open that instead of creating a new editor. - // This ensures that we preserve whatever state the editor was previously in + // This ensures that we preserve whatever type of editor was previously being used // when the user switches back to it. const existingEditorForResource = group.editors.find(editor => isEqual(resource, editor.getResource())); if (existingEditorForResource) { @@ -358,14 +364,12 @@ export class CustomEditorContribution implements IWorkbenchContribution { }; } - const userConfiguredEditors = this.customEditorService.getUserConfiguredCustomEditors(resource); if (userConfiguredEditors.length) { return { override: this.customEditorService.openWith(resource, userConfiguredEditors.allEditors[0].id, options, group), }; } - const contributedEditors = this.customEditorService.getContributedCustomEditors(resource); if (!contributedEditors.length) { return; } From 904951e31f3279c8356cab225ceb4b44e9d1e3c8 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 13 Jan 2020 20:18:56 -0800 Subject: [PATCH 246/315] Only return documentation code action in refactor menu --- .../contrib/codeActions/common/documentationContribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/codeActions/common/documentationContribution.ts b/src/vs/workbench/contrib/codeActions/common/documentationContribution.ts index cc26586e2b4df..17cf4fa1d9f53 100644 --- a/src/vs/workbench/contrib/codeActions/common/documentationContribution.ts +++ b/src/vs/workbench/contrib/codeActions/common/documentationContribution.ts @@ -57,7 +57,7 @@ export class CodeActionDocumentationContribution extends Disposable implements I } async provideCodeActions(_model: ITextModel, _range: Range | Selection, context: CodeActionContext, _token: CancellationToken): Promise { - if (!context.only || !CodeActionKind.Refactor.contains(new CodeActionKind(context.only))) { + if (CodeActionKind.Refactor.value !== context.only) { return { actions: [], dispose: () => { } From c044f090ce4398f717fff28689d2d45713166ee8 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 13 Jan 2020 20:21:48 -0800 Subject: [PATCH 247/315] Remove unused vars --- src/vs/editor/contrib/codeAction/codeActionCommands.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/codeActionCommands.ts index 8185eca6b17cc..6c4912f4a931e 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -16,21 +16,19 @@ import { IPosition } from 'vs/editor/common/core/position'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { CodeAction } from 'vs/editor/common/modes'; -import { CodeActionSet, refactorCommandId, sourceActionCommandId, codeActionCommandId, organizeImportsCommandId, fixAllCommandId } from 'vs/editor/contrib/codeAction/codeAction'; +import { codeActionCommandId, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/codeAction'; import { CodeActionUi } from 'vs/editor/contrib/codeAction/codeActionUi'; import { MessageController } from 'vs/editor/contrib/message/messageController'; import * as nls from 'vs/nls'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { CodeActionModel, CodeActionsState, SUPPORTED_CODE_ACTIONS } from './codeActionModel'; -import { CodeActionAutoApply, CodeActionFilter, CodeActionKind, CodeActionTrigger, CodeActionCommandArgs } from './types'; +import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionFilter, CodeActionKind, CodeActionTrigger } from './types'; function contextKeyForSupportedActions(kind: CodeActionKind) { return ContextKeyExpr.regex( @@ -83,8 +81,6 @@ export class QuickFixController extends Disposable implements IEditorContributio @IMarkerService markerService: IMarkerService, @IContextKeyService contextKeyService: IContextKeyService, @IEditorProgressService progressService: IEditorProgressService, - @IContextMenuService contextMenuService: IContextMenuService, - @IKeybindingService keybindingService: IKeybindingService, @ICommandService private readonly _commandService: ICommandService, @IBulkEditService private readonly _bulkEditService: IBulkEditService, @IInstantiationService private readonly _instantiationService: IInstantiationService, From 560f922d6f008b5aaac6dbbdfc7368128ed847c6 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 13 Jan 2020 20:34:13 -0800 Subject: [PATCH 248/315] Add telemetry on which code actions are applied We are trying to improve the discoverability of code actions. To do this, we need to have a baseline of how often people are applying actions such as refactorings so that we can measure if changes like adding documentation have an impact on their usage rates. Ref #86788 --- .../contrib/codeAction/codeActionCommands.ts | 29 +++++++++++++++---- .../api/browser/mainThreadSaveParticipant.ts | 7 ++--- .../markers/browser/markersTreeViewer.ts | 6 +--- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/codeActionCommands.ts index 6c4912f4a931e..ce30a8227fdce 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -27,6 +27,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { IMarkerService } from 'vs/platform/markers/common/markers'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CodeActionModel, CodeActionsState, SUPPORTED_CODE_ACTIONS } from './codeActionModel'; import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionFilter, CodeActionKind, CodeActionTrigger } from './types'; @@ -81,8 +82,6 @@ export class QuickFixController extends Disposable implements IEditorContributio @IMarkerService markerService: IMarkerService, @IContextKeyService contextKeyService: IContextKeyService, @IEditorProgressService progressService: IEditorProgressService, - @ICommandService private readonly _commandService: ICommandService, - @IBulkEditService private readonly _bulkEditService: IBulkEditService, @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { super(); @@ -133,21 +132,41 @@ export class QuickFixController extends Disposable implements IEditorContributio } private _applyCodeAction(action: CodeAction): Promise { - return this._instantiationService.invokeFunction(applyCodeAction, action, this._bulkEditService, this._commandService, this._editor); + return this._instantiationService.invokeFunction(applyCodeAction, action, this._editor); } } export async function applyCodeAction( accessor: ServicesAccessor, action: CodeAction, - bulkEditService: IBulkEditService, - commandService: ICommandService, editor?: ICodeEditor, ): Promise { + const bulkEditService = accessor.get(IBulkEditService); + const commandService = accessor.get(ICommandService); + const telemetryService = accessor.get(ITelemetryService); const notificationService = accessor.get(INotificationService); + + type ApplyCodeActionEvent = { + codeActionTitle: string; + codeActionKind: string | undefined; + codeActionIsPreferred: boolean; + }; + type ApplyCodeEventClassification = { + codeActionTitle: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + codeActionKind: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + codeActionIsPreferred: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + }; + + telemetryService.publicLog2('codeAction.applyCodeAction', { + codeActionTitle: action.title, + codeActionKind: action.kind, + codeActionIsPreferred: !!action.isPreferred, + }); + if (action.edit) { await bulkEditService.apply(action.edit, { editor }); } + if (action.command) { try { await commandService.executeCommand(action.command.id, ...(action.command.arguments || [])); diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index 64d9ae97df772..764487cde6fb9 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -7,7 +7,6 @@ import { IdleValue } from 'vs/base/common/async'; import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; import * as strings from 'vs/base/common/strings'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { trimTrailingWhitespace } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand'; import { EditOperation } from 'vs/editor/common/core/editOperation'; @@ -23,7 +22,7 @@ import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { localize } from 'vs/nls'; -import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; @@ -247,8 +246,6 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant { class CodeActionOnSaveParticipant implements ISaveParticipantParticipant { constructor( - @IBulkEditService private readonly _bulkEditService: IBulkEditService, - @ICommandService private readonly _commandService: ICommandService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { } @@ -308,7 +305,7 @@ class CodeActionOnSaveParticipant implements ISaveParticipantParticipant { private async applyCodeActions(actionsToRun: readonly CodeAction[]) { for (const action of actionsToRun) { - await this._instantiationService.invokeFunction(applyCodeAction, action, this._bulkEditService, this._commandService); + await this._instantiationService.invokeFunction(applyCodeAction, action); } } diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 4b63f2f07b449..6563a93e62223 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -39,8 +39,6 @@ import { Range } from 'vs/editor/common/core/range'; import { getCodeActions, CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { ITextModel } from 'vs/editor/common/model'; -import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; -import { ICommandService } from 'vs/platform/commands/common/commands'; import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands'; import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; @@ -489,8 +487,6 @@ export class MarkerViewModel extends Disposable { private readonly marker: Marker, @IModelService private modelService: IModelService, @IInstantiationService private instantiationService: IInstantiationService, - @IBulkEditService private readonly bulkEditService: IBulkEditService, - @ICommandService private readonly commandService: ICommandService, @IEditorService private readonly editorService: IEditorService ) { super(); @@ -571,7 +567,7 @@ export class MarkerViewModel extends Disposable { true, () => { return this.openFileAtMarker(this.marker) - .then(() => this.instantiationService.invokeFunction(applyCodeAction, codeAction, this.bulkEditService, this.commandService)); + .then(() => this.instantiationService.invokeFunction(applyCodeAction, codeAction)); })); } From c3c54744ab61fa2a44569d00cf686601aad873bf Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 13 Jan 2020 20:55:32 -0800 Subject: [PATCH 249/315] Convert CodeActionTrigger type enum type --- .../editor/contrib/codeAction/codeAction.ts | 34 +++++++++---------- .../contrib/codeAction/codeActionCommands.ts | 7 ++-- .../contrib/codeAction/codeActionModel.ts | 12 +++---- .../editor/contrib/codeAction/codeActionUi.ts | 4 +-- .../codeAction/test/codeAction.test.ts | 20 +++++------ .../codeAction/test/codeActionModel.test.ts | 9 ++--- src/vs/editor/contrib/codeAction/types.ts | 7 +++- .../editor/contrib/hover/modesContentHover.ts | 4 +-- .../api/browser/mainThreadSaveParticipant.ts | 4 +-- .../markers/browser/markersTreeViewer.ts | 4 +-- .../api/extHostLanguageFeatures.test.ts | 9 ++--- 11 files changed, 60 insertions(+), 54 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/codeAction.ts b/src/vs/editor/contrib/codeAction/codeAction.ts index c4850175c63c5..37744867d8c98 100644 --- a/src/vs/editor/contrib/codeAction/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/codeAction.ts @@ -6,16 +6,16 @@ import { equals, flatten, isNonEmptyArray, mergeSort } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { illegalArgument, isPromiseCanceledError, onUnexpectedExternalError } from 'vs/base/common/errors'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; +import { TextModelCancellationTokenSource } from 'vs/editor/browser/core/editorState'; import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { ITextModel } from 'vs/editor/common/model'; -import { CodeAction, CodeActionContext, CodeActionProviderRegistry, CodeActionTrigger as CodeActionTriggerKind } from 'vs/editor/common/modes'; +import * as modes from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { CodeActionFilter, CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind } from './types'; -import { TextModelCancellationTokenSource } from 'vs/editor/browser/core/editorState'; -import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { CodeActionFilter, CodeActionKind, CodeActionTrigger, CodeActionTriggerType, filtersAction, mayIncludeActionsOfKind } from './types'; export const codeActionCommandId = 'editor.action.codeAction'; export const refactorCommandId = 'editor.action.refactor'; @@ -24,14 +24,14 @@ export const organizeImportsCommandId = 'editor.action.organizeImports'; export const fixAllCommandId = 'editor.action.fixAll'; export interface CodeActionSet extends IDisposable { - readonly validActions: readonly CodeAction[]; - readonly allActions: readonly CodeAction[]; + readonly validActions: readonly modes.CodeAction[]; + readonly allActions: readonly modes.CodeAction[]; readonly hasAutoFix: boolean; } class ManagedCodeActionSet extends Disposable implements CodeActionSet { - private static codeActionsComparator(a: CodeAction, b: CodeAction): number { + private static codeActionsComparator(a: modes.CodeAction, b: modes.CodeAction): number { if (isNonEmptyArray(a.diagnostics)) { if (isNonEmptyArray(b.diagnostics)) { return a.diagnostics[0].message.localeCompare(b.diagnostics[0].message); @@ -45,10 +45,10 @@ class ManagedCodeActionSet extends Disposable implements CodeActionSet { } } - public readonly validActions: readonly CodeAction[]; - public readonly allActions: readonly CodeAction[]; + public readonly validActions: readonly modes.CodeAction[]; + public readonly allActions: readonly modes.CodeAction[]; - public constructor(actions: readonly CodeAction[], disposables: DisposableStore) { + public constructor(actions: readonly modes.CodeAction[], disposables: DisposableStore) { super(); this._register(disposables); this.allActions = mergeSort([...actions], ManagedCodeActionSet.codeActionsComparator); @@ -68,9 +68,9 @@ export function getCodeActions( ): Promise { const filter = trigger.filter || {}; - const codeActionContext: CodeActionContext = { + const codeActionContext: modes.CodeActionContext = { only: filter.include?.value, - trigger: trigger.type === 'manual' ? CodeActionTriggerKind.Manual : CodeActionTriggerKind.Automatic + trigger: trigger.type === CodeActionTriggerType.Manual ? modes.CodeActionTrigger.Manual : modes.CodeActionTrigger.Automatic }; const cts = new TextModelCancellationTokenSource(model, token); @@ -94,8 +94,8 @@ export function getCodeActions( } }); - const listener = CodeActionProviderRegistry.onDidChange(() => { - const newProviders = CodeActionProviderRegistry.all(model); + const listener = modes.CodeActionProviderRegistry.onDidChange(() => { + const newProviders = modes.CodeActionProviderRegistry.all(model); if (!equals(newProviders, providers)) { cts.cancel(); } @@ -114,7 +114,7 @@ function getCodeActionProviders( model: ITextModel, filter: CodeActionFilter ) { - return CodeActionProviderRegistry.all(model) + return modes.CodeActionProviderRegistry.all(model) // Don't include providers that we know will not return code actions of interest .filter(provider => { if (!provider.providedCodeActionKinds) { @@ -125,7 +125,7 @@ function getCodeActionProviders( }); } -registerLanguageCommand('_executeCodeActionProvider', async function (accessor, args): Promise> { +registerLanguageCommand('_executeCodeActionProvider', async function (accessor, args): Promise> { const { resource, rangeOrSelection, kind } = args; if (!(resource instanceof URI)) { throw illegalArgument(); @@ -149,7 +149,7 @@ registerLanguageCommand('_executeCodeActionProvider', async function (accessor, const codeActionSet = await getCodeActions( model, validatedRangeOrSelection, - { type: 'manual', filter: { includeSourceActions: true, include: kind && kind.value ? new CodeActionKind(kind.value) : undefined } }, + { type: CodeActionTriggerType.Manual, filter: { includeSourceActions: true, include: kind && kind.value ? new CodeActionKind(kind.value) : undefined } }, CancellationToken.None); setTimeout(() => codeActionSet.dispose(), 100); diff --git a/src/vs/editor/contrib/codeAction/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/codeActionCommands.ts index ce30a8227fdce..003830553f6ca 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -29,7 +29,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CodeActionModel, CodeActionsState, SUPPORTED_CODE_ACTIONS } from './codeActionModel'; -import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionFilter, CodeActionKind, CodeActionTrigger } from './types'; +import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionFilter, CodeActionKind, CodeActionTrigger, CodeActionTriggerType } from './types'; function contextKeyForSupportedActions(kind: CodeActionKind) { return ContextKeyExpr.regex( @@ -97,7 +97,7 @@ export class QuickFixController extends Disposable implements IEditorContributio await this._applyCodeAction(action); } finally { if (retrigger) { - this._trigger({ type: 'auto', filter: {} }); + this._trigger({ type: CodeActionTriggerType.Auto, filter: {} }); } } } @@ -124,7 +124,7 @@ export class QuickFixController extends Disposable implements IEditorContributio MessageController.get(this._editor).closeMessage(); const triggerPosition = this._editor.getPosition(); - this._trigger({ type: 'manual', filter, autoApply, context: { notAvailableMessage, position: triggerPosition } }); + this._trigger({ type: CodeActionTriggerType.Manual, filter, autoApply, context: { notAvailableMessage, position: triggerPosition } }); } private _trigger(trigger: CodeActionTrigger) { @@ -176,7 +176,6 @@ export async function applyCodeAction( typeof message === 'string' ? message : nls.localize('applyCodeActionFailed', "An unknown error occurred while applying the code action")); - } } } diff --git a/src/vs/editor/contrib/codeAction/codeActionModel.ts b/src/vs/editor/contrib/codeAction/codeActionModel.ts index e574348900514..0f834c0e29fda 100644 --- a/src/vs/editor/contrib/codeAction/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/codeActionModel.ts @@ -16,7 +16,7 @@ import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/cont import { IMarkerService } from 'vs/platform/markers/common/markers'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { getCodeActions, CodeActionSet } from './codeAction'; -import { CodeActionTrigger } from './types'; +import { CodeActionTrigger, CodeActionTriggerType } from './types'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { isEqual } from 'vs/base/common/resources'; @@ -56,14 +56,14 @@ class CodeActionOracle extends Disposable { if (resources.some(resource => isEqual(resource, model.uri))) { this._autoTriggerTimer.cancelAndSet(() => { - this.trigger({ type: 'auto' }); + this.trigger({ type: CodeActionTriggerType.Auto }); }, this._delay); } } private _onCursorChange(): void { this._autoTriggerTimer.cancelAndSet(() => { - this.trigger({ type: 'auto' }); + this.trigger({ type: CodeActionTriggerType.Auto }); }, this._delay); } @@ -88,7 +88,7 @@ class CodeActionOracle extends Disposable { } const model = this._editor.getModel(); const selection = this._editor.getSelection(); - if (selection.isEmpty() && trigger.type === 'auto') { + if (selection.isEmpty() && trigger.type === CodeActionTriggerType.Auto) { const { lineNumber, column } = selection.getPosition(); const line = model.getLineContent(lineNumber); if (line.length === 0) { @@ -214,14 +214,14 @@ export class CodeActionModel extends Disposable { } const actions = createCancelablePromise(token => getCodeActions(model, trigger.selection, trigger.trigger, token)); - if (this._progressService && trigger.trigger.type === 'manual') { + if (this._progressService && trigger.trigger.type === CodeActionTriggerType.Manual) { this._progressService.showWhile(actions, 250); } this.setState(new CodeActionsState.Triggered(trigger.trigger, trigger.selection, trigger.position, actions)); }, undefined); - this._codeActionOracle.value.trigger({ type: 'auto' }); + this._codeActionOracle.value.trigger({ type: CodeActionTriggerType.Auto }); } else { this._supportedCodeActions.reset(); } diff --git a/src/vs/editor/contrib/codeAction/codeActionUi.ts b/src/vs/editor/contrib/codeAction/codeActionUi.ts index 5c34b32fe7f25..6f743d1d76eea 100644 --- a/src/vs/editor/contrib/codeAction/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/codeActionUi.ts @@ -16,7 +16,7 @@ import { MessageController } from 'vs/editor/contrib/message/messageController'; import { CodeActionsState } from './codeActionModel'; import { CodeActionMenu, CodeActionShowOptions } from './codeActionMenu'; import { LightBulbWidget } from './lightBulbWidget'; -import { CodeActionAutoApply, CodeActionTrigger } from './types'; +import { CodeActionAutoApply, CodeActionTrigger, CodeActionTriggerType } from './types'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class CodeActionUi extends Disposable { @@ -67,7 +67,7 @@ export class CodeActionUi extends Disposable { this._lightBulbWidget.getValue().update(actions, newState.position); - if (newState.trigger.type === 'manual') { + if (newState.trigger.type === CodeActionTriggerType.Manual) { if (newState.trigger.filter?.include) { // Triggered for specific scope // Check to see if we want to auto apply. diff --git a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts index 0b7fb8ba4a6e0..bd12d41230d52 100644 --- a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts @@ -9,7 +9,7 @@ import { Range } from 'vs/editor/common/core/range'; import { TextModel } from 'vs/editor/common/model/textModel'; import * as modes from 'vs/editor/common/modes'; import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; +import { CodeActionKind, CodeActionTriggerType } from 'vs/editor/contrib/codeAction/types'; import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -125,7 +125,7 @@ suite('CodeAction', () => { testData.tsLint.abc ]; - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'manual' }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: CodeActionTriggerType.Manual }, CancellationToken.None); assert.equal(actions.length, 6); assert.deepEqual(actions, expected); }); @@ -140,20 +140,20 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); { - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a') } }, CancellationToken.None); assert.equal(actions.length, 2); assert.strictEqual(actions[0].title, 'a'); assert.strictEqual(actions[1].title, 'a.b'); } { - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a.b') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a.b') } }, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a.b'); } { - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a.b.c') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a.b.c') } }, CancellationToken.None); assert.equal(actions.length, 0); } }); @@ -172,7 +172,7 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: new CodeActionKind('a') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a') } }, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a'); }); @@ -186,13 +186,13 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); { - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto' }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: CodeActionTriggerType.Auto }, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'b'); } { - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { include: CodeActionKind.Source, includeSourceActions: true } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: CodeActionTriggerType.Auto, filter: { include: CodeActionKind.Source, includeSourceActions: true } }, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a'); } @@ -209,7 +209,7 @@ suite('CodeAction', () => { { const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { - type: 'auto', filter: { + type: CodeActionTriggerType.Auto, filter: { include: CodeActionKind.Source.append('test'), excludes: [CodeActionKind.Source], includeSourceActions: true, @@ -234,7 +234,7 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { - type: 'auto', + type: CodeActionTriggerType.Auto, filter: { include: CodeActionKind.QuickFix } diff --git a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts index c4a7cba517320..d77f4010da3c9 100644 --- a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts @@ -15,6 +15,7 @@ import { CodeActionModel, CodeActionsState } from 'vs/editor/contrib/codeAction/ import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { MarkerService } from 'vs/platform/markers/common/markerService'; +import { CodeActionTriggerType } from 'vs/editor/contrib/codeAction/types'; const testProvider = { provideCodeActions(): modes.CodeActionList { @@ -59,7 +60,7 @@ suite('CodeActionModel', () => { disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { assertType(e.type === CodeActionsState.Type.Triggered); - assert.strictEqual(e.trigger.type, 'auto'); + assert.strictEqual(e.trigger.type, CodeActionTriggerType.Auto); assert.ok(e.actions); e.actions.then(fixes => { @@ -100,7 +101,7 @@ suite('CodeActionModel', () => { disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { assertType(e.type === CodeActionsState.Type.Triggered); - assert.equal(e.trigger.type, 'auto'); + assert.equal(e.trigger.type, CodeActionTriggerType.Auto); assert.ok(e.actions); e.actions.then(fixes => { model.dispose(); @@ -138,7 +139,7 @@ suite('CodeActionModel', () => { disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { assertType(e.type === CodeActionsState.Type.Triggered); - assert.equal(e.trigger.type, 'auto'); + assert.equal(e.trigger.type, CodeActionTriggerType.Auto); const selection = e.rangeOrSelection; assert.deepEqual(selection.selectionStartLineNumber, 1); assert.deepEqual(selection.selectionStartColumn, 1); @@ -163,7 +164,7 @@ suite('CodeActionModel', () => { disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { assertType(e.type === CodeActionsState.Type.Triggered); - assert.equal(e.trigger.type, 'auto'); + assert.equal(e.trigger.type, CodeActionTriggerType.Auto); ++triggerCount; // give time for second trigger before completing test diff --git a/src/vs/editor/contrib/codeAction/types.ts b/src/vs/editor/contrib/codeAction/types.ts index bce9f61232901..606d9b8ecedc5 100644 --- a/src/vs/editor/contrib/codeAction/types.ts +++ b/src/vs/editor/contrib/codeAction/types.ts @@ -102,8 +102,13 @@ export function filtersAction(filter: CodeActionFilter, action: CodeAction): boo return true; } +export const enum CodeActionTriggerType { + Auto, + Manual +} + export interface CodeActionTrigger { - readonly type: 'auto' | 'manual'; + readonly type: CodeActionTriggerType; readonly filter?: CodeActionFilter; readonly autoApply?: CodeActionAutoApply; readonly context?: { diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 63fd6053301e3..95b8e9813c8c0 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -34,7 +34,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { getCodeActions, CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; import { QuickFixAction, QuickFixController } from 'vs/editor/contrib/codeAction/codeActionCommands'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; +import { CodeActionKind, CodeActionTriggerType } from 'vs/editor/contrib/codeAction/types'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -592,7 +592,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { return getCodeActions( this._editor.getModel()!, new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn), - { type: 'manual', filter: { include: CodeActionKind.QuickFix } }, + { type: CodeActionTriggerType.Manual, filter: { include: CodeActionKind.QuickFix } }, cancellationToken); }); } diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index 764487cde6fb9..667865985c19a 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -18,7 +18,7 @@ import { CodeAction } from 'vs/editor/common/modes'; import { shouldSynchronizeModel } from 'vs/editor/common/services/modelService'; import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; +import { CodeActionKind, CodeActionTriggerType } from 'vs/editor/contrib/codeAction/types'; import { formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { localize } from 'vs/nls'; @@ -311,7 +311,7 @@ class CodeActionOnSaveParticipant implements ISaveParticipantParticipant { private getActionsToRun(model: ITextModel, codeActionKind: CodeActionKind, excludes: readonly CodeActionKind[], token: CancellationToken) { return getCodeActions(model, model.getFullModelRange(), { - type: 'auto', + type: CodeActionTriggerType.Auto, filter: { include: codeActionKind, excludes: excludes, includeSourceActions: true }, }, token); } diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 6563a93e62223..d7d54627f0443 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -37,7 +37,7 @@ import { CancelablePromise, createCancelablePromise, Delayer } from 'vs/base/com import { IModelService } from 'vs/editor/common/services/modelService'; import { Range } from 'vs/editor/common/core/range'; import { getCodeActions, CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; +import { CodeActionKind, CodeActionTriggerType } from 'vs/editor/contrib/codeAction/types'; import { ITextModel } from 'vs/editor/common/model'; import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands'; @@ -548,7 +548,7 @@ export class MarkerViewModel extends Disposable { if (model) { if (!this.codeActionsPromise) { this.codeActionsPromise = createCancelablePromise(cancellationToken => { - return getCodeActions(model, new Range(this.marker.range.startLineNumber, this.marker.range.startColumn, this.marker.range.endLineNumber, this.marker.range.endColumn), { type: 'manual', filter: { include: CodeActionKind.QuickFix } }, cancellationToken).then(actions => { + return getCodeActions(model, new Range(this.marker.range.startLineNumber, this.marker.range.startColumn, this.marker.range.endLineNumber, this.marker.range.endColumn), { type: CodeActionTriggerType.Manual, filter: { include: CodeActionKind.QuickFix } }, cancellationToken).then(actions => { return this._register(actions); }); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index 232becb2a56a4..8e6fb02412c59 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -47,6 +47,7 @@ import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { dispose } from 'vs/base/common/lifecycle'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { CodeActionTriggerType } from 'vs/editor/contrib/codeAction/types'; const defaultSelector = { scheme: 'far' }; const model: ITextModel = EditorModel.createFromString( @@ -589,7 +590,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: CodeActionTriggerType.Manual }, CancellationToken.None); assert.equal(actions.length, 2); const [first, second] = actions; assert.equal(first.title, 'Testing1'); @@ -613,7 +614,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: CodeActionTriggerType.Manual }, CancellationToken.None); assert.equal(actions.length, 1); const [first] = actions; assert.equal(first.title, 'Testing1'); @@ -636,7 +637,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: CodeActionTriggerType.Manual }, CancellationToken.None); assert.equal(actions.length, 1); }); @@ -654,7 +655,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: CodeActionTriggerType.Manual }, CancellationToken.None); assert.equal(actions.length, 1); }); From 501085f224933a62bdabc2e2248f8770f33fa7cb Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Mon, 13 Jan 2020 21:51:16 -0800 Subject: [PATCH 250/315] Add basic save functionality to search editor --- .../search/browser/search.contribution.ts | 5 +- .../contrib/search/browser/searchEditor.ts | 21 ++- .../search/browser/searchEditorCommands.ts | 123 ++++++++++++------ 3 files changed, 108 insertions(+), 41 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 3e698a8a74cfb..9e4d6ce61d8a1 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -57,7 +57,7 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { assertType } from 'vs/base/common/types'; import { SearchViewPaneContainer } from 'vs/workbench/contrib/search/browser/searchViewlet'; import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; -import { SearchEditorInput, SearchEditorInputFactory } from 'vs/workbench/contrib/search/browser/searchEditorCommands'; +import { SearchEditorInput, SearchEditorInputFactory, SearchEditorContribution } from 'vs/workbench/contrib/search/browser/searchEditorCommands'; import { SearchEditor } from 'vs/workbench/contrib/search/browser/searchEditor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; @@ -909,3 +909,6 @@ Registry.as(EditorExtensions.Editors).registerEditor( Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory( SearchEditorInput.ID, SearchEditorInputFactory); + +const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchContributionsRegistry.registerWorkbenchContribution(SearchEditorContribution, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index 505ea541890be..26bc97814d1c9 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -158,6 +158,8 @@ export class SearchEditor extends BaseEditor { } } }); + + this._register(this.searchResultEditor.onDidChangeModel(() => this.hideHeader())); } private async runSearch(instant = false) { @@ -225,14 +227,28 @@ export class SearchEditor extends BaseEditor { (assertIsDefined(this._input) as SearchEditorInput).setConfig(config); const labelFormatter = (uri: URI): string => this.labelService.getUriLabel(uri, { relative: true }); - const results = serializeSearchResultForEditor(searchModel.searchResult, config.includes, config.excludes, config.contextLines, labelFormatter, false); + const results = serializeSearchResultForEditor(searchModel.searchResult, config.includes, config.excludes, config.contextLines, labelFormatter, true); const textModel = assertIsDefined(this.searchResultEditor.getModel()); textModel.setValue(results.text.join(lineDelimiter)); + this.hideHeader(); textModel.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); searchModel.dispose(); } + private hideHeader() { + const headerLines = + this.searchResultEditor + .getModel() + ?.getValueInRange(new Range(1, 1, 6, 1)) + .split('\n') + .filter(line => line.startsWith('#')) + .length + ?? 0; + + this.searchResultEditor.setHiddenAreas([new Range(1, 1, headerLines + 1, 1)]); + } + layout(dimension: DOM.Dimension) { this.dimension = dimension; this.reLayout(); @@ -255,8 +271,7 @@ export class SearchEditor extends BaseEditor { if (!(newInput instanceof SearchEditorInput)) { return; } this.pauseSearching = true; // TODO: Manage model lifecycle in SearchEditorInput - const model = this.modelService.getModel(newInput.getResource()); - + const model = this.modelService.getModel(newInput.resource); this.searchResultEditor.setModel(model); this.queryEditorWidget.setValue(newInput.config.query, true); this.queryEditorWidget.searchInput.setCaseSensitive(newInput.config.caseSensitive); diff --git a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts index 07177d95ea8da..c0caeedac6275 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts @@ -5,13 +5,13 @@ import { coalesce, flatten } from 'vs/base/common/arrays'; import * as network from 'vs/base/common/network'; -import { repeat } from 'vs/base/common/strings'; +import { repeat, endsWith } from 'vs/base/common/strings'; import { assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/searchEditor'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; -import { EndOfLinePreference, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { EndOfLinePreference, TrackedRangeStickiness, ITextModel } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -25,16 +25,22 @@ import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/co import { FileMatch, Match, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IPatternInfo, ISearchConfigurationProperties, ITextQuery } from 'vs/workbench/services/search/common/search'; -import { EditorInput, IEditorInputFactory } from 'vs/workbench/common/editor'; +import { IEditorInputFactory, GroupIdentifier, EditorInput } from 'vs/workbench/common/editor'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { SearchEditor } from 'vs/workbench/contrib/search/browser/searchEditor'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import type { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +// import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; + export type SearchConfiguration = { query: string, includes: string, - excludes: string, + excludes: string contextLines: number, wholeWord: boolean, caseSensitive: boolean, @@ -43,6 +49,27 @@ export type SearchConfiguration = { showIncludesExcludes: boolean, }; +export class SearchEditorContribution implements IWorkbenchContribution { + // constructor( + // @IEditorService private readonly editorService: IEditorService, + // @ITextFileService protected readonly textFileService: ITextFileService, + // ) { + // this.editorService.overrideOpenEditor(async (editor, options, group) => { + // const resource = editor.getResource(); + // if (!resource || !endsWith(resource.path, '.code-search') || !(editor instanceof FileEditorInput)) { + // return undefined; + // } + + // console.log('hello agian'); + // const contents = await this.textFileService.read(resource); + // contents.value; + + // return undefined; + + // }); + // } +} + export class SearchEditorInputFactory implements IEditorInputFactory { canSerialize() { return true; } @@ -52,7 +79,7 @@ export class SearchEditorInputFactory implements IEditorInputFactory { } deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): SearchEditorInput | undefined { - return instantiationService.createInstance(SearchEditorInput, JSON.parse(serializedEditorInput)); + return instantiationService.createInstance(SearchEditorInput, JSON.parse(serializedEditorInput), undefined); } } @@ -60,32 +87,48 @@ let searchEditorInputInstances = 0; export class SearchEditorInput extends EditorInput { static readonly ID: string = 'workbench.editorinputs.searchEditorInput'; - public config: SearchConfiguration; - private instanceNumber: number = searchEditorInputInstances++; + private _config: SearchConfiguration; + public get config(): Readonly { + return this._config; + } + + private model: ITextModel; + public readonly resource: URI; constructor( config: SearchConfiguration | undefined, + initialContents: string | undefined, @IModelService private readonly modelService: IModelService, @IModeService private readonly modeService: IModeService, + @IEditorService protected readonly editorService: IEditorService, + @IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService, + @ITextFileService protected readonly textFileService: ITextFileService, + @IUntitledTextEditorService protected readonly untitledTextEditorService: IUntitledTextEditorService, ) { super(); + this.resource = URI.from({ scheme: 'search-editor', fragment: `${searchEditorInputInstances++}` }); + if (config === undefined) { - this.config = { query: '', includes: '', excludes: '', contextLines: 0, wholeWord: false, caseSensitive: false, regexp: false, useIgnores: true, showIncludesExcludes: false }; + this._config = { query: '', includes: '', excludes: '', contextLines: 0, wholeWord: false, caseSensitive: false, regexp: false, useIgnores: true, showIncludesExcludes: false }; } else { - this.config = config; + this._config = config; } const searchResultMode = this.modeService.create('search-result'); - this.modelService.createModel('', searchResultMode, this.getResource()); + + this.model = this.modelService.createModel(initialContents ?? '', searchResultMode, this.resource); } - getTypeId(): string { - return SearchEditorInput.ID; + async save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { + const path = (this.config.query.replace(/\W/g, '') || 'Untitled Search') + '.code-search'; + const untitledResource = URI.from({ scheme: 'untitled', path, fragment: this.resource.fragment }); + const a = this.untitledTextEditorService.createOrGet(untitledResource, 'search-result', this.model.getValue()); + return a.save(group, options).finally(() => a.dispose()); } - getResource(): URI { - return URI.from({ scheme: 'search-editor', fragment: `${this.instanceNumber}` }); + getTypeId(): string { + return SearchEditorInput.ID; } getName(): string { @@ -93,7 +136,7 @@ export class SearchEditorInput extends EditorInput { } setConfig(config: SearchConfiguration) { - this.config = config; + this._config = config; this._onDidChangeLabel.fire(); } @@ -102,9 +145,17 @@ export class SearchEditorInput extends EditorInput { } dispose() { - this.modelService.destroyModel(this.getResource()); + this.modelService.destroyModel(this.resource); super.dispose(); } + + matches(other: unknown) { + if (this === other) { return true; } + if (other instanceof UntitledTextEditorInput) { + if (other.getResource().fragment === this.resource.fragment) { return true; } + } + return false; + } } // Using \r\n on Windows inserts an extra newline between results. @@ -313,7 +364,7 @@ export const serializeSearchResultForEditor = (searchResult: SearchResult, rawIn .map(folderMatch => folderMatch.matches().sort(searchMatchComparer) .map(fileMatch => fileMatchToSearchResultFormat(fileMatch, labelFormatter))))); - return { matchRanges: allResults.matchRanges.map(translateRangeLines(header.length)), text: header.concat(allResults.text) }; + return { matchRanges: allResults.matchRanges.map(translateRangeLines(header.length)), text: header.concat(allResults.text.length ? allResults.text : ['No Results']) }; }; export const refreshActiveEditorSearch = @@ -373,7 +424,7 @@ export const refreshActiveEditorSearch = await searchModel.search(query); const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); - const results = serializeSearchResultForEditor(searchModel.searchResult, contentPattern.includes, contentPattern.excludes, contextLines, labelFormatter, false); + const results = serializeSearchResultForEditor(searchModel.searchResult, contentPattern.includes, contentPattern.excludes, contextLines, labelFormatter, true); textModel.setValue(results.text.join(lineDelimiter)); textModel.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); @@ -381,7 +432,7 @@ export const refreshActiveEditorSearch = export const openNewSearchEditor = async (editorService: IEditorService, instantiationService: IInstantiationService) => { - await editorService.openEditor(instantiationService.createInstance(SearchEditorInput, undefined), { pinned: true }); + await editorService.openEditor(instantiationService.createInstance(SearchEditorInput, undefined, undefined), { pinned: true }); }; export const createEditorFromSearchResult = @@ -395,7 +446,7 @@ export const createEditorFromSearchResult = const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); - const results = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, 0, labelFormatter, false); + const results = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, 0, labelFormatter, true); const contents = results.text.join(lineDelimiter); let possible = { contents, @@ -418,24 +469,22 @@ export const createEditorFromSearchResult = existing = editorService.getOpened(possible); } - const editor = await editorService.openEditor( - instantiationService.createInstance( - SearchEditorInput, - { - query: searchResult.query.contentPattern.pattern, - regexp: !!searchResult.query.contentPattern.isRegExp, - caseSensitive: !!searchResult.query.contentPattern.isCaseSensitive, - wholeWord: !!searchResult.query.contentPattern.isWordMatch, - includes: rawIncludePattern, - excludes: rawExcludePattern, - contextLines: 0, - useIgnores: !searchResult.query.userDisabledExcludesAndIgnoreFiles, - showIncludesExcludes: !!(rawExcludePattern || rawExcludePattern || searchResult.query.userDisabledExcludesAndIgnoreFiles) - }), - { pinned: true }) as SearchEditor; - + const input = instantiationService.createInstance( + SearchEditorInput, + { + query: searchResult.query.contentPattern.pattern, + regexp: !!searchResult.query.contentPattern.isRegExp, + caseSensitive: !!searchResult.query.contentPattern.isCaseSensitive, + wholeWord: !!searchResult.query.contentPattern.isWordMatch, + includes: rawIncludePattern, + excludes: rawExcludePattern, + contextLines: 0, + useIgnores: !searchResult.query.userDisabledExcludesAndIgnoreFiles, + showIncludesExcludes: !!(rawExcludePattern || rawExcludePattern || searchResult.query.userDisabledExcludesAndIgnoreFiles) + }, contents); + + const editor = await editorService.openEditor(input, { pinned: true }) as SearchEditor; const model = assertIsDefined(editor.getModel()); - model.setValue(contents); model.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); }; From bf350ef870f229b334d3f54a7cc81a7e73841c3b Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Mon, 13 Jan 2020 22:32:17 -0800 Subject: [PATCH 251/315] remove unused --- src/vs/workbench/contrib/search/browser/searchEditorCommands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts index c0caeedac6275..985f49c434751 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts @@ -5,7 +5,7 @@ import { coalesce, flatten } from 'vs/base/common/arrays'; import * as network from 'vs/base/common/network'; -import { repeat, endsWith } from 'vs/base/common/strings'; +import { repeat } from 'vs/base/common/strings'; import { assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/searchEditor'; From 52702da6ef7e381ac3a6cc0f1b25747a007b9401 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 14 Jan 2020 08:34:31 +0100 Subject: [PATCH 252/315] Same editor appears twice in tabs (fix #88551) --- src/vs/workbench/browser/parts/editor/textDiffEditor.ts | 5 +++-- src/vs/workbench/common/editor/diffEditorInput.ts | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 66963c29490ad..3dbb5f41ac56f 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -26,7 +26,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorActivation, IEditorOptions } from 'vs/platform/editor/common/editor'; @@ -182,7 +182,8 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { options = EditorOptions.create(preservingOptions); } - this.editorService.openEditor(binaryDiffInput, options, this.group); + // Replace this editor with the binary one + this.editorService.replaceEditors([{ editor: input, replacement: binaryDiffInput, options }], this.group || ACTIVE_GROUP); return true; } diff --git a/src/vs/workbench/common/editor/diffEditorInput.ts b/src/vs/workbench/common/editor/diffEditorInput.ts index 12e57ac49e09d..b9f1bc2811c8f 100644 --- a/src/vs/workbench/common/editor/diffEditorInput.ts +++ b/src/vs/workbench/common/editor/diffEditorInput.ts @@ -32,6 +32,7 @@ export class DiffEditorInput extends SideBySideEditorInput { if (!super.matches(otherInput)) { return false; } + return otherInput instanceof DiffEditorInput && otherInput.forceOpenAsBinary === this.forceOpenAsBinary; } From 7a62151d6a966be9d9ed4ea659cbd7a4c631c960 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 14 Jan 2020 09:26:42 +0100 Subject: [PATCH 253/315] fix https://github.com/microsoft/vscode/issues/88591 --- build/builtInExtensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index 57808f0f8336e..ef15343787856 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -31,7 +31,7 @@ }, { "name": "ms-vscode.references-view", - "version": "0.0.44", + "version": "0.0.45", "repo": "https://github.com/Microsoft/vscode-reference-view", "metadata": { "id": "dc489f46-520d-4556-ae85-1f9eab3c412d", From 980d8ef1371b14e80162d88e88f45e15f8632070 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 14 Jan 2020 09:36:59 +0100 Subject: [PATCH 254/315] #87461 Fix open view. Consider existing views. --- src/vs/workbench/browser/parts/views/views.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index b2911ed666303..3ba413e80ef9b 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -649,6 +649,7 @@ export class ViewsService extends Disposable implements IViewsService { this.viewDisposable.clear(); })); + this.viewContainersRegistry.all.forEach(viewContainer => this.onViewsRegistered(this.viewDescriptorService.getViews(viewContainer), viewContainer)); this._register(this.viewDescriptorService.onViewsRegistered(({ views, viewContainer }) => this.onViewsRegistered(views, viewContainer))); this._register(this.viewDescriptorService.onViewsDeregistered(({ views, viewContainer }) => this.onViewsDeregistered(views, viewContainer))); } From ad1500b9db68fd8d6d15e583e2897b9e1af8f5b2 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Tue, 14 Jan 2020 10:03:25 +0100 Subject: [PATCH 255/315] Extract paste selection clipboard code to an action --- .../electron-browser/selectionClipboard.ts | 65 +++++++++++-------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts index c6898134ef756..d93deb4a70092 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts @@ -3,11 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { registerEditorContribution, EditorAction, ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import { ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { Range } from 'vs/editor/common/core/range'; @@ -19,8 +20,10 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; export class SelectionClipboard extends Disposable implements IEditorContribution { private static readonly SELECTION_LENGTH_LIMIT = 65536; @@ -81,30 +84,6 @@ export class SelectionClipboard extends Disposable implements IEditorContributio } setSelectionToClipboard.schedule(); })); - - this._register(editor.onKeyDown((e: IKeyboardEvent) => { - if (!isEnabled) { - return; - } - - if (!editor.hasModel()) { - return; - } - - if (e.equals(KeyMod.Shift | KeyCode.Insert)) { - // prevent paste from 'clipboard' clipboard - e.preventDefault(); - - // trigger paste from 'primary' clipboard - clipboardService.readText('selection').then(text => { - editor.focus(); - editor.trigger('keyboard', Handler.Paste, { - text: text, - pasteOnNewLine: false - }); - }); - } - })); } } @@ -133,5 +112,39 @@ class SelectionClipboardPastePreventer implements IWorkbenchContribution { } } +class PasteSelectionClipboardAction extends EditorAction { + + constructor() { + super({ + id: 'editor.action.selectionClipboardPaste', + label: nls.localize('actions.pasteSelectionClipboard', "Paste Selection Clipboard"), + alias: 'Paste Selection Clipboard', + precondition: EditorContextKeys.writable, + kbOpts: { + kbExpr: ContextKeyExpr.and( + EditorContextKeys.editorTextFocus, + ContextKeyExpr.has('config.editor.selectionClipboard') + ), + primary: KeyMod.Shift | KeyCode.Insert, + weight: KeybindingWeight.EditorContrib + } + }); + } + + public async run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): Promise { + const clipboardService = accessor.get(IClipboardService); + + // read selection clipboard + const text = await clipboardService.readText('selection'); + + editor.trigger('keyboard', Handler.Paste, { + text: text, + pasteOnNewLine: false, + multicursorText: null + }); + } +} + registerEditorContribution(SelectionClipboardContributionID, SelectionClipboard); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SelectionClipboardPastePreventer, LifecyclePhase.Ready); +registerEditorAction(PasteSelectionClipboardAction); From a4a3e90dc8ca32d8a47d4a659a81f4cecf11082f Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Tue, 14 Jan 2020 10:07:08 +0100 Subject: [PATCH 256/315] Only register PasteSelectionClipboardAction on Linux --- .../contrib/codeEditor/electron-browser/selectionClipboard.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts index d93deb4a70092..536d0f75c5ce1 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts @@ -147,4 +147,6 @@ class PasteSelectionClipboardAction extends EditorAction { registerEditorContribution(SelectionClipboardContributionID, SelectionClipboard); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SelectionClipboardPastePreventer, LifecyclePhase.Ready); -registerEditorAction(PasteSelectionClipboardAction); +if (platform.isLinux) { + registerEditorAction(PasteSelectionClipboardAction); +} From 3140b9c99f32731a79a0e609fa92d072b4a96cca Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 14 Jan 2020 10:07:29 +0100 Subject: [PATCH 257/315] fix #88561 --- .../bulkEdit/browser/bulkEdit.contribution.ts | 11 ++- .../contrib/bulkEdit/browser/bulkEditPane.ts | 69 ++++++++++++------- .../bulkEdit/browser/bulkEditPreview.ts | 12 ++-- 3 files changed, 62 insertions(+), 30 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts index 2a705730bae14..104ed75e93777 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts @@ -22,6 +22,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { URI } from 'vs/base/common/uri'; function getBulkEditPane(panelService: IPanelService): BulkEditPane | undefined { let view: ViewPane | undefined; @@ -84,7 +85,15 @@ class BulkEditPreviewContribution { // (3) close preview editors for (let group of this._editorGroupsService.groups) { for (let input of group.editors) { - if (input instanceof DiffEditorInput && input.modifiedInput.getResource()?.scheme === BulkEditPreviewProvider.Schema) { + + let resource: URI | undefined; + if (input instanceof DiffEditorInput) { + resource = input.modifiedInput.getResource(); + } else { + resource = input.getResource(); + } + + if (resource?.scheme === BulkEditPreviewProvider.Schema) { group.closeEditor(input, { preserveFocus: true }); } } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index d0310fcc72724..982e5704712bb 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -15,7 +15,7 @@ import { diffInserted, diffRemoved } from 'vs/platform/theme/common/colorRegistr import { localize } from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { BulkEditPreviewProvider, BulkFileOperations } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; +import { BulkEditPreviewProvider, BulkFileOperations, BulkTextEdit, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; import { ILabelService } from 'vs/platform/label/common/label'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { URI } from 'vs/base/common/uri'; @@ -28,6 +28,7 @@ import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewl import { ResourceLabels, IResourceLabelsContainer } from 'vs/workbench/browser/labels'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; +import { basename } from 'vs/base/common/resources'; const enum State { Data = 'data', @@ -107,9 +108,9 @@ export class BulkEditPane extends ViewPane { this._disposables.add(this._tree.onDidOpen(e => { const [first] = e.elements; if (first instanceof TextEditElement) { - this._previewTextEditElement(first); + this._openElementAsEditor(first.parent, first.edit); } else if (first instanceof FileElement) { - this._previewFileElement(); + this._openElementAsEditor(first); } })); @@ -214,32 +215,54 @@ export class BulkEditPane extends ViewPane { this._sessionDisposables.clear(); } - private async _previewTextEditElement(element: TextEditElement): Promise { + private async _openElementAsEditor(fileElement: FileElement, textElement?: BulkTextEdit): Promise { - let leftResource: URI; - try { - (await this._textModelService.createModelReference(element.parent.uri)).dispose(); - leftResource = element.parent.uri; - } catch { - leftResource = BulkEditPreviewProvider.emptyPreview; + if (!textElement) { + textElement = fileElement.edit.textEdits[0]; } - const previewUri = BulkEditPreviewProvider.asPreviewUri(element.parent.uri); - - this._editorService.openEditor({ - leftResource, - rightResource: previewUri, - label: localize('edt.title', "{0} (Refactor Preview)", this._labelService.getUriLabel(element.parent.uri)), - options: { - selection: element.edit.edit.range, - revealInCenterIfOutsideViewport: true, - preserveFocus: true + let leftResource: URI | undefined; + if (fileElement.edit.type & BulkFileOperationType.TextEdit) { + try { + (await this._textModelService.createModelReference(fileElement.uri)).dispose(); + leftResource = fileElement.uri; + } catch { + leftResource = BulkEditPreviewProvider.emptyPreview; } - }); - } + } - private _previewFileElement(): void { + const previewUri = BulkEditPreviewProvider.asPreviewUri(fileElement.uri); + + if (leftResource) { + // show diff editor + this._editorService.openEditor({ + leftResource, + rightResource: previewUri, + label: localize('edt.title', "{0} (refactor preview)", basename(fileElement.uri)), + options: { + selection: textElement?.edit.range, + revealInCenterIfOutsideViewport: true, + preserveFocus: true + } + }); + } else { + // show 'normal' editor + + let typeLabel: string | undefined; + if (fileElement.edit.type & BulkFileOperationType.Rename) { + typeLabel = localize('rename', "rename"); + } else if (fileElement.edit.type & BulkFileOperationType.Create) { + typeLabel = localize('create', "create"); + } else if (fileElement.edit.type & BulkFileOperationType.Delete) { + typeLabel = localize('delete', "delete"); + } + this._editorService.openEditor({ + label: typeLabel && localize('edt.title2', "{0} ({1}, refactor preview)", basename(fileElement.uri), typeLabel), + resource: previewUri, + options: { preserveFocus: true } + }); + } } } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts index 614c152f94a39..6f9ae8474487e 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts @@ -49,15 +49,15 @@ export class BulkTextEdit extends CheckedObject { } export const enum BulkFileOperationType { - None = 0, - Create = 0b0001, - Delete = 0b0010, - Rename = 0b0100, + TextEdit = 1, + Create = 2, + Delete = 4, + Rename = 8, } export class BulkFileOperation extends CheckedObject { - type = BulkFileOperationType.None; + type: BulkFileOperationType = 0; textEdits: BulkTextEdit[] = []; originalEdits = new Map(); newUri?: URI; @@ -118,7 +118,7 @@ export class BulkFileOperations { let type: BulkFileOperationType; if (isResourceTextEdit(edit)) { - type = BulkFileOperationType.None; + type = BulkFileOperationType.TextEdit; uri = edit.resource; } else if (edit.newUri && edit.oldUri) { From 5ee9f6646e5d813989d886326c9c5b6f381814bb Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 14 Jan 2020 10:07:40 +0100 Subject: [PATCH 258/315] add constants --- .../typescript-language-features/package.json | 2 +- .../src/features/semanticTokens.ts | 108 ++++++++++-------- .../typescript-language-features/yarn.lock | 8 +- 3 files changed, 68 insertions(+), 50 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 3ff2f98b8e44e..681c5c4b2d105 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -19,7 +19,7 @@ "jsonc-parser": "^2.1.1", "rimraf": "^2.6.3", "semver": "5.5.1", - "typescript-vscode-sh-plugin": "^0.4.1", + "typescript-vscode-sh-plugin": "^0.3.2", "vscode-extension-telemetry": "0.1.1", "vscode-nls": "^4.0.0" }, diff --git a/extensions/typescript-language-features/src/features/semanticTokens.ts b/extensions/typescript-language-features/src/features/semanticTokens.ts index de3f11e81d7c3..78f7dfb36a83d 100644 --- a/extensions/typescript-language-features/src/features/semanticTokens.ts +++ b/extensions/typescript-language-features/src/features/semanticTokens.ts @@ -16,8 +16,6 @@ export function register(selector: vscode.DocumentSelector, client: ITypeScriptS const provider = new SemanticTokensProvider(client); return vscode.languages.registerSemanticTokensProvider(selector, provider, provider.getLegend()); }); - const provider = new SemanticTokensProvider(client); - return vscode.languages.registerSemanticTokensProvider(selector, provider, provider.getLegend()); } /** @@ -32,12 +30,12 @@ class SemanticTokensProvider implements vscode.SemanticTokensProvider { getLegend(): vscode.SemanticTokensLegend { const tokenTypes = []; - for (let i = 0; i < TokenType._sentinel; i++) { - tokenTypes.push(TokenType[i]); + for (let i = 0; i < VSCodeShPlugin.TokenType._sentinel; i++) { + tokenTypes.push(VSCodeShPlugin.TokenType[i]); } const tokenModifiers = []; - for (let i = 0; i < TokenModifier._sentinel; i++) { - tokenModifiers.push(TokenModifier[i]); + for (let i = 0; i < VSCodeShPlugin.TokenModifier._sentinel; i++) { + tokenModifiers.push(VSCodeShPlugin.TokenModifier[i]); } return new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers); } @@ -76,25 +74,25 @@ class SemanticTokensProvider implements vscode.SemanticTokensProvider { const builder = new vscode.SemanticTokensBuilder(); for (const tokenSpan of allTokenSpans) { - for (let i = 0, len = Math.floor(tokenSpan.length / 3); i < len; i++) { + let i = 0; + while (i < tokenSpan.length) { + const offset = tokenSpan[i++]; + const length = tokenSpan[i++]; + const tsClassification = tokenSpan[i++]; - const tsClassification = tokenSpan[3 * i + 2]; - let tokenType = 0; let tokenModifiers = 0; - if (tsClassification >= 0x100) { - // exendend classifications as returned by the typescript-vscode-sh-plugin - tokenType = (tsClassification >> 8) - 1; - tokenModifiers = tsClassification & 0xFF; + let tokenType = VSCodeShPlugin.getTokenTypeFromClassification(tsClassification); + if (tokenType !== undefined) { + // it's a classification as returned by the typescript-vscode-sh-plugin + tokenModifiers = VSCodeShPlugin.getTokenModifierFromClassification(tsClassification); } else { + // typescript-vscode-sh-plugin is not present tokenType = tokenTypeMap[tsClassification]; if (tokenType === undefined) { continue; } } - const offset = tokenSpan[3 * i]; - const length = tokenSpan[3 * i + 1]; - // we can use the document's range conversion methods because the result is at the same version as the document const startPos = document.positionAt(offset); const endPos = document.positionAt(offset + length); @@ -110,41 +108,61 @@ class SemanticTokensProvider implements vscode.SemanticTokensProvider { } } -// Don't change TokenType and TokenModifier enums without adopting typescript-vscode-sh-plugin -enum TokenType { - 'class', - 'enum', - 'interface', - 'namespace', - 'typeParameter', - 'type', - 'parameter', - 'variable', - 'property', - 'constant', - 'function', - 'member', - _sentinel -} +namespace VSCodeShPlugin { + + // typescript-vscode-sh-plugin encodes type and modifiers in the classification: + // TSClassification = (TokenType + 1) << 8 + TokenModifier + + const TokenTypeOffset = 8; + const TokenModifierMask = (1 << TokenTypeOffset) - 1; // 0xFF + + export function getTokenTypeFromClassification(tsClassification: number): number | undefined { + if (tsClassification > TokenModifierMask) { + return (tsClassification >> TokenTypeOffset) - 1; + } + return undefined; + } + + export function getTokenModifierFromClassification(tsClassification: number) { + return tsClassification & TokenModifierMask; + } -enum TokenModifier { - 'declaration', - 'static', - 'async', - 'readonly', - _sentinel + // Don't change TokenType and TokenModifier enums without adopting typescript-vscode-sh-plugin + export enum TokenType { + 'class', + 'enum', + 'interface', + 'namespace', + 'typeParameter', + 'type', + 'parameter', + 'variable', + 'property', + 'constant', + 'function', + 'member', + _sentinel + } + + export enum TokenModifier { + 'declaration', + 'static', + 'async', + 'readonly', + _sentinel + } } // mapping for the original ExperimentalProtocol.ClassificationType from TypeScript (only used when plugin is not available) const tokenTypeMap: number[] = []; -tokenTypeMap[ExperimentalProtocol.ClassificationType.className] = TokenType.class; -tokenTypeMap[ExperimentalProtocol.ClassificationType.enumName] = TokenType.enum; -tokenTypeMap[ExperimentalProtocol.ClassificationType.interfaceName] = TokenType.interface; -tokenTypeMap[ExperimentalProtocol.ClassificationType.moduleName] = TokenType.namespace; -tokenTypeMap[ExperimentalProtocol.ClassificationType.typeParameterName] = TokenType.typeParameter; -tokenTypeMap[ExperimentalProtocol.ClassificationType.typeAliasName] = TokenType.type; -tokenTypeMap[ExperimentalProtocol.ClassificationType.parameterName] = TokenType.parameter; +tokenTypeMap[ExperimentalProtocol.ClassificationType.className] = VSCodeShPlugin.TokenType.class; +tokenTypeMap[ExperimentalProtocol.ClassificationType.enumName] = VSCodeShPlugin.TokenType.enum; +tokenTypeMap[ExperimentalProtocol.ClassificationType.interfaceName] = VSCodeShPlugin.TokenType.interface; +tokenTypeMap[ExperimentalProtocol.ClassificationType.moduleName] = VSCodeShPlugin.TokenType.namespace; +tokenTypeMap[ExperimentalProtocol.ClassificationType.typeParameterName] = VSCodeShPlugin.TokenType.typeParameter; +tokenTypeMap[ExperimentalProtocol.ClassificationType.typeAliasName] = VSCodeShPlugin.TokenType.type; +tokenTypeMap[ExperimentalProtocol.ClassificationType.parameterName] = VSCodeShPlugin.TokenType.parameter; namespace ExperimentalProtocol { diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index 98feebef484c3..cc6e217941dba 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -626,10 +626,10 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -typescript-vscode-sh-plugin@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.4.1.tgz#6982958419ca760c0add761dd1d5f398815bee8c" - integrity sha512-IPsz/FNuk9HsjaEOsJJxGkiKGK5E4muZtjntp/BDWYZVYOj3R8JtX6++pQ0ftMOcZwxjKxeBmnS1FSUdVMifSw== +typescript-vscode-sh-plugin@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.3.2.tgz#bde9d0eba24ca5856024811fa354a9f5a7aeebe5" + integrity sha512-XsalETsSf3y7VWxk36plqHpsbl+TUDl278HEuhPHVBcNnwuTjcIq52J/CJw84xYmxmBcTIPUgIgLLS4OE5nX2A== uri-js@^4.2.2: version "4.2.2" From d02eb0ecd23317372854e653688ff91db710e015 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 14 Jan 2020 10:15:08 +0100 Subject: [PATCH 259/315] Just apply review feedback --- .../browser/viewParts/linesDecorations/linesDecorations.ts | 2 +- src/vs/editor/browser/widget/diffEditorWidget.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts b/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts index fb2970e763af0..fc6a255eb8322 100644 --- a/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts +++ b/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts @@ -97,7 +97,7 @@ export class LinesDecorationsOverlay extends DedupOverlay { const classNames = toRender[lineIndex]; let lineOutput = ''; for (let i = 0, len = classNames.length; i < len; i++) { - lineOutput += '
Date: Tue, 14 Jan 2020 10:15:58 +0100 Subject: [PATCH 260/315] remote - allow to open files from user home with tilde syntax (#83213) --- .../contrib/search/browser/openFileHandler.ts | 11 +++++----- .../tasks/browser/abstractTaskService.ts | 6 +++--- .../tasks/browser/terminalTaskSystem.ts | 14 +++---------- .../dialogs/browser/simpleFileDialog.ts | 20 ++++++------------- .../services/path/common/remotePathService.ts | 19 ++++++++++++++++-- 5 files changed, 34 insertions(+), 36 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/openFileHandler.ts b/src/vs/workbench/contrib/search/browser/openFileHandler.ts index ffb54bbfa7686..e3c5e56547240 100644 --- a/src/vs/workbench/contrib/search/browser/openFileHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openFileHandler.ts @@ -21,7 +21,6 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IResourceInput } from 'vs/platform/editor/common/editor'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -121,11 +120,10 @@ export class OpenFileHandler extends QuickOpenHandler { @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @ISearchService private readonly searchService: ISearchService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IRemotePathService private readonly remotePathService: IRemotePathService, @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, @IFileService private readonly fileService: IFileService, - @ILabelService private readonly labelService: ILabelService, - @IRemotePathService private readonly remotePathService: IRemotePathService, + @ILabelService private readonly labelService: ILabelService ) { super(); @@ -187,11 +185,12 @@ export class OpenFileHandler extends QuickOpenHandler { } private async getAbsolutePathResult(query: IPreparedQuery): Promise { - const detildifiedQuery = untildify(query.original, this.environmentService.userHome); + const detildifiedQuery = untildify(query.original, (await this.remotePathService.userHome).path); if ((await this.remotePathService.path).isAbsolute(detildifiedQuery)) { const resource = toLocalResource( await this.remotePathService.fileURI(detildifiedQuery), - this.workbenchEnvironmentService.configuration.remoteAuthority); + this.workbenchEnvironmentService.configuration.remoteAuthority + ); try { const stat = await this.fileService.resolve(resource); diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 19760bb59a73a..7c4b1af65ade3 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -70,7 +70,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { RunAutomaticTasks } from 'vs/workbench/contrib/tasks/browser/runAutomaticTasks'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; import { format } from 'vs/base/common/jsonFormatter'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { applyEdits } from 'vs/base/common/jsonEdit'; @@ -256,7 +256,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @ITerminalInstanceService private readonly terminalInstanceService: ITerminalInstanceService, - @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, + @IRemotePathService private readonly remotePathService: IRemotePathService, @ITextModelService private readonly textModelResolverService: ITextModelService, @IPreferencesService private readonly preferencesService: IPreferencesService ) { @@ -1316,7 +1316,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.modelService, this.configurationResolverService, this.telemetryService, this.contextService, this.environmentService, AbstractTaskService.OutputChannelId, this.fileService, this.terminalInstanceService, - this.remoteAgentService, + this.remotePathService, (workspaceFolder: IWorkspaceFolder) => { if (!workspaceFolder) { return undefined; diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 837428886f6e2..9815f49fdf458 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -43,7 +43,7 @@ import { URI } from 'vs/base/common/uri'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { Schemas } from 'vs/base/common/network'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; import { env as processEnv, cwd as processCwd } from 'vs/base/common/process'; interface TerminalData { @@ -176,7 +176,7 @@ export class TerminalTaskSystem implements ITaskSystem { private outputChannelId: string, private fileService: IFileService, private terminalInstanceService: ITerminalInstanceService, - private remoteAgentService: IRemoteAgentService, + private remotePathService: IRemotePathService, taskSystemInfoResolver: TaskSystemInfoResolver, ) { @@ -829,14 +829,6 @@ export class TerminalTaskSystem implements ITaskSystem { return nls.localize('TerminalTaskSystem.terminalName', 'Task - {0}', needsFolderQualification ? task.getQualifiedLabel() : task.configurationProperties.name); } - private async getUserHome(): Promise { - const env = await this.remoteAgentService.getEnvironment(); - if (env) { - return env.userHome; - } - return URI.from({ scheme: Schemas.file, path: this.environmentService.userHome }); - } - private async createShellLaunchConfig(task: CustomTask | ContributedTask, workspaceFolder: IWorkspaceFolder | undefined, variableResolver: VariableResolver, platform: Platform.Platform, options: CommandOptions, command: CommandString, args: CommandString[], waitOnExit: boolean | string): Promise { let shellLaunchConfig: IShellLaunchConfig; let isShellCommand = task.command.runtime === RuntimeType.Shell; @@ -867,7 +859,7 @@ export class TerminalTaskSystem implements ITaskSystem { windowsShellArgs = true; let basename = path.basename(shellLaunchConfig.executable!).toLowerCase(); // If we don't have a cwd, then the terminal uses the home dir. - const userHome = await this.getUserHome(); + const userHome = await this.remotePathService.userHome; if (basename === 'cmd.exe' && ((options.cwd && isUNC(options.cwd)) || (!options.cwd && isUNC(userHome.fsPath)))) { return undefined; } diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index 8d9fcbd9fa0d3..d8161e31d6e91 100644 --- a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -35,6 +35,7 @@ import { ICommandHandler } from 'vs/platform/commands/common/commands'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { normalizeDriveLetter } from 'vs/base/common/labels'; import { SaveReason } from 'vs/workbench/common/editor'; +import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; export namespace OpenLocalFileCommand { export const ID = 'workbench.action.files.openLocalFile'; @@ -134,6 +135,7 @@ export class SimpleFileDialog { @IModeService private readonly modeService: IModeService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, + @IRemotePathService private readonly remotePathService: IRemotePathService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IContextKeyService contextKeyService: IContextKeyService, ) { @@ -154,8 +156,8 @@ export class SimpleFileDialog { public async showOpenDialog(options: IOpenDialogOptions = {}): Promise { this.scheme = this.getScheme(options.availableFileSystems, options.defaultUri); - this.userHome = await this.getUserHome(); - const newOptions = await this.getOptions(options); + this.userHome = await this.remotePathService.userHome; + const newOptions = this.getOptions(options); if (!newOptions) { return Promise.resolve(undefined); } @@ -165,9 +167,9 @@ export class SimpleFileDialog { public async showSaveDialog(options: ISaveDialogOptions): Promise { this.scheme = this.getScheme(options.availableFileSystems, options.defaultUri); - this.userHome = await this.getUserHome(); + this.userHome = await this.remotePathService.userHome; this.requiresTrailing = true; - const newOptions = await this.getOptions(options, true); + const newOptions = this.getOptions(options, true); if (!newOptions) { return Promise.resolve(undefined); } @@ -229,16 +231,6 @@ export class SimpleFileDialog { return this.remoteAgentEnvironment; } - private async getUserHome(): Promise { - if (this.scheme !== Schemas.file) { - const env = await this.getRemoteAgentEnvironment(); - if (env) { - return env.userHome; - } - } - return URI.from({ scheme: this.scheme, path: this.environmentService.userHome }); - } - private async pickResource(isSave: boolean = false): Promise { this.allowFolderSelection = !!this.options.canSelectFolders; this.allowFileSelection = !!this.options.canSelectFiles; diff --git a/src/vs/workbench/services/path/common/remotePathService.ts b/src/vs/workbench/services/path/common/remotePathService.ts index fa6b20e9c5c7d..344ff8c160403 100644 --- a/src/vs/workbench/services/path/common/remotePathService.ts +++ b/src/vs/workbench/services/path/common/remotePathService.ts @@ -9,6 +9,8 @@ import { URI } from 'vs/base/common/uri'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { Schemas } from 'vs/base/common/network'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; const REMOTE_PATH_SERVICE_ID = 'remotePath'; export const IRemotePathService = createDecorator(REMOTE_PATH_SERVICE_ID); @@ -16,8 +18,10 @@ export const IRemotePathService = createDecorator(REMOTE_PAT export interface IRemotePathService { _serviceBrand: undefined; - path: Promise; + readonly path: Promise; fileURI(path: string): Promise; + + readonly userHome: Promise; } /** @@ -29,7 +33,8 @@ export class RemotePathService implements IRemotePathService { private _extHostOS: Promise; constructor( - @IRemoteAgentService readonly remoteAgentService: IRemoteAgentService + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService ) { this._extHostOS = remoteAgentService.getEnvironment().then(remoteEnvironment => { return remoteEnvironment ? remoteEnvironment.os : platform.OS; @@ -76,6 +81,16 @@ export class RemotePathService implements IRemotePathService { fragment: '' }); } + + get userHome(): Promise { + return this.remoteAgentService.getEnvironment().then(env => { + if (env) { + return env.userHome; + } + + return URI.from({ scheme: Schemas.file, path: this.environmentService.userHome }); + }); + } } registerSingleton(IRemotePathService, RemotePathService, true); From 707061ce17eab1b1a4a31f393a827e4ccaa0ac3a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 14 Jan 2020 10:17:42 +0100 Subject: [PATCH 261/315] :lipstick: --- src/vs/workbench/services/path/common/remotePathService.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/services/path/common/remotePathService.ts b/src/vs/workbench/services/path/common/remotePathService.ts index 344ff8c160403..b2d63984cd405 100644 --- a/src/vs/workbench/services/path/common/remotePathService.ts +++ b/src/vs/workbench/services/path/common/remotePathService.ts @@ -84,10 +84,13 @@ export class RemotePathService implements IRemotePathService { get userHome(): Promise { return this.remoteAgentService.getEnvironment().then(env => { + + // remote: use remote environment userHome if (env) { return env.userHome; } + // local: use the userHome from environment return URI.from({ scheme: Schemas.file, path: this.environmentService.userHome }); }); } From 76429d2f58c8d309f152f0619fe0021bf5d27e14 Mon Sep 17 00:00:00 2001 From: Dima Krasner Date: Tue, 14 Jan 2020 11:27:15 +0200 Subject: [PATCH 262/315] =?UTF-8?q?Bump=20node-native-keymap=20to=202.1.1?= =?UTF-8?q?=20for=20easier=20arm64=20cross-compil=E2=80=A6=20(#86659)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump node-native-keymap to 2.1.1 for easier arm64 cross-compilation --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 922d877c743bc..7981b10d33a27 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "jschardet": "2.1.1", "keytar": "^4.11.0", "native-is-elevated": "0.4.1", - "native-keymap": "2.1.0", + "native-keymap": "2.1.1", "native-watchdog": "1.3.0", "node-pty": "^0.10.0-beta2", "onigasm-umd": "2.2.5", diff --git a/yarn.lock b/yarn.lock index 38a8d55738f7e..ebee4f4271586 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6213,10 +6213,10 @@ native-is-elevated@0.4.1: resolved "https://registry.yarnpkg.com/native-is-elevated/-/native-is-elevated-0.4.1.tgz#f6391aafb13441f5b949b39ae0b466b06e7f3986" integrity sha512-2vBXCXCXYKLDjP0WzrXs/AFjDb2njPR31EbGiZ1mR2fMJg211xClK1Xm19RXve35kvAL4dBKOFGCMIyc2+pPsw== -native-keymap@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/native-keymap/-/native-keymap-2.1.0.tgz#f3a92e647ac021fe552587b0020f8132efb03078" - integrity sha512-a5VYhjMqxe+HK5VzJM8yIcJOKkeuMSKYfmS0p7VEKSc7hM0F5IPsq7XO8KtwAgV8PJhfQVgAgyQmK8u/MQQ0aw== +native-keymap@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/native-keymap/-/native-keymap-2.1.1.tgz#8b6ba2d4e81b5fe44948ebe3ff88e3777b70d4d6" + integrity sha512-8ACRLXS9xMmRQVWK8YYp0zUSW4PjXuF2VGYu1kq0WYcND9yyr3BASyhopn+VXP3nDehS3Ct5FjDtsOiLEhBvmQ== native-watchdog@1.3.0: version "1.3.0" From 5a019002121c3dc8b1c0acfd640f2072c3a2ec7b Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 14 Jan 2020 10:31:05 +0100 Subject: [PATCH 263/315] [semantic tokens] Remove "-" minus styles. Fixes #86282 --- .../platform/theme/common/tokenClassificationRegistry.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index 7076d918348c7..d7eb816aa9839 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -71,11 +71,8 @@ export namespace TokenStyle { while ((match = expression.exec(fontStyle))) { switch (match[0]) { case 'bold': bold = true; break; - case '-bold': bold = false; break; case 'italic': italic = true; break; - case '-italic': italic = false; break; case 'underline': underline = true; break; - case '-underline': underline = false; break; } } } @@ -220,10 +217,10 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { }, fontStyle: { type: 'string', - description: nls.localize('schema.token.fontStyle', 'Font style of the rule: \'italic\', \'bold\' or \'underline\', \'-italic\', \'-bold\' or \'-underline\'or a combination. The empty string unsets inherited settings.'), + description: nls.localize('schema.token.fontStyle', 'Font style of the rule: \'italic\', \'bold\' or \'underline\' or a combination. The empty string unsets inherited settings.'), pattern: fontStylePattern, - patternErrorMessage: nls.localize('schema.fontStyle.error', 'Font style must be \'italic\', \'bold\' or \'underline\' to set a style or \'-italic\', \'-bold\' or \'-underline\' to unset or a combination. The empty string unsets all styles.'), - defaultSnippets: [{ label: nls.localize('schema.token.fontStyle.none', 'None (clear inherited style)'), bodyText: '""' }, { body: 'italic' }, { body: 'bold' }, { body: 'underline' }, { body: '-italic' }, { body: '-bold' }, { body: '-underline' }, { body: 'italic bold' }, { body: 'italic underline' }, { body: 'bold underline' }, { body: 'italic bold underline' }] + patternErrorMessage: nls.localize('schema.fontStyle.error', 'Font style must be \'italic\', \'bold\' or \'underline\' or a combination. The empty string unsets all styles.'), + defaultSnippets: [{ label: nls.localize('schema.token.fontStyle.none', 'None (clear inherited style)'), bodyText: '""' }, { body: 'italic' }, { body: 'bold' }, { body: 'underline' }, { body: 'italic underline' }, { body: 'bold underline' }, { body: 'italic bold underline' }] } }, additionalProperties: false, From 3d6aef8b9f211d2f8596ef864b412c1070564a2c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 14 Jan 2020 10:38:38 +0100 Subject: [PATCH 264/315] #87461 Small refactorings - Remove cyclic dependency between view descriptor collection and views service - use view collection for getting and listening on views given a container - return view descriptor colleciton always for a container --- .../parts/activitybar/activitybarPart.ts | 14 +-- .../browser/parts/panel/panelPart.ts | 8 +- src/vs/workbench/browser/parts/views/views.ts | 105 +++++++++--------- src/vs/workbench/common/views.ts | 9 +- .../quickopen/browser/viewPickerHandler.ts | 2 +- 5 files changed, 61 insertions(+), 77 deletions(-) diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 9cdf544c79230..610109d40a44a 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -190,7 +190,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { const viewContainer = this.getViewContainer(viewletDescriptor.id); if (viewContainer?.hideIfEmpty) { const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); - if (viewDescriptors?.activeViewDescriptors.length === 0) { + if (viewDescriptors.activeViewDescriptors.length === 0) { this.hideComposite(viewletDescriptor.id); // Update the composite bar by hiding } } @@ -411,10 +411,8 @@ export class ActivitybarPart extends Part implements IActivityBarService { const viewContainer = this.getViewContainer(viewlet.id); if (viewContainer?.hideIfEmpty) { const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); - if (viewDescriptors) { - this.onDidChangeActiveViews(viewlet, viewDescriptors); - this.viewletDisposables.set(viewlet.id, viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(viewlet, viewDescriptors))); - } + this.onDidChangeActiveViews(viewlet, viewDescriptors); + this.viewletDisposables.set(viewlet.id, viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(viewlet, viewDescriptors))); } } } @@ -552,10 +550,8 @@ export class ActivitybarPart extends Part implements IActivityBarService { const views: { when: string | undefined }[] = []; if (viewContainer) { const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); - if (viewDescriptors) { - for (const { when } of viewDescriptors.allViewDescriptors) { - views.push({ when: when ? when.serialize() : undefined }); - } + for (const { when } of viewDescriptors.allViewDescriptors) { + views.push({ when: when ? when.serialize() : undefined }); } } state.push({ id: compositeItem.id, name: viewlet.name, iconUrl: viewlet.iconUrl && viewlet.iconUrl.scheme === Schemas.file ? viewlet.iconUrl : undefined, views, pinned: compositeItem.pinned, order: compositeItem.order, visible: compositeItem.visible }); diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index c381e6021cd6e..4220c47084b00 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -185,10 +185,8 @@ export class PanelPart extends CompositePart implements IPanelService { const viewContainer = this.getViewContainer(panel.id); if (viewContainer?.hideIfEmpty) { const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); - if (viewDescriptors) { - this.onDidChangeActiveViews(panel, viewDescriptors); - this.panelDisposables.set(panel.id, viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(panel, viewDescriptors))); - } + this.onDidChangeActiveViews(panel, viewDescriptors); + this.panelDisposables.set(panel.id, viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(panel, viewDescriptors))); } } } @@ -296,7 +294,7 @@ export class PanelPart extends CompositePart implements IPanelService { const viewContainer = this.getViewContainer(panelDescriptor.id); if (viewContainer?.hideIfEmpty) { const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer); - if (viewDescriptors?.activeViewDescriptors.length === 0) { + if (viewDescriptors.activeViewDescriptors.length === 0) { this.hideComposite(panelDescriptor.id); // Update the composite bar by hiding } } diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 3ba413e80ef9b..5f19242c30972 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -80,8 +80,11 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl private contextKeys = new CounterSet(); private items: IViewItem[] = []; - private _onDidChange: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }>()); - readonly onDidChangeActiveViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._onDidChange.event; + private _onDidChangeViews: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }>()); + readonly onDidChangeViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._onDidChangeViews.event; + + private _onDidChangeActiveViews: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }>()); + readonly onDidChangeActiveViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._onDidChangeActiveViews.event; get activeViewDescriptors(): IViewDescriptor[] { return this.items @@ -94,35 +97,13 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl } constructor( - container: ViewContainer, @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService ) { super(); - const onRelevantViewsRegistered = filterViewRegisterEvent(container, this.viewDescriptorService.onViewsRegistered); - this._register(onRelevantViewsRegistered(this.onViewsRegistered, this)); - - const onRelevantViewsMoved = filterViewMoveEvent(container, this.viewDescriptorService.onDidChangeContainer); - this._register(onRelevantViewsMoved(({ added, removed }) => { - if (isNonEmptyArray(added)) { - this.onViewsRegistered(added); - } - if (isNonEmptyArray(removed)) { - this.onViewsDeregistered(removed); - } - })); - - const onRelevantViewsDeregistered = filterViewRegisterEvent(container, this.viewDescriptorService.onViewsDeregistered); - this._register(onRelevantViewsDeregistered(this.onViewsDeregistered, this)); - - const onRelevantContextChange = Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys)); - this._register(onRelevantContextChange(this.onContextChanged, this)); - - - this.onViewsRegistered(this.viewDescriptorService.getViews(container)); + this._register(Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys))(this.onContextChanged, this)); } - private onViewsRegistered(viewDescriptors: IViewDescriptor[]): void { + addViews(viewDescriptors: IViewDescriptor[]): void { const added: IViewDescriptor[] = []; for (const viewDescriptor of viewDescriptors) { @@ -144,12 +125,14 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl } } + this._onDidChangeViews.fire({ added: viewDescriptors, removed: [] }); + if (added.length) { - this._onDidChange.fire({ added, removed: [] }); + this._onDidChangeActiveViews.fire({ added, removed: [] }); } } - private onViewsDeregistered(viewDescriptors: IViewDescriptor[]): void { + removeViews(viewDescriptors: IViewDescriptor[]): void { const removed: IViewDescriptor[] = []; for (const viewDescriptor of viewDescriptors) { @@ -173,8 +156,10 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl } } + this._onDidChangeViews.fire({ added: [], removed: viewDescriptors }); + if (removed.length) { - this._onDidChange.fire({ added: [], removed }); + this._onDidChangeActiveViews.fire({ added: [], removed }); } } @@ -197,7 +182,7 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl } if (added.length || removed.length) { - this._onDidChange.fire({ added, removed }); + this._onDidChangeActiveViews.fire({ added, removed }); } } @@ -257,11 +242,8 @@ export class ContributableViewsModel extends Disposable { ) { super(); const viewDescriptorCollection = viewsService.getViewDescriptors(container); - - if (viewDescriptorCollection) { - this._register(viewDescriptorCollection.onDidChangeActiveViews(() => this.onDidChangeViewDescriptors(viewDescriptorCollection.activeViewDescriptors))); - this.onDidChangeViewDescriptors(viewDescriptorCollection.activeViewDescriptors); - } + this._register(viewDescriptorCollection.onDidChangeActiveViews(() => this.onDidChangeViewDescriptors(viewDescriptorCollection.activeViewDescriptors))); + this.onDidChangeViewDescriptors(viewDescriptorCollection.activeViewDescriptors); } isVisible(id: string): boolean { @@ -649,9 +631,14 @@ export class ViewsService extends Disposable implements IViewsService { this.viewDisposable.clear(); })); - this.viewContainersRegistry.all.forEach(viewContainer => this.onViewsRegistered(this.viewDescriptorService.getViews(viewContainer), viewContainer)); - this._register(this.viewDescriptorService.onViewsRegistered(({ views, viewContainer }) => this.onViewsRegistered(views, viewContainer))); - this._register(this.viewDescriptorService.onViewsDeregistered(({ views, viewContainer }) => this.onViewsDeregistered(views, viewContainer))); + this.viewContainersRegistry.all.forEach(viewContainer => { + const viewDescriptorCollection = this.viewDescriptorService.getViewDescriptors(viewContainer); + this.onViewsRegistered(viewDescriptorCollection.allViewDescriptors, viewContainer); + this._register(viewDescriptorCollection.onDidChangeViews(({ added, removed }) => { + this.onViewsRegistered(added, viewContainer); + this.onViewsDeregistered(removed, viewContainer); + })); + }); } private onViewsRegistered(views: IViewDescriptor[], container: ViewContainer): void { @@ -704,7 +691,6 @@ export class ViewsService extends Disposable implements IViewsService { } } - private async openComposite(compositeId: string, location: ViewContainerLocation, focus?: boolean): Promise { if (location === ViewContainerLocation.Sidebar) { return this.viewletService.openViewlet(compositeId, focus); @@ -792,18 +778,14 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor return this.viewContainers.get(viewId) ?? null; } - getViewDescriptors(container: ViewContainer): IViewDescriptorCollection | null { - const registeredViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).get(container.id); - if (registeredViewContainer) { - let viewDescriptorCollectionItem = this.viewDescriptorCollections.get(registeredViewContainer); - if (!viewDescriptorCollectionItem) { - // Create and register the collection if does not exist - this.onDidRegisterViewContainer(registeredViewContainer); - viewDescriptorCollectionItem = this.viewDescriptorCollections.get(registeredViewContainer); - } - return viewDescriptorCollectionItem!.viewDescriptorCollection; + getViewDescriptors(container: ViewContainer): IViewDescriptorCollection { + let viewDescriptorCollectionItem = this.viewDescriptorCollections.get(container); + if (!viewDescriptorCollectionItem) { + // Create and register the collection if does not exist + this.onDidRegisterViewContainer(container); + viewDescriptorCollectionItem = this.viewDescriptorCollections.get(container); } - return null; + return viewDescriptorCollectionItem!.viewDescriptorCollection; } moveViews(views: IViewDescriptor[], viewContainer: ViewContainer): void { @@ -821,13 +803,26 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } } - getViews(container: ViewContainer): IViewDescriptor[] { - return this.viewsRegistry.getViews(container); - } - private onDidRegisterViewContainer(viewContainer: ViewContainer): void { const disposables = new DisposableStore(); - const viewDescriptorCollection = disposables.add(new ViewDescriptorCollection(viewContainer, this.contextKeyService, this)); + const viewDescriptorCollection = disposables.add(new ViewDescriptorCollection(this.contextKeyService)); + viewDescriptorCollection.addViews(this.viewsRegistry.getViews(viewContainer)); + + const onRelevantViewsRegistered = filterViewRegisterEvent(viewContainer, this.onViewsRegistered); + onRelevantViewsRegistered(viewDescriptors => viewDescriptorCollection.addViews(viewDescriptors), this, disposables); + + const onRelevantViewsMoved = filterViewMoveEvent(viewContainer, this.onDidChangeContainer); + onRelevantViewsMoved(({ added, removed }) => { + if (isNonEmptyArray(added)) { + viewDescriptorCollection.addViews(added); + } + if (isNonEmptyArray(removed)) { + viewDescriptorCollection.removeViews(removed); + } + }, this, disposables); + + const onRelevantViewsDeregistered = filterViewRegisterEvent(viewContainer, this.onViewsDeregistered); + onRelevantViewsDeregistered(viewDescriptors => viewDescriptorCollection.removeViews(viewDescriptors), this, disposables); this.onDidChangeActiveViews({ added: viewDescriptorCollection.activeViewDescriptors, removed: [] }); viewDescriptorCollection.onDidChangeActiveViews(changed => this.onDidChangeActiveViews(changed), this, disposables); diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 72f90b88cd6da..7e8cecb2ffa31 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -202,6 +202,7 @@ export interface IViewDescriptor { } export interface IViewDescriptorCollection extends IDisposable { + readonly onDidChangeViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[] }>; readonly onDidChangeActiveViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[] }>; readonly activeViewDescriptors: IViewDescriptor[]; readonly allViewDescriptors: IViewDescriptor[]; @@ -360,17 +361,11 @@ export interface IViewDescriptorService { _serviceBrand: undefined; - readonly onViewsRegistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }>; - - readonly onViewsDeregistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }>; - readonly onDidChangeContainer: Event<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>; moveViews(views: IViewDescriptor[], viewContainer: ViewContainer): void; - getViews(container: ViewContainer): IViewDescriptor[]; - - getViewDescriptors(container: ViewContainer): IViewDescriptorCollection | null; + getViewDescriptors(container: ViewContainer): IViewDescriptorCollection; getViewContainer(viewId: string): ViewContainer | null; } diff --git a/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts b/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts index eefa445672855..88d9c55a925dd 100644 --- a/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts @@ -199,7 +199,7 @@ export class ViewPickerHandler extends QuickOpenHandler { const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).get(viewlet.id); if (viewContainer?.hideIfEmpty) { const viewsCollection = this.viewDescriptorService.getViewDescriptors(viewContainer); - return !!viewsCollection && viewsCollection.activeViewDescriptors.length > 0; + return viewsCollection.activeViewDescriptors.length > 0; } return true; } From 611f25cce24ee2a4a05365a21181977680616e63 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 14 Jan 2020 10:47:29 +0100 Subject: [PATCH 265/315] :lipstick: --- .../browser/browserTextFileService.ts | 2 +- .../textfile/browser/textFileService.ts | 37 +++----- .../textfile/common/textFileEditorModel.ts | 94 ++++++++++++------- 3 files changed, 74 insertions(+), 59 deletions(-) diff --git a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts index 37bbc4bc07066..6ef4e3b0b917f 100644 --- a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts +++ b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts @@ -59,7 +59,7 @@ export class BrowserTextFileService extends AbstractTextFileService { } protected async getWindowCount(): Promise { - return 1; // Browser only ever is 1 window + return 1; // web: we only track 1 window, not multiple } } diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 3c0ece2ce4356..8a6c4157362a1 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { Emitter, AsyncEmitter } from 'vs/base/common/event'; import * as platform from 'vs/base/common/platform'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, ITextFileEditorModelManager, ITextFileEditorModel, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, FileOperationWillRunEvent, FileOperationDidRunEvent, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, ITextFileEditorModel, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, FileOperationWillRunEvent, FileOperationDidRunEvent, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { IRevertOptions, IEncodingSupport } from 'vs/workbench/common/editor'; import { ILifecycleService, ShutdownReason, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; @@ -56,8 +56,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex //#endregion - private _models: TextFileEditorModelManager; - get models(): ITextFileEditorModelManager { return this._models; } + readonly models = this._register(this.instantiationService.createInstance(TextFileEditorModelManager)); abstract get encoding(): IResourceEncodings; @@ -82,13 +81,9 @@ export abstract class AbstractTextFileService extends Disposable implements ITex ) { super(); - this._models = this._register(instantiationService.createInstance(TextFileEditorModelManager)); - this.registerListeners(); } - //#region event handling - private registerListeners(): void { // Lifecycle @@ -96,6 +91,8 @@ export abstract class AbstractTextFileService extends Disposable implements ITex this.lifecycleService.onShutdown(this.dispose, this); } + //#region shutdown / backup handling + protected onBeforeShutdown(reason: ShutdownReason): boolean | Promise { // Dirty files need treatment on shutdown @@ -277,7 +274,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex //#endregion - //#region primitives (read, create, move, delete, update) + //#region text file IO primitives (read, create, move, delete, update) async read(resource: URI, options?: IReadTextFileOptions): Promise { const content = await this.fileService.readFile(resource, options); @@ -479,7 +476,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex //#endregion - //#region save/revert + //#region save async save(resource: URI, options?: ITextFileSaveOptions): Promise { return !(await this.saveAll([resource], options)).results.some(result => result.error); @@ -660,7 +657,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return models; } - return this._models.getAll(arg1); + return this.models.getAll(arg1); } private getDirtyFileModels(resources?: URI | URI[]): ITextFileEditorModel[] { @@ -699,7 +696,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // If the source is an existing text file model, we can directly // use that model to copy the contents to the target destination - const textFileModel = this._models.get(source); + const textFileModel = this.models.get(source); if (textFileModel && textFileModel.isResolved()) { success = await this.doSaveAsTextFile(textFileModel, source, target, options); } @@ -846,6 +843,10 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return untitledResource.with({ path: untitledFileName }); } + //#endregion + + //#region revert + async revert(resource: URI, options?: IRevertOptions): Promise { return !(await this.revertAll([resource], options)).results.some(result => result.error); } @@ -900,6 +901,8 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return { results: mapResourceToResult.values() }; } + //#endregion + getDirty(resources?: URI[]): URI[] { // Collect files @@ -914,21 +917,11 @@ export abstract class AbstractTextFileService extends Disposable implements ITex isDirty(resource?: URI): boolean { // Check for dirty file - if (this._models.getAll(resource).some(model => model.isDirty())) { + if (this.models.getAll(resource).some(model => model.isDirty())) { return true; } // Check for dirty untitled return this.untitledTextEditorService.getDirty().some(dirty => !resource || dirty.toString() === resource.toString()); } - - //#endregion - - dispose(): void { - - // Clear all caches - this._models.clear(); - - super.dispose(); - } } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 7abf65e410c9b..0c0917181d3a5 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -187,6 +187,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.preferredMode = mode; } + //#region Backup + async backup(target = this.resource): Promise { if (this.isResolved()) { @@ -210,6 +212,10 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.backupFileService.hasBackupSync(this.resource, this.versionId); } + //#endregion + + //#region Revert + async revert(options?: IRevertOptions): Promise { if (!this.isResolved()) { return false; @@ -217,7 +223,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Unset flags const wasDirty = this.dirty; - const undo = this.setDirty(false); + const undo = this.doSetDirty(false); // Force read from disk unless reverting soft const softUndo = options?.soft; @@ -244,6 +250,10 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return true; } + //#endregion + + //#region Load + async load(options?: ILoadOptions): Promise { this.logService.trace('[text file model] load() - enter', this.resource.toString()); @@ -349,7 +359,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Guard against the model having changed in the meantime if (currentVersionId === this.versionId) { - this.setDirty(false); // Ensure we are not tracking a stale state + this.doSetDirty(false); // Ensure we are not tracking a stale state } return this; @@ -423,7 +433,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Ensure we are not tracking a stale state else { - this.setDirty(false); + this.doSetDirty(false); } // Model Listeners @@ -434,7 +444,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.logService.trace('[text file model] load() - updated text editor model', this.resource.toString()); // Ensure we are not tracking a stale state - this.setDirty(false); + this.doSetDirty(false); // Update model value in a block that ignores model content change events this.blockModelContentChange = true; @@ -479,7 +489,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Clear flags const wasDirty = this.dirty; - this.setDirty(false); + this.doSetDirty(false); // Emit event if (wasDirty) { @@ -497,6 +507,14 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this._onDidChangeContent.fire(); } + //#endregion + + //#region Dirty + + isDirty(): this is IResolvedTextFileEditorModel { + return this.dirty; + } + makeDirty(): void { if (!this.isResolved()) { return; // only resolved models can be marked dirty @@ -509,7 +527,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Track dirty state and version id const wasDirty = this.dirty; - this.setDirty(true); + this.doSetDirty(true); // Emit as Event if we turned dirty if (!wasDirty) { @@ -517,6 +535,34 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } + private doSetDirty(dirty: boolean): () => void { + const wasDirty = this.dirty; + const wasInConflictMode = this.inConflictMode; + const wasInErrorMode = this.inErrorMode; + const oldBufferSavedVersionId = this.bufferSavedVersionId; + + if (!dirty) { + this.dirty = false; + this.inConflictMode = false; + this.inErrorMode = false; + this.updateSavedVersionId(); + } else { + this.dirty = true; + } + + // Return function to revert this call + return () => { + this.dirty = wasDirty; + this.inConflictMode = wasInConflictMode; + this.inErrorMode = wasInErrorMode; + this.bufferSavedVersionId = oldBufferSavedVersionId; + }; + } + + //#endregion + + //#region Save + async save(options: ITextFileSaveOptions = Object.create(null)): Promise { if (!this.isResolved()) { return false; @@ -666,7 +712,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Update dirty state unless model has changed meanwhile if (versionId === this.versionId) { this.logService.trace(`[text file model] doSave(${versionId}) - setting dirty to false because versionId did not change`, this.resource.toString()); - this.setDirty(false); + this.doSetDirty(false); } else { this.logService.trace(`[text file model] doSave(${versionId}) - not setting dirty to false because versionId did change meanwhile`, this.resource.toString()); } @@ -718,30 +764,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil }, error => onUnexpectedError(error) /* just log any error but do not notify the user since the file was not dirty */)); } - private setDirty(dirty: boolean): () => void { - const wasDirty = this.dirty; - const wasInConflictMode = this.inConflictMode; - const wasInErrorMode = this.inErrorMode; - const oldBufferSavedVersionId = this.bufferSavedVersionId; - - if (!dirty) { - this.dirty = false; - this.inConflictMode = false; - this.inErrorMode = false; - this.updateSavedVersionId(); - } else { - this.dirty = true; - } - - // Return function to revert this call - return () => { - this.dirty = wasDirty; - this.inConflictMode = wasInConflictMode; - this.inErrorMode = wasInErrorMode; - this.bufferSavedVersionId = oldBufferSavedVersionId; - }; - } - private updateSavedVersionId(): void { // we remember the models alternate version id to remember when the version // of the model matches with the saved version on disk. we need to keep this @@ -784,9 +806,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil TextFileEditorModel.saveErrorHandler.onSaveError(error, this); } - isDirty(): this is IResolvedTextFileEditorModel { - return this.dirty; - } + //#endregion getLastSaveAttemptTime(): number { return this.lastSaveAttemptTime; @@ -819,6 +839,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.preferredMode; } + //#region Encoding + getEncoding(): string | undefined { return this.preferredEncoding || this.contentEncoding; } @@ -883,6 +905,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return true; } + //#endregion + isResolved(): this is IResolvedTextFileEditorModel { return !!this.textEditorModel; } @@ -908,5 +932,3 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil super.dispose(); } } - - From 7c8b0844ec5f136bd6290e90bc0dd5d36ee1d04a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 14 Jan 2020 10:48:05 +0100 Subject: [PATCH 266/315] fix #87330 --- .../vscode-api-tests/src/singlefolder-tests/window.test.ts | 6 +----- extensions/vscode-api-tests/src/utils.ts | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts index 1e6d2f60669d3..620ce762632e4 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts @@ -145,23 +145,19 @@ suite('window namespace tests', () => { }); }); - test.skip('active editor not always correct... #49125', async function () { + test('active editor not always correct... #49125', async function () { const randomFile1 = await createRandomFile(); const randomFile2 = await createRandomFile(); - console.log('Created random files: ' + randomFile1.toString() + ' and ' + randomFile2.toString()); - const [docA, docB] = await Promise.all([ workspace.openTextDocument(randomFile1), workspace.openTextDocument(randomFile2) ]); for (let c = 0; c < 4; c++) { let editorA = await window.showTextDocument(docA, ViewColumn.One); - console.log('Showing: ' + editorA.document.fileName + ' and active editor is: ' + window.activeTextEditor?.document.fileName); assert.equal(window.activeTextEditor, editorA); let editorB = await window.showTextDocument(docB, ViewColumn.Two); - console.log('Showing: ' + editorB.document.fileName + ' and active editor is: ' + window.activeTextEditor?.document.fileName); assert.equal(window.activeTextEditor, editorB); } }); diff --git a/extensions/vscode-api-tests/src/utils.ts b/extensions/vscode-api-tests/src/utils.ts index 969a7cd00510a..0058c215f8775 100644 --- a/extensions/vscode-api-tests/src/utils.ts +++ b/extensions/vscode-api-tests/src/utils.ts @@ -46,7 +46,6 @@ export function pathEquals(path1: string, path2: string): boolean { export function closeAllEditors(): Thenable { return vscode.commands.executeCommand('workbench.action.closeAllEditors'); - } export function disposeAll(disposables: vscode.Disposable[]) { From 926407a8b52e078e4d2347f52a43f4609459f82c Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 14 Jan 2020 10:58:10 +0100 Subject: [PATCH 267/315] fix semantic token style caching --- src/vs/editor/common/services/modelServiceImpl.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 95b967ab21509..b32c5fcd02613 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -598,11 +598,12 @@ class SemanticColoringProviderStyling { } else { const tokenType = this._legend.tokenTypes[tokenTypeIndex]; const tokenModifiers: string[] = []; - for (let modifierIndex = 0; tokenModifierSet !== 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) { - if (tokenModifierSet & 1) { + let modifierSet = tokenModifierSet; + for (let modifierIndex = 0; modifierSet > 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) { + if (modifierSet & 1) { tokenModifiers.push(this._legend.tokenModifiers[modifierIndex]); } - tokenModifierSet = tokenModifierSet >> 1; + modifierSet = modifierSet >> 1; } metadata = this._themeService.getTheme().getTokenStyleMetadata(tokenType, tokenModifiers); From 160a1c69787f8c97a5edca6ea2ba34bb36710838 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 14 Jan 2020 10:50:29 +0100 Subject: [PATCH 268/315] suggest - dont force widget creation --- .../contrib/suggest/suggestController.ts | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index cdf2e4f41efa3..4ea61b2929cc7 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -165,7 +165,20 @@ export class SuggestController implements IEditorContribution { })); this._toDispose.add(toDisposable(() => makesTextEdit.reset())); + this._toDispose.add(widget.onDetailsKeyDown(e => { + // cmd + c on macOS, ctrl + c on Win / Linux + if ( + e.toKeybinding().equals(new SimpleKeybinding(true, false, false, false, KeyCode.KEY_C)) || + (platform.isMacintosh && e.toKeybinding().equals(new SimpleKeybinding(false, false, false, true, KeyCode.KEY_C))) + ) { + e.stopPropagation(); + return; + } + if (!e.toKeybinding().isModifierKey()) { + this.editor.focus(); + } + })); return widget; })); @@ -198,21 +211,6 @@ export class SuggestController implements IEditorContribution { } })); - this._toDispose.add(this.widget.getValue().onDetailsKeyDown(e => { - // cmd + c on macOS, ctrl + c on Win / Linux - if ( - e.toKeybinding().equals(new SimpleKeybinding(true, false, false, false, KeyCode.KEY_C)) || - (platform.isMacintosh && e.toKeybinding().equals(new SimpleKeybinding(false, false, false, true, KeyCode.KEY_C))) - ) { - e.stopPropagation(); - return; - } - - if (!e.toKeybinding().isModifierKey()) { - this.editor.focus(); - } - })); - // Manage the acceptSuggestionsOnEnter context key let acceptSuggestionsOnEnter = SuggestContext.AcceptSuggestionsOnEnter.bindTo(_contextKeyService); let updateFromConfig = () => { From 31ef68ff7c293bfbd781cce00aac730e0a3d00eb Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 14 Jan 2020 11:08:58 +0100 Subject: [PATCH 269/315] Adopt latest istanbul libraries --- package.json | 10 +- test/coverage.js | 49 +++--- test/electron/renderer.js | 2 +- yarn.lock | 324 ++++++++++++++++++++++---------------- 4 files changed, 219 insertions(+), 166 deletions(-) diff --git a/package.json b/package.json index 7981b10d33a27..71baf2eb07383 100644 --- a/package.json +++ b/package.json @@ -121,11 +121,11 @@ "husky": "^0.13.1", "innosetup": "5.6.1", "is": "^3.1.0", - "istanbul-lib-coverage": "^2.0.5", - "istanbul-lib-instrument": "^3.3.0", - "istanbul-lib-report": "^2.0.8", - "istanbul-lib-source-maps": "^3.0.6", - "istanbul-reports": "^2.2.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.0", "jsdom-no-contextify": "^3.1.0", "lazy.js": "^0.4.2", "merge-options": "^1.0.1", diff --git a/test/coverage.js b/test/coverage.js index bf7e7aa3f957e..1964dfc4bd025 100644 --- a/test/coverage.js +++ b/test/coverage.js @@ -22,7 +22,7 @@ exports.initialize = function (loaderConfig) { return contents; } // Try to find a .map file - let map = null; + let map = undefined; try { map = JSON.parse(fs.readFileSync(`${source}.map`).toString()); } catch (err) { @@ -35,32 +35,33 @@ exports.initialize = function (loaderConfig) { exports.createReport = function (isSingle) { const mapStore = iLibSourceMaps.createSourceMapStore(); const coverageMap = iLibCoverage.createCoverageMap(global.__coverage__); - const transformed = mapStore.transformCoverage(coverageMap); + return mapStore.transformCoverage(coverageMap).then((transformed) => { + // Paths come out all broken + let newData = Object.create(null); + Object.keys(transformed.data).forEach((file) => { + const entry = transformed.data[file]; + const fixedPath = fixPath(entry.path); + entry.data.path = fixedPath; + newData[fixedPath] = entry; + }); + transformed.data = newData; - // Paths come out all broken - let newData = Object.create(null); - Object.keys(transformed.map.data).forEach((file) => { - const entry = transformed.map.data[file]; - const fixedPath = fixPath(entry.path); - entry.data.path = fixedPath; - newData[fixedPath] = entry; - }); - transformed.map.data = newData; + const context = iLibReport.createContext({ + dir: path.join(__dirname, `../.build/coverage${isSingle ? '-single' : ''}`), + coverageMap: transformed + }); + const tree = context.getTree('flat'); - const tree = iLibReport.summarizers.flat(transformed.map); - const context = iLibReport.createContext({ - dir: path.join(__dirname, `../.build/coverage${isSingle ? '-single' : ''}`) + let reports = []; + if (isSingle) { + reports.push(iReports.create('lcovonly')); + } else { + reports.push(iReports.create('json')); + reports.push(iReports.create('lcov')); + reports.push(iReports.create('html')); + } + reports.forEach(report => tree.visit(report, context)); }); - - let reports = []; - if (isSingle) { - reports.push(iReports.create('lcovonly')); - } else { - reports.push(iReports.create('json')); - reports.push(iReports.create('lcov')); - reports.push(iReports.create('html')); - } - reports.forEach(report => tree.visit(report, context)); }; function toUpperDriveLetter(str) { diff --git a/test/electron/renderer.js b/test/electron/renderer.js index 36668cd53b308..152abff374622 100644 --- a/test/electron/renderer.js +++ b/test/electron/renderer.js @@ -50,7 +50,7 @@ function initLoader(opts) { function createCoverageReport(opts) { if (opts.coverage) { - coverage.createReport(opts.run || opts.runGlob); + return coverage.createReport(opts.run || opts.runGlob); } return Promise.resolve(undefined); } diff --git a/yarn.lock b/yarn.lock index ebee4f4271586..334e3f3054364 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14,39 +14,75 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/generator@^7.4.0", "@babel/generator@^7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.5.0.tgz#f20e4b7a91750ee8b63656073d843d2a736dca4a" - integrity sha512-1TTVrt7J9rcG5PMjvO7VEG3FrEoEJNHxumRq66GemPmzboLWtIjjcJgk8rokuAS7IiRSpgVSu5Vb9lc99iJkOA== +"@babel/code-frame@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" + integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== + dependencies: + "@babel/highlight" "^7.8.3" + +"@babel/core@^7.7.5": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.8.3.tgz#30b0ebb4dd1585de6923a0b4d179e0b9f5d82941" + integrity sha512-4XFkf8AwyrEG7Ziu3L2L0Cv+WyY47Tcsp70JFmpftbAA1K7YL/sgE9jh9HyNj08Y/U50ItUchpN0w6HxAoX1rA== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.8.3" + "@babel/helpers" "^7.8.3" + "@babel/parser" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.0" + lodash "^4.17.13" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.3.tgz#0e22c005b0a94c1c74eafe19ef78ce53a4d45c03" + integrity sha512-WjoPk8hRpDRqqzRpvaR8/gDUPkrnOOeuT2m8cNICJtZH6mwaCo3v0OKMI7Y6SM1pBtyijnLtAL0HDi41pf41ug== dependencies: - "@babel/types" "^7.5.0" + "@babel/types" "^7.8.3" jsesc "^2.5.1" - lodash "^4.17.11" + lodash "^4.17.13" source-map "^0.5.0" - trim-right "^1.0.1" -"@babel/helper-function-name@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz#a0ceb01685f73355d4360c1247f582bfafc8ff53" - integrity sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw== +"@babel/helper-function-name@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca" + integrity sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA== dependencies: - "@babel/helper-get-function-arity" "^7.0.0" - "@babel/template" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/helper-get-function-arity" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/types" "^7.8.3" -"@babel/helper-get-function-arity@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3" - integrity sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ== +"@babel/helper-get-function-arity@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" + integrity sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-split-export-declaration@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" + integrity sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.8.3" -"@babel/helper-split-export-declaration@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677" - integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q== +"@babel/helpers@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.8.3.tgz#382fbb0382ce7c4ce905945ab9641d688336ce85" + integrity sha512-LmU3q9Pah/XyZU89QvBgGt+BCsTPoQa+73RxAQh8fb8qkDyIfeQnmgs+hvzhTCKTzqOyk7JTkS3MS1S8Mq5yrQ== dependencies: - "@babel/types" "^7.4.4" + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" "@babel/highlight@^7.0.0": version "7.0.0" @@ -57,42 +93,51 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.4.3", "@babel/parser@^7.4.4", "@babel/parser@^7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.5.0.tgz#3e0713dff89ad6ae37faec3b29dcfc5c979770b7" - integrity sha512-I5nW8AhGpOXGCCNYGc+p7ExQIBxRFnS2fd/d862bNOKvmoEPjYPcfIjsfdy0ujagYOIYPczKgD9l3FsgTkAzKA== - -"@babel/template@^7.1.0", "@babel/template@^7.4.0": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237" - integrity sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw== +"@babel/highlight@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797" + integrity sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg== dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.4.4" - "@babel/types" "^7.4.4" + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" -"@babel/traverse@^7.4.3": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.5.0.tgz#4216d6586854ef5c3c4592dab56ec7eb78485485" - integrity sha512-SnA9aLbyOCcnnbQEGwdfBggnc142h/rbqqsXcaATj2hZcegCl903pUD/lfpsNBlBSuWow/YDfRyJuWi2EPR5cg== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.5.0" - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.4.4" - "@babel/parser" "^7.5.0" - "@babel/types" "^7.5.0" +"@babel/parser@^7.7.5", "@babel/parser@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.3.tgz#790874091d2001c9be6ec426c2eed47bc7679081" + integrity sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ== + +"@babel/template@^7.7.4", "@babel/template@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.3.tgz#e02ad04fe262a657809327f578056ca15fd4d1b8" + integrity sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/parser" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/traverse@^7.7.4", "@babel/traverse@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.8.3.tgz#a826215b011c9b4f73f3a893afbc05151358bf9a" + integrity sha512-we+a2lti+eEImHmEXp7bM9cTxGzxPmBiVJlLVD+FuuQMeeO7RaDbutbgeheDkw+Xe3mCfJHnGOWLswT74m2IPg== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.8.3" + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/parser" "^7.8.3" + "@babel/types" "^7.8.3" debug "^4.1.0" globals "^11.1.0" - lodash "^4.17.11" + lodash "^4.17.13" -"@babel/types@^7.0.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.5.0.tgz#e47d43840c2e7f9105bc4d3a2c371b4d0c7832ab" - integrity sha512-UFpDVqRABKsW01bvw7/wSUe56uy6RXM5+VJibVVAybDGxEW25jdwiFJEf7ASvSaC7sN7rbE/l3cLp2izav+CtQ== +"@babel/types@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.3.tgz#5a383dffa5416db1b73dedffd311ffd0788fb31c" + integrity sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg== dependencies: esutils "^2.0.2" - lodash "^4.17.11" + lodash "^4.17.13" to-fast-properties "^2.0.0" "@electron/get@^1.0.1": @@ -110,6 +155,11 @@ global-agent "^2.0.2" global-tunnel-ng "^2.7.1" +"@istanbuljs/schema@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" + integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== + "@sindresorhus/is@^0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" @@ -1934,11 +1984,6 @@ commander@~2.13.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" integrity sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA== -commander@~2.20.0: - version "2.20.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" - integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== - commandpost@^1.0.0: version "1.2.1" resolved "https://registry.yarnpkg.com/commandpost/-/commandpost-1.2.1.tgz#2e9c4c7508b9dc704afefaa91cab92ee6054cc68" @@ -2036,6 +2081,13 @@ convert-source-map@^1.5.0: dependencies: safe-buffer "~5.1.1" +convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== + dependencies: + safe-buffer "~5.1.1" + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -3724,6 +3776,11 @@ generate-object-property@^1.1.0: dependencies: is-property "^1.0.0" +gensync@^1.0.0-beta.1: + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" + integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== + get-caller-file@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" @@ -4314,17 +4371,6 @@ gulplog@^1.0.0: dependencies: glogg "^1.0.0" -handlebars@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67" - integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw== - dependencies: - neo-async "^2.6.0" - optimist "^0.6.1" - source-map "^0.6.1" - optionalDependencies: - uglify-js "^3.1.4" - har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -4378,6 +4424,11 @@ has-flag@^3.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + has-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" @@ -4505,6 +4556,11 @@ html-comment-regex@^1.1.0: resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" integrity sha1-ZouTd26q5V696POtRkswekljYl4= +html-escaper@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.0.tgz#71e87f931de3fe09e56661ab9a29aadec707b491" + integrity sha512-a4u9BeERWGu/S8JiWEAQcdrg9v4QArtP9keViQjGMdff20fBdd8waotXaNmODqBe6uZ3Nafi7K/ho4gCQHV3Ig== + "htmlparser2@>= 3.7.3 < 4.0.0", htmlparser2@^3.9.1: version "3.9.2" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338" @@ -5157,50 +5213,49 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= -istanbul-lib-coverage@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49" - integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA== +istanbul-lib-coverage@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" + integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg== -istanbul-lib-instrument@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630" - integrity sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA== - dependencies: - "@babel/generator" "^7.4.0" - "@babel/parser" "^7.4.3" - "@babel/template" "^7.4.0" - "@babel/traverse" "^7.4.3" - "@babel/types" "^7.4.0" - istanbul-lib-coverage "^2.0.5" - semver "^6.0.0" +istanbul-lib-instrument@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.0.tgz#53321a7970f076262fd3292c8f9b2e4ac544aae1" + integrity sha512-Nm4wVHdo7ZXSG30KjZ2Wl5SU/Bw7bDx1PdaiIFzEStdjs0H12mOTncn1GVYuqQSaZxpg87VGBRsVRPGD2cD1AQ== + dependencies: + "@babel/core" "^7.7.5" + "@babel/parser" "^7.7.5" + "@babel/template" "^7.7.4" + "@babel/traverse" "^7.7.4" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" -istanbul-lib-report@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz#5a8113cd746d43c4889eba36ab10e7d50c9b4f33" - integrity sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ== +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== dependencies: - istanbul-lib-coverage "^2.0.5" - make-dir "^2.1.0" - supports-color "^6.1.0" + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" -istanbul-lib-source-maps@^3.0.6: - version "3.0.6" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz#284997c48211752ec486253da97e3879defba8c8" - integrity sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw== +istanbul-lib-source-maps@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz#75743ce6d96bb86dc7ee4352cf6366a23f0b1ad9" + integrity sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg== dependencies: debug "^4.1.1" - istanbul-lib-coverage "^2.0.5" - make-dir "^2.1.0" - rimraf "^2.6.3" + istanbul-lib-coverage "^3.0.0" source-map "^0.6.1" -istanbul-reports@^2.2.6: - version "2.2.6" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.6.tgz#7b4f2660d82b29303a8fe6091f8ca4bf058da1af" - integrity sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA== +istanbul-reports@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.0.tgz#d4d16d035db99581b6194e119bbf36c963c5eb70" + integrity sha512-2osTcC8zcOSUkImzN2EWQta3Vdi4WjjKw99P2yWx5mLnigAM0Rd5uYFn1cf2i/Ois45GkNjaoTqc5CxgMSX80A== dependencies: - handlebars "^4.1.2" + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" istextorbinary@1.0.2: version "1.0.2" @@ -5367,6 +5422,13 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" +json5@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.1.tgz#81b6cb04e9ba496f1c7005d07b4368a2638f90b6" + integrity sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ== + dependencies: + minimist "^1.2.0" + jsonfile@^2.1.0: version "2.4.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" @@ -5674,7 +5736,7 @@ lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== -lodash@^4.17.14, lodash@^4.17.15: +lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== @@ -5742,13 +5804,12 @@ make-dir@^1.0.0: dependencies: pify "^3.0.0" -make-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" - integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== +make-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.0.tgz#1b5f39f6b9270ed33f9f054c5c0f84304989f801" + integrity sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw== dependencies: - pify "^4.0.1" - semver "^5.6.0" + semver "^6.0.0" make-iterator@^1.0.0: version "1.0.1" @@ -6247,11 +6308,6 @@ neo-async@^2.5.0: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.1.tgz#acb909e327b1e87ec9ef15f41b8a269512ad41ee" integrity sha512-3KL3fvuRkZ7s4IFOMfztb7zJp3QaVWnBeGoJlgB38XnCRPj/0tLzzLG5IB8NYOHbJ8g8UGrgZv44GLDk6CxTxA== -neo-async@^2.6.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" - integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== - nice-try@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4" @@ -7017,11 +7073,6 @@ pify@^3.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== - pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" @@ -7999,6 +8050,13 @@ resolve@^1.1.6, resolve@^1.1.7: dependencies: path-parse "^1.0.5" +resolve@^1.3.2: + version "1.14.2" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.14.2.tgz#dbf31d0fa98b1f29aa5169783b9c290cb865fea2" + integrity sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ== + dependencies: + path-parse "^1.0.6" + resolve@^1.4.0: version "1.10.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" @@ -8034,7 +8092,7 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -rimraf@2, rimraf@2.6.3, rimraf@^2.6.3: +rimraf@2, rimraf@2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== @@ -8828,7 +8886,7 @@ supports-color@1.2.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-1.2.0.tgz#ff1ed1e61169d06b3cf2d588e188b18d8847e17e" integrity sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4= -supports-color@6.1.0, supports-color@^6.1.0: +supports-color@6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== @@ -8861,6 +8919,13 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" +supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + sver-compat@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/sver-compat/-/sver-compat-1.5.0.tgz#3cf87dfeb4d07b4a3f14827bc186b3fd0c645cd8" @@ -9154,11 +9219,6 @@ tough-cookie@~2.4.3: resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= -trim-right@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" - integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= - truncate-utf8-bytes@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b" @@ -9285,14 +9345,6 @@ uglify-es@^3.3.4: commander "~2.13.0" source-map "~0.6.1" -uglify-js@^3.1.4: - version "3.6.0" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5" - integrity sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg== - dependencies: - commander "~2.20.0" - source-map "~0.6.1" - uglifyjs-webpack-plugin@^1.2.4: version "1.2.7" resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.7.tgz#57638dd99c853a1ebfe9d97b42160a8a507f9d00" From c96aece131c24c3b3facfcdf042281617f8e1373 Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 14 Jan 2020 11:30:31 +0100 Subject: [PATCH 270/315] tree: respect isChecked and getRole #88553 --- src/vs/base/browser/ui/tree/abstractTree.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 732edc52118e6..2b2ed1d052df9 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -190,7 +190,13 @@ function asListOptions(modelProvider: () => ITreeModel { + return options.ariaProvider!.isChecked!(node.element); + } : undefined, + getRole: options.ariaProvider && options.ariaProvider.getRole ? (node) => { + return options.ariaProvider!.getRole!(node.element); + } : undefined } }; } From da5d2781b03d337f581991eca8ca43e0a988877f Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 14 Jan 2020 11:30:51 +0100 Subject: [PATCH 271/315] replace console with logService. For #84283 --- src/vs/workbench/api/node/extHostCLIServer.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/api/node/extHostCLIServer.ts b/src/vs/workbench/api/node/extHostCLIServer.ts index 1ccb71e52a917..ad28cc495c0f4 100644 --- a/src/vs/workbench/api/node/extHostCLIServer.ts +++ b/src/vs/workbench/api/node/extHostCLIServer.ts @@ -11,6 +11,7 @@ import { IWindowOpenable } from 'vs/platform/windows/common/windows'; import { URI } from 'vs/base/common/uri'; import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; import { INativeOpenWindowOptions } from 'vs/platform/windows/node/window'; +import { ILogService } from 'vs/platform/log/common/log'; export interface OpenCommandPipeArgs { type: 'open'; @@ -39,10 +40,10 @@ export class CLIServer { private _server: http.Server; private _ipcHandlePath: string | undefined; - constructor(@IExtHostCommands private _commands: IExtHostCommands) { + constructor(@IExtHostCommands private _commands: IExtHostCommands, @ILogService private logService: ILogService) { this._server = http.createServer((req, res) => this.onRequest(req, res)); this.setup().catch(err => { - console.error(err); + logService.error(err); return ''; }); } @@ -56,9 +57,9 @@ export class CLIServer { try { this._server.listen(this.ipcHandlePath); - this._server.on('error', err => console.error(err)); + this._server.on('error', err => this.logService.error(err)); } catch (err) { - console.error('Could not start open from terminal server.'); + this.logService.error('Could not start open from terminal server.'); } return this._ipcHandlePath; @@ -79,13 +80,13 @@ export class CLIServer { break; case 'command': this.runCommand(data, res) - .catch(console.error); + .catch(this.logService.error); break; default: res.writeHead(404); res.write(`Unknown message type: ${data.type}`, err => { if (err) { - console.error(err); + this.logService.error(err); } }); res.end(); @@ -139,7 +140,7 @@ export class CLIServer { res.writeHead(500); res.write(String(err), err => { if (err) { - console.error(err); + this.logService.error(err); } }); res.end(); @@ -153,7 +154,7 @@ export class CLIServer { res.writeHead(200); res.write(JSON.stringify(result), err => { if (err) { - console.error(err); + this.logService.error(err); } }); res.end(); @@ -161,7 +162,7 @@ export class CLIServer { res.writeHead(500); res.write(String(err), err => { if (err) { - console.error(err); + this.logService.error(err); } }); res.end(); From f43c3601e5feb57cc4dfe9c033881f1886d1d7ff Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 14 Jan 2020 11:31:29 +0100 Subject: [PATCH 272/315] tokenStyleResolving: fix test --- .../themes/test/electron-browser/tokenStyleResolving.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts index 0e84f4e22c4ea..6e2a7b23ac7f3 100644 --- a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts +++ b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts @@ -306,7 +306,7 @@ suite('Themes - TokenStyleResolving', () => { '*.static': { fontStyle: 'bold' }, '*.declaration': { fontStyle: 'italic' }, '*.async.static': { fontStyle: 'italic underline' }, - '*.async': { foreground: '#000fff', fontStyle: '-italic underline' } + '*.async': { foreground: '#000fff', fontStyle: 'underline' } }); assertTokenStyles(themeData, { @@ -316,7 +316,7 @@ suite('Themes - TokenStyleResolving', () => { 'class': ts('#0000ff', { italic: true }), 'class.static.declaration': ts('#0000ff', { bold: true, italic: true }), 'class.declaration': ts('#0000ff', { italic: true }), - 'class.declaration.async': ts('#000fff', { underline: true, italic: false }), + 'class.declaration.async': ts('#000fff', { underline: true, italic: true }), 'class.declaration.async.static': ts('#000fff', { italic: true, underline: true, bold: true }), }); From 1420967188b1a2ef98326cbb30b0fa5fc219b1c4 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 14 Jan 2020 12:18:20 +0100 Subject: [PATCH 273/315] fixes #86848 --- build/win32/code.iss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/win32/code.iss b/build/win32/code.iss index 5c995f26636c1..15293a0c5cf92 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -970,10 +970,10 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\*\shell\{#RegValueNam Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\directory\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "{cm:OpenWithCodeContextMenu,{#ShellNameShort}}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\directory\shell\{#RegValueName}"; ValueType: expandsz; ValueName: "Icon"; ValueData: "{app}\{#ExeBasename}.exe"; Tasks: addcontextmenufolders Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\directory\shell\{#RegValueName}\command"; ValueType: expandsz; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%V"""; Tasks: addcontextmenufolders -Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\directory\background\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "Open w&ith {#ShellNameShort}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\directory\background\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "{cm:OpenWithCodeContextMenu,{#ShellNameShort}}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\directory\background\shell\{#RegValueName}"; ValueType: expandsz; ValueName: "Icon"; ValueData: "{app}\{#ExeBasename}.exe"; Tasks: addcontextmenufolders Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\directory\background\shell\{#RegValueName}\command"; ValueType: expandsz; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%V"""; Tasks: addcontextmenufolders -Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\Drive\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "Open w&ith {#ShellNameShort}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\Drive\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "{cm:OpenWithCodeContextMenu,{#ShellNameShort}}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\Drive\shell\{#RegValueName}"; ValueType: expandsz; ValueName: "Icon"; ValueData: "{app}\{#ExeBasename}.exe"; Tasks: addcontextmenufolders Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\Drive\shell\{#RegValueName}\command"; ValueType: expandsz; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%V"""; Tasks: addcontextmenufolders From 1f5a5470c5679c85960fd495b40a08f9206d0f02 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 14 Jan 2020 15:30:17 +0100 Subject: [PATCH 274/315] implement backup on shutdown via working copies (#84672) --- build/lib/i18n.resources.json | 4 + .../backup/browser/backup.web.contribution.ts | 12 + .../backup/browser/backupOnShutdown.ts | 55 ++++ .../electron-browser/backup.contribution.ts | 12 + .../electron-browser/backupOnShutdown.ts | 207 +++++++++++++ .../electron-browser/backupOnShutdown.test.ts | 283 ++++++++++++++++++ .../electron-browser/backupRestorer.test.ts | 1 - .../electron-browser/backupTracker.test.ts | 1 - .../customEditor/common/customEditorModel.ts | 4 + .../files/test/browser/editorAutoSave.test.ts | 1 - .../test/browser/fileEditorTracker.test.ts | 2 - .../browser/browserTextFileService.ts | 47 +-- .../textfile/browser/textFileService.ts | 206 +------------ .../electron-browser/nativeTextFileService.ts | 13 +- .../textfile/test/textFileEditorModel.test.ts | 2 +- .../textfile/test/textFileService.io.test.ts | 1 - .../textfile/test/textFileService.test.ts | 230 +------------- .../test/textModelResolverService.test.ts | 1 - .../workingCopy/common/workingCopyService.ts | 6 +- .../test/common/workingCopyService.test.ts | 10 +- .../api/mainThreadSaveParticipant.test.ts | 2 +- .../workbench/test/workbenchTestServices.ts | 19 +- src/vs/workbench/workbench.desktop.main.ts | 3 + src/vs/workbench/workbench.web.main.ts | 3 + 24 files changed, 627 insertions(+), 498 deletions(-) create mode 100644 src/vs/workbench/contrib/backup/browser/backup.web.contribution.ts create mode 100644 src/vs/workbench/contrib/backup/browser/backupOnShutdown.ts create mode 100644 src/vs/workbench/contrib/backup/electron-browser/backup.contribution.ts create mode 100644 src/vs/workbench/contrib/backup/electron-browser/backupOnShutdown.ts create mode 100644 src/vs/workbench/contrib/backup/test/electron-browser/backupOnShutdown.test.ts diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 6b5c3a6589121..d10b90b6d9200 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -30,6 +30,10 @@ "name": "vs/workbench/api/common", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/backup", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/bulkEdit", "project": "vscode-workbench" diff --git a/src/vs/workbench/contrib/backup/browser/backup.web.contribution.ts b/src/vs/workbench/contrib/backup/browser/backup.web.contribution.ts new file mode 100644 index 0000000000000..e9d79317dd8d9 --- /dev/null +++ b/src/vs/workbench/contrib/backup/browser/backup.web.contribution.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { BackupOnShutdown } from 'vs/workbench/contrib/backup/browser/backupOnShutdown'; + +// Register Backup On Shutdown +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(BackupOnShutdown, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/backup/browser/backupOnShutdown.ts b/src/vs/workbench/contrib/backup/browser/backupOnShutdown.ts new file mode 100644 index 0000000000000..8c17c75255249 --- /dev/null +++ b/src/vs/workbench/contrib/backup/browser/backupOnShutdown.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; + +export class BackupOnShutdown extends Disposable implements IWorkbenchContribution { + + constructor( + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, + @ILifecycleService private readonly lifecycleService: ILifecycleService, + ) { + super(); + + this.registerListeners(); + } + + private registerListeners() { + + // Lifecycle + this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdown())); + } + + private onBeforeShutdown(): boolean { + + // Web: we cannot perform long running in the shutdown phase + // As such we need to check sync if there are any dirty working + // copies that have not been backed up yet and then prevent the + // shutdown if that is the case. + + const dirtyWorkingCopies = this.workingCopyService.workingCopies.filter(workingCopy => workingCopy.isDirty()); + if (!dirtyWorkingCopies.length) { + return false; // no dirty: no veto + } + + if (!this.filesConfigurationService.isHotExitEnabled) { + return true; // dirty without backup: veto + } + + for (const dirtyWorkingCopy of dirtyWorkingCopies) { + if (!dirtyWorkingCopy.hasBackup()) { + console.warn('Unload prevented: pending backups'); + return true; // dirty without backup: veto + } + } + + return false; // dirty with backups: no veto + } +} diff --git a/src/vs/workbench/contrib/backup/electron-browser/backup.contribution.ts b/src/vs/workbench/contrib/backup/electron-browser/backup.contribution.ts new file mode 100644 index 0000000000000..26ac4d2a0cb64 --- /dev/null +++ b/src/vs/workbench/contrib/backup/electron-browser/backup.contribution.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { BackupOnShutdown } from 'vs/workbench/contrib/backup/electron-browser/backupOnShutdown'; + +// Register Backup On Shutdown +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(BackupOnShutdown, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/backup/electron-browser/backupOnShutdown.ts b/src/vs/workbench/contrib/backup/electron-browser/backupOnShutdown.ts new file mode 100644 index 0000000000000..8e1053caa6519 --- /dev/null +++ b/src/vs/workbench/contrib/backup/electron-browser/backupOnShutdown.ts @@ -0,0 +1,207 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { ILifecycleService, LifecyclePhase, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { ConfirmResult, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { isMacintosh } from 'vs/base/common/platform'; +import { HotExitConfiguration } from 'vs/platform/files/common/files'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import type { ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; + +export class BackupOnShutdown extends Disposable implements IWorkbenchContribution { + + constructor( + @IBackupFileService private readonly backupFileService: IBackupFileService, + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, + @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, + @ILifecycleService private readonly lifecycleService: ILifecycleService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @INotificationService private readonly notificationService: INotificationService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IElectronService private readonly electronService: IElectronService + ) { + super(); + + this.registerListeners(); + } + + private registerListeners() { + + // Lifecycle + this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdown(event.reason))); + } + + private onBeforeShutdown(reason: ShutdownReason): boolean | Promise { + + // Dirty working copies need treatment on shutdown + const dirtyWorkingCopies = this.workingCopyService.workingCopies.filter(workingCopy => workingCopy.isDirty()); + if (dirtyWorkingCopies.length) { + + // If auto save is enabled, save all working copies and then check again for dirty copies + // We DO NOT run any save participant if we are in the shutdown phase for performance reasons + if (this.filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.OFF) { + return this.doSaveAll(dirtyWorkingCopies, false /* not untitled */, { skipSaveParticipants: true }).then(() => { + + // If we still have dirty working copies, we either have untitled ones or working copies that cannot be saved + const remainingDirtyWorkingCopies = this.workingCopyService.workingCopies.filter(workingCopy => workingCopy.isDirty()); + if (remainingDirtyWorkingCopies.length) { + return this.handleDirtyBeforeShutdown(remainingDirtyWorkingCopies, reason); + } + + return this.noVeto({ cleanUpBackups: false }); // no veto and no backup cleanup (since there are no dirty working copies) + }); + } + + // Auto save is not enabled + return this.handleDirtyBeforeShutdown(dirtyWorkingCopies, reason); + } + + // No dirty working copies: no veto + return this.noVeto({ cleanUpBackups: true }); + } + + private handleDirtyBeforeShutdown(workingCopies: IWorkingCopy[], reason: ShutdownReason): boolean | Promise { + + // If hot exit is enabled, backup dirty working copies and allow to exit without confirmation + if (this.filesConfigurationService.isHotExitEnabled) { + return this.backupBeforeShutdown(workingCopies, reason).then(didBackup => { + if (didBackup) { + return this.noVeto({ cleanUpBackups: false }); // no veto and no backup cleanup (since backup was successful) + } + + // since a backup did not happen, we have to confirm for the dirty working copies now + return this.confirmBeforeShutdown(); + }, error => { + this.notificationService.error(localize('backupOnShutdown.failSave', "Working copies that are dirty could not be written to the backup location (Error: {0}). Try saving your editors first and then exit.", error.message)); + + return true; // veto, the backups failed + }); + } + + // Otherwise just confirm from the user what to do with the dirty working copies + return this.confirmBeforeShutdown(); + } + + private async backupBeforeShutdown(workingCopies: IWorkingCopy[], reason: ShutdownReason): Promise { + + // When quit is requested skip the confirm callback and attempt to backup all workspaces. + // When quit is not requested the confirm callback should be shown when the window being + // closed is the only VS Code window open, except for on Mac where hot exit is only + // ever activated when quit is requested. + + let doBackup: boolean | undefined; + switch (reason) { + case ShutdownReason.CLOSE: + if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { + doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured + } else if (await this.electronService.getWindowCount() > 1 || isMacintosh) { + doBackup = false; // do not backup if a window is closed that does not cause quitting of the application + } else { + doBackup = true; // backup if last window is closed on win/linux where the application quits right after + } + break; + + case ShutdownReason.QUIT: + doBackup = true; // backup because next start we restore all backups + break; + + case ShutdownReason.RELOAD: + doBackup = true; // backup because after window reload, backups restore + break; + + case ShutdownReason.LOAD: + if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { + doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured + } else { + doBackup = false; // do not backup because we are switching contexts + } + break; + } + + if (!doBackup) { + return false; + } + + // Backup all working copies + await Promise.all(workingCopies.map(workingCopy => workingCopy.backup())); + + return true; + } + + private async confirmBeforeShutdown(): Promise { + + // Show confirm dialog for all dirty working copies + const dirtyWorkingCopies = this.workingCopyService.workingCopies.filter(workingCopy => workingCopy.isDirty()); + const confirm = await this.fileDialogService.showSaveConfirm(dirtyWorkingCopies.map(w => w.resource)); + + // Save + if (confirm === ConfirmResult.SAVE) { + await this.doSaveAll(dirtyWorkingCopies, true /* includeUntitled */, { skipSaveParticipants: true }); + + if (this.workingCopyService.hasDirty) { + return true; // veto if any save failed + } + + return this.noVeto({ cleanUpBackups: true }); + } + + // Don't Save + else if (confirm === ConfirmResult.DONT_SAVE) { + + // Make sure to revert working copies so that they do not restore + // see https://github.com/Microsoft/vscode/issues/29572 + await this.doRevertAll(dirtyWorkingCopies, { soft: true } /* soft revert is good enough on shutdown */); + + return this.noVeto({ cleanUpBackups: true }); + } + + // Cancel + else if (confirm === ConfirmResult.CANCEL) { + return true; // veto + } + + return false; + } + + private doSaveAll(workingCopies: IWorkingCopy[], includeUntitled: boolean, options: ISaveOptions): Promise { + return Promise.all(workingCopies.map(async workingCopy => { + if (workingCopy.isDirty() && (includeUntitled || !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled))) { + return workingCopy.save(options); + } + + return false; + })); + } + + private doRevertAll(workingCopies: IWorkingCopy[], options: IRevertOptions): Promise { + return Promise.all(workingCopies.map(workingCopy => workingCopy.revert(options))); + } + + private noVeto(options: { cleanUpBackups: boolean }): boolean | Promise { + if (!options.cleanUpBackups) { + return false; + } + + if (this.lifecycleService.phase < LifecyclePhase.Restored) { + return false; // if editors have not restored, we are not up to speed with backups and thus should not clean them + } + + if (this.environmentService.isExtensionDevelopment) { + return false; // extension development does not track any backups + } + + return this.backupFileService.discardAllWorkspaceBackups().then(() => false, () => false); + } +} diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupOnShutdown.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupOnShutdown.test.ts new file mode 100644 index 0000000000000..db14020165082 --- /dev/null +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupOnShutdown.test.ts @@ -0,0 +1,283 @@ +/*--------------------------------------------------------------------------------------------- + * 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 * as platform from 'vs/base/common/platform'; +import { ILifecycleService, BeforeShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; +import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestContextService, TestFileService, TestElectronService, TestFilesConfigurationService, TestFileDialogService, TestBackupFileService } from 'vs/workbench/test/workbenchTestServices'; +import { toResource } from 'vs/base/test/common/utils'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import { HotExitConfiguration, IFileService } from 'vs/platform/files/common/files'; +import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; +import { IWorkspaceContextService, Workspace } from 'vs/platform/workspace/common/workspace'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; +import { BackupOnShutdown } from 'vs/workbench/contrib/backup/electron-browser/backupOnShutdown'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; + +class ServiceAccessor { + constructor( + @ILifecycleService public lifecycleService: TestLifecycleService, + @ITextFileService public textFileService: TestTextFileService, + @IFilesConfigurationService public filesConfigurationService: TestFilesConfigurationService, + @IUntitledTextEditorService public untitledTextEditorService: IUntitledTextEditorService, + @IWorkspaceContextService public contextService: TestContextService, + @IModelService public modelService: ModelServiceImpl, + @IFileService public fileService: TestFileService, + @IElectronService public electronService: TestElectronService, + @IFileDialogService public fileDialogService: TestFileDialogService, + @IBackupFileService public backupFileService: TestBackupFileService + ) { + } +} + +class BeforeShutdownEventImpl implements BeforeShutdownEvent { + + value: boolean | Promise | undefined; + reason = ShutdownReason.CLOSE; + + veto(value: boolean | Promise): void { + this.value = value; + } +} + +suite('BackupOnShutdown', () => { + + let instantiationService: IInstantiationService; + let model: TextFileEditorModel; + let accessor: ServiceAccessor; + let backupOnShutdown: BackupOnShutdown; + + setup(() => { + instantiationService = workbenchInstantiationService(); + accessor = instantiationService.createInstance(ServiceAccessor); + backupOnShutdown = instantiationService.createInstance(BackupOnShutdown); + }); + + teardown(() => { + if (model) { + model.dispose(); + } + (accessor.textFileService.models).dispose(); + accessor.untitledTextEditorService.revertAll(); + backupOnShutdown.dispose(); + }); + + test('confirm onWillShutdown - no veto', async function () { + model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); + (accessor.textFileService.models).add(model.resource, model); + + const event = new BeforeShutdownEventImpl(); + accessor.lifecycleService.fireWillShutdown(event); + + const veto = event.value; + if (typeof veto === 'boolean') { + assert.ok(!veto); + } else { + assert.ok(!(await veto)); + } + }); + + test('confirm onWillShutdown - veto if user cancels', async function () { + model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); + (accessor.textFileService.models).add(model.resource, model); + + accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL); + + await model.load(); + model.textEditorModel!.setValue('foo'); + assert.equal(accessor.textFileService.getDirty().length, 1); + + const event = new BeforeShutdownEventImpl(); + accessor.lifecycleService.fireWillShutdown(event); + assert.ok(event.value); + }); + + test('confirm onWillShutdown - no veto and backups cleaned up if user does not want to save (hot.exit: off)', async function () { + model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); + (accessor.textFileService.models).add(model.resource, model); + + accessor.fileDialogService.setConfirmResult(ConfirmResult.DONT_SAVE); + accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } }); + + await model.load(); + model.textEditorModel!.setValue('foo'); + assert.equal(accessor.textFileService.getDirty().length, 1); + const event = new BeforeShutdownEventImpl(); + accessor.lifecycleService.fireWillShutdown(event); + + let veto = event.value; + if (typeof veto === 'boolean') { + assert.ok(accessor.backupFileService.didDiscardAllWorkspaceBackups); + assert.ok(!veto); + return; + } + + veto = await veto; + assert.ok(accessor.backupFileService.didDiscardAllWorkspaceBackups); + assert.ok(!veto); + }); + + test('confirm onWillShutdown - save (hot.exit: off)', async function () { + model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); + (accessor.textFileService.models).add(model.resource, model); + + accessor.fileDialogService.setConfirmResult(ConfirmResult.SAVE); + accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } }); + + await model.load(); + model.textEditorModel!.setValue('foo'); + assert.equal(accessor.textFileService.getDirty().length, 1); + const event = new BeforeShutdownEventImpl(); + accessor.lifecycleService.fireWillShutdown(event); + + const veto = await (>event.value); + assert.ok(!veto); + assert.ok(!model.isDirty()); + }); + + suite('Hot Exit', () => { + suite('"onExit" setting', () => { + test('should hot exit on non-Mac (reason: CLOSE, windows: single, workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.CLOSE, false, true, !!platform.isMacintosh); + }); + test('should hot exit on non-Mac (reason: CLOSE, windows: single, empty workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.CLOSE, false, false, !!platform.isMacintosh); + }); + test('should NOT hot exit (reason: CLOSE, windows: multiple, workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.CLOSE, true, true, true); + }); + test('should NOT hot exit (reason: CLOSE, windows: multiple, empty workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.CLOSE, true, false, true); + }); + test('should hot exit (reason: QUIT, windows: single, workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.QUIT, false, true, false); + }); + test('should hot exit (reason: QUIT, windows: single, empty workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.QUIT, false, false, false); + }); + test('should hot exit (reason: QUIT, windows: multiple, workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.QUIT, true, true, false); + }); + test('should hot exit (reason: QUIT, windows: multiple, empty workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.QUIT, true, false, false); + }); + test('should hot exit (reason: RELOAD, windows: single, workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.RELOAD, false, true, false); + }); + test('should hot exit (reason: RELOAD, windows: single, empty workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.RELOAD, false, false, false); + }); + test('should hot exit (reason: RELOAD, windows: multiple, workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.RELOAD, true, true, false); + }); + test('should hot exit (reason: RELOAD, windows: multiple, empty workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.RELOAD, true, false, false); + }); + test('should NOT hot exit (reason: LOAD, windows: single, workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.LOAD, false, true, true); + }); + test('should NOT hot exit (reason: LOAD, windows: single, empty workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.LOAD, false, false, true); + }); + test('should NOT hot exit (reason: LOAD, windows: multiple, workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.LOAD, true, true, true); + }); + test('should NOT hot exit (reason: LOAD, windows: multiple, empty workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.LOAD, true, false, true); + }); + }); + + suite('"onExitAndWindowClose" setting', () => { + test('should hot exit (reason: CLOSE, windows: single, workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.CLOSE, false, true, false); + }); + test('should hot exit (reason: CLOSE, windows: single, empty workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.CLOSE, false, false, !!platform.isMacintosh); + }); + test('should hot exit (reason: CLOSE, windows: multiple, workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.CLOSE, true, true, false); + }); + test('should NOT hot exit (reason: CLOSE, windows: multiple, empty workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.CLOSE, true, false, true); + }); + test('should hot exit (reason: QUIT, windows: single, workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.QUIT, false, true, false); + }); + test('should hot exit (reason: QUIT, windows: single, empty workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.QUIT, false, false, false); + }); + test('should hot exit (reason: QUIT, windows: multiple, workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.QUIT, true, true, false); + }); + test('should hot exit (reason: QUIT, windows: multiple, empty workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.QUIT, true, false, false); + }); + test('should hot exit (reason: RELOAD, windows: single, workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.RELOAD, false, true, false); + }); + test('should hot exit (reason: RELOAD, windows: single, empty workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.RELOAD, false, false, false); + }); + test('should hot exit (reason: RELOAD, windows: multiple, workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.RELOAD, true, true, false); + }); + test('should hot exit (reason: RELOAD, windows: multiple, empty workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.RELOAD, true, false, false); + }); + test('should hot exit (reason: LOAD, windows: single, workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.LOAD, false, true, false); + }); + test('should NOT hot exit (reason: LOAD, windows: single, empty workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.LOAD, false, false, true); + }); + test('should hot exit (reason: LOAD, windows: multiple, workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.LOAD, true, true, false); + }); + test('should NOT hot exit (reason: LOAD, windows: multiple, empty workspace)', function () { + return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.LOAD, true, false, true); + }); + }); + + async function hotExitTest(this: any, setting: string, shutdownReason: ShutdownReason, multipleWindows: boolean, workspace: boolean, shouldVeto: boolean): Promise { + model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); + (accessor.textFileService.models).add(model.resource, model); + + // Set hot exit config + accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: setting } }); + + // Set empty workspace if required + if (!workspace) { + accessor.contextService.setWorkspace(new Workspace('empty:1508317022751')); + } + + // Set multiple windows if required + if (multipleWindows) { + accessor.electronService.windowCount = Promise.resolve(2); + } + + // Set cancel to force a veto if hot exit does not trigger + accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL); + + await model.load(); + model.textEditorModel!.setValue('foo'); + assert.equal(accessor.textFileService.getDirty().length, 1); + + const event = new BeforeShutdownEventImpl(); + event.reason = shutdownReason; + accessor.lifecycleService.fireWillShutdown(event); + + const veto = await (>event.value); + assert.ok(!accessor.backupFileService.didDiscardAllWorkspaceBackups); // When hot exit is set, backups should never be cleaned since the confirm result is cancel + assert.equal(veto, shouldVeto); + } + }); +}); diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts index e3fa6b8c44ea9..ed9707995e9b7 100644 --- a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts @@ -86,7 +86,6 @@ suite('BackupRestorer', () => { dispose(disposables); disposables = []; - (accessor.textFileService.models).clear(); (accessor.textFileService.models).dispose(); accessor.untitledTextEditorService.revertAll(); diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts index 945cb8352de5b..533280f5c3bc4 100644 --- a/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts @@ -92,7 +92,6 @@ suite('BackupTracker', () => { dispose(disposables); disposables = []; - (accessor.textFileService.models).clear(); (accessor.textFileService.models).dispose(); accessor.untitledTextEditorService.revertAll(); diff --git a/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts b/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts index 6f7de36e6b3d0..8d3f2c20ef119 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts @@ -170,6 +170,10 @@ export class CustomEditorModel extends Disposable implements ICustomEditorModel this.updateContentChanged(); } + public hasBackup(): boolean { + return true; //TODO@matt forward to extension + } + public async backup(): Promise { //TODO@matt forward to extension } diff --git a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts index 68ba4c0060ddc..8bdc754410ffe 100644 --- a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts @@ -101,7 +101,6 @@ suite('EditorAutoSave', () => { part.dispose(); editorAutoSave.dispose(); - (accessor.textFileService.models).clear(); (accessor.textFileService.models).dispose(); }); diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts index f0362d48c0f04..d69f586eee02e 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts @@ -79,7 +79,6 @@ suite('Files - FileEditorTracker', () => { assert.equal(snapshotToString(model.createSnapshot()!), 'Hello Html'); tracker.dispose(); - (accessor.textFileService.models).clear(); (accessor.textFileService.models).dispose(); }); @@ -120,7 +119,6 @@ suite('Files - FileEditorTracker', () => { part.dispose(); tracker.dispose(); - (accessor.textFileService.models).clear(); (accessor.textFileService.models).dispose(); }); diff --git a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts index 6ef4e3b0b917f..3e64ab18e6e11 100644 --- a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts +++ b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts @@ -7,7 +7,6 @@ import { AbstractTextFileService } from 'vs/workbench/services/textfile/browser/ import { ITextFileService, IResourceEncodings, IResourceEncoding, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; export class BrowserTextFileService extends AbstractTextFileService { @@ -17,49 +16,21 @@ export class BrowserTextFileService extends AbstractTextFileService { } }; - protected onBeforeShutdown(reason: ShutdownReason): boolean { - // Web: we cannot perform long running in the shutdown phase - // As such we need to check sync if there are any dirty files - // that have not been backed up yet and then prevent the shutdown - // if that is the case. - return this.doBeforeShutdownSync(); + protected registerListeners(): void { + super.registerListeners(); + + // Lifecycle + this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdown(event.reason))); } - private doBeforeShutdownSync(): boolean { + protected onBeforeShutdown(reason: ShutdownReason): boolean { if (this.models.getAll().some(model => model.hasState(ModelState.PENDING_SAVE))) { - return true; // files are pending to be saved: veto - } - - const dirtyResources = this.getDirty(); - if (!dirtyResources.length) { - return false; // no dirty: no veto - } + console.warn('Unload prevented: pending file saves'); - if (!this.filesConfigurationService.isHotExitEnabled) { - return true; // dirty without backup: veto - } - - for (const dirtyResource of dirtyResources) { - let hasBackup = false; - - if (this.fileService.canHandleResource(dirtyResource)) { - const model = this.models.get(dirtyResource); - hasBackup = !!(model?.hasBackup()); - } else if (dirtyResource.scheme === Schemas.untitled) { - hasBackup = this.untitledTextEditorService.hasBackup(dirtyResource); - } - - if (!hasBackup) { - console.warn('Unload prevented: pending backups'); - return true; // dirty without backup: veto - } + return true; // files are pending to be saved: veto } - return false; // dirty with backups: no veto - } - - protected async getWindowCount(): Promise { - return 1; // web: we only track 1 window, not multiple + return false; } } diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 8a6c4157362a1..edf1aac01d93f 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -7,12 +7,10 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { Emitter, AsyncEmitter } from 'vs/base/common/event'; import * as platform from 'vs/base/common/platform'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, ITextFileEditorModel, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, FileOperationWillRunEvent, FileOperationDidRunEvent, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { IRevertOptions, IEncodingSupport } from 'vs/workbench/common/editor'; -import { ILifecycleService, ShutdownReason, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IFileService, FileOperationError, FileOperationResult, HotExitConfiguration, IFileStatWithMetadata, ICreateFileOptions, FileOperation } from 'vs/platform/files/common/files'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { IFileService, FileOperationError, FileOperationResult, IFileStatWithMetadata, ICreateFileOptions, FileOperation } from 'vs/platform/files/common/files'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; @@ -24,9 +22,8 @@ import { Schemas } from 'vs/base/common/network'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { createTextBufferFactoryFromSnapshot, createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { INotificationService } from 'vs/platform/notification/common/notification'; import { isEqualOrParent, isEqual, joinPath, dirname, extname, basename, toLocalResource } from 'vs/base/common/resources'; -import { IDialogService, IFileDialogService, ISaveDialogOptions, IConfirmation, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IFileDialogService, ISaveDialogOptions, IConfirmation } from 'vs/platform/dialogs/common/dialogs'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { coalesce } from 'vs/base/common/arrays'; @@ -35,7 +32,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { ITextSnapshot } from 'vs/editor/common/model'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; -import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; @@ -61,16 +58,13 @@ export abstract class AbstractTextFileService extends Disposable implements ITex abstract get encoding(): IResourceEncodings; constructor( - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IFileService protected readonly fileService: IFileService, @IUntitledTextEditorService protected readonly untitledTextEditorService: IUntitledTextEditorService, - @ILifecycleService private readonly lifecycleService: ILifecycleService, + @ILifecycleService protected readonly lifecycleService: ILifecycleService, @IInstantiationService protected readonly instantiationService: IInstantiationService, @IModeService private readonly modeService: IModeService, @IModelService private readonly modelService: IModelService, @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, - @INotificationService private readonly notificationService: INotificationService, - @IBackupFileService private readonly backupFileService: IBackupFileService, @IHistoryService private readonly historyService: IHistoryService, @IDialogService private readonly dialogService: IDialogService, @IFileDialogService private readonly fileDialogService: IFileDialogService, @@ -84,196 +78,12 @@ export abstract class AbstractTextFileService extends Disposable implements ITex this.registerListeners(); } - private registerListeners(): void { + protected registerListeners(): void { // Lifecycle - this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdown(event.reason))); this.lifecycleService.onShutdown(this.dispose, this); } - //#region shutdown / backup handling - - protected onBeforeShutdown(reason: ShutdownReason): boolean | Promise { - - // Dirty files need treatment on shutdown - const dirty = this.getDirty(); - if (dirty.length) { - - // If auto save is enabled, save all files and then check again for dirty files - // We DO NOT run any save participant if we are in the shutdown phase for performance reasons - if (this.filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.OFF) { - return this.saveAll(false /* files only */, { skipSaveParticipants: true }).then(() => { - - // If we still have dirty files, we either have untitled ones or files that cannot be saved - const remainingDirty = this.getDirty(); - if (remainingDirty.length) { - return this.handleDirtyBeforeShutdown(remainingDirty, reason); - } - - return false; - }); - } - - // Auto save is not enabled - return this.handleDirtyBeforeShutdown(dirty, reason); - } - - // No dirty files: no veto - return this.noVeto({ cleanUpBackups: true }); - } - - private handleDirtyBeforeShutdown(dirty: URI[], reason: ShutdownReason): boolean | Promise { - - // If hot exit is enabled, backup dirty files and allow to exit without confirmation - if (this.filesConfigurationService.isHotExitEnabled) { - return this.backupBeforeShutdown(dirty, reason).then(didBackup => { - if (didBackup) { - return this.noVeto({ cleanUpBackups: false }); // no veto and no backup cleanup (since backup was successful) - } - - // since a backup did not happen, we have to confirm for the dirty files now - return this.confirmBeforeShutdown(); - }, error => { - this.notificationService.error(nls.localize('files.backup.failSave', "Files that are dirty could not be written to the backup location (Error: {0}). Try saving your files first and then exit.", error.message)); - - return true; // veto, the backups failed - }); - } - - // Otherwise just confirm from the user what to do with the dirty files - return this.confirmBeforeShutdown(); - } - - private async backupBeforeShutdown(dirtyToBackup: URI[], reason: ShutdownReason): Promise { - // When quit is requested skip the confirm callback and attempt to backup all workspaces. - // When quit is not requested the confirm callback should be shown when the window being - // closed is the only VS Code window open, except for on Mac where hot exit is only - // ever activated when quit is requested. - - let doBackup: boolean | undefined; - switch (reason) { - case ShutdownReason.CLOSE: - if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { - doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured - } else if (await this.getWindowCount() > 1 || platform.isMacintosh) { - doBackup = false; // do not backup if a window is closed that does not cause quitting of the application - } else { - doBackup = true; // backup if last window is closed on win/linux where the application quits right after - } - break; - - case ShutdownReason.QUIT: - doBackup = true; // backup because next start we restore all backups - break; - - case ShutdownReason.RELOAD: - doBackup = true; // backup because after window reload, backups restore - break; - - case ShutdownReason.LOAD: - if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { - doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured - } else { - doBackup = false; // do not backup because we are switching contexts - } - break; - } - - if (!doBackup) { - return false; - } - - await this.backupAll(dirtyToBackup); - - return true; - } - - protected abstract getWindowCount(): Promise; - - private backupAll(dirtyToBackup: URI[]): Promise { - - // split up between files and untitled - const filesToBackup: ITextFileEditorModel[] = []; - const untitledToBackup: URI[] = []; - dirtyToBackup.forEach(dirty => { - if (this.fileService.canHandleResource(dirty)) { - const model = this.models.get(dirty); - if (model) { - filesToBackup.push(model); - } - } else if (dirty.scheme === Schemas.untitled) { - untitledToBackup.push(dirty); - } - }); - - return this.doBackupAll(filesToBackup, untitledToBackup); - } - - private async doBackupAll(dirtyFileModels: ITextFileEditorModel[], untitledResources: URI[]): Promise { - - // Handle file resources first - await Promise.all(dirtyFileModels.map(model => model.backup())); - - // Handle untitled resources - await Promise.all(untitledResources - .filter(untitled => this.untitledTextEditorService.exists(untitled)) - .map(async untitled => (await this.untitledTextEditorService.createOrGet({ resource: untitled }).resolve()).backup())); - } - - private async confirmBeforeShutdown(): Promise { - const confirm = await this.fileDialogService.showSaveConfirm(this.getDirty()); - - // Save - if (confirm === ConfirmResult.SAVE) { - const result = await this.saveAll(true /* includeUntitled */, { skipSaveParticipants: true }); - - if (result.results.some(r => r.error)) { - return true; // veto if some saves failed - } - - return this.noVeto({ cleanUpBackups: true }); - } - - // Don't Save - else if (confirm === ConfirmResult.DONT_SAVE) { - - // Make sure to revert untitled so that they do not restore - // see https://github.com/Microsoft/vscode/issues/29572 - this.untitledTextEditorService.revertAll(); - - return this.noVeto({ cleanUpBackups: true }); - } - - // Cancel - else if (confirm === ConfirmResult.CANCEL) { - return true; // veto - } - - return false; - } - - private noVeto(options: { cleanUpBackups: boolean }): boolean | Promise { - if (!options.cleanUpBackups) { - return false; - } - - if (this.lifecycleService.phase < LifecyclePhase.Restored) { - return false; // if editors have not restored, we are not up to speed with backups and thus should not clean them - } - - return this.cleanupBackupsBeforeShutdown().then(() => false, () => false); - } - - protected async cleanupBackupsBeforeShutdown(): Promise { - if (this.environmentService.isExtensionDevelopment) { - return; - } - - await this.backupFileService.discardAllWorkspaceBackups(); - } - - //#endregion - //#region text file IO primitives (read, create, move, delete, update) async read(resource: URI, options?: IReadTextFileOptions): Promise { @@ -903,6 +713,8 @@ export abstract class AbstractTextFileService extends Disposable implements ITex //#endregion + //#region dirty + getDirty(resources?: URI[]): URI[] { // Collect files @@ -924,4 +736,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Check for dirty untitled return this.untitledTextEditorService.getDirty().some(dirty => !resource || dirty.toString() === resource.toString()); } + + //#endregion } diff --git a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index 17eeb9d92286a..b9eca7404e9b0 100644 --- a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -27,15 +27,12 @@ import { Readable } from 'stream'; import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; import { ITextSnapshot } from 'vs/editor/common/model'; import { nodeReadableToString, streamToNodeReadable, nodeStreamToVSBufferReadable } from 'vs/base/node/stream'; -import { IElectronService } from 'vs/platform/electron/node/electron'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -46,7 +43,6 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; export class NativeTextFileService extends AbstractTextFileService { constructor( - @IWorkspaceContextService contextService: IWorkspaceContextService, @IFileService fileService: IFileService, @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @ILifecycleService lifecycleService: ILifecycleService, @@ -54,19 +50,16 @@ export class NativeTextFileService extends AbstractTextFileService { @IModeService modeService: IModeService, @IModelService modelService: IModelService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @INotificationService notificationService: INotificationService, - @IBackupFileService backupFileService: IBackupFileService, @IHistoryService historyService: IHistoryService, @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, @IEditorService editorService: IEditorService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, - @IElectronService private readonly electronService: IElectronService, @IProductService private readonly productService: IProductService, @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @ITextModelService textModelService: ITextModelService ) { - super(contextService, fileService, untitledTextEditorService, lifecycleService, instantiationService, modeService, modelService, environmentService, notificationService, backupFileService, historyService, dialogService, fileDialogService, editorService, textResourceConfigurationService, filesConfigurationService, textModelService); + super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modeService, modelService, environmentService, historyService, dialogService, fileDialogService, editorService, textResourceConfigurationService, filesConfigurationService, textModelService); } private _encoding: EncodingOracle | undefined; @@ -312,10 +305,6 @@ export class NativeTextFileService extends AbstractTextFileService { }); }); } - - protected getWindowCount(): Promise { - return this.electronService.getWindowCount(); - } } export interface IEncodingOverride { diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts index 4fac27a7c43c2..ccf2844c23823 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts @@ -47,7 +47,7 @@ suite('Files - TextFileEditorModel', () => { }); teardown(() => { - (accessor.textFileService.models).clear(); + (accessor.textFileService.models).dispose(); TextFileEditorModel.setSaveParticipant(null); // reset any set participant accessor.fileService.setContent(content); }); diff --git a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts index eae7fcd72179b..9b882d978e3e3 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts @@ -94,7 +94,6 @@ suite('Files - TextFileService i/o', () => { }); teardown(async () => { - (accessor.textFileService.models).clear(); (accessor.textFileService.models).dispose(); accessor.untitledTextEditorService.revertAll(); diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index 1c04252cdd71f..4358e4dbc3f59 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -4,24 +4,23 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import * as sinon from 'sinon'; -import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; -import { ILifecycleService, BeforeShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestContextService, TestFileService, TestElectronService, TestFilesConfigurationService, TestFileDialogService } from 'vs/workbench/test/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; -import { HotExitConfiguration, IFileService } from 'vs/platform/files/common/files'; +import { IFileService } from 'vs/platform/files/common/files'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { IWorkspaceContextService, Workspace } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { Schemas } from 'vs/base/common/network'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; -import { IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; +import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; class ServiceAccessor { constructor( @@ -38,16 +37,6 @@ class ServiceAccessor { } } -class BeforeShutdownEventImpl implements BeforeShutdownEvent { - - value: boolean | Promise | undefined; - reason = ShutdownReason.CLOSE; - - veto(value: boolean | Promise): void { - this.value = value; - } -} - suite('Files - TextFileService', () => { let instantiationService: IInstantiationService; @@ -63,87 +52,10 @@ suite('Files - TextFileService', () => { if (model) { model.dispose(); } - (accessor.textFileService.models).clear(); (accessor.textFileService.models).dispose(); accessor.untitledTextEditorService.revertAll(); }); - test('confirm onWillShutdown - no veto', async function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.resource, model); - - const event = new BeforeShutdownEventImpl(); - accessor.lifecycleService.fireWillShutdown(event); - - const veto = event.value; - if (typeof veto === 'boolean') { - assert.ok(!veto); - } else { - assert.ok(!(await veto)); - } - }); - - test('confirm onWillShutdown - veto if user cancels', async function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.resource, model); - - const service = accessor.textFileService; - accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL); - - await model.load(); - model.textEditorModel!.setValue('foo'); - assert.equal(service.getDirty().length, 1); - - const event = new BeforeShutdownEventImpl(); - accessor.lifecycleService.fireWillShutdown(event); - assert.ok(event.value); - }); - - test('confirm onWillShutdown - no veto and backups cleaned up if user does not want to save (hot.exit: off)', async function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.resource, model); - - const service = accessor.textFileService; - accessor.fileDialogService.setConfirmResult(ConfirmResult.DONT_SAVE); - accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } }); - - await model.load(); - model.textEditorModel!.setValue('foo'); - assert.equal(service.getDirty().length, 1); - const event = new BeforeShutdownEventImpl(); - accessor.lifecycleService.fireWillShutdown(event); - - let veto = event.value; - if (typeof veto === 'boolean') { - assert.ok(service.cleanupBackupsBeforeShutdownCalled); - assert.ok(!veto); - return; - } - - veto = await veto; - assert.ok(service.cleanupBackupsBeforeShutdownCalled); - assert.ok(!veto); - }); - - test('confirm onWillShutdown - save (hot.exit: off)', async function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.resource, model); - - const service = accessor.textFileService; - accessor.fileDialogService.setConfirmResult(ConfirmResult.SAVE); - accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } }); - - await model.load(); - model.textEditorModel!.setValue('foo'); - assert.equal(service.getDirty().length, 1); - const event = new BeforeShutdownEventImpl(); - accessor.lifecycleService.fireWillShutdown(event); - - const veto = await (>event.value); - assert.ok(!veto); - assert.ok(!model.isDirty()); - }); - test('isDirty/getDirty - files and untitled', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.models).add(model.resource, model); @@ -310,138 +222,4 @@ suite('Files - TextFileService', () => { sourceModel.dispose(); targetModel.dispose(); } - - suite('Hot Exit', () => { - suite('"onExit" setting', () => { - test('should hot exit on non-Mac (reason: CLOSE, windows: single, workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.CLOSE, false, true, !!platform.isMacintosh); - }); - test('should hot exit on non-Mac (reason: CLOSE, windows: single, empty workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.CLOSE, false, false, !!platform.isMacintosh); - }); - test('should NOT hot exit (reason: CLOSE, windows: multiple, workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.CLOSE, true, true, true); - }); - test('should NOT hot exit (reason: CLOSE, windows: multiple, empty workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.CLOSE, true, false, true); - }); - test('should hot exit (reason: QUIT, windows: single, workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.QUIT, false, true, false); - }); - test('should hot exit (reason: QUIT, windows: single, empty workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.QUIT, false, false, false); - }); - test('should hot exit (reason: QUIT, windows: multiple, workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.QUIT, true, true, false); - }); - test('should hot exit (reason: QUIT, windows: multiple, empty workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.QUIT, true, false, false); - }); - test('should hot exit (reason: RELOAD, windows: single, workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.RELOAD, false, true, false); - }); - test('should hot exit (reason: RELOAD, windows: single, empty workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.RELOAD, false, false, false); - }); - test('should hot exit (reason: RELOAD, windows: multiple, workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.RELOAD, true, true, false); - }); - test('should hot exit (reason: RELOAD, windows: multiple, empty workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.RELOAD, true, false, false); - }); - test('should NOT hot exit (reason: LOAD, windows: single, workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.LOAD, false, true, true); - }); - test('should NOT hot exit (reason: LOAD, windows: single, empty workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.LOAD, false, false, true); - }); - test('should NOT hot exit (reason: LOAD, windows: multiple, workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.LOAD, true, true, true); - }); - test('should NOT hot exit (reason: LOAD, windows: multiple, empty workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.LOAD, true, false, true); - }); - }); - - suite('"onExitAndWindowClose" setting', () => { - test('should hot exit (reason: CLOSE, windows: single, workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.CLOSE, false, true, false); - }); - test('should hot exit (reason: CLOSE, windows: single, empty workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.CLOSE, false, false, !!platform.isMacintosh); - }); - test('should hot exit (reason: CLOSE, windows: multiple, workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.CLOSE, true, true, false); - }); - test('should NOT hot exit (reason: CLOSE, windows: multiple, empty workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.CLOSE, true, false, true); - }); - test('should hot exit (reason: QUIT, windows: single, workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.QUIT, false, true, false); - }); - test('should hot exit (reason: QUIT, windows: single, empty workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.QUIT, false, false, false); - }); - test('should hot exit (reason: QUIT, windows: multiple, workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.QUIT, true, true, false); - }); - test('should hot exit (reason: QUIT, windows: multiple, empty workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.QUIT, true, false, false); - }); - test('should hot exit (reason: RELOAD, windows: single, workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.RELOAD, false, true, false); - }); - test('should hot exit (reason: RELOAD, windows: single, empty workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.RELOAD, false, false, false); - }); - test('should hot exit (reason: RELOAD, windows: multiple, workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.RELOAD, true, true, false); - }); - test('should hot exit (reason: RELOAD, windows: multiple, empty workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.RELOAD, true, false, false); - }); - test('should hot exit (reason: LOAD, windows: single, workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.LOAD, false, true, false); - }); - test('should NOT hot exit (reason: LOAD, windows: single, empty workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.LOAD, false, false, true); - }); - test('should hot exit (reason: LOAD, windows: multiple, workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.LOAD, true, true, false); - }); - test('should NOT hot exit (reason: LOAD, windows: multiple, empty workspace)', function () { - return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.LOAD, true, false, true); - }); - }); - - async function hotExitTest(this: any, setting: string, shutdownReason: ShutdownReason, multipleWindows: boolean, workspace: boolean, shouldVeto: boolean): Promise { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.models).add(model.resource, model); - - const service = accessor.textFileService; - // Set hot exit config - accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: setting } }); - // Set empty workspace if required - if (!workspace) { - accessor.contextService.setWorkspace(new Workspace('empty:1508317022751')); - } - // Set multiple windows if required - if (multipleWindows) { - accessor.electronService.windowCount = Promise.resolve(2); - } - // Set cancel to force a veto if hot exit does not trigger - accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL); - - await model.load(); - model.textEditorModel!.setValue('foo'); - assert.equal(service.getDirty().length, 1); - const event = new BeforeShutdownEventImpl(); - event.reason = shutdownReason; - accessor.lifecycleService.fireWillShutdown(event); - - const veto = await (>event.value); - assert.ok(!service.cleanupBackupsBeforeShutdownCalled); // When hot exit is set, backups should never be cleaned since the confirm result is cancel - assert.equal(veto, shouldVeto); - } - }); }); diff --git a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts index 3ffe921e4cf60..516c26c69cf3d 100644 --- a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts +++ b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts @@ -48,7 +48,6 @@ suite('Workbench - TextModelResolverService', () => { model.dispose(); model = (undefined)!; } - (accessor.textFileService.models).clear(); (accessor.textFileService.models).dispose(); accessor.untitledTextEditorService.revertAll(); }); diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts index 4c12fbb093fb0..c3543e4c82404 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts @@ -9,7 +9,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { Disposable, IDisposable, toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { TernarySearchTree, values } from 'vs/base/common/map'; -import { ISaveOptions } from 'vs/workbench/common/editor'; +import { ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; export const enum WorkingCopyCapabilities { @@ -48,6 +48,10 @@ export interface IWorkingCopy { save(options?: ISaveOptions): Promise; + revert(options?: IRevertOptions): Promise; + + hasBackup(): boolean; + backup(): Promise; //#endregion diff --git a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts index 908c24a2251bf..b94eb61e38406 100644 --- a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { TestWorkingCopyService } from 'vs/workbench/test/workbenchTestServices'; -import { ISaveOptions } from 'vs/workbench/common/editor'; +import { ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; suite('WorkingCopyService', () => { @@ -53,8 +53,16 @@ suite('WorkingCopyService', () => { return true; } + async revert(options?: IRevertOptions): Promise { + this.setDirty(false); + + return true; + } + async backup(): Promise { } + hasBackup(): boolean { return false; } + dispose(): void { this._onDispose.fire(); diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts index 40b3489c80f1e..62a915c253529 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts @@ -33,7 +33,7 @@ suite('MainThreadSaveParticipant', function () { }); teardown(() => { - (accessor.textFileService.models).clear(); + (accessor.textFileService.models).dispose(); TextFileEditorModel.setSaveParticipant(null); // reset any set participant }); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index c9b8e6f48d73d..c048560e2d525 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -190,13 +190,10 @@ export class TestContextService implements IWorkspaceContextService { } export class TestTextFileService extends NativeTextFileService { - cleanupBackupsBeforeShutdownCalled!: boolean; - private promptPath!: URI; private resolveTextContentError!: FileOperationError | null; constructor( - @IWorkspaceContextService contextService: IWorkspaceContextService, @IFileService protected fileService: IFileService, @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @ILifecycleService lifecycleService: ILifecycleService, @@ -204,20 +201,16 @@ export class TestTextFileService extends NativeTextFileService { @IModeService modeService: IModeService, @IModelService modelService: IModelService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @INotificationService notificationService: INotificationService, - @IBackupFileService backupFileService: IBackupFileService, @IHistoryService historyService: IHistoryService, @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, @IEditorService editorService: IEditorService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, - @IElectronService electronService: IElectronService, @IProductService productService: IProductService, @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @ITextModelService textModelService: ITextModelService ) { super( - contextService, fileService, untitledTextEditorService, lifecycleService, @@ -225,14 +218,11 @@ export class TestTextFileService extends NativeTextFileService { modeService, modelService, environmentService, - notificationService, - backupFileService, historyService, dialogService, fileDialogService, editorService, textResourceConfigurationService, - electronService, productService, filesConfigurationService, textModelService @@ -271,11 +261,6 @@ export class TestTextFileService extends NativeTextFileService { promptForPath(_resource: URI, _defaultPath: URI): Promise { return Promise.resolve(this.promptPath); } - - protected cleanupBackupsBeforeShutdown(): Promise { - this.cleanupBackupsBeforeShutdownCalled = true; - return Promise.resolve(); - } } export interface ITestInstantiationService extends IInstantiationService { @@ -1225,7 +1210,11 @@ export class TestBackupFileService implements IBackupFileService { return Promise.resolve(); } + didDiscardAllWorkspaceBackups = false; + discardAllWorkspaceBackups(): Promise { + this.didDiscardAllWorkspaceBackups = true; + return Promise.resolve(); } } diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index f303692242f4e..bd8b76dad8fcf 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -92,6 +92,9 @@ import 'vs/workbench/contrib/splash/electron-browser/partsSplash.contribution'; import 'vs/workbench/contrib/files/electron-browser/files.contribution'; import 'vs/workbench/contrib/files/electron-browser/fileActions.contribution'; +// Backup +import 'vs/workbench/contrib/backup/electron-browser/backup.contribution'; + // Debug import 'vs/workbench/contrib/debug/node/debugHelperService'; import 'vs/workbench/contrib/debug/electron-browser/extensionHostDebugService'; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index ecd9ff04e173e..e6c3807eefff4 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -91,6 +91,9 @@ registerSingleton(IUserDataSyncService, UserDataSyncService); // Explorer import 'vs/workbench/contrib/files/browser/files.web.contribution'; +// Backup +import 'vs/workbench/contrib/backup/browser/backup.web.contribution'; + // Preferences import 'vs/workbench/contrib/preferences/browser/keyboardLayoutPicker'; From 3df58b02801a92135f2f91649f765025912e6bbf Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 14 Jan 2020 16:06:00 +0100 Subject: [PATCH 275/315] debug: use new view pane in debug console --- .../debug/browser/debug.contribution.ts | 50 +++++++--------- .../contrib/debug/browser/debugCommands.ts | 10 ++-- .../debug/browser/debugEditorActions.ts | 8 +-- .../contrib/debug/browser/debugService.ts | 10 ++-- .../contrib/debug/browser/debugViewlet.ts | 22 +++---- .../workbench/contrib/debug/browser/repl.ts | 59 +++++++++---------- .../workbench/contrib/debug/common/debug.ts | 3 +- 7 files changed, 80 insertions(+), 82 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 3874102dd89b5..061cd5dcd61d7 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -14,16 +14,14 @@ import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry' import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionRegistryExtensions } from 'vs/workbench/common/actions'; import { ShowViewletAction } from 'vs/workbench/browser/viewlet'; -import { TogglePanelAction, Extensions as PanelExtensions, PanelRegistry, PanelDescriptor } from 'vs/workbench/browser/panel'; import { BreakpointsView } from 'vs/workbench/contrib/debug/browser/breakpointsView'; import { CallStackView } from 'vs/workbench/contrib/debug/browser/callStackView'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { - IDebugService, VIEWLET_ID, REPL_ID, CONTEXT_IN_DEBUG_MODE, INTERNAL_CONSOLE_OPTIONS_SCHEMA, - CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_DEBUG_UX, BREAKPOINT_EDITOR_CONTRIBUTION_ID, + IDebugService, VIEWLET_ID, DEBUG_PANEL_ID, CONTEXT_IN_DEBUG_MODE, INTERNAL_CONSOLE_OPTIONS_SCHEMA, + CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_DEBUG_UX, BREAKPOINT_EDITOR_CONTRIBUTION_ID, REPL_VIEW_ID, } from 'vs/workbench/contrib/debug/common/debug'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { StartAction, AddFunctionBreakpointAction, ConfigureAction, DisableAllBreakpointsAction, EnableAllBreakpointsAction, RemoveAllBreakpointsAction, RunAction, ReapplyBreakpointsAction, SelectAndStartAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { DebugToolBar } from 'vs/workbench/contrib/debug/browser/debugToolBar'; import * as service from 'vs/workbench/contrib/debug/browser/debugService'; @@ -31,7 +29,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { registerCommands, ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, REVERSE_CONTINUE_ID, STEP_BACK_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { StatusBarColorProvider } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider'; -import { IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views'; import { isMacintosh } from 'vs/base/common/platform'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { URI } from 'vs/base/common/uri'; @@ -48,11 +46,12 @@ import { ClearReplAction, Repl } from 'vs/workbench/contrib/debug/browser/repl'; import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider'; import { StartView } from 'vs/workbench/contrib/debug/browser/startView'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { DebugViewPaneContainer } from 'vs/workbench/contrib/debug/browser/debugViewlet'; +import { DebugViewPaneContainer, OpenDebugPanelAction } from 'vs/workbench/contrib/debug/browser/debugViewlet'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { CallStackEditorContribution } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution'; import { BreakpointEditorContribution } from 'vs/workbench/contrib/debug/browser/breakpointEditorContribution'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; class OpenDebugViewletAction extends ShowViewletAction { public static readonly ID = VIEWLET_ID; @@ -69,20 +68,6 @@ class OpenDebugViewletAction extends ShowViewletAction { } } -class OpenDebugPanelAction extends TogglePanelAction { - public static readonly ID = 'workbench.debug.action.toggleRepl'; - public static readonly LABEL = nls.localize('toggleDebugPanel', "Debug Console"); - - constructor( - id: string, - label: string, - @IPanelService panelService: IPanelService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService - ) { - super(id, label, REPL_ID, panelService, layoutService); - } -} - const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: nls.localize('runAndDebug', "Run and Debug"), @@ -99,14 +84,23 @@ const openPanelKb: IKeybindings = { }; // register repl panel -Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( - Repl, - REPL_ID, - nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, 'Debug Console'), - 'repl', - 30, - OpenDebugPanelAction.ID -)); + +const VIEW_CONTAINER: ViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ + id: DEBUG_PANEL_ID, + name: nls.localize('runAndDebug', "Run and Debug"), + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [DEBUG_PANEL_ID, DEBUG_PANEL_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + focusCommand: { + id: OpenDebugPanelAction.ID, + keybindings: openPanelKb + } +}, ViewContainerLocation.Panel); + +Registry.as(ViewExtensions.ViewsRegistry).registerViews([{ + id: REPL_VIEW_ID, + name: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, 'Debug Console'), + canToggleVisibility: false, + ctorDescriptor: new SyncDescriptor(Repl), +}], VIEW_CONTAINER); // Register default debug views const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 7ea1d5ed7cfc0..d65a6f30be202 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -9,7 +9,7 @@ import { List } from 'vs/base/browser/ui/list/listWidget'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IListService } from 'vs/platform/list/browser/listService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_EXPRESSION_SELECTED, CONTEXT_BREAKPOINT_SELECTED, IConfig, IStackFrame, IThread, IDebugSession, CONTEXT_DEBUG_STATE, REPL_ID, IDebugConfiguration, CONTEXT_JUMP_TO_CURSOR_SUPPORTED } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_EXPRESSION_SELECTED, CONTEXT_BREAKPOINT_SELECTED, IConfig, IStackFrame, IThread, IDebugSession, CONTEXT_DEBUG_STATE, IDebugConfiguration, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; import { Expression, Variable, Breakpoint, FunctionBreakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { IExtensionsViewPaneContainer, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -27,9 +27,9 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IViewsService } from 'vs/workbench/common/views'; export const ADD_CONFIGURATION_ID = 'debug.addConfiguration'; export const TOGGLE_INLINE_BREAKPOINT_ID = 'editor.debug.action.toggleInlineBreakpoint'; @@ -327,9 +327,9 @@ export function registerCommands(): void { CommandsRegistry.registerCommand({ id: FOCUS_REPL_ID, - handler: (accessor) => { - const panelService = accessor.get(IPanelService); - panelService.openPanel(REPL_ID, true); + handler: async (accessor) => { + const viewsService = accessor.get(IViewsService); + await viewsService.openView(REPL_VIEW_ID, true); } }); diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index 9650fba3db70f..2ca496d131c60 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -9,14 +9,14 @@ import { Range } from 'vs/editor/common/core/range'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ServicesAccessor, registerEditorAction, EditorAction, IActionOptions } from 'vs/editor/browser/editorExtensions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE, State, REPL_ID, VIEWLET_ID, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, IBreakpoint, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution } from 'vs/workbench/contrib/debug/common/debug'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE, State, VIEWLET_ID, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, IBreakpoint, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { openBreakpointSource } from 'vs/workbench/contrib/debug/browser/breakpointsView'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { PanelFocusContext } from 'vs/workbench/common/panel'; +import { IViewsService } from 'vs/workbench/common/views'; export const TOGGLE_BREAKPOINT_ID = 'editor.debug.action.toggleBreakpoint'; class ToggleBreakpointAction extends EditorAction { @@ -169,7 +169,7 @@ class SelectionToReplAction extends EditorAction { async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { const debugService = accessor.get(IDebugService); - const panelService = accessor.get(IPanelService); + const viewsService = accessor.get(IViewsService); const viewModel = debugService.getViewModel(); const session = viewModel.focusedSession; if (!editor.hasModel() || !session) { @@ -178,7 +178,7 @@ class SelectionToReplAction extends EditorAction { const text = editor.getModel().getValueInRange(editor.getSelection()); await session.addReplExpression(viewModel.focusedStackFrame!, text); - await panelService.openPanel(REPL_ID, true); + await viewsService.openView(REPL_VIEW_ID, true); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 5f805dd778bd9..77fd7a3ab3b71 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -36,7 +36,7 @@ import { IAction } from 'vs/base/common/actions'; import { deepClone, equals } from 'vs/base/common/objects'; import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, REPL_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IGlobalConfig, IStackFrame, AdapterEndEvent, getStateLabel, IDebugSessionOptions, CONTEXT_DEBUG_UX } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, DEBUG_PANEL_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IGlobalConfig, IStackFrame, AdapterEndEvent, getStateLabel, IDebugSessionOptions, CONTEXT_DEBUG_UX, REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; import { getExtensionHostDebugSession } from 'vs/workbench/contrib/debug/common/debugUtils'; import { isErrorWithActions } from 'vs/base/common/errorsWithActions'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -45,6 +45,7 @@ import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; 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'; const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint'; const DEBUG_FUNCTION_BREAKPOINTS_KEY = 'debug.functionbreakpoint'; @@ -80,6 +81,7 @@ export class DebugService implements IDebugService { @ITextFileService private readonly textFileService: ITextFileService, @IViewletService private readonly viewletService: IViewletService, @IPanelService private readonly panelService: IPanelService, + @IViewsService private readonly viewsService: IViewsService, @INotificationService private readonly notificationService: INotificationService, @IDialogService private readonly dialogService: IDialogService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @@ -466,7 +468,7 @@ export class DebugService implements IDebugService { const internalConsoleOptions = session.configuration.internalConsoleOptions || this.configurationService.getValue('debug').internalConsoleOptions; if (internalConsoleOptions === 'openOnSessionStart' || (this.viewModel.firstSessionStart && internalConsoleOptions === 'openOnFirstSessionStart')) { - this.panelService.openPanel(REPL_ID, false); + this.viewsService.openView(REPL_VIEW_ID, false); } this.viewModel.firstSessionStart = false; @@ -492,7 +494,7 @@ export class DebugService implements IDebugService { // Show the repl if some error got logged there #5870 if (session && session.getReplElements().length > 0) { - this.panelService.openPanel(REPL_ID, false); + this.viewsService.openView(REPL_VIEW_ID, false); } if (session.configuration && session.configuration.request === 'attach' && session.configuration.__autoAttach) { @@ -577,7 +579,7 @@ export class DebugService implements IDebugService { const dataBreakpoints = this.model.getDataBreakpoints().filter(dbp => !dbp.canPersist); dataBreakpoints.forEach(dbp => this.model.removeDataBreakpoints(dbp.getId())); - if (this.panelService.getLastActivePanelId() === REPL_ID && this.configurationService.getValue('debug').console.closeOnEnd) { + if (this.panelService.getLastActivePanelId() === DEBUG_PANEL_ID && this.configurationService.getValue('debug').console.closeOnEnd) { this.panelService.hideActivePanel(); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index 5afeb4290cb9d..df274ebb967fb 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -8,7 +8,7 @@ import * as nls from 'vs/nls'; import { IAction } from 'vs/base/common/actions'; import * as DOM from 'vs/base/browser/dom'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IDebugService, VIEWLET_ID, State, BREAKPOINTS_VIEW_ID, IDebugConfiguration, REPL_ID, CONTEXT_DEBUG_UX, CONTEXT_DEBUG_UX_KEY } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, VIEWLET_ID, State, BREAKPOINTS_VIEW_ID, IDebugConfiguration, DEBUG_PANEL_ID, CONTEXT_DEBUG_UX, CONTEXT_DEBUG_UX_KEY } from 'vs/workbench/contrib/debug/common/debug'; import { StartAction, ConfigureAction, SelectAndStartAction, FocusSessionAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { StartDebugActionViewItem, FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -105,8 +105,8 @@ export class DebugViewPaneContainer extends ViewPaneContainer { } @memoize - private get toggleReplAction(): ToggleReplAction { - return this._register(this.instantiationService.createInstance(ToggleReplAction, ToggleReplAction.ID, ToggleReplAction.LABEL)); + private get toggleReplAction(): OpenDebugPanelAction { + return this._register(this.instantiationService.createInstance(OpenDebugPanelAction, OpenDebugPanelAction.ID, OpenDebugPanelAction.LABEL)); } @memoize @@ -228,14 +228,16 @@ export class DebugViewPaneContainer extends ViewPaneContainer { } } -class ToggleReplAction extends TogglePanelAction { - static readonly ID = 'debug.toggleRepl'; - static readonly LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugConsoleAction' }, 'Debug Console'); +export class OpenDebugPanelAction extends TogglePanelAction { + public static readonly ID = 'workbench.debug.action.toggleRepl'; + public static readonly LABEL = nls.localize('toggleDebugPanel', "Debug Console"); - constructor(id: string, label: string, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @IPanelService panelService: IPanelService + constructor( + id: string, + label: string, + @IPanelService panelService: IPanelService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { - super(id, label, REPL_ID, panelService, layoutService, 'debug-action codicon-terminal'); + super(id, label, DEBUG_PANEL_ID, panelService, layoutService); } } diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 49af349bca63c..1503ed1a983e7 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -18,17 +18,15 @@ import { registerEditorAction, ServicesAccessor, EditorAction } from 'vs/editor/ import { IModelService } from 'vs/editor/common/services/modelService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { Panel } from 'vs/workbench/browser/panel'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { memoize } from 'vs/base/common/decorators'; import { dispose, IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { IDebugService, REPL_ID, DEBUG_SCHEME, CONTEXT_IN_DEBUG_REPL, IDebugSession, State, IReplElement, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, DEBUG_SCHEME, CONTEXT_IN_DEBUG_REPL, IDebugSession, State, IReplElement, IDebugConfiguration, REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; import { HistoryNavigator } from 'vs/base/common/history'; import { IHistoryNavigationWidget } from 'vs/base/browser/history'; import { createAndBindHistoryNavigationWidgetScopedContextKeyService } from 'vs/platform/browser/contextScopedHistoryWidget'; @@ -40,7 +38,6 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems'; import { CompletionContext, CompletionList, CompletionProviderRegistry, CompletionItem, completionKindFromString, CompletionItemKind } from 'vs/editor/common/modes'; import { first } from 'vs/base/common/arrays'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; @@ -56,6 +53,9 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; import { ReplDelegate, ReplVariablesRenderer, ReplSimpleElementsRenderer, ReplEvaluationInputsRenderer, ReplEvaluationResultsRenderer, ReplRawObjectsRenderer, ReplDataSource, ReplAccessibilityProvider } from 'vs/workbench/contrib/debug/browser/replViewer'; import { localize } from 'vs/nls'; +import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IViewsService } from 'vs/workbench/common/views'; const $ = dom.$; @@ -77,7 +77,8 @@ function revealLastElement(tree: WorkbenchAsyncDataTree) { } const sessionsToIgnore = new Set(); -export class Repl extends Panel implements IPrivateReplService, IHistoryNavigationWidget { + +export class Repl extends ViewPane implements IPrivateReplService, IHistoryNavigationWidget { _serviceBrand: undefined; private static readonly REFRESH_DELAY = 100; // delay in ms to refresh the repl for new elements to show @@ -100,21 +101,22 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati private modelChangeListener: IDisposable = Disposable.None; constructor( + options: IViewPaneOptions, @IDebugService private readonly debugService: IDebugService, - @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IStorageService private readonly storageService: IStorageService, @IThemeService protected themeService: IThemeService, @IModelService private readonly modelService: IModelService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @ICodeEditorService codeEditorService: ICodeEditorService, - @IContextMenuService private readonly contextMenuService: IContextMenuService, - @IConfigurationService private readonly configurationService: IConfigurationService, + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService configurationService: IConfigurationService, @ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService, @IClipboardService private readonly clipboardService: IClipboardService, - @IEditorService private readonly editorService: IEditorService + @IEditorService private readonly editorService: IEditorService, + @IKeybindingService keybindingService: IKeybindingService ) { - super(REPL_ID, telemetryService, themeService, storageService); + super({ ...(options as IViewPaneOptions), id: REPL_VIEW_ID, ariaHeaderLabel: localize('debugConsole', "Debug Console") }, keybindingService, contextMenuService, configurationService, contextKeyService); this.history = new HistoryNavigator(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')), 50); codeEditorService.registerDecorationType(DECORATION_KEY, {}); @@ -187,7 +189,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati if (!input || input.state === State.Inactive) { await this.selectSession(newSession); } - this.updateTitleArea(); + this.updateActions(); })); this._register(this.themeService.onThemeChange(() => { this.refreshReplElements(false); @@ -195,7 +197,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.updateInputDecoration(); } })); - this._register(this.onDidChangeVisibility(visible => { + this._register(this.onDidChangeBodyVisibility(visible => { if (!visible) { dispose(this.model); } else { @@ -328,7 +330,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati // Ignore inactive sessions which got cleared - so they are not shown any more sessionsToIgnore.add(session); await this.selectSession(); - this.updateTitleArea(); + this.updateActions(); } } this.replInput.focus(); @@ -345,7 +347,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.replInputLineCount = 1; if (shouldRelayout) { // Trigger a layout to shrink a potential multi line input - this.layout(this.dimension); + this.layoutBody(this.dimension.height, this.dimension.width); } } } @@ -366,21 +368,21 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati return removeAnsiEscapeCodes(text); } - layout(dimension: dom.Dimension): void { - this.dimension = dimension; + protected layoutBody(height: number, width: number): void { + this.dimension = new dom.Dimension(width, height); const replInputHeight = Repl.REPL_INPUT_LINE_HEIGHT * this.replInputLineCount; if (this.tree) { const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight; - const treeHeight = dimension.height - replInputHeight; + const treeHeight = height - replInputHeight; this.tree.getHTMLElement().style.height = `${treeHeight}px`; - this.tree.layout(treeHeight, dimension.width); + this.tree.layout(treeHeight, width); if (lastElementVisible) { revealLastElement(this.tree); } } this.replInputContainer.style.height = `${replInputHeight}px`; - this.replInput.layout({ width: dimension.width - 20, height: replInputHeight }); + this.replInput.layout({ width: width - 20, height: replInputHeight }); } focus(): void { @@ -410,12 +412,12 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati // --- Cached locals @memoize private get selectReplAction(): SelectReplAction { - return this.scopedInstantiationService.createInstance(SelectReplAction, SelectReplAction.ID, SelectReplAction.LABEL); + return this.instantiationService.createInstance(SelectReplAction, SelectReplAction.ID, SelectReplAction.LABEL); } @memoize private get clearReplAction(): ClearReplAction { - return this.scopedInstantiationService.createInstance(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL); + return this.instantiationService.createInstance(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL); } @memoize @@ -436,8 +438,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati // --- Creation - create(parent: HTMLElement): void { - super.create(parent); + protected renderBody(parent: HTMLElement): void { this.container = dom.append(parent, $('.repl')); const treeContainer = dom.append(this.container, $('.repl-tree')); this.createReplInput(this.container); @@ -511,7 +512,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati const lineCount = model ? Math.min(10, model.getLineCount()) : 1; if (lineCount !== this.replInputLineCount) { this.replInputLineCount = lineCount; - this.layout(this.dimension); + this.layoutBody(this.dimension.height, this.dimension.width); } })); // We add the input decoration only when the focus is in the input #61126 @@ -590,7 +591,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.replInput.setDecorations(DECORATION_KEY, decorations); } - protected saveState(): void { + saveState(): void { const replHistory = this.history.getHistory(); if (replHistory.length) { this.storageService.store(HISTORY_STORAGE_KEY, JSON.stringify(replHistory), StorageScope.WORKSPACE); @@ -712,8 +713,6 @@ class SelectReplAction extends Action { } else { await this.replService.selectSession(session); } - - return Promise.resolve(undefined); } } @@ -722,14 +721,14 @@ export class ClearReplAction extends Action { static readonly LABEL = localize('clearRepl', "Clear Console"); constructor(id: string, label: string, - @IPanelService private readonly panelService: IPanelService + @IViewsService private readonly viewsService: IViewsService ) { super(id, label, 'debug-action codicon-clear-all'); } async run(): Promise { - const repl = this.panelService.openPanel(REPL_ID); - await repl.clearRepl(); + const view = await this.viewsService.openView(REPL_VIEW_ID) as Repl; + await view.clearRepl(); aria.status(localize('debugConsoleCleared', "Debug console was cleared")); } } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 55b78e3a7d16e..380be6fa5305b 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -31,7 +31,8 @@ export const WATCH_VIEW_ID = 'workbench.debug.watchExpressionsView'; export const CALLSTACK_VIEW_ID = 'workbench.debug.callStackView'; export const LOADED_SCRIPTS_VIEW_ID = 'workbench.debug.loadedScriptsView'; export const BREAKPOINTS_VIEW_ID = 'workbench.debug.breakPointsView'; -export const REPL_ID = 'workbench.panel.repl'; +export const DEBUG_PANEL_ID = 'workbench.panel.repl'; +export const REPL_VIEW_ID = 'workbench.panel.repl.view'; export const DEBUG_SERVICE_ID = 'debugService'; export const CONTEXT_DEBUG_TYPE = new RawContextKey('debugType', undefined); export const CONTEXT_DEBUG_CONFIGURATION_TYPE = new RawContextKey('debugConfigurationType', undefined); From e7de4f9f8a288406bfba7a97de4636cad3d821c6 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 14 Jan 2020 16:18:11 +0100 Subject: [PATCH 276/315] remote - ensure errors go into file streams and not unknown serialized data --- .../common/remoteAgentFileSystemChannel.ts | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts b/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts index 6abb9086c8d5f..77e8b56d79cf2 100644 --- a/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts +++ b/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts @@ -15,6 +15,7 @@ import { OperatingSystem } from 'vs/base/common/platform'; import { newWriteableStream, ReadableStreamEvents, ReadableStreamEventPayload } from 'vs/base/common/stream'; import { CancellationToken } from 'vs/base/common/cancellation'; import { canceled } from 'vs/base/common/errors'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; export const REMOTE_FILE_SYSTEM_CHANNEL_NAME = 'remotefilesystem'; @@ -32,13 +33,13 @@ export class RemoteFileSystemProvider extends Disposable implements private readonly session: string = generateUuid(); private readonly _onDidChange = this._register(new Emitter()); - readonly onDidChangeFile: Event = this._onDidChange.event; + readonly onDidChangeFile = this._onDidChange.event; - private _onDidWatchErrorOccur: Emitter = this._register(new Emitter()); - readonly onDidErrorOccur: Event = this._onDidWatchErrorOccur.event; + private _onDidWatchErrorOccur = this._register(new Emitter()); + readonly onDidErrorOccur = this._onDidWatchErrorOccur.event; private readonly _onDidChangeCapabilities = this._register(new Emitter()); - readonly onDidChangeCapabilities: Event = this._onDidChangeCapabilities.event; + readonly onDidChangeCapabilities = this._onDidChangeCapabilities.event; private _capabilities!: FileSystemProviderCapabilities; get capabilities(): FileSystemProviderCapabilities { return this._capabilities; } @@ -117,14 +118,29 @@ export class RemoteFileSystemProvider extends Disposable implements // Reading as file stream goes through an event to the remote side const listener = this.channel.listen>('readFileStream', [resource, opts])(dataOrErrorOrEnd => { - if (dataOrErrorOrEnd instanceof VSBuffer) { - // data: forward into the stream + // data + if (dataOrErrorOrEnd instanceof VSBuffer) { stream.write(dataOrErrorOrEnd.buffer); - } else { + } - // error / end: always end the stream on errors too - stream.end(dataOrErrorOrEnd === 'end' ? undefined : dataOrErrorOrEnd); + // end or error + else { + if (dataOrErrorOrEnd === 'end') { + stream.end(); + } else { + + // Since we receive data through a IPC channel, it is likely + // that the error was not serialized, or only partially. To + // ensure our API use is correct, we convert the data to an + // error here to forward it properly. + let error = dataOrErrorOrEnd; + if (!(error instanceof Error)) { + error = new Error(toErrorMessage(error)); + } + + stream.end(error); + } // Signal to the remote side that we no longer listen listener.dispose(); From 1665beab9e5255e93d0de21e31f0a05d2841a671 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 14 Jan 2020 16:28:28 +0100 Subject: [PATCH 277/315] move TitleMenus from customView to ViewPane, also fixes #88557 --- .../browser/parts/views/customView.ts | 66 +++---------------- .../browser/parts/views/viewPaneContainer.ts | 63 ++++++++++++++++-- .../bulkEdit/browser/bulkEdit.contribution.ts | 47 +++++++++++-- .../contrib/bulkEdit/browser/bulkEditPane.ts | 15 +---- .../contrib/debug/browser/breakpointsView.ts | 4 +- .../contrib/debug/browser/callStackView.ts | 4 +- .../debug/browser/loadedScriptsView.ts | 4 +- .../contrib/debug/browser/startView.ts | 6 +- .../contrib/debug/browser/variablesView.ts | 4 +- .../debug/browser/watchExpressionsView.ts | 4 +- .../extensions/browser/extensionsViews.ts | 2 +- .../electron-browser/extensionsViews.test.ts | 6 +- .../contrib/files/browser/views/emptyView.ts | 4 +- .../files/browser/views/explorerView.ts | 4 +- .../files/browser/views/openEditorsView.ts | 4 +- .../contrib/markers/browser/markersView.ts | 4 +- .../contrib/outline/browser/outlinePane.ts | 2 +- .../contrib/remote/browser/remote.ts | 2 +- .../contrib/remote/browser/tunnelView.ts | 2 +- .../workbench/contrib/scm/browser/mainPane.ts | 4 +- .../contrib/scm/browser/repositoryPane.ts | 2 +- .../contrib/search/browser/searchView.ts | 4 +- 22 files changed, 144 insertions(+), 113 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 6d582d72c39ec..ceefbf34af694 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -5,13 +5,13 @@ import 'vs/css!./media/views'; import { Event, Emitter } from 'vs/base/common/event'; -import { IDisposable, Disposable, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IAction, IActionViewItem, ActionRunner, Action } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; -import { ContextAwareMenuEntryActionViewItem, createAndFillInActionBarActions, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeViewDescriptor, IViewsRegistry, ViewContainer, ITreeItemLabel, Extensions } from 'vs/workbench/common/views'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; @@ -54,8 +54,9 @@ export class CustomTreeViewPane extends ViewPane { @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, + @IInstantiationService instantiationService: IInstantiationService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); const { treeView } = (Registry.as(Extensions.ViewsRegistry).getView(options.id)); this.treeView = treeView; this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this)); @@ -81,11 +82,11 @@ export class CustomTreeViewPane extends ViewPane { } getActions(): IAction[] { - return [...this.treeView.getPrimaryActions()]; + return [...super.getActions(), ...this.treeView.getPrimaryActions()]; } getSecondaryActions(): IAction[] { - return [...this.treeView.getSecondaryActions()]; + return [...super.getSecondaryActions(), ...this.treeView.getSecondaryActions()]; } getActionViewItem(action: IAction): IActionViewItem | undefined { @@ -101,51 +102,6 @@ export class CustomTreeViewPane extends ViewPane { } } -class TitleMenus extends Disposable { - - private titleActions: IAction[] = []; - private readonly titleActionsDisposable = this._register(new MutableDisposable()); - private titleSecondaryActions: IAction[] = []; - - private _onDidChangeTitle = this._register(new Emitter()); - readonly onDidChangeTitle: Event = this._onDidChangeTitle.event; - - constructor( - id: string, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IMenuService private readonly menuService: IMenuService, - ) { - super(); - - const scopedContextKeyService = this._register(this.contextKeyService.createScoped()); - scopedContextKeyService.createKey('view', id); - - const titleMenu = this._register(this.menuService.createMenu(MenuId.ViewTitle, scopedContextKeyService)); - const updateActions = () => { - this.titleActions = []; - this.titleSecondaryActions = []; - this.titleActionsDisposable.value = createAndFillInActionBarActions(titleMenu, undefined, { primary: this.titleActions, secondary: this.titleSecondaryActions }); - this._onDidChangeTitle.fire(); - }; - - this._register(titleMenu.onDidChange(updateActions)); - updateActions(); - - this._register(toDisposable(() => { - this.titleActions = []; - this.titleSecondaryActions = []; - })); - } - - getTitleActions(): IAction[] { - return this.titleActions; - } - - getTitleSecondaryActions(): IAction[] { - return this.titleSecondaryActions; - } -} - class Root implements ITreeItem { label = { label: 'root' }; handle = '0'; @@ -175,7 +131,6 @@ export class CustomTreeView extends Disposable implements ITreeView { private root: ITreeItem; private elementsToRefresh: ITreeItem[] = []; - private menus: TitleMenus; private readonly _onDidExpandItem: Emitter = this._register(new Emitter()); readonly onDidExpandItem: Event = this._onDidExpandItem.event; @@ -211,8 +166,6 @@ export class CustomTreeView extends Disposable implements ITreeView { ) { super(); this.root = new Root(); - this.menus = this._register(instantiationService.createInstance(TitleMenus, this.id)); - this._register(this.menus.onDidChangeTitle(() => this._onDidChangeActions.fire())); this._register(this.themeService.onDidFileIconThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); this._register(this.themeService.onThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); this._register(this.configurationService.onDidChangeConfiguration(e => { @@ -309,15 +262,14 @@ export class CustomTreeView extends Disposable implements ITreeView { getPrimaryActions(): IAction[] { if (this.showCollapseAllAction) { - const collapseAllAction = new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action codicon-collapse-all', true, () => this.tree ? new CollapseAllAction(this.tree, true).run() : Promise.resolve()); - return [...this.menus.getTitleActions(), collapseAllAction]; + return [new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action codicon-collapse-all', true, () => this.tree ? new CollapseAllAction(this.tree, true).run() : Promise.resolve())]; } else { - return this.menus.getTitleActions(); + return []; } } getSecondaryActions(): IAction[] { - return this.menus.getTitleSecondaryActions(); + return []; } setVisibility(isVisible: boolean): void { diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 95095030c4848..822718b18f3b8 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -10,7 +10,7 @@ import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { attachStyler, IColorMapping } from 'vs/platform/theme/common/styler'; import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER } from 'vs/workbench/common/theme'; import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener } from 'vs/base/browser/dom'; -import { IDisposable, combinedDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { firstIndex } from 'vs/base/common/arrays'; import { IAction, IActionRunner, ActionRunner } from 'vs/base/common/actions'; import { IActionViewItem, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -36,6 +36,8 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer'; import { Component } from 'vs/workbench/common/component'; +import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; export interface IPaneColors extends IColorMapping { dropBackground?: ColorIdentifier; @@ -73,6 +75,8 @@ export abstract class ViewPane extends Pane implements IView { readonly id: string; title: string; + private readonly menuActions: CustomViewMenuActions; + protected actionRunner?: IActionRunner; protected toolbar?: ToolBar; private readonly showActionsAlways: boolean = false; @@ -85,7 +89,8 @@ export abstract class ViewPane extends Pane implements IView { @IKeybindingService protected keybindingService: IKeybindingService, @IContextMenuService protected contextMenuService: IContextMenuService, @IConfigurationService protected readonly configurationService: IConfigurationService, - @IContextKeyService contextKeyService: IContextKeyService + @IContextKeyService contextKeyService: IContextKeyService, + @IInstantiationService protected instantiationService: IInstantiationService, ) { super(options); @@ -94,6 +99,9 @@ export abstract class ViewPane extends Pane implements IView { this.actionRunner = options.actionRunner; this.showActionsAlways = !!options.showActionsAlways; this.focusedViewContextKey = FocusedViewContext.bindTo(contextKeyService); + + this.menuActions = this._register(instantiationService.createInstance(CustomViewMenuActions, this.id)); + this._register(this.menuActions.onDidChangeTitle(() => this.updateActions())); } setVisible(visible: boolean): void { @@ -207,14 +215,17 @@ export abstract class ViewPane extends Pane implements IView { } getActions(): IAction[] { - return []; + return this.menuActions.getPrimaryActions(); } getSecondaryActions(): IAction[] { - return []; + return this.menuActions.getSecondaryActions(); } getActionViewItem(action: IAction): IActionViewItem | undefined { + if (action instanceof MenuItemAction) { + return this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action); + } return undefined; } @@ -759,3 +770,47 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } +class CustomViewMenuActions extends Disposable { + + private primaryActions: IAction[] = []; + private readonly titleActionsDisposable = this._register(new MutableDisposable()); + private secondaryActions: IAction[] = []; + + private _onDidChangeTitle = this._register(new Emitter()); + readonly onDidChangeTitle: Event = this._onDidChangeTitle.event; + + constructor( + id: string, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IMenuService private readonly menuService: IMenuService, + ) { + super(); + + const scopedContextKeyService = this._register(this.contextKeyService.createScoped()); + scopedContextKeyService.createKey('view', id); + + const titleMenu = this._register(this.menuService.createMenu(MenuId.ViewTitle, scopedContextKeyService)); + const updateActions = () => { + this.primaryActions = []; + this.secondaryActions = []; + this.titleActionsDisposable.value = createAndFillInActionBarActions(titleMenu, undefined, { primary: this.primaryActions, secondary: this.secondaryActions }); + this._onDidChangeTitle.fire(); + }; + + this._register(titleMenu.onDidChange(updateActions)); + updateActions(); + + this._register(toDisposable(() => { + this.primaryActions = []; + this.secondaryActions = []; + })); + } + + getPrimaryActions(): IAction[] { + return this.primaryActions; + } + + getSecondaryActions(): IAction[] { + return this.secondaryActions; + } +} diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts index 104ed75e93777..ec51b044e9c6d 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts @@ -23,6 +23,8 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { URI } from 'vs/base/common/uri'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; function getBulkEditPane(panelService: IPanelService): BulkEditPane | undefined { let view: ViewPane | undefined; @@ -102,20 +104,51 @@ class BulkEditPreviewContribution { } } -KeybindingsRegistry.registerCommandAndKeybindingRule({ +// CMD: accept +CommandsRegistry.registerCommand('refactorPreview.apply', accessor => { + const panelService = accessor.get(IPanelService); + const view = getBulkEditPane(panelService); + if (view) { + view.accept(); + } +}); +KeybindingsRegistry.registerKeybindingRule({ id: 'refactorPreview.apply', weight: KeybindingWeight.WorkbenchContrib, when: BulkEditPreviewContribution.ctxEnabled, primary: KeyMod.Shift + KeyCode.Enter, - handler(accessor) { - const panelService = accessor.get(IPanelService); - const view = getBulkEditPane(panelService); - if (view) { - view.accept(); - } +}); +MenuRegistry.appendMenuItem(MenuId.ViewTitle, { + command: { + id: 'refactorPreview.apply', + title: { value: localize('apply', "Apply Changes"), original: 'Apply Changes' }, + icon: { id: 'codicon/check' }, + precondition: BulkEditPreviewContribution.ctxEnabled + }, + when: ContextKeyExpr.equals('view', BulkEditPane.ID), + group: 'navigation' +}); + +// CMD: discard +CommandsRegistry.registerCommand('refactorPreview.discard', accessor => { + const panelService = accessor.get(IPanelService); + const view = getBulkEditPane(panelService); + if (view) { + view.discard(); } }); +MenuRegistry.appendMenuItem(MenuId.ViewTitle, { + command: { + id: 'refactorPreview.discard', + title: { value: localize('Discard', "Discard Changes"), original: 'Discard Changes' }, + icon: { id: 'codicon/clear-all' }, + precondition: BulkEditPreviewContribution.ctxEnabled + }, + when: ContextKeyExpr.equals('view', BulkEditPane.ID), + group: 'navigation' +}); +// CMD: toggle KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'refactorPreview.toggleCheckedState', weight: KeybindingWeight.WorkbenchContrib, diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index 982e5704712bb..44f82cbc7c62c 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -10,7 +10,6 @@ import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElement import { FuzzyScore } from 'vs/base/common/filters'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; -import { Action } from 'vs/base/common/actions'; import { diffInserted, diffRemoved } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -42,9 +41,6 @@ export class BulkEditPane extends ViewPane { private _tree!: WorkbenchAsyncDataTree; private _message!: HTMLSpanElement; - private readonly _acceptAction = new Action('ok', localize('ok', "Apply Changes"), 'codicon-check', false, async () => this.accept()); - private readonly _discardAction = new Action('discard', localize('discard', "Discard Changes"), 'codicon-clear-all', false, async () => this.discard()); - private readonly _disposables = new DisposableStore(); private readonly _sessionDisposables = new DisposableStore(); @@ -65,7 +61,7 @@ export class BulkEditPane extends ViewPane { ) { super( options, - keybindingService, contextMenuService, configurationService, contextKeyService + keybindingService, contextMenuService, configurationService, contextKeyService, _instaService ); this.element.classList.add('bulk-edit-panel', 'show-file-icons'); @@ -124,10 +120,6 @@ export class BulkEditPane extends ViewPane { this._setState(State.Message); } - getActions() { - return [this._acceptAction, this._discardAction]; - } - protected layoutBody(height: number, width: number): void { this._tree.layout(height, width); } @@ -152,9 +144,6 @@ export class BulkEditPane extends ViewPane { this._currentInput = input; - this._acceptAction.enabled = true; - this._discardAction.enabled = true; - return new Promise(async resolve => { this._currentResolve = resolve; @@ -207,8 +196,6 @@ export class BulkEditPane extends ViewPane { private _done(accept: boolean): void { if (this._currentResolve) { this._currentResolve(accept ? this._currentInput?.asWorkspaceEdit() : undefined); - this._acceptAction.enabled = false; - this._discardAction.enabled = false; this._currentInput = undefined; } this._setState(State.Message); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 3e402cfb207a0..1c2711791deba 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -61,14 +61,14 @@ export class BreakpointsView extends ViewPane { @IContextMenuService contextMenuService: IContextMenuService, @IDebugService private readonly debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IThemeService private readonly themeService: IThemeService, @IEditorService private readonly editorService: IEditorService, @IContextViewService private readonly contextViewService: IContextViewService, @IConfigurationService configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); this.minimumBodySize = this.maximumBodySize = getExpandedBodySize(this.debugService.getModel()); this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange())); diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 7314cb261a9af..966695aaaf621 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -91,13 +91,13 @@ export class CallStackView extends ViewPane { @IContextMenuService contextMenuService: IContextMenuService, @IDebugService private readonly debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService configurationService: IConfigurationService, @IMenuService menuService: IMenuService, @IContextKeyService readonly contextKeyService: IContextKeyService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); this.callStackItemType = CONTEXT_CALLSTACK_ITEM_TYPE.bindTo(contextKeyService); this.contributedContextMenu = menuService.createMenu(MenuId.DebugCallStackContext, contextKeyService); diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index 0866f659ed8e9..1bdb1efa5bac7 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -402,7 +402,7 @@ export class LoadedScriptsView extends ViewPane { options: IViewletViewOptions, @IContextMenuService contextMenuService: IContextMenuService, @IKeybindingService keybindingService: IKeybindingService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IConfigurationService configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, @IContextKeyService readonly contextKeyService: IContextKeyService, @@ -411,7 +411,7 @@ export class LoadedScriptsView extends ViewPane { @IDebugService private readonly debugService: IDebugService, @ILabelService private readonly labelService: ILabelService ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('loadedScriptsSection', "Loaded Scripts Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('loadedScriptsSection', "Loaded Scripts Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); this.loadedScriptsItemType = CONTEXT_LOADED_SCRIPTS_ITEM_TYPE.bindTo(contextKeyService); } diff --git a/src/vs/workbench/contrib/debug/browser/startView.ts b/src/vs/workbench/contrib/debug/browser/startView.ts index 6e7aee1172dd3..d45f2e4700c69 100644 --- a/src/vs/workbench/contrib/debug/browser/startView.ts +++ b/src/vs/workbench/contrib/debug/browser/startView.ts @@ -23,6 +23,7 @@ import { equals } from 'vs/base/common/arrays'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; const $ = dom.$; export class StartView extends ViewPane { @@ -47,9 +48,10 @@ export class StartView extends ViewPane { @IDebugService private readonly debugService: IDebugService, @IEditorService private readonly editorService: IEditorService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IFileDialogService private readonly dialogService: IFileDialogService + @IFileDialogService private readonly dialogService: IFileDialogService, + @IInstantiationService instantiationService: IInstantiationService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: localize('debugStart', "Debug Start Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: localize('debugStart', "Debug Start Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); this._register(editorService.onDidActiveEditorChange(() => this.updateView())); this._register(this.debugService.getConfigurationManager().onDidRegisterDebugger(() => this.updateView())); } diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index d94bbb7b945a9..71b9e9f8d32cb 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -50,11 +50,11 @@ export class VariablesView extends ViewPane { @IDebugService private readonly debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService, @IConfigurationService configurationService: IConfigurationService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IClipboardService private readonly clipboardService: IClipboardService, @IContextKeyService contextKeyService: IContextKeyService ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); // Use scheduler to prevent unnecessary flashing this.onFocusStackFrameScheduler = new RunOnceScheduler(async () => { diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 7ee2d0fcaca3d..e21d0c884a759 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -47,11 +47,11 @@ export class WatchExpressionsView extends ViewPane { @IContextMenuService contextMenuService: IContextMenuService, @IDebugService private readonly debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IConfigurationService configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('watchExpressionsSection', "Watch Expressions Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('watchExpressionsSection', "Watch Expressions Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => { this.needsRefresh = false; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 6047594ffb5ce..477532889fd56 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -110,7 +110,7 @@ export class ExtensionsListView extends ViewPane { @IContextKeyService private readonly contextKeyService: IContextKeyService, @IMenuService private readonly menuService: IMenuService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title, showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title, showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); this.server = options.server; } diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index df34fc18de0a0..44ba84328b140 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -27,7 +27,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { TestContextService, TestSharedProcessService } from 'vs/workbench/test/workbenchTestServices'; +import { TestContextService, TestSharedProcessService, TestMenuService } from 'vs/workbench/test/workbenchTestServices'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { URLService } from 'vs/platform/url/node/urlService'; @@ -44,6 +44,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { ILabelService } from 'vs/platform/label/common/label'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; +import { IMenuService } from 'vs/platform/actions/common/actions'; suite('ExtensionsListView Tests', () => { @@ -92,7 +93,8 @@ suite('ExtensionsListView Tests', () => { instantiationService.stub(IExtensionManagementService, 'onUninstallExtension', uninstallEvent.event); instantiationService.stub(IExtensionManagementService, 'onDidUninstallExtension', didUninstallEvent.event); instantiationService.stub(IRemoteAgentService, RemoteAgentService); - instantiationService.stub(IContextKeyService, MockContextKeyService); + instantiationService.stub(IContextKeyService, new MockContextKeyService()); + instantiationService.stub(IMenuService, new TestMenuService()); instantiationService.stub(IExtensionManagementServerService, new class extends ExtensionManagementServerService { private _localExtensionManagementServer: IExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local', authority: 'vscode-local' }; diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts index 65959f41a1e4a..350b41d643cb4 100644 --- a/src/vs/workbench/contrib/files/browser/views/emptyView.ts +++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -37,7 +37,7 @@ export class EmptyView extends ViewPane { constructor( options: IViewletViewOptions, @IThemeService private readonly themeService: IThemeService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IKeybindingService keybindingService: IKeybindingService, @IContextMenuService contextMenuService: IContextMenuService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @@ -46,7 +46,7 @@ export class EmptyView extends ViewPane { @ILabelService private labelService: ILabelService, @IContextKeyService contextKeyService: IContextKeyService ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); this._register(this.contextService.onDidChangeWorkbenchState(() => this.setLabels())); this._register(this.labelService.onDidChangeFormatters(() => this.setLabels())); } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 08a6a6eba05c7..1639ca2e1138d 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -107,7 +107,7 @@ export class ExplorerView extends ViewPane { constructor( options: IViewPaneOptions, @IContextMenuService contextMenuService: IContextMenuService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IProgressService private readonly progressService: IProgressService, @IEditorService private readonly editorService: IEditorService, @@ -125,7 +125,7 @@ export class ExplorerView extends ViewPane { @IClipboardService private clipboardService: IClipboardService, @IFileService private readonly fileService: IFileService ) { - super({ ...(options as IViewPaneOptions), id: ExplorerView.ID, ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewPaneOptions), id: ExplorerView.ID, ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); this.resourceContext = instantiationService.createInstance(ResourceContextKey); this._register(this.resourceContext); diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index f5166ae8ee8d4..a5c809c541bbb 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -67,7 +67,7 @@ export class OpenEditorsView extends ViewPane { constructor( options: IViewletViewOptions, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IContextMenuService contextMenuService: IContextMenuService, @IEditorService private readonly editorService: IEditorService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @@ -83,7 +83,7 @@ export class OpenEditorsView extends ViewPane { super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize({ key: 'openEditosrSection', comment: ['Open is an adjective'] }, "Open Editors Section"), - }, keybindingService, contextMenuService, configurationService, contextKeyService); + }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); this.structuralRefreshDelay = 0; this.listRefreshScheduler = new RunOnceScheduler(() => { diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index 20c6c153a44ea..03106bdf8b9cc 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -101,7 +101,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { constructor( options: IViewPaneOptions, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @@ -113,7 +113,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { @IKeybindingService keybindingService: IKeybindingService, @IStorageService storageService: IStorageService, ) { - super({ ...(options as IViewPaneOptions), id: Constants.MARKERS_VIEW_ID, ariaHeaderLabel: Messages.MARKERS_PANEL_TITLE_PROBLEMS }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewPaneOptions), id: Constants.MARKERS_VIEW_ID, ariaHeaderLabel: Messages.MARKERS_PANEL_TITLE_PROBLEMS }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); this.panelFoucusContextKey = Constants.MarkerPanelFocusContextKey.bindTo(contextKeyService); this.panelState = new Memento(Constants.MARKERS_PANEL_STORAGE_ID, storageService).getMemento(StorageScope.WORKSPACE); this.markersViewModel = this._register(instantiationService.createInstance(MarkersViewModel, this.panelState['multiline'])); diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts index ed98d3814be44..598b4cab64415 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -266,7 +266,7 @@ export class OutlinePane extends ViewPane { @IContextKeyService contextKeyService: IContextKeyService, @IContextMenuService contextMenuService: IContextMenuService, ) { - super(options, keybindingService, contextMenuService, _configurationService, contextKeyService); + super(options, keybindingService, contextMenuService, _configurationService, contextKeyService, _instantiationService); this._outlineViewState.restore(this._storageService); this._contextKeyFocused = OutlineViewFocused.bindTo(contextKeyService); this._contextKeyFiltered = OutlineViewFiltered.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 27ce85e8cb3a1..af5e22dcfefbd 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -368,7 +368,7 @@ class HelpPanel extends ViewPane { @IRemoteExplorerService protected readonly remoteExplorerService: IRemoteExplorerService, @IWorkbenchEnvironmentService protected readonly workbenchEnvironmentService: IWorkbenchEnvironmentService ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); } protected renderBody(container: HTMLElement): void { diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index c88132b8b2c42..4a16572e0dd76 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -415,7 +415,7 @@ export class TunnelPanel extends ViewPane { @IThemeService private readonly themeService: IThemeService, @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); this.tunnelTypeContext = TunnelTypeContextKey.bindTo(contextKeyService); this.tunnelCloseableContext = TunnelCloseableContextKey.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/scm/browser/mainPane.ts b/src/vs/workbench/contrib/scm/browser/mainPane.ts index f44e13e5e9ca7..695e932501198 100644 --- a/src/vs/workbench/contrib/scm/browser/mainPane.ts +++ b/src/vs/workbench/contrib/scm/browser/mainPane.ts @@ -183,12 +183,12 @@ export class MainPane extends ViewPane { @IKeybindingService protected keybindingService: IKeybindingService, @IContextMenuService protected contextMenuService: IContextMenuService, @ISCMService protected scmService: ISCMService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IMenuService private readonly menuService: IMenuService, @IConfigurationService configurationService: IConfigurationService ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); } protected renderBody(container: HTMLElement): void { diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts index 95626a88b2ca8..012392258b69f 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -618,7 +618,7 @@ export class RepositoryPane extends ViewPane { @IMenuService protected menuService: IMenuService, @IStorageService private storageService: IStorageService ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); this.menus = instantiationService.createInstance(SCMMenus, this.repository.provider); this._register(this.menus); diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 1a7005618f5f9..cd0d8fe620b49 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -156,7 +156,7 @@ export class SearchView extends ViewPane { @INotificationService private readonly notificationService: INotificationService, @IDialogService private readonly dialogService: IDialogService, @IContextViewService private readonly contextViewService: IContextViewService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IConfigurationService configurationService: IConfigurationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @ISearchWorkbenchService private readonly searchWorkbenchService: ISearchWorkbenchService, @@ -174,7 +174,7 @@ export class SearchView extends ViewPane { @ILabelService private readonly labelService: ILabelService, @IOpenerService private readonly openerService: IOpenerService ) { - super({ ...options, id: VIEW_ID, ariaHeaderLabel: nls.localize('searchView', "Search") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...options, id: VIEW_ID, ariaHeaderLabel: nls.localize('searchView', "Search") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); this.viewletVisible = Constants.SearchViewVisibleKey.bindTo(contextKeyService); this.viewletFocused = Constants.SearchViewFocusedKey.bindTo(contextKeyService); From 49aa355e06db26a8899c7fa6f49bba89187e411d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 14 Jan 2020 16:30:21 +0100 Subject: [PATCH 278/315] fix #86843 --- src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index df3a76569fa4d..d556bdf660c48 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -1123,6 +1123,8 @@ export class DirtyDiffModel extends Disposable { this.originalModelDisposables.add(ref.object.textEditorModel.onDidChangeContent(() => this.triggerDiff())); return originalUri; + }).catch(error => { + return null; // possibly invalid reference }); }); From e7459ebe6eaeff26aeb7b2bb5d14a7a6494b24e3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 14 Jan 2020 16:30:47 +0100 Subject: [PATCH 279/315] config - no need to check for file exist on error --- .../services/configuration/browser/configuration.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 455ea3af6718f..ac42737797cb3 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -9,7 +9,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import * as errors from 'vs/base/common/errors'; import { Disposable, IDisposable, dispose, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; -import { FileChangeType, FileChangesEvent, IFileService, whenProviderRegistered } from 'vs/platform/files/common/files'; +import { FileChangeType, FileChangesEvent, IFileService, whenProviderRegistered, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { ConfigurationModel, ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels'; import { WorkspaceConfigurationModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels'; import { TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; @@ -127,8 +127,7 @@ class FileServiceBasedConfigurationWithNames extends Disposable { const content = await this.fileService.readFile(resource); return content.value.toString(); } catch (error) { - const exists = await this.fileService.exists(resource); - if (exists) { + if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { errors.onUnexpectedError(error); } } From b4a79d682a48fd9b5a1353ba08e36ad424c2b925 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 14 Jan 2020 15:09:39 +0100 Subject: [PATCH 280/315] Add getters for computed editor options (Fixes microsoft/monaco-editor#1734) --- build/gulpfile.editor.js | 7 + build/monaco/api.js | 53 +- build/monaco/api.ts | 63 +- build/monaco/monaco.d.ts.recipe | 3 +- src/vs/editor/browser/editorBrowser.ts | 4 +- src/vs/editor/common/config/editorOptions.ts | 73 +- .../common/standalone/standaloneEnums.ts | 722 ++++++++++++------ .../standalone/browser/standaloneEditor.ts | 24 +- src/vs/monaco.d.ts | 401 +++++++++- 9 files changed, 1006 insertions(+), 344 deletions(-) diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index bc11c8b503450..b686d280189d4 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -353,6 +353,13 @@ gulp.task('editor-distro', ) ); +gulp.task('monacodts', task.define('monacodts', () => { + const result = monacoapi.execute(); + fs.writeFileSync(result.filePath, result.content); + fs.writeFileSync(path.join(root, 'src/vs/editor/common/standalone/standaloneEnums.ts'), result.enums); + return Promise.resolve(true); +})); + //#region monaco type checking function createTscCompileTask(watch) { diff --git a/build/monaco/api.js b/build/monaco/api.js index a429cd66cde20..ee9505c059568 100644 --- a/build/monaco/api.js +++ b/build/monaco/api.js @@ -9,7 +9,7 @@ const ts = require("typescript"); const path = require("path"); const fancyLog = require("fancy-log"); const ansiColors = require("ansi-colors"); -const dtsv = '2'; +const dtsv = '3'; const tsfmt = require('../../tsfmt.json'); const SRC = path.join(__dirname, '../../src'); exports.RECIPE_PATH = path.join(__dirname, './monaco.d.ts.recipe'); @@ -148,12 +148,35 @@ function getMassagedTopLevelDeclarationText(sourceFile, declaration, importName, } }); } + else if (declaration.kind === ts.SyntaxKind.VariableStatement) { + const jsDoc = result.substr(0, declaration.getLeadingTriviaWidth(sourceFile)); + if (jsDoc.indexOf('@monacodtsreplace') >= 0) { + const jsDocLines = jsDoc.split(/\r\n|\r|\n/); + let directives = []; + for (const jsDocLine of jsDocLines) { + const m = jsDocLine.match(/^\s*\* \/([^/]+)\/([^/]+)\/$/); + if (m) { + directives.push([new RegExp(m[1], 'g'), m[2]]); + } + } + // remove the jsdoc + result = result.substr(jsDoc.length); + if (directives.length > 0) { + // apply replace directives + const replacer = createReplacerFromDirectives(directives); + result = replacer(result); + } + } + } result = result.replace(/export default /g, 'export '); result = result.replace(/export declare /g, 'export '); result = result.replace(/declare /g, ''); if (declaration.kind === ts.SyntaxKind.EnumDeclaration) { result = result.replace(/const enum/, 'enum'); - enums.push(result); + enums.push({ + enumName: declaration.name.getText(sourceFile), + text: result + }); } return result; } @@ -277,6 +300,14 @@ function format(text, endl) { return result; } } +function createReplacerFromDirectives(directives) { + return (str) => { + for (let i = 0; i < directives.length; i++) { + str = str.replace(directives[i][0], directives[i][1]); + } + return str; + }; +} function createReplacer(data) { data = data || ''; let rawDirectives = data.split(';'); @@ -292,12 +323,7 @@ function createReplacer(data) { findStr = '\\b' + findStr + '\\b'; directives.push([new RegExp(findStr, 'g'), replaceStr]); }); - return (str) => { - for (let i = 0; i < directives.length; i++) { - str = str.replace(directives[i][0], directives[i][1]); - } - return str; - }; + return createReplacerFromDirectives(directives); } function generateDeclarationFile(recipe, sourceFileGetter) { const endl = /\r\n/.test(recipe) ? '\r\n' : '\n'; @@ -415,6 +441,15 @@ function generateDeclarationFile(recipe, sourceFileGetter) { resultTxt = resultTxt.split(/\r\n|\n|\r/).join(endl); resultTxt = format(resultTxt, endl); resultTxt = resultTxt.split(/\r\n|\n|\r/).join(endl); + enums.sort((e1, e2) => { + if (e1.enumName < e2.enumName) { + return -1; + } + if (e1.enumName > e2.enumName) { + return 1; + } + return 0; + }); let resultEnums = [ '/*---------------------------------------------------------------------------------------------', ' * Copyright (c) Microsoft Corporation. All rights reserved.', @@ -423,7 +458,7 @@ function generateDeclarationFile(recipe, sourceFileGetter) { '', '// THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY.', '' - ].concat(enums).join(endl); + ].concat(enums.map(e => e.text)).join(endl); resultEnums = resultEnums.split(/\r\n|\n|\r/).join(endl); resultEnums = format(resultEnums, endl); resultEnums = resultEnums.split(/\r\n|\n|\r/).join(endl); diff --git a/build/monaco/api.ts b/build/monaco/api.ts index ae058292344d7..b52b0b63431fa 100644 --- a/build/monaco/api.ts +++ b/build/monaco/api.ts @@ -9,7 +9,7 @@ import * as path from 'path'; import * as fancyLog from 'fancy-log'; import * as ansiColors from 'ansi-colors'; -const dtsv = '2'; +const dtsv = '3'; const tsfmt = require('../../tsfmt.json'); @@ -138,7 +138,7 @@ function isDefaultExport(declaration: ts.InterfaceDeclaration | ts.ClassDeclarat ); } -function getMassagedTopLevelDeclarationText(sourceFile: ts.SourceFile, declaration: TSTopLevelDeclare, importName: string, usage: string[], enums: string[]): string { +function getMassagedTopLevelDeclarationText(sourceFile: ts.SourceFile, declaration: TSTopLevelDeclare, importName: string, usage: string[], enums: IEnumEntry[]): string { let result = getNodeText(sourceFile, declaration); if (declaration.kind === ts.SyntaxKind.InterfaceDeclaration || declaration.kind === ts.SyntaxKind.ClassDeclaration) { let interfaceDeclaration = declaration; @@ -177,6 +177,25 @@ function getMassagedTopLevelDeclarationText(sourceFile: ts.SourceFile, declarati // life.. } }); + } else if (declaration.kind === ts.SyntaxKind.VariableStatement) { + const jsDoc = result.substr(0, declaration.getLeadingTriviaWidth(sourceFile)); + if (jsDoc.indexOf('@monacodtsreplace') >= 0) { + const jsDocLines = jsDoc.split(/\r\n|\r|\n/); + let directives: [RegExp, string][] = []; + for (const jsDocLine of jsDocLines) { + const m = jsDocLine.match(/^\s*\* \/([^/]+)\/([^/]+)\/$/); + if (m) { + directives.push([new RegExp(m[1], 'g'), m[2]]); + } + } + // remove the jsdoc + result = result.substr(jsDoc.length); + if (directives.length > 0) { + // apply replace directives + const replacer = createReplacerFromDirectives(directives); + result = replacer(result); + } + } } result = result.replace(/export default /g, 'export '); result = result.replace(/export declare /g, 'export '); @@ -184,7 +203,10 @@ function getMassagedTopLevelDeclarationText(sourceFile: ts.SourceFile, declarati if (declaration.kind === ts.SyntaxKind.EnumDeclaration) { result = result.replace(/const enum/, 'enum'); - enums.push(result); + enums.push({ + enumName: declaration.name.getText(sourceFile), + text: result + }); } return result; @@ -324,6 +346,15 @@ function format(text: string, endl: string): string { } } +function createReplacerFromDirectives(directives: [RegExp, string][]): (str: string) => string { + return (str: string) => { + for (let i = 0; i < directives.length; i++) { + str = str.replace(directives[i][0], directives[i][1]); + } + return str; + }; +} + function createReplacer(data: string): (str: string) => string { data = data || ''; let rawDirectives = data.split(';'); @@ -341,12 +372,7 @@ function createReplacer(data: string): (str: string) => string { directives.push([new RegExp(findStr, 'g'), replaceStr]); }); - return (str: string) => { - for (let i = 0; i < directives.length; i++) { - str = str.replace(directives[i][0], directives[i][1]); - } - return str; - }; + return createReplacerFromDirectives(directives); } interface ITempResult { @@ -355,6 +381,11 @@ interface ITempResult { enums: string; } +interface IEnumEntry { + enumName: string; + text: string; +} + function generateDeclarationFile(recipe: string, sourceFileGetter: SourceFileGetter): ITempResult | null { const endl = /\r\n/.test(recipe) ? '\r\n' : '\n'; @@ -376,7 +407,7 @@ function generateDeclarationFile(recipe: string, sourceFileGetter: SourceFileGet return importName; }; - let enums: string[] = []; + let enums: IEnumEntry[] = []; let version: string | null = null; lines.forEach(line => { @@ -492,6 +523,16 @@ function generateDeclarationFile(recipe: string, sourceFileGetter: SourceFileGet resultTxt = format(resultTxt, endl); resultTxt = resultTxt.split(/\r\n|\n|\r/).join(endl); + enums.sort((e1, e2) => { + if (e1.enumName < e2.enumName) { + return -1; + } + if (e1.enumName > e2.enumName) { + return 1; + } + return 0; + }); + let resultEnums = [ '/*---------------------------------------------------------------------------------------------', ' * Copyright (c) Microsoft Corporation. All rights reserved.', @@ -500,7 +541,7 @@ function generateDeclarationFile(recipe: string, sourceFileGetter: SourceFileGet '', '// THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY.', '' - ].concat(enums).join(endl); + ].concat(enums.map(e => e.text)).join(endl); resultEnums = resultEnums.split(/\r\n|\n|\r/).join(endl); resultEnums = format(resultEnums, endl); resultEnums = resultEnums.split(/\r\n|\n|\r/).join(endl); diff --git a/build/monaco/monaco.d.ts.recipe b/build/monaco/monaco.d.ts.recipe index f16ed378bd579..fdcdf533406e9 100644 --- a/build/monaco/monaco.d.ts.recipe +++ b/build/monaco/monaco.d.ts.recipe @@ -62,6 +62,7 @@ export interface ICommandHandler { #includeAll(vs/editor/common/editorCommon;editorOptions.=>): IScrollEvent #includeAll(vs/editor/common/model/textModelEvents): #includeAll(vs/editor/common/controller/cursorEvents): +#include(vs/platform/accessibility/common/accessibility): AccessibilitySupport #includeAll(vs/editor/common/config/editorOptions): #includeAll(vs/editor/browser/editorBrowser;editorCommon.=>;editorOptions.=>): #include(vs/editor/common/config/fontInfo): FontInfo, BareFontInfo @@ -87,4 +88,4 @@ declare namespace monaco.worker { } -//dtsv=2 +//dtsv=3 diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index cbbe9d1701449..bf098e0699f83 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -540,12 +540,12 @@ export interface ICodeEditor extends editorCommon.IEditor { setModel(model: ITextModel | null): void; /** - * @internal + * Gets all the editor computed options. */ getOptions(): IComputedEditorOptions; /** - * @internal + * Gets a specific editor option. */ getOption(id: T): FindComputedEditorOptionValueById; diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 100e0a7fdb8b3..6304bfb375437 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -33,7 +33,6 @@ export type EditorAutoClosingOvertypeStrategy = 'always' | 'auto' | 'never'; /** * Configuration options for auto indentation in the editor - * @internal */ export const enum EditorAutoIndentStrategy { None = 0, @@ -636,7 +635,7 @@ export class ValidatedEditorOptions { } /** - * @internal + * All computed editor options. */ export interface IComputedEditorOptions { get(id: T): FindComputedEditorOptionValueById; @@ -660,9 +659,6 @@ export interface IEnvironmentalOptions { readonly accessibilitySupport: AccessibilitySupport; } -/** - * @internal - */ export interface IEditorOption { readonly id: K1; readonly name: string; @@ -1000,7 +996,6 @@ class EditorAccessibilitySupport extends BaseEditorOption>; class EditorFind extends BaseEditorOption { @@ -1364,9 +1355,6 @@ export interface IGotoLocationOptions { alternativeReferenceCommand?: string; } -/** - * @internal - */ export type GoToLocationOptions = Readonly>; class EditorGoToLocation extends BaseEditorOption { @@ -1496,9 +1484,6 @@ export interface IEditorHoverOptions { sticky?: boolean; } -/** - * @internal - */ export type EditorHoverOptions = Readonly>; class EditorHover extends BaseEditorOption { @@ -1924,9 +1909,6 @@ export interface IEditorLightbulbOptions { enabled?: boolean; } -/** - * @internal - */ export type EditorLightbulbOptions = Readonly>; class EditorLightbulb extends BaseEditorOption { @@ -2018,9 +2000,6 @@ export interface IEditorMinimapOptions { scale?: number; } -/** - * @internal - */ export type EditorMinimapOptions = Readonly>; class EditorMinimap extends BaseEditorOption { @@ -2122,9 +2101,6 @@ export interface IEditorParameterHintOptions { cycle?: boolean; } -/** - * @internal - */ export type InternalParameterHintOptions = Readonly>; class EditorParameterHints extends BaseEditorOption { @@ -2191,9 +2167,6 @@ export interface IQuickSuggestionsOptions { strings: boolean; } -/** - * @internal - */ export type ValidQuickSuggestionsOptions = boolean | Readonly>; class EditorQuickSuggestions extends BaseEditorOption { @@ -2270,9 +2243,6 @@ class EditorQuickSuggestions extends BaseEditorOption string); -/** - * @internal - */ export const enum RenderLineNumbersType { Off = 0, On = 1, @@ -2281,9 +2251,6 @@ export const enum RenderLineNumbersType { Custom = 4 } -/** - * @internal - */ export interface InternalEditorRenderLineNumbersOptions { readonly renderType: RenderLineNumbersType; readonly renderFn: ((lineNumber: number) => string) | null; @@ -2439,9 +2406,6 @@ export interface IEditorScrollbarOptions { horizontalSliderSize?: number; } -/** - * @internal - */ export interface InternalEditorScrollbarOptions { readonly arrowSize: number; readonly vertical: ScrollbarVisibility; @@ -2656,9 +2620,6 @@ export interface ISuggestOptions { showSnippets?: boolean; } -/** - * @internal - */ export type InternalSuggestOptions = Readonly>; class EditorSuggest extends BaseEditorOption { @@ -2952,7 +2913,6 @@ class EditorTabFocusMode extends ComputedEditorOption(option: IEditorOption): IEd return option; } -/** - * @internal - */ export const enum EditorOption { acceptSuggestionOnCommitCharacter, acceptSuggestionOnEnter, @@ -3224,7 +3178,18 @@ export const enum EditorOption { } /** - * @internal + * WORKAROUND: TS emits "any" for complex editor options values (anything except string, bool, enum, etc. ends up being "any") + * @monacodtsreplace + * /accessibilitySupport, any/accessibilitySupport, AccessibilitySupport/ + * /find, any/find, EditorFindOptions/ + * /fontInfo, any/fontInfo, FontInfo/ + * /gotoLocation, any/gotoLocation, GoToLocationOptions/ + * /hover, any/hover, EditorHoverOptions/ + * /lightbulb, any/lightbulb, EditorLightbulbOptions/ + * /minimap, any/minimap, EditorMinimapOptions/ + * /parameterHints, any/parameterHints, InternalParameterHintOptions/ + * /quickSuggestions, any/quickSuggestions, ValidQuickSuggestionsOptions/ + * /suggest, any/suggest, InternalSuggestOptions/ */ export const EditorOptions = { acceptSuggestionOnCommitCharacter: register(new EditorBooleanOption( @@ -3800,19 +3765,7 @@ export const EditorOptions = { wrappingInfo: register(new EditorWrappingInfoComputer()) }; -/** - * @internal - */ type EditorOptionsType = typeof EditorOptions; -/** - * @internal - */ type FindEditorOptionsKeyById = { [K in keyof EditorOptionsType]: EditorOptionsType[K]['id'] extends T ? K : never }[keyof EditorOptionsType]; -/** - * @internal - */ type ComputedEditorOptionValue> = T extends IEditorOption ? R : never; -/** - * @internal - */ export type FindComputedEditorOptionValueById = NonNullable]>>; diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 3340dd5c045b1..f23767462c37f 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -6,16 +6,326 @@ // THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY. -export enum MarkerTag { - Unnecessary = 1, - Deprecated = 2 +export enum AccessibilitySupport { + /** + * This should be the browser case where it is not known if a screen reader is attached or no. + */ + Unknown = 0, + Disabled = 1, + Enabled = 2 } -export enum MarkerSeverity { - Hint = 1, - Info = 2, - Warning = 4, - Error = 8 +export enum CompletionItemInsertTextRule { + /** + * Adjust whitespace/indentation of multiline insert texts to + * match the current line indentation. + */ + KeepWhitespace = 1, + /** + * `insertText` is a snippet. + */ + InsertAsSnippet = 4 +} + +export enum CompletionItemKind { + Method = 0, + Function = 1, + Constructor = 2, + Field = 3, + Variable = 4, + Class = 5, + Struct = 6, + Interface = 7, + Module = 8, + Property = 9, + Event = 10, + Operator = 11, + Unit = 12, + Value = 13, + Constant = 14, + Enum = 15, + EnumMember = 16, + Keyword = 17, + Text = 18, + Color = 19, + File = 20, + Reference = 21, + Customcolor = 22, + Folder = 23, + TypeParameter = 24, + Snippet = 25 +} + +export enum CompletionItemTag { + Deprecated = 1 +} + +/** + * How a suggest provider was triggered. + */ +export enum CompletionTriggerKind { + Invoke = 0, + TriggerCharacter = 1, + TriggerForIncompleteCompletions = 2 +} + +/** + * A positioning preference for rendering content widgets. + */ +export enum ContentWidgetPositionPreference { + /** + * Place the content widget exactly at a position + */ + EXACT = 0, + /** + * Place the content widget above a position + */ + ABOVE = 1, + /** + * Place the content widget below a position + */ + BELOW = 2 +} + +/** + * Describes the reason the cursor has changed its position. + */ +export enum CursorChangeReason { + /** + * Unknown or not set. + */ + NotSet = 0, + /** + * A `model.setValue()` was called. + */ + ContentFlush = 1, + /** + * The `model` has been changed outside of this cursor and the cursor recovers its position from associated markers. + */ + RecoverFromMarkers = 2, + /** + * There was an explicit user gesture. + */ + Explicit = 3, + /** + * There was a Paste. + */ + Paste = 4, + /** + * There was an Undo. + */ + Undo = 5, + /** + * There was a Redo. + */ + Redo = 6 +} + +/** + * The default end of line to use when instantiating models. + */ +export enum DefaultEndOfLine { + /** + * Use line feed (\n) as the end of line character. + */ + LF = 1, + /** + * Use carriage return and line feed (\r\n) as the end of line character. + */ + CRLF = 2 +} + +/** + * A document highlight kind. + */ +export enum DocumentHighlightKind { + /** + * A textual occurrence. + */ + Text = 0, + /** + * Read-access of a symbol, like reading a variable. + */ + Read = 1, + /** + * Write-access of a symbol, like writing to a variable. + */ + Write = 2 +} + +/** + * Configuration options for auto indentation in the editor + */ +export enum EditorAutoIndentStrategy { + None = 0, + Keep = 1, + Brackets = 2, + Advanced = 3, + Full = 4 +} + +export enum EditorOption { + acceptSuggestionOnCommitCharacter = 0, + acceptSuggestionOnEnter = 1, + accessibilitySupport = 2, + accessibilityPageSize = 3, + ariaLabel = 4, + autoClosingBrackets = 5, + autoClosingOvertype = 6, + autoClosingQuotes = 7, + autoIndent = 8, + automaticLayout = 9, + autoSurround = 10, + codeLens = 11, + colorDecorators = 12, + contextmenu = 13, + copyWithSyntaxHighlighting = 14, + cursorBlinking = 15, + cursorSmoothCaretAnimation = 16, + cursorStyle = 17, + cursorSurroundingLines = 18, + cursorSurroundingLinesStyle = 19, + cursorWidth = 20, + disableLayerHinting = 21, + disableMonospaceOptimizations = 22, + dragAndDrop = 23, + emptySelectionClipboard = 24, + extraEditorClassName = 25, + fastScrollSensitivity = 26, + find = 27, + fixedOverflowWidgets = 28, + folding = 29, + foldingStrategy = 30, + fontFamily = 31, + fontInfo = 32, + fontLigatures = 33, + fontSize = 34, + fontWeight = 35, + formatOnPaste = 36, + formatOnType = 37, + glyphMargin = 38, + gotoLocation = 39, + hideCursorInOverviewRuler = 40, + highlightActiveIndentGuide = 41, + hover = 42, + inDiffEditor = 43, + letterSpacing = 44, + lightbulb = 45, + lineDecorationsWidth = 46, + lineHeight = 47, + lineNumbers = 48, + lineNumbersMinChars = 49, + links = 50, + matchBrackets = 51, + minimap = 52, + mouseStyle = 53, + mouseWheelScrollSensitivity = 54, + mouseWheelZoom = 55, + multiCursorMergeOverlapping = 56, + multiCursorModifier = 57, + multiCursorPaste = 58, + occurrencesHighlight = 59, + overviewRulerBorder = 60, + overviewRulerLanes = 61, + parameterHints = 62, + quickSuggestions = 63, + quickSuggestionsDelay = 64, + readOnly = 65, + renderControlCharacters = 66, + renderIndentGuides = 67, + renderFinalNewline = 68, + renderLineHighlight = 69, + renderWhitespace = 70, + revealHorizontalRightPadding = 71, + roundedSelection = 72, + rulers = 73, + scrollbar = 74, + scrollBeyondLastColumn = 75, + scrollBeyondLastLine = 76, + selectionClipboard = 77, + selectionHighlight = 78, + selectOnLineNumbers = 79, + showFoldingControls = 80, + showUnused = 81, + snippetSuggestions = 82, + smoothScrolling = 83, + stopRenderingLineAfter = 84, + suggest = 85, + suggestFontSize = 86, + suggestLineHeight = 87, + suggestOnTriggerCharacters = 88, + suggestSelection = 89, + tabCompletion = 90, + useTabStops = 91, + wordSeparators = 92, + wordWrap = 93, + wordWrapBreakAfterCharacters = 94, + wordWrapBreakBeforeCharacters = 95, + wordWrapBreakObtrusiveCharacters = 96, + wordWrapColumn = 97, + wordWrapMinified = 98, + wrappingIndent = 99, + editorClassName = 100, + pixelRatio = 101, + tabFocusMode = 102, + layoutInfo = 103, + wrappingInfo = 104 +} + +/** + * End of line character preference. + */ +export enum EndOfLinePreference { + /** + * Use the end of line character identified in the text buffer. + */ + TextDefined = 0, + /** + * Use line feed (\n) as the end of line character. + */ + LF = 1, + /** + * Use carriage return and line feed (\r\n) as the end of line character. + */ + CRLF = 2 +} + +/** + * End of line character preference. + */ +export enum EndOfLineSequence { + /** + * Use line feed (\n) as the end of line character. + */ + LF = 0, + /** + * Use carriage return and line feed (\r\n) as the end of line character. + */ + CRLF = 1 +} + +/** + * Describes what to do with the indentation when pressing Enter. + */ +export enum IndentAction { + /** + * Insert new line and copy the previous line's indentation. + */ + None = 0, + /** + * Insert new line and indent once (relative to the previous line's indentation). + */ + Indent = 1, + /** + * Insert two new lines: + * - the first one indented which will hold the cursor + * - the second one at the same indentation level + */ + IndentOutdent = 2, + /** + * Insert new line and outdent once (relative to the previous line's indentation). + */ + Outdent = 3 } /** @@ -199,34 +509,16 @@ export enum KeyCode { MAX_VALUE = 112 } -/** - * The direction of a selection. - */ -export enum SelectionDirection { - /** - * The selection starts above where it ends. - */ - LTR = 0, - /** - * The selection starts below where it ends. - */ - RTL = 1 -} - -export enum ScrollbarVisibility { - Auto = 1, - Hidden = 2, - Visible = 3 +export enum MarkerSeverity { + Hint = 1, + Info = 2, + Warning = 4, + Error = 8 } -/** - * Vertical Lane in the overview ruler of the editor. - */ -export enum OverviewRulerLane { - Left = 1, - Center = 2, - Right = 4, - Full = 7 +export enum MarkerTag { + Unnecessary = 1, + Deprecated = 2 } /** @@ -238,155 +530,17 @@ export enum MinimapPosition { } /** - * End of line character preference. + * Type of hit element with the mouse in the editor. */ -export enum EndOfLinePreference { +export enum MouseTargetType { /** - * Use the end of line character identified in the text buffer. + * Mouse is on top of an unknown element. */ - TextDefined = 0, + UNKNOWN = 0, /** - * Use line feed (\n) as the end of line character. + * Mouse is on top of the textarea used for input. */ - LF = 1, - /** - * Use carriage return and line feed (\r\n) as the end of line character. - */ - CRLF = 2 -} - -/** - * The default end of line to use when instantiating models. - */ -export enum DefaultEndOfLine { - /** - * Use line feed (\n) as the end of line character. - */ - LF = 1, - /** - * Use carriage return and line feed (\r\n) as the end of line character. - */ - CRLF = 2 -} - -/** - * End of line character preference. - */ -export enum EndOfLineSequence { - /** - * Use line feed (\n) as the end of line character. - */ - LF = 0, - /** - * Use carriage return and line feed (\r\n) as the end of line character. - */ - CRLF = 1 -} - -/** - * Describes the behavior of decorations when typing/editing near their edges. - * Note: Please do not edit the values, as they very carefully match `DecorationRangeBehavior` - */ -export enum TrackedRangeStickiness { - AlwaysGrowsWhenTypingAtEdges = 0, - NeverGrowsWhenTypingAtEdges = 1, - GrowsOnlyWhenTypingBefore = 2, - GrowsOnlyWhenTypingAfter = 3 -} - -export enum ScrollType { - Smooth = 0, - Immediate = 1 -} - -/** - * Describes the reason the cursor has changed its position. - */ -export enum CursorChangeReason { - /** - * Unknown or not set. - */ - NotSet = 0, - /** - * A `model.setValue()` was called. - */ - ContentFlush = 1, - /** - * The `model` has been changed outside of this cursor and the cursor recovers its position from associated markers. - */ - RecoverFromMarkers = 2, - /** - * There was an explicit user gesture. - */ - Explicit = 3, - /** - * There was a Paste. - */ - Paste = 4, - /** - * There was an Undo. - */ - Undo = 5, - /** - * There was a Redo. - */ - Redo = 6 -} - -export enum RenderMinimap { - None = 0, - Text = 1, - Blocks = 2 -} - -/** - * A positioning preference for rendering content widgets. - */ -export enum ContentWidgetPositionPreference { - /** - * Place the content widget exactly at a position - */ - EXACT = 0, - /** - * Place the content widget above a position - */ - ABOVE = 1, - /** - * Place the content widget below a position - */ - BELOW = 2 -} - -/** - * A positioning preference for rendering overlay widgets. - */ -export enum OverlayWidgetPositionPreference { - /** - * Position the overlay widget in the top right corner - */ - TOP_RIGHT_CORNER = 0, - /** - * Position the overlay widget in the bottom right corner - */ - BOTTOM_RIGHT_CORNER = 1, - /** - * Position the overlay widget in the top center - */ - TOP_CENTER = 2 -} - -/** - * Type of hit element with the mouse in the editor. - */ -export enum MouseTargetType { - /** - * Mouse is on top of an unknown element. - */ - UNKNOWN = 0, - /** - * Mouse is on top of the textarea used for input. - */ - TEXTAREA = 1, + TEXTAREA = 1, /** * Mouse is on top of the glyph margin */ @@ -438,105 +592,76 @@ export enum MouseTargetType { } /** - * Describes what to do with the indentation when pressing Enter. + * A positioning preference for rendering overlay widgets. */ -export enum IndentAction { - /** - * Insert new line and copy the previous line's indentation. - */ - None = 0, +export enum OverlayWidgetPositionPreference { /** - * Insert new line and indent once (relative to the previous line's indentation). + * Position the overlay widget in the top right corner */ - Indent = 1, + TOP_RIGHT_CORNER = 0, /** - * Insert two new lines: - * - the first one indented which will hold the cursor - * - the second one at the same indentation level + * Position the overlay widget in the bottom right corner */ - IndentOutdent = 2, + BOTTOM_RIGHT_CORNER = 1, /** - * Insert new line and outdent once (relative to the previous line's indentation). + * Position the overlay widget in the top center */ - Outdent = 3 + TOP_CENTER = 2 } -export enum CompletionItemKind { - Method = 0, - Function = 1, - Constructor = 2, - Field = 3, - Variable = 4, - Class = 5, - Struct = 6, - Interface = 7, - Module = 8, - Property = 9, - Event = 10, - Operator = 11, - Unit = 12, - Value = 13, - Constant = 14, - Enum = 15, - EnumMember = 16, - Keyword = 17, - Text = 18, - Color = 19, - File = 20, - Reference = 21, - Customcolor = 22, - Folder = 23, - TypeParameter = 24, - Snippet = 25 +/** + * Vertical Lane in the overview ruler of the editor. + */ +export enum OverviewRulerLane { + Left = 1, + Center = 2, + Right = 4, + Full = 7 } -export enum CompletionItemTag { - Deprecated = 1 +export enum RenderLineNumbersType { + Off = 0, + On = 1, + Relative = 2, + Interval = 3, + Custom = 4 } -export enum CompletionItemInsertTextRule { - /** - * Adjust whitespace/indentation of multiline insert texts to - * match the current line indentation. - */ - KeepWhitespace = 1, - /** - * `insertText` is a snippet. - */ - InsertAsSnippet = 4 +export enum RenderMinimap { + None = 0, + Text = 1, + Blocks = 2 } -/** - * How a suggest provider was triggered. - */ -export enum CompletionTriggerKind { - Invoke = 0, - TriggerCharacter = 1, - TriggerForIncompleteCompletions = 2 +export enum ScrollType { + Smooth = 0, + Immediate = 1 } -export enum SignatureHelpTriggerKind { - Invoke = 1, - TriggerCharacter = 2, - ContentChange = 3 +export enum ScrollbarVisibility { + Auto = 1, + Hidden = 2, + Visible = 3 } /** - * A document highlight kind. + * The direction of a selection. */ -export enum DocumentHighlightKind { - /** - * A textual occurrence. - */ - Text = 0, +export enum SelectionDirection { /** - * Read-access of a symbol, like reading a variable. + * The selection starts above where it ends. */ - Read = 1, + LTR = 0, /** - * Write-access of a symbol, like writing to a variable. + * The selection starts below where it ends. */ - Write = 2 + RTL = 1 +} + +export enum SignatureHelpTriggerKind { + Invoke = 1, + TriggerCharacter = 2, + ContentChange = 3 } /** @@ -573,4 +698,97 @@ export enum SymbolKind { export enum SymbolTag { Deprecated = 1 +} + +/** + * The kind of animation in which the editor's cursor should be rendered. + */ +export enum TextEditorCursorBlinkingStyle { + /** + * Hidden + */ + Hidden = 0, + /** + * Blinking + */ + Blink = 1, + /** + * Blinking with smooth fading + */ + Smooth = 2, + /** + * Blinking with prolonged filled state and smooth fading + */ + Phase = 3, + /** + * Expand collapse animation on the y axis + */ + Expand = 4, + /** + * No-Blinking + */ + Solid = 5 +} + +/** + * The style in which the editor's cursor should be rendered. + */ +export enum TextEditorCursorStyle { + /** + * As a vertical line (sitting between two characters). + */ + Line = 1, + /** + * As a block (sitting on top of a character). + */ + Block = 2, + /** + * As a horizontal line (sitting under a character). + */ + Underline = 3, + /** + * As a thin vertical line (sitting between two characters). + */ + LineThin = 4, + /** + * As an outlined block (sitting on top of a character). + */ + BlockOutline = 5, + /** + * As a thin horizontal line (sitting under a character). + */ + UnderlineThin = 6 +} + +/** + * Describes the behavior of decorations when typing/editing near their edges. + * Note: Please do not edit the values, as they very carefully match `DecorationRangeBehavior` + */ +export enum TrackedRangeStickiness { + AlwaysGrowsWhenTypingAtEdges = 0, + NeverGrowsWhenTypingAtEdges = 1, + GrowsOnlyWhenTypingBefore = 2, + GrowsOnlyWhenTypingAfter = 3 +} + +/** + * Describes how to indent wrapped lines. + */ +export enum WrappingIndent { + /** + * No indentation => wrapped lines begin at column 1. + */ + None = 0, + /** + * Same => wrapped lines get the same indentation as the parent. + */ + Same = 1, + /** + * Indent => wrapped lines get +1 indentation toward the parent. + */ + Indent = 2, + /** + * DeepIndent => wrapped lines get +2 indentation toward the parent. + */ + DeepIndent = 3 } \ No newline at end of file diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 14e54cbb0bb36..65285eb9a2d2d 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -10,7 +10,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { OpenerService } from 'vs/editor/browser/services/openerService'; import { DiffNavigator, IDiffNavigator } from 'vs/editor/browser/widget/diffNavigator'; -import { ConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions'; +import { EditorOptions, ConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; import { Token } from 'vs/editor/common/core/token'; import * as editorCommon from 'vs/editor/common/editorCommon'; @@ -346,19 +346,26 @@ export function createMonacoEditorAPI(): typeof monaco.editor { remeasureFonts: remeasureFonts, // enums - ScrollbarVisibility: standaloneEnums.ScrollbarVisibility, - OverviewRulerLane: standaloneEnums.OverviewRulerLane, - MinimapPosition: standaloneEnums.MinimapPosition, - EndOfLinePreference: standaloneEnums.EndOfLinePreference, + AccessibilitySupport: standaloneEnums.AccessibilitySupport, + ContentWidgetPositionPreference: standaloneEnums.ContentWidgetPositionPreference, + CursorChangeReason: standaloneEnums.CursorChangeReason, DefaultEndOfLine: standaloneEnums.DefaultEndOfLine, + EditorAutoIndentStrategy: standaloneEnums.EditorAutoIndentStrategy, + EditorOption: standaloneEnums.EditorOption, + EndOfLinePreference: standaloneEnums.EndOfLinePreference, EndOfLineSequence: standaloneEnums.EndOfLineSequence, - TrackedRangeStickiness: standaloneEnums.TrackedRangeStickiness, - CursorChangeReason: standaloneEnums.CursorChangeReason, + MinimapPosition: standaloneEnums.MinimapPosition, MouseTargetType: standaloneEnums.MouseTargetType, - ContentWidgetPositionPreference: standaloneEnums.ContentWidgetPositionPreference, OverlayWidgetPositionPreference: standaloneEnums.OverlayWidgetPositionPreference, + OverviewRulerLane: standaloneEnums.OverviewRulerLane, + RenderLineNumbersType: standaloneEnums.RenderLineNumbersType, RenderMinimap: standaloneEnums.RenderMinimap, + ScrollbarVisibility: standaloneEnums.ScrollbarVisibility, ScrollType: standaloneEnums.ScrollType, + TextEditorCursorBlinkingStyle: standaloneEnums.TextEditorCursorBlinkingStyle, + TextEditorCursorStyle: standaloneEnums.TextEditorCursorStyle, + TrackedRangeStickiness: standaloneEnums.TrackedRangeStickiness, + WrappingIndent: standaloneEnums.WrappingIndent, // classes ConfigurationChangedEvent: ConfigurationChangedEvent, @@ -369,6 +376,7 @@ export function createMonacoEditorAPI(): typeof monaco.editor { // vars EditorType: editorCommon.EditorType, + EditorOptions: EditorOptions }; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 1ff3e1c0cd512..14651914b4f14 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2473,6 +2473,15 @@ declare namespace monaco.editor { readonly reason: CursorChangeReason; } + export enum AccessibilitySupport { + /** + * This should be the browser case where it is not known if a screen reader is attached or no. + */ + Unknown = 0, + Disabled = 1, + Enabled = 2 + } + /** * Configuration options for auto closing quotes and brackets */ @@ -2488,6 +2497,17 @@ declare namespace monaco.editor { */ export type EditorAutoClosingOvertypeStrategy = 'always' | 'auto' | 'never'; + /** + * Configuration options for auto indentation in the editor + */ + export enum EditorAutoIndentStrategy { + None = 0, + Keep = 1, + Brackets = 2, + Advanced = 3, + Full = 4 + } + /** * Configuration options for the editor. */ @@ -3043,6 +3063,79 @@ declare namespace monaco.editor { export class ConfigurationChangedEvent { } + /** + * All computed editor options. + */ + export interface IComputedEditorOptions { + get(id: T): FindComputedEditorOptionValueById; + } + + export interface IEditorOption { + readonly id: K1; + readonly name: string; + defaultValue: V; + } + + /** + * The kind of animation in which the editor's cursor should be rendered. + */ + export enum TextEditorCursorBlinkingStyle { + /** + * Hidden + */ + Hidden = 0, + /** + * Blinking + */ + Blink = 1, + /** + * Blinking with smooth fading + */ + Smooth = 2, + /** + * Blinking with prolonged filled state and smooth fading + */ + Phase = 3, + /** + * Expand collapse animation on the y axis + */ + Expand = 4, + /** + * No-Blinking + */ + Solid = 5 + } + + /** + * The style in which the editor's cursor should be rendered. + */ + export enum TextEditorCursorStyle { + /** + * As a vertical line (sitting between two characters). + */ + Line = 1, + /** + * As a block (sitting on top of a character). + */ + Block = 2, + /** + * As a horizontal line (sitting under a character). + */ + Underline = 3, + /** + * As a thin vertical line (sitting between two characters). + */ + LineThin = 4, + /** + * As an outlined block (sitting on top of a character). + */ + BlockOutline = 5, + /** + * As a thin horizontal line (sitting under a character). + */ + UnderlineThin = 6 + } + /** * Configuration options for editor find widget */ @@ -3058,6 +3151,8 @@ declare namespace monaco.editor { addExtraSpaceOnTop?: boolean; } + export type EditorFindOptions = Readonly>; + export type GoToLocationValues = 'peek' | 'gotoAndPeek' | 'goto'; /** @@ -3077,6 +3172,8 @@ declare namespace monaco.editor { alternativeReferenceCommand?: string; } + export type GoToLocationOptions = Readonly>; + /** * Configuration options for editor hover */ @@ -3098,6 +3195,8 @@ declare namespace monaco.editor { sticky?: boolean; } + export type EditorHoverOptions = Readonly>; + /** * Configuration options for semantic highlighting */ @@ -3238,6 +3337,8 @@ declare namespace monaco.editor { enabled?: boolean; } + export type EditorLightbulbOptions = Readonly>; + /** * Configuration options for editor minimap */ @@ -3273,6 +3374,8 @@ declare namespace monaco.editor { scale?: number; } + export type EditorMinimapOptions = Readonly>; + /** * Configuration options for parameter hints */ @@ -3289,6 +3392,8 @@ declare namespace monaco.editor { cycle?: boolean; } + export type InternalParameterHintOptions = Readonly>; + /** * Configuration options for quick suggestions */ @@ -3298,8 +3403,23 @@ declare namespace monaco.editor { strings: boolean; } + export type ValidQuickSuggestionsOptions = boolean | Readonly>; + export type LineNumbersType = 'on' | 'off' | 'relative' | 'interval' | ((lineNumber: number) => string); + export enum RenderLineNumbersType { + Off = 0, + On = 1, + Relative = 2, + Interval = 3, + Custom = 4 + } + + export interface InternalEditorRenderLineNumbersOptions { + readonly renderType: RenderLineNumbersType; + readonly renderFn: ((lineNumber: number) => string) | null; + } + /** * Configuration options for editor scrollbars */ @@ -3366,6 +3486,21 @@ declare namespace monaco.editor { horizontalSliderSize?: number; } + export interface InternalEditorScrollbarOptions { + readonly arrowSize: number; + readonly vertical: ScrollbarVisibility; + readonly horizontal: ScrollbarVisibility; + readonly useShadows: boolean; + readonly verticalHasArrows: boolean; + readonly horizontalHasArrows: boolean; + readonly handleMouseWheel: boolean; + readonly alwaysConsumeMouseWheel: boolean; + readonly horizontalScrollbarSize: number; + readonly horizontalSliderSize: number; + readonly verticalScrollbarSize: number; + readonly verticalSliderSize: number; + } + /** * Configuration options for editor suggest widget */ @@ -3504,6 +3639,262 @@ declare namespace monaco.editor { showSnippets?: boolean; } + export type InternalSuggestOptions = Readonly>; + + /** + * Describes how to indent wrapped lines. + */ + export enum WrappingIndent { + /** + * No indentation => wrapped lines begin at column 1. + */ + None = 0, + /** + * Same => wrapped lines get the same indentation as the parent. + */ + Same = 1, + /** + * Indent => wrapped lines get +1 indentation toward the parent. + */ + Indent = 2, + /** + * DeepIndent => wrapped lines get +2 indentation toward the parent. + */ + DeepIndent = 3 + } + + export interface EditorWrappingInfo { + readonly isDominatedByLongLines: boolean; + readonly isWordWrapMinified: boolean; + readonly isViewportWrapping: boolean; + readonly wrappingColumn: number; + } + + export enum EditorOption { + acceptSuggestionOnCommitCharacter = 0, + acceptSuggestionOnEnter = 1, + accessibilitySupport = 2, + accessibilityPageSize = 3, + ariaLabel = 4, + autoClosingBrackets = 5, + autoClosingOvertype = 6, + autoClosingQuotes = 7, + autoIndent = 8, + automaticLayout = 9, + autoSurround = 10, + codeLens = 11, + colorDecorators = 12, + contextmenu = 13, + copyWithSyntaxHighlighting = 14, + cursorBlinking = 15, + cursorSmoothCaretAnimation = 16, + cursorStyle = 17, + cursorSurroundingLines = 18, + cursorSurroundingLinesStyle = 19, + cursorWidth = 20, + disableLayerHinting = 21, + disableMonospaceOptimizations = 22, + dragAndDrop = 23, + emptySelectionClipboard = 24, + extraEditorClassName = 25, + fastScrollSensitivity = 26, + find = 27, + fixedOverflowWidgets = 28, + folding = 29, + foldingStrategy = 30, + fontFamily = 31, + fontInfo = 32, + fontLigatures = 33, + fontSize = 34, + fontWeight = 35, + formatOnPaste = 36, + formatOnType = 37, + glyphMargin = 38, + gotoLocation = 39, + hideCursorInOverviewRuler = 40, + highlightActiveIndentGuide = 41, + hover = 42, + inDiffEditor = 43, + letterSpacing = 44, + lightbulb = 45, + lineDecorationsWidth = 46, + lineHeight = 47, + lineNumbers = 48, + lineNumbersMinChars = 49, + links = 50, + matchBrackets = 51, + minimap = 52, + mouseStyle = 53, + mouseWheelScrollSensitivity = 54, + mouseWheelZoom = 55, + multiCursorMergeOverlapping = 56, + multiCursorModifier = 57, + multiCursorPaste = 58, + occurrencesHighlight = 59, + overviewRulerBorder = 60, + overviewRulerLanes = 61, + parameterHints = 62, + quickSuggestions = 63, + quickSuggestionsDelay = 64, + readOnly = 65, + renderControlCharacters = 66, + renderIndentGuides = 67, + renderFinalNewline = 68, + renderLineHighlight = 69, + renderWhitespace = 70, + revealHorizontalRightPadding = 71, + roundedSelection = 72, + rulers = 73, + scrollbar = 74, + scrollBeyondLastColumn = 75, + scrollBeyondLastLine = 76, + selectionClipboard = 77, + selectionHighlight = 78, + selectOnLineNumbers = 79, + showFoldingControls = 80, + showUnused = 81, + snippetSuggestions = 82, + smoothScrolling = 83, + stopRenderingLineAfter = 84, + suggest = 85, + suggestFontSize = 86, + suggestLineHeight = 87, + suggestOnTriggerCharacters = 88, + suggestSelection = 89, + tabCompletion = 90, + useTabStops = 91, + wordSeparators = 92, + wordWrap = 93, + wordWrapBreakAfterCharacters = 94, + wordWrapBreakBeforeCharacters = 95, + wordWrapBreakObtrusiveCharacters = 96, + wordWrapColumn = 97, + wordWrapMinified = 98, + wrappingIndent = 99, + editorClassName = 100, + pixelRatio = 101, + tabFocusMode = 102, + layoutInfo = 103, + wrappingInfo = 104 + } + export const EditorOptions: { + acceptSuggestionOnCommitCharacter: IEditorOption; + acceptSuggestionOnEnter: IEditorOption; + accessibilitySupport: IEditorOption; + accessibilityPageSize: IEditorOption; + ariaLabel: IEditorOption; + autoClosingBrackets: IEditorOption; + autoClosingOvertype: IEditorOption; + autoClosingQuotes: IEditorOption; + autoIndent: IEditorOption; + automaticLayout: IEditorOption; + autoSurround: IEditorOption; + codeLens: IEditorOption; + colorDecorators: IEditorOption; + contextmenu: IEditorOption; + copyWithSyntaxHighlighting: IEditorOption; + cursorBlinking: IEditorOption; + cursorSmoothCaretAnimation: IEditorOption; + cursorStyle: IEditorOption; + cursorSurroundingLines: IEditorOption; + cursorSurroundingLinesStyle: IEditorOption; + cursorWidth: IEditorOption; + disableLayerHinting: IEditorOption; + disableMonospaceOptimizations: IEditorOption; + dragAndDrop: IEditorOption; + emptySelectionClipboard: IEditorOption; + extraEditorClassName: IEditorOption; + fastScrollSensitivity: IEditorOption; + find: IEditorOption; + fixedOverflowWidgets: IEditorOption; + folding: IEditorOption; + foldingStrategy: IEditorOption; + fontFamily: IEditorOption; + fontInfo: IEditorOption; + fontLigatures2: IEditorOption; + fontSize: IEditorOption; + fontWeight: IEditorOption; + formatOnPaste: IEditorOption; + formatOnType: IEditorOption; + glyphMargin: IEditorOption; + gotoLocation: IEditorOption; + hideCursorInOverviewRuler: IEditorOption; + highlightActiveIndentGuide: IEditorOption; + hover: IEditorOption; + inDiffEditor: IEditorOption; + letterSpacing: IEditorOption; + lightbulb: IEditorOption; + lineDecorationsWidth: IEditorOption; + lineHeight: IEditorOption; + lineNumbers: IEditorOption; + lineNumbersMinChars: IEditorOption; + links: IEditorOption; + matchBrackets: IEditorOption; + minimap: IEditorOption; + mouseStyle: IEditorOption; + mouseWheelScrollSensitivity: IEditorOption; + mouseWheelZoom: IEditorOption; + multiCursorMergeOverlapping: IEditorOption; + multiCursorModifier: IEditorOption; + multiCursorPaste: IEditorOption; + occurrencesHighlight: IEditorOption; + overviewRulerBorder: IEditorOption; + overviewRulerLanes: IEditorOption; + parameterHints: IEditorOption; + quickSuggestions: IEditorOption; + quickSuggestionsDelay: IEditorOption; + readOnly: IEditorOption; + renderControlCharacters: IEditorOption; + renderIndentGuides: IEditorOption; + renderFinalNewline: IEditorOption; + renderLineHighlight: IEditorOption; + renderWhitespace: IEditorOption; + revealHorizontalRightPadding: IEditorOption; + roundedSelection: IEditorOption; + rulers: IEditorOption; + scrollbar: IEditorOption; + scrollBeyondLastColumn: IEditorOption; + scrollBeyondLastLine: IEditorOption; + selectionClipboard: IEditorOption; + selectionHighlight: IEditorOption; + selectOnLineNumbers: IEditorOption; + showFoldingControls: IEditorOption; + showUnused: IEditorOption; + snippetSuggestions: IEditorOption; + smoothScrolling: IEditorOption; + stopRenderingLineAfter: IEditorOption; + suggest: IEditorOption; + suggestFontSize: IEditorOption; + suggestLineHeight: IEditorOption; + suggestOnTriggerCharacters: IEditorOption; + suggestSelection: IEditorOption; + tabCompletion: IEditorOption; + useTabStops: IEditorOption; + wordSeparators: IEditorOption; + wordWrap: IEditorOption; + wordWrapBreakAfterCharacters: IEditorOption; + wordWrapBreakBeforeCharacters: IEditorOption; + wordWrapBreakObtrusiveCharacters: IEditorOption; + wordWrapColumn: IEditorOption; + wordWrapMinified: IEditorOption; + wrappingIndent: IEditorOption; + editorClassName: IEditorOption; + pixelRatio: IEditorOption; + tabFocusMode: IEditorOption; + layoutInfo: IEditorOption; + wrappingInfo: IEditorOption; + }; + + type EditorOptionsType = typeof EditorOptions; + + type FindEditorOptionsKeyById = { + [K in keyof EditorOptionsType]: EditorOptionsType[K]['id'] extends T ? K : never; + }[keyof EditorOptionsType]; + + type ComputedEditorOptionValue> = T extends IEditorOption ? R : never; + + export type FindComputedEditorOptionValueById = NonNullable]>>; + /** * A view zone is a full horizontal rectangle that 'pushes' text down. * The editor reserves space for view zones when rendering. @@ -3954,6 +4345,14 @@ declare namespace monaco.editor { * It is safe to call setModel(null) to simply detach the current model from the editor. */ setModel(model: ITextModel | null): void; + /** + * Gets all the editor computed options. + */ + getOptions(): IComputedEditorOptions; + /** + * Gets a specific editor option. + */ + getOption(id: T): FindComputedEditorOptionValueById; /** * Returns the editor's configuration (without any validation or defaults). */ @@ -5706,4 +6105,4 @@ declare namespace monaco.worker { } -//dtsv=2 +//dtsv=3 From 10603fe212d50a4be238fabc18d653acb5e13780 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 14 Jan 2020 16:31:00 +0100 Subject: [PATCH 281/315] Rerun monacodts on latest and replace " with ' in strings --- build/monaco/api.js | 9 + build/monaco/api.ts | 9 + .../common/standalone/standaloneEnums.ts | 150 ++++++++------- src/vs/monaco.d.ts | 182 +++++++++--------- 4 files changed, 187 insertions(+), 163 deletions(-) diff --git a/build/monaco/api.js b/build/monaco/api.js index ee9505c059568..0fbaf7335b7da 100644 --- a/build/monaco/api.js +++ b/build/monaco/api.js @@ -171,6 +171,15 @@ function getMassagedTopLevelDeclarationText(sourceFile, declaration, importName, result = result.replace(/export default /g, 'export '); result = result.replace(/export declare /g, 'export '); result = result.replace(/declare /g, ''); + let lines = result.split(/\r\n|\r|\n/); + for (let i = 0; i < lines.length; i++) { + if (/\s*\*/.test(lines[i])) { + // very likely a comment + continue; + } + lines[i] = lines[i].replace(/"/g, '\''); + } + result = lines.join('\n'); if (declaration.kind === ts.SyntaxKind.EnumDeclaration) { result = result.replace(/const enum/, 'enum'); enums.push({ diff --git a/build/monaco/api.ts b/build/monaco/api.ts index b52b0b63431fa..511768ee64be2 100644 --- a/build/monaco/api.ts +++ b/build/monaco/api.ts @@ -200,6 +200,15 @@ function getMassagedTopLevelDeclarationText(sourceFile: ts.SourceFile, declarati result = result.replace(/export default /g, 'export '); result = result.replace(/export declare /g, 'export '); result = result.replace(/declare /g, ''); + let lines = result.split(/\r\n|\r|\n/); + for (let i = 0; i < lines.length; i++) { + if (/\s*\*/.test(lines[i])) { + // very likely a comment + continue; + } + lines[i] = lines[i].replace(/"/g, '\''); + } + result = lines.join('\n'); if (declaration.kind === ts.SyntaxKind.EnumDeclaration) { result = result.replace(/const enum/, 'enum'); diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index f23767462c37f..3d436fb75bcf8 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -196,80 +196,82 @@ export enum EditorOption { fixedOverflowWidgets = 28, folding = 29, foldingStrategy = 30, - fontFamily = 31, - fontInfo = 32, - fontLigatures = 33, - fontSize = 34, - fontWeight = 35, - formatOnPaste = 36, - formatOnType = 37, - glyphMargin = 38, - gotoLocation = 39, - hideCursorInOverviewRuler = 40, - highlightActiveIndentGuide = 41, - hover = 42, - inDiffEditor = 43, - letterSpacing = 44, - lightbulb = 45, - lineDecorationsWidth = 46, - lineHeight = 47, - lineNumbers = 48, - lineNumbersMinChars = 49, - links = 50, - matchBrackets = 51, - minimap = 52, - mouseStyle = 53, - mouseWheelScrollSensitivity = 54, - mouseWheelZoom = 55, - multiCursorMergeOverlapping = 56, - multiCursorModifier = 57, - multiCursorPaste = 58, - occurrencesHighlight = 59, - overviewRulerBorder = 60, - overviewRulerLanes = 61, - parameterHints = 62, - quickSuggestions = 63, - quickSuggestionsDelay = 64, - readOnly = 65, - renderControlCharacters = 66, - renderIndentGuides = 67, - renderFinalNewline = 68, - renderLineHighlight = 69, - renderWhitespace = 70, - revealHorizontalRightPadding = 71, - roundedSelection = 72, - rulers = 73, - scrollbar = 74, - scrollBeyondLastColumn = 75, - scrollBeyondLastLine = 76, - selectionClipboard = 77, - selectionHighlight = 78, - selectOnLineNumbers = 79, - showFoldingControls = 80, - showUnused = 81, - snippetSuggestions = 82, - smoothScrolling = 83, - stopRenderingLineAfter = 84, - suggest = 85, - suggestFontSize = 86, - suggestLineHeight = 87, - suggestOnTriggerCharacters = 88, - suggestSelection = 89, - tabCompletion = 90, - useTabStops = 91, - wordSeparators = 92, - wordWrap = 93, - wordWrapBreakAfterCharacters = 94, - wordWrapBreakBeforeCharacters = 95, - wordWrapBreakObtrusiveCharacters = 96, - wordWrapColumn = 97, - wordWrapMinified = 98, - wrappingIndent = 99, - editorClassName = 100, - pixelRatio = 101, - tabFocusMode = 102, - layoutInfo = 103, - wrappingInfo = 104 + foldingHighlight = 31, + fontFamily = 32, + fontInfo = 33, + fontLigatures = 34, + fontSize = 35, + fontWeight = 36, + formatOnPaste = 37, + formatOnType = 38, + glyphMargin = 39, + gotoLocation = 40, + hideCursorInOverviewRuler = 41, + highlightActiveIndentGuide = 42, + hover = 43, + inDiffEditor = 44, + letterSpacing = 45, + lightbulb = 46, + lineDecorationsWidth = 47, + lineHeight = 48, + lineNumbers = 49, + lineNumbersMinChars = 50, + links = 51, + matchBrackets = 52, + minimap = 53, + mouseStyle = 54, + mouseWheelScrollSensitivity = 55, + mouseWheelZoom = 56, + multiCursorMergeOverlapping = 57, + multiCursorModifier = 58, + multiCursorPaste = 59, + occurrencesHighlight = 60, + overviewRulerBorder = 61, + overviewRulerLanes = 62, + parameterHints = 63, + peekWidgetFocusInlineEditor = 64, + quickSuggestions = 65, + quickSuggestionsDelay = 66, + readOnly = 67, + renderControlCharacters = 68, + renderIndentGuides = 69, + renderFinalNewline = 70, + renderLineHighlight = 71, + renderWhitespace = 72, + revealHorizontalRightPadding = 73, + roundedSelection = 74, + rulers = 75, + scrollbar = 76, + scrollBeyondLastColumn = 77, + scrollBeyondLastLine = 78, + selectionClipboard = 79, + selectionHighlight = 80, + selectOnLineNumbers = 81, + showFoldingControls = 82, + showUnused = 83, + snippetSuggestions = 84, + smoothScrolling = 85, + stopRenderingLineAfter = 86, + suggest = 87, + suggestFontSize = 88, + suggestLineHeight = 89, + suggestOnTriggerCharacters = 90, + suggestSelection = 91, + tabCompletion = 92, + useTabStops = 93, + wordSeparators = 94, + wordWrap = 95, + wordWrapBreakAfterCharacters = 96, + wordWrapBreakBeforeCharacters = 97, + wordWrapColumn = 98, + wordWrapMinified = 99, + wrappingIndent = 100, + wrappingAlgorithm = 101, + editorClassName = 102, + pixelRatio = 103, + tabFocusMode = 104, + layoutInfo = 105, + wrappingInfo = 106 } /** diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 14651914b4f14..905bcd9cdc835 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3702,84 +3702,86 @@ declare namespace monaco.editor { fixedOverflowWidgets = 28, folding = 29, foldingStrategy = 30, - fontFamily = 31, - fontInfo = 32, - fontLigatures = 33, - fontSize = 34, - fontWeight = 35, - formatOnPaste = 36, - formatOnType = 37, - glyphMargin = 38, - gotoLocation = 39, - hideCursorInOverviewRuler = 40, - highlightActiveIndentGuide = 41, - hover = 42, - inDiffEditor = 43, - letterSpacing = 44, - lightbulb = 45, - lineDecorationsWidth = 46, - lineHeight = 47, - lineNumbers = 48, - lineNumbersMinChars = 49, - links = 50, - matchBrackets = 51, - minimap = 52, - mouseStyle = 53, - mouseWheelScrollSensitivity = 54, - mouseWheelZoom = 55, - multiCursorMergeOverlapping = 56, - multiCursorModifier = 57, - multiCursorPaste = 58, - occurrencesHighlight = 59, - overviewRulerBorder = 60, - overviewRulerLanes = 61, - parameterHints = 62, - quickSuggestions = 63, - quickSuggestionsDelay = 64, - readOnly = 65, - renderControlCharacters = 66, - renderIndentGuides = 67, - renderFinalNewline = 68, - renderLineHighlight = 69, - renderWhitespace = 70, - revealHorizontalRightPadding = 71, - roundedSelection = 72, - rulers = 73, - scrollbar = 74, - scrollBeyondLastColumn = 75, - scrollBeyondLastLine = 76, - selectionClipboard = 77, - selectionHighlight = 78, - selectOnLineNumbers = 79, - showFoldingControls = 80, - showUnused = 81, - snippetSuggestions = 82, - smoothScrolling = 83, - stopRenderingLineAfter = 84, - suggest = 85, - suggestFontSize = 86, - suggestLineHeight = 87, - suggestOnTriggerCharacters = 88, - suggestSelection = 89, - tabCompletion = 90, - useTabStops = 91, - wordSeparators = 92, - wordWrap = 93, - wordWrapBreakAfterCharacters = 94, - wordWrapBreakBeforeCharacters = 95, - wordWrapBreakObtrusiveCharacters = 96, - wordWrapColumn = 97, - wordWrapMinified = 98, - wrappingIndent = 99, - editorClassName = 100, - pixelRatio = 101, - tabFocusMode = 102, - layoutInfo = 103, - wrappingInfo = 104 + foldingHighlight = 31, + fontFamily = 32, + fontInfo = 33, + fontLigatures = 34, + fontSize = 35, + fontWeight = 36, + formatOnPaste = 37, + formatOnType = 38, + glyphMargin = 39, + gotoLocation = 40, + hideCursorInOverviewRuler = 41, + highlightActiveIndentGuide = 42, + hover = 43, + inDiffEditor = 44, + letterSpacing = 45, + lightbulb = 46, + lineDecorationsWidth = 47, + lineHeight = 48, + lineNumbers = 49, + lineNumbersMinChars = 50, + links = 51, + matchBrackets = 52, + minimap = 53, + mouseStyle = 54, + mouseWheelScrollSensitivity = 55, + mouseWheelZoom = 56, + multiCursorMergeOverlapping = 57, + multiCursorModifier = 58, + multiCursorPaste = 59, + occurrencesHighlight = 60, + overviewRulerBorder = 61, + overviewRulerLanes = 62, + parameterHints = 63, + peekWidgetFocusInlineEditor = 64, + quickSuggestions = 65, + quickSuggestionsDelay = 66, + readOnly = 67, + renderControlCharacters = 68, + renderIndentGuides = 69, + renderFinalNewline = 70, + renderLineHighlight = 71, + renderWhitespace = 72, + revealHorizontalRightPadding = 73, + roundedSelection = 74, + rulers = 75, + scrollbar = 76, + scrollBeyondLastColumn = 77, + scrollBeyondLastLine = 78, + selectionClipboard = 79, + selectionHighlight = 80, + selectOnLineNumbers = 81, + showFoldingControls = 82, + showUnused = 83, + snippetSuggestions = 84, + smoothScrolling = 85, + stopRenderingLineAfter = 86, + suggest = 87, + suggestFontSize = 88, + suggestLineHeight = 89, + suggestOnTriggerCharacters = 90, + suggestSelection = 91, + tabCompletion = 92, + useTabStops = 93, + wordSeparators = 94, + wordWrap = 95, + wordWrapBreakAfterCharacters = 96, + wordWrapBreakBeforeCharacters = 97, + wordWrapColumn = 98, + wordWrapMinified = 99, + wrappingIndent = 100, + wrappingAlgorithm = 101, + editorClassName = 102, + pixelRatio = 103, + tabFocusMode = 104, + layoutInfo = 105, + wrappingInfo = 106 } export const EditorOptions: { acceptSuggestionOnCommitCharacter: IEditorOption; - acceptSuggestionOnEnter: IEditorOption; + acceptSuggestionOnEnter: IEditorOption; accessibilitySupport: IEditorOption; accessibilityPageSize: IEditorOption; ariaLabel: IEditorOption; @@ -3797,7 +3799,7 @@ declare namespace monaco.editor { cursorSmoothCaretAnimation: IEditorOption; cursorStyle: IEditorOption; cursorSurroundingLines: IEditorOption; - cursorSurroundingLinesStyle: IEditorOption; + cursorSurroundingLinesStyle: IEditorOption; cursorWidth: IEditorOption; disableLayerHinting: IEditorOption; disableMonospaceOptimizations: IEditorOption; @@ -3808,7 +3810,8 @@ declare namespace monaco.editor { find: IEditorOption; fixedOverflowWidgets: IEditorOption; folding: IEditorOption; - foldingStrategy: IEditorOption; + foldingStrategy: IEditorOption; + foldingHighlight: IEditorOption; fontFamily: IEditorOption; fontInfo: IEditorOption; fontLigatures2: IEditorOption; @@ -3829,26 +3832,27 @@ declare namespace monaco.editor { lineNumbers: IEditorOption; lineNumbersMinChars: IEditorOption; links: IEditorOption; - matchBrackets: IEditorOption; + matchBrackets: IEditorOption; minimap: IEditorOption; - mouseStyle: IEditorOption; + mouseStyle: IEditorOption; mouseWheelScrollSensitivity: IEditorOption; mouseWheelZoom: IEditorOption; multiCursorMergeOverlapping: IEditorOption; - multiCursorModifier: IEditorOption; - multiCursorPaste: IEditorOption; + multiCursorModifier: IEditorOption; + multiCursorPaste: IEditorOption; occurrencesHighlight: IEditorOption; overviewRulerBorder: IEditorOption; overviewRulerLanes: IEditorOption; parameterHints: IEditorOption; + peekWidgetFocusInlineEditor: IEditorOption; quickSuggestions: IEditorOption; quickSuggestionsDelay: IEditorOption; readOnly: IEditorOption; renderControlCharacters: IEditorOption; renderIndentGuides: IEditorOption; renderFinalNewline: IEditorOption; - renderLineHighlight: IEditorOption; - renderWhitespace: IEditorOption; + renderLineHighlight: IEditorOption; + renderWhitespace: IEditorOption; revealHorizontalRightPadding: IEditorOption; roundedSelection: IEditorOption; rulers: IEditorOption; @@ -3858,26 +3862,26 @@ declare namespace monaco.editor { selectionClipboard: IEditorOption; selectionHighlight: IEditorOption; selectOnLineNumbers: IEditorOption; - showFoldingControls: IEditorOption; + showFoldingControls: IEditorOption; showUnused: IEditorOption; - snippetSuggestions: IEditorOption; + snippetSuggestions: IEditorOption; smoothScrolling: IEditorOption; stopRenderingLineAfter: IEditorOption; suggest: IEditorOption; suggestFontSize: IEditorOption; suggestLineHeight: IEditorOption; suggestOnTriggerCharacters: IEditorOption; - suggestSelection: IEditorOption; - tabCompletion: IEditorOption; + suggestSelection: IEditorOption; + tabCompletion: IEditorOption; useTabStops: IEditorOption; wordSeparators: IEditorOption; - wordWrap: IEditorOption; + wordWrap: IEditorOption; wordWrapBreakAfterCharacters: IEditorOption; wordWrapBreakBeforeCharacters: IEditorOption; - wordWrapBreakObtrusiveCharacters: IEditorOption; wordWrapColumn: IEditorOption; wordWrapMinified: IEditorOption; wrappingIndent: IEditorOption; + wrappingAlgorithm: IEditorOption; editorClassName: IEditorOption; pixelRatio: IEditorOption; tabFocusMode: IEditorOption; From b713669535346ae36f6ac91c845daf1966bc2301 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 14 Jan 2020 16:33:19 +0100 Subject: [PATCH 282/315] Rerun monacodts again --- .../common/standalone/standaloneEnums.ts | 51 +++++++++--------- src/vs/monaco.d.ts | 52 ++++++++++--------- 2 files changed, 53 insertions(+), 50 deletions(-) diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 3d436fb75bcf8..0d02283d1bb44 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -247,31 +247,32 @@ export enum EditorOption { selectionClipboard = 79, selectionHighlight = 80, selectOnLineNumbers = 81, - showFoldingControls = 82, - showUnused = 83, - snippetSuggestions = 84, - smoothScrolling = 85, - stopRenderingLineAfter = 86, - suggest = 87, - suggestFontSize = 88, - suggestLineHeight = 89, - suggestOnTriggerCharacters = 90, - suggestSelection = 91, - tabCompletion = 92, - useTabStops = 93, - wordSeparators = 94, - wordWrap = 95, - wordWrapBreakAfterCharacters = 96, - wordWrapBreakBeforeCharacters = 97, - wordWrapColumn = 98, - wordWrapMinified = 99, - wrappingIndent = 100, - wrappingAlgorithm = 101, - editorClassName = 102, - pixelRatio = 103, - tabFocusMode = 104, - layoutInfo = 105, - wrappingInfo = 106 + semanticHighlighting = 82, + showFoldingControls = 83, + showUnused = 84, + snippetSuggestions = 85, + smoothScrolling = 86, + stopRenderingLineAfter = 87, + suggest = 88, + suggestFontSize = 89, + suggestLineHeight = 90, + suggestOnTriggerCharacters = 91, + suggestSelection = 92, + tabCompletion = 93, + useTabStops = 94, + wordSeparators = 95, + wordWrap = 96, + wordWrapBreakAfterCharacters = 97, + wordWrapBreakBeforeCharacters = 98, + wordWrapColumn = 99, + wordWrapMinified = 100, + wrappingIndent = 101, + wrappingAlgorithm = 102, + editorClassName = 103, + pixelRatio = 104, + tabFocusMode = 105, + layoutInfo = 106, + wrappingInfo = 107 } /** diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 905bcd9cdc835..3a6307fbbdae9 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3753,31 +3753,32 @@ declare namespace monaco.editor { selectionClipboard = 79, selectionHighlight = 80, selectOnLineNumbers = 81, - showFoldingControls = 82, - showUnused = 83, - snippetSuggestions = 84, - smoothScrolling = 85, - stopRenderingLineAfter = 86, - suggest = 87, - suggestFontSize = 88, - suggestLineHeight = 89, - suggestOnTriggerCharacters = 90, - suggestSelection = 91, - tabCompletion = 92, - useTabStops = 93, - wordSeparators = 94, - wordWrap = 95, - wordWrapBreakAfterCharacters = 96, - wordWrapBreakBeforeCharacters = 97, - wordWrapColumn = 98, - wordWrapMinified = 99, - wrappingIndent = 100, - wrappingAlgorithm = 101, - editorClassName = 102, - pixelRatio = 103, - tabFocusMode = 104, - layoutInfo = 105, - wrappingInfo = 106 + semanticHighlighting = 82, + showFoldingControls = 83, + showUnused = 84, + snippetSuggestions = 85, + smoothScrolling = 86, + stopRenderingLineAfter = 87, + suggest = 88, + suggestFontSize = 89, + suggestLineHeight = 90, + suggestOnTriggerCharacters = 91, + suggestSelection = 92, + tabCompletion = 93, + useTabStops = 94, + wordSeparators = 95, + wordWrap = 96, + wordWrapBreakAfterCharacters = 97, + wordWrapBreakBeforeCharacters = 98, + wordWrapColumn = 99, + wordWrapMinified = 100, + wrappingIndent = 101, + wrappingAlgorithm = 102, + editorClassName = 103, + pixelRatio = 104, + tabFocusMode = 105, + layoutInfo = 106, + wrappingInfo = 107 } export const EditorOptions: { acceptSuggestionOnCommitCharacter: IEditorOption; @@ -3862,6 +3863,7 @@ declare namespace monaco.editor { selectionClipboard: IEditorOption; selectionHighlight: IEditorOption; selectOnLineNumbers: IEditorOption; + semanticHighlighting: IEditorOption; showFoldingControls: IEditorOption; showUnused: IEditorOption; snippetSuggestions: IEditorOption; From b7381d341dce10f6e47b1b19410386932cf29104 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 14 Jan 2020 16:35:46 +0100 Subject: [PATCH 283/315] fix repl --- src/vs/workbench/contrib/debug/browser/repl.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 1503ed1a983e7..d9381e21706ae 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -103,7 +103,7 @@ export class Repl extends ViewPane implements IPrivateReplService, IHistoryNavig constructor( options: IViewPaneOptions, @IDebugService private readonly debugService: IDebugService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IStorageService private readonly storageService: IStorageService, @IThemeService protected themeService: IThemeService, @IModelService private readonly modelService: IModelService, @@ -116,7 +116,7 @@ export class Repl extends ViewPane implements IPrivateReplService, IHistoryNavig @IEditorService private readonly editorService: IEditorService, @IKeybindingService keybindingService: IKeybindingService ) { - super({ ...(options as IViewPaneOptions), id: REPL_VIEW_ID, ariaHeaderLabel: localize('debugConsole', "Debug Console") }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewPaneOptions), id: REPL_VIEW_ID, ariaHeaderLabel: localize('debugConsole', "Debug Console") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); this.history = new HistoryNavigator(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')), 50); codeEditorService.registerDecorationType(DECORATION_KEY, {}); From 31a85df309f9e6958382c4d9b6d8a6e9594b9b16 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 14 Jan 2020 16:46:25 +0100 Subject: [PATCH 284/315] Avoid memory leak where the _markersData never has its elements removed --- src/vs/editor/common/services/markerDecorationsServiceImpl.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts index 44fd54c6c6d5b..d37fbe83d1ef0 100644 --- a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts +++ b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts @@ -38,7 +38,9 @@ class MarkerDecorations extends Disposable { } public update(markers: IMarker[], newDecorations: IModelDeltaDecoration[]): void { - const ids = this.model.deltaDecorations(keys(this._markersData), newDecorations); + const oldIds = keys(this._markersData); + this._markersData.clear(); + const ids = this.model.deltaDecorations(oldIds, newDecorations); for (let index = 0; index < ids.length; index++) { this._markersData.set(ids[index], markers[index]); } From 6133239792e1ad1dade13ce4b64cfe6a2599c749 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 14 Jan 2020 16:58:32 +0100 Subject: [PATCH 285/315] :lipstick: --- src/vs/editor/common/config/commonEditorConfig.ts | 6 +++--- .../editor/common/services/editorSimpleWorker.ts | 4 ++-- .../common/services/editorWorkerServiceImpl.ts | 6 +++--- src/vs/editor/common/viewLayout/viewLayout.ts | 14 +++++++------- src/vs/editor/common/viewModel/viewModelImpl.ts | 12 ++++++------ .../contrib/bracketMatching/bracketMatching.ts | 4 ++-- .../editor/contrib/codelens/codelensController.ts | 4 ++-- .../editor/contrib/comment/blockCommentCommand.ts | 10 +++++----- .../editor/contrib/comment/lineCommentCommand.ts | 12 ++++++------ src/vs/editor/contrib/dnd/dnd.ts | 6 +++--- src/vs/editor/contrib/dnd/dragAndDropCommand.ts | 8 ++++---- src/vs/editor/contrib/find/findController.ts | 4 ++-- src/vs/editor/contrib/find/findModel.ts | 8 ++++---- src/vs/editor/contrib/find/replaceAllCommand.ts | 8 ++++---- src/vs/editor/contrib/format/format.ts | 6 +++--- src/vs/editor/contrib/format/formatActions.ts | 6 +++--- src/vs/editor/contrib/gotoError/gotoError.ts | 4 ++-- .../gotoSymbol/link/goToDefinitionAtPosition.ts | 4 ++-- .../gotoSymbol/peek/referencesController.ts | 4 ++-- .../contrib/gotoSymbol/peek/referencesWidget.ts | 6 +++--- .../inPlaceReplace/inPlaceReplaceCommand.ts | 8 ++++---- .../contrib/linesOperations/copyLinesCommand.ts | 8 ++++---- .../contrib/linesOperations/sortLinesCommand.ts | 8 ++++---- src/vs/editor/contrib/links/links.ts | 4 ++-- src/vs/editor/contrib/message/messageController.ts | 6 +++--- .../contrib/wordHighlighter/wordHighlighter.ts | 4 ++-- .../browser/quickOpen/editorQuickOpen.ts | 8 ++++---- .../standalone/browser/quickOpen/gotoLine.ts | 10 +++++----- src/vs/editor/standalone/browser/simpleServices.ts | 8 ++++---- .../editor/standalone/browser/standaloneEditor.ts | 6 +++--- src/vs/editor/test/browser/testCodeEditor.ts | 6 +++--- src/vs/editor/test/browser/testCommand.ts | 10 +++++----- src/vs/workbench/api/browser/mainThreadEditor.ts | 12 ++++++------ .../browser/keybindingsEditorContribution.ts | 4 ++-- .../contrib/snippets/browser/tabCompletion.ts | 4 ++-- 35 files changed, 121 insertions(+), 121 deletions(-) diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index cee6665029fa4..a0b1708567968 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -11,7 +11,7 @@ import * as arrays from 'vs/base/common/arrays'; import { IEditorOptions, editorOptionsRegistry, ValidatedEditorOptions, IEnvironmentalOptions, IComputedEditorOptions, ConfigurationChangedEvent, EDITOR_MODEL_DEFAULTS, EditorOption, FindComputedEditorOptionValueById } from 'vs/editor/common/config/editorOptions'; import { EditorZoom } from 'vs/editor/common/config/editorZoom'; import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IConfiguration, IDimension } from 'vs/editor/common/editorCommon'; import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; @@ -278,7 +278,7 @@ function deepCloneAndMigrateOptions(_options: IEditorOptions): IEditorOptions { return options; } -export abstract class CommonEditorConfiguration extends Disposable implements editorCommon.IConfiguration { +export abstract class CommonEditorConfiguration extends Disposable implements IConfiguration { private _onDidChange = this._register(new Emitter()); public readonly onDidChange: Event = this._onDidChange.event; @@ -308,7 +308,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed this._register(TabFocus.onDidChangeTabFocus(_ => this._recomputeOptions())); } - public observeReferenceElement(dimension?: editorCommon.IDimension): void { + public observeReferenceElement(dimension?: IDimension): void { } public dispose(): void { diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index 4960aa3984a1d..3e5361ce764c6 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -13,7 +13,7 @@ import { IRequestHandler } from 'vs/base/common/worker/simpleWorker'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { DiffComputer } from 'vs/editor/common/diff/diffComputer'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IChange } from 'vs/editor/common/editorCommon'; import { EndOfLineSequence, IWordAtPosition } from 'vs/editor/common/model'; import { IModelChangedEvent, MirrorTextModel as BaseMirrorModel } from 'vs/editor/common/model/mirrorTextModel'; import { ensureValidWordDefinition, getWordAtText } from 'vs/editor/common/model/wordHelper'; @@ -419,7 +419,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { return true; } - public async computeDirtyDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean): Promise { + public async computeDirtyDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean): Promise { let original = this._getModel(originalUrl); let modified = this._getModel(modifiedUrl); if (!original || !modified) { diff --git a/src/vs/editor/common/services/editorWorkerServiceImpl.ts b/src/vs/editor/common/services/editorWorkerServiceImpl.ts index e1e5804fdc044..1f2df5a96b7a9 100644 --- a/src/vs/editor/common/services/editorWorkerServiceImpl.ts +++ b/src/vs/editor/common/services/editorWorkerServiceImpl.ts @@ -10,7 +10,7 @@ import { SimpleWorkerClient, logOnceWebWorkerWarning, IWorkerClient } from 'vs/b import { DefaultWorkerFactory } from 'vs/base/worker/defaultWorkerFactory'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IChange } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; @@ -90,7 +90,7 @@ export class EditorWorkerServiceImpl extends Disposable implements IEditorWorker return (canSyncModel(this._modelService, original) && canSyncModel(this._modelService, modified)); } - public computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise { + public computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise { return this._workerManager.withWorker().then(client => client.computeDirtyDiff(original, modified, ignoreTrimWhitespace)); } @@ -437,7 +437,7 @@ export class EditorWorkerClient extends Disposable { }); } - public computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise { + public computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise { return this._withSyncedResources([original, modified]).then(proxy => { return proxy.computeDirtyDiff(original.toString(), modified.toString(), ignoreTrimWhitespace); }); diff --git a/src/vs/editor/common/viewLayout/viewLayout.ts b/src/vs/editor/common/viewLayout/viewLayout.ts index 115e4a1c85b50..6ab6d10098b76 100644 --- a/src/vs/editor/common/viewLayout/viewLayout.ts +++ b/src/vs/editor/common/viewLayout/viewLayout.ts @@ -5,9 +5,9 @@ import { Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { IScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable'; +import { IScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility, INewScrollPosition } from 'vs/base/common/scrollable'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IConfiguration } from 'vs/editor/common/editorCommon'; import { LinesLayout, IEditorWhitespace, IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout'; import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; import { IViewLayout, IViewWhitespaceViewportData, Viewport } from 'vs/editor/common/viewModel/viewModel'; @@ -16,13 +16,13 @@ const SMOOTH_SCROLLING_TIME = 125; export class ViewLayout extends Disposable implements IViewLayout { - private readonly _configuration: editorCommon.IConfiguration; + private readonly _configuration: IConfiguration; private readonly _linesLayout: LinesLayout; public readonly scrollable: Scrollable; public readonly onDidScroll: Event; - constructor(configuration: editorCommon.IConfiguration, lineCount: number, scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable) { + constructor(configuration: IConfiguration, lineCount: number, scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable) { super(); this._configuration = configuration; @@ -253,15 +253,15 @@ export class ViewLayout extends Disposable implements IViewLayout { return currentScrollPosition.scrollTop; } - public validateScrollPosition(scrollPosition: editorCommon.INewScrollPosition): IScrollPosition { + public validateScrollPosition(scrollPosition: INewScrollPosition): IScrollPosition { return this.scrollable.validateScrollPosition(scrollPosition); } - public setScrollPositionNow(position: editorCommon.INewScrollPosition): void { + public setScrollPositionNow(position: INewScrollPosition): void { this.scrollable.setScrollPositionNow(position); } - public setScrollPositionSmooth(position: editorCommon.INewScrollPosition): void { + public setScrollPositionSmooth(position: INewScrollPosition): void { this.scrollable.setScrollPositionSmooth(position); } diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 6bba786da592d..75842518ee98c 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -9,7 +9,7 @@ import * as strings from 'vs/base/common/strings'; import { ConfigurationChangedEvent, EDITOR_FONT_DEFAULTS, EditorOption } from 'vs/editor/common/config/editorOptions'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IConfiguration, IViewState } from 'vs/editor/common/editorCommon'; import { EndOfLinePreference, IActiveIndentGuideInfo, ITextModel, TrackedRangeStickiness, TextModelResolvedOptions } from 'vs/editor/common/model'; import { ModelDecorationOverviewRulerOptions, ModelDecorationMinimapOptions } from 'vs/editor/common/model/textModel'; import * as textModelEvents from 'vs/editor/common/model/textModelEvents'; @@ -30,7 +30,7 @@ const USE_IDENTITY_LINES_COLLECTION = true; export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel { private readonly editorId: number; - private readonly configuration: editorCommon.IConfiguration; + private readonly configuration: IConfiguration; private readonly model: ITextModel; private readonly _tokenizeViewportSoon: RunOnceScheduler; private hasFocus: boolean; @@ -44,7 +44,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel constructor( editorId: number, - configuration: editorCommon.IConfiguration, + configuration: IConfiguration, model: ITextModel, domLineBreaksComputerFactory: ILineBreaksComputerFactory, monospaceLineBreaksComputerFactory: ILineBreaksComputerFactory, @@ -447,7 +447,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel ); } - public saveState(): editorCommon.IViewState { + public saveState(): IViewState { const compatViewState = this.viewLayout.saveState(); const scrollTop = compatViewState.scrollTop; @@ -462,7 +462,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel }; } - public reduceRestoreState(state: editorCommon.IViewState): { scrollLeft: number; scrollTop: number; } { + public reduceRestoreState(state: IViewState): { scrollLeft: number; scrollTop: number; } { if (typeof state.firstPosition === 'undefined') { // This is a view state serialized by an older version return this._reduceRestoreStateCompatibility(state); @@ -477,7 +477,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel }; } - private _reduceRestoreStateCompatibility(state: editorCommon.IViewState): { scrollLeft: number; scrollTop: number; } { + private _reduceRestoreStateCompatibility(state: IViewState): { scrollLeft: number; scrollTop: number; } { return { scrollLeft: state.scrollLeft, scrollTop: state.scrollTopWithoutViewZones! diff --git a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts index a669b572023cc..5e150312ce66f 100644 --- a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts +++ b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts @@ -13,7 +13,7 @@ import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorCon import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IModelDeltaDecoration, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; @@ -103,7 +103,7 @@ class BracketsData { } } -export class BracketMatchingController extends Disposable implements editorCommon.IEditorContribution { +export class BracketMatchingController extends Disposable implements IEditorContribution { public static readonly ID = 'editor.contrib.bracketMatchingController'; public static get(editor: ICodeEditor): BracketMatchingController { diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index 40fca0ab3bde5..7a644f1bfe4ef 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -9,7 +9,7 @@ import { toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle import { StableEditorScrollState } from 'vs/editor/browser/core/editorState'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; import { CodeLensProviderRegistry, CodeLens } from 'vs/editor/common/modes'; import { CodeLensModel, getCodeLensData, CodeLensItem } from 'vs/editor/contrib/codelens/codelens'; @@ -21,7 +21,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { createStyleSheet } from 'vs/base/browser/dom'; import { hash } from 'vs/base/common/hash'; -export class CodeLensContribution implements editorCommon.IEditorContribution { +export class CodeLensContribution implements IEditorContribution { public static readonly ID: string = 'css.editor.codeLens'; diff --git a/src/vs/editor/contrib/comment/blockCommentCommand.ts b/src/vs/editor/contrib/comment/blockCommentCommand.ts index 5c5273c9c3595..161c5878611fb 100644 --- a/src/vs/editor/contrib/comment/blockCommentCommand.ts +++ b/src/vs/editor/contrib/comment/blockCommentCommand.ts @@ -8,11 +8,11 @@ import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { ICommand, IEditOperationBuilder, ICursorStateComputerData } from 'vs/editor/common/editorCommon'; import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; -export class BlockCommentCommand implements editorCommon.ICommand { +export class BlockCommentCommand implements ICommand { private readonly _selection: Selection; private _usedEndToken: string | null; @@ -53,7 +53,7 @@ export class BlockCommentCommand implements editorCommon.ICommand { return true; } - private _createOperationsForBlockComment(selection: Range, startToken: string, endToken: string, model: ITextModel, builder: editorCommon.IEditOperationBuilder): void { + private _createOperationsForBlockComment(selection: Range, startToken: string, endToken: string, model: ITextModel, builder: IEditOperationBuilder): void { const startLineNumber = selection.startLineNumber; const startColumn = selection.startColumn; const endLineNumber = selection.endLineNumber; @@ -164,7 +164,7 @@ export class BlockCommentCommand implements editorCommon.ICommand { return res; } - public getEditOperations(model: ITextModel, builder: editorCommon.IEditOperationBuilder): void { + public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void { const startLineNumber = this._selection.startLineNumber; const startColumn = this._selection.startColumn; @@ -179,7 +179,7 @@ export class BlockCommentCommand implements editorCommon.ICommand { this._createOperationsForBlockComment(this._selection, config.blockCommentStartToken, config.blockCommentEndToken, model, builder); } - public computeCursorState(model: ITextModel, helper: editorCommon.ICursorStateComputerData): Selection { + public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { const inverseEditOperations = helper.getInverseEditOperations(); if (inverseEditOperations.length === 2) { const startTokenEditOperation = inverseEditOperations[0]; diff --git a/src/vs/editor/contrib/comment/lineCommentCommand.ts b/src/vs/editor/contrib/comment/lineCommentCommand.ts index 2eecd5ae2c51e..f6a3cc40766c4 100644 --- a/src/vs/editor/contrib/comment/lineCommentCommand.ts +++ b/src/vs/editor/contrib/comment/lineCommentCommand.ts @@ -9,7 +9,7 @@ import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { ICommand, IEditOperationBuilder, ICursorStateComputerData } from 'vs/editor/common/editorCommon'; import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { BlockCommentCommand } from 'vs/editor/contrib/comment/blockCommentCommand'; @@ -47,7 +47,7 @@ export const enum Type { ForceRemove = 2 } -export class LineCommentCommand implements editorCommon.ICommand { +export class LineCommentCommand implements ICommand { private readonly _selection: Selection; private _selectionId: string | null; @@ -187,7 +187,7 @@ export class LineCommentCommand implements editorCommon.ICommand { /** * Given a successful analysis, execute either insert line comments, either remove line comments */ - private _executeLineComments(model: ISimpleModel, builder: editorCommon.IEditOperationBuilder, data: IPreflightDataSupported, s: Selection): void { + private _executeLineComments(model: ISimpleModel, builder: IEditOperationBuilder, data: IPreflightDataSupported, s: Selection): void { let ops: IIdentifiedSingleEditOperation[]; @@ -266,7 +266,7 @@ export class LineCommentCommand implements editorCommon.ICommand { /** * Given an unsuccessful analysis, delegate to the block comment command */ - private _executeBlockComment(model: ITextModel, builder: editorCommon.IEditOperationBuilder, s: Selection): void { + private _executeBlockComment(model: ITextModel, builder: IEditOperationBuilder, s: Selection): void { model.tokenizeIfCheap(s.startLineNumber); let languageId = model.getLanguageIdAtPosition(s.startLineNumber, 1); let config = LanguageConfigurationRegistry.getComments(languageId); @@ -307,7 +307,7 @@ export class LineCommentCommand implements editorCommon.ICommand { } } - public getEditOperations(model: ITextModel, builder: editorCommon.IEditOperationBuilder): void { + public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void { let s = this._selection; this._moveEndPositionDown = false; @@ -325,7 +325,7 @@ export class LineCommentCommand implements editorCommon.ICommand { return this._executeBlockComment(model, builder, s); } - public computeCursorState(model: ITextModel, helper: editorCommon.ICursorStateComputerData): Selection { + public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { let result = helper.getTrackedSelection(this._selectionId!); if (this._moveEndPositionDown) { diff --git a/src/vs/editor/contrib/dnd/dnd.ts b/src/vs/editor/contrib/dnd/dnd.ts index 6d4226cbe5ea5..ea3cda654bc32 100644 --- a/src/vs/editor/contrib/dnd/dnd.ts +++ b/src/vs/editor/contrib/dnd/dnd.ts @@ -10,7 +10,7 @@ import { isMacintosh } from 'vs/base/common/platform'; import { KeyCode } from 'vs/base/common/keyCodes'; import { ICodeEditor, IEditorMouseEvent, IMouseTarget, MouseTargetType, IPartialEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; @@ -29,7 +29,7 @@ function hasTriggerModifier(e: IKeyboardEvent | IMouseEvent): boolean { } } -export class DragAndDropController extends Disposable implements editorCommon.IEditorContribution { +export class DragAndDropController extends Disposable implements IEditorContribution { public static readonly ID = 'editor.contrib.dragAndDrop'; @@ -201,7 +201,7 @@ export class DragAndDropController extends Disposable implements editorCommon.IE }]; this._dndDecorationIds = this._editor.deltaDecorations(this._dndDecorationIds, newDecorations); - this._editor.revealPosition(position, editorCommon.ScrollType.Immediate); + this._editor.revealPosition(position, ScrollType.Immediate); } private _removeDecoration(): void { diff --git a/src/vs/editor/contrib/dnd/dragAndDropCommand.ts b/src/vs/editor/contrib/dnd/dragAndDropCommand.ts index 8d1d8a32b020a..e6d7b427ae59d 100644 --- a/src/vs/editor/contrib/dnd/dragAndDropCommand.ts +++ b/src/vs/editor/contrib/dnd/dragAndDropCommand.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { ICommand, IEditOperationBuilder, ICursorStateComputerData } from 'vs/editor/common/editorCommon'; import { Selection } from 'vs/editor/common/core/selection'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; -export class DragAndDropCommand implements editorCommon.ICommand { +export class DragAndDropCommand implements ICommand { private readonly selection: Selection; private readonly targetPosition: Position; @@ -24,7 +24,7 @@ export class DragAndDropCommand implements editorCommon.ICommand { this.targetSelection = null; } - public getEditOperations(model: ITextModel, builder: editorCommon.IEditOperationBuilder): void { + public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void { let text = model.getValueInRange(this.selection); if (!this.copy) { builder.addEditOperation(this.selection, null); @@ -102,7 +102,7 @@ export class DragAndDropCommand implements editorCommon.ICommand { } } - public computeCursorState(model: ITextModel, helper: editorCommon.ICursorStateComputerData): Selection { + public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { return this.targetSelection!; } } diff --git a/src/vs/editor/contrib/find/findController.ts b/src/vs/editor/contrib/find/findController.ts index fbd9bf5500b60..fee09b8429d8e 100644 --- a/src/vs/editor/contrib/find/findController.ts +++ b/src/vs/editor/contrib/find/findController.ts @@ -10,7 +10,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, EditorCommand, ServicesAccessor, registerEditorAction, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { CONTEXT_FIND_INPUT_FOCUSED, CONTEXT_FIND_WIDGET_VISIBLE, FIND_IDS, FindModelBoundToEditorModel, ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleSearchScopeKeybinding, ToggleWholeWordKeybinding, CONTEXT_REPLACE_INPUT_FOCUSED } from 'vs/editor/contrib/find/findModel'; import { FindOptionsWidget } from 'vs/editor/contrib/find/findOptionsWidget'; @@ -68,7 +68,7 @@ export interface IFindStartOptions { updateSearchScope: boolean; } -export class CommonFindController extends Disposable implements editorCommon.IEditorContribution { +export class CommonFindController extends Disposable implements IEditorContribution { public static readonly ID = 'editor.contrib.findController'; diff --git a/src/vs/editor/contrib/find/findModel.ts b/src/vs/editor/contrib/find/findModel.ts index da67f86eb78ba..0653ac4f5c4cd 100644 --- a/src/vs/editor/contrib/find/findModel.ts +++ b/src/vs/editor/contrib/find/findModel.ts @@ -13,7 +13,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { Constants } from 'vs/base/common/uint'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { ScrollType, ICommand } from 'vs/editor/common/editorCommon'; import { EndOfLinePreference, FindMatch, ITextModel } from 'vs/editor/common/model'; import { SearchParams } from 'vs/editor/common/model/textModelSearch'; import { FindDecorations } from 'vs/editor/contrib/find/findDecorations'; @@ -209,7 +209,7 @@ export class FindModelBoundToEditorModel { let findScope = this._decorations.getFindScope(); if (findScope) { // Reveal the selection so user is reminded that 'selection find' is on. - this._editor.revealRangeInCenterIfOutsideViewport(findScope, editorCommon.ScrollType.Smooth); + this._editor.revealRangeInCenterIfOutsideViewport(findScope, ScrollType.Smooth); } return true; } @@ -225,7 +225,7 @@ export class FindModelBoundToEditorModel { ); this._editor.setSelection(match); - this._editor.revealRangeInCenterIfOutsideViewport(match, editorCommon.ScrollType.Smooth); + this._editor.revealRangeInCenterIfOutsideViewport(match, ScrollType.Smooth); } private _prevSearchPosition(before: Position) { @@ -536,7 +536,7 @@ export class FindModelBoundToEditorModel { this._editor.setSelections(selections); } - private _executeEditorCommand(source: string, command: editorCommon.ICommand): void { + private _executeEditorCommand(source: string, command: ICommand): void { try { this._ignoreModelContentChanged = true; this._editor.pushUndoStop(); diff --git a/src/vs/editor/contrib/find/replaceAllCommand.ts b/src/vs/editor/contrib/find/replaceAllCommand.ts index bdf2942f014af..fb2de090a52a7 100644 --- a/src/vs/editor/contrib/find/replaceAllCommand.ts +++ b/src/vs/editor/contrib/find/replaceAllCommand.ts @@ -5,7 +5,7 @@ import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { ICommand, IEditOperationBuilder, ICursorStateComputerData } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; interface IEditOperation { @@ -13,7 +13,7 @@ interface IEditOperation { text: string; } -export class ReplaceAllCommand implements editorCommon.ICommand { +export class ReplaceAllCommand implements ICommand { private readonly _editorSelection: Selection; private _trackedEditorSelectionId: string | null; @@ -27,7 +27,7 @@ export class ReplaceAllCommand implements editorCommon.ICommand { this._trackedEditorSelectionId = null; } - public getEditOperations(model: ITextModel, builder: editorCommon.IEditOperationBuilder): void { + public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void { if (this._ranges.length > 0) { // Collect all edit operations let ops: IEditOperation[] = []; @@ -66,7 +66,7 @@ export class ReplaceAllCommand implements editorCommon.ICommand { this._trackedEditorSelectionId = builder.trackSelection(this._editorSelection); } - public computeCursorState(model: ITextModel, helper: editorCommon.ICursorStateComputerData): Selection { + public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { return helper.getTrackedSelection(this._trackedEditorSelectionId!); } } diff --git a/src/vs/editor/contrib/format/format.ts b/src/vs/editor/contrib/format/format.ts index 58602a5bfafa0..6d87263ad5157 100644 --- a/src/vs/editor/contrib/format/format.ts +++ b/src/vs/editor/contrib/format/format.ts @@ -14,7 +14,7 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { ScrollType } from 'vs/editor/common/editorCommon'; import { ISingleEditOperation, ITextModel } from 'vs/editor/common/model'; import { DocumentFormattingEditProvider, DocumentFormattingEditProviderRegistry, DocumentRangeFormattingEditProvider, DocumentRangeFormattingEditProviderRegistry, FormattingOptions, OnTypeFormattingEditProviderRegistry, TextEdit } from 'vs/editor/common/modes'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; @@ -182,7 +182,7 @@ export async function formatDocumentRangeWithProvider( alertFormattingEdits(edits); editorOrModel.pushUndoStop(); editorOrModel.focus(); - editorOrModel.revealPositionInCenterIfOutsideViewport(editorOrModel.getPosition(), editorCommon.ScrollType.Immediate); + editorOrModel.revealPositionInCenterIfOutsideViewport(editorOrModel.getPosition(), ScrollType.Immediate); } else { // use model to apply edits @@ -272,7 +272,7 @@ export async function formatDocumentWithProvider( alertFormattingEdits(edits); editorOrModel.pushUndoStop(); editorOrModel.focus(); - editorOrModel.revealPositionInCenterIfOutsideViewport(editorOrModel.getPosition(), editorCommon.ScrollType.Immediate); + editorOrModel.revealPositionInCenterIfOutsideViewport(editorOrModel.getPosition(), ScrollType.Immediate); } } else { diff --git a/src/vs/editor/contrib/format/formatActions.ts b/src/vs/editor/contrib/format/formatActions.ts index 879000c9140bd..61050ac167b10 100644 --- a/src/vs/editor/contrib/format/formatActions.ts +++ b/src/vs/editor/contrib/format/formatActions.ts @@ -12,7 +12,7 @@ import { EditorAction, registerEditorAction, registerEditorContribution, Service import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CharacterSet } from 'vs/editor/common/core/characterClassifier'; import { Range } from 'vs/editor/common/core/range'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { DocumentRangeFormattingEditProviderRegistry, OnTypeFormattingEditProviderRegistry } from 'vs/editor/common/modes'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; @@ -26,7 +26,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { onUnexpectedError } from 'vs/base/common/errors'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; -class FormatOnType implements editorCommon.IEditorContribution { +class FormatOnType implements IEditorContribution { public static readonly ID = 'editor.contrib.autoFormat'; @@ -149,7 +149,7 @@ class FormatOnType implements editorCommon.IEditorContribution { } } -class FormatOnPaste implements editorCommon.IEditorContribution { +class FormatOnPaste implements IEditorContribution { public static readonly ID = 'editor.contrib.formatOnPaste'; diff --git a/src/vs/editor/contrib/gotoError/gotoError.ts b/src/vs/editor/contrib/gotoError/gotoError.ts index 11e2b47051053..10f48cad8bb55 100644 --- a/src/vs/editor/contrib/gotoError/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/gotoError.ts @@ -12,7 +12,7 @@ import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/cont import { IMarker, IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { registerEditorAction, registerEditorContribution, ServicesAccessor, IActionOptions, EditorAction, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -189,7 +189,7 @@ class MarkerModel { } } -export class MarkerController implements editorCommon.IEditorContribution { +export class MarkerController implements IEditorContribution { public static readonly ID = 'editor.contrib.markerController'; diff --git a/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts index 240bf4c52df2b..d3678e527d920 100644 --- a/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts +++ b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts @@ -11,7 +11,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { IModeService } from 'vs/editor/common/services/modeService'; import { Range, IRange } from 'vs/editor/common/core/range'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { DefinitionProviderRegistry, LocationLink } from 'vs/editor/common/modes'; import { ICodeEditor, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; @@ -28,7 +28,7 @@ import { Position } from 'vs/editor/common/core/position'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -export class GotoDefinitionAtPositionEditorContribution implements editorCommon.IEditorContribution { +export class GotoDefinitionAtPositionEditorContribution implements IEditorContribution { public static readonly ID = 'editor.contrib.gotodefinitionatposition'; static readonly MAX_SOURCE_PREVIEW_LINES = 8; diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts index be8af636d69ef..60f7428d6cf80 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts @@ -11,7 +11,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { IContextKey, IContextKeyService, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ReferencesModel, OneReference } from '../referencesModel'; import { ReferenceWidget, LayoutData } from './referencesWidget'; @@ -29,7 +29,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; export const ctxReferenceSearchVisible = new RawContextKey('referenceSearchVisible', false); -export abstract class ReferencesController implements editorCommon.IEditorContribution { +export abstract class ReferencesController implements IEditorContribution { static readonly ID = 'editor.contrib.referencesController'; diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts index 10211eb0abc99..f4d1f08555b80 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts @@ -17,7 +17,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IRange, Range } from 'vs/editor/common/core/range'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { ScrollType } from 'vs/editor/common/editorCommon'; import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions, TextModel } from 'vs/editor/common/model/textModel'; import { Location } from 'vs/editor/common/modes'; @@ -247,7 +247,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget { } show(where: IRange) { - this.editor.revealRangeInCenterIfOutsideViewport(where, editorCommon.ScrollType.Smooth); + this.editor.revealRangeInCenterIfOutsideViewport(where, ScrollType.Smooth); super.show(where, this.layoutData.heightInLines || 18); } @@ -526,7 +526,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget { // show in editor const model = ref.object; if (model) { - const scrollType = this._preview.getModel() === model.textEditorModel ? editorCommon.ScrollType.Smooth : editorCommon.ScrollType.Immediate; + const scrollType = this._preview.getModel() === model.textEditorModel ? ScrollType.Smooth : ScrollType.Immediate; const sel = Range.lift(reference.range).collapseToStart(); this._previewModelReference = ref; this._preview.setModel(model.textEditorModel); diff --git a/src/vs/editor/contrib/inPlaceReplace/inPlaceReplaceCommand.ts b/src/vs/editor/contrib/inPlaceReplace/inPlaceReplaceCommand.ts index a64be8e0109de..0d069f4f6c372 100644 --- a/src/vs/editor/contrib/inPlaceReplace/inPlaceReplaceCommand.ts +++ b/src/vs/editor/contrib/inPlaceReplace/inPlaceReplaceCommand.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { Selection } from 'vs/editor/common/core/selection'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { ICommand, IEditOperationBuilder, ICursorStateComputerData } from 'vs/editor/common/editorCommon'; import { Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; -export class InPlaceReplaceCommand implements editorCommon.ICommand { +export class InPlaceReplaceCommand implements ICommand { private readonly _editRange: Range; private readonly _originalSelection: Selection; @@ -20,11 +20,11 @@ export class InPlaceReplaceCommand implements editorCommon.ICommand { this._text = text; } - public getEditOperations(model: ITextModel, builder: editorCommon.IEditOperationBuilder): void { + public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void { builder.addTrackedEditOperation(this._editRange, this._text); } - public computeCursorState(model: ITextModel, helper: editorCommon.ICursorStateComputerData): Selection { + public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { const inverseEditOperations = helper.getInverseEditOperations(); const srcRange = inverseEditOperations[0].range; diff --git a/src/vs/editor/contrib/linesOperations/copyLinesCommand.ts b/src/vs/editor/contrib/linesOperations/copyLinesCommand.ts index 3d20782006186..6b76a75b7632f 100644 --- a/src/vs/editor/contrib/linesOperations/copyLinesCommand.ts +++ b/src/vs/editor/contrib/linesOperations/copyLinesCommand.ts @@ -5,10 +5,10 @@ import { Range } from 'vs/editor/common/core/range'; import { Selection, SelectionDirection } from 'vs/editor/common/core/selection'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { ICommand, IEditOperationBuilder, ICursorStateComputerData } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -export class CopyLinesCommand implements editorCommon.ICommand { +export class CopyLinesCommand implements ICommand { private readonly _selection: Selection; private readonly _isCopyingDown: boolean; @@ -27,7 +27,7 @@ export class CopyLinesCommand implements editorCommon.ICommand { this._endLineNumberDelta = 0; } - public getEditOperations(model: ITextModel, builder: editorCommon.IEditOperationBuilder): void { + public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void { let s = this._selection; this._startLineNumberDelta = 0; @@ -61,7 +61,7 @@ export class CopyLinesCommand implements editorCommon.ICommand { this._selectionDirection = this._selection.getDirection(); } - public computeCursorState(model: ITextModel, helper: editorCommon.ICursorStateComputerData): Selection { + public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { let result = helper.getTrackedSelection(this._selectionId!); if (this._startLineNumberDelta !== 0 || this._endLineNumberDelta !== 0) { diff --git a/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts b/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts index a201370cea3ba..0a1bc6bf90c18 100644 --- a/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts +++ b/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts @@ -6,10 +6,10 @@ import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { ICommand, IEditOperationBuilder, ICursorStateComputerData } from 'vs/editor/common/editorCommon'; import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; -export class SortLinesCommand implements editorCommon.ICommand { +export class SortLinesCommand implements ICommand { private static _COLLATOR: Intl.Collator | null = null; public static getCollator(): Intl.Collator { @@ -29,7 +29,7 @@ export class SortLinesCommand implements editorCommon.ICommand { this.selectionId = null; } - public getEditOperations(model: ITextModel, builder: editorCommon.IEditOperationBuilder): void { + public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void { let op = sortLines(model, this.selection, this.descending); if (op) { builder.addEditOperation(op.range, op.text); @@ -38,7 +38,7 @@ export class SortLinesCommand implements editorCommon.ICommand { this.selectionId = builder.trackSelection(this.selection); } - public computeCursorState(model: ITextModel, helper: editorCommon.ICursorStateComputerData): Selection { + public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { return helper.getTrackedSelection(this.selectionId!); } diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index 6bce7e78d8d45..bf5399f274a62 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -14,7 +14,7 @@ import * as platform from 'vs/base/common/platform'; import { ICodeEditor, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { Position } from 'vs/editor/common/core/position'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { LinkProviderRegistry } from 'vs/editor/common/modes'; @@ -97,7 +97,7 @@ class LinkOccurrence { } } -class LinkDetector implements editorCommon.IEditorContribution { +class LinkDetector implements IEditorContribution { public static readonly ID: string = 'editor.linkDetector'; diff --git a/src/vs/editor/contrib/message/messageController.ts b/src/vs/editor/contrib/message/messageController.ts index badf5eb2d6a99..a4fcb5f2d1a91 100644 --- a/src/vs/editor/contrib/message/messageController.ts +++ b/src/vs/editor/contrib/message/messageController.ts @@ -10,7 +10,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { IDisposable, Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { Range } from 'vs/editor/common/core/range'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; import { registerEditorContribution, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions'; import { ICodeEditor, IContentWidget, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -19,7 +19,7 @@ import { registerThemingParticipant, HIGH_CONTRAST } from 'vs/platform/theme/com import { inputValidationInfoBorder, inputValidationInfoBackground, inputValidationInfoForeground } from 'vs/platform/theme/common/colorRegistry'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -export class MessageController extends Disposable implements editorCommon.IEditorContribution { +export class MessageController extends Disposable implements IEditorContribution { public static readonly ID = 'editor.contrib.messageController'; @@ -144,7 +144,7 @@ class MessageWidget implements IContentWidget { constructor(editor: ICodeEditor, { lineNumber, column }: IPosition, text: string) { this._editor = editor; - this._editor.revealLinesInCenterIfOutsideViewport(lineNumber, lineNumber, editorCommon.ScrollType.Smooth); + this._editor.revealLinesInCenterIfOutsideViewport(lineNumber, lineNumber, ScrollType.Smooth); this._position = { lineNumber, column: column - 1 }; this._domNode = document.createElement('div'); diff --git a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts index e4200b5dcfac6..d2af95af60d0c 100644 --- a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts @@ -16,7 +16,7 @@ import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/commo import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IModelDeltaDecoration, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; @@ -456,7 +456,7 @@ class WordHighlighter { } } -class WordHighlighterContribution extends Disposable implements editorCommon.IEditorContribution { +class WordHighlighterContribution extends Disposable implements IEditorContribution { public static readonly ID = 'editor.contrib.wordHighlighter'; diff --git a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts b/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts index bee2ce31a268d..b5d6aea147010 100644 --- a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts +++ b/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts @@ -10,7 +10,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, IActionOptions, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IEditorContribution, ScrollType, IEditor } from 'vs/editor/common/editorCommon'; import { IModelDeltaDecoration } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { QuickOpenEditorWidget } from 'vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget'; @@ -22,7 +22,7 @@ export interface IQuickOpenControllerOpts { getAutoFocus(searchValue: string): IAutoFocus; } -export class QuickOpenController implements editorCommon.IEditorContribution, IDecorator { +export class QuickOpenController implements IEditorContribution, IDecorator { public static readonly ID = 'editor.controller.quickOpenController'; @@ -61,7 +61,7 @@ export class QuickOpenController implements editorCommon.IEditorContribution, ID // Restore selection if canceled if (canceled && this.lastKnownEditorSelection) { this.editor.setSelection(this.lastKnownEditorSelection); - this.editor.revealRangeInCenterIfOutsideViewport(this.lastKnownEditorSelection, editorCommon.ScrollType.Smooth); + this.editor.revealRangeInCenterIfOutsideViewport(this.lastKnownEditorSelection, ScrollType.Smooth); } this.lastKnownEditorSelection = null; @@ -165,7 +165,7 @@ export abstract class BaseEditorQuickOpenAction extends EditorAction { } export interface IDecorator { - decorateLine(range: Range, editor: editorCommon.IEditor): void; + decorateLine(range: Range, editor: IEditor): void; clearDecorations(): void; } diff --git a/src/vs/editor/standalone/browser/quickOpen/gotoLine.ts b/src/vs/editor/standalone/browser/quickOpen/gotoLine.ts index 5d2960a549750..5274a3ae2c351 100644 --- a/src/vs/editor/standalone/browser/quickOpen/gotoLine.ts +++ b/src/vs/editor/standalone/browser/quickOpen/gotoLine.ts @@ -12,7 +12,7 @@ import { ICodeEditor, IDiffEditor, isCodeEditor } from 'vs/editor/browser/editor import { ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IEditor, ScrollType } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ITextModel } from 'vs/editor/common/model'; import { BaseEditorQuickOpenAction, IDecorator } from 'vs/editor/standalone/browser/quickOpen/editorQuickOpen'; @@ -28,9 +28,9 @@ interface ParseResult { export class GotoLineEntry extends QuickOpenEntry { private readonly parseResult: ParseResult; private readonly decorator: IDecorator; - private readonly editor: editorCommon.IEditor; + private readonly editor: IEditor; - constructor(line: string, editor: editorCommon.IEditor, decorator: IDecorator) { + constructor(line: string, editor: IEditor, decorator: IDecorator) { super(); this.editor = editor; @@ -108,7 +108,7 @@ export class GotoLineEntry extends QuickOpenEntry { // Apply selection and focus const range = this.toSelection(); (this.editor).setSelection(range); - (this.editor).revealRangeInCenter(range, editorCommon.ScrollType.Smooth); + (this.editor).revealRangeInCenter(range, ScrollType.Smooth); this.editor.focus(); return true; @@ -124,7 +124,7 @@ export class GotoLineEntry extends QuickOpenEntry { // Select Line Position const range = this.toSelection(); - this.editor.revealRangeInCenter(range, editorCommon.ScrollType.Smooth); + this.editor.revealRangeInCenter(range, ScrollType.Smooth); // Decorate if possible this.decorator.decorateLine(range, this.editor); diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 19a2aead6c3a2..2f2419ca6a8fa 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -18,7 +18,7 @@ import { isDiffEditorConfigurationKey, isEditorConfigurationKey } from 'vs/edito import { EditOperation } from 'vs/editor/common/core/editOperation'; import { IPosition, Position as Pos } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IEditor } from 'vs/editor/common/editorCommon'; import { ITextModel, ITextSnapshot } from 'vs/editor/common/model'; import { TextEdit, WorkspaceEdit, isResourceTextEdit } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -90,7 +90,7 @@ export interface IOpenEditorDelegate { (url: string): boolean; } -function withTypedEditor(widget: editorCommon.IEditor, codeEditorCallback: (editor: ICodeEditor) => T, diffEditorCallback: (editor: IDiffEditor) => T): T { +function withTypedEditor(widget: IEditor, codeEditorCallback: (editor: ICodeEditor) => T, diffEditorCallback: (editor: IDiffEditor) => T): T { if (isCodeEditor(widget)) { // Single Editor return codeEditorCallback(widget); @@ -104,13 +104,13 @@ export class SimpleEditorModelResolverService implements ITextModelService { public _serviceBrand: undefined; private readonly modelService: IModelService | undefined; - private editor?: editorCommon.IEditor; + private editor?: IEditor; constructor(modelService: IModelService | undefined) { this.modelService = modelService; } - public setEditor(editor: editorCommon.IEditor): void { + public setEditor(editor: IEditor): void { this.editor = editor; } diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 65285eb9a2d2d..e35ab13d4f45e 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -13,7 +13,7 @@ import { DiffNavigator, IDiffNavigator } from 'vs/editor/browser/widget/diffNavi import { EditorOptions, ConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; import { Token } from 'vs/editor/common/core/token'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IEditor, EditorType } from 'vs/editor/common/editorCommon'; import { FindMatch, ITextModel, TextModelResolvedOptions } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { NULL_STATE, nullTokenize } from 'vs/editor/common/modes/nullMode'; @@ -42,7 +42,7 @@ import { IEditorProgressService } from 'vs/platform/progress/common/progress'; type Omit = Pick>; -function withAllStandaloneServices(domElement: HTMLElement, override: IEditorOverrideServices, callback: (services: DynamicStandaloneServices) => T): T { +function withAllStandaloneServices(domElement: HTMLElement, override: IEditorOverrideServices, callback: (services: DynamicStandaloneServices) => T): T { let services = new DynamicStandaloneServices(domElement, override); let simpleEditorModelResolverService: SimpleEditorModelResolverService | null = null; @@ -375,7 +375,7 @@ export function createMonacoEditorAPI(): typeof monaco.editor { FindMatch: FindMatch, // vars - EditorType: editorCommon.EditorType, + EditorType: EditorType, EditorOptions: EditorOptions }; diff --git a/src/vs/editor/test/browser/testCodeEditor.ts b/src/vs/editor/test/browser/testCodeEditor.ts index c4eb0282b2cd0..8626630e183a9 100644 --- a/src/vs/editor/test/browser/testCodeEditor.ts +++ b/src/vs/editor/test/browser/testCodeEditor.ts @@ -9,7 +9,7 @@ import { View } from 'vs/editor/browser/view/viewImpl'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; import * as editorOptions from 'vs/editor/common/config/editorOptions'; import { Cursor } from 'vs/editor/common/controller/cursor'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IConfiguration, IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; @@ -29,7 +29,7 @@ import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService export class TestCodeEditor extends CodeEditorWidget implements editorBrowser.ICodeEditor { //#region testing overrides - protected _createConfiguration(options: editorOptions.IEditorConstructionOptions): editorCommon.IConfiguration { + protected _createConfiguration(options: editorOptions.IEditorConstructionOptions): IConfiguration { return new TestConfiguration(options); } protected _createView(viewModel: ViewModel, cursor: Cursor): [View, boolean] { @@ -42,7 +42,7 @@ export class TestCodeEditor extends CodeEditorWidget implements editorBrowser.IC public getCursor(): Cursor | undefined { return this._modelData ? this._modelData.cursor : undefined; } - public registerAndInstantiateContribution(id: string, ctor: any): T { + public registerAndInstantiateContribution(id: string, ctor: any): T { let r = this._instantiationService.createInstance(ctor, this); this._contributions[id] = r; return r; diff --git a/src/vs/editor/test/browser/testCommand.ts b/src/vs/editor/test/browser/testCommand.ts index 5b96fde1e00e1..ce33b94a913c7 100644 --- a/src/vs/editor/test/browser/testCommand.ts +++ b/src/vs/editor/test/browser/testCommand.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { ICommand, Handler, IEditOperationBuilder } from 'vs/editor/common/editorCommon'; import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import { LanguageIdentifier } from 'vs/editor/common/modes'; @@ -16,7 +16,7 @@ export function testCommand( lines: string[], languageIdentifier: LanguageIdentifier | null, selection: Selection, - commandFactory: (selection: Selection) => editorCommon.ICommand, + commandFactory: (selection: Selection) => ICommand, expectedLines: string[], expectedSelection: Selection, forceTokenization?: boolean @@ -33,7 +33,7 @@ export function testCommand( cursor.setSelections('tests', [selection]); - cursor.trigger('tests', editorCommon.Handler.ExecuteCommand, commandFactory(cursor.getSelection())); + cursor.trigger('tests', Handler.ExecuteCommand, commandFactory(cursor.getSelection())); assert.deepEqual(model.getLinesContent(), expectedLines); @@ -47,9 +47,9 @@ export function testCommand( /** * Extract edit operations if command `command` were to execute on model `model` */ -export function getEditOperation(model: ITextModel, command: editorCommon.ICommand): IIdentifiedSingleEditOperation[] { +export function getEditOperation(model: ITextModel, command: ICommand): IIdentifiedSingleEditOperation[] { let operations: IIdentifiedSingleEditOperation[] = []; - let editOperationBuilder: editorCommon.IEditOperationBuilder = { + let editOperationBuilder: IEditOperationBuilder = { addEditOperation: (range: Range, text: string) => { operations.push({ range: range, diff --git a/src/vs/workbench/api/browser/mainThreadEditor.ts b/src/vs/workbench/api/browser/mainThreadEditor.ts index 239f87096f1d0..4de14d85d612a 100644 --- a/src/vs/workbench/api/browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/browser/mainThreadEditor.ts @@ -9,7 +9,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { RenderLineNumbersType, TextEditorCursorStyle, cursorStyleToString, EditorOption } from 'vs/editor/common/config/editorOptions'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IDecorationOptions, ScrollType } from 'vs/editor/common/editorCommon'; import { IIdentifiedSingleEditOperation, ISingleEditOperation, ITextModel, ITextModelUpdateOptions } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; @@ -365,7 +365,7 @@ export class MainThreadTextEditor { } } - public setDecorations(key: string, ranges: editorCommon.IDecorationOptions[]): void { + public setDecorations(key: string, ranges: IDecorationOptions[]): void { if (!this._codeEditor) { return; } @@ -389,16 +389,16 @@ export class MainThreadTextEditor { } switch (revealType) { case TextEditorRevealType.Default: - this._codeEditor.revealRange(range, editorCommon.ScrollType.Smooth); + this._codeEditor.revealRange(range, ScrollType.Smooth); break; case TextEditorRevealType.InCenter: - this._codeEditor.revealRangeInCenter(range, editorCommon.ScrollType.Smooth); + this._codeEditor.revealRangeInCenter(range, ScrollType.Smooth); break; case TextEditorRevealType.InCenterIfOutsideViewport: - this._codeEditor.revealRangeInCenterIfOutsideViewport(range, editorCommon.ScrollType.Smooth); + this._codeEditor.revealRangeInCenterIfOutsideViewport(range, ScrollType.Smooth); break; case TextEditorRevealType.AtTop: - this._codeEditor.revealRangeAtTop(range, editorCommon.ScrollType.Smooth); + this._codeEditor.revealRangeAtTop(range, ScrollType.Smooth); break; default: console.warn(`Unknown revealType: ${revealType}`); diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts index 8d8eb232266d7..77f48d1ec618a 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts @@ -12,7 +12,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Range } from 'vs/editor/common/core/range'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { registerEditorContribution, ServicesAccessor, registerEditorCommand, EditorCommand } from 'vs/editor/browser/editorExtensions'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; @@ -37,7 +37,7 @@ const NLS_KB_LAYOUT_ERROR_MESSAGE = nls.localize('defineKeybinding.kbLayoutError const INTERESTING_FILE = /keybindings\.json$/; -export class DefineKeybindingController extends Disposable implements editorCommon.IEditorContribution { +export class DefineKeybindingController extends Disposable implements IEditorContribution { public static readonly ID = 'editor.contrib.defineKeybinding'; diff --git a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts index 4f54adf23018d..227169377e9aa 100644 --- a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts +++ b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts @@ -10,7 +10,7 @@ import { ISnippetsService } from './snippets.contribution'; import { getNonWhitespacePrefix } from './snippetsService'; import { endsWith } from 'vs/base/common/strings'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { Range } from 'vs/editor/common/core/range'; import { registerEditorContribution, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; @@ -21,7 +21,7 @@ import { Snippet } from './snippetsFile'; import { SnippetCompletion } from './snippetCompletionProvider'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; -export class TabCompletionController implements editorCommon.IEditorContribution { +export class TabCompletionController implements IEditorContribution { public static readonly ID = 'editor.tabCompletionController'; static readonly ContextKey = new RawContextKey('hasSnippetCompletions', undefined); From 66fa2c0d2133657c885eb31602f21da420666423 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Tue, 14 Jan 2020 16:59:02 +0100 Subject: [PATCH 286/315] Extension id format (microsoft/vscode-remote-release#1800) --- .../configuration-editing/schemas/attachContainer.schema.json | 4 +++- .../configuration-editing/schemas/devContainer.schema.json | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/extensions/configuration-editing/schemas/attachContainer.schema.json b/extensions/configuration-editing/schemas/attachContainer.schema.json index cebd0f2bc3e7b..58e59d72ee0d3 100644 --- a/extensions/configuration-editing/schemas/attachContainer.schema.json +++ b/extensions/configuration-editing/schemas/attachContainer.schema.json @@ -40,7 +40,9 @@ "type": "array", "description": "An array of extensions that should be installed into the container.", "items": { - "type": "string" + "type": "string", + "pattern": "^([a-z0-9A-Z][a-z0-9\\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\\-A-Z]*)$", + "errorMessage": "Expected format '${publisher}.${name}'. Example: 'vscode.csharp'." } } } diff --git a/extensions/configuration-editing/schemas/devContainer.schema.json b/extensions/configuration-editing/schemas/devContainer.schema.json index aed58455d016b..c9c34af63fae5 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.json @@ -15,7 +15,9 @@ "type": "array", "description": "An array of extensions that should be installed into the container.", "items": { - "type": "string" + "type": "string", + "pattern": "^([a-z0-9A-Z][a-z0-9\\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\\-A-Z]*)$", + "errorMessage": "Expected format '${publisher}.${name}'. Example: 'vscode.csharp'." } }, "settings": { From 1faf9a62214971256b5501e329ad6aa837e413a9 Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 14 Jan 2020 17:11:07 +0100 Subject: [PATCH 287/315] debugStart: add telemetry --- .../contrib/debug/browser/startView.ts | 55 +++++++++++++------ 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/startView.ts b/src/vs/workbench/contrib/debug/browser/startView.ts index d45f2e4700c69..cd6218472de75 100644 --- a/src/vs/workbench/contrib/debug/browser/startView.ts +++ b/src/vs/workbench/contrib/debug/browser/startView.ts @@ -24,8 +24,31 @@ import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/vie import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const $ = dom.$; +interface DebugStartMetrics { + debuggers?: string[]; +} +type DebugStartMetricsClassification = { + debuggers?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; +}; + +function createClickElement(textContent: string, action: () => any): HTMLSpanElement { + const clickElement = $('span.click'); + clickElement.textContent = textContent; + clickElement.onclick = action; + clickElement.tabIndex = 0; + clickElement.onkeyup = (e) => { + const keyboardEvent = new StandardKeyboardEvent(e); + if (keyboardEvent.keyCode === KeyCode.Enter || (keyboardEvent.keyCode === KeyCode.Space)) { + action(); + } + }; + + return clickElement; +} + export class StartView extends ViewPane { static ID = 'workbench.debug.startView'; @@ -50,6 +73,7 @@ export class StartView extends ViewPane { @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IFileDialogService private readonly dialogService: IFileDialogService, @IInstantiationService instantiationService: IInstantiationService, + @ITelemetryService private readonly telemetryService: ITelemetryService ) { super({ ...(options as IViewPaneOptions), ariaHeaderLabel: localize('debugStart', "Debug Start Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); this._register(editorService.onDidActiveEditorChange(() => this.updateView())); @@ -79,12 +103,18 @@ export class StartView extends ViewPane { const setSecondMessage = () => { secondMessageElement.textContent = localize('specifyHowToRun', "To customize Run and Debug"); - this.clickElement = this.createClickElement(localize('configure', " create a launch.json file."), () => this.commandService.executeCommand(ConfigureAction.ID)); + this.clickElement = createClickElement(localize('configure', " create a launch.json file."), () => { + this.telemetryService.publicLog2('debugStart.configure', { debuggers: this.debuggerLabels }); + this.commandService.executeCommand(ConfigureAction.ID); + }); this.secondMessageContainer.appendChild(this.clickElement); }; const setSecondMessageWithFolder = () => { secondMessageElement.textContent = localize('noLaunchConfiguration', "To customize Run and Debug, "); - this.clickElement = this.createClickElement(localize('openFolder', " open a folder"), () => this.dialogService.pickFolderAndOpen({ forceNewWindow: false })); + this.clickElement = createClickElement(localize('openFolder', " open a folder"), () => { + this.telemetryService.publicLog2('debugStart.openFolder', { debuggers: this.debuggerLabels }); + this.dialogService.pickFolderAndOpen({ forceNewWindow: false }); + }); this.secondMessageContainer.appendChild(this.clickElement); const moreText = $('span.moreText'); @@ -109,7 +139,10 @@ export class StartView extends ViewPane { } if (!enabled && emptyWorkbench) { - this.clickElement = this.createClickElement(localize('openFile', "Open a file"), () => this.dialogService.pickFileAndOpen({ forceNewWindow: false })); + this.clickElement = createClickElement(localize('openFile', "Open a file"), () => { + this.telemetryService.publicLog2('debugStart.openFile'); + this.dialogService.pickFileAndOpen({ forceNewWindow: false }); + }); this.firstMessageContainer.appendChild(this.clickElement); const firstMessageElement = $('span'); this.firstMessageContainer.appendChild(firstMessageElement); @@ -120,21 +153,6 @@ export class StartView extends ViewPane { } } - private createClickElement(textContent: string, action: () => any): HTMLSpanElement { - const clickElement = $('span.click'); - clickElement.textContent = textContent; - clickElement.onclick = action; - clickElement.tabIndex = 0; - clickElement.onkeyup = (e) => { - const keyboardEvent = new StandardKeyboardEvent(e); - if (keyboardEvent.keyCode === KeyCode.Enter || (keyboardEvent.keyCode === KeyCode.Space)) { - action(); - } - }; - - return clickElement; - } - protected renderBody(container: HTMLElement): void { this.firstMessageContainer = $('.top-section'); container.appendChild(this.firstMessageContainer); @@ -142,6 +160,7 @@ export class StartView extends ViewPane { this.debugButton = new Button(container); this._register(this.debugButton.onDidClick(() => { this.commandService.executeCommand(StartAction.ID); + this.telemetryService.publicLog2('debugStart.runAndDebug', { debuggers: this.debuggerLabels }); })); attachButtonStyler(this.debugButton, this.themeService); From 7fa04ddceaab62c6dfed69eaf1fcd38c0ba4a2e1 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 14 Jan 2020 16:40:48 +0100 Subject: [PATCH 288/315] #85216 pull and push support in synchronisers --- .../userDataSync/common/extensionsSync.ts | 97 ++++++++++++++--- .../userDataSync/common/globalStateSync.ts | 88 +++++++++++++-- .../userDataSync/common/keybindingsSync.ts | 96 ++++++++++++++-- .../userDataSync/common/settingsMerge.ts | 11 +- .../userDataSync/common/settingsSync.ts | 103 ++++++++++++++++-- .../userDataSync/common/userDataSync.ts | 2 + .../userDataSync/common/userDataSyncIpc.ts | 4 + .../common/userDataSyncService.ts | 32 ++++++ .../test/common/settingsMerge.test.ts | 6 +- .../electron-browser/settingsSyncService.ts | 8 ++ .../electron-browser/userDataSyncService.ts | 8 ++ 11 files changed, 401 insertions(+), 54 deletions(-) diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 98beb932ceda6..d8d83228bbdc1 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -19,11 +19,13 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { localize } from 'vs/nls'; import { merge } from 'vs/platform/userDataSync/common/extensionsMerge'; -export interface ISyncPreviewResult { +interface ISyncPreviewResult { readonly added: ISyncExtension[]; - readonly removed: ISyncExtension[]; + readonly removed: IExtensionIdentifier[]; readonly updated: ISyncExtension[]; readonly remote: ISyncExtension[] | null; + readonly remoteUserData: IUserData | null; + readonly skippedExtensions: ISyncExtension[]; } interface ILastSyncUserData extends IUserData { @@ -72,6 +74,61 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser } } + async pull(): Promise { + if (!this.configurationService.getValue('sync.enableExtensions')) { + this.logService.info('Extensions: Skipped pulling extensions as it is disabled.'); + return; + } + + this.stop(); + + try { + this.logService.info('Extensions: Started pulling extensions...'); + this.setStatus(SyncStatus.Syncing); + + const remoteUserData = await this.getRemoteUserData(); + + if (remoteUserData.content !== null) { + const localExtensions = await this.getLocalExtensions(); + const remoteExtensions: ISyncExtension[] = JSON.parse(remoteUserData.content); + const { added, updated, remote } = merge(localExtensions, remoteExtensions, [], [], this.getIgnoredExtensions()); + await this.apply({ added, removed: [], updated, remote, remoteUserData, skippedExtensions: [] }); + } + + // No remote exists to pull + else { + this.logService.info('Extensions: Remote extensions does not exist.'); + } + + this.logService.info('Extensions: Finished pulling extensions.'); + } finally { + this.setStatus(SyncStatus.Idle); + } + } + + async push(): Promise { + if (!this.configurationService.getValue('sync.enableExtensions')) { + this.logService.info('Extensions: Skipped pushing extensions as it is disabled.'); + return; + } + + this.stop(); + + try { + this.logService.info('Extensions: Started pushing extensions...'); + this.setStatus(SyncStatus.Syncing); + + const localExtensions = await this.getLocalExtensions(); + const { added, removed, updated, remote } = merge(localExtensions, null, null, [], this.getIgnoredExtensions()); + await this.apply({ added, removed, updated, remote, remoteUserData: null, skippedExtensions: [] }); + + this.logService.info('Extensions: Finished pulling extensions.'); + } finally { + this.setStatus(SyncStatus.Idle); + } + + } + async sync(): Promise { if (!this.configurationService.getValue('sync.enableExtensions')) { this.logService.trace('Extensions: Skipping synchronizing extensions as it is disabled.'); @@ -90,7 +147,8 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser this.setStatus(SyncStatus.Syncing); try { - await this.doSync(); + const previewResult = await this.getPreview(); + await this.apply(previewResult); } catch (e) { this.setStatus(SyncStatus.Idle); if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { @@ -114,7 +172,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser } async hasRemote(): Promise { - const remoteUserData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, null); + const remoteUserData = await this.getRemoteUserData(); return remoteUserData.content !== null; } @@ -134,13 +192,13 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser }); } - private async doSync(): Promise { + private async getPreview(): Promise { const lastSyncData = await this.getLastSyncUserData(); const lastSyncExtensions: ISyncExtension[] | null = lastSyncData ? JSON.parse(lastSyncData.content!) : null; - let skippedExtensions: ISyncExtension[] = lastSyncData ? lastSyncData.skippedExtensions || [] : []; + const skippedExtensions: ISyncExtension[] = lastSyncData ? lastSyncData.skippedExtensions || [] : []; - let remoteData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, lastSyncData); - const remoteExtensions: ISyncExtension[] = remoteData.content ? JSON.parse(remoteData.content) : null; + const remoteUserData = await this.getRemoteUserData(lastSyncData); + const remoteExtensions: ISyncExtension[] = remoteUserData.content ? JSON.parse(remoteUserData.content) : null; const localExtensions = await this.getLocalExtensions(); @@ -150,9 +208,16 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser this.logService.info('Extensions: Remote extensions does not exist. Synchronizing extensions for the first time.'); } - const ignoredExtensions = this.configurationService.getValue('sync.ignoredExtensions') || []; - const { added, removed, updated, remote } = merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions, ignoredExtensions); + const { added, removed, updated, remote } = merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions, this.getIgnoredExtensions()); + + return { added, removed, updated, remote, skippedExtensions, remoteUserData }; + } + + private getIgnoredExtensions() { + return this.configurationService.getValue('sync.ignoredExtensions') || []; + } + private async apply({ added, removed, updated, remote, remoteUserData, skippedExtensions }: ISyncPreviewResult): Promise { if (!added.length && !removed.length && !updated.length && !remote) { this.logService.trace('Extensions: No changes found during synchronizing extensions.'); } @@ -165,15 +230,13 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser if (remote) { // update remote this.logService.info('Extensions: Updating remote extensions...'); - remoteData = await this.writeToRemote(remote, remoteData.ref); + remoteUserData = await this.writeToRemote(remote, remoteUserData ? remoteUserData.ref : null); } - if (remoteData.content - && (!lastSyncData || lastSyncData.ref !== remoteData.ref) - ) { + if (remoteUserData?.content) { // update last sync this.logService.info('Extensions: Updating last synchronised extensions...'); - await this.updateLastSyncValue({ ...remoteData, skippedExtensions }); + await this.updateLastSyncValue({ ...remoteUserData, skippedExtensions }); } } @@ -243,6 +306,10 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser await this.fileService.writeFile(this.lastSyncExtensionsResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData))); } + private getRemoteUserData(lastSyncData?: IUserData | null): Promise { + return this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, lastSyncData || null); + } + private async writeToRemote(extensions: ISyncExtension[], ref: string | null): Promise { const content = JSON.stringify(extensions); ref = await this.userDataSyncStoreService.write(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, content, ref); diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index 5677baaf1f14a..36c81f2e03878 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -19,6 +19,12 @@ import { parse } from 'vs/base/common/json'; const argvProperties: string[] = ['locale']; +interface ISyncPreviewResult { + readonly local: IGlobalState | undefined; + readonly remote: IGlobalState | undefined; + readonly remoteUserData: IUserData | null; +} + export class GlobalStateSynchroniser extends Disposable implements ISynchroniser { private static EXTERNAL_USER_DATA_GLOBAL_STATE_KEY: string = 'globalState'; @@ -53,6 +59,58 @@ export class GlobalStateSynchroniser extends Disposable implements ISynchroniser } } + async pull(): Promise { + if (!this.configurationService.getValue('sync.enableUIState')) { + this.logService.info('UI State: Skipped pulling ui state as it is disabled.'); + return; + } + + this.stop(); + + try { + this.logService.info('UI State: Started pulling ui state...'); + this.setStatus(SyncStatus.Syncing); + + const remoteUserData = await this.getRemoteUserData(); + + if (remoteUserData.content !== null) { + const local: IGlobalState = JSON.parse(remoteUserData.content); + await this.apply({ local, remote: undefined, remoteUserData }); + } + + // No remote exists to pull + else { + this.logService.info('UI State: Remote UI state does not exist.'); + } + + this.logService.info('UI State: Finished pulling UI state.'); + } finally { + this.setStatus(SyncStatus.Idle); + } + } + + async push(): Promise { + if (!this.configurationService.getValue('sync.enableUIState')) { + this.logService.info('UI State: Skipped pushing UI State as it is disabled.'); + return; + } + + this.stop(); + + try { + this.logService.info('UI State: Started pushing UI State...'); + this.setStatus(SyncStatus.Syncing); + + const remote = await this.getLocalGlobalState(); + await this.apply({ local: undefined, remote, remoteUserData: null }); + + this.logService.info('UI State: Finished pulling UI State.'); + } finally { + this.setStatus(SyncStatus.Idle); + } + + } + async sync(): Promise { if (!this.configurationService.getValue('sync.enableUIState')) { this.logService.trace('UI State: Skipping synchronizing UI state as it is disabled.'); @@ -68,9 +126,9 @@ export class GlobalStateSynchroniser extends Disposable implements ISynchroniser this.setStatus(SyncStatus.Syncing); try { - await this.doSync(); + const result = await this.getPreview(); + await this.apply(result); this.logService.trace('UI State: Finised synchronizing ui state.'); - this.setStatus(SyncStatus.Idle); return true; } catch (e) { this.setStatus(SyncStatus.Idle); @@ -80,6 +138,8 @@ export class GlobalStateSynchroniser extends Disposable implements ISynchroniser return this.sync(); } throw e; + } finally { + this.setStatus(SyncStatus.Idle); } } @@ -91,21 +151,25 @@ export class GlobalStateSynchroniser extends Disposable implements ISynchroniser } async hasRemote(): Promise { - const remoteUserData = await this.userDataSyncStoreService.read(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, null); + const remoteUserData = await this.getRemoteUserData(); return remoteUserData.content !== null; } - private async doSync(): Promise { + private async getPreview(): Promise { const lastSyncData = await this.getLastSyncUserData(); const lastSyncGlobalState = lastSyncData && lastSyncData.content ? JSON.parse(lastSyncData.content) : null; - let remoteData = await this.userDataSyncStoreService.read(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, lastSyncData); - const remoteGlobalState: IGlobalState = remoteData.content ? JSON.parse(remoteData.content) : null; + const remoteUserData = await this.getRemoteUserData(); + const remoteGlobalState: IGlobalState = remoteUserData.content ? JSON.parse(remoteUserData.content) : null; const localGloablState = await this.getLocalGlobalState(); const { local, remote } = merge(localGloablState, remoteGlobalState, lastSyncGlobalState); + return { local, remote, remoteUserData }; + } + + private async apply({ local, remote, remoteUserData }: ISyncPreviewResult): Promise { if (local) { // update local this.logService.info('UI State: Updating local ui state...'); @@ -115,15 +179,13 @@ export class GlobalStateSynchroniser extends Disposable implements ISynchroniser if (remote) { // update remote this.logService.info('UI State: Updating remote ui state...'); - remoteData = await this.writeToRemote(remote, remoteData.ref); + remoteUserData = await this.writeToRemote(remote, remoteUserData ? remoteUserData.ref : null); } - if (remoteData.content - && (!lastSyncData || lastSyncData.ref !== remoteData.ref) - ) { + if (remoteUserData?.content) { // update last sync this.logService.info('UI State: Updating last synchronised ui state...'); - await this.updateLastSyncValue(remoteData); + await this.updateLastSyncValue(remoteUserData); } } @@ -166,6 +228,10 @@ export class GlobalStateSynchroniser extends Disposable implements ISynchroniser await this.fileService.writeFile(this.lastSyncGlobalStateResource, VSBuffer.fromString(JSON.stringify(remoteUserData))); } + private getRemoteUserData(lastSyncData?: IUserData | null): Promise { + return this.userDataSyncStoreService.read(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, lastSyncData || null); + } + private async writeToRemote(globalState: IGlobalState, ref: string | null): Promise { const content = JSON.stringify(globalState); ref = await this.userDataSyncStoreService.write(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, content, ref); diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 1b92064f97207..bea6e5671c875 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -30,7 +30,7 @@ interface ISyncContent { interface ISyncPreviewResult { readonly fileContent: IFileContent | null; - readonly remoteUserData: IUserData; + readonly remoteUserData: IUserData | null; readonly hasLocalChanged: boolean; readonly hasRemoteChanged: boolean; readonly hasConflicts: boolean; @@ -73,6 +73,79 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser } } + async pull(): Promise { + if (!this.configurationService.getValue('sync.enableKeybindings')) { + this.logService.info('Keybindings: Skipped pulling keybindings as it is disabled.'); + return; + } + + this.stop(); + + try { + this.logService.info('Keybindings: Started pulling keybindings...'); + this.setStatus(SyncStatus.Syncing); + + const remoteUserData = await this.getRemoteUserData(); + const remoteContent = remoteUserData.content !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.content) : null; + + if (remoteContent !== null) { + await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(remoteContent)); + this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ + fileContent: null, + hasConflicts: false, + hasLocalChanged: true, + hasRemoteChanged: false, + remoteUserData + })); + await this.apply(); + } + + // No remote exists to pull + else { + this.logService.info('Keybindings: Remote keybindings does not exist.'); + } + } finally { + this.setStatus(SyncStatus.Idle); + } + + } + + async push(): Promise { + if (!this.configurationService.getValue('sync.enableKeybindings')) { + this.logService.info('Keybindings: Skipped pushing keybindings as it is disabled.'); + return; + } + + this.stop(); + + try { + this.logService.info('Keybindings: Started pushing keybindings...'); + this.setStatus(SyncStatus.Syncing); + + const fileContent = await this.getLocalFileContent(); + + if (fileContent !== null) { + await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, fileContent.value); + this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ + fileContent, + hasConflicts: false, + hasLocalChanged: false, + hasRemoteChanged: true, + remoteUserData: null + })); + await this.apply(); + } + + // No local exists to push + else { + this.logService.info('Keybindings: Local keybindings does not exist.'); + } + } finally { + this.setStatus(SyncStatus.Idle); + } + + } + async sync(_continue?: boolean): Promise { if (!this.configurationService.getValue('sync.enableKeybindings')) { this.logService.trace('Keybindings: Skipping synchronizing keybindings as it is disabled.'); @@ -115,6 +188,8 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser return this.sync(); } throw e; + } finally { + this.setStatus(SyncStatus.Idle); } } @@ -134,7 +209,7 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser } async hasRemote(): Promise { - const remoteUserData = await this.userDataSyncStoreService.read(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, null); + const remoteUserData = await this.getRemoteUserData(); return remoteUserData.content !== null; } @@ -143,6 +218,7 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser return false; } await this.apply(); + this.setStatus(SyncStatus.Idle); return true; } @@ -170,11 +246,12 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser } if (hasRemoteChanged) { this.logService.info('Keybindings: Updating remote keybindings'); - const remoteContents = this.updateSyncContent(content, remoteUserData.content); - const ref = await this.updateRemoteUserData(remoteContents, remoteUserData.ref); + let remoteContents = remoteUserData ? remoteUserData.content : (await this.getRemoteUserData()).content; + remoteContents = this.updateSyncContent(content, remoteContents); + const ref = await this.updateRemoteUserData(remoteContents, remoteUserData ? remoteUserData.ref : null); remoteUserData = { ref, content: remoteContents }; } - if (remoteUserData.content) { + if (remoteUserData?.content) { this.logService.info('Keybindings: Updating last synchronised keybindings'); const lastSyncContent = this.updateSyncContent(content, null); await this.updateLastSyncUserData({ ref: remoteUserData.ref, content: lastSyncContent }); @@ -188,7 +265,6 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser this.logService.trace('Keybindings: Finised synchronizing keybindings.'); this.syncPreviewResultPromise = null; - this.setStatus(SyncStatus.Idle); } private hasErrors(content: string): boolean { @@ -210,7 +286,7 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser const remoteUserData = await this.getRemoteUserData(lastSyncData); const remoteContent = remoteUserData.content ? this.getKeybindingsContentFromSyncContent(remoteUserData.content) : null; // Get file content last to get the latest - const fileContent = await this.getLocalContent(); + const fileContent = await this.getLocalFileContent(); let hasLocalChanged: boolean = false; let hasRemoteChanged: boolean = false; let hasConflicts: boolean = false; @@ -262,7 +338,7 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser return this._formattingOptions; } - private async getLocalContent(): Promise { + private async getLocalFileContent(): Promise { try { return await this.fileService.readFile(this.environmentService.keybindingsResource); } catch (error) { @@ -293,8 +369,8 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser await this.fileService.writeFile(this.lastSyncKeybindingsResource, VSBuffer.fromString(JSON.stringify(remoteUserData))); } - private async getRemoteUserData(lastSyncData: IUserData | null): Promise { - return this.userDataSyncStoreService.read(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, lastSyncData); + private async getRemoteUserData(lastSyncData?: IUserData | null): Promise { + return this.userDataSyncStoreService.read(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, lastSyncData || null); } private async updateRemoteUserData(content: string, ref: string | null): Promise { diff --git a/src/vs/platform/userDataSync/common/settingsMerge.ts b/src/vs/platform/userDataSync/common/settingsMerge.ts index 1b710e133bd15..c00080dbcb132 100644 --- a/src/vs/platform/userDataSync/common/settingsMerge.ts +++ b/src/vs/platform/userDataSync/common/settingsMerge.ts @@ -12,17 +12,14 @@ import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import * as contentUtil from 'vs/platform/userDataSync/common/content'; import { IConflictSetting } from 'vs/platform/userDataSync/common/userDataSync'; -export function computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: string[], formattingOptions: FormattingOptions): string { +export function updateIgnoredSettings(targetContent: string, sourceContent: string, ignoredSettings: string[], formattingOptions: FormattingOptions): string { if (ignoredSettings.length) { - const remote = parse(remoteContent); - const ignored = ignoredSettings.reduce((set, key) => { set.add(key); return set; }, new Set()); + const source = parse(sourceContent); for (const key of ignoredSettings) { - if (ignored.has(key)) { - localContent = contentUtil.edit(localContent, [key], remote[key], formattingOptions); - } + targetContent = contentUtil.edit(targetContent, [key], source[key], formattingOptions); } } - return localContent; + return targetContent; } export function merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: string[], resolvedConflicts: { key: string, value: any | undefined }[], formattingOptions: FormattingOptions): { mergeContent: string, hasChanges: boolean, conflicts: IConflictSetting[] } { diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 89dc8c0ae81bf..813826aa7285e 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -17,14 +17,14 @@ import { joinPath, dirname } from 'vs/base/common/resources'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { startsWith } from 'vs/base/common/strings'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { computeRemoteContent, merge } from 'vs/platform/userDataSync/common/settingsMerge'; +import { updateIgnoredSettings, merge } from 'vs/platform/userDataSync/common/settingsMerge'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import * as arrays from 'vs/base/common/arrays'; import * as objects from 'vs/base/common/objects'; interface ISyncPreviewResult { readonly fileContent: IFileContent | null; - readonly remoteUserData: IUserData; + readonly remoteUserData: IUserData | null; readonly hasLocalChanged: boolean; readonly hasRemoteChanged: boolean; readonly conflicts: IConflictSetting[]; @@ -86,6 +86,87 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer } } + async pull(): Promise { + if (!this.configurationService.getValue('sync.enableSettings')) { + this.logService.info('Settings: Skipped pulling settings as it is disabled.'); + return; + } + + this.stop(); + + try { + this.logService.info('Settings: Started pulling settings...'); + this.setStatus(SyncStatus.Syncing); + + const remoteUserData = await this.getRemoteUserData(); + + if (remoteUserData.content !== null) { + const fileContent = await this.getLocalFileContent(); + const formatUtils = await this.getFormattingOptions(); + // Update ignored settings + const content = updateIgnoredSettings(remoteUserData.content, fileContent ? fileContent.value.toString() : '{}', getIgnoredSettings(this.configurationService), formatUtils); + await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(content)); + + this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ + conflicts: [], + fileContent, + hasLocalChanged: true, + hasRemoteChanged: false, + remoteUserData + })); + + await this.apply(); + } + + // No remote exists to pull + else { + this.logService.info('Settings: Remote settings does not exist.'); + } + } finally { + this.setStatus(SyncStatus.Idle); + } + } + + async push(): Promise { + if (!this.configurationService.getValue('sync.enableSettings')) { + this.logService.info('Settings: Skipped pushing settings as it is disabled.'); + return; + } + + this.stop(); + + try { + this.logService.info('Settings: Started pushing settings...'); + this.setStatus(SyncStatus.Syncing); + + const fileContent = await this.getLocalFileContent(); + + if (fileContent !== null) { + const formatUtils = await this.getFormattingOptions(); + // Remove ignored settings + const content = updateIgnoredSettings(fileContent.value.toString(), '{}', getIgnoredSettings(this.configurationService), formatUtils); + await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(content)); + + this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ + conflicts: [], + fileContent, + hasLocalChanged: false, + hasRemoteChanged: true, + remoteUserData: null + })); + + await this.apply(); + } + + // No local exists to push + else { + this.logService.info('Settings: Local settings does not exist.'); + } + } finally { + this.setStatus(SyncStatus.Idle); + } + } + async sync(_continue?: boolean): Promise { if (!this.configurationService.getValue('sync.enableSettings')) { this.logService.trace('Settings: Skipping synchronizing settings as it is disabled.'); @@ -123,7 +204,7 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer } async hasRemote(): Promise { - const remoteUserData = await this.userDataSyncStoreService.read(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, null); + const remoteUserData = await this.getRemoteUserData(); return remoteUserData.content !== null; } @@ -159,12 +240,15 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer return this.sync(); } throw e; + } finally { + this.setStatus(SyncStatus.Idle); } } private async continueSync(): Promise { if (this.status === SyncStatus.HasConflicts) { await this.apply(); + this.setStatus(SyncStatus.Idle); } return true; } @@ -193,12 +277,12 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer } if (hasRemoteChanged) { const formatUtils = await this.getFormattingOptions(); - const remoteContent = remoteUserData.content ? computeRemoteContent(content, remoteUserData.content, getIgnoredSettings(this.configurationService, content), formatUtils) : content; + const remoteContent = remoteUserData?.content ? updateIgnoredSettings(content, remoteUserData.content, getIgnoredSettings(this.configurationService, content), formatUtils) : content; this.logService.info('Settings: Updating remote settings'); - const ref = await this.writeToRemote(remoteContent, remoteUserData.ref); + const ref = await this.writeToRemote(remoteContent, remoteUserData ? remoteUserData.ref : null); remoteUserData = { ref, content }; } - if (remoteUserData.content) { + if (remoteUserData?.content) { this.logService.info('Settings: Updating last synchronised sttings'); await this.updateLastSyncValue(remoteUserData); } @@ -211,7 +295,6 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer this.logService.trace('Settings: Finised synchronizing settings.'); this.syncPreviewResultPromise = null; - this.setStatus(SyncStatus.Idle); } private hasErrors(content: string): boolean { @@ -229,7 +312,7 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer private async generatePreview(resolvedConflicts: { key: string, value: any }[], token: CancellationToken): Promise { const lastSyncData = await this.getLastSyncUserData(); - const remoteUserData = await this.userDataSyncStoreService.read(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, lastSyncData); + const remoteUserData = await this.getRemoteUserData(lastSyncData); const remoteContent: string | null = remoteUserData.content; // Get file content last to get the latest const fileContent = await this.getLocalFileContent(); @@ -303,6 +386,10 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer } } + private getRemoteUserData(lastSyncData?: IUserData | null): Promise { + return this.userDataSyncStoreService.read(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, lastSyncData || null); + } + private async writeToRemote(content: string, ref: string | null): Promise { return this.userDataSyncStoreService.write(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, content, ref); } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 3558afcb8e838..eacd383b6503a 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -184,6 +184,8 @@ export interface ISynchroniser { readonly status: SyncStatus; readonly onDidChangeStatus: Event; readonly onDidChangeLocal: Event; + pull(): Promise; + push(): Promise; sync(_continue?: boolean): Promise; stop(): void; hasPreviouslySynced(): Promise diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index 05f4d99128f59..61f634471baf1 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -25,6 +25,8 @@ export class UserDataSyncChannel implements IServerChannel { call(context: any, command: string, args?: any): Promise { switch (command) { case 'sync': return this.service.sync(args[0]); + case 'pull': return this.service.pull(); + case 'push': return this.service.push(); case '_getInitialStatus': return Promise.resolve(this.service.status); case 'getConflictsSource': return Promise.resolve(this.service.conflictsSource); case 'removeExtension': return this.service.removeExtension(args[0]); @@ -52,6 +54,8 @@ export class SettingsSyncChannel implements IServerChannel { call(context: any, command: string, args?: any): Promise { switch (command) { case 'sync': return this.service.sync(args[0]); + case 'pull': return this.service.pull(); + case 'push': return this.service.push(); case '_getInitialStatus': return Promise.resolve(this.service.status); case '_getInitialConflicts': return Promise.resolve(this.service.conflicts); case 'stop': this.service.stop(); return Promise.resolve(); diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 5a23f9d01c4ab..9c4aaf3cb0cc0 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -56,6 +56,38 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => s.onDidChangeLocal)); } + async pull(): Promise { + if (!this.userDataSyncStoreService.userDataSyncStore) { + throw new Error('Not enabled'); + } + if (this.authTokenService.status === AuthTokenStatus.SignedOut) { + throw new Error('Not Authenticated. Please sign in to start sync.'); + } + for (const synchroniser of this.synchronisers) { + try { + await synchroniser.pull(); + } catch (e) { + this.logService.error(`${this.getSyncSource(synchroniser)}: ${toErrorMessage(e)}`); + } + } + } + + async push(): Promise { + if (!this.userDataSyncStoreService.userDataSyncStore) { + throw new Error('Not enabled'); + } + if (this.authTokenService.status === AuthTokenStatus.SignedOut) { + throw new Error('Not Authenticated. Please sign in to start sync.'); + } + for (const synchroniser of this.synchronisers) { + try { + await synchroniser.push(); + } catch (e) { + this.logService.error(`${this.getSyncSource(synchroniser)}: ${toErrorMessage(e)}`); + } + } + } + async sync(_continue?: boolean): Promise { if (!this.userDataSyncStoreService.userDataSyncStore) { throw new Error('Not enabled'); diff --git a/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts index 343c99b38d633..4f51f87c2e3ec 100644 --- a/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { merge, computeRemoteContent } from 'vs/platform/userDataSync/common/settingsMerge'; +import { merge, updateIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; import { IConflictSetting } from 'vs/platform/userDataSync/common/userDataSync'; const formattingOptions = { eol: '\n', insertSpaces: false, tabSize: 4 }; @@ -567,7 +567,7 @@ suite('SettingsMerge - Compute Remote Content', () => { 'd': 4, 'e': 6, }); - const actual = computeRemoteContent(localContent, remoteContent, [], formattingOptions); + const actual = updateIgnoredSettings(localContent, remoteContent, [], formattingOptions); assert.equal(actual, localContent); }); @@ -588,7 +588,7 @@ suite('SettingsMerge - Compute Remote Content', () => { 'b': 2, 'c': 3, }); - const actual = computeRemoteContent(localContent, remoteContent, ['a'], formattingOptions); + const actual = updateIgnoredSettings(localContent, remoteContent, ['a'], formattingOptions); assert.equal(actual, expected); }); diff --git a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts index 51c3a999ff652..345482f53b1c6 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts @@ -45,6 +45,14 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ }); } + pull(): Promise { + return this.channel.call('pull'); + } + + push(): Promise { + return this.channel.call('push'); + } + sync(_continue?: boolean): Promise { return this.channel.call('sync', [_continue]); } diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index fe09e70eec638..a8b04ea93024a 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -38,6 +38,14 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ }); } + pull(): Promise { + return this.channel.call('pull'); + } + + push(): Promise { + return this.channel.call('push'); + } + sync(_continue?: boolean): Promise { return this.channel.call('sync', [_continue]); } From d5c8eac689b6a075a8203a21d2d27a156288e4b5 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 14 Jan 2020 17:33:26 +0100 Subject: [PATCH 289/315] :lipstick: --- .../editor/browser/controller/mouseHandler.ts | 40 +++++++++---------- .../editor/browser/widget/inlineDiffMargin.ts | 10 ++--- .../contrib/codelens/codelensController.ts | 12 +++--- .../editor/contrib/codelens/codelensWidget.ts | 26 ++++++------ src/vs/editor/test/browser/testCodeEditor.ts | 4 +- 5 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index 973a5ca1d3d70..efeecf1e053b3 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -10,7 +10,7 @@ import { RunOnceScheduler, TimeoutTimer } from 'vs/base/common/async'; import { Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { HitTestContext, IViewZoneData, MouseTarget, MouseTargetFactory, PointerHandlerLastRenderData } from 'vs/editor/browser/controller/mouseTarget'; -import * as editorBrowser from 'vs/editor/browser/editorBrowser'; +import { IMouseTarget, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { ClientCoordinates, EditorMouseEvent, EditorMouseEventFactory, GlobalEditorMouseMoveMonitor, createEditorPagePosition } from 'vs/editor/browser/editorDom'; import { ViewController } from 'vs/editor/browser/view/viewController'; import { EditorZoom } from 'vs/editor/common/config/editorZoom'; @@ -148,7 +148,7 @@ export class MouseHandler extends ViewEventHandler { } // --- end event handlers - public getTargetAtClientPoint(clientX: number, clientY: number): editorBrowser.IMouseTarget | null { + public getTargetAtClientPoint(clientX: number, clientY: number): IMouseTarget | null { const clientPos = new ClientCoordinates(clientX, clientY); const pos = clientPos.toPageCoordinates(); const editorPos = createEditorPagePosition(this.viewHelper.viewDomNode); @@ -160,7 +160,7 @@ export class MouseHandler extends ViewEventHandler { return this.mouseTargetFactory.createMouseTarget(this.viewHelper.getLastRenderData(), editorPos, pos, null); } - protected _createMouseTarget(e: EditorMouseEvent, testEventTarget: boolean): editorBrowser.IMouseTarget { + protected _createMouseTarget(e: EditorMouseEvent, testEventTarget: boolean): IMouseTarget { return this.mouseTargetFactory.createMouseTarget(this.viewHelper.getLastRenderData(), e.editorPos, e.pos, testEventTarget ? e.target : null); } @@ -210,12 +210,12 @@ export class MouseHandler extends ViewEventHandler { public _onMouseDown(e: EditorMouseEvent): void { const t = this._createMouseTarget(e, true); - const targetIsContent = (t.type === editorBrowser.MouseTargetType.CONTENT_TEXT || t.type === editorBrowser.MouseTargetType.CONTENT_EMPTY); - const targetIsGutter = (t.type === editorBrowser.MouseTargetType.GUTTER_GLYPH_MARGIN || t.type === editorBrowser.MouseTargetType.GUTTER_LINE_NUMBERS || t.type === editorBrowser.MouseTargetType.GUTTER_LINE_DECORATIONS); - const targetIsLineNumbers = (t.type === editorBrowser.MouseTargetType.GUTTER_LINE_NUMBERS); + const targetIsContent = (t.type === MouseTargetType.CONTENT_TEXT || t.type === MouseTargetType.CONTENT_EMPTY); + const targetIsGutter = (t.type === MouseTargetType.GUTTER_GLYPH_MARGIN || t.type === MouseTargetType.GUTTER_LINE_NUMBERS || t.type === MouseTargetType.GUTTER_LINE_DECORATIONS); + const targetIsLineNumbers = (t.type === MouseTargetType.GUTTER_LINE_NUMBERS); const selectOnLineNumbers = this._context.configuration.options.get(EditorOption.selectOnLineNumbers); - const targetIsViewZone = (t.type === editorBrowser.MouseTargetType.CONTENT_VIEW_ZONE || t.type === editorBrowser.MouseTargetType.GUTTER_VIEW_ZONE); - const targetIsWidget = (t.type === editorBrowser.MouseTargetType.CONTENT_WIDGET); + const targetIsViewZone = (t.type === MouseTargetType.CONTENT_VIEW_ZONE || t.type === MouseTargetType.GUTTER_VIEW_ZONE); + const targetIsWidget = (t.type === MouseTargetType.CONTENT_WIDGET); let shouldHandle = e.leftButton || e.middleButton; if (platform.isMacintosh && e.leftButton && e.ctrlKey) { @@ -269,7 +269,7 @@ class MouseDownOperation extends Disposable { private readonly _context: ViewContext; private readonly _viewController: ViewController; private readonly _viewHelper: IPointerHandlerHelper; - private readonly _createMouseTarget: (e: EditorMouseEvent, testEventTarget: boolean) => editorBrowser.IMouseTarget; + private readonly _createMouseTarget: (e: EditorMouseEvent, testEventTarget: boolean) => IMouseTarget; private readonly _getMouseColumn: (e: EditorMouseEvent) => number; private readonly _mouseMoveMonitor: GlobalEditorMouseMoveMonitor; @@ -284,7 +284,7 @@ class MouseDownOperation extends Disposable { context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper, - createMouseTarget: (e: EditorMouseEvent, testEventTarget: boolean) => editorBrowser.IMouseTarget, + createMouseTarget: (e: EditorMouseEvent, testEventTarget: boolean) => IMouseTarget, getMouseColumn: (e: EditorMouseEvent) => number ) { super(); @@ -331,10 +331,10 @@ class MouseDownOperation extends Disposable { } } - public start(targetType: editorBrowser.MouseTargetType, e: EditorMouseEvent): void { + public start(targetType: MouseTargetType, e: EditorMouseEvent): void { this._lastMouseEvent = e; - this._mouseState.setStartedOnLineNumbers(targetType === editorBrowser.MouseTargetType.GUTTER_LINE_NUMBERS); + this._mouseState.setStartedOnLineNumbers(targetType === MouseTargetType.GUTTER_LINE_NUMBERS); this._mouseState.setStartButtons(e); this._mouseState.setModifiers(e); const position = this._findMousePosition(e, true); @@ -356,7 +356,7 @@ class MouseDownOperation extends Disposable { && e.detail < 2 // only single click on a selection can work && !this._isActive // the mouse is not down yet && !this._currentSelection.isEmpty() // we don't drag single cursor - && (position.type === editorBrowser.MouseTargetType.CONTENT_TEXT) // single click on text + && (position.type === MouseTargetType.CONTENT_TEXT) // single click on text && position.position && this._currentSelection.containsPosition(position.position) // single click on a selection ) { this._mouseState.isDragAndDrop = true; @@ -436,12 +436,12 @@ class MouseDownOperation extends Disposable { if (viewZoneData) { const newPosition = this._helpPositionJumpOverViewZone(viewZoneData); if (newPosition) { - return new MouseTarget(null, editorBrowser.MouseTargetType.OUTSIDE_EDITOR, mouseColumn, newPosition); + return new MouseTarget(null, MouseTargetType.OUTSIDE_EDITOR, mouseColumn, newPosition); } } const aboveLineNumber = viewLayout.getLineNumberAtVerticalOffset(verticalOffset); - return new MouseTarget(null, editorBrowser.MouseTargetType.OUTSIDE_EDITOR, mouseColumn, new Position(aboveLineNumber, 1)); + return new MouseTarget(null, MouseTargetType.OUTSIDE_EDITOR, mouseColumn, new Position(aboveLineNumber, 1)); } if (e.posy > editorContent.y + editorContent.height) { @@ -450,22 +450,22 @@ class MouseDownOperation extends Disposable { if (viewZoneData) { const newPosition = this._helpPositionJumpOverViewZone(viewZoneData); if (newPosition) { - return new MouseTarget(null, editorBrowser.MouseTargetType.OUTSIDE_EDITOR, mouseColumn, newPosition); + return new MouseTarget(null, MouseTargetType.OUTSIDE_EDITOR, mouseColumn, newPosition); } } const belowLineNumber = viewLayout.getLineNumberAtVerticalOffset(verticalOffset); - return new MouseTarget(null, editorBrowser.MouseTargetType.OUTSIDE_EDITOR, mouseColumn, new Position(belowLineNumber, model.getLineMaxColumn(belowLineNumber))); + return new MouseTarget(null, MouseTargetType.OUTSIDE_EDITOR, mouseColumn, new Position(belowLineNumber, model.getLineMaxColumn(belowLineNumber))); } const possibleLineNumber = viewLayout.getLineNumberAtVerticalOffset(viewLayout.getCurrentScrollTop() + (e.posy - editorContent.y)); if (e.posx < editorContent.x) { - return new MouseTarget(null, editorBrowser.MouseTargetType.OUTSIDE_EDITOR, mouseColumn, new Position(possibleLineNumber, 1)); + return new MouseTarget(null, MouseTargetType.OUTSIDE_EDITOR, mouseColumn, new Position(possibleLineNumber, 1)); } if (e.posx > editorContent.x + editorContent.width) { - return new MouseTarget(null, editorBrowser.MouseTargetType.OUTSIDE_EDITOR, mouseColumn, new Position(possibleLineNumber, model.getLineMaxColumn(possibleLineNumber))); + return new MouseTarget(null, MouseTargetType.OUTSIDE_EDITOR, mouseColumn, new Position(possibleLineNumber, model.getLineMaxColumn(possibleLineNumber))); } return null; @@ -483,7 +483,7 @@ class MouseDownOperation extends Disposable { return null; } - if (t.type === editorBrowser.MouseTargetType.CONTENT_VIEW_ZONE || t.type === editorBrowser.MouseTargetType.GUTTER_VIEW_ZONE) { + if (t.type === MouseTargetType.CONTENT_VIEW_ZONE || t.type === MouseTargetType.GUTTER_VIEW_ZONE) { const newPosition = this._helpPositionJumpOverViewZone(t.detail); if (newPosition) { return new MouseTarget(t.element, t.type, t.mouseColumn, newPosition, null, t.detail); diff --git a/src/vs/editor/browser/widget/inlineDiffMargin.ts b/src/vs/editor/browser/widget/inlineDiffMargin.ts index 6ad8f6c007dd0..9e61bc0b05e53 100644 --- a/src/vs/editor/browser/widget/inlineDiffMargin.ts +++ b/src/vs/editor/browser/widget/inlineDiffMargin.ts @@ -9,7 +9,7 @@ import { Action } from 'vs/base/common/actions'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import * as editorBrowser from 'vs/editor/browser/editorBrowser'; +import { IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -150,8 +150,8 @@ export class InlineDiffMargin extends Disposable { })); - this._register(editor.onMouseMove((e: editorBrowser.IEditorMouseEvent) => { - if (e.target.type === editorBrowser.MouseTargetType.CONTENT_VIEW_ZONE || e.target.type === editorBrowser.MouseTargetType.GUTTER_VIEW_ZONE) { + this._register(editor.onMouseMove((e: IEditorMouseEvent) => { + if (e.target.type === MouseTargetType.CONTENT_VIEW_ZONE || e.target.type === MouseTargetType.GUTTER_VIEW_ZONE) { const viewZoneId = e.target.detail.viewZoneId; if (viewZoneId === this._viewZoneId) { @@ -165,12 +165,12 @@ export class InlineDiffMargin extends Disposable { } })); - this._register(editor.onMouseDown((e: editorBrowser.IEditorMouseEvent) => { + this._register(editor.onMouseDown((e: IEditorMouseEvent) => { if (!e.event.rightButton) { return; } - if (e.target.type === editorBrowser.MouseTargetType.CONTENT_VIEW_ZONE || e.target.type === editorBrowser.MouseTargetType.GUTTER_VIEW_ZONE) { + if (e.target.type === MouseTargetType.CONTENT_VIEW_ZONE || e.target.type === MouseTargetType.GUTTER_VIEW_ZONE) { const viewZoneId = e.target.detail.viewZoneId; if (viewZoneId === this._viewZoneId) { diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index 7a644f1bfe4ef..f905d33c94e82 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -7,7 +7,7 @@ import { CancelablePromise, RunOnceScheduler, createCancelablePromise, disposabl import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors'; import { toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { StableEditorScrollState } from 'vs/editor/browser/core/editorState'; -import * as editorBrowser from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, MouseTargetType, IViewZoneChangeAccessor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; @@ -40,7 +40,7 @@ export class CodeLensContribution implements IEditorContribution { private _detectVisibleLenses: RunOnceScheduler | undefined; constructor( - private readonly _editor: editorBrowser.ICodeEditor, + private readonly _editor: ICodeEditor, @ICommandService private readonly _commandService: ICommandService, @INotificationService private readonly _notificationService: INotificationService, @ICodeLensCache private readonly _codeLensCache: ICodeLensCache @@ -223,7 +223,7 @@ export class CodeLensContribution implements IEditorContribution { } })); this._localToDispose.add(this._editor.onMouseUp(e => { - if (e.target.type !== editorBrowser.MouseTargetType.CONTENT_WIDGET) { + if (e.target.type !== MouseTargetType.CONTENT_WIDGET) { return; } let target = e.target.element; @@ -243,7 +243,7 @@ export class CodeLensContribution implements IEditorContribution { scheduler.schedule(); } - private _disposeAllLenses(decChangeAccessor: IModelDecorationsChangeAccessor | undefined, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor | undefined): void { + private _disposeAllLenses(decChangeAccessor: IModelDecorationsChangeAccessor | undefined, viewZoneChangeAccessor: IViewZoneChangeAccessor | undefined): void { const helper = new CodeLensHelper(); for (const lens of this._lenses) { lens.dispose(helper, viewZoneChangeAccessor); @@ -300,7 +300,7 @@ export class CodeLensContribution implements IEditorContribution { groupsIndex++; codeLensIndex++; } else { - this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule())); + this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule())); codeLensIndex++; groupsIndex++; } @@ -314,7 +314,7 @@ export class CodeLensContribution implements IEditorContribution { // Create extra symbols while (groupsIndex < groups.length) { - this._lenses.push(new CodeLensWidget(groups[groupsIndex], this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule())); + this._lenses.push(new CodeLensWidget(groups[groupsIndex], this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule())); groupsIndex++; } diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index a661af5def121..622e7785aaf33 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -7,7 +7,7 @@ import 'vs/css!./codelensWidget'; import * as dom from 'vs/base/browser/dom'; import { renderCodicons } from 'vs/base/common/codicons'; import { escape } from 'vs/base/common/strings'; -import * as editorBrowser from 'vs/editor/browser/editorBrowser'; +import { IViewZone, IContentWidget, IActiveCodeEditor, IContentWidgetPosition, ContentWidgetPositionPreference, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; @@ -17,7 +17,7 @@ import { CodeLensItem } from 'vs/editor/contrib/codelens/codelens'; import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -class CodeLensViewZone implements editorBrowser.IViewZone { +class CodeLensViewZone implements IViewZone { readonly heightInLines: number; readonly suppressMouseDown: boolean; @@ -47,7 +47,7 @@ class CodeLensViewZone implements editorBrowser.IViewZone { } } -class CodeLensContentWidget implements editorBrowser.IContentWidget { +class CodeLensContentWidget implements IContentWidget { private static _idPool: number = 0; @@ -57,14 +57,14 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { private readonly _id: string; private readonly _domNode: HTMLElement; - private readonly _editor: editorBrowser.IActiveCodeEditor; + private readonly _editor: IActiveCodeEditor; private readonly _commands = new Map(); - private _widgetPosition?: editorBrowser.IContentWidgetPosition; + private _widgetPosition?: IContentWidgetPosition; private _isEmpty: boolean = true; constructor( - editor: editorBrowser.IActiveCodeEditor, + editor: IActiveCodeEditor, className: string, line: number, ) { @@ -137,11 +137,11 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { const column = this._editor.getModel().getLineFirstNonWhitespaceColumn(line); this._widgetPosition = { position: { lineNumber: line, column: column }, - preference: [editorBrowser.ContentWidgetPositionPreference.ABOVE] + preference: [ContentWidgetPositionPreference.ABOVE] }; } - getPosition(): editorBrowser.IContentWidgetPosition | null { + getPosition(): IContentWidgetPosition | null { return this._widgetPosition || null; } } @@ -181,7 +181,7 @@ export class CodeLensHelper { export class CodeLensWidget { - private readonly _editor: editorBrowser.IActiveCodeEditor; + private readonly _editor: IActiveCodeEditor; private readonly _className: string; private readonly _viewZone!: CodeLensViewZone; private readonly _viewZoneId!: string; @@ -193,10 +193,10 @@ export class CodeLensWidget { constructor( data: CodeLensItem[], - editor: editorBrowser.IActiveCodeEditor, + editor: IActiveCodeEditor, className: string, helper: CodeLensHelper, - viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor, + viewZoneChangeAccessor: IViewZoneChangeAccessor, updateCallback: Function ) { this._editor = editor; @@ -244,7 +244,7 @@ export class CodeLensWidget { } } - dispose(helper: CodeLensHelper, viewZoneChangeAccessor?: editorBrowser.IViewZoneChangeAccessor): void { + dispose(helper: CodeLensHelper, viewZoneChangeAccessor?: IViewZoneChangeAccessor): void { this._decorationIds.forEach(helper.removeDecoration, helper); this._decorationIds = []; if (viewZoneChangeAccessor) { @@ -322,7 +322,7 @@ export class CodeLensWidget { return -1; } - update(viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor): void { + update(viewZoneChangeAccessor: IViewZoneChangeAccessor): void { if (this.isValid()) { const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]); if (range) { diff --git a/src/vs/editor/test/browser/testCodeEditor.ts b/src/vs/editor/test/browser/testCodeEditor.ts index 8626630e183a9..47a341a2d63e3 100644 --- a/src/vs/editor/test/browser/testCodeEditor.ts +++ b/src/vs/editor/test/browser/testCodeEditor.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as editorBrowser from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { View } from 'vs/editor/browser/view/viewImpl'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; @@ -26,7 +26,7 @@ import { TestNotificationService } from 'vs/platform/notification/test/common/te import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; -export class TestCodeEditor extends CodeEditorWidget implements editorBrowser.ICodeEditor { +export class TestCodeEditor extends CodeEditorWidget implements ICodeEditor { //#region testing overrides protected _createConfiguration(options: editorOptions.IEditorConstructionOptions): IConfiguration { From 661bc5da418e104c25086a145f27a2095b2b2b35 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 14 Jan 2020 17:06:45 +0100 Subject: [PATCH 290/315] define constants in typescript-vscode-sh-plugin --- .../typescript-language-features/package.json | 2 +- .../src/features/semanticTokens.ts | 108 ++++++++---------- .../src/utils/api.ts | 1 - .../typescript-language-features/yarn.lock | 8 +- 4 files changed, 53 insertions(+), 66 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 681c5c4b2d105..4238a966b5b83 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -19,7 +19,7 @@ "jsonc-parser": "^2.1.1", "rimraf": "^2.6.3", "semver": "5.5.1", - "typescript-vscode-sh-plugin": "^0.3.2", + "typescript-vscode-sh-plugin": "^0.5.0", "vscode-extension-telemetry": "0.1.1", "vscode-nls": "^4.0.0" }, diff --git a/extensions/typescript-language-features/src/features/semanticTokens.ts b/extensions/typescript-language-features/src/features/semanticTokens.ts index 78f7dfb36a83d..e34dc09de9a42 100644 --- a/extensions/typescript-language-features/src/features/semanticTokens.ts +++ b/extensions/typescript-language-features/src/features/semanticTokens.ts @@ -9,7 +9,10 @@ import * as Proto from '../protocol'; import { VersionDependentRegistration } from '../utils/dependentRegistration'; import API from '../utils/api'; -const minTypeScriptVersion = API.v370; +// all constants are const +import { TokenType, TokenModifier, TokenEncodingConsts, VersionRequirement } from 'typescript-vscode-sh-plugin/lib/constants'; + +const minTypeScriptVersion = API.fromVersionString(`${VersionRequirement.major}.${VersionRequirement.minor}`); export function register(selector: vscode.DocumentSelector, client: ITypeScriptServiceClient) { return new VersionDependentRegistration(client, minTypeScriptVersion, () => { @@ -29,18 +32,16 @@ class SemanticTokensProvider implements vscode.SemanticTokensProvider { } getLegend(): vscode.SemanticTokensLegend { - const tokenTypes = []; - for (let i = 0; i < VSCodeShPlugin.TokenType._sentinel; i++) { - tokenTypes.push(VSCodeShPlugin.TokenType[i]); + if (tokenTypes.length !== TokenType._) { + console.warn('typescript-vscode-sh-plugin has added new tokens types.'); } - const tokenModifiers = []; - for (let i = 0; i < VSCodeShPlugin.TokenModifier._sentinel; i++) { - tokenModifiers.push(VSCodeShPlugin.TokenModifier[i]); + if (tokenModifiers.length !== TokenModifier._) { + console.warn('typescript-vscode-sh-plugin has added new tokens modifiers.'); } return new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers); } - async provideSemanticTokens(document: vscode.TextDocument, _options: vscode.SemanticTokensRequestOptions, token: vscode.CancellationToken): Promise { + async provideSemanticTokens(document: vscode.TextDocument, options: vscode.SemanticTokensRequestOptions, token: vscode.CancellationToken): Promise { const file = this.client.toOpenedFilePath(document); if (!file) { return null; @@ -51,8 +52,8 @@ class SemanticTokensProvider implements vscode.SemanticTokensProvider { const allTokenSpans: number[][] = []; let requestArgs: ExperimentalProtocol.EncodedSemanticClassificationsRequestArgs[] = []; - if (_options.ranges) { - requestArgs = _options.ranges.map(r => { const start = document.offsetAt(r.start); const length = document.offsetAt(r.end) - start; return { file, start, length }; }); + if (options.ranges) { + requestArgs = options.ranges.map(r => { const start = document.offsetAt(r.start); const length = document.offsetAt(r.end) - start; return { file, start, length }; }); requestArgs = requestArgs.sort((a1, a2) => a1.start - a2.start); } else { requestArgs = [{ file, start: 0, length: document.getText().length }]; // full document @@ -81,10 +82,10 @@ class SemanticTokensProvider implements vscode.SemanticTokensProvider { const tsClassification = tokenSpan[i++]; let tokenModifiers = 0; - let tokenType = VSCodeShPlugin.getTokenTypeFromClassification(tsClassification); + let tokenType = getTokenTypeFromClassification(tsClassification); if (tokenType !== undefined) { // it's a classification as returned by the typescript-vscode-sh-plugin - tokenModifiers = VSCodeShPlugin.getTokenModifierFromClassification(tsClassification); + tokenModifiers = getTokenModifierFromClassification(tsClassification); } else { // typescript-vscode-sh-plugin is not present tokenType = tokenTypeMap[tsClassification]; @@ -108,61 +109,48 @@ class SemanticTokensProvider implements vscode.SemanticTokensProvider { } } -namespace VSCodeShPlugin { - - // typescript-vscode-sh-plugin encodes type and modifiers in the classification: - // TSClassification = (TokenType + 1) << 8 + TokenModifier - - const TokenTypeOffset = 8; - const TokenModifierMask = (1 << TokenTypeOffset) - 1; // 0xFF - - export function getTokenTypeFromClassification(tsClassification: number): number | undefined { - if (tsClassification > TokenModifierMask) { - return (tsClassification >> TokenTypeOffset) - 1; - } - return undefined; - } - - export function getTokenModifierFromClassification(tsClassification: number) { - return tsClassification & TokenModifierMask; - } +// typescript-vscode-sh-plugin encodes type and modifiers in the classification: +// TSClassification = (TokenType + 1) << 8 + TokenModifier - // Don't change TokenType and TokenModifier enums without adopting typescript-vscode-sh-plugin - export enum TokenType { - 'class', - 'enum', - 'interface', - 'namespace', - 'typeParameter', - 'type', - 'parameter', - 'variable', - 'property', - 'constant', - 'function', - 'member', - _sentinel +function getTokenTypeFromClassification(tsClassification: number): number | undefined { + if (tsClassification > TokenEncodingConsts.modifierMask) { + return (tsClassification >> TokenEncodingConsts.typeOffset) - 1; } + return undefined; +} - export enum TokenModifier { - 'declaration', - 'static', - 'async', - 'readonly', - _sentinel - } +function getTokenModifierFromClassification(tsClassification: number) { + return tsClassification & TokenEncodingConsts.modifierMask; } -// mapping for the original ExperimentalProtocol.ClassificationType from TypeScript (only used when plugin is not available) +const tokenTypes: string[] = []; +tokenTypes[TokenType.class] = 'class'; +tokenTypes[TokenType.enum] = 'enum'; +tokenTypes[TokenType.interface] = 'interface'; +tokenTypes[TokenType.namespace] = 'namespace'; +tokenTypes[TokenType.typeParameter] = 'typeParameter'; +tokenTypes[TokenType.type] = 'type'; +tokenTypes[TokenType.parameter] = 'parameter'; +tokenTypes[TokenType.variable] = 'variable'; +tokenTypes[TokenType.property] = 'property'; +tokenTypes[TokenType.function] = 'function'; +tokenTypes[TokenType.member] = 'member'; + +const tokenModifiers: string[] = []; +tokenModifiers[TokenModifier.async] = 'async'; +tokenModifiers[TokenModifier.declaration] = 'declaration'; +tokenModifiers[TokenModifier.readonly] = 'readonly'; +tokenModifiers[TokenModifier.static] = 'static'; +// mapping for the original ExperimentalProtocol.ClassificationType from TypeScript (only used when plugin is not available) const tokenTypeMap: number[] = []; -tokenTypeMap[ExperimentalProtocol.ClassificationType.className] = VSCodeShPlugin.TokenType.class; -tokenTypeMap[ExperimentalProtocol.ClassificationType.enumName] = VSCodeShPlugin.TokenType.enum; -tokenTypeMap[ExperimentalProtocol.ClassificationType.interfaceName] = VSCodeShPlugin.TokenType.interface; -tokenTypeMap[ExperimentalProtocol.ClassificationType.moduleName] = VSCodeShPlugin.TokenType.namespace; -tokenTypeMap[ExperimentalProtocol.ClassificationType.typeParameterName] = VSCodeShPlugin.TokenType.typeParameter; -tokenTypeMap[ExperimentalProtocol.ClassificationType.typeAliasName] = VSCodeShPlugin.TokenType.type; -tokenTypeMap[ExperimentalProtocol.ClassificationType.parameterName] = VSCodeShPlugin.TokenType.parameter; +tokenTypeMap[ExperimentalProtocol.ClassificationType.className] = TokenType.class; +tokenTypeMap[ExperimentalProtocol.ClassificationType.enumName] = TokenType.enum; +tokenTypeMap[ExperimentalProtocol.ClassificationType.interfaceName] = TokenType.interface; +tokenTypeMap[ExperimentalProtocol.ClassificationType.moduleName] = TokenType.namespace; +tokenTypeMap[ExperimentalProtocol.ClassificationType.typeParameterName] = TokenType.typeParameter; +tokenTypeMap[ExperimentalProtocol.ClassificationType.typeAliasName] = TokenType.type; +tokenTypeMap[ExperimentalProtocol.ClassificationType.parameterName] = TokenType.parameter; namespace ExperimentalProtocol { diff --git a/extensions/typescript-language-features/src/utils/api.ts b/extensions/typescript-language-features/src/utils/api.ts index 8f181f14b6586..0fa41cb01a0b8 100644 --- a/extensions/typescript-language-features/src/utils/api.ts +++ b/extensions/typescript-language-features/src/utils/api.ts @@ -31,7 +31,6 @@ export default class API { public static readonly v340 = API.fromSimpleString('3.4.0'); public static readonly v345 = API.fromSimpleString('3.4.5'); public static readonly v350 = API.fromSimpleString('3.5.0'); - public static readonly v370 = API.fromSimpleString('3.7.0'); public static readonly v380 = API.fromSimpleString('3.8.0'); public static fromVersionString(versionString: string): API { diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index cc6e217941dba..086a2a9a10650 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -626,10 +626,10 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -typescript-vscode-sh-plugin@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.3.2.tgz#bde9d0eba24ca5856024811fa354a9f5a7aeebe5" - integrity sha512-XsalETsSf3y7VWxk36plqHpsbl+TUDl278HEuhPHVBcNnwuTjcIq52J/CJw84xYmxmBcTIPUgIgLLS4OE5nX2A== +typescript-vscode-sh-plugin@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.5.0.tgz#014dd928f2fa5000396147ed00792a2c901d97b9" + integrity sha512-MKqivbdkgllHS3Rab/zvXlGAxwCb1AHzgO/a8vmG6i5kExGIytwjUyXALdnnLUWS03B9eEJmIjzOz4y3MpgliQ== uri-js@^4.2.2: version "4.2.2" From 3198a2817692fdb2dd0cff06b7c1a2b2db1eca09 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 14 Jan 2020 17:35:54 +0100 Subject: [PATCH 291/315] make consistent with plugin --- .../src/modes/javascriptSemanticTokens.ts | 86 +++++++++++-------- 1 file changed, 48 insertions(+), 38 deletions(-) diff --git a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts index 38a87159a327f..2824a880d4cb9 100644 --- a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts +++ b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts @@ -6,44 +6,63 @@ import { TextDocument, SemanticTokenData } from './languageModes'; import * as ts from 'typescript'; +export function getSemanticTokenLegend() { + if (tokenTypes.length !== TokenType._) { + console.warn('TokenType has added new entries.'); + } + if (tokenModifiers.length !== TokenModifier._) { + console.warn('TokenModifier has added new entries.'); + } + return { types: tokenTypes, modifiers: tokenModifiers }; +} export function getSemanticTokens(jsLanguageService: ts.LanguageService, currentTextDocument: TextDocument, fileName: string): SemanticTokenData[] { //https://ts-ast-viewer.com/#code/AQ0g2CmAuwGbALzAJwG4BQZQGNwEMBnQ4AQQEYBmYAb2C22zgEtJwATJVTRxgcwD27AQAp8AGmAAjAJS0A9POB8+7NQ168oscAJz5wANXwAnLug2bsJmAFcTAO2XAA1MHyvgu-UdOeWbOw8ViAAvpagocBAA let resultTokens: SemanticTokenData[] = []; + const collector = (node: ts.Node, typeIdx: number, modifierSet: number) => { + resultTokens.push({ start: currentTextDocument.positionAt(node.getStart()), length: node.getWidth(), typeIdx, modifierSet }); + }; + collectTokens(jsLanguageService, fileName, { start: 0, length: currentTextDocument.getText().length }, collector); + + return resultTokens; +} + +function collectTokens(jsLanguageService: ts.LanguageService, fileName: string, span: ts.TextSpan, collector: (node: ts.Node, tokenType: number, tokenModifier: number) => void) { const program = jsLanguageService.getProgram(); if (program) { const typeChecker = program.getTypeChecker(); function visit(node: ts.Node) { + if (!node || !ts.textSpanIntersectsWith(span, node.pos, node.getFullWidth())) { + return; + } if (ts.isIdentifier(node)) { const symbol = typeChecker.getSymbolAtLocation(node); if (symbol) { let typeIdx = classifySymbol(symbol); - if (typeIdx !== undefined) { - let modifierSet = 0; if (node.parent) { const parentTypeIdx = tokenFromDeclarationMapping[node.parent.kind]; if (parentTypeIdx === typeIdx && (node.parent).name === node) { - modifierSet = TokenModifier.declaration; + modifierSet = 1 << TokenModifier.declaration; } } const decl = symbol.valueDeclaration; const modifiers = decl ? ts.getCombinedModifierFlags(decl) : 0; const nodeFlags = decl ? ts.getCombinedNodeFlags(decl) : 0; if (modifiers & ts.ModifierFlags.Static) { - modifierSet |= TokenModifier.static; + modifierSet |= 1 << TokenModifier.static; } if (modifiers & ts.ModifierFlags.Async) { - modifierSet |= TokenModifier.async; + modifierSet |= 1 << TokenModifier.async; } if ((modifiers & ts.ModifierFlags.Readonly) || (nodeFlags & ts.NodeFlags.Const) || (symbol.getFlags() & ts.SymbolFlags.EnumMember)) { - modifierSet |= TokenModifier.readonly; + modifierSet |= 1 << TokenModifier.readonly; } - resultTokens.push({ start: currentTextDocument.positionAt(node.getStart()), length: node.getWidth(), typeIdx, modifierSet }); + collector(node, typeIdx, modifierSet); } } } @@ -55,12 +74,9 @@ export function getSemanticTokens(jsLanguageService: ts.LanguageService, current visit(sourceFile); } } - - return resultTokens; } function classifySymbol(symbol: ts.Symbol) { - const flags = symbol.getFlags(); if (flags & ts.SymbolFlags.Class) { return TokenType.class; @@ -79,38 +95,32 @@ function classifySymbol(symbol: ts.Symbol) { return decl && tokenFromDeclarationMapping[decl.kind]; } - - -export function getSemanticTokenLegend() { - return { types: tokenTypes, modifiers: tokenModifiers }; +export const enum TokenType { + class, enum, interface, namespace, typeParameter, type, parameter, variable, property, function, member, _ } - -const tokenTypes: string[] = ['class', 'enum', 'interface', 'namespace', 'typeParameter', 'type', 'parameter', 'variable', 'property', 'constant', 'function', 'member']; -const tokenModifiers: string[] = ['declaration', 'static', 'async', 'readonly']; - -const enum TokenType { - 'class' = 0, - 'enum' = 1, - 'interface' = 2, - 'namespace' = 3, - 'typeParameter' = 4, - 'type' = 5, - 'parameter' = 6, - 'variable' = 7, - 'property' = 8, - 'constant' = 9, - 'function' = 10, - 'member' = 11 +export const enum TokenModifier { + declaration, static, async, readonly, _ } - -const enum TokenModifier { - 'declaration' = 0x01, - 'static' = 0x02, - 'async' = 0x04, - 'readonly' = 0x08, -} +const tokenTypes: string[] = []; +tokenTypes[TokenType.class] = 'class'; +tokenTypes[TokenType.enum] = 'enum'; +tokenTypes[TokenType.interface] = 'interface'; +tokenTypes[TokenType.namespace] = 'namespace'; +tokenTypes[TokenType.typeParameter] = 'typeParameter'; +tokenTypes[TokenType.type] = 'type'; +tokenTypes[TokenType.parameter] = 'parameter'; +tokenTypes[TokenType.variable] = 'variable'; +tokenTypes[TokenType.property] = 'property'; +tokenTypes[TokenType.function] = 'function'; +tokenTypes[TokenType.member] = 'member'; + +const tokenModifiers: string[] = []; +tokenModifiers[TokenModifier.async] = 'async'; +tokenModifiers[TokenModifier.declaration] = 'declaration'; +tokenModifiers[TokenModifier.readonly] = 'readonly'; +tokenModifiers[TokenModifier.static] = 'static'; const tokenFromDeclarationMapping: { [name: string]: TokenType } = { [ts.SyntaxKind.VariableDeclaration]: TokenType.variable, From 7cd3e04fb8af123ed6d995bc575a2b12c76d4586 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 14 Jan 2020 17:38:52 +0100 Subject: [PATCH 292/315] editors - favor closeEditors() over closeEditor() --- .../contrib/bulkEdit/browser/bulkEdit.contribution.ts | 8 +++++++- .../contrib/extensions/browser/extensionsViewlet.ts | 3 +-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts index ec51b044e9c6d..aacc0db7d511c 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts @@ -25,6 +25,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { URI } from 'vs/base/common/uri'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { IEditorInput } from 'vs/workbench/common/editor'; function getBulkEditPane(panelService: IPanelService): BulkEditPane | undefined { let view: ViewPane | undefined; @@ -86,6 +87,7 @@ class BulkEditPreviewContribution { // (3) close preview editors for (let group of this._editorGroupsService.groups) { + let previewEditors: IEditorInput[] = []; for (let input of group.editors) { let resource: URI | undefined; @@ -96,9 +98,13 @@ class BulkEditPreviewContribution { } if (resource?.scheme === BulkEditPreviewProvider.Schema) { - group.closeEditor(input, { preserveFocus: true }); + previewEditors.push(input); } } + + if (previewEditors.length) { + group.closeEditors(previewEditors, { preserveFocus: true }); + } } } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 9accf3727af69..8c450fe0b0c08 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -583,9 +583,8 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE if (this.configurationService.getValue(CloseExtensionDetailsOnViewChangeKey)) { const promises = this.editorGroupService.groups.map(group => { const editors = group.editors.filter(input => input instanceof ExtensionsInput); - const promises = editors.map(editor => group.closeEditor(editor)); - return Promise.all(promises); + return group.closeEditors(editors); }); Promise.all(promises); From 294f3b04c5e1baa305f4ef6179506633ce4c8694 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 14 Jan 2020 18:09:12 +0100 Subject: [PATCH 293/315] text files - allow to save any existing model --- .../textfile/browser/textFileService.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index edf1aac01d93f..5ee41d8fab96d 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -35,6 +35,7 @@ import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; +import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; /** * The workbench file service implementation implements the raw file service spec and adds additional methods on top. @@ -519,8 +520,8 @@ export abstract class AbstractTextFileService extends Disposable implements ITex success = true; } - // Finally, if the source does not seem to be a file, we have to - // try to resolve a text model from the resource to get at the + // Next, if the source does not seem to be a file, we try to + // resolve a text model from the resource to get at the // contents and additional meta data (e.g. encoding). else if (this.textModelService.hasTextModelContentProvider(source.scheme)) { const modelReference = await this.textModelService.createModelReference(source); @@ -529,6 +530,20 @@ export abstract class AbstractTextFileService extends Disposable implements ITex modelReference.dispose(); // free up our use of the reference } + // Finally we simply check if we can find a editor model that + // would give us access to the contents. + else { + const textModel = this.modelService.getModel(source); + if (textModel) { + const model = new BaseTextEditorModel(this.modelService, this.modeService, source); + if (model.isResolved()) { + success = await this.doSaveAsTextFile(model, source, target, options); + } + + model.dispose(); // free up + } + } + // Revert the source if result is success if (success) { await this.revert(source); From 56a9ef199e349a727309a1e15b1a99af5dc0100e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 14 Jan 2020 18:13:14 +0100 Subject: [PATCH 294/315] build - see if test retry helps --- .../test/electron-main/workspacesMainService.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts b/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts index e7baa0d80bddb..e082679d7007a 100644 --- a/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts +++ b/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts @@ -320,7 +320,9 @@ suite('WorkspacesMainService', () => { service.deleteUntitledWorkspaceSync(workspace); }); - test('getUntitledWorkspaceSync', async () => { + test('getUntitledWorkspaceSync', async function () { + this.retries(3); + let untitled = service.getUntitledWorkspacesSync(); assert.equal(untitled.length, 0); From 441a5860c5253f4b5576e03e808b53d58cf0b24e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 14 Jan 2020 18:57:45 +0100 Subject: [PATCH 295/315] Command to show sync log --- .../userDataSync/browser/userDataSync.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 666725b992295..222643aa7b448 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -33,6 +33,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { UserDataAutoSync } from 'vs/workbench/contrib/userDataSync/browser/userDataAutoSync'; import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger'; import { timeout } from 'vs/base/common/async'; +import { IOutputService } from 'vs/workbench/contrib/output/common/output'; +import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey('authTokenStatus', AuthTokenStatus.Initializing); const SYNC_PUSH_LIGHT_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/check-light.svg`)); @@ -63,6 +65,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @IDialogService private readonly dialogService: IDialogService, @IQuickInputService private readonly quickInputService: IQuickInputService, @IInstantiationService instantiationService: IInstantiationService, + @IOutputService private readonly outputService: IOutputService, ) { super(); this.userDataSyncStore = getUserDataSyncStore(configurationService); @@ -425,6 +428,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo return null; } + private showSyncLog(): Promise { + return this.outputService.showChannel(Constants.userDataSyncLogChannelId); + } + private registerActions(): void { const turnOnSyncCommandId = 'workbench.userData.actions.syncStart'; @@ -570,5 +577,15 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }, when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`)), }); + + const showSyncLogCommandId = 'workbench.userData.actions.showSyncLog'; + CommandsRegistry.registerCommand(showSyncLogCommandId, () => this.showSyncLog()); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: showSyncLogCommandId, + title: localize('show sync log', "Sync: Show Sync Log") + }, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)), + }); } } From 0fc545e916059d42ac782379433122f9de5e55a1 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 13 Jan 2020 21:43:59 -0800 Subject: [PATCH 296/315] Also show refactor documentation in normal lightbulb menu if refactorings are returned For #86788 --- src/vs/editor/common/modes.ts | 11 +++-- .../editor/contrib/codeAction/codeAction.ts | 6 +-- .../contrib/codeAction/codeActionCommands.ts | 8 +-- .../contrib/codeAction/codeActionMenu.ts | 35 +++++++------ .../contrib/codeAction/codeActionModel.ts | 4 +- .../editor/contrib/codeAction/codeActionUi.ts | 18 +++---- .../contrib/codeAction/lightBulbWidget.ts | 11 +++-- .../codeAction/test/codeAction.test.ts | 20 ++++---- .../codeAction/test/codeActionModel.test.ts | 9 ++-- src/vs/editor/contrib/codeAction/types.ts | 8 +-- .../editor/contrib/hover/modesContentHover.ts | 13 +++-- .../api/browser/mainThreadSaveParticipant.ts | 4 +- .../common/documentationContribution.ts | 49 ++++++++----------- .../markers/browser/markersTreeViewer.ts | 7 ++- .../api/extHostLanguageFeatures.test.ts | 9 ++-- 15 files changed, 110 insertions(+), 102 deletions(-) diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 78495217dc32b..66f65392bd6b3 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -555,8 +555,8 @@ export interface CodeAction { /** * @internal */ -export const enum CodeActionTrigger { - Automatic = 1, +export const enum CodeActionTriggerType { + Auto = 1, Manual = 2, } @@ -565,7 +565,7 @@ export const enum CodeActionTrigger { */ export interface CodeActionContext { only?: string; - trigger: CodeActionTrigger; + trigger: CodeActionTriggerType; } export interface CodeActionList extends IDisposable { @@ -587,6 +587,11 @@ export interface CodeActionProvider { * Optional list of CodeActionKinds that this provider returns. */ providedCodeActionKinds?: ReadonlyArray; + + /** + * @internal + */ + _getAdditionalMenuItems?(context: CodeActionContext, actions: readonly CodeAction[]): Command[]; } /** diff --git a/src/vs/editor/contrib/codeAction/codeAction.ts b/src/vs/editor/contrib/codeAction/codeAction.ts index 37744867d8c98..b0a9aa8051f39 100644 --- a/src/vs/editor/contrib/codeAction/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/codeAction.ts @@ -15,7 +15,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { ITextModel } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { CodeActionFilter, CodeActionKind, CodeActionTrigger, CodeActionTriggerType, filtersAction, mayIncludeActionsOfKind } from './types'; +import { CodeActionFilter, CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind } from './types'; export const codeActionCommandId = 'editor.action.codeAction'; export const refactorCommandId = 'editor.action.refactor'; @@ -70,7 +70,7 @@ export function getCodeActions( const codeActionContext: modes.CodeActionContext = { only: filter.include?.value, - trigger: trigger.type === CodeActionTriggerType.Manual ? modes.CodeActionTrigger.Manual : modes.CodeActionTrigger.Automatic + trigger: trigger.type, }; const cts = new TextModelCancellationTokenSource(model, token); @@ -149,7 +149,7 @@ registerLanguageCommand('_executeCodeActionProvider', async function (accessor, const codeActionSet = await getCodeActions( model, validatedRangeOrSelection, - { type: CodeActionTriggerType.Manual, filter: { includeSourceActions: true, include: kind && kind.value ? new CodeActionKind(kind.value) : undefined } }, + { type: modes.CodeActionTriggerType.Manual, filter: { includeSourceActions: true, include: kind && kind.value ? new CodeActionKind(kind.value) : undefined } }, CancellationToken.None); setTimeout(() => codeActionSet.dispose(), 100); diff --git a/src/vs/editor/contrib/codeAction/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/codeActionCommands.ts index 003830553f6ca..185ebd8686793 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -15,7 +15,7 @@ import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { IPosition } from 'vs/editor/common/core/position'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { CodeAction } from 'vs/editor/common/modes'; +import { CodeAction, CodeActionTriggerType } from 'vs/editor/common/modes'; import { codeActionCommandId, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/codeAction'; import { CodeActionUi } from 'vs/editor/contrib/codeAction/codeActionUi'; import { MessageController } from 'vs/editor/contrib/message/messageController'; @@ -29,7 +29,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CodeActionModel, CodeActionsState, SUPPORTED_CODE_ACTIONS } from './codeActionModel'; -import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionFilter, CodeActionKind, CodeActionTrigger, CodeActionTriggerType } from './types'; +import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionFilter, CodeActionKind, CodeActionTrigger } from './types'; function contextKeyForSupportedActions(kind: CodeActionKind) { return ContextKeyExpr.regex( @@ -109,8 +109,8 @@ export class QuickFixController extends Disposable implements IEditorContributio this._ui.getValue().update(newState); } - public showCodeActions(actions: CodeActionSet, at: IAnchor | IPosition) { - return this._ui.getValue().showCodeActionList(actions, at, { includeDisabledActions: false }); + public showCodeActions(trigger: CodeActionTrigger, actions: CodeActionSet, at: IAnchor | IPosition) { + return this._ui.getValue().showCodeActionList(trigger, actions, at, { includeDisabledActions: false }); } public manualTriggerAtCurrentPosition( diff --git a/src/vs/editor/contrib/codeAction/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/codeActionMenu.ts index 7be72d3d2e3da..e732dec94c31f 100644 --- a/src/vs/editor/contrib/codeAction/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/codeActionMenu.ts @@ -14,9 +14,9 @@ import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { ScrollType } from 'vs/editor/common/editorCommon'; -import { CodeAction } from 'vs/editor/common/modes'; +import { CodeAction, CodeActionProviderRegistry } from 'vs/editor/common/modes'; import { codeActionCommandId, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/codeAction'; -import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionKind } from 'vs/editor/contrib/codeAction/types'; +import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionKind, CodeActionTrigger } from 'vs/editor/contrib/codeAction/types'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; @@ -68,7 +68,7 @@ export class CodeActionMenu extends Disposable { return this._visible; } - public async show(codeActions: CodeActionSet, at: IAnchor | IPosition, options: CodeActionShowOptions): Promise { + public async show(trigger: CodeActionTrigger, codeActions: CodeActionSet, at: IAnchor | IPosition, options: CodeActionShowOptions): Promise { const actionsToShow = options.includeDisabledActions ? codeActions.allActions : codeActions.validActions; if (!actionsToShow.length) { this._visible = false; @@ -84,7 +84,7 @@ export class CodeActionMenu extends Disposable { this._visible = true; this._showingActions.value = codeActions; - const menuActions = this.getMenuActions(actionsToShow); + const menuActions = this.getMenuActions(trigger, actionsToShow); const anchor = Position.isIPosition(at) ? this._toCoords(at) : at || { x: 0, y: 0 }; const resolver = this._keybindingResolver.getResolver(); @@ -101,19 +101,26 @@ export class CodeActionMenu extends Disposable { }); } - private getMenuActions(actionsToShow: readonly CodeAction[]): IAction[] { - const allActions = actionsToShow - .map(action => new CodeActionAction(action, () => this._delegate.onSelectCodeAction(action))); + private getMenuActions(trigger: CodeActionTrigger, actionsToShow: readonly CodeAction[]): IAction[] { + const toCodeActionAction = (action: CodeAction): CodeActionAction => new CodeActionAction(action, () => this._delegate.onSelectCodeAction(action)); - // Treat documentation actions as special - const result: IAction[] = allActions - .filter(action => !action.action.kind || !CodeActionKind.RefactorDocumentation.contains(new CodeActionKind(action.action.kind))); + const result: IAction[] = actionsToShow + .map(toCodeActionAction); - const documentationActions = allActions - .filter(action => action.action.kind && CodeActionKind.RefactorDocumentation.contains(new CodeActionKind(action.action.kind))); - if (documentationActions.length) { - result.push(new Separator(), ...documentationActions); + const model = this._editor.getModel(); + if (model && result.length) { + for (const provider of CodeActionProviderRegistry.all(model)) { + if (provider._getAdditionalMenuItems) { + const items = provider._getAdditionalMenuItems({ trigger: trigger.type, only: trigger.filter?.include?.value }, actionsToShow); + if (items.length) { + result.push(new Separator(), ...items.map(command => toCodeActionAction({ + title: command.title, + command: command, + }))); + } + } + } } return result; diff --git a/src/vs/editor/contrib/codeAction/codeActionModel.ts b/src/vs/editor/contrib/codeAction/codeActionModel.ts index 0f834c0e29fda..e8645271f350d 100644 --- a/src/vs/editor/contrib/codeAction/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/codeActionModel.ts @@ -11,12 +11,12 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import { CodeActionProviderRegistry } from 'vs/editor/common/modes'; +import { CodeActionProviderRegistry, CodeActionTriggerType } from 'vs/editor/common/modes'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { getCodeActions, CodeActionSet } from './codeAction'; -import { CodeActionTrigger, CodeActionTriggerType } from './types'; +import { CodeActionTrigger } from './types'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { isEqual } from 'vs/base/common/resources'; diff --git a/src/vs/editor/contrib/codeAction/codeActionUi.ts b/src/vs/editor/contrib/codeAction/codeActionUi.ts index 6f743d1d76eea..7d2aeff74f3b3 100644 --- a/src/vs/editor/contrib/codeAction/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/codeActionUi.ts @@ -10,14 +10,14 @@ import { Lazy } from 'vs/base/common/lazy'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IPosition } from 'vs/editor/common/core/position'; -import { CodeAction } from 'vs/editor/common/modes'; +import { CodeAction, CodeActionTriggerType } from 'vs/editor/common/modes'; import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; import { MessageController } from 'vs/editor/contrib/message/messageController'; -import { CodeActionsState } from './codeActionModel'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { CodeActionMenu, CodeActionShowOptions } from './codeActionMenu'; +import { CodeActionsState } from './codeActionModel'; import { LightBulbWidget } from './lightBulbWidget'; -import { CodeActionAutoApply, CodeActionTrigger, CodeActionTriggerType } from './types'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { CodeActionAutoApply, CodeActionTrigger } from './types'; export class CodeActionUi extends Disposable { @@ -46,7 +46,7 @@ export class CodeActionUi extends Disposable { this._lightBulbWidget = new Lazy(() => { const widget = this._register(instantiationService.createInstance(LightBulbWidget, this._editor, quickFixActionId, preferredFixActionId)); - this._register(widget.onClick(e => this.showCodeActionList(e.actions, e, { includeDisabledActions: false }))); + this._register(widget.onClick(e => this.showCodeActionList(e.trigger, e.actions, e, { includeDisabledActions: false }))); return widget; }); } @@ -65,7 +65,7 @@ export class CodeActionUi extends Disposable { return; } - this._lightBulbWidget.getValue().update(actions, newState.position); + this._lightBulbWidget.getValue().update(actions, newState.trigger, newState.position); if (newState.trigger.type === CodeActionTriggerType.Manual) { if (newState.trigger.filter?.include) { // Triggered for specific scope @@ -103,7 +103,7 @@ export class CodeActionUi extends Disposable { } this._activeCodeActions.value = actions; - this._codeActionWidget.getValue().show(actions, newState.position, { includeDisabledActions }); + this._codeActionWidget.getValue().show(newState.trigger, actions, newState.position, { includeDisabledActions }); } else { // auto magically triggered if (this._codeActionWidget.getValue().isVisible) { @@ -143,7 +143,7 @@ export class CodeActionUi extends Disposable { return undefined; } - public async showCodeActionList(actions: CodeActionSet, at: IAnchor | IPosition, options: CodeActionShowOptions): Promise { - this._codeActionWidget.getValue().show(actions, at, options); + public async showCodeActionList(trigger: CodeActionTrigger, actions: CodeActionSet, at: IAnchor | IPosition, options: CodeActionShowOptions): Promise { + this._codeActionWidget.getValue().show(trigger, actions, at, options); } } diff --git a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts index 66c9a7786f014..cdabd20798982 100644 --- a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts +++ b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts @@ -18,6 +18,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry'; import { Gesture } from 'vs/base/browser/touch'; +import type { CodeActionTrigger } from 'vs/editor/contrib/codeAction/types'; namespace LightBulbState { @@ -33,6 +34,7 @@ namespace LightBulbState { constructor( public readonly actions: CodeActionSet, + public readonly trigger: CodeActionTrigger, public readonly editorPosition: IPosition, public readonly widgetPosition: IContentWidgetPosition, ) { } @@ -48,7 +50,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { private readonly _domNode: HTMLDivElement; - private readonly _onClick = this._register(new Emitter<{ x: number; y: number; actions: CodeActionSet; }>()); + private readonly _onClick = this._register(new Emitter<{ x: number; y: number; actions: CodeActionSet; trigger: CodeActionTrigger }>()); public readonly onClick = this._onClick.event; private _state: LightBulbState.State = LightBulbState.Hidden; @@ -95,7 +97,8 @@ export class LightBulbWidget extends Disposable implements IContentWidget { this._onClick.fire({ x: e.posx, y: top + height + pad, - actions: this.state.actions + actions: this.state.actions, + trigger: this.state.trigger, }); })); this._register(dom.addDisposableListener(this._domNode, 'mouseenter', (e: MouseEvent) => { @@ -139,7 +142,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { return this._state.type === LightBulbState.Type.Showing ? this._state.widgetPosition : null; } - public update(actions: CodeActionSet, atPosition: IPosition) { + public update(actions: CodeActionSet, trigger: CodeActionTrigger, atPosition: IPosition) { if (actions.validActions.length <= 0) { return this.hide(); } @@ -177,7 +180,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { } } - this.state = new LightBulbState.Showing(actions, atPosition, { + this.state = new LightBulbState.Showing(actions, trigger, atPosition, { position: { lineNumber: effectiveLineNumber, column: 1 }, preference: LightBulbWidget._posPref }); diff --git a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts index bd12d41230d52..8a829bff0cafe 100644 --- a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts @@ -9,7 +9,7 @@ import { Range } from 'vs/editor/common/core/range'; import { TextModel } from 'vs/editor/common/model/textModel'; import * as modes from 'vs/editor/common/modes'; import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; -import { CodeActionKind, CodeActionTriggerType } from 'vs/editor/contrib/codeAction/types'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -125,7 +125,7 @@ suite('CodeAction', () => { testData.tsLint.abc ]; - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: CodeActionTriggerType.Manual }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Manual }, CancellationToken.None); assert.equal(actions.length, 6); assert.deepEqual(actions, expected); }); @@ -140,20 +140,20 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); { - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a') } }, CancellationToken.None); assert.equal(actions.length, 2); assert.strictEqual(actions[0].title, 'a'); assert.strictEqual(actions[1].title, 'a.b'); } { - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a.b') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a.b') } }, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a.b'); } { - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a.b.c') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a.b.c') } }, CancellationToken.None); assert.equal(actions.length, 0); } }); @@ -172,7 +172,7 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a') } }, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a'); }); @@ -186,13 +186,13 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); { - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: CodeActionTriggerType.Auto }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto }, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'b'); } { - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: CodeActionTriggerType.Auto, filter: { include: CodeActionKind.Source, includeSourceActions: true } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: CodeActionKind.Source, includeSourceActions: true } }, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a'); } @@ -209,7 +209,7 @@ suite('CodeAction', () => { { const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { - type: CodeActionTriggerType.Auto, filter: { + type: modes.CodeActionTriggerType.Auto, filter: { include: CodeActionKind.Source.append('test'), excludes: [CodeActionKind.Source], includeSourceActions: true, @@ -234,7 +234,7 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { - type: CodeActionTriggerType.Auto, + type: modes.CodeActionTriggerType.Auto, filter: { include: CodeActionKind.QuickFix } diff --git a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts index d77f4010da3c9..c605ed49ab68d 100644 --- a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts @@ -15,7 +15,6 @@ import { CodeActionModel, CodeActionsState } from 'vs/editor/contrib/codeAction/ import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { MarkerService } from 'vs/platform/markers/common/markerService'; -import { CodeActionTriggerType } from 'vs/editor/contrib/codeAction/types'; const testProvider = { provideCodeActions(): modes.CodeActionList { @@ -60,7 +59,7 @@ suite('CodeActionModel', () => { disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { assertType(e.type === CodeActionsState.Type.Triggered); - assert.strictEqual(e.trigger.type, CodeActionTriggerType.Auto); + assert.strictEqual(e.trigger.type, modes.CodeActionTriggerType.Auto); assert.ok(e.actions); e.actions.then(fixes => { @@ -101,7 +100,7 @@ suite('CodeActionModel', () => { disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { assertType(e.type === CodeActionsState.Type.Triggered); - assert.equal(e.trigger.type, CodeActionTriggerType.Auto); + assert.equal(e.trigger.type, modes.CodeActionTriggerType.Auto); assert.ok(e.actions); e.actions.then(fixes => { model.dispose(); @@ -139,7 +138,7 @@ suite('CodeActionModel', () => { disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { assertType(e.type === CodeActionsState.Type.Triggered); - assert.equal(e.trigger.type, CodeActionTriggerType.Auto); + assert.equal(e.trigger.type, modes.CodeActionTriggerType.Auto); const selection = e.rangeOrSelection; assert.deepEqual(selection.selectionStartLineNumber, 1); assert.deepEqual(selection.selectionStartColumn, 1); @@ -164,7 +163,7 @@ suite('CodeActionModel', () => { disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { assertType(e.type === CodeActionsState.Type.Triggered); - assert.equal(e.trigger.type, CodeActionTriggerType.Auto); + assert.equal(e.trigger.type, modes.CodeActionTriggerType.Auto); ++triggerCount; // give time for second trigger before completing test diff --git a/src/vs/editor/contrib/codeAction/types.ts b/src/vs/editor/contrib/codeAction/types.ts index 606d9b8ecedc5..391a389daf930 100644 --- a/src/vs/editor/contrib/codeAction/types.ts +++ b/src/vs/editor/contrib/codeAction/types.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { startsWith } from 'vs/base/common/strings'; -import { CodeAction } from 'vs/editor/common/modes'; +import { CodeAction, CodeActionTriggerType } from 'vs/editor/common/modes'; import { Position } from 'vs/editor/common/core/position'; export class CodeActionKind { @@ -14,7 +14,6 @@ export class CodeActionKind { public static readonly Empty = new CodeActionKind(''); public static readonly QuickFix = new CodeActionKind('quickfix'); public static readonly Refactor = new CodeActionKind('refactor'); - public static readonly RefactorDocumentation = new CodeActionKind('refactor.documentation'); public static readonly Source = new CodeActionKind('source'); public static readonly SourceOrganizeImports = CodeActionKind.Source.append('organizeImports'); public static readonly SourceFixAll = CodeActionKind.Source.append('fixAll'); @@ -102,11 +101,6 @@ export function filtersAction(filter: CodeActionFilter, action: CodeAction): boo return true; } -export const enum CodeActionTriggerType { - Auto, - Manual -} - export interface CodeActionTrigger { readonly type: CodeActionTriggerType; readonly filter?: CodeActionFilter; diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 95b8e9813c8c0..ef800dc9f59d7 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -13,7 +13,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { DocumentColorProvider, Hover as MarkdownHover, HoverProviderRegistry, IColor, TokenizationRegistry } from 'vs/editor/common/modes'; +import { DocumentColorProvider, Hover as MarkdownHover, HoverProviderRegistry, IColor, TokenizationRegistry, CodeActionTriggerType } from 'vs/editor/common/modes'; import { getColorPresentations } from 'vs/editor/contrib/colorPicker/color'; import { ColorDetector } from 'vs/editor/contrib/colorPicker/colorDetector'; import { ColorPickerModel } from 'vs/editor/contrib/colorPicker/colorPickerModel'; @@ -34,7 +34,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { getCodeActions, CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; import { QuickFixAction, QuickFixController } from 'vs/editor/contrib/codeAction/codeActionCommands'; -import { CodeActionKind, CodeActionTriggerType } from 'vs/editor/contrib/codeAction/types'; +import { CodeActionKind, CodeActionTrigger } from 'vs/editor/contrib/codeAction/types'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -186,6 +186,11 @@ class ModesContentComputer implements IHoverComputer { } } +const markerCodeActionTrigger: CodeActionTrigger = { + type: CodeActionTriggerType.Manual, + filter: { include: CodeActionKind.QuickFix } +}; + export class ModesContentHoverWidget extends ContentHoverWidget { static readonly ID = 'editor.contrib.modesContentHoverWidget'; @@ -575,7 +580,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { showing = true; const controller = QuickFixController.get(this._editor); const elementPosition = dom.getDomNodePagePosition(target); - controller.showCodeActions(actions, { + controller.showCodeActions(markerCodeActionTrigger, actions, { x: elementPosition.left + 6, y: elementPosition.top + elementPosition.height + 6 }); @@ -592,7 +597,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { return getCodeActions( this._editor.getModel()!, new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn), - { type: CodeActionTriggerType.Manual, filter: { include: CodeActionKind.QuickFix } }, + markerCodeActionTrigger, cancellationToken); }); } diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index 667865985c19a..b8f663529418a 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -14,11 +14,11 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { ITextModel } from 'vs/editor/common/model'; -import { CodeAction } from 'vs/editor/common/modes'; +import { CodeAction, CodeActionTriggerType } from 'vs/editor/common/modes'; import { shouldSynchronizeModel } from 'vs/editor/common/services/modelService'; import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands'; -import { CodeActionKind, CodeActionTriggerType } from 'vs/editor/contrib/codeAction/types'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { localize } from 'vs/nls'; diff --git a/src/vs/workbench/contrib/codeActions/common/documentationContribution.ts b/src/vs/workbench/contrib/codeActions/common/documentationContribution.ts index 17cf4fa1d9f53..9c2b75c277dc4 100644 --- a/src/vs/workbench/contrib/codeActions/common/documentationContribution.ts +++ b/src/vs/workbench/contrib/codeActions/common/documentationContribution.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { ITextModel } from 'vs/editor/common/model'; -import { CodeAction, CodeActionContext, CodeActionList, CodeActionProvider, CodeActionProviderRegistry } from 'vs/editor/common/modes'; +import * as modes from 'vs/editor/common/modes'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -16,7 +16,7 @@ import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensi import { DocumentationExtensionPoint } from './documentationExtensionPoint'; -export class CodeActionDocumentationContribution extends Disposable implements IWorkbenchContribution, CodeActionProvider { +export class CodeActionDocumentationContribution extends Disposable implements IWorkbenchContribution, modes.CodeActionProvider { private contributions: { title: string; @@ -24,13 +24,18 @@ export class CodeActionDocumentationContribution extends Disposable implements I command: string; }[] = []; + private readonly emptyCodeActionsList = { + actions: [], + dispose: () => { } + }; + constructor( extensionPoint: IExtensionPoint, @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { super(); - CodeActionProviderRegistry.register('*', this); + this._register(modes.CodeActionProviderRegistry.register('*', this)); extensionPoint.setHandler(points => { this.contributions = []; @@ -56,36 +61,24 @@ export class CodeActionDocumentationContribution extends Disposable implements I }); } - async provideCodeActions(_model: ITextModel, _range: Range | Selection, context: CodeActionContext, _token: CancellationToken): Promise { - if (CodeActionKind.Refactor.value !== context.only) { - return { - actions: [], - dispose: () => { } - }; - } - - const actions: CodeAction[] = []; + async provideCodeActions(_model: ITextModel, _range: Range | Selection, context: modes.CodeActionContext, _token: CancellationToken): Promise { + return this.emptyCodeActionsList; + } - for (const contribution of this.contributions) { - if (!this.contextKeyService.contextMatchesRules(contribution.when)) { - continue; + public _getAdditionalMenuItems(context: modes.CodeActionContext, actions: readonly modes.CodeAction[]): modes.Command[] { + if (context.only !== CodeActionKind.Refactor.value) { + if (!actions.some(action => action.kind && CodeActionKind.Refactor.contains(new CodeActionKind(action.kind)))) { + return []; } + } - actions.push({ - title: contribution.title, - kind: CodeActionKind.RefactorDocumentation.value, - command: { + return this.contributions + .filter(contribution => this.contextKeyService.contextMatchesRules(contribution.when)) + .map(contribution => { + return { id: contribution.command, title: contribution.title - } + }; }); - } - - return { - actions, - dispose: () => { } - }; } - - public readonly providedCodeActionKinds = [CodeActionKind.RefactorDocumentation.value] as const; } diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index d7d54627f0443..3e5306bc9ede0 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -37,11 +37,12 @@ import { CancelablePromise, createCancelablePromise, Delayer } from 'vs/base/com import { IModelService } from 'vs/editor/common/services/modelService'; import { Range } from 'vs/editor/common/core/range'; import { getCodeActions, CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; -import { CodeActionKind, CodeActionTriggerType } from 'vs/editor/contrib/codeAction/types'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { ITextModel } from 'vs/editor/common/model'; import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands'; import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; +import { CodeActionTriggerType } from 'vs/editor/common/modes'; export type TreeElement = ResourceMarkers | Marker | RelatedInformation; @@ -548,7 +549,9 @@ export class MarkerViewModel extends Disposable { if (model) { if (!this.codeActionsPromise) { this.codeActionsPromise = createCancelablePromise(cancellationToken => { - return getCodeActions(model, new Range(this.marker.range.startLineNumber, this.marker.range.startColumn, this.marker.range.endLineNumber, this.marker.range.endColumn), { type: CodeActionTriggerType.Manual, filter: { include: CodeActionKind.QuickFix } }, cancellationToken).then(actions => { + return getCodeActions(model, new Range(this.marker.range.startLineNumber, this.marker.range.startColumn, this.marker.range.endLineNumber, this.marker.range.endColumn), { + type: CodeActionTriggerType.Manual, filter: { include: CodeActionKind.QuickFix } + }, cancellationToken).then(actions => { return this._register(actions); }); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index 8e6fb02412c59..b4b04e1cc8334 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -47,7 +47,6 @@ import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { dispose } from 'vs/base/common/lifecycle'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { CodeActionTriggerType } from 'vs/editor/contrib/codeAction/types'; const defaultSelector = { scheme: 'far' }; const model: ITextModel = EditorModel.createFromString( @@ -590,7 +589,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: CodeActionTriggerType.Manual }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, CancellationToken.None); assert.equal(actions.length, 2); const [first, second] = actions; assert.equal(first.title, 'Testing1'); @@ -614,7 +613,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: CodeActionTriggerType.Manual }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, CancellationToken.None); assert.equal(actions.length, 1); const [first] = actions; assert.equal(first.title, 'Testing1'); @@ -637,7 +636,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: CodeActionTriggerType.Manual }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, CancellationToken.None); assert.equal(actions.length, 1); }); @@ -655,7 +654,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: CodeActionTriggerType.Manual }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, CancellationToken.None); assert.equal(actions.length, 1); }); From 95793304ccd41a2e19473528a70255a149e40136 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 14 Jan 2020 13:03:59 -0800 Subject: [PATCH 297/315] Also show extract to function as disabled in js/ts Currently we only show `extract constant` --- .../src/features/refactor.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/extensions/typescript-language-features/src/features/refactor.ts b/extensions/typescript-language-features/src/features/refactor.ts index 017ea358b2a37..c866665f041ee 100644 --- a/extensions/typescript-language-features/src/features/refactor.ts +++ b/extensions/typescript-language-features/src/features/refactor.ts @@ -313,11 +313,26 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider { private appendInvalidActions(actions: vscode.CodeAction[]): vscode.CodeAction[] { if (!actions.some(action => action.kind && Extract_Constant.kind.contains(action.kind))) { - const disabledAction = new vscode.CodeAction('Extract to constant', Extract_Constant.kind); + const disabledAction = new vscode.CodeAction( + localize('extractConstant.disabled.title', "Extract to constant"), + Extract_Constant.kind); + disabledAction.disabled = { - reason: localize('extract.disabled', "The current selection cannot be extracted"), + reason: localize('extractConstant.disabled.reason', "The current selection cannot be extracted"), }; disabledAction.isPreferred = true; + + actions.push(disabledAction); + } + + if (!actions.some(action => action.kind && Extract_Function.kind.contains(action.kind))) { + const disabledAction = new vscode.CodeAction( + localize('extractFunction.disabled.title', "Extract to function"), + Extract_Function.kind); + + disabledAction.disabled = { + reason: localize('extractFunction.disabled.reason', "The current selection cannot be extracted"), + }; actions.push(disabledAction); } return actions; From b13740b4f38a93500fc967884ddb793c70b8022c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 14 Jan 2020 22:29:09 +0100 Subject: [PATCH 298/315] #88637 temp fix --- src/vs/workbench/contrib/debug/browser/debug.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 061cd5dcd61d7..5b666732de917 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -87,7 +87,7 @@ const openPanelKb: IKeybindings = { const VIEW_CONTAINER: ViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: DEBUG_PANEL_ID, - name: nls.localize('runAndDebug', "Run and Debug"), + name: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, 'Debug Console'), ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [DEBUG_PANEL_ID, DEBUG_PANEL_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), focusCommand: { id: OpenDebugPanelAction.ID, From 60beab2535f09dab971d6049fa5c0bbd0631339e Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Tue, 14 Jan 2020 14:50:37 -0800 Subject: [PATCH 299/315] Move settings sync auth into built in extension --- extensions/vscode-account/.vscodeignore | 10 + .../vscode-account/media}/auth.css | 0 .../vscode-account/media}/auth.html | 3 + extensions/vscode-account/package.json | 30 + extensions/vscode-account/src/AADHelper.ts | 245 +++++++ .../vscode-account/src}/authServer.ts | 40 +- extensions/vscode-account/src/extension.ts | 38 + extensions/vscode-account/src/keychain.ts | 66 ++ extensions/vscode-account/src/utils.ts | 8 + .../vscode-account/src/vscode.proposed.d.ts | 46 ++ extensions/vscode-account/tsconfig.json | 24 + extensions/vscode-account/yarn.lock | 658 ++++++++++++++++++ .../sharedProcess/sharedProcessMain.ts | 14 +- src/vs/editor/common/modes.ts | 19 + src/vs/platform/auth/common/auth.ts | 31 - src/vs/platform/auth/common/authTokenIpc.ts | 31 - .../auth/electron-browser/authTokenService.ts | 276 -------- .../platform/product/common/productService.ts | 7 - .../common/userDataAuthTokenService.ts | 33 + .../userDataSync/common/userDataAutoSync.ts | 20 +- .../userDataSync/common/userDataSync.ts | 11 + .../userDataSync/common/userDataSyncIpc.ts | 21 +- .../common/userDataSyncService.ts | 22 +- .../common/userDataSyncStoreService.ts | 6 +- .../electron-browser/userDataAutoSync.ts | 5 +- src/vs/vscode.proposed.d.ts | 23 +- .../api/browser/extensionHost.contribution.ts | 1 + .../api/browser/mainThreadAuthentication.ts | 69 ++ .../workbench/api/common/extHost.api.impl.ts | 8 + .../workbench/api/common/extHost.protocol.ts | 17 +- .../api/common/extHostAuthentication.ts | 83 +++ .../userDataSync/browser/userDataAutoSync.ts | 5 +- .../userDataSync/browser/userDataSync.ts | 157 +++-- .../authToken/browser/authTokenService.ts | 74 -- .../electron-browser/authTokenService.ts | 61 -- .../browser/authenticationService.ts | 96 +++ .../userDataAuthTokenService.ts | 37 + src/vs/workbench/workbench.desktop.main.ts | 3 +- src/vs/workbench/workbench.web.main.ts | 5 +- 39 files changed, 1728 insertions(+), 575 deletions(-) create mode 100644 extensions/vscode-account/.vscodeignore rename {src/vs/platform/auth/common => extensions/vscode-account/media}/auth.css (100%) rename {src/vs/platform/auth/common => extensions/vscode-account/media}/auth.html (99%) create mode 100644 extensions/vscode-account/package.json create mode 100644 extensions/vscode-account/src/AADHelper.ts rename {src/vs/platform/auth/electron-browser => extensions/vscode-account/src}/authServer.ts (81%) create mode 100644 extensions/vscode-account/src/extension.ts create mode 100644 extensions/vscode-account/src/keychain.ts create mode 100644 extensions/vscode-account/src/utils.ts create mode 100644 extensions/vscode-account/src/vscode.proposed.d.ts create mode 100644 extensions/vscode-account/tsconfig.json create mode 100644 extensions/vscode-account/yarn.lock delete mode 100644 src/vs/platform/auth/common/auth.ts delete mode 100644 src/vs/platform/auth/common/authTokenIpc.ts delete mode 100644 src/vs/platform/auth/electron-browser/authTokenService.ts create mode 100644 src/vs/platform/userDataSync/common/userDataAuthTokenService.ts create mode 100644 src/vs/workbench/api/browser/mainThreadAuthentication.ts create mode 100644 src/vs/workbench/api/common/extHostAuthentication.ts delete mode 100644 src/vs/workbench/services/authToken/browser/authTokenService.ts delete mode 100644 src/vs/workbench/services/authToken/electron-browser/authTokenService.ts create mode 100644 src/vs/workbench/services/authentication/browser/authenticationService.ts create mode 100644 src/vs/workbench/services/userDataSync/electron-browser/userDataAuthTokenService.ts diff --git a/extensions/vscode-account/.vscodeignore b/extensions/vscode-account/.vscodeignore new file mode 100644 index 0000000000000..ed3f9d37c1f24 --- /dev/null +++ b/extensions/vscode-account/.vscodeignore @@ -0,0 +1,10 @@ +.vscode/** +.vscode-test/** +out/test/** +src/** +.gitignore +vsc-extension-quickstart.md +**/tsconfig.json +**/tslint.json +**/*.map +**/*.ts \ No newline at end of file diff --git a/src/vs/platform/auth/common/auth.css b/extensions/vscode-account/media/auth.css similarity index 100% rename from src/vs/platform/auth/common/auth.css rename to extensions/vscode-account/media/auth.css diff --git a/src/vs/platform/auth/common/auth.html b/extensions/vscode-account/media/auth.html similarity index 99% rename from src/vs/platform/auth/common/auth.html rename to extensions/vscode-account/media/auth.html index 8fe3e50e7b7cc..0fcba4e3c625b 100644 --- a/src/vs/platform/auth/common/auth.html +++ b/extensions/vscode-account/media/auth.html @@ -1,6 +1,7 @@ + @@ -8,6 +9,7 @@ + Visual Studio Code @@ -32,4 +34,5 @@ } + diff --git a/extensions/vscode-account/package.json b/extensions/vscode-account/package.json new file mode 100644 index 0000000000000..46b6c92ed3642 --- /dev/null +++ b/extensions/vscode-account/package.json @@ -0,0 +1,30 @@ +{ + "name": "login", + "publisher": "vscode", + "displayName": "Account", + "description": "", + "version": "0.0.1", + "engines": { + "vscode": "^1.42.0" + }, + "categories": [ + "Other" + ], + "enableProposedApi": true, + "activationEvents": [ + "*" + ], + "main": "./out/extension.js", + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "tsc -p ./", + "watch": "tsc -watch -p ./" + }, + "devDependencies": { + "typescript": "^3.7.4", + "tslint": "^5.12.1", + "@types/node": "^10.12.21", + "@types/keytar": "^4.0.1", + "@types/vscode": "^1.41.0" + } +} diff --git a/extensions/vscode-account/src/AADHelper.ts b/extensions/vscode-account/src/AADHelper.ts new file mode 100644 index 0000000000000..b15aec326971a --- /dev/null +++ b/extensions/vscode-account/src/AADHelper.ts @@ -0,0 +1,245 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as crypto from 'crypto'; +import * as vscode from 'vscode'; +import * as https from 'https'; +import * as querystring from 'querystring'; +import { keychain } from './keychain'; +import { toBase64UrlEncoding } from './utils'; +import { createServer, startServer } from './authServer'; + +const redirectUrl = 'https://vscode-redirect.azurewebsites.net/'; +const loginEndpointUrl = 'https://login.microsoftonline.com/'; +const clientId = 'aebc6443-996d-45c2-90f0-388ff96faa56'; +const scope = 'https://management.core.windows.net/.default offline_access'; +const tenant = 'common'; + +interface IToken { + expiresIn: string; // How long access token is valid, in seconds + accessToken: string; + refreshToken: string; +} + +export const onDidChangeAccounts = new vscode.EventEmitter(); + +export class AzureActiveDirectoryService { + private _token: IToken | undefined; + private _refreshTimeout: NodeJS.Timeout | undefined; + + public async initialize(): Promise { + const existingRefreshToken = await keychain.getToken(); + if (existingRefreshToken) { + await this.refreshToken(existingRefreshToken); + } + } + + private tokenToAccount(token: IToken): vscode.Account { + return { + id: '', + accessToken: token.accessToken, + displayName: this.getDisplayNameFromToken(token.accessToken) + }; + } + + private getDisplayNameFromToken(accessToken: string): string { + let displayName = 'user@example.com'; + try { + // TODO fixme + displayName = JSON.parse(atob(accessToken.split('.')[1])); + } catch (e) { + // Fall back to example display name + } + + return displayName; + } + + get accounts(): vscode.Account[] { + return this._token ? [this.tokenToAccount(this._token)] : []; + } + + public async login(): Promise { + const nonce = crypto.randomBytes(16).toString('base64'); + const { server, redirectPromise, codePromise } = createServer(nonce); + + let token: IToken | undefined; + try { + const port = await startServer(server); + vscode.env.openExternal(vscode.Uri.parse(`http://localhost:${port}/signin?nonce=${encodeURIComponent(nonce)}`)); + + const redirectReq = await redirectPromise; + if ('err' in redirectReq) { + const { err, res } = redirectReq; + res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unknown error')}` }); + res.end(); + throw err; + } + + const host = redirectReq.req.headers.host || ''; + const updatedPortStr = (/^[^:]+:(\d+)$/.exec(Array.isArray(host) ? host[0] : host) || [])[1]; + const updatedPort = updatedPortStr ? parseInt(updatedPortStr, 10) : port; + + const state = `${updatedPort},${encodeURIComponent(nonce)}`; + + const codeVerifier = toBase64UrlEncoding(crypto.randomBytes(32).toString('base64')); + const codeChallenge = toBase64UrlEncoding(crypto.createHash('sha256').update(codeVerifier).digest('base64')); + const loginUrl = `${loginEndpointUrl}${tenant}/oauth2/v2.0/authorize?response_type=code&response_mode=query&client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUrl)}&state=${state}&scope=${encodeURIComponent(scope)}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}`; + + await redirectReq.res.writeHead(302, { Location: loginUrl }); + redirectReq.res.end(); + + const codeRes = await codePromise; + const res = codeRes.res; + + try { + if ('err' in codeRes) { + throw codeRes.err; + } + token = await this.exchangeCodeForToken(codeRes.code, codeVerifier); + this.setToken(token); + res.writeHead(302, { Location: '/' }); + res.end(); + } catch (err) { + res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unknown error')}` }); + res.end(); + } + } finally { + setTimeout(() => { + server.close(); + }, 5000); + } + } + + private async setToken(token: IToken): Promise { + this._token = token; + + if (this._refreshTimeout) { + clearTimeout(this._refreshTimeout); + } + + this._refreshTimeout = setTimeout(async () => { + try { + await this.refreshToken(token.refreshToken); + } catch (e) { + vscode.window.showErrorMessage(`You have been signed out.`); + this._token = undefined; + } finally { + onDidChangeAccounts.fire(this.accounts); + } + }, 1000 * (parseInt(token.expiresIn) - 10)); + + await keychain.setToken(token.refreshToken); + } + + private async exchangeCodeForToken(code: string, codeVerifier: string): Promise { + return new Promise((resolve: (value: IToken) => void, reject) => { + try { + const postData = querystring.stringify({ + grant_type: 'authorization_code', + code: code, + client_id: clientId, + scope: scope, + code_verifier: codeVerifier, + redirect_uri: redirectUrl + }); + + const tokenUrl = vscode.Uri.parse(`${loginEndpointUrl}${tenant}/oauth2/v2.0/token`); + + const post = https.request({ + host: tokenUrl.authority, + path: tokenUrl.path, + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': postData.length + } + }, result => { + const buffer: Buffer[] = []; + result.on('data', (chunk: Buffer) => { + buffer.push(chunk); + }); + result.on('end', () => { + if (result.statusCode === 200) { + const json = JSON.parse(Buffer.concat(buffer).toString()); + resolve({ + expiresIn: json.expires_in, + accessToken: json.access_token, + refreshToken: json.refresh_token + }); + } else { + reject(new Error('Unable to login.')); + } + }); + }); + + post.write(postData); + + post.end(); + post.on('error', err => { + reject(err); + }); + + } catch (e) { + reject(e); + } + }); + } + + private async refreshToken(refreshToken: string): Promise { + return new Promise((resolve: (value: IToken) => void, reject) => { + const postData = querystring.stringify({ + refresh_token: refreshToken, + client_id: clientId, + grant_type: 'refresh_token', + scope: scope + }); + + const post = https.request({ + host: 'login.microsoftonline.com', + path: `/${tenant}/oauth2/v2.0/token`, + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': postData.length + } + }, result => { + const buffer: Buffer[] = []; + result.on('data', (chunk: Buffer) => { + buffer.push(chunk); + }); + result.on('end', () => { + if (result.statusCode === 200) { + const json = JSON.parse(Buffer.concat(buffer).toString()); + const token = { + expiresIn: json.expires_in, + accessToken: json.access_token, + refreshToken: json.refresh_token + }; + this.setToken(token); + resolve(token); + } else { + vscode.window.showInformationMessage(`error`); + reject(new Error('Bad!')); + } + }); + }); + + post.write(postData); + + post.end(); + post.on('error', err => { + reject(err); + }); + }); + } + + public async logout() { + delete this._token; + await keychain.deleteToken(); + if (this._refreshTimeout) { + clearTimeout(this._refreshTimeout); + } + } +} diff --git a/src/vs/platform/auth/electron-browser/authServer.ts b/extensions/vscode-account/src/authServer.ts similarity index 81% rename from src/vs/platform/auth/electron-browser/authServer.ts rename to extensions/vscode-account/src/authServer.ts index 57bd4fdfb5f90..b78803cec169a 100644 --- a/src/vs/platform/auth/electron-browser/authServer.ts +++ b/extensions/vscode-account/src/authServer.ts @@ -7,14 +7,46 @@ import * as http from 'http'; import * as url from 'url'; import * as fs from 'fs'; import * as net from 'net'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; -import { assertIsDefined } from 'vs/base/common/types'; +import * as path from 'path'; interface Deferred { resolve: (result: T | Promise) => void; reject: (reason: any) => void; } +const _typeof = { + number: 'number', + string: 'string', + undefined: 'undefined', + object: 'object', + function: 'function' +}; + +/** + * @returns whether the provided parameter is undefined. + */ +export function isUndefined(obj: any): obj is undefined { + return typeof (obj) === _typeof.undefined; +} + +/** + * @returns whether the provided parameter is undefined or null. + */ +export function isUndefinedOrNull(obj: any): obj is undefined | null { + return isUndefined(obj) || obj === null; +} + +/** + * Asserts that the argument passed in is neither undefined nor null. + */ +export function assertIsDefined(arg: T | null | undefined): T { + if (isUndefinedOrNull(arg)) { + throw new Error('Assertion Failed: argument is undefined or null'); + } + + return arg; +} + export function createTerminateServer(server: http.Server) { const sockets: Record = {}; let socketCount = 0; @@ -140,10 +172,10 @@ export function createServer(nonce: string) { } break; case '/': - sendFile(res, getPathFromAmdModule(require, '../common/auth.html'), 'text/html; charset=utf-8'); + sendFile(res, path.join(__dirname, '../media/auth.html'), 'text/html; charset=utf-8'); break; case '/auth.css': - sendFile(res, getPathFromAmdModule(require, '../common/auth.css'), 'text/css; charset=utf-8'); + sendFile(res, path.join(__dirname, '../media/auth.css'), 'text/css; charset=utf-8'); break; case '/callback': deferredCode.resolve(callback(nonce, reqUrl) diff --git a/extensions/vscode-account/src/extension.ts b/extensions/vscode-account/src/extension.ts new file mode 100644 index 0000000000000..53793dc4c43c1 --- /dev/null +++ b/extensions/vscode-account/src/extension.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { AzureActiveDirectoryService, onDidChangeAccounts } from './AADHelper'; + +export async function activate(context: vscode.ExtensionContext) { + + const loginService = new AzureActiveDirectoryService(); + + await loginService.initialize(); + + vscode.authentication.registerAuthenticationProvider({ + id: 'MSA', + displayName: 'Microsoft Account', // TODO localize + onDidChangeAccounts: onDidChangeAccounts.event, + accounts: loginService.accounts, + login: async () => { + try { + await loginService.login(); + return loginService.accounts[0]!; + } catch (e) { + vscode.window.showErrorMessage(`Logging in failed: ${e}`); + throw e; + } + }, + logout: async (id: string) => { + return loginService.logout(); + } + }); + + return; +} + +// this method is called when your extension is deactivated +export function deactivate() { } diff --git a/extensions/vscode-account/src/keychain.ts b/extensions/vscode-account/src/keychain.ts new file mode 100644 index 0000000000000..a53d848c76c0b --- /dev/null +++ b/extensions/vscode-account/src/keychain.ts @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// keytar depends on a native module shipped in vscode, so this is +// how we load it +import * as keytarType from 'keytar'; + +function getKeytar(): Keytar | undefined { + try { + return require('keytar'); + } catch (err) { + console.log(err); + } + + return undefined; +} + +export type Keytar = { + getPassword: typeof keytarType['getPassword']; + setPassword: typeof keytarType['setPassword']; + deletePassword: typeof keytarType['deletePassword']; +}; + +const SERVICE_ID = 'vscode.login'; +const ACCOUNT_ID = 'account'; + +export class Keychain { + private keytar: Keytar; + + constructor() { + const keytar = getKeytar(); + if (!keytar) { + throw new Error('System keychain unavailable'); + } + + this.keytar = keytar; + } + + async setToken(token: string): Promise { + try { + return await this.keytar.setPassword(SERVICE_ID, ACCOUNT_ID, token); + } catch (e) { + // Ignore + } + } + + async getToken() { + try { + return await this.keytar.getPassword(SERVICE_ID, ACCOUNT_ID); + } catch (e) { + // Ignore + } + } + + async deleteToken() { + try { + return await this.keytar.deletePassword(SERVICE_ID, ACCOUNT_ID); + } catch (e) { + // Ignore + } + } +} + +export const keychain = new Keychain(); diff --git a/extensions/vscode-account/src/utils.ts b/extensions/vscode-account/src/utils.ts new file mode 100644 index 0000000000000..164f2236221b6 --- /dev/null +++ b/extensions/vscode-account/src/utils.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export function toBase64UrlEncoding(base64string: string) { + return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); // Need to use base64url encoding +} diff --git a/extensions/vscode-account/src/vscode.proposed.d.ts b/extensions/vscode-account/src/vscode.proposed.d.ts new file mode 100644 index 0000000000000..0f4a53fee1a22 --- /dev/null +++ b/extensions/vscode-account/src/vscode.proposed.d.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * This is the place for API experiments and proposals. + * These API are NOT stable and subject to change. They are only available in the Insiders + * distribution and CANNOT be used in published extensions. + * + * To test these API in local environment: + * - Use Insiders release of VS Code. + * - Add `"enableProposedApi": true` to your package.json. + * - Copy this file to your project. + */ + +declare module 'vscode' { + + export interface Account { + readonly id: string; + readonly accessToken: string; + readonly displayName: string; + } + + export interface AuthenticationProvider { + readonly id: string; + readonly displayName: string; + + readonly accounts: ReadonlyArray; + readonly onDidChangeAccounts: Event>; + + login(): Promise; + logout(accountId: string): Promise; + } + + export namespace authentication { + export function registerAuthenticationProvider(provider: AuthenticationProvider): Disposable; + } + + // #region Ben - extension auth flow (desktop+web) + + export namespace env { + + export function asExternalUri(target: Uri): Thenable + } +} diff --git a/extensions/vscode-account/tsconfig.json b/extensions/vscode-account/tsconfig.json new file mode 100644 index 0000000000000..46be6dc9581ab --- /dev/null +++ b/extensions/vscode-account/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "out", + "lib": [ + "es6", + "es2016", + "dom" + ], + "typeRoots": [ + "node_modules/@types", + "src/typings" + ], + "sourceMap": true, + "rootDir": "src", + "strict": true, + "noImplicitAny": true + }, + "exclude": [ + "node_modules", + ".vscode-test" + ] +} diff --git a/extensions/vscode-account/yarn.lock b/extensions/vscode-account/yarn.lock new file mode 100644 index 0000000000000..4fc295de4b92d --- /dev/null +++ b/extensions/vscode-account/yarn.lock @@ -0,0 +1,658 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" + integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== + dependencies: + "@babel/highlight" "^7.8.3" + +"@babel/highlight@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797" + integrity sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg== + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" + +"@types/keytar@^4.0.1": + version "4.4.2" + resolved "https://registry.yarnpkg.com/@types/keytar/-/keytar-4.4.2.tgz#49ef917d6cbb4f19241c0ab50cd35097b5729b32" + integrity sha512-xtQcDj9ruGnMwvSu1E2BH4SFa5Dv2PvSPd0CKEBLN5hEj/v5YpXJY+B6hAfuKIbvEomD7vJTc/P1s1xPNh2kRw== + dependencies: + keytar "*" + +"@types/node@^10.12.21": + version "10.17.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.13.tgz#ccebcdb990bd6139cd16e84c39dc2fb1023ca90c" + integrity sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg== + +"@types/vscode@^1.41.0": + version "1.41.0" + resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.41.0.tgz#b0d75920220f84e07093285e59180c0f11d336cd" + integrity sha512-7SfeY5u9jgiELwxyLB3z7l6l/GbN9CqpCQGkcRlB7tKRFBxzbz2PoBfGrLxI1vRfUCIq5+hg5vtDHExwq5j3+A== + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +bl@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88" + integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A== + dependencies: + readable-stream "^3.0.1" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +builtin-modules@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= + +chalk@^2.0.0, chalk@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chownr@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" + integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw== + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +commander@^2.12.1: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +decompress-response@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" + integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== + dependencies: + mimic-response "^2.0.0" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= + +glob@^7.1.1: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +keytar@*: + version "5.0.0" + resolved "https://registry.yarnpkg.com/keytar/-/keytar-5.0.0.tgz#c89b6b7a4608fd7af633d9f8474b1a7eb97cbe6f" + integrity sha512-a5UheK59YOlJf9i+2Osaj/kkH6mK0RCHVMtJ84u6ZfbfRIbOJ/H4b5VlOF/LgNHF6s78dRSBzZnvIuPiBKv6wg== + dependencies: + nan "2.14.0" + prebuild-install "5.3.3" + +mimic-response@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.0.0.tgz#996a51c60adf12cb8a87d7fb8ef24c2f3d5ebb46" + integrity sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +nan@2.14.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" + integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + +napi-build-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.1.tgz#1381a0f92c39d66bf19852e7873432fc2123e508" + integrity sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA== + +node-abi@^2.7.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.13.0.tgz#e2f2ec444d0aca3ea1b3874b6de41d1665828f63" + integrity sha512-9HrZGFVTR5SOu3PZAnAY2hLO36aW1wmA+FDsVkr85BTST32TLCA1H/AEcatVRAsWLyXS3bqUDYCAjq5/QGuSTA== + dependencies: + semver "^5.4.1" + +noop-logger@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" + integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI= + +npmlog@^4.0.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +prebuild-install@5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.3.tgz#ef4052baac60d465f5ba6bf003c9c1de79b9da8e" + integrity sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g== + dependencies: + detect-libc "^1.0.3" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.0" + mkdirp "^0.5.1" + napi-build-utils "^1.0.1" + node-abi "^2.7.0" + noop-logger "^0.1.1" + npmlog "^4.0.1" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^3.0.3" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + which-pm-runs "^1.0.0" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +readable-stream@^2.0.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.1, readable-stream@^3.1.1: + version "3.4.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc" + integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +resolve@^1.3.2: + version "1.14.2" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.14.2.tgz#dbf31d0fa98b1f29aa5169783b9c290cb865fea2" + integrity sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ== + dependencies: + path-parse "^1.0.6" + +safe-buffer@^5.0.1, safe-buffer@~5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +semver@^5.3.0, semver@^5.4.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +signal-exit@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +simple-concat@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" + integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY= + +simple-get@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3" + integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA== + dependencies: + decompress-response "^4.2.0" + once "^1.3.1" + simple-concat "^1.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +tar-fs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.0.tgz#677700fc0c8b337a78bee3623fdc235f21d7afad" + integrity sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA== + dependencies: + chownr "^1.1.1" + mkdirp "^0.5.1" + pump "^3.0.0" + tar-stream "^2.0.0" + +tar-stream@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3" + integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw== + dependencies: + bl "^3.0.0" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tslib@^1.8.0, tslib@^1.8.1: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + +tslint@^5.12.1: + version "5.20.1" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.20.1.tgz#e401e8aeda0152bc44dd07e614034f3f80c67b7d" + integrity sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg== + dependencies: + "@babel/code-frame" "^7.0.0" + builtin-modules "^1.1.1" + chalk "^2.3.0" + commander "^2.12.1" + diff "^4.0.1" + glob "^7.1.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + mkdirp "^0.5.1" + resolve "^1.3.2" + semver "^5.3.0" + tslib "^1.8.0" + tsutils "^2.29.0" + +tsutils@^2.29.0: + version "2.29.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" + integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== + dependencies: + tslib "^1.8.1" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +typescript@^3.7.4: + version "3.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.4.tgz#1743a5ec5fef6a1fa9f3e4708e33c81c73876c19" + integrity sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw== + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +which-pm-runs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" + integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= + +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index ee7fab8ba629f..4db9464573e21 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -50,20 +50,18 @@ import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, ISettingsSyncService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; -import { UserDataSyncChannel, UserDataSyncUtilServiceClient, SettingsSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; +import { UserDataSyncChannel, UserDataSyncUtilServiceClient, SettingsSyncChannel, UserDataAuthTokenServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { LoggerService } from 'vs/platform/log/node/loggerService'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; -import { IAuthTokenService } from 'vs/platform/auth/common/auth'; -import { AuthTokenService } from 'vs/platform/auth/electron-browser/authTokenService'; -import { AuthTokenChannel } from 'vs/platform/auth/common/authTokenIpc'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; import { UserDataAutoSync } from 'vs/platform/userDataSync/electron-browser/userDataAutoSync'; import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; +import { UserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataAuthTokenService'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -183,7 +181,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService)); services.set(ICredentialsService, new SyncDescriptor(KeytarCredentialsService)); - services.set(IAuthTokenService, new SyncDescriptor(AuthTokenService)); + services.set(IUserDataAuthTokenService, new SyncDescriptor(UserDataAuthTokenService)); services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService)); services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', activeWindowRouter))); services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService)); @@ -207,8 +205,8 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const diagnosticsChannel = new DiagnosticsChannel(diagnosticsService); server.registerChannel('diagnostics', diagnosticsChannel); - const authTokenService = accessor.get(IAuthTokenService); - const authTokenChannel = new AuthTokenChannel(authTokenService); + const authTokenService = accessor.get(IUserDataAuthTokenService); + const authTokenChannel = new UserDataAuthTokenServiceChannel(authTokenService); server.registerChannel('authToken', authTokenChannel); const settingsSyncService = accessor.get(ISettingsSyncService); diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 66f65392bd6b3..52c783c91177f 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -1280,6 +1280,25 @@ export interface RenameProvider { resolveRenameLocation?(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult; } +/** + * @internal + */ +export interface Account { + id: string; + accessToken: string; + displayName: string; +} + +/** + * @internal + */ +export interface AuthenticationProvider { + getAccount(): Promise; + onDidChangeAccount: Event; + login(): Promise; + logout(accountId: string): Promise; +} + export interface Command { id: string; diff --git a/src/vs/platform/auth/common/auth.ts b/src/vs/platform/auth/common/auth.ts deleted file mode 100644 index 81af0bbf0a4a7..0000000000000 --- a/src/vs/platform/auth/common/auth.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { Event, Emitter } from 'vs/base/common/event'; -import { URI } from 'vs/base/common/uri'; - -export const enum AuthTokenStatus { - Initializing = 'Initializing', - SignedOut = 'SignedOut', - SignedIn = 'SignedIn', - SigningIn = 'SigningIn', - RefreshingToken = 'RefreshingToken' -} - -export const IAuthTokenService = createDecorator('IAuthTokenService'); - -export interface IAuthTokenService { - _serviceBrand: undefined; - - readonly status: AuthTokenStatus; - readonly onDidChangeStatus: Event; - readonly _onDidGetCallback: Emitter; - - getToken(): Promise; - refreshToken(): Promise; - login(): Promise; - logout(): Promise; -} diff --git a/src/vs/platform/auth/common/authTokenIpc.ts b/src/vs/platform/auth/common/authTokenIpc.ts deleted file mode 100644 index eff088c111473..0000000000000 --- a/src/vs/platform/auth/common/authTokenIpc.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { Event } from 'vs/base/common/event'; -import { IAuthTokenService } from 'vs/platform/auth/common/auth'; - -export class AuthTokenChannel implements IServerChannel { - - constructor(private readonly service: IAuthTokenService) { } - - listen(_: unknown, event: string): Event { - switch (event) { - case 'onDidChangeStatus': return this.service.onDidChangeStatus; - } - throw new Error(`Event not found: ${event}`); - } - - call(context: any, command: string, args?: any): Promise { - switch (command) { - case '_getInitialStatus': return Promise.resolve(this.service.status); - case 'getToken': return this.service.getToken(); - case 'refreshToken': return this.service.refreshToken(); - case 'login': return this.service.login(); - case 'logout': return this.service.logout(); - } - throw new Error('Invalid call'); - } -} diff --git a/src/vs/platform/auth/electron-browser/authTokenService.ts b/src/vs/platform/auth/electron-browser/authTokenService.ts deleted file mode 100644 index 971e8f7a96717..0000000000000 --- a/src/vs/platform/auth/electron-browser/authTokenService.ts +++ /dev/null @@ -1,276 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as crypto from 'crypto'; -import * as https from 'https'; -import { Event, Emitter } from 'vs/base/common/event'; -import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; -import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import { generateUuid } from 'vs/base/common/uuid'; -import { shell } from 'electron'; -import { createServer, startServer } from 'vs/platform/auth/electron-browser/authServer'; -import { IProductService } from 'vs/platform/product/common/productService'; - -const SERVICE_NAME = 'VS Code'; -const ACCOUNT = 'MyAccount'; - -const activeDirectoryResourceId = 'https://management.core.windows.net/'; - -function toQuery(obj: any): string { - return Object.keys(obj).map(key => `${key}=${obj[key]}`).join('&'); -} - -function toBase64UrlEncoding(base64string: string) { - return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); // Need to use base64url encoding -} - -export interface IToken { - expiresIn: string; // How long access token is valid, in seconds - expiresOn: string; // When the access token expires in epoch time - accessToken: string; - refreshToken: string; -} - -export class AuthTokenService extends Disposable implements IAuthTokenService { - _serviceBrand: undefined; - - private _status: AuthTokenStatus = AuthTokenStatus.Initializing; - get status(): AuthTokenStatus { return this._status; } - private _onDidChangeStatus: Emitter = this._register(new Emitter()); - readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; - - public readonly _onDidGetCallback: Emitter = this._register(new Emitter()); - readonly onDidGetCallback: Event = this._onDidGetCallback.event; - - private _activeToken: IToken | undefined; - - constructor( - @ICredentialsService private readonly credentialsService: ICredentialsService, - @IProductService private readonly productService: IProductService - ) { - super(); - if (!this.productService.auth) { - return; - } - - this.credentialsService.getPassword(SERVICE_NAME, ACCOUNT).then(storedRefreshToken => { - if (storedRefreshToken) { - this.refresh(storedRefreshToken); - } else { - this.setStatus(AuthTokenStatus.SignedOut); - } - }); - } - - public async login(): Promise { - if (!this.productService.auth) { - throw new Error('Authentication is not configured.'); - } - - this.setStatus(AuthTokenStatus.SigningIn); - - const nonce = generateUuid(); - const { server, redirectPromise, codePromise } = createServer(nonce); - - try { - const port = await startServer(server); - shell.openExternal(`http://localhost:${port}/signin?nonce=${encodeURIComponent(nonce)}`); - - const redirectReq = await redirectPromise; - if ('err' in redirectReq) { - const { err, res } = redirectReq; - res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unkown error')}` }); - res.end(); - throw err; - } - - const host = redirectReq.req.headers.host || ''; - const updatedPortStr = (/^[^:]+:(\d+)$/.exec(Array.isArray(host) ? host[0] : host) || [])[1]; - const updatedPort = updatedPortStr ? parseInt(updatedPortStr, 10) : port; - - const state = `${updatedPort},${encodeURIComponent(nonce)}`; - - const codeVerifier = toBase64UrlEncoding(crypto.randomBytes(32).toString('base64')); - const codeChallenge = toBase64UrlEncoding(crypto.createHash('sha256').update(codeVerifier).digest('base64')); - - let uri = URI.parse(this.productService.auth.loginUrl); - uri = uri.with({ - query: `response_type=code&client_id=${encodeURIComponent(this.productService.auth.clientId)}&redirect_uri=${this.productService.auth.redirectUrl}&state=${encodeURIComponent(state)}&resource=${activeDirectoryResourceId}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}` - }); - - await redirectReq.res.writeHead(302, { Location: uri.toString(true) }); - redirectReq.res.end(); - - const codeRes = await codePromise; - const res = codeRes.res; - - try { - if ('err' in codeRes) { - throw codeRes.err; - } - const token = await this.exchangeCodeForToken(codeRes.code, codeVerifier); - this.setToken(token); - res.writeHead(302, { Location: '/' }); - res.end(); - } catch (err) { - res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unkown error')}` }); - res.end(); - } - } finally { - setTimeout(() => { - server.close(); - }, 5000); - } - - } - - public getToken(): Promise { - return Promise.resolve(this._activeToken?.accessToken); - } - - public async refreshToken(): Promise { - if (!this._activeToken) { - throw new Error('No token to refresh'); - } - - this.refresh(this._activeToken.refreshToken); - } - - private setToken(token: IToken) { - this._activeToken = token; - this.credentialsService.setPassword(SERVICE_NAME, ACCOUNT, token.refreshToken); - this.setStatus(AuthTokenStatus.SignedIn); - } - - private exchangeCodeForToken(code: string, codeVerifier: string): Promise { - return new Promise((resolve: (value: IToken) => void, reject) => { - try { - if (!this.productService.auth) { - throw new Error('Authentication is not configured.'); - } - - const postData = toQuery({ - grant_type: 'authorization_code', - code: code, - client_id: this.productService.auth?.clientId, - code_verifier: codeVerifier, - redirect_uri: this.productService.auth?.redirectUrl - }); - - const tokenUrl = URI.parse(this.productService.auth.tokenUrl); - - const post = https.request({ - host: tokenUrl.authority, - path: tokenUrl.path, - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': postData.length - } - }, result => { - const buffer: Buffer[] = []; - result.on('data', (chunk: Buffer) => { - buffer.push(chunk); - }); - result.on('end', () => { - if (result.statusCode === 200) { - const json = JSON.parse(Buffer.concat(buffer).toString()); - resolve({ - expiresIn: json.access_token, - expiresOn: json.expires_on, - accessToken: json.access_token, - refreshToken: json.refresh_token - }); - } else { - reject(new Error('Bad!')); - } - }); - }); - - post.write(postData); - - post.end(); - post.on('error', err => { - reject(err); - }); - - } catch (e) { - reject(e); - } - }); - } - - private async refresh(refreshToken: string): Promise { - return new Promise((resolve, reject) => { - if (!this.productService.auth) { - throw new Error('Authentication is not configured.'); - } - - this.setStatus(AuthTokenStatus.RefreshingToken); - const postData = toQuery({ - refresh_token: refreshToken, - client_id: this.productService.auth?.clientId, - grant_type: 'refresh_token', - resource: activeDirectoryResourceId - }); - - const tokenUrl = URI.parse(this.productService.auth.tokenUrl); - - const post = https.request({ - host: tokenUrl.authority, - path: tokenUrl.path, - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': postData.length - } - }, result => { - const buffer: Buffer[] = []; - result.on('data', (chunk: Buffer) => { - buffer.push(chunk); - }); - result.on('end', () => { - if (result.statusCode === 200) { - const json = JSON.parse(Buffer.concat(buffer).toString()); - this.setToken({ - expiresIn: json.access_token, - expiresOn: json.expires_on, - accessToken: json.access_token, - refreshToken: json.refresh_token - }); - resolve(); - } else { - reject(new Error('Refreshing token failed.')); - } - }); - }); - - post.write(postData); - - post.end(); - post.on('error', err => { - this.setStatus(AuthTokenStatus.SignedOut); - reject(err); - }); - }); - } - - async logout(): Promise { - await this.credentialsService.deletePassword(SERVICE_NAME, ACCOUNT); - this._activeToken = undefined; - this.setStatus(AuthTokenStatus.SignedOut); - } - - private setStatus(status: AuthTokenStatus): void { - if (this._status !== status) { - this._status = status; - this._onDidChangeStatus.fire(status); - } - } - -} - diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts index 1c37427fd5d02..120fd666444c3 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -102,13 +102,6 @@ export interface IProductConfiguration { readonly msftInternalDomains?: string[]; readonly linkProtectionTrustedDomains?: readonly string[]; - - readonly auth?: { - loginUrl: string; - tokenUrl: string; - redirectUrl: string; - clientId: string; - }; } export interface IExeBasedExtensionTip { diff --git a/src/vs/platform/userDataSync/common/userDataAuthTokenService.ts b/src/vs/platform/userDataSync/common/userDataAuthTokenService.ts new file mode 100644 index 0000000000000..03ba0c45dfe98 --- /dev/null +++ b/src/vs/platform/userDataSync/common/userDataAuthTokenService.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync'; + +export class UserDataAuthTokenService extends Disposable implements IUserDataAuthTokenService { + + _serviceBrand: any; + + private _onDidChangeToken: Emitter = this._register(new Emitter()); + readonly onDidChangeToken: Event = this._onDidChangeToken.event; + + private _token: string | undefined; + + constructor() { + super(); + } + + async getToken(): Promise { + return this._token; + } + + async setToken(token: string | undefined): Promise { + if (token !== this._token) { + this._token = token; + this._onDidChangeToken.fire(token); + } + } +} diff --git a/src/vs/platform/userDataSync/common/userDataAutoSync.ts b/src/vs/platform/userDataSync/common/userDataAutoSync.ts index 7512345914141..e6a3d612dee02 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSync.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSync.ts @@ -3,12 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, SyncStatus, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { timeout } from 'vs/base/common/async'; import { Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { timeout } from 'vs/base/common/async'; -import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; +import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync'; export class UserDataAutoSync extends Disposable { @@ -18,16 +17,17 @@ export class UserDataAutoSync extends Disposable { @IConfigurationService private readonly configurationService: IConfigurationService, @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, - @IAuthTokenService private readonly authTokenService: IAuthTokenService, + @IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService, ) { super(); this.updateEnablement(false); - this._register(Event.any(authTokenService.onDidChangeStatus, userDataSyncService.onDidChangeStatus)(() => this.updateEnablement(true))); + this._register(Event.any(userDataAuthTokenService.onDidChangeToken)(() => this.updateEnablement(true))); + this._register(Event.any(userDataSyncService.onDidChangeStatus)(() => this.updateEnablement(true))); this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('sync.enable'))(() => this.updateEnablement(true))); } - private updateEnablement(stopIfDisabled: boolean): void { - const enabled = this.isSyncEnabled(); + private async updateEnablement(stopIfDisabled: boolean): Promise { + const enabled = await this.isSyncEnabled(); if (this.enabled === enabled) { return; } @@ -60,10 +60,10 @@ export class UserDataAutoSync extends Disposable { } } - private isSyncEnabled(): boolean { + private async isSyncEnabled(): Promise { return this.configurationService.getValue('sync.enable') && this.userDataSyncService.status !== SyncStatus.Uninitialized - && this.authTokenService.status === AuthTokenStatus.SignedIn; + && !!(await this.userDataAuthTokenService.getToken()); } } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index eacd383b6503a..71c11fdd64595 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -206,6 +206,17 @@ export interface IUserDataSyncUtilService { resolveFormattingOptions(resource: URI): Promise; } +export const IUserDataAuthTokenService = createDecorator('IUserDataAuthTokenService'); + +export interface IUserDataAuthTokenService { + _serviceBrand: undefined; + + readonly onDidChangeToken: Event; + + getToken(): Promise; + setToken(accessToken: string | undefined): Promise; +} + export const IUserDataSyncLogService = createDecorator('IUserDataSyncLogService'); export interface IUserDataSyncLogService extends ILogService { } diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index 61f634471baf1..8e647cfef9938 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -5,7 +5,7 @@ import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; -import { IUserDataSyncService, IUserDataSyncUtilService, ISettingsSyncService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync'; import { URI } from 'vs/base/common/uri'; import { IStringDictionary } from 'vs/base/common/collections'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; @@ -67,6 +67,25 @@ export class SettingsSyncChannel implements IServerChannel { } } +export class UserDataAuthTokenServiceChannel implements IServerChannel { + constructor(private readonly service: IUserDataAuthTokenService) { } + + listen(_: unknown, event: string): Event { + switch (event) { + case 'onDidChangeToken': return this.service.onDidChangeToken; + } + throw new Error(`Event not found: ${event}`); + } + + call(context: any, command: string, args?: any): Promise { + switch (command) { + case 'setToken': return this.service.setToken(args); + case 'getToken': return this.service.getToken(); + } + throw new Error('Invalid call'); + } +} + export class UserDataSycnUtilServiceChannel implements IServerChannel { constructor(private readonly service: IUserDataSyncUtilService) { } diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 9c4aaf3cb0cc0..cf1b0d10aae60 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -3,14 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService, IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { Emitter, Event } from 'vs/base/common/event'; import { ExtensionsSynchroniser } from 'vs/platform/userDataSync/common/extensionsSync'; import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; import { KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync'; import { GlobalStateSynchroniser } from 'vs/platform/userDataSync/common/globalStateSync'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -38,9 +37,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ constructor( @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IAuthTokenService private readonly authTokenService: IAuthTokenService, @ISettingsSyncService private readonly settingsSynchroniser: ISettingsSyncService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + @IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService, ) { super(); this.keybindingsSynchroniser = this._register(this.instantiationService.createInstance(KeybindingsSynchroniser)); @@ -51,6 +50,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ if (this.userDataSyncStoreService.userDataSyncStore) { this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeStatus, () => undefined)))(() => this.updateStatus())); + this._register(this.userDataAuthTokenService.onDidChangeToken(e => this.onDidChangeAuthTokenStatus(e))); } this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => s.onDidChangeLocal)); @@ -60,7 +60,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ if (!this.userDataSyncStoreService.userDataSyncStore) { throw new Error('Not enabled'); } - if (this.authTokenService.status === AuthTokenStatus.SignedOut) { + if (!(await this.userDataAuthTokenService.getToken())) { throw new Error('Not Authenticated. Please sign in to start sync.'); } for (const synchroniser of this.synchronisers) { @@ -76,7 +76,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ if (!this.userDataSyncStoreService.userDataSyncStore) { throw new Error('Not enabled'); } - if (this.authTokenService.status === AuthTokenStatus.SignedOut) { + if (!(await this.userDataAuthTokenService.getToken())) { throw new Error('Not Authenticated. Please sign in to start sync.'); } for (const synchroniser of this.synchronisers) { @@ -92,7 +92,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ if (!this.userDataSyncStoreService.userDataSyncStore) { throw new Error('Not enabled'); } - if (this.authTokenService.status === AuthTokenStatus.SignedOut) { + if (!(await this.userDataAuthTokenService.getToken())) { throw new Error('Not Authenticated. Please sign in to start sync.'); } for (const synchroniser of this.synchronisers) { @@ -120,7 +120,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ if (!this.userDataSyncStoreService.userDataSyncStore) { throw new Error('Not enabled'); } - if (this.authTokenService.status === AuthTokenStatus.SignedOut) { + if (!!(await this.userDataAuthTokenService.getToken())) { throw new Error('Not Authenticated. Please sign in to start sync.'); } for (const synchroniser of this.synchronisers) { @@ -135,7 +135,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ if (!this.userDataSyncStoreService.userDataSyncStore) { throw new Error('Not enabled'); } - if (this.authTokenService.status === AuthTokenStatus.SignedOut) { + if (!!(await this.userDataAuthTokenService.getToken())) { throw new Error('Not Authenticated. Please sign in to start sync.'); } for (const synchroniser of this.synchronisers) { @@ -192,4 +192,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } return SyncSource.UIState; } + + private onDidChangeAuthTokenStatus(token: string | undefined): void { + if (!token) { + this.stop(); + } + } } diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index bc499a7c48aff..a77449c0577bb 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -4,13 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, } from 'vs/base/common/lifecycle'; -import { IUserData, IUserDataSyncStoreService, UserDataSyncStoreErrorCode, UserDataSyncStoreError, IUserDataSyncStore, getUserDataSyncStore } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, IUserDataSyncStoreService, UserDataSyncStoreErrorCode, UserDataSyncStoreError, IUserDataSyncStore, getUserDataSyncStore, IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync'; import { IRequestService, asText, isSuccess } from 'vs/platform/request/common/request'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IHeaders, IRequestOptions, IRequestContext } from 'vs/base/parts/request/common/request'; -import { IAuthTokenService } from 'vs/platform/auth/common/auth'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class UserDataSyncStoreService extends Disposable implements IUserDataSyncStoreService { @@ -22,7 +21,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn constructor( @IConfigurationService configurationService: IConfigurationService, @IRequestService private readonly requestService: IRequestService, - @IAuthTokenService private readonly authTokenService: IAuthTokenService, + @IUserDataAuthTokenService private readonly authTokenService: IUserDataAuthTokenService, ) { super(); this.userDataSyncStore = getUserDataSyncStore(configurationService); @@ -98,7 +97,6 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn const context = await this.requestService.request(options, token); if (context.res.statusCode === 401) { - this.authTokenService.refreshToken(); // Throw Unauthorized Error throw new UserDataSyncStoreError('Unauthorized', UserDataSyncStoreErrorCode.Unauthroized); } diff --git a/src/vs/platform/userDataSync/electron-browser/userDataAutoSync.ts b/src/vs/platform/userDataSync/electron-browser/userDataAutoSync.ts index 90ec9533ecd0a..821d8a05ed5aa 100644 --- a/src/vs/platform/userDataSync/electron-browser/userDataAutoSync.ts +++ b/src/vs/platform/userDataSync/electron-browser/userDataAutoSync.ts @@ -3,12 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncLogService, IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync'; import { Event } from 'vs/base/common/event'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { UserDataAutoSync as BaseUserDataAutoSync } from 'vs/platform/userDataSync/common/userDataAutoSync'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IAuthTokenService } from 'vs/platform/auth/common/auth'; export class UserDataAutoSync extends BaseUserDataAutoSync { @@ -17,7 +16,7 @@ export class UserDataAutoSync extends BaseUserDataAutoSync { @IElectronService electronService: IElectronService, @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncLogService logService: IUserDataSyncLogService, - @IAuthTokenService authTokenService: IAuthTokenService, + @IUserDataAuthTokenService authTokenService: IUserDataAuthTokenService, ) { super(configurationService, userDataSyncService, logService, authTokenService); diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 27a17629b1339..be480f96639e2 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -16,7 +16,28 @@ declare module 'vscode' { - //#region Alex - resolvers, AlexR - ports + export interface Account { + readonly id: string; + readonly accessToken: string; + readonly displayName: string; + } + + export interface AuthenticationProvider { + readonly id: string; + readonly displayName: string; + + readonly accounts: ReadonlyArray; + readonly onDidChangeAccounts: Event>; + + login(): Promise; + logout(accountId: string): Promise; + } + + export namespace authentication { + export function registerAuthenticationProvider(provider: AuthenticationProvider): Disposable; + } + + //#region Alex - resolvers export interface RemoteAuthorityResolverContext { resolveAttempt: number; diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index 5560678f607a3..d77e37c35d802 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -59,6 +59,7 @@ import './mainThreadComments'; import './mainThreadTask'; import './mainThreadLabelService'; import './mainThreadTunnelService'; +import './mainThreadAuthentication'; import 'vs/workbench/api/common/apiCommands'; export class ExtensionPoints implements IWorkbenchContribution { diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts new file mode 100644 index 0000000000000..c9fbc6e3e50a8 --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import * as modes from 'vs/editor/common/modes'; +import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; +import { ExtHostAuthenticationShape, ExtHostContext, IExtHostContext, MainContext, MainThreadAuthenticationShape } from '../common/extHost.protocol'; + +export class MainThreadAuthenticationProvider { + public readonly handle: number; + constructor( + private readonly _proxy: ExtHostAuthenticationShape, + public readonly id: string, + handle: number + ) { + this.handle = handle; + } + + accounts(): Promise> { + return this._proxy.$accounts(this.handle); + } + + login(): Promise { + return this._proxy.$login(this.handle); + } + + logout(accountId: string): Promise { + return this._proxy.$logout(this.handle, accountId); + } +} + +@extHostNamedCustomer(MainContext.MainThreadAuthentication) +export class MainThreadAuthentication extends Disposable implements MainThreadAuthenticationShape { + private readonly _proxy: ExtHostAuthenticationShape; + private _handlers = new Map(); + + constructor( + extHostContext: IExtHostContext, + @IAuthenticationService private readonly authenticationService: IAuthenticationService, + ) { + super(); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostAuthentication); + } + + $registerAuthenticationProvider(handle: number, id: string): void { + const provider = new MainThreadAuthenticationProvider(this._proxy, id, handle); + this._handlers.set(handle, id); + this.authenticationService.registerAuthenticationProvider(id, provider); + } + + $unregisterAuthenticationProvider(handle: number): void { + const id = this._handlers.get(handle); + if (!id) { + throw new Error(`No authentication provider registered with id ${id}`); + } + + this.authenticationService.unregisterAuthenticationProvider(id); + } + + $onDidChangeAccounts(handle: number, accounts: ReadonlyArray) { + const id = this._handlers.get(handle); + if (id) { + this.authenticationService.accountsUpdate(id, accounts); + } + } +} diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index d38277ed35b55..0438ad43777b9 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -69,6 +69,7 @@ import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming'; import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; +import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; export interface IExtensionApiFactory { (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode; @@ -128,6 +129,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress))); const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHostLabelService, new ExtHostLabelService(rpcProtocol)); const extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol)); + const extHostAuthentication = rpcProtocol.set(ExtHostContext.ExtHostAuthentication, new ExtHostAuthentication(rpcProtocol)); // Check that no named customers are missing const expected: ProxyIdentifier[] = values(ExtHostContext); @@ -175,6 +177,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }; })(); + const authentication: typeof vscode.authentication = { + registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable { + return extHostAuthentication.registerAuthenticationProvider(provider); + } + }; // namespace: commands const commands: typeof vscode.commands = { @@ -830,6 +837,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return { version: initData.version, // namespaces + authentication, commands, debug, env, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 227a885f876f5..12081788f0498 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -147,6 +147,12 @@ export interface MainThreadCommentsShape extends IDisposable { $onDidCommentThreadsChange(handle: number, event: modes.CommentThreadChangedEvent): void; } +export interface MainThreadAuthenticationShape extends IDisposable { + $registerAuthenticationProvider(handle: number, id: string): void; + $unregisterAuthenticationProvider(handle: number): void; + $onDidChangeAccounts(handle: number, accounts: ReadonlyArray): void; +} + export interface MainThreadConfigurationShape extends IDisposable { $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, overrides: IConfigurationOverrides | undefined, scopeToLanguage: boolean | undefined): Promise; $removeConfigurationOption(target: ConfigurationTarget | null, key: string, overrides: IConfigurationOverrides | undefined, scopeToLanguage: boolean | undefined): Promise; @@ -891,6 +897,13 @@ export interface ExtHostLabelServiceShape { $registerResourceLabelFormatter(formatter: ResourceLabelFormatter): IDisposable; } +export interface ExtHostAuthenticationShape { + $accounts(handle: number): Promise>; + $login(handle: number): Promise; + $logout(handle: number, accountId: string): Promise; + // TODO rmacfarlane +} + export interface ExtHostSearchShape { $provideFileSearchResults(handle: number, session: number, query: search.IRawQuery, token: CancellationToken): Promise; $provideTextSearchResults(handle: number, session: number, query: search.IRawTextQuery, token: CancellationToken): Promise; @@ -1412,6 +1425,7 @@ export interface ExtHostTunnelServiceShape { // --- proxy identifiers export const MainContext = { + MainThreadAuthentication: createMainId('MainThreadAuthentication'), MainThreadClipboard: createMainId('MainThreadClipboard'), MainThreadCommands: createMainId('MainThreadCommands'), MainThreadComments: createMainId('MainThreadComments'), @@ -1487,5 +1501,6 @@ export const ExtHostContext = { ExtHostOutputService: createMainId('ExtHostOutputService'), ExtHostLabelService: createMainId('ExtHostLabelService'), ExtHostTheming: createMainId('ExtHostTheming'), - ExtHostTunnelService: createMainId('ExtHostTunnelService') + ExtHostTunnelService: createMainId('ExtHostTunnelService'), + ExtHostAuthentication: createMainId('ExtHostAuthentication') }; diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts new file mode 100644 index 0000000000000..1ab42296c86fd --- /dev/null +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -0,0 +1,83 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import * as modes from 'vs/editor/common/modes'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IMainContext, MainContext, MainThreadAuthenticationShape, ExtHostAuthenticationShape } from 'vs/workbench/api/common/extHost.protocol'; +import { IDisposable } from 'vs/base/common/lifecycle'; + +export class ExtHostAuthenticationProvider implements IDisposable { + constructor(private _provider: vscode.AuthenticationProvider, + private readonly _handle: number, + private _proxy: MainThreadAuthenticationShape) { + this._provider.onDidChangeAccounts(x => this._proxy.$onDidChangeAccounts(this._handle, this._provider.accounts)); + } + + get accounts(): ReadonlyArray { + return this._provider.accounts; + } + + login(): Promise { + return this._provider.login(); + } + + logout(accountId: string): Promise { + return this._provider.logout(accountId); + } + + dispose(): void { + this._proxy.$unregisterAuthenticationProvider(this._handle); + } +} + +export class ExtHostAuthentication implements ExtHostAuthenticationShape { + public static _handlePool: number = 0; + private _proxy: MainThreadAuthenticationShape; + private _authenticationProviders: Map = new Map(); + + constructor(mainContext: IMainContext) { + this._proxy = mainContext.getProxy(MainContext.MainThreadAuthentication); + } + + private readonly _onDidRefreshToken = new Emitter(); + readonly onDidRefreshToken: Event = this._onDidRefreshToken.event; + + registerAuthenticationProvider(provider: vscode.AuthenticationProvider) { + const handle = ExtHostAuthentication._handlePool++; + const authenticationProvider = new ExtHostAuthenticationProvider(provider, handle, this._proxy); + this._authenticationProviders.set(handle, authenticationProvider); + + this._proxy.$registerAuthenticationProvider(handle, provider.id); + return authenticationProvider; + } + + $accounts(handle: number): Promise> { + const authProvider = this._authenticationProviders.get(handle); + if (authProvider) { + return Promise.resolve(authProvider.accounts); + } + + throw new Error(`Unable to find authentication provider with handle: ${handle}`); + } + + $login(handle: number): Promise { + const authProvider = this._authenticationProviders.get(handle); + if (authProvider) { + return authProvider.login(); + } + + throw new Error(`Unable to find authentication provider with handle: ${handle}`); + } + + $logout(handle: number, accountId: string): Promise { + const authProvider = this._authenticationProviders.get(handle); + if (authProvider) { + return authProvider.logout(accountId); + } + + throw new Error(`Unable to find authentication provider with handle: ${handle}`); + } +} diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSync.ts index d0d54c3c4038c..31ec8de3166e0 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataAutoSync.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncLogService, IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IAuthTokenService } from 'vs/platform/auth/common/auth'; import { Event } from 'vs/base/common/event'; import { UserDataAutoSync as BaseUserDataAutoSync } from 'vs/platform/userDataSync/common/userDataAutoSync'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -18,7 +17,7 @@ export class UserDataAutoSync extends BaseUserDataAutoSync { @IUserDataSyncService userDataSyncService: IUserDataSyncService, @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncLogService logService: IUserDataSyncLogService, - @IAuthTokenService authTokenService: IAuthTokenService, + @IUserDataAuthTokenService authTokenService: IUserDataAuthTokenService, @IInstantiationService instantiationService: IInstantiationService, @IHostService hostService: IHostService, ) { diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 222643aa7b448..5d87233cb6638 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore, ISyncConfiguration } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync'; import { localize } from 'vs/nls'; import { Disposable, MutableDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -24,9 +24,7 @@ import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { isEqual } from 'vs/base/common/resources'; import { IEditorInput } from 'vs/workbench/common/editor'; -import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { FalseContext } from 'vs/platform/contextkey/common/contextkeys'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { isWeb } from 'vs/base/common/platform'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -35,25 +33,35 @@ import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/u import { timeout } from 'vs/base/common/async'; import { IOutputService } from 'vs/workbench/contrib/output/common/output'; import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; +import { IAuthenticationService, ChangeAccountEventData } from 'vs/workbench/services/authentication/browser/authenticationService'; +import { Account } from 'vs/editor/common/modes'; -const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey('authTokenStatus', AuthTokenStatus.Initializing); +const enum MSAAuthStatus { + Initializing = 'Initializing', + SignedIn = 'SignedIn', + SignedOut = 'SignedOut' +} +const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey('authTokenStatus', MSAAuthStatus.Initializing); const SYNC_PUSH_LIGHT_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/check-light.svg`)); const SYNC_PUSH_DARK_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/check-dark.svg`)); +const MSA = 'MSA'; + export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution { private static readonly ENABLEMENT_SETTING = 'sync.enable'; private readonly userDataSyncStore: IUserDataSyncStore | undefined; private readonly syncStatusContext: IContextKey; - private readonly authTokenContext: IContextKey; + private readonly authenticationState: IContextKey; private readonly badgeDisposable = this._register(new MutableDisposable()); private readonly conflictsWarningDisposable = this._register(new MutableDisposable()); private readonly signInNotificationDisposable = this._register(new MutableDisposable()); + private _activeAccount: Account | undefined; constructor( @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, - @IAuthTokenService private readonly authTokenService: IAuthTokenService, + @IAuthenticationService private readonly authenticationService: IAuthenticationService, @IContextKeyService contextKeyService: IContextKeyService, @IActivityService private readonly activityService: IActivityService, @INotificationService private readonly notificationService: INotificationService, @@ -66,45 +74,111 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @IQuickInputService private readonly quickInputService: IQuickInputService, @IInstantiationService instantiationService: IInstantiationService, @IOutputService private readonly outputService: IOutputService, + @IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService, ) { super(); this.userDataSyncStore = getUserDataSyncStore(configurationService); this.syncStatusContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService); - this.authTokenContext = CONTEXT_AUTH_TOKEN_STATE.bindTo(contextKeyService); - + this.authenticationState = CONTEXT_AUTH_TOKEN_STATE.bindTo(contextKeyService); if (this.userDataSyncStore) { registerConfiguration(); - this.onDidChangeAuthTokenStatus(this.authTokenService.status); this.onDidChangeSyncStatus(this.userDataSyncService.status); - this._register(Event.debounce(authTokenService.onDidChangeStatus, () => undefined, 500)(() => this.onDidChangeAuthTokenStatus(this.authTokenService.status))); this._register(Event.debounce(userDataSyncService.onDidChangeStatus, () => undefined, 500)(() => this.onDidChangeSyncStatus(this.userDataSyncService.status))); this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING))(() => this.onDidChangeEnablement())); + this._register(this.authenticationService.onDidRegisterAuthenticationProvider(e => this.onDidRegisterAuthenticationProvider(e))); + this._register(this.authenticationService.onDidUnregisterAuthenticationProvider(e => this.onDidUnregisterAuthenticationProvider(e))); + this._register(this.authenticationService.onDidChangeAccounts(e => this.onDidChangeAccounts(e))); this.registerActions(); - - if (isWeb) { - this._register(instantiationService.createInstance(UserDataAutoSync)); - } else { - this._register(instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync(() => this.triggerSync())); - } + this.initializeActiveAccount().then(_ => { + if (isWeb) { + this._register(instantiationService.createInstance(UserDataAutoSync)); + } else { + this._register(instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync(() => this.triggerSync())); + } + }); } } private triggerSync(): void { if (this.configurationService.getValue('sync.enable') && this.userDataSyncService.status !== SyncStatus.Uninitialized - && this.authTokenService.status === AuthTokenStatus.SignedIn) { + && this.authenticationState.get() === MSAAuthStatus.SignedIn) { this.userDataSyncService.sync(); } } - private onDidChangeAuthTokenStatus(status: AuthTokenStatus) { - this.authTokenContext.set(status); - if (status === AuthTokenStatus.SignedIn) { - this.signInNotificationDisposable.clear(); + private async initializeActiveAccount(): Promise { + const accounts = await this.authenticationService.getAccounts(MSA); + // MSA provider has not yet been registered + if (!accounts) { + return; + } + + if (accounts.length === 0) { + this.activeAccount = undefined; + return; } + + if (accounts.length === 1) { + this.activeAccount = accounts[0]; + return; + } + + const selectedAccount = await this.quickInputService.pick(accounts.map(account => { + return { + id: account.id, + label: account.displayName + }; + }), { canPickMany: false }); + + if (selectedAccount) { + this.activeAccount = accounts.filter(account => selectedAccount.id === account.id)[0]; + } + } + + get activeAccount(): Account | undefined { + return this._activeAccount; + } + + set activeAccount(account: Account | undefined) { + this._activeAccount = account; + + if (account) { + this.userDataAuthTokenService.setToken(account.accessToken); + this.authenticationState.set(MSAAuthStatus.SignedIn); + } else { + this.userDataAuthTokenService.setToken(undefined); + this.authenticationState.set(MSAAuthStatus.SignedOut); + } + this.updateBadge(); } + private onDidChangeAccounts(event: ChangeAccountEventData): void { + if (event.providerId === MSA) { + if (this.activeAccount) { + // Try to update existing account, case where access token has been refreshed + const matchingAccount = event.accounts.filter(a => a.id === this.activeAccount?.id)[0]; + this.activeAccount = matchingAccount; + } else { + this.initializeActiveAccount(); + } + } + } + + private async onDidRegisterAuthenticationProvider(providerId: string) { + if (providerId === MSA) { + await this.initializeActiveAccount(); + } + } + + private onDidUnregisterAuthenticationProvider(providerId: string) { + if (providerId === MSA) { + this.activeAccount = undefined; + this.authenticationState.reset(); + } + } + private onDidChangeSyncStatus(status: SyncStatus) { this.syncStatusContext.set(status); @@ -140,7 +214,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.updateBadge(); const enabled = this.configurationService.getValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING); if (enabled) { - if (this.authTokenService.status === AuthTokenStatus.SignedOut) { + if (this.authenticationState.get() === MSAAuthStatus.SignedOut) { const handle = this.notificationService.prompt(Severity.Info, this.getSignInAndTurnOnDetailString(), [ { @@ -156,19 +230,15 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private updateBadge(): void { + private async updateBadge(): Promise { this.badgeDisposable.clear(); let badge: IBadge | undefined = undefined; let clazz: string | undefined; let priority: number | undefined = undefined; - if (this.userDataSyncService.status !== SyncStatus.Uninitialized && this.configurationService.getValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING) && this.authTokenService.status === AuthTokenStatus.SignedOut) { + if (this.userDataSyncService.status !== SyncStatus.Uninitialized && this.configurationService.getValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING) && this.authenticationState.get() === MSAAuthStatus.SignedOut) { badge = new NumberBadge(1, () => localize('sign in to sync', "Sign in to Sync")); - } else if (this.authTokenService.status === AuthTokenStatus.SigningIn) { - badge = new ProgressBadge(() => localize('signing in', "Signing in...")); - clazz = 'progress-badge'; - priority = 1; } else if (this.userDataSyncService.status === SyncStatus.HasConflicts) { badge = new NumberBadge(1, () => localize('resolve conflicts', "Resolve Conflicts")); } else if (this.userDataSyncService.status === SyncStatus.Syncing) { @@ -279,7 +349,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private async turnOn(): Promise { const message = localize('turn on sync', "Turn on Sync"); let detail: string, primaryButton: string; - if (this.authTokenService.status === AuthTokenStatus.SignedIn) { + if (this.authenticationState.get() === MSAAuthStatus.SignedIn) { detail = this.getTurnOnDetailString(); primaryButton = localize('turn on', "Turn on"); } else { @@ -291,7 +361,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo case 1: return; case 2: await this.configureSyncOptions(); return this.turnOn(); } - if (this.authTokenService.status !== AuthTokenStatus.SignedIn) { + if (this.authenticationState.get() === MSAAuthStatus.SignedOut) { await this.signIn(); } await this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, true); @@ -357,7 +427,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private async signIn(): Promise { try { - await this.authTokenService.login(); + this.activeAccount = await this.authenticationService.login(MSA); } catch (e) { this.notificationService.error(e); throw e; @@ -365,7 +435,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private async signOut(): Promise { - await this.authTokenService.logout(); + if (this.activeAccount) { + await this.authenticationService.logout(MSA, this.activeAccount.id); + this.activeAccount = undefined; + } } private async continueSync(): Promise { @@ -435,7 +508,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private registerActions(): void { const turnOnSyncCommandId = 'workbench.userData.actions.syncStart'; - const turnOnSyncWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.not(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.notEqualsTo(AuthTokenStatus.SigningIn)); + const turnOnSyncWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.not(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.notEqualsTo(MSAAuthStatus.Initializing)); CommandsRegistry.registerCommand(turnOnSyncCommandId, () => this.turnOn()); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', @@ -454,7 +527,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); const signInCommandId = 'workbench.userData.actions.signin'; - const signInWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.SignedOut)); + const signInWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.isEqualTo(MSAAuthStatus.SignedOut)); CommandsRegistry.registerCommand(signInCommandId, () => this.signIn()); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', @@ -472,18 +545,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo when: signInWhenContext, }); - const signingInCommandId = 'workbench.userData.actions.signingin'; - CommandsRegistry.registerCommand(signingInCommandId, () => null); - MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '5_sync', - command: { - id: signingInCommandId, - title: localize('signinig in', "Signing in..."), - precondition: FalseContext - }, - when: CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.SigningIn) - }); - const stopSyncCommandId = 'workbench.userData.actions.stopSync'; CommandsRegistry.registerCommand(stopSyncCommandId, () => this.turnOff()); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { @@ -492,7 +553,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo id: stopSyncCommandId, title: localize('global activity stop sync', "Turn off sync") }, - when: ContextKeyExpr.and(ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.SignedIn), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.HasConflicts)) + when: ContextKeyExpr.and(ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.isEqualTo(MSAAuthStatus.SignedIn), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.HasConflicts)) }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { @@ -563,7 +624,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo id: 'workbench.userData.actions.signout', title: localize('sign out', "Sync: Sign out") }, - when: ContextKeyExpr.and(CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.SignedIn)), + when: ContextKeyExpr.and(CONTEXT_AUTH_TOKEN_STATE.isEqualTo(MSAAuthStatus.SignedIn)), }; CommandsRegistry.registerCommand(signOutMenuItem.command.id, () => this.signOut()); MenuRegistry.appendMenuItem(MenuId.CommandPalette, signOutMenuItem); diff --git a/src/vs/workbench/services/authToken/browser/authTokenService.ts b/src/vs/workbench/services/authToken/browser/authTokenService.ts deleted file mode 100644 index ef7af7506a296..0000000000000 --- a/src/vs/workbench/services/authToken/browser/authTokenService.ts +++ /dev/null @@ -1,74 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { localize } from 'vs/nls'; -import { Event, Emitter } from 'vs/base/common/event'; -import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; -import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { URI } from 'vs/base/common/uri'; - -const SERVICE_NAME = 'VS Code'; -const ACCOUNT = 'MyAccount'; - -export class AuthTokenService extends Disposable implements IAuthTokenService { - _serviceBrand: undefined; - - private _status: AuthTokenStatus = AuthTokenStatus.Initializing; - get status(): AuthTokenStatus { return this._status; } - private _onDidChangeStatus: Emitter = this._register(new Emitter()); - readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; - - readonly _onDidGetCallback: Emitter = this._register(new Emitter()); - - constructor( - @ICredentialsService private readonly credentialsService: ICredentialsService, - @IQuickInputService private readonly quickInputService: IQuickInputService - ) { - super(); - this.getToken().then(token => { - if (token) { - this.setStatus(AuthTokenStatus.SignedIn); - } else { - this.setStatus(AuthTokenStatus.SignedOut); - } - }); - } - - async getToken(): Promise { - const token = await this.credentialsService.getPassword(SERVICE_NAME, ACCOUNT); - if (token) { - return token; - } - - return; - } - - async login(): Promise { - const token = await this.quickInputService.input({ placeHolder: localize('enter token', "Please provide the auth bearer token"), ignoreFocusLost: true, }); - if (token) { - await this.credentialsService.setPassword(SERVICE_NAME, ACCOUNT, token); - this.setStatus(AuthTokenStatus.SignedIn); - } - } - - async refreshToken(): Promise { - await this.logout(); - } - - async logout(): Promise { - await this.credentialsService.deletePassword(SERVICE_NAME, ACCOUNT); - this.setStatus(AuthTokenStatus.SignedOut); - } - - private setStatus(status: AuthTokenStatus): void { - if (this._status !== status) { - this._status = status; - this._onDidChangeStatus.fire(status); - } - } - -} diff --git a/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts b/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts deleted file mode 100644 index ad7abd588267c..0000000000000 --- a/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts +++ /dev/null @@ -1,61 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { Emitter, Event } from 'vs/base/common/event'; -import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; -import { URI } from 'vs/base/common/uri'; - -export class AuthTokenService extends Disposable implements IAuthTokenService { - - _serviceBrand: undefined; - - private readonly channel: IChannel; - - private _status: AuthTokenStatus = AuthTokenStatus.Initializing; - get status(): AuthTokenStatus { return this._status; } - private _onDidChangeStatus: Emitter = this._register(new Emitter()); - readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; - - readonly _onDidGetCallback: Emitter = this._register(new Emitter()); - - constructor( - @ISharedProcessService sharedProcessService: ISharedProcessService, - ) { - super(); - this.channel = sharedProcessService.getChannel('authToken'); - this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status))); - this.channel.call('_getInitialStatus').then(status => this.updateStatus(status)); - } - - getToken(): Promise { - return this.channel.call('getToken'); - } - - login(): Promise { - return this.channel.call('login'); - } - - refreshToken(): Promise { - return this.channel.call('getToken'); - } - - logout(): Promise { - return this.channel.call('logout'); - } - - private async updateStatus(status: AuthTokenStatus): Promise { - if (status !== AuthTokenStatus.Initializing) { - this._status = status; - this._onDidChangeStatus.fire(status); - } - } - -} - -registerSingleton(IAuthTokenService, AuthTokenService); diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts new file mode 100644 index 0000000000000..ca2a2fd8e1a61 --- /dev/null +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -0,0 +1,96 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Account } from 'vs/editor/common/modes'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { MainThreadAuthenticationProvider } from 'vs/workbench/api/browser/mainThreadAuthentication'; + +export const IAuthenticationService = createDecorator('IAuthenticationService'); + +export interface ChangeAccountEventData { + providerId: string; + accounts: ReadonlyArray; +} + +export interface IAuthenticationService { + _serviceBrand: undefined; + + registerAuthenticationProvider(id: string, provider: MainThreadAuthenticationProvider): void; + unregisterAuthenticationProvider(id: string): void; + accountsUpdate(providerId: string, accounts: ReadonlyArray): void; + + readonly onDidRegisterAuthenticationProvider: Event; + readonly onDidUnregisterAuthenticationProvider: Event; + + readonly onDidChangeAccounts: Event; + getAccounts(providerId: string): Promise | undefined>; + login(providerId: string): Promise; + logout(providerId: string, accountId: string): Promise; +} + +export class AuthenticationService extends Disposable implements IAuthenticationService { + _serviceBrand: undefined; + + private _authenticationProviders: Map = new Map(); + + private _onDidRegisterAuthenticationProvider: Emitter = this._register(new Emitter()); + readonly onDidRegisterAuthenticationProvider: Event = this._onDidRegisterAuthenticationProvider.event; + + private _onDidUnregisterAuthenticationProvider: Emitter = this._register(new Emitter()); + readonly onDidUnregisterAuthenticationProvider: Event = this._onDidUnregisterAuthenticationProvider.event; + + private _onDidChangeAccounts: Emitter = this._register(new Emitter()); + readonly onDidChangeAccounts: Event = this._onDidChangeAccounts.event; + + constructor() { + super(); + } + + registerAuthenticationProvider(id: string, authenticationProvider: MainThreadAuthenticationProvider): void { + this._authenticationProviders.set(id, authenticationProvider); + this._onDidRegisterAuthenticationProvider.fire(id); + } + + unregisterAuthenticationProvider(id: string): void { + this._authenticationProviders.delete(id); + this._onDidUnregisterAuthenticationProvider.fire(id); + } + + accountsUpdate(providerId: string, accounts: ReadonlyArray): void { + this._onDidChangeAccounts.fire({ providerId, accounts }); + } + + async getAccounts(id: string): Promise | undefined> { + const authProvider = this._authenticationProviders.get(id); + if (authProvider) { + return await authProvider.accounts(); + } + + return undefined; + } + + async login(id: string): Promise { + const authProvider = this._authenticationProviders.get(id); + if (authProvider) { + return authProvider.login(); + } else { + throw new Error(`No authentication provider '${id}' is currently registered.`); + } + } + + async logout(id: string, accountId: string): Promise { + const authProvider = this._authenticationProviders.get(id); + if (authProvider) { + return authProvider.logout(accountId); + } else { + throw new Error(`No authentication provider '${id}' is currently registered.`); + } + } +} + +registerSingleton(IAuthenticationService, AuthenticationService); diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataAuthTokenService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataAuthTokenService.ts new file mode 100644 index 0000000000000..998630e1ef91d --- /dev/null +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataAuthTokenService.ts @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Emitter, Event } from 'vs/base/common/event'; + +export class UserDataAuthTokenService extends Disposable implements IUserDataAuthTokenService { + + _serviceBrand: undefined; + + private readonly channel: IChannel; + private _onDidChangeToken: Emitter = this._register(new Emitter()); + readonly onDidChangeToken: Event = this._onDidChangeToken.event; + + constructor( + @ISharedProcessService sharedProcessService: ISharedProcessService, + ) { + super(); + this.channel = sharedProcessService.getChannel('authToken'); + } + + getToken(): Promise { + return this.channel.call('getToken'); + } + + setToken(token: string | undefined): Promise { + return this.channel.call('setToken', token); + } +} + +registerSingleton(IUserDataAuthTokenService, UserDataAuthTokenService); diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index bd8b76dad8fcf..f9656900129c2 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -51,7 +51,8 @@ import 'vs/workbench/services/workspaces/electron-browser/workspacesService'; import 'vs/workbench/services/workspaces/electron-browser/workspaceEditingService'; import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncService'; import 'vs/workbench/services/userDataSync/electron-browser/settingsSyncService'; -import 'vs/workbench/services/authToken/electron-browser/authTokenService'; +import 'vs/workbench/services/userDataSync/electron-browser/userDataAuthTokenService'; +import 'vs/workbench/services/authentication/browser/authenticationService'; import 'vs/workbench/services/host/electron-browser/desktopHostService'; import 'vs/workbench/services/request/electron-browser/requestService'; import 'vs/workbench/services/lifecycle/electron-browser/lifecycleService'; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index e6c3807eefff4..9db377b87dd21 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -63,9 +63,8 @@ import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { TunnelService } from 'vs/workbench/services/remote/common/tunnelService'; import { ILoggerService } from 'vs/platform/log/common/log'; import { FileLoggerService } from 'vs/platform/log/common/fileLogService'; -import { IAuthTokenService } from 'vs/platform/auth/common/auth'; -import { AuthTokenService } from 'vs/workbench/services/authToken/browser/authTokenService'; import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService, ISettingsSyncService } from 'vs/platform/userDataSync/common/userDataSync'; +import { AuthenticationService, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; @@ -77,7 +76,7 @@ registerSingleton(IAccessibilityService, BrowserAccessibilityService, true); registerSingleton(IContextMenuService, ContextMenuService); registerSingleton(ITunnelService, TunnelService, true); registerSingleton(ILoggerService, FileLoggerService); -registerSingleton(IAuthTokenService, AuthTokenService); +registerSingleton(IAuthenticationService, AuthenticationService); registerSingleton(IUserDataSyncLogService, UserDataSyncLogService); registerSingleton(IUserDataSyncStoreService, UserDataSyncStoreService); registerSingleton(ISettingsSyncService, SettingsSynchroniser); From 3883ebd0615109548afc3c0b4d5fcca8dc701a03 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 14 Jan 2020 15:27:42 -0800 Subject: [PATCH 300/315] Add common service for logging deprecated api usage (#88585) * Add common service for logging deprecated api usage For #88391 Adds a new `ExtHostApiDeprecationService`. This service logs a warning and telemetry when a deprecated API is used. Updates some of the more simple deprecated apis to use this new service * Note that extensionId cannot be undefined * Fix event name --- .../workbench/api/common/extHost.api.impl.ts | 23 ++++-- .../common/extHostApiDeprecationService.ts | 71 +++++++++++++++++++ .../api/common/extHostLanguageFeatures.ts | 47 ++++++++---- src/vs/workbench/api/node/extHost.services.ts | 2 + .../extensions/worker/extHost.services.ts | 2 + .../api/extHostApiCommands.test.ts | 3 +- .../api/extHostLanguageFeatures.test.ts | 3 +- 7 files changed, 130 insertions(+), 21 deletions(-) create mode 100644 src/vs/workbench/api/common/extHostApiDeprecationService.ts diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 0438ad43777b9..1cc6c7e6108f0 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -69,6 +69,7 @@ import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming'; import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; +import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; export interface IExtensionApiFactory { @@ -90,6 +91,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostStorage = accessor.get(IExtHostStorage); const extHostLogService = accessor.get(ILogService); const extHostTunnelService = accessor.get(IExtHostTunnelService); + const extHostApiDeprecation = accessor.get(IExtHostApiDeprecationService); // register addressable instances rpcProtocol.set(ExtHostContext.ExtHostLogService, extHostLogService); @@ -119,7 +121,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService)); const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, initData.environment)); const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol, extHostLogService)); - const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, uriTransformer, extHostDocuments, extHostCommands, extHostDiagnostics, extHostLogService)); + const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, uriTransformer, extHostDocuments, extHostCommands, extHostDiagnostics, extHostLogService, extHostApiDeprecation)); const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures)); const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostLogService, extHostDocumentsAndEditors)); const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands)); @@ -388,7 +390,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostLanguageFeatures.registerCallHierarchyProvider(extension, selector, provider); }, setLanguageConfiguration: (language: string, configuration: vscode.LanguageConfiguration): vscode.Disposable => { - return extHostLanguageFeatures.setLanguageConfiguration(language, configuration); + return extHostLanguageFeatures.setLanguageConfiguration(extension, language, configuration); } }; @@ -508,6 +510,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostStatusBar.setStatusBarMessage(text, timeoutOrThenable); }, withScmProgress(task: (progress: vscode.Progress) => Thenable) { + extHostApiDeprecation.report('window.withScmProgress', extension, + `Use 'withProgress' instead.`); + return extHostProgress.withProgress(extension, { location: extHostTypes.ProgressLocation.SourceControl }, (progress, token) => task({ report(n: number) { /*noop*/ } })); }, withProgress(options: vscode.ProgressOptions, task: (progress: vscode.Progress<{ message?: string; worked?: number }>, token: vscode.CancellationToken) => Thenable) { @@ -569,13 +574,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }; // namespace: workspace - let warnedRootPathDeprecated = false; + const workspace: typeof vscode.workspace = { get rootPath() { - if (extension.isUnderDevelopment && !warnedRootPathDeprecated) { - warnedRootPathDeprecated = true; - extHostLogService.warn(`[Deprecation Warning] 'workspace.rootPath' is deprecated and should no longer be used. Please use 'workspace.workspaceFolders' instead. More details: https://aka.ms/vscode-eliminating-rootpath`); - } + extHostApiDeprecation.report('workspace.rootPath', extension, + `Please use 'workspace.workspaceFolders' instead. More details: https://aka.ms/vscode-eliminating-rootpath`); return extHostWorkspace.getPath(); }, @@ -689,6 +692,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostDocumentContentProviders.registerTextDocumentContentProvider(scheme, provider); }, registerTaskProvider: (type: string, provider: vscode.TaskProvider) => { + extHostApiDeprecation.report('window.registerTaskProvider', extension, + `Use the corresponding function on the 'tasks' namespace instead`); + return extHostTask.registerTaskProvider(extension, type, provider); }, registerFileSystemProvider(scheme, provider, options) { @@ -740,6 +746,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // namespace: scm const scm: typeof vscode.scm = { get inputBox() { + extHostApiDeprecation.report('scm.inputBox', extension, + `Use 'SourceControl.inputBox' instead`); + return extHostSCM.getLastInputBox(extension)!; // Strict null override - Deprecated api }, createSourceControl(id: string, label: string, rootUri?: vscode.Uri) { diff --git a/src/vs/workbench/api/common/extHostApiDeprecationService.ts b/src/vs/workbench/api/common/extHostApiDeprecationService.ts new file mode 100644 index 0000000000000..010458267f094 --- /dev/null +++ b/src/vs/workbench/api/common/extHostApiDeprecationService.ts @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; +import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; + +export interface IExtHostApiDeprecationService { + _serviceBrand: undefined; + + report(apiId: string, extension: IExtensionDescription, migrationSuggestion: string): void; +} + +export const IExtHostApiDeprecationService = createDecorator('IExtHostApiDeprecationService'); + +export class ExtHostApiDeprecationService implements IExtHostApiDeprecationService { + + _serviceBrand: undefined; + + private readonly _reportedUsages = new Set(); + private readonly _telemetryShape: extHostProtocol.MainThreadTelemetryShape; + + constructor( + @IExtHostRpcService rpc: IExtHostRpcService, + @ILogService private readonly _extHostLogService: ILogService, + ) { + this._telemetryShape = rpc.getProxy(extHostProtocol.MainContext.MainThreadTelemetry); + } + + public report(apiId: string, extension: IExtensionDescription, migrationSuggestion: string): void { + const key = this.getUsageKey(apiId, extension); + if (this._reportedUsages.has(key)) { + return; + } + this._reportedUsages.add(key); + + if (extension.isUnderDevelopment) { + this._extHostLogService.warn(`[Deprecation Warning] '${apiId}' is deprecated. ${migrationSuggestion}`); + } + + type DeprecationTelemetry = { + extensionId: string; + apiId: string; + }; + type DeprecationTelemetryMeta = { + extensionId: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' }; + apiId: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' }; + }; + this._telemetryShape.$publicLog2('extHostDeprecatedApiUsage', { + extensionId: extension.identifier.value, + apiId: apiId, + }); + } + + private getUsageKey(apiId: string, extension: IExtensionDescription): string { + return `${apiId}-${extension.identifier.value}`; + } +} + + +export const NullApiDeprecationService = Object.freeze(new class implements IExtHostApiDeprecationService { + _serviceBrand: undefined; + + public report(_apiId: string, _extension: IExtensionDescription, _warningMessage: string): void { + // noop + } +}()); diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index adfbebe622ab8..c440c5a3f14de 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -29,6 +29,7 @@ import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { VSBuffer } from 'vs/base/common/buffer'; import { encodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokens'; import { IdGenerator } from 'vs/base/common/idGenerator'; +import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; // --- adapter @@ -326,7 +327,8 @@ class CodeActionAdapter { private readonly _diagnostics: ExtHostDiagnostics, private readonly _provider: vscode.CodeActionProvider, private readonly _logService: ILogService, - private readonly _extensionId: ExtensionIdentifier + private readonly _extension: IExtensionDescription, + private readonly _apiDeprecation: IExtHostApiDeprecationService, ) { } provideCodeActions(resource: URI, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise { @@ -367,6 +369,10 @@ class CodeActionAdapter { } if (CodeActionAdapter._isCommand(candidate)) { // old school: synthetic code action + + this._apiDeprecation.report('CodeActionProvider.provideCodeActions - return commands', this._extension, + `Return 'CodeAction' instances instead.`); + actions.push({ _isSynthetic: true, title: candidate.title, @@ -375,9 +381,9 @@ class CodeActionAdapter { } else { if (codeActionContext.only) { if (!candidate.kind) { - this._logService.warn(`${this._extensionId.value} - Code actions of kind '${codeActionContext.only.value} 'requested but returned code action does not have a 'kind'. Code action will be dropped. Please set 'CodeAction.kind'.`); + this._logService.warn(`${this._extension.identifier.value} - Code actions of kind '${codeActionContext.only.value} 'requested but returned code action does not have a 'kind'. Code action will be dropped. Please set 'CodeAction.kind'.`); } else if (!codeActionContext.only.contains(candidate.kind)) { - this._logService.warn(`${this._extensionId.value} - Code actions of kind '${codeActionContext.only.value} 'requested but returned code action is of kind '${candidate.kind.value}'. Code action will be dropped. Please check 'CodeActionContext.only' to only return requested code actions.`); + this._logService.warn(`${this._extension.identifier.value} - Code actions of kind '${codeActionContext.only.value} 'requested but returned code action is of kind '${candidate.kind.value}'. Code action will be dropped. Please check 'CodeActionContext.only' to only return requested code actions.`); } } @@ -748,8 +754,9 @@ class SuggestAdapter { private readonly _commands: CommandsConverter, private readonly _provider: vscode.CompletionItemProvider, private readonly _logService: ILogService, + private readonly _apiDeprecation: IExtHostApiDeprecationService, private readonly _telemetry: extHostProtocol.MainThreadTelemetryShape, - private readonly _extensionId: ExtensionIdentifier + private readonly _extension: IExtensionDescription, ) { } provideCompletionItems(resource: URI, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise { @@ -838,15 +845,15 @@ class SuggestAdapter { let _mustNotChangeIndex = !this._didWarnMust && SuggestAdapter._mustNotChangeDiff(_mustNotChange, resolvedItem); if (typeof _mustNotChangeIndex === 'string') { - this._logService.warn(`[${this._extensionId.value}] INVALID result from 'resolveCompletionItem', extension MUST NOT change any of: label, sortText, filterText, insertText, or textEdit`); - this._telemetry.$publicLog2('resolveCompletionItem/invalid', { extensionId: this._extensionId.value, kind: 'must', index: _mustNotChangeIndex }); + this._logService.warn(`[${this._extension.identifier.value}] INVALID result from 'resolveCompletionItem', extension MUST NOT change any of: label, sortText, filterText, insertText, or textEdit`); + this._telemetry.$publicLog2('resolveCompletionItem/invalid', { extensionId: this._extension.identifier.value, kind: 'must', index: _mustNotChangeIndex }); this._didWarnMust = true; } let _mayNotChangeIndex = !this._didWarnShould && SuggestAdapter._mayNotChangeDiff(_mayNotChange, resolvedItem); if (typeof _mayNotChangeIndex === 'string') { - this._logService.info(`[${this._extensionId.value}] UNSAVE result from 'resolveCompletionItem', extension SHOULD NOT change any of: additionalTextEdits, or command`); - this._telemetry.$publicLog2('resolveCompletionItem/invalid', { extensionId: this._extensionId.value, kind: 'should', index: _mayNotChangeIndex }); + this._logService.info(`[${this._extension.identifier.value}] UNSAVE result from 'resolveCompletionItem', extension SHOULD NOT change any of: additionalTextEdits, or command`); + this._telemetry.$publicLog2('resolveCompletionItem/invalid', { extensionId: this._extension.identifier.value, kind: 'should', index: _mayNotChangeIndex }); this._didWarnShould = true; } @@ -892,6 +899,9 @@ class SuggestAdapter { // 'insertText'-logic if (item.textEdit) { + this._apiDeprecation.report('CompletionItem.textEdit', this._extension, + `Use 'CompletionItem.insertText' and 'CompletionItem.range' instead.`); + result[extHostProtocol.ISuggestDataDtoField.insertText] = item.textEdit.newText; } else if (typeof item.insertText === 'string') { @@ -1339,6 +1349,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF private _diagnostics: ExtHostDiagnostics; private _adapter = new Map(); private readonly _logService: ILogService; + private readonly _apiDeprecation: IExtHostApiDeprecationService; constructor( mainContext: extHostProtocol.IMainContext, @@ -1346,7 +1357,8 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF documents: ExtHostDocuments, commands: ExtHostCommands, diagnostics: ExtHostDiagnostics, - logService: ILogService + logService: ILogService, + apiDeprecationService: IExtHostApiDeprecationService, ) { this._uriTransformer = uriTransformer; this._proxy = mainContext.getProxy(extHostProtocol.MainContext.MainThreadLanguageFeatures); @@ -1355,6 +1367,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF this._commands = commands; this._diagnostics = diagnostics; this._logService = logService; + this._apiDeprecation = apiDeprecationService; } private _transformDocumentSelector(selector: vscode.DocumentSelector): Array { @@ -1562,7 +1575,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF // --- quick fix registerCodeActionProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider, metadata?: vscode.CodeActionProviderMetadata): vscode.Disposable { - const handle = this._addNewAdapter(new CodeActionAdapter(this._documents, this._commands.converter, this._diagnostics, provider, this._logService, extension.identifier), extension); + const handle = this._addNewAdapter(new CodeActionAdapter(this._documents, this._commands.converter, this._diagnostics, provider, this._logService, extension, this._apiDeprecation), extension); this._proxy.$registerQuickFixSupport(handle, this._transformDocumentSelector(selector), (metadata && metadata.providedCodeActionKinds) ? metadata.providedCodeActionKinds.map(kind => kind.value) : undefined); return this._createDisposable(handle); } @@ -1665,7 +1678,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF // --- suggestion registerCompletionItemProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, triggerCharacters: string[]): vscode.Disposable { - const handle = this._addNewAdapter(new SuggestAdapter(this._documents, this._commands.converter, provider, this._logService, this._telemetryShape, extension.identifier), extension); + const handle = this._addNewAdapter(new SuggestAdapter(this._documents, this._commands.converter, provider, this._logService, this._apiDeprecation, this._telemetryShape, extension), extension); this._proxy.$registerSuggestSupport(handle, this._transformDocumentSelector(selector), triggerCharacters, SuggestAdapter.supportsResolving(provider), extension.identifier); return this._createDisposable(handle); } @@ -1813,7 +1826,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return onEnterRules.map(ExtHostLanguageFeatures._serializeOnEnterRule); } - setLanguageConfiguration(languageId: string, configuration: vscode.LanguageConfiguration): vscode.Disposable { + setLanguageConfiguration(extension: IExtensionDescription, languageId: string, configuration: vscode.LanguageConfiguration): vscode.Disposable { let { wordPattern } = configuration; // check for a valid word pattern @@ -1828,6 +1841,16 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF this._documents.setWordDefinitionFor(languageId, undefined); } + if (configuration.__electricCharacterSupport) { + this._apiDeprecation.report('LanguageConfiguration.__electricCharacterSupport', extension, + `Do not use.`); + } + + if (configuration.__characterPairSupport) { + this._apiDeprecation.report('LanguageConfiguration.__characterPairSupport', extension, + `Do not use.`); + } + const handle = this._nextHandle(); const serializedConfiguration: extHostProtocol.ILanguageConfigurationDto = { comments: configuration.comments, diff --git a/src/vs/workbench/api/node/extHost.services.ts b/src/vs/workbench/api/node/extHost.services.ts index 326a1c9cb8d7e..72ad75d63ef73 100644 --- a/src/vs/workbench/api/node/extHost.services.ts +++ b/src/vs/workbench/api/node/extHost.services.ts @@ -28,9 +28,11 @@ import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService'; import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; import { ExtHostTunnelService } from 'vs/workbench/api/node/extHostTunnelService'; +import { IExtHostApiDeprecationService, ExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; // register singleton services registerSingleton(ILogService, ExtHostLogService); +registerSingleton(IExtHostApiDeprecationService, ExtHostApiDeprecationService); registerSingleton(IExtHostOutputService, ExtHostOutputService2); registerSingleton(IExtHostWorkspace, ExtHostWorkspace); registerSingleton(IExtHostDecorations, ExtHostDecorations); diff --git a/src/vs/workbench/services/extensions/worker/extHost.services.ts b/src/vs/workbench/services/extensions/worker/extHost.services.ts index 168fd0872709a..bbb72e9511c72 100644 --- a/src/vs/workbench/services/extensions/worker/extHost.services.ts +++ b/src/vs/workbench/services/extensions/worker/extHost.services.ts @@ -22,9 +22,11 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostLogService } from 'vs/workbench/api/worker/extHostLogService'; import { IExtHostTunnelService, ExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; +import { IExtHostApiDeprecationService, ExtHostApiDeprecationService, } from 'vs/workbench/api/common/extHostApiDeprecationService'; // register singleton services registerSingleton(ILogService, ExtHostLogService); +registerSingleton(IExtHostApiDeprecationService, ExtHostApiDeprecationService); registerSingleton(IExtHostOutputService, ExtHostOutputService); registerSingleton(IExtHostWorkspace, ExtHostWorkspace); registerSingleton(IExtHostDecorations, ExtHostDecorations); diff --git a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts index 06138aaba40cf..3142b84cc1a62 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts @@ -32,6 +32,7 @@ import { nullExtensionDescription } from 'vs/workbench/services/extensions/commo import { dispose } from 'vs/base/common/lifecycle'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; +import { NullApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; const defaultSelector = { scheme: 'far' }; const model: ITextModel = EditorModel.createFromString( @@ -122,7 +123,7 @@ suite('ExtHostLanguageFeatureCommands', function () { const diagnostics = new ExtHostDiagnostics(rpcProtocol, new NullLogService()); rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, diagnostics); - extHost = new ExtHostLanguageFeatures(rpcProtocol, null, extHostDocuments, commands, diagnostics, new NullLogService()); + extHost = new ExtHostLanguageFeatures(rpcProtocol, null, extHostDocuments, commands, diagnostics, new NullLogService(), NullApiDeprecationService); rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, extHost); mainThread = rpcProtocol.set(MainContext.MainThreadLanguageFeatures, inst.createInstance(MainThreadLanguageFeatures, rpcProtocol)); diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index b4b04e1cc8334..0208284f9ccd1 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -47,6 +47,7 @@ import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { dispose } from 'vs/base/common/lifecycle'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { NullApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; const defaultSelector = { scheme: 'far' }; const model: ITextModel = EditorModel.createFromString( @@ -105,7 +106,7 @@ suite('ExtHostLanguageFeatures', function () { const diagnostics = new ExtHostDiagnostics(rpcProtocol, new NullLogService()); rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, diagnostics); - extHost = new ExtHostLanguageFeatures(rpcProtocol, null, extHostDocuments, commands, diagnostics, new NullLogService()); + extHost = new ExtHostLanguageFeatures(rpcProtocol, null, extHostDocuments, commands, diagnostics, new NullLogService(), NullApiDeprecationService); rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, extHost); mainThread = rpcProtocol.set(MainContext.MainThreadLanguageFeatures, inst.createInstance(MainThreadLanguageFeatures, rpcProtocol)); From ffcc6b92e1899f2633536826e1a4b225ab55a152 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 15 Jan 2020 01:35:38 +0100 Subject: [PATCH 301/315] :lipstick: --- src/vs/platform/userDataSync/common/extensionsSync.ts | 2 +- .../platform/userDataSync/common/globalStateSync.ts | 2 +- .../platform/userDataSync/common/keybindingsSync.ts | 11 ++++++++--- src/vs/platform/userDataSync/common/settingsSync.ts | 6 +++++- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index d8d83228bbdc1..d97262ec02c18 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -122,7 +122,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser const { added, removed, updated, remote } = merge(localExtensions, null, null, [], this.getIgnoredExtensions()); await this.apply({ added, removed, updated, remote, remoteUserData: null, skippedExtensions: [] }); - this.logService.info('Extensions: Finished pulling extensions.'); + this.logService.info('Extensions: Finished pushing extensions.'); } finally { this.setStatus(SyncStatus.Idle); } diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index 36c81f2e03878..1baefb1c24e81 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -104,7 +104,7 @@ export class GlobalStateSynchroniser extends Disposable implements ISynchroniser const remote = await this.getLocalGlobalState(); await this.apply({ local: undefined, remote, remoteUserData: null }); - this.logService.info('UI State: Finished pulling UI State.'); + this.logService.info('UI State: Finished pushing UI State.'); } finally { this.setStatus(SyncStatus.Idle); } diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index bea6e5671c875..3167c18320f51 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -89,9 +89,10 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser const remoteContent = remoteUserData.content !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.content) : null; if (remoteContent !== null) { - await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(remoteContent)); + await this.fileService.writeFile(this.environmentService.keybindingsSyncPreviewResource, VSBuffer.fromString(remoteContent)); + const fileContent = await this.getLocalFileContent(); this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ - fileContent: null, + fileContent, hasConflicts: false, hasLocalChanged: true, hasRemoteChanged: false, @@ -104,6 +105,8 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser else { this.logService.info('Keybindings: Remote keybindings does not exist.'); } + + this.logService.info('Keybindings: Finished pulling keybindings.'); } finally { this.setStatus(SyncStatus.Idle); } @@ -140,6 +143,8 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser else { this.logService.info('Keybindings: Local keybindings does not exist.'); } + + this.logService.info('Keybindings: Finished pushing keybindings.'); } finally { this.setStatus(SyncStatus.Idle); } @@ -173,6 +178,7 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser return false; } await this.apply(); + this.logService.trace('Keybindings: Finished synchronizing keybindings...'); return true; } catch (e) { this.syncPreviewResultPromise = null; @@ -263,7 +269,6 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser this.logService.trace('Keybindings: No changes found during synchronizing keybindings.'); } - this.logService.trace('Keybindings: Finised synchronizing keybindings.'); this.syncPreviewResultPromise = null; } diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 813826aa7285e..203743d2b7c67 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -122,6 +122,8 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer else { this.logService.info('Settings: Remote settings does not exist.'); } + + this.logService.info('Settings: Finished pulling settings.'); } finally { this.setStatus(SyncStatus.Idle); } @@ -162,6 +164,8 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer else { this.logService.info('Settings: Local settings does not exist.'); } + + this.logService.info('Settings: Finished pushing settings.'); } finally { this.setStatus(SyncStatus.Idle); } @@ -225,6 +229,7 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer return false; } await this.apply(); + this.logService.trace('Settings: Finished synchronizing settings.'); return true; } catch (e) { this.syncPreviewResultPromise = null; @@ -293,7 +298,6 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer this.logService.trace('Settings: No changes found during synchronizing settings.'); } - this.logService.trace('Settings: Finised synchronizing settings.'); this.syncPreviewResultPromise = null; } From f1ad679258c02ef403dc773852cac40999c59db8 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 15 Jan 2020 01:38:09 +0100 Subject: [PATCH 302/315] disable caching of sync requests --- src/vs/platform/userDataSync/common/userDataSyncStoreService.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index a77449c0577bb..df5094c426b09 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -34,6 +34,8 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn const url = joinPath(URI.parse(this.userDataSyncStore.url), 'resource', key, 'latest').toString(); const headers: IHeaders = {}; + // Disable caching as they are cached by synchronisers + headers['Cache-Control'] = 'no-cache'; if (oldValue) { headers['If-None-Match'] = oldValue.ref; } From bbc2da76a9798c2f8a562af56ac41c5a420a7e37 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 15 Jan 2020 01:51:35 +0100 Subject: [PATCH 303/315] fix token check --- .../userDataSync/browser/userDataSync.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 5d87233cb6638..0f7b0c92f7c5d 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -364,6 +364,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo if (this.authenticationState.get() === MSAAuthStatus.SignedOut) { await this.signIn(); } + + await this.handleFirstTimeSync(); await this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, true); this.notificationService.info(localize('Sync Started', "Sync Started.")); } @@ -413,6 +415,34 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); } + private async handleFirstTimeSync(): Promise { + + const hasRemote = await this.userDataSyncService.hasRemote(); + const hasPreviouslySynced = await this.userDataSyncService.hasPreviouslySynced(); + + if (hasRemote && !hasPreviouslySynced) { + const result = await this.dialogService.show( + Severity.Info, + localize('firs time sync', "First time synchronization"), + [ + localize('continue', "Continue"), + localize('cancel', "Cancel"), + localize('download', "Download"), + localize('upload', "Upload"), + ], + { + cancelId: 1, + detail: localize('first time sync detail', "Synchronising from this device for the first time. Would you like to \nDownload and replace with the remote data or \nUpload from this device and replace the remote data?") + } + ); + + switch (result.choice) { + case 2: return this.userDataSyncService.pull(); + case 3: return this.userDataSyncService.push(); + } + } + } + private async turnOff(): Promise { const result = await this.dialogService.confirm({ type: 'info', From cc2d1a64443063d9116a5b68baf794fcecad013f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 15 Jan 2020 01:57:05 +0100 Subject: [PATCH 304/315] Revert "fix token check" This reverts commit bbc2da76a9798c2f8a562af56ac41c5a420a7e37. --- .../userDataSync/browser/userDataSync.ts | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 0f7b0c92f7c5d..5d87233cb6638 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -364,8 +364,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo if (this.authenticationState.get() === MSAAuthStatus.SignedOut) { await this.signIn(); } - - await this.handleFirstTimeSync(); await this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, true); this.notificationService.info(localize('Sync Started', "Sync Started.")); } @@ -415,34 +413,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); } - private async handleFirstTimeSync(): Promise { - - const hasRemote = await this.userDataSyncService.hasRemote(); - const hasPreviouslySynced = await this.userDataSyncService.hasPreviouslySynced(); - - if (hasRemote && !hasPreviouslySynced) { - const result = await this.dialogService.show( - Severity.Info, - localize('firs time sync', "First time synchronization"), - [ - localize('continue', "Continue"), - localize('cancel', "Cancel"), - localize('download', "Download"), - localize('upload', "Upload"), - ], - { - cancelId: 1, - detail: localize('first time sync detail', "Synchronising from this device for the first time. Would you like to \nDownload and replace with the remote data or \nUpload from this device and replace the remote data?") - } - ); - - switch (result.choice) { - case 2: return this.userDataSyncService.pull(); - case 3: return this.userDataSyncService.push(); - } - } - } - private async turnOff(): Promise { const result = await this.dialogService.confirm({ type: 'info', From 18653ef66af71e12650409a025e78849ee9a3d64 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 15 Jan 2020 01:57:52 +0100 Subject: [PATCH 305/315] fix token check --- src/vs/platform/userDataSync/common/userDataSyncService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index cf1b0d10aae60..84102d532bdfd 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -120,7 +120,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ if (!this.userDataSyncStoreService.userDataSyncStore) { throw new Error('Not enabled'); } - if (!!(await this.userDataAuthTokenService.getToken())) { + if (!(await this.userDataAuthTokenService.getToken())) { throw new Error('Not Authenticated. Please sign in to start sync.'); } for (const synchroniser of this.synchronisers) { @@ -135,7 +135,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ if (!this.userDataSyncStoreService.userDataSyncStore) { throw new Error('Not enabled'); } - if (!!(await this.userDataAuthTokenService.getToken())) { + if (!(await this.userDataAuthTokenService.getToken())) { throw new Error('Not Authenticated. Please sign in to start sync.'); } for (const synchroniser of this.synchronisers) { From 17faa988790062ab85cd58c3fbfe209137a28d7c Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Tue, 14 Jan 2020 10:00:20 -0800 Subject: [PATCH 306/315] WIP on search editors backed by files --- .../contrib/search/browser/searchEditor.ts | 11 ++- .../search/browser/searchEditorCommands.ts | 97 +++++++++++++------ 2 files changed, 75 insertions(+), 33 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index 26bc97814d1c9..055a554042654 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -35,6 +35,7 @@ import { SearchModel } from 'vs/workbench/contrib/search/common/searchModel'; import { IPatternInfo, ISearchConfigurationProperties, ITextQuery } from 'vs/workbench/services/search/common/search'; import { Delayer } from 'vs/base/common/async'; import { serializeSearchResultForEditor, SearchConfiguration, SearchEditorInput } from 'vs/workbench/contrib/search/browser/searchEditorCommands'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; const RESULT_LINE_REGEX = /^(\s+)(\d+)(:| )(\s+)(.*)$/; @@ -70,6 +71,7 @@ export class SearchEditor extends BaseEditor { @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextViewService private readonly contextViewService: IContextViewService, @ICommandService private readonly commandService: ICommandService, + @ITextFileService private readonly textFileService: ITextFileService, ) { super(SearchEditor.ID, telemetryService, themeService, storageService); } @@ -268,10 +270,16 @@ export class SearchEditor extends BaseEditor { } async setInput(newInput: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { + await super.setInput(newInput, options, token); + if (!(newInput instanceof SearchEditorInput)) { return; } this.pauseSearching = true; - // TODO: Manage model lifecycle in SearchEditorInput const model = this.modelService.getModel(newInput.resource); + if (newInput.resource.scheme !== 'search-editor') { + if (model?.getValue() === '') { + model.setValue((await this.textFileService.read(newInput.resource)).value); + } + } this.searchResultEditor.setModel(model); this.queryEditorWidget.setValue(newInput.config.query, true); this.queryEditorWidget.searchInput.setCaseSensitive(newInput.config.caseSensitive); @@ -284,7 +292,6 @@ export class SearchEditor extends BaseEditor { this.toggleIncludesExcludes(newInput.config.showIncludesExcludes); this.focusInput(); - await super.setInput(newInput, options, token); this.pauseSearching = false; } diff --git a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts index 985f49c434751..bbcc5ba4d2e6b 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts @@ -5,7 +5,7 @@ import { coalesce, flatten } from 'vs/base/common/arrays'; import * as network from 'vs/base/common/network'; -import { repeat } from 'vs/base/common/strings'; +import { repeat, endsWith } from 'vs/base/common/strings'; import { assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/searchEditor'; @@ -33,7 +33,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import type { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -// import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; @@ -50,24 +50,45 @@ export type SearchConfiguration = { }; export class SearchEditorContribution implements IWorkbenchContribution { - // constructor( - // @IEditorService private readonly editorService: IEditorService, - // @ITextFileService protected readonly textFileService: ITextFileService, - // ) { - // this.editorService.overrideOpenEditor(async (editor, options, group) => { - // const resource = editor.getResource(); - // if (!resource || !endsWith(resource.path, '.code-search') || !(editor instanceof FileEditorInput)) { - // return undefined; - // } - - // console.log('hello agian'); - // const contents = await this.textFileService.read(resource); - // contents.value; - - // return undefined; - - // }); - // } + constructor( + @IEditorService private readonly editorService: IEditorService, + @ITextFileService protected readonly textFileService: ITextFileService, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + ) { + this.editorService.overrideOpenEditor((editor, options, group) => { + const resource = editor.getResource(); + if (!resource || !endsWith(resource.path, '.code-search') || !(editor instanceof FileEditorInput)) { + return undefined; + } + + if (group.isOpened(editor)) { + return undefined; + } + + return { + override: (async () => { + const contents = (await this.textFileService.read(resource)).value; + const header = searchHeaderToContentPattern(contents.split('\n').slice(0, 5)); + + const input = instantiationService.createInstance( + SearchEditorInput, + { + query: header.pattern, + regexp: header.flags.regex, + caseSensitive: header.flags.caseSensitive, + wholeWord: header.flags.wholeWord, + includes: header.includes, + excludes: header.excludes, + contextLines: header.context ?? 0, + useIgnores: !header.flags.ignoreExcludes, + showIncludesExcludes: !!(header.includes || header.excludes || header.flags.ignoreExcludes) + }, contents, resource); + + return editorService.openEditor(input, { ...options, pinned: true, ignoreOverrides: true }, group); + })() + }; + }); + } } export class SearchEditorInputFactory implements IEditorInputFactory { @@ -75,11 +96,17 @@ export class SearchEditorInputFactory implements IEditorInputFactory { canSerialize() { return true; } serialize(input: SearchEditorInput) { - return JSON.stringify(input.config); + let resource = undefined; + if (input.resource.path) { + resource = input.resource.toString(); + } + + return JSON.stringify({ ...input.config, resource }); } deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): SearchEditorInput | undefined { - return instantiationService.createInstance(SearchEditorInput, JSON.parse(serializedEditorInput), undefined); + const { resource, ...config } = JSON.parse(serializedEditorInput); + return instantiationService.createInstance(SearchEditorInput, config, undefined, resource && URI.parse(resource)); } } @@ -98,6 +125,7 @@ export class SearchEditorInput extends EditorInput { constructor( config: SearchConfiguration | undefined, initialContents: string | undefined, + resource: URI | undefined, @IModelService private readonly modelService: IModelService, @IModeService private readonly modeService: IModeService, @IEditorService protected readonly editorService: IEditorService, @@ -106,8 +134,7 @@ export class SearchEditorInput extends EditorInput { @IUntitledTextEditorService protected readonly untitledTextEditorService: IUntitledTextEditorService, ) { super(); - this.resource = URI.from({ scheme: 'search-editor', fragment: `${searchEditorInputInstances++}` }); - + this.resource = resource ?? URI.from({ scheme: 'search-editor', fragment: `${searchEditorInputInstances++}` }); if (config === undefined) { this._config = { query: '', includes: '', excludes: '', contextLines: 0, wholeWord: false, caseSensitive: false, regexp: false, useIgnores: true, showIncludesExcludes: false }; @@ -117,14 +144,19 @@ export class SearchEditorInput extends EditorInput { const searchResultMode = this.modeService.create('search-result'); - this.model = this.modelService.createModel(initialContents ?? '', searchResultMode, this.resource); + this.model = this.modelService.getModel(this.resource) ?? this.modelService.createModel(initialContents ?? '', searchResultMode, this.resource); } async save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { - const path = (this.config.query.replace(/\W/g, '') || 'Untitled Search') + '.code-search'; - const untitledResource = URI.from({ scheme: 'untitled', path, fragment: this.resource.fragment }); - const a = this.untitledTextEditorService.createOrGet(untitledResource, 'search-result', this.model.getValue()); - return a.save(group, options).finally(() => a.dispose()); + if (this.resource.scheme === 'search-editor') { + const path = (this.config.query.replace(/\W/g, '') || 'Untitled Search') + '.code-search'; + const untitledResource = URI.from({ scheme: 'untitled', path, fragment: this.resource.fragment }); + const a = this.untitledTextEditorService.createOrGet(untitledResource, 'search-result', this.model.getValue()); + return a.save(group, options).finally(() => a.dispose()); + } else { + const a = this.untitledTextEditorService.createOrGet(this.resource, 'search-result', this.model.getValue(), undefined, true); + return a.save(group, options).finally(() => a.dispose()); + } } getTypeId(): string { @@ -154,6 +186,9 @@ export class SearchEditorInput extends EditorInput { if (other instanceof UntitledTextEditorInput) { if (other.getResource().fragment === this.resource.fragment) { return true; } } + if (other instanceof SearchEditorInput) { + if (other.resource.path && other.resource.path === this.resource.path) { return true; } + } return false; } } @@ -432,7 +467,7 @@ export const refreshActiveEditorSearch = export const openNewSearchEditor = async (editorService: IEditorService, instantiationService: IInstantiationService) => { - await editorService.openEditor(instantiationService.createInstance(SearchEditorInput, undefined, undefined), { pinned: true }); + await editorService.openEditor(instantiationService.createInstance(SearchEditorInput, undefined, undefined, undefined), { pinned: true }); }; export const createEditorFromSearchResult = @@ -481,7 +516,7 @@ export const createEditorFromSearchResult = contextLines: 0, useIgnores: !searchResult.query.userDisabledExcludesAndIgnoreFiles, showIncludesExcludes: !!(rawExcludePattern || rawExcludePattern || searchResult.query.userDisabledExcludesAndIgnoreFiles) - }, contents); + }, contents, undefined); const editor = await editorService.openEditor(input, { pinned: true }) as SearchEditor; const model = assertIsDefined(editor.getModel()); From b6b3fe55ce606fa268b454adfe1580e4d0976728 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Tue, 14 Jan 2020 18:18:15 -0800 Subject: [PATCH 307/315] Ongoing work on save/open search results. --- .../search/browser/searchEditorCommands.ts | 88 +++++++++++++++---- 1 file changed, 73 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts index bbcc5ba4d2e6b..93f3fc6ea7f0a 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts @@ -25,7 +25,7 @@ import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/co import { FileMatch, Match, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IPatternInfo, ISearchConfigurationProperties, ITextQuery } from 'vs/workbench/services/search/common/search'; -import { IEditorInputFactory, GroupIdentifier, EditorInput } from 'vs/workbench/common/editor'; +import { IEditorInputFactory, GroupIdentifier, EditorInput, SaveContext } from 'vs/workbench/common/editor'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { SearchEditor } from 'vs/workbench/contrib/search/browser/searchEditor'; @@ -34,6 +34,11 @@ import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/te import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import type { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { dirname, joinPath, isEqual } from 'vs/base/common/resources'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { basename } from 'vs/base/common/path'; @@ -54,10 +59,14 @@ export class SearchEditorContribution implements IWorkbenchContribution { @IEditorService private readonly editorService: IEditorService, @ITextFileService protected readonly textFileService: ITextFileService, @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IModelService protected readonly modelService: IModelService, ) { + this.editorService.overrideOpenEditor((editor, options, group) => { const resource = editor.getResource(); - if (!resource || !endsWith(resource.path, '.code-search') || !(editor instanceof FileEditorInput)) { + if (!resource || + !(endsWith(resource.path, '.code-search') || resource.scheme === 'search-editor') || + !(editor instanceof FileEditorInput || (resource.scheme === 'search-editor'))) { return undefined; } @@ -67,7 +76,7 @@ export class SearchEditorContribution implements IWorkbenchContribution { return { override: (async () => { - const contents = (await this.textFileService.read(resource)).value; + const contents = resource.scheme === 'search-editor' ? this.modelService.getModel(resource)?.getValue() ?? '' : (await this.textFileService.read(resource)).value; const header = searchHeaderToContentPattern(contents.split('\n').slice(0, 5)); const input = instantiationService.createInstance( @@ -84,7 +93,7 @@ export class SearchEditorContribution implements IWorkbenchContribution { showIncludesExcludes: !!(header.includes || header.excludes || header.flags.ignoreExcludes) }, contents, resource); - return editorService.openEditor(input, { ...options, pinned: true, ignoreOverrides: true }, group); + return editorService.openEditor(input, { ...options, pinned: resource.scheme === 'search-editor', ignoreOverrides: true }, group); })() }; }); @@ -132,6 +141,10 @@ export class SearchEditorInput extends EditorInput { @IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService, @ITextFileService protected readonly textFileService: ITextFileService, @IUntitledTextEditorService protected readonly untitledTextEditorService: IUntitledTextEditorService, + @IHistoryService private readonly historyService: IHistoryService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); this.resource = resource ?? URI.from({ scheme: 'search-editor', fragment: `${searchEditorInputInstances++}` }); @@ -149,22 +162,42 @@ export class SearchEditorInput extends EditorInput { async save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { if (this.resource.scheme === 'search-editor') { - const path = (this.config.query.replace(/\W/g, '') || 'Untitled Search') + '.code-search'; - const untitledResource = URI.from({ scheme: 'untitled', path, fragment: this.resource.fragment }); - const a = this.untitledTextEditorService.createOrGet(untitledResource, 'search-result', this.model.getValue()); - return a.save(group, options).finally(() => a.dispose()); + const path = await this.promptForPath(this.resource, this.suggestFileName()); + if (path) { + if (await this.textFileService.saveAs(this.resource, path, options)) { + if (options?.context !== SaveContext.EDITOR_CLOSE && !isEqual(path, this.resource)) { + const replacement = this.instantiationService.createInstance(SearchEditorInput, this.config, undefined, path); + await this.editorService.replaceEditors([{ editor: this, replacement, options: { pinned: true } }], group); + return true; + } + } + } + return false; } else { - const a = this.untitledTextEditorService.createOrGet(this.resource, 'search-result', this.model.getValue(), undefined, true); - return a.save(group, options).finally(() => a.dispose()); + return !!this.textFileService.write(this.resource, this.model.getValue(), options); } } + // Brining this over from textFileService because it only suggests for untitled scheme. + // In the future I may just use the untitled scheme. I dont get particular benefit from using search-editor... + private async promptForPath(resource: URI, defaultUri: URI): Promise { + // Help user to find a name for the file by opening it first + await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); + return this.fileDialogService.pickFileToSave({ + defaultUri, + title: localize('saveAsTitle', "Save As"), + }); + } + getTypeId(): string { return SearchEditorInput.ID; } getName(): string { - return this.config.query ? localize('searchTitle.withQuery', "Search: {0}", this.config.query) : localize('searchTitle', "Search"); + if (this.resource.scheme === 'search-editor') { + return this.config.query ? localize('searchTitle.withQuery', "Search: {0}", this.config.query) : localize('searchTitle', "Search"); + } + return localize('searchTitle.withQuery', "Search: {0}", basename(this.resource.path, '.code-search')); } setConfig(config: SearchConfiguration) { @@ -183,14 +216,39 @@ export class SearchEditorInput extends EditorInput { matches(other: unknown) { if (this === other) { return true; } - if (other instanceof UntitledTextEditorInput) { - if (other.getResource().fragment === this.resource.fragment) { return true; } - } + if (other instanceof SearchEditorInput) { - if (other.resource.path && other.resource.path === this.resource.path) { return true; } + if ( + (other.resource.path && other.resource.path === this.resource.path) || + (other.resource.fragment && other.resource.fragment === this.resource.fragment) + ) { + return true; + } } return false; } + + // Bringing this over from textFileService because it only suggests for untitled scheme. + // In the future I may just use the untitled scheme. I dont get particular benefit from using search-editor... + private suggestFileName(): URI { + const searchFileName = (this.config.query.replace(/[^\w \-_]+/g, '_') || 'Search') + '.code-search'; + + const remoteAuthority = this.environmentService.configuration.remoteAuthority; + const schemeFilter = remoteAuthority ? network.Schemas.vscodeRemote : network.Schemas.file; + + const lastActiveFile = this.historyService.getLastActiveFile(schemeFilter); + if (lastActiveFile) { + const lastDir = dirname(lastActiveFile); + return joinPath(lastDir, searchFileName); + } + + const lastActiveFolder = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); + if (lastActiveFolder) { + return joinPath(lastActiveFolder, searchFileName); + } + + return URI.from({ scheme: schemeFilter, path: searchFileName }); + } } // Using \r\n on Windows inserts an extra newline between results. From b6fcbb1b1691219079f7065b2e68e2b0bfb22f76 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Tue, 14 Jan 2020 19:37:38 -0800 Subject: [PATCH 308/315] Add dirty indicators --- .../contrib/search/browser/searchEditor.ts | 6 ++++++ .../contrib/search/browser/searchEditorCommands.ts | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index 055a554042654..de8d65295ece4 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -162,6 +162,7 @@ export class SearchEditor extends BaseEditor { }); this._register(this.searchResultEditor.onDidChangeModel(() => this.hideHeader())); + this._register(this.searchResultEditor.onDidChangeModelContent(() => (this._input as SearchEditorInput)?.setDirty(true))); } private async runSearch(instant = false) { @@ -232,6 +233,7 @@ export class SearchEditor extends BaseEditor { const results = serializeSearchResultForEditor(searchModel.searchResult, config.includes, config.excludes, config.contextLines, labelFormatter, true); const textModel = assertIsDefined(this.searchResultEditor.getModel()); textModel.setValue(results.text.join(lineDelimiter)); + this.getInput()?.setDirty(this.getInput()?.resource.scheme !== 'search-editor'); this.hideHeader(); textModel.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); @@ -269,6 +271,10 @@ export class SearchEditor extends BaseEditor { } } + private getInput(): SearchEditorInput | undefined { + return this._input as SearchEditorInput; + } + async setInput(newInput: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { await super.setInput(newInput, options, token); diff --git a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts index 93f3fc6ea7f0a..38be7b88471d6 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts @@ -131,6 +131,8 @@ export class SearchEditorInput extends EditorInput { private model: ITextModel; public readonly resource: URI; + private dirty: boolean = false; + constructor( config: SearchConfiguration | undefined, initialContents: string | undefined, @@ -169,11 +171,14 @@ export class SearchEditorInput extends EditorInput { const replacement = this.instantiationService.createInstance(SearchEditorInput, this.config, undefined, path); await this.editorService.replaceEditors([{ editor: this, replacement, options: { pinned: true } }], group); return true; + } else if (options?.context === SaveContext.EDITOR_CLOSE) { + return true; } } } return false; } else { + this.setDirty(false); return !!this.textFileService.write(this.resource, this.model.getValue(), options); } } @@ -209,6 +214,15 @@ export class SearchEditorInput extends EditorInput { return null; } + setDirty(dirty: boolean) { + this.dirty = dirty; + this._onDidChangeDirty.fire(); + } + + isDirty() { + return this.dirty; + } + dispose() { this.modelService.destroyModel(this.resource); super.dispose(); From b40e5ce729ce49b7311e92f65f1f21c514aa9c2c Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Tue, 14 Jan 2020 20:11:36 -0800 Subject: [PATCH 309/315] Add `matchienessHeuristic` to delay some regex searches. Fixes #88539. --- .../contrib/search/browser/searchWidget.ts | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 30b5c34eb4ab7..c526a5f0fdec5 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -499,7 +499,31 @@ export class SearchWidget extends Widget { this.temporarilySkipSearchOnChange = false; } else { this._onSearchCancel.fire({ focus: false }); - this._searchDelayer.trigger((() => this.submitSearch(true)), this.searchConfiguration.searchOnTypeDebouncePeriod); + if (this.searchInput.getRegex()) { + try { + const regex = new RegExp(this.searchInput.getValue(), 'ug'); + const matchienessHeuristic = ` +~!@#$%^&*()_+ +\`1234567890-= +qwertyuiop[]\\ +QWERTYUIOP{}| +asdfghjkl;' +ASDFGHJKL:" +zxcvbnm,./ +ZXCVBNM<>? `.match(regex)?.length ?? 0; + + const delayMultiplier = + matchienessHeuristic < 50 ? 1 : + matchienessHeuristic < 100 ? 5 : // expressions like `.` or `\w` + 10; // only things matching empty string + + this._searchDelayer.trigger((() => this.submitSearch(true)), this.searchConfiguration.searchOnTypeDebouncePeriod * delayMultiplier); + } catch { + // pass + } + } else { + this._searchDelayer.trigger((() => this.submitSearch(true)), this.searchConfiguration.searchOnTypeDebouncePeriod); + } } } } From e2d7fc79a6d0b8d6f5c73e95dc90da1ceea003ee Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 15 Jan 2020 07:02:42 +0100 Subject: [PATCH 310/315] #85216 Show download/upload options when syncing for first time --- .../userDataSync/browser/userDataSync.ts | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 5d87233cb6638..e91b707a36cd1 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -35,6 +35,7 @@ import { IOutputService } from 'vs/workbench/contrib/output/common/output'; import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; import { IAuthenticationService, ChangeAccountEventData } from 'vs/workbench/services/authentication/browser/authenticationService'; import { Account } from 'vs/editor/common/modes'; +import { canceled, isPromiseCanceledError } from 'vs/base/common/errors'; const enum MSAAuthStatus { Initializing = 'Initializing', @@ -364,6 +365,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo if (this.authenticationState.get() === MSAAuthStatus.SignedOut) { await this.signIn(); } + + await this.handleFirstTimeSync(); await this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, true); this.notificationService.info(localize('Sync Started', "Sync Started.")); } @@ -413,6 +416,36 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); } + private async handleFirstTimeSync(): Promise { + + const hasRemote = await this.userDataSyncService.hasRemote(); + const hasPreviouslySynced = await this.userDataSyncService.hasPreviouslySynced(); + + if (hasRemote && !hasPreviouslySynced) { + const result = await this.dialogService.show( + Severity.Info, + localize('firs time sync', "First time synchronization"), + [ + localize('continue', "Continue"), + localize('cancel', "Cancel"), + localize('download', "Download"), + localize('upload', "Upload"), + ], + { + cancelId: 1, + detail: localize('first time sync detail', "Synchronising from this device for the first time. Would you like to \nDownload and replace with the remote data or \nUpload from this device and replace the remote data?") + } + ); + + switch (result.choice) { + case 1: return Promise.reject(canceled()); + case 2: return this.userDataSyncService.pull(); + // case 3: return this.userDataSyncService.push(); + case 3: return this.notificationService.info('not yet supported'); + } + } + } + private async turnOff(): Promise { const result = await this.dialogService.confirm({ type: 'info', @@ -509,7 +542,15 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo const turnOnSyncCommandId = 'workbench.userData.actions.syncStart'; const turnOnSyncWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.not(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.notEqualsTo(MSAAuthStatus.Initializing)); - CommandsRegistry.registerCommand(turnOnSyncCommandId, () => this.turnOn()); + CommandsRegistry.registerCommand(turnOnSyncCommandId, async () => { + try { + await this.turnOn(); + } catch (e) { + if (!isPromiseCanceledError(e)) { + this.notificationService.error(e); + } + } + }); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', command: { From f85a3c815315679ecbe3ce595488c0c06fd0180a Mon Sep 17 00:00:00 2001 From: Jens Fischer Date: Tue, 14 Jan 2020 19:05:23 +0100 Subject: [PATCH 311/315] Clarify the docs for QuickPickItem.description and detail --- src/vs/vscode.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index eabfc93a66b4d..cf02afe365ee2 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -1583,12 +1583,12 @@ declare module 'vscode' { label: string; /** - * A human-readable string which is rendered less prominent. + * A human-readable string which is rendered less prominent in the same line. */ description?: string; /** - * A human-readable string which is rendered less prominent. + * A human-readable string which is rendered less prominent in a separate line. */ detail?: string; From 78ba3c1ff4d5b162a810066914bfedaa98b1b07b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 15 Jan 2020 08:24:22 +0100 Subject: [PATCH 312/315] textfiles - no need to instantiate BaseTextEditorModel for "Save As" --- .../textfile/browser/textFileService.ts | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 5ee41d8fab96d..6c32866863e91 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -29,7 +29,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { coalesce } from 'vs/base/common/arrays'; import { trim } from 'vs/base/common/strings'; import { VSBuffer } from 'vs/base/common/buffer'; -import { ITextSnapshot } from 'vs/editor/common/model'; +import { ITextSnapshot, ITextModel } from 'vs/editor/common/model'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; @@ -535,12 +535,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex else { const textModel = this.modelService.getModel(source); if (textModel) { - const model = new BaseTextEditorModel(this.modelService, this.modeService, source); - if (model.isResolved()) { - success = await this.doSaveAsTextFile(model, source, target, options); - } - - model.dispose(); // free up + success = await this.doSaveAsTextFile(textModel, source, target, options); } } @@ -552,7 +547,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return target; } - private async doSaveAsTextFile(sourceModel: IResolvedTextEditorModel, source: URI, target: URI, options?: ITextFileSaveOptions): Promise { + private async doSaveAsTextFile(sourceModel: IResolvedTextEditorModel | ITextModel, source: URI, target: URI, options?: ITextFileSaveOptions): Promise { // Find source encoding if any let sourceModelEncoding: string | undefined = undefined; @@ -606,15 +601,29 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return false; } + let sourceTextModel: ITextModel | undefined = undefined; + if (sourceModel instanceof BaseTextEditorModel) { + if (sourceModel.isResolved()) { + sourceTextModel = sourceModel.textEditorModel; + } + } else { + sourceTextModel = sourceModel as ITextModel; + } + + let targetTextModel: ITextModel | undefined = undefined; + if (targetModel.isResolved()) { + targetTextModel = targetModel.textEditorModel; + } + // take over model value, encoding and mode (only if more specific) from source model targetModel.updatePreferredEncoding(sourceModelEncoding); - if (sourceModel.isResolved() && targetModel.isResolved()) { - this.modelService.updateModel(targetModel.textEditorModel, createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot())); + if (sourceTextModel && targetTextModel) { + this.modelService.updateModel(targetTextModel, createTextBufferFactoryFromSnapshot(sourceTextModel.createSnapshot())); - const sourceMode = sourceModel.textEditorModel.getLanguageIdentifier(); - const targetMode = targetModel.textEditorModel.getLanguageIdentifier(); + const sourceMode = sourceTextModel.getLanguageIdentifier(); + const targetMode = targetTextModel.getLanguageIdentifier(); if (sourceMode.language !== PLAINTEXT_MODE_ID && targetMode.language === PLAINTEXT_MODE_ID) { - targetModel.textEditorModel.setMode(sourceMode); // only use if more specific than plain/text + targetTextModel.setMode(sourceMode); // only use if more specific than plain/text } } From 33c79d5ad447956814a2a3658029dffb9e28bae6 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 15 Jan 2020 09:27:19 +0100 Subject: [PATCH 313/315] Fix #88659 --- src/vs/workbench/browser/parts/views/views.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 5f19242c30972..f817c344e4e84 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -631,14 +631,17 @@ export class ViewsService extends Disposable implements IViewsService { this.viewDisposable.clear(); })); - this.viewContainersRegistry.all.forEach(viewContainer => { - const viewDescriptorCollection = this.viewDescriptorService.getViewDescriptors(viewContainer); - this.onViewsRegistered(viewDescriptorCollection.allViewDescriptors, viewContainer); - this._register(viewDescriptorCollection.onDidChangeViews(({ added, removed }) => { - this.onViewsRegistered(added, viewContainer); - this.onViewsDeregistered(removed, viewContainer); - })); - }); + this.viewContainersRegistry.all.forEach(viewContainer => this.onViewContainerRegistered(viewContainer)); + this._register(this.viewContainersRegistry.onDidRegister(({ viewContainer }) => this.onViewContainerRegistered(viewContainer))); + } + + private onViewContainerRegistered(viewContainer: ViewContainer): void { + const viewDescriptorCollection = this.viewDescriptorService.getViewDescriptors(viewContainer); + this.onViewsRegistered(viewDescriptorCollection.allViewDescriptors, viewContainer); + this._register(viewDescriptorCollection.onDidChangeViews(({ added, removed }) => { + this.onViewsRegistered(added, viewContainer); + this.onViewsDeregistered(removed, viewContainer); + })); } private onViewsRegistered(views: IViewDescriptor[], container: ViewContainer): void { From de264a8bf54c6314eb44270b35d418b4b22e82a0 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 15 Jan 2020 09:33:47 +0100 Subject: [PATCH 314/315] files - working copy service for explorer delete --- .../browser/parts/editor/editorAutoSave.ts | 4 +- .../backup/browser/backupOnShutdown.ts | 2 +- .../electron-browser/backupOnShutdown.ts | 6 +- .../contrib/files/browser/fileActions.ts | 135 +++++++++--------- .../contrib/files/browser/fileCommands.ts | 4 +- .../workingCopy/common/workingCopyService.ts | 86 +++++------ .../test/common/workingCopyService.test.ts | 3 + 7 files changed, 122 insertions(+), 118 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts index 6be968e338f4a..4bf84682dba5b 100644 --- a/src/vs/workbench/browser/parts/editor/editorAutoSave.ts +++ b/src/vs/workbench/browser/parts/editor/editorAutoSave.ts @@ -133,8 +133,8 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution } private saveAllDirty(options?: ISaveOptions): void { - for (const workingCopy of this.workingCopyService.workingCopies) { - if (workingCopy.isDirty() && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled)) { + for (const workingCopy of this.workingCopyService.dirtyWorkingCopies) { + if (!(workingCopy.capabilities & WorkingCopyCapabilities.Untitled)) { workingCopy.save(options); } } diff --git a/src/vs/workbench/contrib/backup/browser/backupOnShutdown.ts b/src/vs/workbench/contrib/backup/browser/backupOnShutdown.ts index 8c17c75255249..607607f678c75 100644 --- a/src/vs/workbench/contrib/backup/browser/backupOnShutdown.ts +++ b/src/vs/workbench/contrib/backup/browser/backupOnShutdown.ts @@ -34,7 +34,7 @@ export class BackupOnShutdown extends Disposable implements IWorkbenchContributi // copies that have not been backed up yet and then prevent the // shutdown if that is the case. - const dirtyWorkingCopies = this.workingCopyService.workingCopies.filter(workingCopy => workingCopy.isDirty()); + const dirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies; if (!dirtyWorkingCopies.length) { return false; // no dirty: no veto } diff --git a/src/vs/workbench/contrib/backup/electron-browser/backupOnShutdown.ts b/src/vs/workbench/contrib/backup/electron-browser/backupOnShutdown.ts index 8e1053caa6519..e6e17435bc321 100644 --- a/src/vs/workbench/contrib/backup/electron-browser/backupOnShutdown.ts +++ b/src/vs/workbench/contrib/backup/electron-browser/backupOnShutdown.ts @@ -46,7 +46,7 @@ export class BackupOnShutdown extends Disposable implements IWorkbenchContributi private onBeforeShutdown(reason: ShutdownReason): boolean | Promise { // Dirty working copies need treatment on shutdown - const dirtyWorkingCopies = this.workingCopyService.workingCopies.filter(workingCopy => workingCopy.isDirty()); + const dirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies; if (dirtyWorkingCopies.length) { // If auto save is enabled, save all working copies and then check again for dirty copies @@ -55,7 +55,7 @@ export class BackupOnShutdown extends Disposable implements IWorkbenchContributi return this.doSaveAll(dirtyWorkingCopies, false /* not untitled */, { skipSaveParticipants: true }).then(() => { // If we still have dirty working copies, we either have untitled ones or working copies that cannot be saved - const remainingDirtyWorkingCopies = this.workingCopyService.workingCopies.filter(workingCopy => workingCopy.isDirty()); + const remainingDirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies; if (remainingDirtyWorkingCopies.length) { return this.handleDirtyBeforeShutdown(remainingDirtyWorkingCopies, reason); } @@ -143,7 +143,7 @@ export class BackupOnShutdown extends Disposable implements IWorkbenchContributi private async confirmBeforeShutdown(): Promise { // Show confirm dialog for all dirty working copies - const dirtyWorkingCopies = this.workingCopyService.workingCopies.filter(workingCopy => workingCopy.isDirty()); + const dirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies; const confirm = await this.fileDialogService.showSaveConfirm(dirtyWorkingCopies.map(w => w.resource)); // Save diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 3774785acbcf9..c6ccc289d7d8b 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -143,7 +143,7 @@ export class GlobalNewUntitledFileAction extends Action { } } -async function deleteFiles(textFileService: ITextFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise { +async function deleteFiles(workingCopyService: IWorkingCopyService, textFileService: ITextFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise { let primaryButton: string; if (useTrash) { primaryButton = isWindows ? nls.localize('deleteButtonLabelRecycleBin', "&&Move to Recycle Bin") : nls.localize({ key: 'deleteButtonLabelTrash', comment: ['&& denotes a mnemonic'] }, "&&Move to Trash"); @@ -155,16 +155,16 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi // Handle dirty let confirmed = true; - const dirty = textFileService.getDirty().filter(d => distinctElements.some(e => resources.isEqualOrParent(d, e.resource))); - if (dirty.length) { + const dirtyWorkingCopies = workingCopyService.dirtyWorkingCopies.filter(workingCopy => distinctElements.some(e => resources.isEqualOrParent(workingCopy.resource, e.resource))); + if (dirtyWorkingCopies.length) { let message: string; if (distinctElements.length > 1) { message = nls.localize('dirtyMessageFilesDelete', "You are deleting files with unsaved changes. Do you want to continue?"); } else if (distinctElements[0].isDirectory) { - if (dirty.length === 1) { + if (dirtyWorkingCopies.length === 1) { message = nls.localize('dirtyMessageFolderOneDelete', "You are deleting a folder with unsaved changes in 1 file. Do you want to continue?"); } else { - message = nls.localize('dirtyMessageFolderDelete', "You are deleting a folder with unsaved changes in {0} files. Do you want to continue?", dirty.length); + message = nls.localize('dirtyMessageFolderDelete', "You are deleting a folder with unsaved changes in {0} files. Do you want to continue?", dirtyWorkingCopies.length); } } else { message = nls.localize('dirtyMessageFileDelete', "You are deleting a file with unsaved changes. Do you want to continue?"); @@ -181,7 +181,7 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi confirmed = false; } else { skipConfirm = true; - await textFileService.revertAll(dirty); + await Promise.all(dirtyWorkingCopies.map(dirty => dirty.revert())); } } @@ -190,11 +190,11 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi return; } - let confirmDeletePromise: Promise; + let confirmation: IConfirmationResult; // Check if we need to ask for confirmation at all if (skipConfirm || (useTrash && configurationService.getValue(CONFIRM_DELETE_SETTING_KEY) === false)) { - confirmDeletePromise = Promise.resolve({ confirmed: true }); + confirmation = { confirmed: true }; } // Confirm for moving to trash @@ -207,7 +207,7 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi detail += distinctElements.length > 1 ? nls.localize('undoTrashFiles', "You can restore these files from the Trash.") : nls.localize('undoTrash', "You can restore this file from the Trash."); } - confirmDeletePromise = dialogService.confirm({ + confirmation = await dialogService.confirm({ message, detail, primaryButton, @@ -223,7 +223,7 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi let { message, detail } = getDeleteMessage(distinctElements); detail += detail ? '\n' : ''; detail += nls.localize('irreversible', "This action is irreversible!"); - confirmDeletePromise = dialogService.confirm({ + confirmation = await dialogService.confirm({ message, detail, primaryButton, @@ -231,61 +231,54 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi }); } - return confirmDeletePromise.then(confirmation => { - // Check for confirmation checkbox - let updateConfirmSettingsPromise: Promise = Promise.resolve(undefined); - if (confirmation.confirmed && confirmation.checkboxChecked === true) { - updateConfirmSettingsPromise = configurationService.updateValue(CONFIRM_DELETE_SETTING_KEY, false, ConfigurationTarget.USER); - } - return updateConfirmSettingsPromise.then(() => { + // Check for confirmation checkbox + if (confirmation.confirmed && confirmation.checkboxChecked === true) { + await configurationService.updateValue(CONFIRM_DELETE_SETTING_KEY, false, ConfigurationTarget.USER); + } - // Check for confirmation - if (!confirmation.confirmed) { - return Promise.resolve(undefined); - } - // Call function - const servicePromise = Promise.all(distinctElements.map(e => textFileService.delete(e.resource, { useTrash: useTrash, recursive: true }))) - .then(undefined, (error: any) => { - // Handle error to delete file(s) from a modal confirmation dialog - let errorMessage: string; - let detailMessage: string | undefined; - let primaryButton: string; - if (useTrash) { - errorMessage = isWindows ? nls.localize('binFailed', "Failed to delete using the Recycle Bin. Do you want to permanently delete instead?") : nls.localize('trashFailed', "Failed to delete using the Trash. Do you want to permanently delete instead?"); - detailMessage = nls.localize('irreversible', "This action is irreversible!"); - primaryButton = nls.localize({ key: 'deletePermanentlyButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete Permanently"); - } else { - errorMessage = toErrorMessage(error, false); - primaryButton = nls.localize({ key: 'retryButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Retry"); - } + // Check for confirmation + if (!confirmation.confirmed) { + return; + } - return dialogService.confirm({ - message: errorMessage, - detail: detailMessage, - type: 'warning', - primaryButton - }).then(res => { + // Call function + try { + await Promise.all(distinctElements.map(e => textFileService.delete(e.resource, { useTrash: useTrash, recursive: true }))); + } catch (error) { - if (res.confirmed) { - if (useTrash) { - useTrash = false; // Delete Permanently - } + // Handle error to delete file(s) from a modal confirmation dialog + let errorMessage: string; + let detailMessage: string | undefined; + let primaryButton: string; + if (useTrash) { + errorMessage = isWindows ? nls.localize('binFailed', "Failed to delete using the Recycle Bin. Do you want to permanently delete instead?") : nls.localize('trashFailed', "Failed to delete using the Trash. Do you want to permanently delete instead?"); + detailMessage = nls.localize('irreversible', "This action is irreversible!"); + primaryButton = nls.localize({ key: 'deletePermanentlyButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete Permanently"); + } else { + errorMessage = toErrorMessage(error, false); + primaryButton = nls.localize({ key: 'retryButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Retry"); + } - skipConfirm = true; + const res = await dialogService.confirm({ + message: errorMessage, + detail: detailMessage, + type: 'warning', + primaryButton + }); - return deleteFiles(textFileService, dialogService, configurationService, elements, useTrash, skipConfirm); - } + if (res.confirmed) { + if (useTrash) { + useTrash = false; // Delete Permanently + } - return Promise.resolve(); - }); - }); + skipConfirm = true; - return servicePromise.then(undefined); - }); - }); + return deleteFiles(workingCopyService, textFileService, dialogService, configurationService, elements, useTrash, skipConfirm); + } + } } function getMoveToTrashMessage(distinctElements: ExplorerItem[]): { message: string, detail: string } { @@ -648,7 +641,7 @@ export class ShowActiveFileInExplorer extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const resource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource) { this.commandService.executeCommand(REVEAL_IN_EXPLORER_COMMAND_ID, resource); @@ -656,7 +649,7 @@ export class ShowActiveFileInExplorer extends Action { this.notificationService.info(nls.localize('openFileToShow', "Open a file first to show it in the explorer")); } - return Promise.resolve(true); + return true; } } @@ -726,7 +719,7 @@ export class ShowOpenedFileInNewWindow extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (fileResource) { if (this.fileService.canHandleResource(fileResource)) { @@ -738,7 +731,7 @@ export class ShowOpenedFileInNewWindow extends Action { this.notificationService.info(nls.localize('openFileToShowInNewWindow.nofile', "Open a file first to open in new window")); } - return Promise.resolve(true); + return true; } } @@ -822,7 +815,7 @@ export class CompareWithClipboardAction extends Action { this.enabled = true; } - run(): Promise { + async run(): Promise { const resource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource && (this.fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled)) { if (!this.registrationDisposal) { @@ -839,7 +832,7 @@ export class CompareWithClipboardAction extends Action { }); } - return Promise.resolve(true); + return true; } dispose(): void { @@ -902,15 +895,17 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole folder.addChild(newStat); - const onSuccess = (value: string): Promise => { - const createPromise = isFolder ? fileService.createFolder(resources.joinPath(folder.resource, value)) : textFileService.create(resources.joinPath(folder.resource, value)); - return createPromise.then(created => { + const onSuccess = async (value: string): Promise => { + try { + const created = isFolder ? await fileService.createFolder(resources.joinPath(folder.resource, value)) : await textFileService.create(resources.joinPath(folder.resource, value)); refreshIfSeparator(value, explorerService); - return isFolder ? explorerService.select(created.resource, true) - : editorService.openEditor({ resource: created.resource, options: { pinned: true } }).then(() => undefined); - }, error => { + + isFolder ? + await explorerService.select(created.resource, true) : + await editorService.openEditor({ resource: created.resource, options: { pinned: true } }); + } catch (error) { onErrorWithRetry(notificationService, error, () => onSuccess(value)); - }); + } }; explorerService.setEditable(newStat, { @@ -974,7 +969,7 @@ export const moveFileToTrashHandler = async (accessor: ServicesAccessor) => { const explorerService = accessor.get(IExplorerService); const stats = explorerService.getContext(true).filter(s => !s.isRoot); if (stats.length) { - await deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true); + await deleteFiles(accessor.get(IWorkingCopyService), accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true); } }; @@ -983,7 +978,7 @@ export const deleteFileHandler = async (accessor: ServicesAccessor) => { const stats = explorerService.getContext(true).filter(s => !s.isRoot); if (stats.length) { - await deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false); + await deleteFiles(accessor.get(IWorkingCopyService), accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false); } }; diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index bb78165612457..cf6c906c91db9 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -200,7 +200,7 @@ CommandsRegistry.registerCommand({ CommandsRegistry.registerCommand({ id: COMPARE_SELECTED_COMMAND_ID, - handler: (accessor, resource: URI | object) => { + handler: async (accessor, resource: URI | object) => { const editorService = accessor.get(IEditorService); const explorerService = accessor.get(IExplorerService); const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService, explorerService); @@ -212,7 +212,7 @@ CommandsRegistry.registerCommand({ }); } - return Promise.resolve(true); + return true; } }); diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts index c3543e4c82404..3193a3dd81cbc 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts @@ -81,6 +81,8 @@ export interface IWorkingCopyService { readonly dirtyCount: number; + readonly dirtyWorkingCopies: IWorkingCopy[]; + readonly hasDirty: boolean; isDirty(resource: URI): boolean; @@ -118,46 +120,6 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic //#endregion - //#region Dirty Tracking - - isDirty(resource: URI): boolean { - const workingCopies = this.mapResourceToWorkingCopy.get(resource.toString()); - if (workingCopies) { - for (const workingCopy of workingCopies) { - if (workingCopy.isDirty()) { - return true; - } - } - } - - return false; - } - - get hasDirty(): boolean { - for (const workingCopy of this._workingCopies) { - if (workingCopy.isDirty()) { - return true; - } - } - - return false; - } - - get dirtyCount(): number { - let totalDirtyCount = 0; - - for (const workingCopy of this._workingCopies) { - if (workingCopy.isDirty()) { - totalDirtyCount++; - } - } - - return totalDirtyCount; - } - - //#endregion - - //#region Registry private mapResourceToWorkingCopy = TernarySearchTree.forPaths>(); @@ -216,6 +178,50 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic } //#endregion + + + //#region Dirty Tracking + + get hasDirty(): boolean { + for (const workingCopy of this._workingCopies) { + if (workingCopy.isDirty()) { + return true; + } + } + + return false; + } + + get dirtyCount(): number { + let totalDirtyCount = 0; + + for (const workingCopy of this._workingCopies) { + if (workingCopy.isDirty()) { + totalDirtyCount++; + } + } + + return totalDirtyCount; + } + + get dirtyWorkingCopies(): IWorkingCopy[] { + return this.workingCopies.filter(workingCopy => workingCopy.isDirty()); + } + + isDirty(resource: URI): boolean { + const workingCopies = this.mapResourceToWorkingCopy.get(resource.toString()); + if (workingCopies) { + for (const workingCopy of workingCopies) { + if (workingCopy.isDirty()) { + return true; + } + } + } + + return false; + } + + //#endregion } registerSingleton(IWorkingCopyService, WorkingCopyService, true); diff --git a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts index b94eb61e38406..a6a5269f46221 100644 --- a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts @@ -105,7 +105,10 @@ suite('WorkingCopyService', () => { copy1.setDirty(true); + assert.equal(copy1.isDirty(), true); assert.equal(service.dirtyCount, 1); + assert.equal(service.dirtyWorkingCopies.length, 1); + assert.equal(service.dirtyWorkingCopies[0], copy1); assert.equal(service.isDirty(resource1), true); assert.equal(service.hasDirty, true); assert.equal(onDidChangeDirty.length, 1); From 3d5b61c8435f651a3788062978ea344abdf865e5 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 15 Jan 2020 09:56:26 +0100 Subject: [PATCH 315/315] fixes #86822 --- src/vs/workbench/contrib/files/browser/fileActions.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index c6ccc289d7d8b..fa1c63a1df7e0 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -162,12 +162,12 @@ async function deleteFiles(workingCopyService: IWorkingCopyService, textFileServ message = nls.localize('dirtyMessageFilesDelete', "You are deleting files with unsaved changes. Do you want to continue?"); } else if (distinctElements[0].isDirectory) { if (dirtyWorkingCopies.length === 1) { - message = nls.localize('dirtyMessageFolderOneDelete', "You are deleting a folder with unsaved changes in 1 file. Do you want to continue?"); + message = nls.localize('dirtyMessageFolderOneDelete', "You are deleting a folder {0} with unsaved changes in 1 file. Do you want to continue?", distinctElements[0].name); } else { - message = nls.localize('dirtyMessageFolderDelete', "You are deleting a folder with unsaved changes in {0} files. Do you want to continue?", dirtyWorkingCopies.length); + message = nls.localize('dirtyMessageFolderDelete', "You are deleting a folder {0} with unsaved changes in {1} files. Do you want to continue?", distinctElements[0].name, dirtyWorkingCopies.length); } } else { - message = nls.localize('dirtyMessageFileDelete', "You are deleting a file with unsaved changes. Do you want to continue?"); + message = nls.localize('dirtyMessageFileDelete', "You are deleting {0} with unsaved changes. Do you want to continue?", distinctElements[0].name); } const response = await dialogService.confirm({