From 021352c440d113e63f983cfa5590630e2e4042bd Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 28 Jan 2022 12:48:13 +0100 Subject: [PATCH 001/197] change the format of CellUri#generate and #parse --- .../contrib/notebook/common/notebookCommon.ts | 36 ++++++++++++------- .../test/browser/notebookCommon.test.ts | 15 ++++++++ 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index fae60493ef3..e1c82a83170 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -508,33 +508,45 @@ export namespace CellUri { export const scheme = Schemas.vscodeNotebookCell; - const _regex = /^ch(\d{7,})/; + + const _lengths = ['W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f']; + const _padRegexp = new RegExp(`^[${_lengths.join('')}]+`); + const _radix = 7; export function generate(notebook: URI, handle: number): URI { - return notebook.with({ - scheme, - fragment: `ch${handle.toString().padStart(7, '0')}${notebook.scheme !== Schemas.file ? notebook.scheme : ''}` - }); + + const s = handle.toString(_radix); + const p = s.length < _lengths.length ? _lengths[s.length - 1] : 'z'; + + const fragment = `${p}${s}s${btoa(notebook.scheme)}`; + return notebook.with({ scheme, fragment }); } export function parse(cell: URI): { notebook: URI, handle: number; } | undefined { if (cell.scheme !== scheme) { return undefined; } - const match = _regex.exec(cell.fragment); - if (!match) { + + const idx = cell.fragment.indexOf('s'); + if (idx < 0) { + return undefined; + } + + const handle = parseInt(cell.fragment.substring(0, idx).replace(_padRegexp, ''), _radix); + const _scheme = atob(cell.fragment.substring(idx + 1)); + + if (isNaN(handle)) { return undefined; } - const handle = Number(match[1]); return { handle, - notebook: cell.with({ - scheme: cell.fragment.substring(match[0].length) || Schemas.file, - fragment: null - }) + notebook: cell.with({ scheme: _scheme, fragment: null }) }; } + + const _regex = /^(\d{8,})(\w[\w\d+.-]*)$/; + export function generateCellOutputUri(notebook: URI, handle: number, outputId?: string) { return notebook.with({ scheme: Schemas.vscodeNotebookCellOutput, diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts index 05875d1a2fd..3394ddfe084 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts @@ -316,6 +316,21 @@ suite('CellUri', function () { assert.strictEqual(actual?.handle, id); assert.strictEqual(actual?.notebook.toString(), nb.toString()); }); + + test('stable order', function () { + + const nb = URI.parse('foo:///bar/følder/file.nb'); + const handles = [1, 2, 9, 10, 88, 100, 666666, 7777777]; + + const uris = handles.map(h => CellUri.generate(nb, h)).sort(); + + const strUris = uris.map(String).sort(); + const parsedUris = strUris.map(s => URI.parse(s)); + + const actual = parsedUris.map(u => CellUri.parse(u)?.handle); + + assert.deepStrictEqual(actual, handles); + }); }); From 6b83265753c277b75b0fb7210e97cf50b5811eb1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 28 Jan 2022 13:54:29 +0100 Subject: [PATCH 002/197] dont use atob and btoa util --- src/vs/workbench/contrib/notebook/common/notebookCommon.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index e1c82a83170..cae7eb5c821 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { VSBuffer } from 'vs/base/common/buffer'; +import { decodeBase64, encodeBase64, VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IDiffResult, ISequence } from 'vs/base/common/diff/diff'; import { Event } from 'vs/base/common/event'; @@ -518,7 +518,7 @@ export namespace CellUri { const s = handle.toString(_radix); const p = s.length < _lengths.length ? _lengths[s.length - 1] : 'z'; - const fragment = `${p}${s}s${btoa(notebook.scheme)}`; + const fragment = `${p}${s}s${encodeBase64(VSBuffer.fromString(notebook.scheme), true, true)}`; return notebook.with({ scheme, fragment }); } @@ -533,7 +533,7 @@ export namespace CellUri { } const handle = parseInt(cell.fragment.substring(0, idx).replace(_padRegexp, ''), _radix); - const _scheme = atob(cell.fragment.substring(idx + 1)); + const _scheme = decodeBase64(cell.fragment.substring(idx + 1)).toString(); if (isNaN(handle)) { return undefined; From d8127336ad8d440e6eaf12c84ecdad38c0b8b7f7 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 28 Jan 2022 14:12:04 +0100 Subject: [PATCH 003/197] tweak existing test to its title --- .../contrib/notebook/test/browser/notebookCommon.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts index 3394ddfe084..2a1148335b5 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts @@ -295,7 +295,7 @@ suite('CellUri', function () { test('parse, generate (file-scheme)', function () { - const nb = URI.parse('foo:///bar/følder/file.nb'); + const nb = URI.parse('file:///bar/følder/file.nb'); const id = 17; const data = CellUri.generate(nb, id); From d0f88caadf78ac1a505b32f9d3438ee1b403844c Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Tue, 28 Jun 2022 09:49:28 -0700 Subject: [PATCH 004/197] added a widget on click, working on functionality --- .../codeAction/browser/codeActionMenu.ts | 236 +++++++++++++++++- 1 file changed, 234 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 5536c1c1a45..a2d60793ae8 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -3,13 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// import * as dom from 'vs/base/browser/dom'; import { getDomNodePagePosition } from 'vs/base/browser/dom'; import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; +import { IListEvent, IListRenderer } from 'vs/base/browser/ui/list/list'; +import { List } from 'vs/base/browser/ui/list/listWidget'; import { Action, IAction, Separator } from 'vs/base/common/actions'; import { canceled } from 'vs/base/common/errors'; +import { Emitter, Event } from 'vs/base/common/event'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { Lazy } from 'vs/base/common/lazy'; -import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, dispose, MutableDisposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IPosition, Position } from 'vs/editor/common/core/position'; @@ -17,11 +21,15 @@ import { ScrollType } from 'vs/editor/common/editorCommon'; import { CodeAction, Command } from 'vs/editor/common/languages'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { codeActionCommandId, CodeActionItem, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/browser/codeAction'; +import { CodeActionModel } from 'vs/editor/contrib/codeAction/browser/codeActionModel'; import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionKind, CodeActionTrigger, CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/browser/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'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { attachListStyler } from 'vs/platform/theme/common/styler'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +// import { Emitter } from 'vs/base/common/event'; interface CodeActionWidgetDelegate { onSelectCodeAction: (action: CodeActionItem, trigger: CodeActionTrigger) => Promise; @@ -50,13 +58,108 @@ export interface CodeActionShowOptions { readonly includeDisabledActions: boolean; readonly fromLightbulb?: boolean; } +export interface ICodeActionMenuItem { + title: string; + detail?: string; + decoratorRight?: string; + isDisabled?: boolean; +} + +export interface ICodeMenuOptions { + useCustomDrawn?: boolean; + ariaLabel?: string; + ariaDescription?: string; + minBottomMargin?: number; + optionsAsChildren?: boolean; +} + +export interface ICodeActionMenuTemplateData { + root: HTMLElement; + text: HTMLElement; + detail: HTMLElement; + decoratorRight: HTMLElement; + disposables: IDisposable[]; +} + +// export interface ICodeMenuData { +// selected: string; +// index: number; +// } + +const TEMPLATE_ID = 'test'; +class CodeMenuRenderer implements IListRenderer { + get templateId(): string { return TEMPLATE_ID; } + + renderTemplate(container: HTMLElement): ICodeActionMenuTemplateData { + const data: ICodeActionMenuTemplateData = Object.create(null); + data.disposables = []; + data.root = container; + data.text = document.createElement('span'); + container.append(data.text); + + // data.text = dom.append(container, $('.option-text')); + // data.detail = dom.append(container, $('.option-detail')); + // data.decoratorRight = dom.append(container, $('.option-decorator-right')); + + return data; + } + renderElement(element: ICodeActionMenuItem, index: number, templateData: ICodeActionMenuTemplateData): void { + const data: ICodeActionMenuTemplateData = templateData; + + const text = element.title; + + const isDisabled = element.isDisabled; + + data.text.textContent = text; + data.detail.textContent = ''; + data.decoratorRight.innerText = ''; + + if (isDisabled) { + data.root.classList.add('option-disabled'); + + } else { + data.root.classList.remove('option-disabled'); + } + + } + disposeTemplate(templateData: ICodeActionMenuTemplateData): void { + templateData.disposables = dispose(templateData.disposables); + } + +} + + +export class CodeActionWidget extends List { + +} + +interface ISelectedCodeAction { + action: CodeAction; + index: number; + model: CodeActionModel; +} + export class CodeActionMenu extends Disposable { + private codeActionList!: List; + private options: ICodeActionMenuItem[] = []; private _visible: boolean = false; private readonly _showingActions = this._register(new MutableDisposable()); + private readonly _disposables = new DisposableStore(); + private readonly _onDidSelect = new Emitter(); + private parent: HTMLElement; + private listTrigger!: CodeActionTrigger; + private selected!: CodeActionItem; + // private showActions: CodeActionItem[]; + + readonly onDidSelect: Event = this._onDidSelect.event; + private readonly _keybindingResolver: CodeActionKeybindingResolver; + listRenderer: any; + // selected: any; + // _isVisible: any; constructor( private readonly _editor: ICodeEditor, @@ -64,19 +167,136 @@ export class CodeActionMenu extends Disposable { @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IKeybindingService keybindingService: IKeybindingService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, - @ITelemetryService private readonly _telemetryService: ITelemetryService + @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IThemeService _themeService: IThemeService, ) { super(); this._keybindingResolver = new CodeActionKeybindingResolver({ getKeybindings: () => keybindingService.getKeybindings() }); + + + // this._onDidSelect = new Emitter(); + // this._register(this._onDidSelect); + + // this.registerListeners(); + + + // this.selected = 0; + + // const codeOption = { text: 'test', detail: 'test detail' }; + // const codeOption2 = { text: 'test2', detail: 'test2 detail' }; + // const codeOption3 = { text: 'test3', detail: 'test3 detail' }; + // const codeOption4 = { text: 'test4', detail: 'test4 detail' }; + // const codeOption5 = { text: 'test5', detail: 'test5 detail' }; + // const codeOption6 = { text: 'test6', detail: 'test6 detail' }; + // this.options = [codeOption, codeOption2, codeOption3, codeOption4, codeOption5, codeOption6]; + + // if (this.options) { + // this.setOptions(this.options, this.selected); + // } + + if (this.codeActionList) { + const temp = this.codeActionList.getSelection(); + console.log(temp); + } + + + + this.parent = document.createElement('div'); + this.parent.style.backgroundColor = 'none'; + this.parent.style.border = '3px solid red'; + this.parent.style.width = '300px'; + this.parent.style.height = '500px'; + this.parent.id = 'testRedSquare'; + this.parent.style.position = 'absolute'; + this.parent.style.top = '0'; + this.listRenderer = new CodeMenuRenderer(); + + this.codeActionList = new List('test', this.parent, { + getHeight(element) { + return 20; + }, + getTemplateId(element) { + return 'test'; + } + }, [this.listRenderer], + + + + //new class, + new instance, id of rendere match id of getTemplateID + //renderTemplate, renderElement + ); + + + if (this.codeActionList) { + this._disposables.add(this.codeActionList.onDidChangeSelection(e => this._onListSelection(e))); + } + + } get isVisible(): boolean { return this._visible; } + private _onListSelection(e: IListEvent): void { + if (e.elements.length) { + const toCodeActionAction = (item: CodeActionItem): CodeActionAction => new CodeActionAction(item.action, () => this._delegate.onSelectCodeAction(item, this.listTrigger)); + // this._select(e.elements[0], e.indexes[0]); + } + } + + // private _select(action: CodeAction, index: number): void { + // const completionModel = this._completionModel; + // if (completionModel) { + // this._onDidSelect.fire({ action, index, model: completionModel }); + // // this.editor.focus(); + // } + // } + + + private setCodeActionMenuList() { + this.codeActionList?.splice(0, this.codeActionList.length, this.options); + } + + private createOption(value: string, index: number, disabled?: boolean): HTMLOptionElement { + const option = document.createElement('option'); + option.value = value; + option.text = value; + option.disabled = !!disabled; + + return option; + } + + private createCodeActionMenuList(element: HTMLElement): void { + // if (this.codeActionList) { + // return; + // } + + const codeOption = { title: 'Extract to function in global scope', detail: 'test detail' }; + const codeOption2 = { title: 'test2', detail: 'test2 detail' }; + const codeOption3 = { title: 'test3', detail: 'test3 detail' }; + const codeOption4 = { title: 'test4', detail: 'test4 detail' }; + const codeOption5 = { title: 'test5', detail: 'test5 detail' }; + const codeOption6 = { title: 'test6', detail: 'test6 detail' }; + this.options = [codeOption, codeOption2, codeOption3, codeOption4, codeOption5, codeOption6]; + + // const paragraph = document.createTextNode('new paragraph and some more text'); + // divElement.appendChild(paragraph); + // this.selectElement = document.createElement('select'); + // this.selectElement.add(this.createOption('testestestest', 0, false)); + // this.selectElement.add(this.createOption('test2', 0, false)); + // divElement.appendChild(this.selectElement); + + // const listContainer = document.createElement('div'); + // divElement.appendChild(listContainer); + + this._editor.getDomNode()?.append(this.parent); + } + + public async show(trigger: CodeActionTrigger, codeActions: CodeActionSet, at: IAnchor | IPosition, options: CodeActionShowOptions): Promise { const actionsToShow = options.includeDisabledActions ? codeActions.allActions : codeActions.validActions; if (!actionsToShow.length) { @@ -84,6 +304,10 @@ export class CodeActionMenu extends Disposable { return; } + // this.showActions = actionsToShow; + + //Some helper that will make a call to this.getMenuActions() + if (!this._editor.getDomNode()) { // cancel when editor went off-dom this._visible = false; @@ -93,6 +317,14 @@ export class CodeActionMenu extends Disposable { this._visible = true; this._showingActions.value = codeActions; + + this.listTrigger = trigger; + this.createCodeActionMenuList(this.parent); + this.setCodeActionMenuList(); + + + + const menuActions = this.getMenuActions(trigger, actionsToShow, codeActions.documentation); const anchor = Position.isIPosition(at) ? this._toCoords(at) : at || { x: 0, y: 0 }; From 94d7de963a5f562df85345d8382173caeca71a7c Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Tue, 28 Jun 2022 16:14:20 -0700 Subject: [PATCH 005/197] addedn select parses out code action, need to convert into actual runnable item --- .../codeAction/browser/codeActionMenu.ts | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index a2d60793ae8..5a17ba651f4 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -61,6 +61,7 @@ export interface CodeActionShowOptions { export interface ICodeActionMenuItem { title: string; detail?: string; + action?: CodeActionAction; decoratorRight?: string; isDisabled?: boolean; } @@ -151,6 +152,7 @@ export class CodeActionMenu extends Disposable { private parent: HTMLElement; private listTrigger!: CodeActionTrigger; private selected!: CodeActionItem; + private menuAction: IAction[] = []; // private showActions: CodeActionItem[]; readonly onDidSelect: Event = this._onDidSelect.event; @@ -197,12 +199,6 @@ export class CodeActionMenu extends Disposable { // this.setOptions(this.options, this.selected); // } - if (this.codeActionList) { - const temp = this.codeActionList.getSelection(); - console.log(temp); - } - - this.parent = document.createElement('div'); this.parent.style.backgroundColor = 'none'; @@ -212,6 +208,7 @@ export class CodeActionMenu extends Disposable { this.parent.id = 'testRedSquare'; this.parent.style.position = 'absolute'; this.parent.style.top = '0'; + this.listRenderer = new CodeMenuRenderer(); this.codeActionList = new List('test', this.parent, { @@ -243,6 +240,11 @@ export class CodeActionMenu extends Disposable { private _onListSelection(e: IListEvent): void { if (e.elements.length) { + e.elements.forEach(element => { + const itemAction = element.action.action; + console.log(itemAction); + }); + const toCodeActionAction = (item: CodeActionItem): CodeActionAction => new CodeActionAction(item.action, () => this._delegate.onSelectCodeAction(item, this.listTrigger)); // this._select(e.elements[0], e.indexes[0]); } @@ -270,18 +272,22 @@ export class CodeActionMenu extends Disposable { return option; } - private createCodeActionMenuList(element: HTMLElement): void { + private createCodeActionMenuList(element: HTMLElement, inputArray: IAction[]): void { // if (this.codeActionList) { // return; // } - const codeOption = { title: 'Extract to function in global scope', detail: 'test detail' }; - const codeOption2 = { title: 'test2', detail: 'test2 detail' }; - const codeOption3 = { title: 'test3', detail: 'test3 detail' }; - const codeOption4 = { title: 'test4', detail: 'test4 detail' }; - const codeOption5 = { title: 'test5', detail: 'test5 detail' }; - const codeOption6 = { title: 'test6', detail: 'test6 detail' }; - this.options = [codeOption, codeOption2, codeOption3, codeOption4, codeOption5, codeOption6]; + inputArray.forEach((item, index) => { + this.options.push({ title: item.label, detail: item.tooltip, action: item }); + }); + + // const codeOption = { title: 'Extract to function in global scope', detail: 'test detail' }; + // const codeOption2 = { title: 'test2', detail: 'test2 detail' }; + // const codeOption3 = { title: 'test3', detail: 'test3 detail' }; + // const codeOption4 = { title: 'test4', detail: 'test4 detail' }; + // const codeOption5 = { title: 'test5', detail: 'test5 detail' }; + // const codeOption6 = { title: 'test6', detail: 'test6 detail' }; + // this.options = [codeOption, codeOption2, codeOption3, codeOption4, codeOption5, codeOption6]; // const paragraph = document.createTextNode('new paragraph and some more text'); // divElement.appendChild(paragraph); @@ -319,19 +325,22 @@ export class CodeActionMenu extends Disposable { this.listTrigger = trigger; - this.createCodeActionMenuList(this.parent); - this.setCodeActionMenuList(); - - + // cycle through menuActions and build menu options from there const menuActions = this.getMenuActions(trigger, actionsToShow, codeActions.documentation); + this.menuAction = menuActions; + + const anchor = Position.isIPosition(at) ? this._toCoords(at) : at || { x: 0, y: 0 }; const resolver = this._keybindingResolver.getResolver(); const useShadowDOM = this._editor.getOption(EditorOption.useShadowDOM); + this.createCodeActionMenuList(this.parent, this.menuAction); + this.setCodeActionMenuList(); + this._contextMenuService.showContextMenu({ domForShadowRoot: useShadowDOM ? this._editor.getDomNode()! : undefined, getAnchor: () => anchor, From 5bafd28ff8c4f77dac0612f25a37148f97eb232a Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Thu, 30 Jun 2022 12:13:28 -0700 Subject: [PATCH 006/197] working menu item clicks --- .../codeAction/browser/codeActionMenu.ts | 53 ++++++------------- 1 file changed, 15 insertions(+), 38 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 5a17ba651f4..24ac1f1cf47 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -61,7 +61,7 @@ export interface CodeActionShowOptions { export interface ICodeActionMenuItem { title: string; detail?: string; - action?: CodeActionAction; + action: IAction; decoratorRight?: string; isDisabled?: boolean; } @@ -135,7 +135,7 @@ export class CodeActionWidget extends List { } interface ISelectedCodeAction { - action: CodeAction; + action: CodeActionAction; index: number; model: CodeActionModel; } @@ -153,6 +153,7 @@ export class CodeActionMenu extends Disposable { private listTrigger!: CodeActionTrigger; private selected!: CodeActionItem; private menuAction: IAction[] = []; + private loc: any; // private showActions: CodeActionItem[]; readonly onDidSelect: Event = this._onDidSelect.event; @@ -199,7 +200,6 @@ export class CodeActionMenu extends Disposable { // this.setOptions(this.options, this.selected); // } - this.parent = document.createElement('div'); this.parent.style.backgroundColor = 'none'; this.parent.style.border = '3px solid red'; @@ -209,6 +209,7 @@ export class CodeActionMenu extends Disposable { this.parent.style.position = 'absolute'; this.parent.style.top = '0'; + this.listRenderer = new CodeMenuRenderer(); this.codeActionList = new List('test', this.parent, { @@ -220,8 +221,6 @@ export class CodeActionMenu extends Disposable { } }, [this.listRenderer], - - //new class, + new instance, id of rendere match id of getTemplateID //renderTemplate, renderElement ); @@ -238,26 +237,19 @@ export class CodeActionMenu extends Disposable { return this._visible; } - private _onListSelection(e: IListEvent): void { + private _onListSelection(e: IListEvent): void { if (e.elements.length) { e.elements.forEach(element => { - const itemAction = element.action.action; + const itemAction = element; console.log(itemAction); + element.action.run(); + // const toCodeActionAction = (item: CodeActionItem): CodeActionAction => new CodeActionAction(itemAction, () => this._delegate.onSelectCodeAction(item, this.listTrigger)); + // console.log(toCodeActionAction); }); - const toCodeActionAction = (item: CodeActionItem): CodeActionAction => new CodeActionAction(item.action, () => this._delegate.onSelectCodeAction(item, this.listTrigger)); - // this._select(e.elements[0], e.indexes[0]); } } - // private _select(action: CodeAction, index: number): void { - // const completionModel = this._completionModel; - // if (completionModel) { - // this._onDidSelect.fire({ action, index, model: completionModel }); - // // this.editor.focus(); - // } - // } - private setCodeActionMenuList() { this.codeActionList?.splice(0, this.codeActionList.length, this.options); @@ -272,33 +264,15 @@ export class CodeActionMenu extends Disposable { return option; } - private createCodeActionMenuList(element: HTMLElement, inputArray: IAction[]): void { + private createCodeActionMenuList(element: HTMLElement, inputArray: IAction[], anchor: any): void { // if (this.codeActionList) { // return; // } inputArray.forEach((item, index) => { - this.options.push({ title: item.label, detail: item.tooltip, action: item }); + this.options.push({ title: item.label, detail: item.tooltip, action: inputArray[index] }); }); - // const codeOption = { title: 'Extract to function in global scope', detail: 'test detail' }; - // const codeOption2 = { title: 'test2', detail: 'test2 detail' }; - // const codeOption3 = { title: 'test3', detail: 'test3 detail' }; - // const codeOption4 = { title: 'test4', detail: 'test4 detail' }; - // const codeOption5 = { title: 'test5', detail: 'test5 detail' }; - // const codeOption6 = { title: 'test6', detail: 'test6 detail' }; - // this.options = [codeOption, codeOption2, codeOption3, codeOption4, codeOption5, codeOption6]; - - // const paragraph = document.createTextNode('new paragraph and some more text'); - // divElement.appendChild(paragraph); - // this.selectElement = document.createElement('select'); - // this.selectElement.add(this.createOption('testestestest', 0, false)); - // this.selectElement.add(this.createOption('test2', 0, false)); - // divElement.appendChild(this.selectElement); - - // const listContainer = document.createElement('div'); - // divElement.appendChild(listContainer); - this._editor.getDomNode()?.append(this.parent); } @@ -334,11 +308,14 @@ export class CodeActionMenu extends Disposable { const anchor = Position.isIPosition(at) ? this._toCoords(at) : at || { x: 0, y: 0 }; + this.loc = anchor; + + const resolver = this._keybindingResolver.getResolver(); const useShadowDOM = this._editor.getOption(EditorOption.useShadowDOM); - this.createCodeActionMenuList(this.parent, this.menuAction); + this.createCodeActionMenuList(this.parent, this.menuAction, anchor); this.setCodeActionMenuList(); this._contextMenuService.showContextMenu({ From d329ac9dec423c5d41f38721aec1f7cae11ba0df Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Thu, 30 Jun 2022 15:57:18 -0700 Subject: [PATCH 007/197] working dispoable menu --- .../codeAction/browser/codeActionMenu.ts | 169 ++++++++++-------- 1 file changed, 96 insertions(+), 73 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 24ac1f1cf47..aff8cd9fc5a 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -14,7 +14,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, dispose, MutableDisposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { ScrollType } from 'vs/editor/common/editorCommon'; @@ -130,9 +130,6 @@ class CodeMenuRenderer implements IListRenderer extends List { - -} interface ISelectedCodeAction { action: CodeActionAction; @@ -141,7 +138,7 @@ interface ISelectedCodeAction { } -export class CodeActionMenu extends Disposable { +export class CodeActionMenu extends Disposable implements IContentWidget { private codeActionList!: List; private options: ICodeActionMenuItem[] = []; @@ -149,7 +146,7 @@ export class CodeActionMenu extends Disposable { private readonly _showingActions = this._register(new MutableDisposable()); private readonly _disposables = new DisposableStore(); private readonly _onDidSelect = new Emitter(); - private parent: HTMLElement; + private parent!: HTMLElement; private listTrigger!: CodeActionTrigger; private selected!: CodeActionItem; private menuAction: IAction[] = []; @@ -200,8 +197,71 @@ export class CodeActionMenu extends Disposable { // this.setOptions(this.options, this.selected); // } + } + allowEditorOverflow?: boolean | undefined; + suppressMouseDown?: boolean | undefined; + + getId(): string { + throw new Error('Method not implemented.'); + } + getDomNode(): HTMLElement { + throw new Error('Method not implemented.'); + } + getPosition(): IContentWidgetPosition | null { + throw new Error('Method not implemented.'); + } + + get isVisible(): boolean { + return this._visible; + } + + private _onListSelection(e: IListEvent): void { + if (e.elements.length) { + e.elements.forEach(element => { + const itemAction = element; + console.log(itemAction); + element.action.run(); + // const toCodeActionAction = (item: CodeActionItem): CodeActionAction => new CodeActionAction(itemAction, () => this._delegate.onSelectCodeAction(item, this.listTrigger)); + // console.log(toCodeActionAction); + }); + // this.codeActionList.dispose(); + // this._editor.removeContentWidget(this); + this._editor.getDomNode()?.removeChild(this.parent); + this.codeActionList.dispose(); + this.menuAction = []; + this.options = []; + + // this.parent.dispose(); + } + } + + + private setCodeActionMenuList() { + this.codeActionList?.splice(0, this.codeActionList.length, this.options); + } + + private createOption(value: string, index: number, disabled?: boolean): HTMLOptionElement { + const option = document.createElement('option'); + option.value = value; + option.text = value; + option.disabled = !!disabled; + + return option; + } + + // override dispose(): void { + // this.codeActionList.dispose(); + // this._disposables.dispose(); + // } + + + private createCodeActionMenuList(element: HTMLElement, inputArray: IAction[], anchor: any): void { + // if (this.codeActionList) { + // return; + // } + this.parent = document.createElement('div'); - this.parent.style.backgroundColor = 'none'; + this.parent.style.backgroundColor = 'red'; this.parent.style.border = '3px solid red'; this.parent.style.width = '300px'; this.parent.style.height = '500px'; @@ -231,43 +291,6 @@ export class CodeActionMenu extends Disposable { } - } - - get isVisible(): boolean { - return this._visible; - } - - private _onListSelection(e: IListEvent): void { - if (e.elements.length) { - e.elements.forEach(element => { - const itemAction = element; - console.log(itemAction); - element.action.run(); - // const toCodeActionAction = (item: CodeActionItem): CodeActionAction => new CodeActionAction(itemAction, () => this._delegate.onSelectCodeAction(item, this.listTrigger)); - // console.log(toCodeActionAction); - }); - - } - } - - - private setCodeActionMenuList() { - this.codeActionList?.splice(0, this.codeActionList.length, this.options); - } - - private createOption(value: string, index: number, disabled?: boolean): HTMLOptionElement { - const option = document.createElement('option'); - option.value = value; - option.text = value; - option.disabled = !!disabled; - - return option; - } - - private createCodeActionMenuList(element: HTMLElement, inputArray: IAction[], anchor: any): void { - // if (this.codeActionList) { - // return; - // } inputArray.forEach((item, index) => { this.options.push({ title: item.label, detail: item.tooltip, action: inputArray[index] }); @@ -318,40 +341,40 @@ export class CodeActionMenu extends Disposable { this.createCodeActionMenuList(this.parent, this.menuAction, anchor); this.setCodeActionMenuList(); - this._contextMenuService.showContextMenu({ - domForShadowRoot: useShadowDOM ? this._editor.getDomNode()! : undefined, - getAnchor: () => anchor, - getActions: () => menuActions, - onHide: (didCancel) => { - const openedFromString = (options.fromLightbulb) ? CodeActionTriggerSource.Lightbulb : trigger.triggerAction; + // this._contextMenuService.showContextMenu({ + // domForShadowRoot: useShadowDOM ? this._editor.getDomNode()! : undefined, + // getAnchor: () => anchor, + // getActions: () => menuActions, + // onHide: (didCancel) => { + // const openedFromString = (options.fromLightbulb) ? CodeActionTriggerSource.Lightbulb : trigger.triggerAction; - type ApplyCodeActionEvent = { - codeActionFrom: CodeActionTriggerSource; - validCodeActions: number; - cancelled: boolean; - }; + // type ApplyCodeActionEvent = { + // codeActionFrom: CodeActionTriggerSource; + // validCodeActions: number; + // cancelled: boolean; + // }; - type ApplyCodeEventClassification = { - codeActionFrom: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of action used to opened the code action.' }; - validCodeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The total number of valid actions that are highlighted and can be used.' }; - cancelled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The indicator if the menu was selected or cancelled.' }; - owner: 'mjbvz'; - comment: 'Event used to gain insights into how code actions are being triggered'; - }; + // type ApplyCodeEventClassification = { + // codeActionFrom: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of action used to opened the code action.' }; + // validCodeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The total number of valid actions that are highlighted and can be used.' }; + // cancelled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The indicator if the menu was selected or cancelled.' }; + // owner: 'mjbvz'; + // comment: 'Event used to gain insights into how code actions are being triggered'; + // }; - this._telemetryService.publicLog2('codeAction.applyCodeAction', { - codeActionFrom: openedFromString, - validCodeActions: codeActions.validActions.length, - cancelled: didCancel, + // this._telemetryService.publicLog2('codeAction.applyCodeAction', { + // codeActionFrom: openedFromString, + // validCodeActions: codeActions.validActions.length, + // cancelled: didCancel, - }); + // }); - this._visible = false; - this._editor.focus(); - }, - autoSelectFirstItem: true, - getKeyBinding: action => action instanceof CodeActionAction ? resolver(action.action) : undefined, - }); + // this._visible = false; + // this._editor.focus(); + // }, + // autoSelectFirstItem: true, + // getKeyBinding: action => action instanceof CodeActionAction ? resolver(action.action) : undefined, + // }); } private getMenuActions( From 6d099df0f186033a4bbc6e621340997abf928dec Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Wed, 6 Jul 2022 13:02:07 -0700 Subject: [PATCH 008/197] working with kinda styled menu --- .../codeAction/browser/codeActionMenu.ts | 82 +++++++++---------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index aff8cd9fc5a..a396a424647 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -31,6 +31,8 @@ import { attachListStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; // import { Emitter } from 'vs/base/common/event'; +// const $ = dom.$; + interface CodeActionWidgetDelegate { onSelectCodeAction: (action: CodeActionItem, trigger: CodeActionTrigger) => Promise; } @@ -60,7 +62,7 @@ export interface CodeActionShowOptions { } export interface ICodeActionMenuItem { title: string; - detail?: string; + detail: string; action: IAction; decoratorRight?: string; isDisabled?: boolean; @@ -96,11 +98,14 @@ class CodeMenuRenderer implements IListRenderer()); private readonly _disposables = new DisposableStore(); private readonly _onDidSelect = new Emitter(); + private readonly _onDidHideContextMenu = new Emitter(); + readonly onDidHideContextMenu = this._onDidHideContextMenu.event; private parent!: HTMLElement; private listTrigger!: CodeActionTrigger; private selected!: CodeActionItem; @@ -176,27 +182,6 @@ export class CodeActionMenu extends Disposable implements IContentWidget { getKeybindings: () => keybindingService.getKeybindings() }); - - // this._onDidSelect = new Emitter(); - // this._register(this._onDidSelect); - - // this.registerListeners(); - - - // this.selected = 0; - - // const codeOption = { text: 'test', detail: 'test detail' }; - // const codeOption2 = { text: 'test2', detail: 'test2 detail' }; - // const codeOption3 = { text: 'test3', detail: 'test3 detail' }; - // const codeOption4 = { text: 'test4', detail: 'test4 detail' }; - // const codeOption5 = { text: 'test5', detail: 'test5 detail' }; - // const codeOption6 = { text: 'test6', detail: 'test6 detail' }; - // this.options = [codeOption, codeOption2, codeOption3, codeOption4, codeOption5, codeOption6]; - - // if (this.options) { - // this.setOptions(this.options, this.selected); - // } - } allowEditorOverflow?: boolean | undefined; suppressMouseDown?: boolean | undefined; @@ -249,25 +234,24 @@ export class CodeActionMenu extends Disposable implements IContentWidget { return option; } - // override dispose(): void { - // this.codeActionList.dispose(); - // this._disposables.dispose(); - // } - - private createCodeActionMenuList(element: HTMLElement, inputArray: IAction[], anchor: any): void { // if (this.codeActionList) { // return; // } this.parent = document.createElement('div'); - this.parent.style.backgroundColor = 'red'; - this.parent.style.border = '3px solid red'; - this.parent.style.width = '300px'; - this.parent.style.height = '500px'; + this.parent.style.backgroundColor = 'rgb(48, 48, 49)'; + this.parent.style.border = '1px black'; + this.parent.style.borderRadius = '5px'; + this.parent.style.color = 'rgb(204, 204, 204)'; + this.parent.style.boxShadow = 'rgb(0,0,0,0.36) 0px 2px 8px'; + this.parent.style.width = '350px'; + this.parent.style.height = '200px'; this.parent.id = 'testRedSquare'; - this.parent.style.position = 'absolute'; - this.parent.style.top = '0'; + this.parent.style.position = 'fixed'; + this.parent.style.left = this.loc.x + 'px'; + this.parent.style.top = this.loc.y + 'px'; + this.listRenderer = new CodeMenuRenderer(); @@ -293,6 +277,7 @@ export class CodeActionMenu extends Disposable implements IContentWidget { inputArray.forEach((item, index) => { + // const tooltip = item.tooltip ? item.tooltip : ''; this.options.push({ title: item.label, detail: item.tooltip, action: inputArray[index] }); }); @@ -323,7 +308,6 @@ export class CodeActionMenu extends Disposable implements IContentWidget { this.listTrigger = trigger; - // cycle through menuActions and build menu options from there const menuActions = this.getMenuActions(trigger, actionsToShow, codeActions.documentation); @@ -377,6 +361,22 @@ export class CodeActionMenu extends Disposable implements IContentWidget { // }); } + /** + * + * Comments about menu: + * + * flyout might be too big, not used anywhere else + * + * making the editor editable + * + * better view in the refactor preview pane + * + * should we be showing all the refactor options? should we only show options that are valid, like in the + * lightbulb action + * + * + */ + private getMenuActions( trigger: CodeActionTrigger, actionsToShow: readonly CodeActionItem[], From 473ee7735e85978c094060b354a254203955c53e Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Wed, 6 Jul 2022 13:58:03 -0700 Subject: [PATCH 009/197] fixed bad scroll error --- .../codeAction/browser/codeActionMenu.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index a396a424647..1e967748732 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -33,6 +33,19 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; // const $ = dom.$; + +/** + * Was work from home or work from cafe yesterday + * Some widget working stuff - demo rq, ask about some list widget stuff + * Weekend update - gasworks, climbing for 6 hours, + * got microsoft merch that i ordered from the company store stolen, and UPS couldnt find it, + * so also if anyone knows if i can get the order reordered or who i can go annoy about this lmk + * + * + * + */ + + interface CodeActionWidgetDelegate { onSelectCodeAction: (action: CodeActionItem, trigger: CodeActionTrigger) => Promise; } @@ -118,8 +131,8 @@ class CodeMenuRenderer implements IListRenderer Date: Wed, 6 Jul 2022 14:21:22 -0700 Subject: [PATCH 010/197] removed comments --- .../codeAction/browser/codeActionMenu.ts | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 1e967748732..20798857a40 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -271,7 +271,7 @@ export class CodeActionMenu extends Disposable implements IContentWidget { this.codeActionList = new List('test', this.parent, { getHeight(element) { - return 20; + return 23; }, getTemplateId(element) { return 'test'; @@ -339,40 +339,40 @@ export class CodeActionMenu extends Disposable implements IContentWidget { this.setCodeActionMenuList(); this.codeActionList.layout(180); - // this._contextMenuService.showContextMenu({ - // domForShadowRoot: useShadowDOM ? this._editor.getDomNode()! : undefined, - // getAnchor: () => anchor, - // getActions: () => menuActions, - // onHide: (didCancel) => { - // const openedFromString = (options.fromLightbulb) ? CodeActionTriggerSource.Lightbulb : trigger.triggerAction; + this._contextMenuService.showContextMenu({ + domForShadowRoot: useShadowDOM ? this._editor.getDomNode()! : undefined, + getAnchor: () => anchor, + getActions: () => menuActions, + onHide: (didCancel) => { + const openedFromString = (options.fromLightbulb) ? CodeActionTriggerSource.Lightbulb : trigger.triggerAction; - // type ApplyCodeActionEvent = { - // codeActionFrom: CodeActionTriggerSource; - // validCodeActions: number; - // cancelled: boolean; - // }; + type ApplyCodeActionEvent = { + codeActionFrom: CodeActionTriggerSource; + validCodeActions: number; + cancelled: boolean; + }; - // type ApplyCodeEventClassification = { - // codeActionFrom: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of action used to opened the code action.' }; - // validCodeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The total number of valid actions that are highlighted and can be used.' }; - // cancelled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The indicator if the menu was selected or cancelled.' }; - // owner: 'mjbvz'; - // comment: 'Event used to gain insights into how code actions are being triggered'; - // }; + type ApplyCodeEventClassification = { + codeActionFrom: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of action used to opened the code action.' }; + validCodeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The total number of valid actions that are highlighted and can be used.' }; + cancelled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The indicator if the menu was selected or cancelled.' }; + owner: 'mjbvz'; + comment: 'Event used to gain insights into how code actions are being triggered'; + }; - // this._telemetryService.publicLog2('codeAction.applyCodeAction', { - // codeActionFrom: openedFromString, - // validCodeActions: codeActions.validActions.length, - // cancelled: didCancel, + this._telemetryService.publicLog2('codeAction.applyCodeAction', { + codeActionFrom: openedFromString, + validCodeActions: codeActions.validActions.length, + cancelled: didCancel, - // }); + }); - // this._visible = false; - // this._editor.focus(); - // }, - // autoSelectFirstItem: true, - // getKeyBinding: action => action instanceof CodeActionAction ? resolver(action.action) : undefined, - // }); + this._visible = false; + this._editor.focus(); + }, + autoSelectFirstItem: true, + getKeyBinding: action => action instanceof CodeActionAction ? resolver(action.action) : undefined, + }); } /** From 401245cf1433dbe6dff0522bf692fff4bc9cc629 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Thu, 7 Jul 2022 14:22:59 -0700 Subject: [PATCH 011/197] modified to use the context view service wrapper --- .../codeAction/browser/codeActionMenu.ts | 161 ++++++++++-------- 1 file changed, 90 insertions(+), 71 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 20798857a40..6b97d66d4cb 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -5,7 +5,7 @@ // import * as dom from 'vs/base/browser/dom'; import { getDomNodePagePosition } from 'vs/base/browser/dom'; -import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; +import { IAnchor, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { IListEvent, IListRenderer } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { Action, IAction, Separator } from 'vs/base/common/actions'; @@ -23,7 +23,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat import { codeActionCommandId, CodeActionItem, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/browser/codeAction'; import { CodeActionModel } from 'vs/editor/contrib/codeAction/browser/codeActionModel'; import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionKind, CodeActionTrigger, CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/browser/types'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -34,18 +34,6 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; // const $ = dom.$; -/** - * Was work from home or work from cafe yesterday - * Some widget working stuff - demo rq, ask about some list widget stuff - * Weekend update - gasworks, climbing for 6 hours, - * got microsoft merch that i ordered from the company store stolen, and UPS couldnt find it, - * so also if anyone knows if i can get the order reordered or who i can go annoy about this lmk - * - * - * - */ - - interface CodeActionWidgetDelegate { onSelectCodeAction: (action: CodeActionItem, trigger: CodeActionTrigger) => Promise; } @@ -134,9 +122,11 @@ class CodeMenuRenderer implements IListRenderer = this._onDidSelect.event; + + private readonly _keybindingResolver: CodeActionKeybindingResolver; listRenderer: any; // selected: any; @@ -184,6 +176,7 @@ export class CodeActionMenu extends Disposable implements IContentWidget { private readonly _editor: ICodeEditor, private readonly _delegate: CodeActionWidgetDelegate, @IContextMenuService private readonly _contextMenuService: IContextMenuService, + @IContextViewService private readonly _contextViewService: IContextViewService, @IKeybindingService keybindingService: IKeybindingService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @@ -216,15 +209,17 @@ export class CodeActionMenu extends Disposable implements IContentWidget { private _onListSelection(e: IListEvent): void { if (e.elements.length) { e.elements.forEach(element => { - const itemAction = element; - console.log(itemAction); - element.action.run(); + if (element.isDisabled) { + const itemAction = element; + console.log(itemAction); + element.action.run(); + } // const toCodeActionAction = (item: CodeActionItem): CodeActionAction => new CodeActionAction(itemAction, () => this._delegate.onSelectCodeAction(item, this.listTrigger)); // console.log(toCodeActionAction); }); // this.codeActionList.dispose(); // this._editor.removeContentWidget(this); - this._editor.getDomNode()?.removeChild(this.parent); + // this._editor.getDomNode()?.removeChild(this.parent); this.codeActionList.dispose(); this.menuAction = []; this.options = []; @@ -247,29 +242,33 @@ export class CodeActionMenu extends Disposable implements IContentWidget { return option; } - private createCodeActionMenuList(element: HTMLElement, inputArray: IAction[], anchor: any): void { + private renderCodeActionMenuList(element: HTMLElement, inputArray: IAction[]): IDisposable { // if (this.codeActionList) { // return; // } - this.parent = document.createElement('div'); - this.parent.style.backgroundColor = 'rgb(48, 48, 49)'; - this.parent.style.border = '1px black'; - this.parent.style.borderRadius = '5px'; - this.parent.style.color = 'rgb(204, 204, 204)'; - this.parent.style.boxShadow = 'rgb(0,0,0,0.36) 0px 2px 8px'; - this.parent.style.width = '350px'; - this.parent.style.height = '200px'; - this.parent.id = 'testRedSquare'; - this.parent.style.position = 'fixed'; - this.parent.style.left = this.loc.x + 'px'; - this.parent.style.top = this.loc.y + 'px'; + const renderDisposables = new DisposableStore(); + + const renderMenu = document.createElement('div'); + renderMenu.style.backgroundColor = 'rgb(48, 48, 49)'; + renderMenu.style.border = '1px black'; + renderMenu.style.borderRadius = '5px'; + renderMenu.style.color = 'rgb(204, 204, 204)'; + renderMenu.style.boxShadow = 'rgb(0,0,0,0.36) 0px 2px 8px'; + renderMenu.style.width = '350px'; + renderMenu.style.height = '200px'; + renderMenu.id = 'testRedSquare'; + + element.appendChild(renderMenu); + // element.style.position = 'fixed'; + // element.style.left = this.loc.x + 'px'; + // element.style.top = this.loc.y + 'px'; this.listRenderer = new CodeMenuRenderer(); - this.codeActionList = new List('test', this.parent, { + this.codeActionList = new List('test', renderMenu, { getHeight(element) { return 23; }, @@ -284,17 +283,22 @@ export class CodeActionMenu extends Disposable implements IContentWidget { if (this.codeActionList) { - this._disposables.add(this.codeActionList.onDidChangeSelection(e => this._onListSelection(e))); + renderDisposables.add(this.codeActionList.onDidChangeSelection(e => this._onListSelection(e))); } inputArray.forEach((item, index) => { // const tooltip = item.tooltip ? item.tooltip : ''; - this.options.push({ title: item.label, detail: item.tooltip, action: inputArray[index] }); + this.options.push({ title: item.label, detail: item.tooltip, action: inputArray[index], isDisabled: item.enabled }); }); - this._editor.getDomNode()?.append(this.parent); + // this._editor.getDomNode()?.append(element); + + this.codeActionList?.splice(0, this.codeActionList.length, this.options); + this.codeActionList.layout(180); + return renderDisposables; + } @@ -324,55 +328,70 @@ export class CodeActionMenu extends Disposable implements IContentWidget { const menuActions = this.getMenuActions(trigger, actionsToShow, codeActions.documentation); - this.menuAction = menuActions; + // this.menuAction = menuActions; const anchor = Position.isIPosition(at) ? this._toCoords(at) : at || { x: 0, y: 0 }; - this.loc = anchor; + // this.loc = anchor; const resolver = this._keybindingResolver.getResolver(); const useShadowDOM = this._editor.getOption(EditorOption.useShadowDOM); - this.createCodeActionMenuList(this.parent, this.menuAction, anchor); - this.setCodeActionMenuList(); - this.codeActionList.layout(180); + // this.createCodeActionMenuList(this.parent, this.menuAction); - this._contextMenuService.showContextMenu({ - domForShadowRoot: useShadowDOM ? this._editor.getDomNode()! : undefined, + // this.codeActionList.layout(180); + + // this.parent = document.createElement('div'); + + this._contextViewService.showContextView({ getAnchor: () => anchor, - getActions: () => menuActions, + render: (container: HTMLElement) => this.renderCodeActionMenuList(container, menuActions), onHide: (didCancel) => { - const openedFromString = (options.fromLightbulb) ? CodeActionTriggerSource.Lightbulb : trigger.triggerAction; - - type ApplyCodeActionEvent = { - codeActionFrom: CodeActionTriggerSource; - validCodeActions: number; - cancelled: boolean; - }; - - type ApplyCodeEventClassification = { - codeActionFrom: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of action used to opened the code action.' }; - validCodeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The total number of valid actions that are highlighted and can be used.' }; - cancelled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The indicator if the menu was selected or cancelled.' }; - owner: 'mjbvz'; - comment: 'Event used to gain insights into how code actions are being triggered'; - }; - - this._telemetryService.publicLog2('codeAction.applyCodeAction', { - codeActionFrom: openedFromString, - validCodeActions: codeActions.validActions.length, - cancelled: didCancel, - - }); - this._visible = false; this._editor.focus(); + this._contextViewService.hideContextView(); }, - autoSelectFirstItem: true, - getKeyBinding: action => action instanceof CodeActionAction ? resolver(action.action) : undefined, - }); + }, + //this._editor.getDomNode(), if we use shadow dom ( + shadow dom param) + ); + + + // this._contextMenuService.showContextMenu({ + // domForShadowRoot: useShadowDOM ? this._editor.getDomNode()! : undefined, + // getAnchor: () => anchor, + // getActions: () => menuActions, + // onHide: (didCancel) => { + // const openedFromString = (options.fromLightbulb) ? CodeActionTriggerSource.Lightbulb : trigger.triggerAction; + + // type ApplyCodeActionEvent = { + // codeActionFrom: CodeActionTriggerSource; + // validCodeActions: number; + // cancelled: boolean; + // }; + + // type ApplyCodeEventClassification = { + // codeActionFrom: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of action used to opened the code action.' }; + // validCodeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The total number of valid actions that are highlighted and can be used.' }; + // cancelled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The indicator if the menu was selected or cancelled.' }; + // owner: 'mjbvz'; + // comment: 'Event used to gain insights into how code actions are being triggered'; + // }; + + // this._telemetryService.publicLog2('codeAction.applyCodeAction', { + // codeActionFrom: openedFromString, + // validCodeActions: codeActions.validActions.length, + // cancelled: didCancel, + + // }); + + // this._visible = false; + // this._editor.focus(); + // }, + // autoSelectFirstItem: true, + // getKeyBinding: action => action instanceof CodeActionAction ? resolver(action.action) : undefined, + // }); } /** From 743082ca2b21b797036ccf2551171774a51ee8d1 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Thu, 7 Jul 2022 14:31:52 -0700 Subject: [PATCH 012/197] cleaned up code and disposables --- .../codeAction/browser/codeActionMenu.ts | 47 ++----------------- 1 file changed, 3 insertions(+), 44 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 6b97d66d4cb..a29ae81e013 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -145,7 +145,7 @@ interface ISelectedCodeAction { } -export class CodeActionMenu extends Disposable implements IContentWidget { +export class CodeActionMenu extends Disposable { private codeActionList!: List; private options: ICodeActionMenuItem[] = []; @@ -155,22 +155,11 @@ export class CodeActionMenu extends Disposable implements IContentWidget { private readonly _onDidSelect = new Emitter(); private readonly _onDidHideContextMenu = new Emitter(); readonly onDidHideContextMenu = this._onDidHideContextMenu.event; - private parent!: HTMLElement; - private listTrigger!: CodeActionTrigger; - private selected!: CodeActionItem; - private menuAction: IAction[] = []; - private loc: any; - // private showActions: CodeActionItem[]; readonly onDidSelect: Event = this._onDidSelect.event; - - - private readonly _keybindingResolver: CodeActionKeybindingResolver; listRenderer: any; - // selected: any; - // _isVisible: any; constructor( private readonly _editor: ICodeEditor, @@ -192,16 +181,6 @@ export class CodeActionMenu extends Disposable implements IContentWidget { allowEditorOverflow?: boolean | undefined; suppressMouseDown?: boolean | undefined; - getId(): string { - throw new Error('Method not implemented.'); - } - getDomNode(): HTMLElement { - throw new Error('Method not implemented.'); - } - getPosition(): IContentWidgetPosition | null { - throw new Error('Method not implemented.'); - } - get isVisible(): boolean { return this._visible; } @@ -221,7 +200,7 @@ export class CodeActionMenu extends Disposable implements IContentWidget { // this._editor.removeContentWidget(this); // this._editor.getDomNode()?.removeChild(this.parent); this.codeActionList.dispose(); - this.menuAction = []; + this._contextViewService.hideContextView(); this.options = []; // this.parent.dispose(); @@ -260,11 +239,6 @@ export class CodeActionMenu extends Disposable implements IContentWidget { renderMenu.id = 'testRedSquare'; element.appendChild(renderMenu); - // element.style.position = 'fixed'; - // element.style.left = this.loc.x + 'px'; - // element.style.top = this.loc.y + 'px'; - - this.listRenderer = new CodeMenuRenderer(); @@ -277,8 +251,6 @@ export class CodeActionMenu extends Disposable implements IContentWidget { } }, [this.listRenderer], - //new class, + new instance, id of rendere match id of getTemplateID - //renderTemplate, renderElement ); @@ -287,14 +259,11 @@ export class CodeActionMenu extends Disposable implements IContentWidget { } - inputArray.forEach((item, index) => { // const tooltip = item.tooltip ? item.tooltip : ''; this.options.push({ title: item.label, detail: item.tooltip, action: inputArray[index], isDisabled: item.enabled }); }); - // this._editor.getDomNode()?.append(element); - this.codeActionList?.splice(0, this.codeActionList.length, this.options); this.codeActionList.layout(180); return renderDisposables; @@ -322,10 +291,6 @@ export class CodeActionMenu extends Disposable implements IContentWidget { this._visible = true; this._showingActions.value = codeActions; - - this.listTrigger = trigger; - - const menuActions = this.getMenuActions(trigger, actionsToShow, codeActions.documentation); // this.menuAction = menuActions; @@ -339,19 +304,13 @@ export class CodeActionMenu extends Disposable implements IContentWidget { const useShadowDOM = this._editor.getOption(EditorOption.useShadowDOM); - // this.createCodeActionMenuList(this.parent, this.menuAction); - - // this.codeActionList.layout(180); - - // this.parent = document.createElement('div'); - this._contextViewService.showContextView({ getAnchor: () => anchor, render: (container: HTMLElement) => this.renderCodeActionMenuList(container, menuActions), onHide: (didCancel) => { + console.log(didCancel); this._visible = false; this._editor.focus(); - this._contextViewService.hideContextView(); }, }, //this._editor.getDomNode(), if we use shadow dom ( + shadow dom param) From ad160665dedc6fee084bd7280c4599056e07212e Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Thu, 7 Jul 2022 15:21:03 -0700 Subject: [PATCH 013/197] disposable slow off click --- .../codeAction/browser/codeActionMenu.ts | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index a29ae81e013..504b79cdab6 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -5,7 +5,7 @@ // import * as dom from 'vs/base/browser/dom'; import { getDomNodePagePosition } from 'vs/base/browser/dom'; -import { IAnchor, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; import { IListEvent, IListRenderer } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { Action, IAction, Separator } from 'vs/base/common/actions'; @@ -14,7 +14,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, dispose, MutableDisposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { ScrollType } from 'vs/editor/common/editorCommon'; @@ -22,12 +22,11 @@ import { CodeAction, Command } from 'vs/editor/common/languages'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { codeActionCommandId, CodeActionItem, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/browser/codeAction'; import { CodeActionModel } from 'vs/editor/contrib/codeAction/browser/codeActionModel'; -import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionKind, CodeActionTrigger, CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/browser/types'; +import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionKind, CodeActionTrigger } from 'vs/editor/contrib/codeAction/browser/types'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { attachListStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; // import { Emitter } from 'vs/base/common/event'; @@ -202,8 +201,6 @@ export class CodeActionMenu extends Disposable { this.codeActionList.dispose(); this._contextViewService.hideContextView(); this.options = []; - - // this.parent.dispose(); } } @@ -270,6 +267,10 @@ export class CodeActionMenu extends Disposable { } + override dispose() { + this._contextViewService.hideContextView(); + } + public async show(trigger: CodeActionTrigger, codeActions: CodeActionSet, at: IAnchor | IPosition, options: CodeActionShowOptions): Promise { const actionsToShow = options.includeDisabledActions ? codeActions.allActions : codeActions.validActions; @@ -278,10 +279,6 @@ export class CodeActionMenu extends Disposable { return; } - // this.showActions = actionsToShow; - - //Some helper that will make a call to this.getMenuActions() - if (!this._editor.getDomNode()) { // cancel when editor went off-dom this._visible = false; @@ -293,12 +290,7 @@ export class CodeActionMenu extends Disposable { const menuActions = this.getMenuActions(trigger, actionsToShow, codeActions.documentation); - // this.menuAction = menuActions; - - const anchor = Position.isIPosition(at) ? this._toCoords(at) : at || { x: 0, y: 0 }; - // this.loc = anchor; - const resolver = this._keybindingResolver.getResolver(); From 8c5a4966395b0fbe217e2d27b04d6fb295ac8639 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Mon, 11 Jul 2022 12:33:32 -0700 Subject: [PATCH 014/197] added dispose on offclick - slow response from offclick, optimizing in next push --- .../editor/contrib/codeAction/browser/codeActionMenu.ts | 9 ++++++++- src/vs/editor/contrib/codeAction/browser/codeActionUi.ts | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 504b79cdab6..03a32ec8f71 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -66,6 +66,7 @@ export interface ICodeActionMenuItem { action: IAction; decoratorRight?: string; isDisabled?: boolean; + disposables?: IDisposable[]; } export interface ICodeMenuOptions { @@ -135,6 +136,10 @@ class CodeMenuRenderer implements IListRenderer Date: Mon, 11 Jul 2022 16:27:13 -0700 Subject: [PATCH 015/197] added css --- .../codeAction/browser/codeActionMenu.ts | 71 ++-- .../codeAction/browser/media/action.css | 302 ++++++++++++++++++ 2 files changed, 326 insertions(+), 47 deletions(-) create mode 100644 src/vs/editor/contrib/codeAction/browser/media/action.css diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 03a32ec8f71..89d6d944f3a 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -14,6 +14,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, dispose, MutableDisposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import 'vs/css!./media/action'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IPosition, Position } from 'vs/editor/common/core/position'; @@ -22,7 +23,7 @@ import { CodeAction, Command } from 'vs/editor/common/languages'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { codeActionCommandId, CodeActionItem, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/browser/codeAction'; import { CodeActionModel } from 'vs/editor/contrib/codeAction/browser/codeActionModel'; -import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionKind, CodeActionTrigger } from 'vs/editor/contrib/codeAction/browser/types'; +import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionKind, CodeActionTrigger, CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/browser/types'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; @@ -85,11 +86,6 @@ export interface ICodeActionMenuTemplateData { disposables: IDisposable[]; } -// export interface ICodeMenuData { -// selected: string; -// index: number; -// } - const TEMPLATE_ID = 'test'; class CodeMenuRenderer implements IListRenderer { get templateId(): string { return TEMPLATE_ID; } @@ -123,10 +119,7 @@ class CodeMenuRenderer implements IListRenderer new CodeActionAction(itemAction, () => this._delegate.onSelectCodeAction(item, this.listTrigger)); - // console.log(toCodeActionAction); }); - // this.codeActionList.dispose(); - // this._editor.removeContentWidget(this); - // this._editor.getDomNode()?.removeChild(this.parent); - this.codeActionList.dispose(); - this._contextViewService.hideContextView(); - this.options = []; + this.dispose(); } } - - private setCodeActionMenuList() { - this.codeActionList?.splice(0, this.codeActionList.length, this.options); - } - - private createOption(value: string, index: number, disabled?: boolean): HTMLOptionElement { - const option = document.createElement('option'); - option.value = value; - option.text = value; - option.disabled = !!disabled; - - return option; - } - private renderCodeActionMenuList(element: HTMLElement, inputArray: IAction[]): IDisposable { - // if (this.codeActionList) { - // return; - // } - const renderDisposables = new DisposableStore(); - const renderMenu = document.createElement('div'); - renderMenu.style.backgroundColor = 'rgb(48, 48, 49)'; - renderMenu.style.border = '1px black'; - renderMenu.style.borderRadius = '5px'; - renderMenu.style.color = 'rgb(204, 204, 204)'; - renderMenu.style.boxShadow = 'rgb(0,0,0,0.36) 0px 2px 8px'; + + + // Menu.initializeOrUpdateStyleSheet(renderMenu, {}); + + // renderMenu.style.backgroundColor = 'rgb(48, 48, 49)'; + // renderMenu.style.border = '1px black'; + // renderMenu.style.borderRadius = '5px'; + // renderMenu.style.color = 'rgb(204, 204, 204)'; + // renderMenu.style.boxShadow = 'rgb(0,0,0,0.36) 0px 2px 8px'; renderMenu.style.width = '350px'; renderMenu.style.height = '200px'; renderMenu.id = 'testMenu'; @@ -252,21 +223,18 @@ export class CodeActionMenu extends Disposable { return 'test'; } }, [this.listRenderer], - ); - if (this.codeActionList) { renderDisposables.add(this.codeActionList.onDidChangeSelection(e => this._onListSelection(e))); } - inputArray.forEach((item, index) => { // const tooltip = item.tooltip ? item.tooltip : ''; this.options.push({ title: item.label, detail: item.tooltip, action: inputArray[index], isDisabled: item.enabled }); }); - this.codeActionList?.splice(0, this.codeActionList.length, this.options); + this.codeActionList.splice(0, this.codeActionList.length, this.options); this.codeActionList.layout(180); return renderDisposables; @@ -276,9 +244,9 @@ export class CodeActionMenu extends Disposable { this.codeActionList.dispose(); this.options = []; this._contextViewService.hideContextView(); + this._disposables.dispose(); } - public async show(trigger: CodeActionTrigger, codeActions: CodeActionSet, at: IAnchor | IPosition, options: CodeActionShowOptions): Promise { const actionsToShow = options.includeDisabledActions ? codeActions.allActions : codeActions.validActions; if (!actionsToShow.length) { @@ -310,6 +278,8 @@ export class CodeActionMenu extends Disposable { console.log(didCancel); this._visible = false; this._editor.focus(); + + // TODO: Telemetry to be added }, }, //this._editor.getDomNode(), if we use shadow dom ( + shadow dom param) @@ -490,3 +460,10 @@ export class CodeActionKeybindingResolver { }, undefined as ResolveCodeActionKeybinding | undefined); } } +function newFunction(data: ICodeActionMenuTemplateData) { + data.root.classList.add('option-disabled'); + data.root.style.backgroundColor = 'transparent !important'; + data.root.style.color = 'rgb(204, 204, 204, 0.5)'; + data.root.style.cursor = 'default'; +} + diff --git a/src/vs/editor/contrib/codeAction/browser/media/action.css b/src/vs/editor/contrib/codeAction/browser/media/action.css new file mode 100644 index 00000000000..08aa26b9c36 --- /dev/null +++ b/src/vs/editor/contrib/codeAction/browser/media/action.css @@ -0,0 +1,302 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-menu { + font-size: 13px; + border-radius: 5px; + min-width: 160px; +} + +.testMenu { + background-color: red !important; +} +/* +${formatRule(Codicon.menuSelection)} +${formatRule(Codicon.menuSubmenu)} */ + +.monaco-menu .monaco-action-bar { + text-align: right; + overflow: hidden; + white-space: nowrap; +} + +.monaco-menu .monaco-action-bar .actions-container { + display: flex; + margin: 0 auto; + padding: 0; + width: 100%; + justify-content: flex-end; +} + +.monaco-menu .monaco-action-bar.vertical .actions-container { + display: inline-block; +} + +.monaco-menu .monaco-action-bar.reverse .actions-container { + flex-direction: row-reverse; +} + +.monaco-menu .monaco-action-bar .action-item { + cursor: pointer; + display: inline-block; + transition: transform 50ms ease; + position: relative; /* DO NOT REMOVE - this is the key to preventing the ghosting icon bug in Chrome 42 */ +} + +.monaco-menu .monaco-action-bar .action-item.disabled { + cursor: default; +} + +.monaco-menu .monaco-action-bar.animated .action-item.active { + transform: scale(1.272019649, 1.272019649); /* 1.272019649 = √φ */ +} + +.monaco-menu .monaco-action-bar .action-item .icon, +.monaco-menu .monaco-action-bar .action-item .codicon { + display: inline-block; +} + +.monaco-menu .monaco-action-bar .action-item .codicon { + display: flex; + align-items: center; +} + +.monaco-menu .monaco-action-bar .action-label { + font-size: 11px; + margin-right: 4px; +} + +.monaco-menu .monaco-action-bar .action-item.disabled .action-label, +.monaco-menu .monaco-action-bar .action-item.disabled .action-label:hover { + color: var(--vscode-disabledForeground); +} + +/* Vertical actions */ + +.monaco-menu .monaco-action-bar.vertical { + text-align: left; +} + +.monaco-menu .monaco-action-bar.vertical .action-item { + display: block; +} + +.monaco-menu .monaco-action-bar.vertical .action-label.separator { + display: block; + border-bottom: 1px solid var(--vscode-menu-separatorBackground); + padding-top: 1px; + padding: 30px; +} + +.monaco-menu .secondary-actions .monaco-action-bar .action-label { + margin-left: 6px; +} + +/* Action Items */ +.monaco-menu .monaco-action-bar .action-item.select-container { + overflow: hidden; /* somehow the dropdown overflows its container, we prevent it here to not push */ + flex: 1; + max-width: 170px; + min-width: 60px; + display: flex; + align-items: center; + justify-content: center; + margin-right: 10px; +} + +.monaco-menu .monaco-action-bar.vertical { + margin-left: 0; + overflow: visible; +} + +.monaco-menu .monaco-action-bar.vertical .actions-container { + display: block; +} + +.monaco-menu .monaco-action-bar.vertical .action-item { + padding: 0; + transform: none; + display: flex; +} + +.monaco-menu .monaco-action-bar.vertical .action-item.active { + transform: none; +} + +.monaco-menu .monaco-action-bar.vertical .action-menu-item { + flex: 1 1 auto; + display: flex; + height: 2em; + align-items: center; + position: relative; +} + +.monaco-menu .monaco-action-bar.vertical .action-menu-item:hover .keybinding, +.monaco-menu .monaco-action-bar.vertical .action-menu-item:focus .keybinding { + opacity: unset; +} + +.monaco-menu .monaco-action-bar.vertical .action-label { + flex: 1 1 auto; + text-decoration: none; + padding: 0 1em; + background: none; + font-size: 12px; + line-height: 1; +} + +.monaco-menu .monaco-action-bar.vertical .keybinding, +.monaco-menu .monaco-action-bar.vertical .submenu-indicator { + display: inline-block; + flex: 2 1 auto; + padding: 0 1em; + text-align: right; + font-size: 12px; + line-height: 1; +} + +.monaco-menu .monaco-action-bar.vertical .submenu-indicator { + height: 100%; +} + +.monaco-menu .monaco-action-bar.vertical .submenu-indicator.codicon { + font-size: 16px !important; + display: flex; + align-items: center; +} + +.monaco-menu .monaco-action-bar.vertical .submenu-indicator.codicon::before { + margin-left: auto; + margin-right: -20px; +} + +.monaco-menu .monaco-action-bar.vertical .action-item.disabled .keybinding, +.monaco-menu .monaco-action-bar.vertical .action-item.disabled .submenu-indicator { + opacity: 0.4; +} + +.monaco-menu .monaco-action-bar.vertical .action-label:not(.separator) { + display: inline-block; + box-sizing: border-box; + margin: 0; +} + +.monaco-menu .monaco-action-bar.vertical .action-item { + position: static; + overflow: visible; +} + +.monaco-menu .monaco-action-bar.vertical .action-item .monaco-submenu { + position: absolute; +} + +.monaco-menu .monaco-action-bar.vertical .action-label.separator { + width: 100%; + height: 0px !important; + opacity: 1; +} + +.monaco-menu .monaco-action-bar.vertical .action-label.separator.text { + padding: 0.7em 1em 0.1em 1em; + font-weight: bold; + opacity: 1; +} + +.monaco-menu .monaco-action-bar.vertical .action-label:hover { + color: inherit; +} + +.monaco-menu .monaco-action-bar.vertical .menu-item-check { + position: absolute; + visibility: hidden; + width: 1em; + height: 100%; +} + +.monaco-menu .monaco-action-bar.vertical .action-menu-item.checked .menu-item-check { + visibility: visible; + display: flex; + align-items: center; + justify-content: center; +} + +/* Context Menu */ + +.context-view.monaco-menu-container { + outline: 0; + border: none; + animation: fadeIn 0.083s linear; + -webkit-app-region: no-drag; +} + +.context-view.monaco-menu-container :focus, +.context-view.monaco-menu-container .monaco-action-bar.vertical:focus, +.context-view.monaco-menu-container .monaco-action-bar.vertical :focus { + outline: 0; +} + +.hc-black .context-view.monaco-menu-container, +.hc-light .context-view.monaco-menu-container, +:host-context(.hc-black) .context-view.monaco-menu-container, +:host-context(.hc-light) .context-view.monaco-menu-container { + box-shadow: none; +} + +.hc-black .monaco-menu .monaco-action-bar.vertical .action-item.focused, +.hc-light .monaco-menu .monaco-action-bar.vertical .action-item.focused, +:host-context(.hc-black) .monaco-menu .monaco-action-bar.vertical .action-item.focused, +:host-context(.hc-light) .monaco-menu .monaco-action-bar.vertical .action-item.focused { + background: none; +} + +/* Vertical Action Bar Styles */ + +.monaco-menu .monaco-action-bar.vertical { + padding: .6em 0; +} + +.monaco-menu .monaco-action-bar.vertical .action-menu-item { + height: 2em; +} + +.monaco-menu .monaco-action-bar.vertical .action-label:not(.separator), +.monaco-menu .monaco-action-bar.vertical .keybinding { + font-size: inherit; + padding: 0 2em; +} + +.monaco-menu .monaco-action-bar.vertical .menu-item-check { + font-size: inherit; + width: 2em; +} + +.monaco-menu .monaco-action-bar.vertical .action-label.separator { + font-size: inherit; + margin: 5px 0 !important; + padding: 0; + border-radius: 0; +} + +.linux .monaco-menu .monaco-action-bar.vertical .action-label.separator, +:host-context(.linux) .monaco-menu .monaco-action-bar.vertical .action-label.separator { + margin-left: 0; + margin-right: 0; +} + +.monaco-menu .monaco-action-bar.vertical .submenu-indicator { + font-size: 60%; + padding: 0 1.8em; +} + +/* .linux .monaco-menu .monaco-action-bar.vertical .submenu-indicator { +:host-context(.linux) .monaco-menu .monaco-action-bar.vertical .submenu-indicator { + height: 100%; + mask-size: 10px 10px; + -webkit-mask-size: 10px 10px; +} */ + +.monaco-menu .action-item { + cursor: default; +}; From 239141ec215649cb84a368daab6e8a668cf332b8 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Mon, 11 Jul 2022 22:28:06 -0700 Subject: [PATCH 016/197] styled menu, minus width --- .../codeAction/browser/codeActionMenu.ts | 17 ++++--- .../codeAction/browser/media/action.css | 48 ++++++++++++++++++- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 89d6d944f3a..c85d49a64ae 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -207,14 +207,19 @@ export class CodeActionMenu extends Disposable { // renderMenu.style.borderRadius = '5px'; // renderMenu.style.color = 'rgb(204, 204, 204)'; // renderMenu.style.boxShadow = 'rgb(0,0,0,0.36) 0px 2px 8px'; - renderMenu.style.width = '350px'; - renderMenu.style.height = '200px'; - renderMenu.id = 'testMenu'; - - element.appendChild(renderMenu); + // renderMenu.style.width = '350px'; this.listRenderer = new CodeMenuRenderer(); + const height = inputArray.length * 23; + renderMenu.style.height = String(height) + 'px'; + + + renderMenu.id = 'testMenu'; + renderMenu.classList.add('testMenu'); + + element.appendChild(renderMenu); + this.codeActionList = new List('test', renderMenu, { getHeight(element) { return 23; @@ -235,7 +240,7 @@ export class CodeActionMenu extends Disposable { }); this.codeActionList.splice(0, this.codeActionList.length, this.options); - this.codeActionList.layout(180); + this.codeActionList.layout(this.codeActionList.length * 23); return renderDisposables; } diff --git a/src/vs/editor/contrib/codeAction/browser/media/action.css b/src/vs/editor/contrib/codeAction/browser/media/action.css index 08aa26b9c36..dfc29597e3e 100644 --- a/src/vs/editor/contrib/codeAction/browser/media/action.css +++ b/src/vs/editor/contrib/codeAction/browser/media/action.css @@ -10,8 +10,54 @@ } .testMenu { - background-color: red !important; + padding: 10px 10px 10px 10px; + overflow: auto; + font-size: 13px; + border-radius: 5px; + min-width: 160px; + z-index: 40; + display: flex; + flex-direction: column; + flex: 0 1 auto; + width: 100%; + border-style: solid; + border-width: 1px; + border-color: var(--vscode-editorSuggestWidget-border); + background-color: var(--vscode-editorSuggestWidget-background); } + +.testMenu .monaco-list { + user-select: none; + -webkit-user-select: none; + -ms-user-select: none; +} + +.testMenu .monaco-list .monaco-scrollable-element .monaco-list-rows{ + height: 100% !important; +} +/** Styles for each row in the list element **/ + +.testMenu .monaco-list .monaco-list-row { + display: flex; + -mox-box-sizing: border-box; + box-sizing: border-box; + padding-right: 10px; + background-repeat: no-repeat; + background-position: 2px 2px; + white-space: nowrap; + cursor: pointer; + touch-action: none; +} + +.testMenu .monaco-list .monaco-list-row:hover:not(.option-disabled) { + color: var(--vscode-editorSuggestWidget-selectedForeground); + background-color: rgb(4, 57, 94) !important; +} + +.testMenu .monaco-list .option-disabled { + pointer-events: none; +} + /* ${formatRule(Codicon.menuSelection)} ${formatRule(Codicon.menuSubmenu)} */ From 6c190bd36e8b809b29023399e82d0f1e70cad393 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Wed, 13 Jul 2022 14:35:01 -0700 Subject: [PATCH 017/197] fix on slow disposable and focus --- .../codeAction/browser/codeActionMenu.ts | 119 ++++++++++++++---- .../codeAction/browser/codeActionUi.ts | 1 - .../codeAction/browser/media/action.css | 72 ++++++++--- 3 files changed, 152 insertions(+), 40 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index c85d49a64ae..a6100828dac 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// import * as dom from 'vs/base/browser/dom'; -import { getDomNodePagePosition } from 'vs/base/browser/dom'; +import * as dom from 'vs/base/browser/dom'; import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; import { IListEvent, IListRenderer } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; @@ -15,7 +14,7 @@ import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, dispose, MutableDisposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import 'vs/css!./media/action'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { ScrollType } from 'vs/editor/common/editorCommon'; @@ -24,7 +23,11 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat import { codeActionCommandId, CodeActionItem, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/browser/codeAction'; import { CodeActionModel } from 'vs/editor/contrib/codeAction/browser/codeActionModel'; import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionKind, CodeActionTrigger, CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/browser/types'; +import { ICancelEvent } from 'vs/editor/contrib/suggest/browser/suggestModel'; +import { localize } from 'vs/nls'; +import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { historyNavigationVisible } from 'vs/platform/history/browser/contextScopedHistoryWidget'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -33,6 +36,17 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; // const $ = dom.$; +export const Context = { + Visible: historyNavigationVisible, + // HasFocusedSuggestion: new RawContextKey('suggestWidgetHasFocusedSuggestion', false, localize('suggestWidgetHasSelection', "Whether any suggestion is focused")), + // DetailsVisible: new RawContextKey('suggestWidgetDetailsVisible', false, localize('suggestWidgetDetailsVisible', "Whether suggestion details are visible")), + // MultipleSuggestions: new RawContextKey('suggestWidgetMultipleSuggestions', false, localize('suggestWidgetMultipleSuggestions', "Whether there are multiple suggestions to pick from")), + // MakesTextEdit: new RawContextKey('suggestionMakesTextEdit', true, localize('suggestionMakesTextEdit', "Whether inserting the current suggestion yields in a change or has everything already been typed")), + // AcceptSuggestionsOnEnter: new RawContextKey('acceptSuggestionOnEnter', true, localize('acceptSuggestionOnEnter', "Whether suggestions are inserted when pressing Enter")), + // HasInsertAndReplaceRange: new RawContextKey('suggestionHasInsertAndReplaceRange', false, localize('suggestionHasInsertAndReplaceRange', "Whether the current suggestion has insert and replace behaviour")), + // InsertMode: new RawContextKey<'insert' | 'replace'>('suggestionInsertMode', undefined, { type: 'string', description: localize('suggestionInsertMode', "Whether the default behaviour is to insert or replace") }), + // CanResolve: new RawContextKey('suggestionCanResolve', false, localize('suggestionCanResolve', "Whether the current suggestion supports to resolve further details")), +}; interface CodeActionWidgetDelegate { onSelectCodeAction: (action: CodeActionItem, trigger: CodeActionTrigger) => Promise; @@ -66,7 +80,8 @@ export interface ICodeActionMenuItem { detail: string; action: IAction; decoratorRight?: string; - isDisabled?: boolean; + isSeparator?: boolean; + isEnabled?: boolean; disposables?: IDisposable[]; } @@ -112,18 +127,27 @@ class CodeMenuRenderer implements IListRenderer(); private readonly _onDidHideContextMenu = new Emitter(); + // private readonly _onDidCancel = new Emitter(); readonly onDidHideContextMenu = this._onDidHideContextMenu.event; + private readonly _ctxMenuWidgetIsFocused?: IContextKey; + private readonly _ctxMenuWidgetVisible: IContextKey; + private element!: HTMLElement; - readonly onDidSelect: Event = this._onDidSelect.event; + + // private _onDidCancel = this._register(new Emitter({ onFirstListenerAdd: () => this.cancelHasListener = true })); + // readonly onDidCancel = this._onDidCancel.event; + + // readonly onDidSelect: Event = this._onDidSelect.event; private readonly _keybindingResolver: CodeActionKeybindingResolver; listRenderer: any; @@ -163,6 +195,7 @@ export class CodeActionMenu extends Disposable { private readonly _delegate: CodeActionWidgetDelegate, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IContextViewService private readonly _contextViewService: IContextViewService, + @IContextKeyService _contextKeyService: IContextKeyService, @IKeybindingService keybindingService: IKeybindingService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @@ -174,10 +207,21 @@ export class CodeActionMenu extends Disposable { getKeybindings: () => keybindingService.getKeybindings() }); + this._ctxMenuWidgetVisible = Context.Visible.bindTo(_contextKeyService); + + if (this.codeActionList && !this.codeActionList.isDOMFocused()) { + this.dispose(); + } + + // this.onDidCancel(() => this._contextViewService.hideContextView(true)); + } + allowEditorOverflow?: boolean | undefined; suppressMouseDown?: boolean | undefined; + + get isVisible(): boolean { return this._visible; } @@ -185,7 +229,7 @@ export class CodeActionMenu extends Disposable { private _onListSelection(e: IListEvent): void { if (e.elements.length) { e.elements.forEach(element => { - if (element.isDisabled) { + if (element.isEnabled) { const itemAction = element; console.log(itemAction); element.action.run(); @@ -195,10 +239,32 @@ export class CodeActionMenu extends Disposable { } } + private _onListFocus(e: IListEvent): void { + + this._ctxMenuWidgetIsFocused?.set(true); + const item = e.elements[0]; + const index = e.indexes[0]; + + } + + // private _onEditorMouseDown(mouseEvent: IEditorMouseEvent): void { + // if (this.codeActionList.getDOMNode().contains(mouseEvent.target.element)) { + // // Clicking inside details + // this.element.click(); + // this.element.onmouseleave = () => this.element.classList.remove('pointer'); + // this._details.widget.domNode.focus(); + // } else { + // // Clicking outside details and inside suggest + // if (this.element.domNode.contains(mouseEvent.target.element)) { + // this.editor.focus(); + // } + // } + // } + private renderCodeActionMenuList(element: HTMLElement, inputArray: IAction[]): IDisposable { const renderDisposables = new DisposableStore(); const renderMenu = document.createElement('div'); - + this.element = element; // Menu.initializeOrUpdateStyleSheet(renderMenu, {}); @@ -211,7 +277,7 @@ export class CodeActionMenu extends Disposable { this.listRenderer = new CodeMenuRenderer(); - const height = inputArray.length * 23; + const height = inputArray.length * 25; renderMenu.style.height = String(height) + 'px'; @@ -222,7 +288,7 @@ export class CodeActionMenu extends Disposable { this.codeActionList = new List('test', renderMenu, { getHeight(element) { - return 23; + return 25; }, getTemplateId(element) { return 'test'; @@ -236,16 +302,33 @@ export class CodeActionMenu extends Disposable { inputArray.forEach((item, index) => { // const tooltip = item.tooltip ? item.tooltip : ''; - this.options.push({ title: item.label, detail: item.tooltip, action: inputArray[index], isDisabled: item.enabled }); + this.options.push({ title: item.label, detail: item.tooltip, action: inputArray[index], isEnabled: item.enabled, isSeparator: item.class === 'separator' }); }); + // const w = dom.$('.monaco-list-row').innerWidth(); + this.codeActionList.splice(0, this.codeActionList.length, this.options); - this.codeActionList.layout(this.codeActionList.length * 23); + this.codeActionList.layout(height); + this.codeActionList.domFocus(); + + + + const focusTracker = dom.trackFocus(element); + const blurListener = focusTracker.onDidBlur(() => { + this.dispose(); + this._contextViewService.hideContextView({ source: this }); + }); + + renderDisposables.add(blurListener); + renderDisposables.add(focusTracker); + + this._ctxMenuWidgetVisible.set(true); return renderDisposables; } override dispose() { + this._ctxMenuWidgetVisible.reset(); this.codeActionList.dispose(); this.options = []; this._contextViewService.hideContextView(); @@ -382,7 +465,7 @@ export class CodeActionMenu extends Disposable { // Translate to absolute editor position const cursorCoords = this._editor.getScrolledVisiblePosition(position); - const editorCoords = getDomNodePagePosition(this._editor.getDomNode()); + const editorCoords = dom.getDomNodePagePosition(this._editor.getDomNode()); const x = editorCoords.left + cursorCoords.left; const y = editorCoords.top + cursorCoords.top + cursorCoords.height; @@ -465,10 +548,4 @@ export class CodeActionKeybindingResolver { }, undefined as ResolveCodeActionKeybinding | undefined); } } -function newFunction(data: ICodeActionMenuTemplateData) { - data.root.classList.add('option-disabled'); - data.root.style.backgroundColor = 'transparent !important'; - data.root.style.color = 'rgb(204, 204, 204, 0.5)'; - data.root.style.cursor = 'default'; -} diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts index 368a5e5da70..0f8cd9b237d 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts @@ -119,7 +119,6 @@ export class CodeActionUi extends Disposable { // auto magically triggered if (this._codeActionWidget.getValue().isVisible) { // TODO: Figure out if we should update the showing menu? - this._codeActionWidget.getValue().dispose(); actions.dispose(); } else { this._activeCodeActions.value = actions; diff --git a/src/vs/editor/contrib/codeAction/browser/media/action.css b/src/vs/editor/contrib/codeAction/browser/media/action.css index dfc29597e3e..525d395a15e 100644 --- a/src/vs/editor/contrib/codeAction/browser/media/action.css +++ b/src/vs/editor/contrib/codeAction/browser/media/action.css @@ -10,15 +10,15 @@ } .testMenu { - padding: 10px 10px 10px 10px; + padding: 10px 0px 10px 0px; overflow: auto; font-size: 13px; border-radius: 5px; - min-width: 160px; + min-width: 320px; z-index: 40; - display: flex; - flex-direction: column; - flex: 0 1 auto; + display: block; + /* flex-direction: column; + flex: 0 1 auto; */ width: 100%; border-style: solid; border-width: 1px; @@ -32,16 +32,60 @@ -ms-user-select: none; } -.testMenu .monaco-list .monaco-scrollable-element .monaco-list-rows{ +.testMenu .monaco-list .monaco-scrollable-element .monaco-list-rows { height: 100% !important; } + +.testMenu .monaco-list .monaco-scrollable-element { + overflow: visible; +} /** Styles for each row in the list element **/ -.testMenu .monaco-list .monaco-list-row { +.testMenu .monaco-list .monaco-list-row:not(.separator) { + display: flex; + -mox-box-sizing: border-box; + box-sizing: border-box; + padding: 0px 10px 0px 10px; + background-repeat: no-repeat; + background-position: 2px 2px; + white-space: nowrap; + cursor: pointer; + touch-action: none; + width: 100%; +} + + + +.testMenu .monaco-list .monaco-list-row:hover:not(.option-disabled), +.testMenu .monaco-list .monaco-list-row .focused:not(.option-disabled) { + color: var(--vscode-editorSuggestWidget-selectedForeground); + background-color: rgb(4, 57, 94) !important; +} + +.testMenu .monaco-list .option-disabled, +.testMenu .monaco-list .option-disabled .focused { + pointer-events: none; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.testMenu .monaco-list .separator { + border-bottom: 1px solid var(--vscode-menu-separatorBackground); + padding-top: 10px !important; + /* padding: 30px; */ + width: 100%; + height: 0px !important; + opacity: 1; + font-size: inherit; + margin: 5px 0 !important; + border-radius: 0; display: flex; -mox-box-sizing: border-box; box-sizing: border-box; - padding-right: 10px; background-repeat: no-repeat; background-position: 2px 2px; white-space: nowrap; @@ -49,18 +93,10 @@ touch-action: none; } -.testMenu .monaco-list .monaco-list-row:hover:not(.option-disabled) { - color: var(--vscode-editorSuggestWidget-selectedForeground); - background-color: rgb(4, 57, 94) !important; -} - -.testMenu .monaco-list .option-disabled { - pointer-events: none; -} - /* ${formatRule(Codicon.menuSelection)} -${formatRule(Codicon.menuSubmenu)} */ +${formatRule(Codicon.menuSubmenu)} +*/ .monaco-menu .monaco-action-bar { text-align: right; From e88ed29077104ec0d4719269743caac1415e0a81 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Thu, 14 Jul 2022 00:04:00 -0700 Subject: [PATCH 018/197] fixed disposables, fixed css on focus, now focuses --- .../codeAction/browser/codeActionMenu.ts | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index a6100828dac..1e9fe3061ca 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -11,6 +11,7 @@ import { Action, IAction, Separator } from 'vs/base/common/actions'; import { canceled } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; +import { KeyCode } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, dispose, MutableDisposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import 'vs/css!./media/action'; @@ -25,10 +26,11 @@ import { CodeActionModel } from 'vs/editor/contrib/codeAction/browser/codeAction import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionKind, CodeActionTrigger, CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/browser/types'; import { ICancelEvent } from 'vs/editor/contrib/suggest/browser/suggestModel'; import { localize } from 'vs/nls'; -import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { historyNavigationVisible } from 'vs/platform/history/browser/contextScopedHistoryWidget'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -37,7 +39,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; // const $ = dom.$; export const Context = { - Visible: historyNavigationVisible, + Visible: new RawContextKey('codeActionMenuWidgetIsVisible', false, localize('codeActionMenuWidgetIsVisible', "Whether the Code Action Menu is visible.")), // HasFocusedSuggestion: new RawContextKey('suggestWidgetHasFocusedSuggestion', false, localize('suggestWidgetHasSelection', "Whether any suggestion is focused")), // DetailsVisible: new RawContextKey('suggestWidgetDetailsVisible', false, localize('suggestWidgetDetailsVisible', "Whether suggestion details are visible")), // MultipleSuggestions: new RawContextKey('suggestWidgetMultipleSuggestions', false, localize('suggestWidgetMultipleSuggestions', "Whether there are multiple suggestions to pick from")), @@ -213,6 +215,8 @@ export class CodeActionMenu extends Disposable { this.dispose(); } + // this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Escape).on(e => this.onEscape(e), this)); + // this.onDidCancel(() => this._contextViewService.hideContextView(true)); } @@ -298,6 +302,7 @@ export class CodeActionMenu extends Disposable { if (this.codeActionList) { renderDisposables.add(this.codeActionList.onDidChangeSelection(e => this._onListSelection(e))); + renderDisposables.add(this.codeActionList.onDidChangeFocus(e => this._onListFocus(e))); } inputArray.forEach((item, index) => { @@ -310,8 +315,7 @@ export class CodeActionMenu extends Disposable { this.codeActionList.splice(0, this.codeActionList.length, this.options); this.codeActionList.layout(height); this.codeActionList.domFocus(); - - + this.codeActionList.getHTMLElement().style.border = 'none !important'; const focusTracker = dom.trackFocus(element); const blurListener = focusTracker.onDidBlur(() => { @@ -549,3 +553,13 @@ export class CodeActionKeybindingResolver { } } +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'codeActionMenu.selectEditor', + weight: KeybindingWeight.WorkbenchContrib + 1, + primary: KeyCode.Escape, + when: ContextKeyExpr.and(Context.Visible), + handler(accessor) { + console.log('hello hi'); + } +}); + From 3af187eec209ebe3272401e6cf3f2eef2762488b Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Thu, 14 Jul 2022 00:29:18 -0700 Subject: [PATCH 019/197] cleaning up code --- .../codeAction/browser/codeActionMenu.ts | 3 --- .../codeAction/browser/codeActionUi.ts | 13 ++++++++++ .../codeAction/browser/media/action.css | 25 +++++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 1e9fe3061ca..1cea2893735 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -306,12 +306,9 @@ export class CodeActionMenu extends Disposable { } inputArray.forEach((item, index) => { - // const tooltip = item.tooltip ? item.tooltip : ''; this.options.push({ title: item.label, detail: item.tooltip, action: inputArray[index], isEnabled: item.enabled, isSeparator: item.class === 'separator' }); }); - // const w = dom.$('.monaco-list-row').innerWidth(); - this.codeActionList.splice(0, this.codeActionList.length, this.options); this.codeActionList.layout(height); this.codeActionList.domFocus(); diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts index 0f8cd9b237d..9a5edfb16b7 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts @@ -8,6 +8,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { registerEditorCommand } from 'vs/editor/browser/editorExtensions'; import { IPosition } from 'vs/editor/common/core/position'; import { CodeActionTriggerType } from 'vs/editor/common/languages'; import { CodeActionItem, CodeActionSet } from 'vs/editor/contrib/codeAction/browser/codeAction'; @@ -158,3 +159,15 @@ export class CodeActionUi extends Disposable { this._codeActionWidget.getValue().show(trigger, actions, at, options); } } + +// registerEditorCommand(new SuggestCommand({ +// id: 'hideSuggestWidget', +// precondition: SuggestContext.Visible, +// handler: x => x.cancelSuggestWidget(), +// kbOpts: { +// weight: weight, +// kbExpr: EditorContextKeys.textInputFocus, +// primary: KeyCode.Escape, +// secondary: [KeyMod.Shift | KeyCode.Escape] +// } +// })); diff --git a/src/vs/editor/contrib/codeAction/browser/media/action.css b/src/vs/editor/contrib/codeAction/browser/media/action.css index 525d395a15e..3b1e0e1b651 100644 --- a/src/vs/editor/contrib/codeAction/browser/media/action.css +++ b/src/vs/editor/contrib/codeAction/browser/media/action.css @@ -19,6 +19,9 @@ display: block; /* flex-direction: column; flex: 0 1 auto; */ + + + width: 100%; border-style: solid; border-width: 1px; @@ -26,12 +29,34 @@ background-color: var(--vscode-editorSuggestWidget-background); } +.testMenu .monaco-list:not(.element-focused):focus:before { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 5; /* make sure we are on top of the tree items */ + content: ""; + pointer-events: none; /* enable click through */ + outline: 0px solid; /* we still need to handle the empty tree or no focus item case */ + outline-width: 0px; + outline-style: none; + outline-offset: 0px; +} + .testMenu .monaco-list { user-select: none; -webkit-user-select: none; -ms-user-select: none; + border: none !important; + border-width: 0px !important; } +/* .testMenu .monaco-list:not(.element-focus) { + border: none !important; + border-width: 0px !important; +} */ + .testMenu .monaco-list .monaco-scrollable-element .monaco-list-rows { height: 100% !important; } From 5966152662d45fddba062a975554fb54a79440c6 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Thu, 14 Jul 2022 15:31:00 -0700 Subject: [PATCH 020/197] resized widget, fits to with, fix on focus --- src/vs/base/browser/ui/list/listWidget.ts | 5 ++ .../codeAction/browser/codeActionMenu.ts | 75 +++++++++++++++---- .../codeAction/browser/media/action.css | 5 +- 3 files changed, 68 insertions(+), 17 deletions(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index db0a0b718dc..bdb76b49d09 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -1799,6 +1799,11 @@ export class List implements ISpliceable, IThemable, IDisposable { return this.view.domNode; } + getElementID(index: number): string { + console.log(index); + return this.view.getElementDomId(index); + } + style(styles: IListStyles): void { this.styleController.style(styles); } diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 1cea2893735..15288b8a9ed 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -11,14 +11,16 @@ import { Action, IAction, Separator } from 'vs/base/common/actions'; import { canceled } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; -import { KeyCode } from 'vs/base/common/keyCodes'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, dispose, MutableDisposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import 'vs/css!./media/action'; import { ICodeEditor, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; +import { EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { ScrollType } from 'vs/editor/common/editorCommon'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { CodeAction, Command } from 'vs/editor/common/languages'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { codeActionCommandId, CodeActionItem, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/browser/codeAction'; @@ -39,7 +41,8 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; // const $ = dom.$; export const Context = { - Visible: new RawContextKey('codeActionMenuWidgetIsVisible', false, localize('codeActionMenuWidgetIsVisible', "Whether the Code Action Menu is visible.")), + Visible: new RawContextKey('CodeActionMenuVisible', false, localize('CodeActionMenuVisible', "Whether suggestion are visible")) + // HasFocusedSuggestion: new RawContextKey('suggestWidgetHasFocusedSuggestion', false, localize('suggestWidgetHasSelection', "Whether any suggestion is focused")), // DetailsVisible: new RawContextKey('suggestWidgetDetailsVisible', false, localize('suggestWidgetDetailsVisible', "Whether suggestion details are visible")), // MultipleSuggestions: new RawContextKey('suggestWidgetMultipleSuggestions', false, localize('suggestWidgetMultipleSuggestions', "Whether there are multiple suggestions to pick from")), @@ -183,6 +186,11 @@ export class CodeActionMenu extends Disposable { private readonly _ctxMenuWidgetVisible: IContextKey; private element!: HTMLElement; + public static readonly ID: string = 'editor.contrib.codeActionMenu'; + + public static get(editor: ICodeEditor): CodeActionMenu | null { + return editor.getContribution(CodeActionMenu.ID); + } // private _onDidCancel = this._register(new Emitter({ onFirstListenerAdd: () => this.cancelHasListener = true })); // readonly onDidCancel = this._onDidCancel.event; @@ -284,7 +292,6 @@ export class CodeActionMenu extends Disposable { const height = inputArray.length * 25; renderMenu.style.height = String(height) + 'px'; - renderMenu.id = 'testMenu'; renderMenu.classList.add('testMenu'); @@ -311,8 +318,29 @@ export class CodeActionMenu extends Disposable { this.codeActionList.splice(0, this.codeActionList.length, this.options); this.codeActionList.layout(height); + + + this.codeActionList.getElementID(2); + + const temp = this.codeActionList.getElementID(0); + console.log(temp); + + const arr: number[] = []; + this.options.forEach((item, index) => { + const element = document.getElementById(this.codeActionList.getElementID(index))?.getElementsByTagName('span')[0].offsetWidth; + arr.push(Number(element)); + }); + + const maxWidth = Math.max(...arr); + renderMenu.style.width = maxWidth + 20 + 'px'; + this.codeActionList.layout(height, maxWidth); + + // resize observer + // supports dynamic height but not width this.codeActionList.domFocus(); this.codeActionList.getHTMLElement().style.border = 'none !important'; + this.codeActionList.setFocus([0]); + //multiselect false const focusTracker = dom.trackFocus(element); const blurListener = focusTracker.onDidBlur(() => { @@ -328,8 +356,14 @@ export class CodeActionMenu extends Disposable { } + arrowOnAvailableItems() { + this.codeActionList.setFocus([0]); + this.codeActionList.setSelection([0]); + + } + override dispose() { - this._ctxMenuWidgetVisible.reset(); + // this._ctxMenuWidgetVisible.reset(); this.codeActionList.dispose(); this.options = []; this._contextViewService.hideContextView(); @@ -550,13 +584,28 @@ export class CodeActionKeybindingResolver { } } -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'codeActionMenu.selectEditor', - weight: KeybindingWeight.WorkbenchContrib + 1, - primary: KeyCode.Escape, - when: ContextKeyExpr.and(Context.Visible), - handler(accessor) { - console.log('hello hi'); - } -}); +const CodeActionCommand = EditorCommand.bindToContribution(CodeActionMenu.get); + +const weight = KeybindingWeight.EditorContrib + 90; + +registerEditorCommand(new CodeActionCommand({ + id: 'hideCodeActionMenuWidget', + precondition: Context.Visible, + handler: x => x.dispose(), + kbOpts: { + weight: weight, + primary: KeyCode.Escape, + secondary: [KeyMod.Shift | KeyCode.Escape] + } +})); + +// KeybindingsRegistry.registerCommandAndKeybindingRule({ +// id: 'codeActionMenu.selectEditor', +// weight: KeybindingWeight.WorkbenchContrib + 1, +// primary: KeyCode.Escape, +// when: ContextKeyExpr.and(Context.Visible), +// handler(accessor) { +// console.log('hello hi'); +// } +// }); diff --git a/src/vs/editor/contrib/codeAction/browser/media/action.css b/src/vs/editor/contrib/codeAction/browser/media/action.css index 3b1e0e1b651..bd373765e1d 100644 --- a/src/vs/editor/contrib/codeAction/browser/media/action.css +++ b/src/vs/editor/contrib/codeAction/browser/media/action.css @@ -14,14 +14,11 @@ overflow: auto; font-size: 13px; border-radius: 5px; - min-width: 320px; + min-width: 160px; z-index: 40; display: block; /* flex-direction: column; flex: 0 1 auto; */ - - - width: 100%; border-style: solid; border-width: 1px; From 1759918acab5d34c11de40bf96848288dfc6e2c9 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Fri, 15 Jul 2022 10:36:47 -0700 Subject: [PATCH 021/197] code cleanup and adding experimental setting --- .../codeAction/browser/codeActionMenu.ts | 207 +++++++----------- .../codeAction/browser/media/action.css | 24 +- 2 files changed, 92 insertions(+), 139 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 15288b8a9ed..5e8c35488c2 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -9,30 +9,27 @@ import { IListEvent, IListRenderer } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { Action, IAction, Separator } from 'vs/base/common/actions'; import { canceled } from 'vs/base/common/errors'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, dispose, MutableDisposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import 'vs/css!./media/action'; -import { ICodeEditor, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IPosition, Position } from 'vs/editor/common/core/position'; -import { ScrollType } from 'vs/editor/common/editorCommon'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; import { CodeAction, Command } from 'vs/editor/common/languages'; +import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { codeActionCommandId, CodeActionItem, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/browser/codeAction'; -import { CodeActionModel } from 'vs/editor/contrib/codeAction/browser/codeActionModel'; import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionKind, CodeActionTrigger, CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/browser/types'; -import { ICancelEvent } from 'vs/editor/contrib/suggest/browser/suggestModel'; import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { historyNavigationVisible } from 'vs/platform/history/browser/contextScopedHistoryWidget'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -106,7 +103,7 @@ export interface ICodeActionMenuTemplateData { disposables: IDisposable[]; } -const TEMPLATE_ID = 'test'; +const TEMPLATE_ID = 'codeActionWidget'; class CodeMenuRenderer implements IListRenderer { get templateId(): string { return TEMPLATE_ID; } @@ -116,14 +113,9 @@ class CodeMenuRenderer implements IListRenderer; private options: ICodeActionMenuItem[] = []; private _visible: boolean = false; private readonly _showingActions = this._register(new MutableDisposable()); private readonly _disposables = new DisposableStore(); - private readonly _onDidSelect = new Emitter(); private readonly _onDidHideContextMenu = new Emitter(); // private readonly _onDidCancel = new Emitter(); readonly onDidHideContextMenu = this._onDidHideContextMenu.event; private readonly _ctxMenuWidgetIsFocused?: IContextKey; private readonly _ctxMenuWidgetVisible: IContextKey; - private element!: HTMLElement; + private readonly editor: ICodeEditor; public static readonly ID: string = 'editor.contrib.codeActionMenu'; @@ -192,11 +170,6 @@ export class CodeActionMenu extends Disposable { return editor.getContribution(CodeActionMenu.ID); } - // private _onDidCancel = this._register(new Emitter({ onFirstListenerAdd: () => this.cancelHasListener = true })); - // readonly onDidCancel = this._onDidCancel.event; - - // readonly onDidSelect: Event = this._onDidSelect.event; - private readonly _keybindingResolver: CodeActionKeybindingResolver; listRenderer: any; @@ -210,9 +183,12 @@ export class CodeActionMenu extends Disposable { @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @IThemeService _themeService: IThemeService, + @IConfigurationService private readonly _configurationService: IConfigurationService, ) { super(); + this.editor = _editor; + this._keybindingResolver = new CodeActionKeybindingResolver({ getKeybindings: () => keybindingService.getKeybindings() }); @@ -222,22 +198,21 @@ export class CodeActionMenu extends Disposable { if (this.codeActionList && !this.codeActionList.isDOMFocused()) { this.dispose(); } - - // this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Escape).on(e => this.onEscape(e), this)); - - // this.onDidCancel(() => this._contextViewService.hideContextView(true)); - } allowEditorOverflow?: boolean | undefined; suppressMouseDown?: boolean | undefined; - - get isVisible(): boolean { return this._visible; } + private isCodeActionWidgetEnabled(model: ITextModel): boolean { + return this._configurationService.getValue('editor.econtrib.codeAction.enabled', { + resource: model.uri + }); + } + private _onListSelection(e: IListEvent): void { if (e.elements.length) { e.elements.forEach(element => { @@ -252,57 +227,31 @@ export class CodeActionMenu extends Disposable { } private _onListFocus(e: IListEvent): void { - this._ctxMenuWidgetIsFocused?.set(true); const item = e.elements[0]; const index = e.indexes[0]; } - // private _onEditorMouseDown(mouseEvent: IEditorMouseEvent): void { - // if (this.codeActionList.getDOMNode().contains(mouseEvent.target.element)) { - // // Clicking inside details - // this.element.click(); - // this.element.onmouseleave = () => this.element.classList.remove('pointer'); - // this._details.widget.domNode.focus(); - // } else { - // // Clicking outside details and inside suggest - // if (this.element.domNode.contains(mouseEvent.target.element)) { - // this.editor.focus(); - // } - // } - // } - private renderCodeActionMenuList(element: HTMLElement, inputArray: IAction[]): IDisposable { const renderDisposables = new DisposableStore(); const renderMenu = document.createElement('div'); - this.element = element; - - // Menu.initializeOrUpdateStyleSheet(renderMenu, {}); - - // renderMenu.style.backgroundColor = 'rgb(48, 48, 49)'; - // renderMenu.style.border = '1px black'; - // renderMenu.style.borderRadius = '5px'; - // renderMenu.style.color = 'rgb(204, 204, 204)'; - // renderMenu.style.boxShadow = 'rgb(0,0,0,0.36) 0px 2px 8px'; - // renderMenu.style.width = '350px'; - this.listRenderer = new CodeMenuRenderer(); const height = inputArray.length * 25; renderMenu.style.height = String(height) + 'px'; - renderMenu.id = 'testMenu'; - renderMenu.classList.add('testMenu'); + renderMenu.id = 'codeActioniMenuWidget'; + renderMenu.classList.add('codeActioniMenuWidget'); element.appendChild(renderMenu); - this.codeActionList = new List('test', renderMenu, { + this.codeActionList = new List('codeActionWidget', renderMenu, { getHeight(element) { return 25; }, getTemplateId(element) { - return 'test'; + return 'codeActionWidget'; } }, [this.listRenderer], ); @@ -319,7 +268,6 @@ export class CodeActionMenu extends Disposable { this.codeActionList.splice(0, this.codeActionList.length, this.options); this.codeActionList.layout(height); - this.codeActionList.getElementID(2); const temp = this.codeActionList.getElementID(0); @@ -335,13 +283,10 @@ export class CodeActionMenu extends Disposable { renderMenu.style.width = maxWidth + 20 + 'px'; this.codeActionList.layout(height, maxWidth); - // resize observer - // supports dynamic height but not width + // resize observer - supports dynamic height but not width this.codeActionList.domFocus(); this.codeActionList.getHTMLElement().style.border = 'none !important'; this.codeActionList.setFocus([0]); - //multiselect false - const focusTracker = dom.trackFocus(element); const blurListener = focusTracker.onDidBlur(() => { this.dispose(); @@ -359,11 +304,10 @@ export class CodeActionMenu extends Disposable { arrowOnAvailableItems() { this.codeActionList.setFocus([0]); this.codeActionList.setSelection([0]); - } override dispose() { - // this._ctxMenuWidgetVisible.reset(); + this._ctxMenuWidgetVisible.reset(); this.codeActionList.dispose(); this.options = []; this._contextViewService.hideContextView(); @@ -371,6 +315,7 @@ export class CodeActionMenu extends Disposable { } public async show(trigger: CodeActionTrigger, codeActions: CodeActionSet, at: IAnchor | IPosition, options: CodeActionShowOptions): Promise { + const model = this.editor.getModel(); const actionsToShow = options.includeDisabledActions ? codeActions.allActions : codeActions.validActions; if (!actionsToShow.length) { this._visible = false; @@ -394,73 +339,81 @@ export class CodeActionMenu extends Disposable { const useShadowDOM = this._editor.getOption(EditorOption.useShadowDOM); + + // if (this.isCodeActionWidgetEnabled(model)) { this._contextViewService.showContextView({ getAnchor: () => anchor, render: (container: HTMLElement) => this.renderCodeActionMenuList(container, menuActions), onHide: (didCancel) => { - console.log(didCancel); + const openedFromString = (options.fromLightbulb) ? CodeActionTriggerSource.Lightbulb : trigger.triggerAction; + + type ApplyCodeActionEvent = { + codeActionFrom: CodeActionTriggerSource; + validCodeActions: number; + cancelled: boolean; + }; + + type ApplyCodeEventClassification = { + codeActionFrom: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of action used to opened the code action.' }; + validCodeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The total number of valid actions that are highlighted and can be used.' }; + cancelled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The indicator if the menu was selected or cancelled.' }; + owner: 'mjbvz'; + comment: 'Event used to gain insights into how code actions are being triggered'; + }; + + this._telemetryService.publicLog2('codeAction.applyCodeAction', { + codeActionFrom: openedFromString, + validCodeActions: codeActions.validActions.length, + cancelled: didCancel, + + }); this._visible = false; this._editor.focus(); - // TODO: Telemetry to be added }, }, //this._editor.getDomNode(), if we use shadow dom ( + shadow dom param) ); + // } - // this._contextMenuService.showContextMenu({ - // domForShadowRoot: useShadowDOM ? this._editor.getDomNode()! : undefined, - // getAnchor: () => anchor, - // getActions: () => menuActions, - // onHide: (didCancel) => { - // const openedFromString = (options.fromLightbulb) ? CodeActionTriggerSource.Lightbulb : trigger.triggerAction; - // type ApplyCodeActionEvent = { - // codeActionFrom: CodeActionTriggerSource; - // validCodeActions: number; - // cancelled: boolean; - // }; + this._contextMenuService.showContextMenu({ + domForShadowRoot: useShadowDOM ? this._editor.getDomNode()! : undefined, + getAnchor: () => anchor, + getActions: () => menuActions, + onHide: (didCancel) => { + const openedFromString = (options.fromLightbulb) ? CodeActionTriggerSource.Lightbulb : trigger.triggerAction; - // type ApplyCodeEventClassification = { - // codeActionFrom: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of action used to opened the code action.' }; - // validCodeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The total number of valid actions that are highlighted and can be used.' }; - // cancelled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The indicator if the menu was selected or cancelled.' }; - // owner: 'mjbvz'; - // comment: 'Event used to gain insights into how code actions are being triggered'; - // }; + type ApplyCodeActionEvent = { + codeActionFrom: CodeActionTriggerSource; + validCodeActions: number; + cancelled: boolean; + }; - // this._telemetryService.publicLog2('codeAction.applyCodeAction', { - // codeActionFrom: openedFromString, - // validCodeActions: codeActions.validActions.length, - // cancelled: didCancel, + type ApplyCodeEventClassification = { + codeActionFrom: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of action used to opened the code action.' }; + validCodeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The total number of valid actions that are highlighted and can be used.' }; + cancelled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The indicator if the menu was selected or cancelled.' }; + owner: 'mjbvz'; + comment: 'Event used to gain insights into how code actions are being triggered'; + }; - // }); + this._telemetryService.publicLog2('codeAction.applyCodeAction', { + codeActionFrom: openedFromString, + validCodeActions: codeActions.validActions.length, + cancelled: didCancel, - // this._visible = false; - // this._editor.focus(); - // }, - // autoSelectFirstItem: true, - // getKeyBinding: action => action instanceof CodeActionAction ? resolver(action.action) : undefined, - // }); + }); + + this._visible = false; + this._editor.focus(); + }, + autoSelectFirstItem: true, + getKeyBinding: action => action instanceof CodeActionAction ? resolver(action.action) : undefined, + }); } - /** - * - * Comments about menu: - * - * flyout might be too big, not used anywhere else - * - * making the editor editable - * - * better view in the refactor preview pane - * - * should we be showing all the refactor options? should we only show options that are valid, like in the - * lightbulb action - * - * - */ - private getMenuActions( trigger: CodeActionTrigger, actionsToShow: readonly CodeActionItem[], diff --git a/src/vs/editor/contrib/codeAction/browser/media/action.css b/src/vs/editor/contrib/codeAction/browser/media/action.css index bd373765e1d..86b5ac44e48 100644 --- a/src/vs/editor/contrib/codeAction/browser/media/action.css +++ b/src/vs/editor/contrib/codeAction/browser/media/action.css @@ -9,7 +9,7 @@ min-width: 160px; } -.testMenu { +.codeActionMenuWidget { padding: 10px 0px 10px 0px; overflow: auto; font-size: 13px; @@ -26,7 +26,7 @@ background-color: var(--vscode-editorSuggestWidget-background); } -.testMenu .monaco-list:not(.element-focused):focus:before { +.codeActionMenuWidget .monaco-list:not(.element-focused):focus:before { position: absolute; top: 0; left: 0; @@ -41,7 +41,7 @@ outline-offset: 0px; } -.testMenu .monaco-list { +.codeActionMenuWidget .monaco-list { user-select: none; -webkit-user-select: none; -ms-user-select: none; @@ -49,21 +49,21 @@ border-width: 0px !important; } -/* .testMenu .monaco-list:not(.element-focus) { +/* .codeActionMenuWidget .monaco-list:not(.element-focus) { border: none !important; border-width: 0px !important; } */ -.testMenu .monaco-list .monaco-scrollable-element .monaco-list-rows { +.codeActionMenuWidget .monaco-list .monaco-scrollable-element .monaco-list-rows { height: 100% !important; } -.testMenu .monaco-list .monaco-scrollable-element { +.codeActionMenuWidget .monaco-list .monaco-scrollable-element { overflow: visible; } /** Styles for each row in the list element **/ -.testMenu .monaco-list .monaco-list-row:not(.separator) { +.codeActionMenuWidget .monaco-list .monaco-list-row:not(.separator) { display: flex; -mox-box-sizing: border-box; box-sizing: border-box; @@ -78,14 +78,14 @@ -.testMenu .monaco-list .monaco-list-row:hover:not(.option-disabled), -.testMenu .monaco-list .monaco-list-row .focused:not(.option-disabled) { +.codeActionMenuWidget .monaco-list .monaco-list-row:hover:not(.option-disabled), +.codeActionMenuWidget .monaco-list .monaco-list-row .focused:not(.option-disabled) { color: var(--vscode-editorSuggestWidget-selectedForeground); background-color: rgb(4, 57, 94) !important; } -.testMenu .monaco-list .option-disabled, -.testMenu .monaco-list .option-disabled .focused { +.codeActionMenuWidget .monaco-list .option-disabled, +.codeActionMenuWidget .monaco-list .option-disabled .focused { pointer-events: none; -webkit-touch-callout: none; -webkit-user-select: none; @@ -95,7 +95,7 @@ user-select: none; } -.testMenu .monaco-list .separator { +.codeActionMenuWidget .monaco-list .separator { border-bottom: 1px solid var(--vscode-menu-separatorBackground); padding-top: 10px !important; /* padding: 30px; */ From 7111a5daccf6820ffb882e6595849d5a95a6747a Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Mon, 18 Jul 2022 10:19:56 -0700 Subject: [PATCH 022/197] fix from context view service, code cleanup, and working on keys and exp setting --- src/vs/base/browser/ui/list/listWidget.ts | 1 - .../codeAction/browser/codeActionMenu.ts | 102 ++++++++++-------- .../codeAction/browser/codeActionUi.ts | 43 +++++--- .../browser/codeActionWidgetContribution.ts | 25 +++++ .../codeAction/browser/media/action.css | 2 +- .../contextview/browser/contextViewService.ts | 2 +- 6 files changed, 111 insertions(+), 64 deletions(-) create mode 100644 src/vs/editor/contrib/codeAction/browser/codeActionWidgetContribution.ts diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index bdb76b49d09..5a3bbc025f3 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -1800,7 +1800,6 @@ export class List implements ISpliceable, IThemable, IDisposable { } getElementID(index: number): string { - console.log(index); return this.view.getElementDomId(index); } diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 5e8c35488c2..540e332580d 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -16,7 +16,7 @@ import { Lazy } from 'vs/base/common/lazy'; import { Disposable, dispose, MutableDisposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import 'vs/css!./media/action'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions'; +import { EditorCommand, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; @@ -30,6 +30,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -128,7 +129,7 @@ class CodeMenuRenderer implements IListRenderer(); readonly onDidHideContextMenu = this._onDidHideContextMenu.event; private readonly _ctxMenuWidgetIsFocused?: IContextKey; - private readonly _ctxMenuWidgetVisible: IContextKey; + private _ctxMenuWidgetVisible!: IContextKey; private readonly editor: ICodeEditor; public static readonly ID: string = 'editor.contrib.codeActionMenu'; @@ -178,7 +179,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { private readonly _delegate: CodeActionWidgetDelegate, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IContextViewService private readonly _contextViewService: IContextViewService, - @IContextKeyService _contextKeyService: IContextKeyService, + @IContextKeyService private _contextKeyService: IContextKeyService, @IKeybindingService keybindingService: IKeybindingService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @@ -193,11 +194,12 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { getKeybindings: () => keybindingService.getKeybindings() }); - this._ctxMenuWidgetVisible = Context.Visible.bindTo(_contextKeyService); if (this.codeActionList && !this.codeActionList.isDOMFocused()) { this.dispose(); } + + this._ctxMenuWidgetVisible = Context.Visible.bindTo(_contextKeyService); } allowEditorOverflow?: boolean | undefined; @@ -217,8 +219,6 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { if (e.elements.length) { e.elements.forEach(element => { if (element.isEnabled) { - const itemAction = element; - console.log(itemAction); element.action.run(); } }); @@ -241,8 +241,8 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { const height = inputArray.length * 25; renderMenu.style.height = String(height) + 'px'; - renderMenu.id = 'codeActioniMenuWidget'; - renderMenu.classList.add('codeActioniMenuWidget'); + renderMenu.id = 'codeActionMenuWidget'; + renderMenu.classList.add('codeActionMenuWidget'); element.appendChild(renderMenu); @@ -268,11 +268,6 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { this.codeActionList.splice(0, this.codeActionList.length, this.options); this.codeActionList.layout(height); - this.codeActionList.getElementID(2); - - const temp = this.codeActionList.getElementID(0); - console.log(temp); - const arr: number[] = []; this.options.forEach((item, index) => { const element = document.getElementById(this.codeActionList.getElementID(index))?.getElementsByTagName('span')[0].offsetWidth; @@ -285,7 +280,6 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { // resize observer - supports dynamic height but not width this.codeActionList.domFocus(); - this.codeActionList.getHTMLElement().style.border = 'none !important'; this.codeActionList.setFocus([0]); const focusTracker = dom.trackFocus(element); const blurListener = focusTracker.onDidBlur(() => { @@ -295,7 +289,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { renderDisposables.add(blurListener); renderDisposables.add(focusTracker); - + // this._ctxMenuWidgetVisible = Context.Visible.bindTo(this._contextKeyService.createScoped(element)); this._ctxMenuWidgetVisible.set(true); return renderDisposables; @@ -372,46 +366,46 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { }, }, - //this._editor.getDomNode(), if we use shadow dom ( + shadow dom param) + this._editor.getDomNode()!, false, ); // } - this._contextMenuService.showContextMenu({ - domForShadowRoot: useShadowDOM ? this._editor.getDomNode()! : undefined, - getAnchor: () => anchor, - getActions: () => menuActions, - onHide: (didCancel) => { - const openedFromString = (options.fromLightbulb) ? CodeActionTriggerSource.Lightbulb : trigger.triggerAction; + // this._contextMenuService.showContextMenu({ + // domForShadowRoot: useShadowDOM ? this._editor.getDomNode()! : undefined, + // getAnchor: () => anchor, + // getActions: () => menuActions, + // onHide: (didCancel) => { + // const openedFromString = (options.fromLightbulb) ? CodeActionTriggerSource.Lightbulb : trigger.triggerAction; - type ApplyCodeActionEvent = { - codeActionFrom: CodeActionTriggerSource; - validCodeActions: number; - cancelled: boolean; - }; + // type ApplyCodeActionEvent = { + // codeActionFrom: CodeActionTriggerSource; + // validCodeActions: number; + // cancelled: boolean; + // }; - type ApplyCodeEventClassification = { - codeActionFrom: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of action used to opened the code action.' }; - validCodeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The total number of valid actions that are highlighted and can be used.' }; - cancelled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The indicator if the menu was selected or cancelled.' }; - owner: 'mjbvz'; - comment: 'Event used to gain insights into how code actions are being triggered'; - }; + // type ApplyCodeEventClassification = { + // codeActionFrom: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of action used to opened the code action.' }; + // validCodeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The total number of valid actions that are highlighted and can be used.' }; + // cancelled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The indicator if the menu was selected or cancelled.' }; + // owner: 'mjbvz'; + // comment: 'Event used to gain insights into how code actions are being triggered'; + // }; - this._telemetryService.publicLog2('codeAction.applyCodeAction', { - codeActionFrom: openedFromString, - validCodeActions: codeActions.validActions.length, - cancelled: didCancel, + // this._telemetryService.publicLog2('codeAction.applyCodeAction', { + // codeActionFrom: openedFromString, + // validCodeActions: codeActions.validActions.length, + // cancelled: didCancel, - }); + // }); - this._visible = false; - this._editor.focus(); - }, - autoSelectFirstItem: true, - getKeyBinding: action => action instanceof CodeActionAction ? resolver(action.action) : undefined, - }); + // this._visible = false; + // this._editor.focus(); + // }, + // autoSelectFirstItem: true, + // getKeyBinding: action => action instanceof CodeActionAction ? resolver(action.action) : undefined, + // }); } private getMenuActions( @@ -537,6 +531,7 @@ export class CodeActionKeybindingResolver { } } +// registerEditorContribution(CodeActionMenu.ID, CodeActionMenu); const CodeActionCommand = EditorCommand.bindToContribution(CodeActionMenu.get); const weight = KeybindingWeight.EditorContrib + 90; @@ -544,7 +539,9 @@ const weight = KeybindingWeight.EditorContrib + 90; registerEditorCommand(new CodeActionCommand({ id: 'hideCodeActionMenuWidget', precondition: Context.Visible, - handler: x => x.dispose(), + handler(x) { + console.log('hello hi'); + }, kbOpts: { weight: weight, primary: KeyCode.Escape, @@ -552,6 +549,17 @@ registerEditorCommand(new CodeActionCommand({ } })); +/** + * + * need to create a new constructor/new class for the code action menu controller? + * + * + * + * + */ + + + // KeybindingsRegistry.registerCommandAndKeybindingRule({ // id: 'codeActionMenu.selectEditor', // weight: KeybindingWeight.WorkbenchContrib + 1, diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts index 9a5edfb16b7..bcb999c3f10 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts @@ -5,21 +5,24 @@ import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { registerEditorCommand } from 'vs/editor/browser/editorExtensions'; +import { EditorCommand, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IPosition } from 'vs/editor/common/core/position'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { CodeActionTriggerType } from 'vs/editor/common/languages'; import { CodeActionItem, CodeActionSet } from 'vs/editor/contrib/codeAction/browser/codeAction'; import { MessageController } from 'vs/editor/contrib/message/browser/messageController'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { CodeActionMenu, CodeActionShowOptions } from './codeActionMenu'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { CodeActionMenu, CodeActionShowOptions, Context } from './codeActionMenu'; import { CodeActionsState } from './codeActionModel'; import { LightBulbWidget } from './lightBulbWidget'; import { CodeActionAutoApply, CodeActionTrigger } from './types'; -export class CodeActionUi extends Disposable { +export class CodeActionUi extends Disposable implements IEditorContribution { private readonly _codeActionWidget: Lazy; private readonly _lightBulbWidget: Lazy; @@ -27,6 +30,12 @@ export class CodeActionUi extends Disposable { #disposed = false; + public static readonly ID: string = 'editor.contrib.codeActionMenu'; + + static get(editor: ICodeEditor): CodeActionUi | null { + return editor.getContribution(CodeActionUi.ID); + } + constructor( private readonly _editor: ICodeEditor, quickFixActionId: string, @@ -160,14 +169,20 @@ export class CodeActionUi extends Disposable { } } -// registerEditorCommand(new SuggestCommand({ -// id: 'hideSuggestWidget', -// precondition: SuggestContext.Visible, -// handler: x => x.cancelSuggestWidget(), -// kbOpts: { -// weight: weight, -// kbExpr: EditorContextKeys.textInputFocus, -// primary: KeyCode.Escape, -// secondary: [KeyMod.Shift | KeyCode.Escape] -// } -// })); +// registerEditorContribution(CodeActionUi.ID, CodeActionUi); +const CodeActionCommand = EditorCommand.bindToContribution(CodeActionUi.get); + +const weight = KeybindingWeight.EditorContrib + 90; + +registerEditorCommand(new CodeActionCommand({ + id: 'hideCodeActionMenuWidget-fromUI', + precondition: Context.Visible, + handler(x) { + console.log('hello hi'); + }, + kbOpts: { + weight: weight, + primary: KeyCode.Escape, + secondary: [KeyMod.Shift | KeyCode.Escape] + } +})); diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionWidgetContribution.ts b/src/vs/editor/contrib/codeAction/browser/codeActionWidgetContribution.ts new file mode 100644 index 00000000000..10f90781dea --- /dev/null +++ b/src/vs/editor/contrib/codeAction/browser/codeActionWidgetContribution.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { editorConfigurationBaseNode } from 'vs/editor/common/config/editorConfigurationSchema'; +import { CodeActionMenu } from 'vs/editor/contrib/codeAction/browser/codeActionMenu'; +import * as nls from 'vs/nls'; +import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; + +registerEditorContribution(CodeActionMenu.ID, CodeActionMenu); + +Registry.as(Extensions.Configuration).registerConfiguration({ + ...editorConfigurationBaseNode, + properties: { + 'editor.experimental.codeActionWidget.enabled': { + type: 'boolean', + scope: ConfigurationScope.LANGUAGE_OVERRIDABLE, + description: nls.localize('codeActionWidget', "Enable/disable opening the experimental Code Action Widget."), + default: false, + }, + } +}); diff --git a/src/vs/editor/contrib/codeAction/browser/media/action.css b/src/vs/editor/contrib/codeAction/browser/media/action.css index 86b5ac44e48..27863688115 100644 --- a/src/vs/editor/contrib/codeAction/browser/media/action.css +++ b/src/vs/editor/contrib/codeAction/browser/media/action.css @@ -79,7 +79,7 @@ .codeActionMenuWidget .monaco-list .monaco-list-row:hover:not(.option-disabled), -.codeActionMenuWidget .monaco-list .monaco-list-row .focused:not(.option-disabled) { +.codeActionMenuWidget .monaco-list .moncao-list-row.focused:not(.option-disabled) { color: var(--vscode-editorSuggestWidget-selectedForeground); background-color: rgb(4, 57, 94) !important; } diff --git a/src/vs/platform/contextview/browser/contextViewService.ts b/src/vs/platform/contextview/browser/contextViewService.ts index 57be989bcc4..00b107b0921 100644 --- a/src/vs/platform/contextview/browser/contextViewService.ts +++ b/src/vs/platform/contextview/browser/contextViewService.ts @@ -35,7 +35,7 @@ export class ContextViewService extends Disposable implements IContextViewServic showContextView(delegate: IContextViewDelegate, container?: HTMLElement, shadowRoot?: boolean): IDisposable { if (container) { - if (container !== this.container) { + if (container !== this.container || true) { this.container = container; this.setContainer(container, shadowRoot ? ContextViewDOMPosition.FIXED_SHADOW : ContextViewDOMPosition.FIXED); } From 8846ac54887deb74afdb262cbdaecdd12b824f98 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 18 Jul 2022 10:37:48 -0700 Subject: [PATCH 023/197] Check if execution values are true, delay registered ctx key Another attempt at #155348 which got reverted Fixes #155336 Part of #155227 --- .../workbench/contrib/tasks/browser/abstractTaskService.ts | 7 ++++--- .../workbench/contrib/tasks/browser/task.contribution.ts | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 4bfc497f05a..c123a941880 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -293,7 +293,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer })); this._taskRunningState = TASK_RUNNING_STATE.bindTo(_contextKeyService); this._onDidStateChange = this._register(new Emitter()); - this._registerCommands(); + this._registerCommands().then(() => { + TaskCommandsRegistered.bindTo(this._contextKeyService).set(true); + }); this._configurationResolverService.contributeVariable('defaultBuildTask', async (): Promise => { let tasks = await this._getTasksForGroup(TaskGroup.Build); if (tasks.length > 0) { @@ -491,7 +493,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this._openTaskFile(resource, TaskSourceKind.WorkspaceFile); } }); - TaskCommandsRegistered.bindTo(this._contextKeyService).set(true); } private get workspaceFolders(): IWorkspaceFolder[] { @@ -2173,7 +2174,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } private get _jsonTasksSupported(): boolean { - return !!ShellExecutionSupportedContext.getValue(this._contextKeyService) && !!ProcessExecutionSupportedContext.getValue(this._contextKeyService); + return ShellExecutionSupportedContext.getValue(this._contextKeyService) === true && ProcessExecutionSupportedContext.getValue(this._contextKeyService) === true; } private _computeWorkspaceFolderTasks(workspaceFolder: IWorkspaceFolder, runSource: TaskRunSource = TaskRunSource.User): Promise { diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index 3b7aa6162af..9b5df9014e5 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -40,7 +40,7 @@ import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDe import { TerminalMenuBarGroup } from 'vs/workbench/contrib/terminal/browser/terminalMenus'; import { isString } from 'vs/base/common/types'; -const SHOW_TASKS_COMMANDS_CONTEXT = ContextKeyExpr.or(ShellExecutionSupportedContext, ProcessExecutionSupportedContext); +const SHOW_TASKS_COMMANDS_CONTEXT = ContextKeyExpr.and(ShellExecutionSupportedContext, ProcessExecutionSupportedContext); const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(RunAutomaticTasks, LifecyclePhase.Eventually); From 6cf7c9ab57597a1cf120f843573eab61216855b5 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Mon, 18 Jul 2022 11:19:58 -0700 Subject: [PATCH 024/197] keybindings working! --- .../codeAction/browser/codeActionCommands.ts | 24 ++++++++++++++- .../codeAction/browser/codeActionMenu.ts | 24 +++------------ .../codeAction/browser/codeActionUi.ts | 29 ++++--------------- .../browser/codeActionWidgetContribution.ts | 2 -- 4 files changed, 32 insertions(+), 47 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts index 408079626e5..14c476643ce 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts @@ -11,7 +11,7 @@ import { Lazy } from 'vs/base/common/lazy'; import { Disposable } from 'vs/base/common/lifecycle'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, EditorCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorAction, EditorCommand, registerEditorCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { IBulkEditService, ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; import { IPosition } from 'vs/editor/common/core/position'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; @@ -32,6 +32,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CodeActionModel, CodeActionsState, SUPPORTED_CODE_ACTIONS } from './codeActionModel'; import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionFilter, CodeActionKind, CodeActionTrigger, CodeActionTriggerSource } from './types'; +import { Context } from 'vs/editor/contrib/codeAction/browser/codeActionMenu'; function contextKeyForSupportedActions(kind: CodeActionKind) { return ContextKeyExpr.regex( @@ -132,6 +133,10 @@ export class QuickFixController extends Disposable implements IEditorContributio this._ui.getValue().update(newState); } + public hideCodeActionMenu() { + this._ui.getValue().hideCodeActionWidget(); + } + public showCodeActions(trigger: CodeActionTrigger, actions: CodeActionSet, at: IAnchor | IPosition) { return this._ui.getValue().showCodeActionList(trigger, actions, at, { includeDisabledActions: false, fromLightbulb: false }); } @@ -490,3 +495,20 @@ export class AutoFixAction extends EditorAction { CodeActionAutoApply.IfSingle, undefined, CodeActionTriggerSource.AutoFix); } } + +const CodeActionContribution = EditorCommand.bindToContribution(QuickFixController.get); + +const weight = KeybindingWeight.EditorContrib + 90; + +registerEditorCommand(new CodeActionContribution({ + id: 'hideCodeActionMenuWidget', + precondition: Context.Visible, + handler(x) { + x.hideCodeActionMenu(); + }, + kbOpts: { + weight: weight, + primary: KeyCode.Escape, + secondary: [KeyMod.Shift | KeyCode.Escape] + } +})); diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 540e332580d..146716d162b 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -24,6 +24,7 @@ import { CodeAction, Command } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { codeActionCommandId, CodeActionItem, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/browser/codeAction'; +import { QuickFixController } from 'vs/editor/contrib/codeAction/browser/codeActionCommands'; import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionKind, CodeActionTrigger, CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/browser/types'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -310,6 +311,9 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { public async show(trigger: CodeActionTrigger, codeActions: CodeActionSet, at: IAnchor | IPosition, options: CodeActionShowOptions): Promise { const model = this.editor.getModel(); + if (!model) { + return; + } const actionsToShow = options.includeDisabledActions ? codeActions.allActions : codeActions.validActions; if (!actionsToShow.length) { this._visible = false; @@ -532,30 +536,10 @@ export class CodeActionKeybindingResolver { } // registerEditorContribution(CodeActionMenu.ID, CodeActionMenu); -const CodeActionCommand = EditorCommand.bindToContribution(CodeActionMenu.get); -const weight = KeybindingWeight.EditorContrib + 90; - -registerEditorCommand(new CodeActionCommand({ - id: 'hideCodeActionMenuWidget', - precondition: Context.Visible, - handler(x) { - console.log('hello hi'); - }, - kbOpts: { - weight: weight, - primary: KeyCode.Escape, - secondary: [KeyMod.Shift | KeyCode.Escape] - } -})); /** - * * need to create a new constructor/new class for the code action menu controller? - * - * - * - * */ diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts index bcb999c3f10..97defcbb932 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts @@ -30,12 +30,6 @@ export class CodeActionUi extends Disposable implements IEditorContribution { #disposed = false; - public static readonly ID: string = 'editor.contrib.codeActionMenu'; - - static get(editor: ICodeEditor): CodeActionUi | null { - return editor.getContribution(CodeActionUi.ID); - } - constructor( private readonly _editor: ICodeEditor, quickFixActionId: string, @@ -65,6 +59,11 @@ export class CodeActionUi extends Disposable implements IEditorContribution { override dispose() { this.#disposed = true; super.dispose(); + + } + + public hideCodeActionWidget() { + this._codeActionWidget.getValue().dispose(); } public async update(newState: CodeActionsState.State): Promise { @@ -168,21 +167,3 @@ export class CodeActionUi extends Disposable implements IEditorContribution { this._codeActionWidget.getValue().show(trigger, actions, at, options); } } - -// registerEditorContribution(CodeActionUi.ID, CodeActionUi); -const CodeActionCommand = EditorCommand.bindToContribution(CodeActionUi.get); - -const weight = KeybindingWeight.EditorContrib + 90; - -registerEditorCommand(new CodeActionCommand({ - id: 'hideCodeActionMenuWidget-fromUI', - precondition: Context.Visible, - handler(x) { - console.log('hello hi'); - }, - kbOpts: { - weight: weight, - primary: KeyCode.Escape, - secondary: [KeyMod.Shift | KeyCode.Escape] - } -})); diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionWidgetContribution.ts b/src/vs/editor/contrib/codeAction/browser/codeActionWidgetContribution.ts index 10f90781dea..86c88df9597 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionWidgetContribution.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionWidgetContribution.ts @@ -10,8 +10,6 @@ import * as nls from 'vs/nls'; import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; -registerEditorContribution(CodeActionMenu.ID, CodeActionMenu); - Registry.as(Extensions.Configuration).registerConfiguration({ ...editorConfigurationBaseNode, properties: { From c6d5dcf8f5b2c6e7b3befbbac70641c611544bda Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 18 Jul 2022 11:49:30 -0700 Subject: [PATCH 025/197] Respect wrapped lines when copying command output Fixes #155402 --- .../common/capabilities/commandDetectionCapability.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index 7194815abb1..f4b242a0c69 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -11,7 +11,7 @@ import { ICommandDetectionCapability, TerminalCapability, ITerminalCommand, IHan import { ISerializedCommand, ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/terminalProcess'; // Importing types is safe in any layer // eslint-disable-next-line code-import-patterns -import type { IBuffer, IDisposable, IMarker, Terminal } from 'xterm-headless'; +import type { IBuffer, IBufferLine, IDisposable, IMarker, Terminal } from 'xterm-headless'; export interface ICurrentPartialCommand { previousCommandMarker?: IMarker; @@ -601,8 +601,13 @@ function getOutputForCommand(executedMarker: IMarker | undefined, endMarker: IMa return undefined; } let output = ''; + let line: IBufferLine | undefined; for (let i = startLine; i < endLine; i++) { - output += buffer.getLine(i)?.translateToString(true) + '\n'; + line = buffer.getLine(i); + if (!line) { + continue; + } + output += line.translateToString(!line.isWrapped) + (line.isWrapped ? '' : '\n'); } return output === '' ? undefined : output; } From 94ef2403f04d7963a439a58e8a053f5bba0623e2 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Mon, 18 Jul 2022 13:04:07 -0700 Subject: [PATCH 026/197] fix on arrows --- .../codeAction/browser/codeActionCommands.ts | 32 ++++++++ .../codeAction/browser/codeActionMenu.ts | 77 ++++++++++++++++--- .../codeAction/browser/codeActionUi.ts | 9 +++ 3 files changed, 107 insertions(+), 11 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts index 14c476643ce..f11666f366f 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts @@ -137,6 +137,10 @@ export class QuickFixController extends Disposable implements IEditorContributio this._ui.getValue().hideCodeActionWidget(); } + public navigateCodeActionList(navUp: Boolean) { + this._ui.getValue().navigateList(navUp); + } + public showCodeActions(trigger: CodeActionTrigger, actions: CodeActionSet, at: IAnchor | IPosition) { return this._ui.getValue().showCodeActionList(trigger, actions, at, { includeDisabledActions: false, fromLightbulb: false }); } @@ -512,3 +516,31 @@ registerEditorCommand(new CodeActionContribution({ secondary: [KeyMod.Shift | KeyCode.Escape] } })); + +registerEditorCommand(new CodeActionContribution({ + id: 'navigatePrevious', + precondition: Context.Visible, + handler(x) { + x.navigateCodeActionList(true); + }, + kbOpts: { + weight: weight + 100000, + primary: KeyCode.KeyH, + // secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow], + } +})); + +registerEditorCommand(new CodeActionContribution({ + id: 'navigateNext', + precondition: Context.Visible, + handler(x) { + x.navigateCodeActionList(false); + }, + kbOpts: { + weight: weight + 100000, + primary: KeyCode.KeyK, + // secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow], + } +})); + + diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 146716d162b..6213805cc74 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -85,7 +85,8 @@ export interface ICodeActionMenuItem { action: IAction; decoratorRight?: string; isSeparator?: boolean; - isEnabled?: boolean; + isEnabled: boolean; + index: number; disposables?: IDisposable[]; } @@ -165,6 +166,8 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { private readonly _ctxMenuWidgetIsFocused?: IContextKey; private _ctxMenuWidgetVisible!: IContextKey; private readonly editor: ICodeEditor; + private viewItems: ICodeActionMenuItem[] = []; + private focusedItem!: number | undefined; public static readonly ID: string = 'editor.contrib.codeActionMenu'; @@ -263,7 +266,11 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { } inputArray.forEach((item, index) => { - this.options.push({ title: item.label, detail: item.tooltip, action: inputArray[index], isEnabled: item.enabled, isSeparator: item.class === 'separator' }); + const menuItem = { title: item.label, detail: item.tooltip, action: inputArray[index], isEnabled: item.enabled, isSeparator: item.class === 'separator', index }; + if (item.enabled) { + this.viewItems.push(menuItem); + } + this.options.push(menuItem); }); this.codeActionList.splice(0, this.codeActionList.length, this.options); @@ -281,10 +288,9 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { // resize observer - supports dynamic height but not width this.codeActionList.domFocus(); - this.codeActionList.setFocus([0]); const focusTracker = dom.trackFocus(element); const blurListener = focusTracker.onDidBlur(() => { - this.dispose(); + this.hideCodeActionWidget(); this._contextViewService.hideContextView({ source: this }); }); @@ -293,22 +299,71 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { // this._ctxMenuWidgetVisible = Context.Visible.bindTo(this._contextKeyService.createScoped(element)); this._ctxMenuWidgetVisible.set(true); return renderDisposables; - } - arrowOnAvailableItems() { - this.codeActionList.setFocus([0]); - this.codeActionList.setSelection([0]); + protected focusPrevious(forceLoop?: Boolean) { + if (typeof this.focusedItem === 'undefined') { + this.focusedItem = this.viewItems[0].index; + } else if (this.viewItems.length <= 1) { + return false; + } + + const startIndex = this.focusedItem; + let item: ICodeActionMenuItem; + + do { + this.focusedItem = this.focusedItem - 1; + if (this.focusedItem < 0) { + this.focusedItem = this.viewItems.length - 1; + } + item = this.viewItems[this.focusedItem]; + this.codeActionList.setFocus([item.index]); + } while (this.focusedItem !== startIndex && ((!item.isEnabled) || item.action.id === Separator.ID)); + + return true; + } + + protected focusNext(forceLoop?: Boolean) { + if (typeof this.focusedItem === 'undefined') { + this.focusedItem = this.viewItems.length - 1; + } else if (this.viewItems.length <= 1) { + return false; + } + + const startIndex = this.focusedItem; + let item: ICodeActionMenuItem; + + do { + this.focusedItem = (this.focusedItem + 1) % this.viewItems.length; + item = this.viewItems[this.focusedItem]; + this.codeActionList.setFocus([item.index]); + } while (this.focusedItem !== startIndex && ((!item.isEnabled) || item.action.id === Separator.ID)); + + return true; + } + + public navigateListWithKeysUp() { + this.focusPrevious(); + } + + public navigateListWithKeysDown() { + this.focusNext(); } override dispose() { - this._ctxMenuWidgetVisible.reset(); this.codeActionList.dispose(); - this.options = []; - this._contextViewService.hideContextView(); this._disposables.dispose(); } + hideCodeActionWidget() { + this._ctxMenuWidgetVisible.reset(); + this.options = []; + this.viewItems = []; + this.focusedItem = undefined; + this._contextViewService.hideContextView(); + this.dispose(); + } + public async show(trigger: CodeActionTrigger, codeActions: CodeActionSet, at: IAnchor | IPosition, options: CodeActionShowOptions): Promise { const model = this.editor.getModel(); if (!model) { diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts index 97defcbb932..b0086cf99ec 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts @@ -66,6 +66,15 @@ export class CodeActionUi extends Disposable implements IEditorContribution { this._codeActionWidget.getValue().dispose(); } + public navigateList(navUp: Boolean) { + if (navUp) { + this._codeActionWidget.getValue().navigateListWithKeysUp(); + } else { + this._codeActionWidget.getValue().navigateListWithKeysDown(); + } + + } + public async update(newState: CodeActionsState.State): Promise { if (newState.type !== CodeActionsState.Type.Triggered) { this._lightBulbWidget.rawValue?.hide(); From 750491ef1ba62da519221460b6472106cbc20c5a Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Mon, 18 Jul 2022 13:53:25 -0700 Subject: [PATCH 027/197] 99% working widget! --- .../editor/contrib/codeAction/browser/codeActionCommands.ts | 4 ++-- src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts index f11666f366f..330bc3a8d4b 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts @@ -525,7 +525,7 @@ registerEditorCommand(new CodeActionContribution({ }, kbOpts: { weight: weight + 100000, - primary: KeyCode.KeyH, + primary: KeyCode.UpArrow, // secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow], } })); @@ -538,7 +538,7 @@ registerEditorCommand(new CodeActionContribution({ }, kbOpts: { weight: weight + 100000, - primary: KeyCode.KeyK, + primary: KeyCode.DownArrow, // secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow], } })); diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 6213805cc74..55e66c8b4c2 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -257,7 +257,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { getTemplateId(element) { return 'codeActionWidget'; } - }, [this.listRenderer], + }, [this.listRenderer], { keyboardSupport: false } ); if (this.codeActionList) { @@ -288,6 +288,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { // resize observer - supports dynamic height but not width this.codeActionList.domFocus(); + this.codeActionList.setFocus([this.viewItems[0].index]); const focusTracker = dom.trackFocus(element); const blurListener = focusTracker.onDidBlur(() => { this.hideCodeActionWidget(); From e6700900174e4aab7434a47919b94689700cc5b2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Jul 2022 11:27:55 +0200 Subject: [PATCH 028/197] macOS - tweak traffic light position handling (#155558) //cc @jrieken --- src/vs/platform/windows/electron-main/window.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts index a9eb659f467..85fc9e0c27e 100644 --- a/src/vs/platform/windows/electron-main/window.ts +++ b/src/vs/platform/windows/electron-main/window.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { app, BrowserWindow, BrowserWindowConstructorOptions, Display, Event, nativeImage, NativeImage, Rectangle, screen, SegmentedControlSegment, systemPreferences, TouchBar, TouchBarSegmentedControl } from 'electron'; +import { app, BrowserWindow, BrowserWindowConstructorOptions, Display, Event, nativeImage, NativeImage, Point, Rectangle, screen, SegmentedControlSegment, systemPreferences, TouchBar, TouchBarSegmentedControl } from 'electron'; import { RunOnceScheduler } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -141,6 +141,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { private documentEdited: boolean | undefined; private customTrafficLightPosition: boolean | undefined; + private defaultTrafficLightPosition: Point | undefined; private readonly whenReadyCallbacks: { (window: ICodeWindow): void }[] = []; @@ -1325,9 +1326,14 @@ export class CodeWindow extends Disposable implements ICodeWindow { const useCustomTrafficLightPosition = this.configurationService.getValue(commandCenterSettingKey); if (useCustomTrafficLightPosition) { - this._win.setTrafficLightPosition({ x: 7, y: 9 }); + if (!this.defaultTrafficLightPosition) { + this.defaultTrafficLightPosition = this._win.getTrafficLightPosition(); // remember default to restore later + } + this._win.setTrafficLightPosition({ x: 7, y: 10 }); } else { - this._win.setTrafficLightPosition({ x: 7, y: 6 }); + if (this.defaultTrafficLightPosition) { + this._win.setTrafficLightPosition(this.defaultTrafficLightPosition); + } } this.customTrafficLightPosition = useCustomTrafficLightPosition; From a8444a04712ff1bf9b83d1670f2e78733c327f3b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Jul 2022 11:28:51 +0200 Subject: [PATCH 029/197] telemetry - document keys (#155563) --- src/vs/base/common/actions.ts | 4 +- .../browser/telemetry.contribution.ts | 50 +++++++++++-------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index f9729a18770..cfaa9389600 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -15,8 +15,8 @@ export interface ITelemetryData { export type WorkbenchActionExecutedClassification = { owner: 'bpasero'; - id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - from: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the action that was run.' }; + from: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the component the action was run from.' }; }; export type WorkbenchActionExecutedEvent = { diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index 674741ed4d0..40346309f5e 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -36,11 +36,11 @@ type TelemetryData = { }; type FileTelemetryDataFragment = { - mimeType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - path: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - reason?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; - allowlistedjson?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + mimeType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The language type of the file (for example XML).' }; + ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The file extension of the file (for example xml).' }; + path: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The path of the file as a hash.' }; + reason?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The reason why a file is read or written. Allows to e.g. distinguish auto save from normal save.' }; + allowlistedjson?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the file but only if it matches some well known file names such as package.json or tsconfig.json.' }; }; export class TelemetryContribution extends Disposable implements IWorkbenchContribution { @@ -67,27 +67,29 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr const activeViewlet = paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar); type WindowSizeFragment = { - innerHeight: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; - innerWidth: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; - outerHeight: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; - outerWidth: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; + innerHeight: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The height of the current window.' }; + innerWidth: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The width of the current window.' }; + outerHeight: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The height of the current window with all decoration removed.' }; + outerWidth: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The width of the current window with all decoration removed.' }; + comment: 'The size of the window.'; }; type WorkspaceLoadClassification = { owner: 'bpasero'; - userAgent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - emptyWorkbench: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; + userAgent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The user agent as reported by `navigator.userAgent` by Electron or the web browser.' }; + emptyWorkbench: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether a folder or workspace is opened or not.' }; windowSize: WindowSizeFragment; - 'workbench.filesToOpenOrCreate': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; - 'workbench.filesToDiff': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; - 'workbench.filesToMerge': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; + 'workbench.filesToOpenOrCreate': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Number of files that should open or be created.' }; + 'workbench.filesToDiff': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Number of files that should be compared.' }; + 'workbench.filesToMerge': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Number of files that should be merged.' }; customKeybindingsCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; - theme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - language: { classification: 'SystemMetaData'; purpose: 'BusinessInsight' }; - pinnedViewlets: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - restoredViewlet?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - restoredEditors: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; - startupKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; + theme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The current theme of the window.' }; + language: { classification: 'SystemMetaData'; purpose: 'BusinessInsight'; comment: 'The display language of the window.' }; + pinnedViewlets: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifiers of views that are pinned.' }; + restoredViewlet?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the view that is restored.' }; + restoredEditors: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of editors that restored.' }; + startupKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'How the window was opened, e.g via reload or not.' }; + comment: 'Metadata around the workspace that is being loaded into a window.'; }; type WorkspaceLoadEvent = { @@ -141,13 +143,15 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr if (settingsType) { type SettingsReadClassification = { owner: 'bpasero'; - settingsType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + settingsType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The type of the settings file that was read.' }; + comment: 'Track when a settings file was read, for example from an editor.'; }; 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 = { owner: 'bpasero'; + comment: 'Track when a file was read, for example from an editor.'; } & FileTelemetryDataFragment; this.telemetryService.publicLog2('fileGet', this.getTelemetryData(e.model.resource, e.reason)); @@ -159,12 +163,14 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr if (settingsType) { type SettingsWrittenClassification = { owner: 'bpasero'; - settingsType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + settingsType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The type of the settings file that was written to.' }; + comment: 'Track when a settings file was written to, for example from an editor.'; }; 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 = { owner: 'bpasero'; + comment: 'Track when a file was written to, for example from an editor.'; } & FileTelemetryDataFragment; this.telemetryService.publicLog2('filePUT', this.getTelemetryData(e.model.resource, e.reason)); } From 6a4e5cc26b29359472378c2a8951c33f4ea73244 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 19 Jul 2022 11:52:21 +0200 Subject: [PATCH 030/197] Button - fix style of the disabled dropdown button (#155581) Another attempt to fix style of the disabled dropdown button --- src/vs/base/browser/ui/button/button.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/button/button.css b/src/vs/base/browser/ui/button/button.css index f5c80d09184..98a1c8aadfa 100644 --- a/src/vs/base/browser/ui/button/button.css +++ b/src/vs/base/browser/ui/button/button.css @@ -49,7 +49,7 @@ .monaco-button-dropdown.disabled > .monaco-button.disabled, .monaco-button-dropdown.disabled > .monaco-button.disabled:focus, .monaco-button-dropdown.disabled > .monaco-button-dropdown-separator { - opacity: 0.4; + opacity: 0.4 !important; } .monaco-button-dropdown .monaco-button-dropdown-separator { From aba7e6c58bb996cd23fcad410b651ca036d45a07 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Jul 2022 14:17:20 +0200 Subject: [PATCH 031/197] speed up more unit tests (#149712) (#155576) * undo change to split up tests * faster node.js disk i/o tests * skip slow one --- src/vs/base/test/node/pfs/pfs.test.ts | 29 +++-- .../electron-main/backupMainService.test.ts | 114 ++---------------- .../files/test/node/diskFileService.test.ts | 21 ++-- .../test/browser/storageService.test.ts | 2 +- 4 files changed, 40 insertions(+), 126 deletions(-) diff --git a/src/vs/base/test/node/pfs/pfs.test.ts b/src/vs/base/test/node/pfs/pfs.test.ts index 4c15c3ce143..0f97f800ec6 100644 --- a/src/vs/base/test/node/pfs/pfs.test.ts +++ b/src/vs/base/test/node/pfs/pfs.test.ts @@ -21,18 +21,13 @@ flakySuite('PFS', function () { let testDir: string; setup(() => { - configureFlushOnWrite(true); // but enable flushing for the purpose of these tests testDir = getRandomTestPath(tmpdir(), 'vsctests', 'pfs'); return Promises.mkdir(testDir, { recursive: true }); }); - teardown(async () => { - try { - await Promises.rm(testDir); - } finally { - configureFlushOnWrite(false); - } + teardown(() => { + return Promises.rm(testDir); }); test('writeFile', async () => { @@ -375,24 +370,36 @@ flakySuite('PFS', function () { const smallData = 'Hello World'; const bigData = (new Array(100 * 1024)).join('Large String\n'); - return testWriteFileAndFlush(smallData, smallData, bigData, bigData); + return testWriteFile(smallData, smallData, bigData, bigData); + }); + + test('writeFile (string) - flush on write', async () => { + configureFlushOnWrite(true); + try { + const smallData = 'Hello World'; + const bigData = (new Array(100 * 1024)).join('Large String\n'); + + return await testWriteFile(smallData, smallData, bigData, bigData); + } finally { + configureFlushOnWrite(false); + } }); test('writeFile (Buffer)', async () => { const smallData = 'Hello World'; const bigData = (new Array(100 * 1024)).join('Large String\n'); - return testWriteFileAndFlush(Buffer.from(smallData), smallData, Buffer.from(bigData), bigData); + return testWriteFile(Buffer.from(smallData), smallData, Buffer.from(bigData), bigData); }); test('writeFile (UInt8Array)', async () => { const smallData = 'Hello World'; const bigData = (new Array(100 * 1024)).join('Large String\n'); - return testWriteFileAndFlush(VSBuffer.fromString(smallData).buffer, smallData, VSBuffer.fromString(bigData).buffer, bigData); + return testWriteFile(VSBuffer.fromString(smallData).buffer, smallData, VSBuffer.fromString(bigData).buffer, bigData); }); - async function testWriteFileAndFlush( + async function testWriteFile( smallData: string | Buffer | Uint8Array, smallDataValue: string, bigData: string | Buffer | Uint8Array, diff --git a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts index b1800765cb0..f522354dca8 100644 --- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts +++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts @@ -140,16 +140,13 @@ flakySuite('BackupMainService', () => { return pfs.Promises.rm(testDir); }); - test('service validates backup workspaces on startup and cleans up (folder workspaces) (1)', async function () { + test('service validates backup workspaces on startup and cleans up (folder workspaces)', async function () { // 1) backup workspace path does not exist service.registerFolderBackupSync(toFolderBackupInfo(fooFile)); service.registerFolderBackupSync(toFolderBackupInfo(barFile)); await service.initialize(); assertEqualFolderInfos(service.getFolderBackupPaths(), []); - }); - - test('service validates backup workspaces on startup and cleans up (folder workspaces) (2)', async function () { // 2) backup workspace path exists with empty contents within fs.mkdirSync(service.toBackupPath(fooFile)); @@ -160,9 +157,6 @@ flakySuite('BackupMainService', () => { assertEqualFolderInfos(service.getFolderBackupPaths(), []); assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); assert.ok(!fs.existsSync(service.toBackupPath(barFile))); - }); - - test('service validates backup workspaces on startup and cleans up (folder workspaces) (3)', async function () { // 3) backup workspace path exists with empty folders within fs.mkdirSync(service.toBackupPath(fooFile)); @@ -175,9 +169,6 @@ flakySuite('BackupMainService', () => { assertEqualFolderInfos(service.getFolderBackupPaths(), []); assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); assert.ok(!fs.existsSync(service.toBackupPath(barFile))); - }); - - test('service validates backup workspaces on startup and cleans up (folder workspaces) (4)', async function () { // 4) backup workspace path points to a workspace that no longer exists // so it should convert the backup worspace to an empty workspace backup @@ -194,16 +185,13 @@ flakySuite('BackupMainService', () => { assert.strictEqual(service.getEmptyWindowBackupPaths().length, 1); }); - test('service validates backup workspaces on startup and cleans up (root workspaces) (1)', async function () { + test('service validates backup workspaces on startup and cleans up (root workspaces)', async function () { // 1) backup workspace path does not exist service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath)); await service.initialize(); assert.deepStrictEqual(service.getWorkspaceBackups(), []); - }); - - test('service validates backup workspaces on startup and cleans up (root workspaces) (2)', async function () { // 2) backup workspace path exists with empty contents within fs.mkdirSync(service.toBackupPath(fooFile)); @@ -214,9 +202,6 @@ flakySuite('BackupMainService', () => { assert.deepStrictEqual(service.getWorkspaceBackups(), []); assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); assert.ok(!fs.existsSync(service.toBackupPath(barFile))); - }); - - test('service validates backup workspaces on startup and cleans up (root workspaces) (3)', async function () { // 3) backup workspace path exists with empty folders within fs.mkdirSync(service.toBackupPath(fooFile)); @@ -229,9 +214,6 @@ flakySuite('BackupMainService', () => { assert.deepStrictEqual(service.getWorkspaceBackups(), []); assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); assert.ok(!fs.existsSync(service.toBackupPath(barFile))); - }); - - test('service validates backup workspaces on startup and cleans up (root workspaces) (4)', async function () { // 4) backup workspace path points to a workspace that no longer exists // so it should convert the backup worspace to an empty workspace backup @@ -291,19 +273,13 @@ flakySuite('BackupMainService', () => { assertEqualFolderInfos(service.getFolderBackupPaths(), []); }); - test('getFolderBackupPaths() should return [] when workspaces.json is not properly formed JSON (1)', async () => { + test('getFolderBackupPaths() should return [] when workspaces.json is not properly formed JSON', async () => { fs.writeFileSync(backupWorkspacesPath, ''); await service.initialize(); assertEqualFolderInfos(service.getFolderBackupPaths(), []); - }); - - test('getFolderBackupPaths() should return [] when workspaces.json is not properly formed JSON (2)', async () => { fs.writeFileSync(backupWorkspacesPath, '{]'); await service.initialize(); assertEqualFolderInfos(service.getFolderBackupPaths(), []); - }); - - test('getFolderBackupPaths() should return [] when workspaces.json is not properly formed JSON (3)', async () => { fs.writeFileSync(backupWorkspacesPath, 'foo'); await service.initialize(); assertEqualFolderInfos(service.getFolderBackupPaths(), []); @@ -315,37 +291,22 @@ flakySuite('BackupMainService', () => { assertEqualFolderInfos(service.getFolderBackupPaths(), []); }); - test('getFolderBackupPaths() should return [] when folderWorkspaceInfos in workspaces.json is not a string array (1)', async () => { + test('getFolderBackupPaths() should return [] when folderWorkspaceInfos in workspaces.json is not a string array', async () => { fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaceInfos":{}}'); await service.initialize(); assertEqualFolderInfos(service.getFolderBackupPaths(), []); - }); - - test('getFolderBackupPaths() should return [] when folderWorkspaceInfos in workspaces.json is not a string array (2)', async () => { fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaceInfos":{"foo": ["bar"]}}'); await service.initialize(); assertEqualFolderInfos(service.getFolderBackupPaths(), []); - }); - - test('getFolderBackupPaths() should return [] when folderWorkspaceInfos in workspaces.json is not a string array (3)', async () => { fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaceInfos":{"foo": []}}'); await service.initialize(); assertEqualFolderInfos(service.getFolderBackupPaths(), []); - }); - - test('getFolderBackupPaths() should return [] when folderWorkspaceInfos in workspaces.json is not a string array (4)', async () => { fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaceInfos":{"foo": "bar"}}'); await service.initialize(); assertEqualFolderInfos(service.getFolderBackupPaths(), []); - }); - - test('getFolderBackupPaths() should return [] when folderWorkspaceInfos in workspaces.json is not a string array (5)', async () => { fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaceInfos":"foo"}'); await service.initialize(); assertEqualFolderInfos(service.getFolderBackupPaths(), []); - }); - - test('getFolderBackupPaths() should return [] when folderWorkspaceInfos in workspaces.json is not a string array (6)', async () => { fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaceInfos":1}'); await service.initialize(); assertEqualFolderInfos(service.getFolderBackupPaths(), []); @@ -372,19 +333,13 @@ flakySuite('BackupMainService', () => { assert.deepStrictEqual(service.getWorkspaceBackups(), []); }); - test('getWorkspaceBackups() should return [] when workspaces.json is not properly formed JSON (1)', async () => { + test('getWorkspaceBackups() should return [] when workspaces.json is not properly formed JSON', async () => { fs.writeFileSync(backupWorkspacesPath, ''); await service.initialize(); assert.deepStrictEqual(service.getWorkspaceBackups(), []); - }); - - test('getWorkspaceBackups() should return [] when workspaces.json is not properly formed JSON (2)', async () => { fs.writeFileSync(backupWorkspacesPath, '{]'); await service.initialize(); assert.deepStrictEqual(service.getWorkspaceBackups(), []); - }); - - test('getWorkspaceBackups() should return [] when workspaces.json is not properly formed JSON (3)', async () => { fs.writeFileSync(backupWorkspacesPath, 'foo'); await service.initialize(); assert.deepStrictEqual(service.getWorkspaceBackups(), []); @@ -396,73 +351,43 @@ flakySuite('BackupMainService', () => { assert.deepStrictEqual(service.getWorkspaceBackups(), []); }); - test('getWorkspaceBackups() should return [] when rootWorkspaces in workspaces.json is not a object array (1)', async () => { + test('getWorkspaceBackups() should return [] when rootWorkspaces in workspaces.json is not a object array', async () => { fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":{}}'); await service.initialize(); assert.deepStrictEqual(service.getWorkspaceBackups(), []); - }); - - test('getWorkspaceBackups() should return [] when rootWorkspaces in workspaces.json is not a object array (2)', async () => { fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":{"foo": ["bar"]}}'); await service.initialize(); assert.deepStrictEqual(service.getWorkspaceBackups(), []); - }); - - test('getWorkspaceBackups() should return [] when rootWorkspaces in workspaces.json is not a object array (3)', async () => { fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":{"foo": []}}'); await service.initialize(); assert.deepStrictEqual(service.getWorkspaceBackups(), []); - }); - - test('getWorkspaceBackups() should return [] when rootWorkspaces in workspaces.json is not a object array (4)', async () => { fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":{"foo": "bar"}}'); await service.initialize(); assert.deepStrictEqual(service.getWorkspaceBackups(), []); - }); - - test('getWorkspaceBackups() should return [] when rootWorkspaces in workspaces.json is not a object array (5)', async () => { fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":"foo"}'); await service.initialize(); assert.deepStrictEqual(service.getWorkspaceBackups(), []); - }); - - test('getWorkspaceBackups() should return [] when rootWorkspaces in workspaces.json is not a object array (6)', async () => { fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":1}'); await service.initialize(); assert.deepStrictEqual(service.getWorkspaceBackups(), []); }); - test('getWorkspaceBackups() should return [] when rootURIWorkspaces in workspaces.json is not a object array (1)', async () => { + test('getWorkspaceBackups() should return [] when rootURIWorkspaces in workspaces.json is not a object array', async () => { fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":{}}'); await service.initialize(); assert.deepStrictEqual(service.getWorkspaceBackups(), []); - }); - - test('getWorkspaceBackups() should return [] when rootURIWorkspaces in workspaces.json is not a object array (2)', async () => { fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":{"foo": ["bar"]}}'); await service.initialize(); assert.deepStrictEqual(service.getWorkspaceBackups(), []); - }); - - test('getWorkspaceBackups() should return [] when rootURIWorkspaces in workspaces.json is not a object array (3)', async () => { fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":{"foo": []}}'); await service.initialize(); assert.deepStrictEqual(service.getWorkspaceBackups(), []); - }); - - test('getWorkspaceBackups() should return [] when rootURIWorkspaces in workspaces.json is not a object array (4)', async () => { fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":{"foo": "bar"}}'); await service.initialize(); assert.deepStrictEqual(service.getWorkspaceBackups(), []); - }); - - test('getWorkspaceBackups() should return [] when rootURIWorkspaces in workspaces.json is not a object array (5)', async () => { fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":"foo"}'); await service.initialize(); assert.deepStrictEqual(service.getWorkspaceBackups(), []); - }); - - test('getWorkspaceBackups() should return [] when rootURIWorkspaces in workspaces.json is not a object array (6)', async () => { fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":1}'); await service.initialize(); assert.deepStrictEqual(service.getWorkspaceBackups(), []); @@ -482,19 +407,13 @@ flakySuite('BackupMainService', () => { assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); }); - test('getEmptyWorkspaceBackupPaths() should return [] when workspaces.json is not properly formed JSON (1)', async () => { + test('getEmptyWorkspaceBackupPaths() should return [] when workspaces.json is not properly formed JSON', async () => { fs.writeFileSync(backupWorkspacesPath, ''); await service.initialize(); assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); - }); - - test('getEmptyWorkspaceBackupPaths() should return [] when workspaces.json is not properly formed JSON (2)', async () => { fs.writeFileSync(backupWorkspacesPath, '{]'); await service.initialize(); assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); - }); - - test('getEmptyWorkspaceBackupPaths() should return [] when workspaces.json is not properly formed JSON (3)', async () => { fs.writeFileSync(backupWorkspacesPath, 'foo'); await service.initialize(); assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); @@ -506,37 +425,22 @@ flakySuite('BackupMainService', () => { assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); }); - test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array (1)', async function () { + test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array', async function () { fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{}}'); await service.initialize(); assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); - }); - - test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array (2)', async function () { fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{"foo": ["bar"]}}'); await service.initialize(); assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); - }); - - test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array (3)', async function () { fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{"foo": []}}'); await service.initialize(); assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); - }); - - test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array (4)', async function () { fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{"foo": "bar"}}'); await service.initialize(); assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); - }); - - test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array (5)', async function () { fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":"foo"}'); await service.initialize(); assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); - }); - - test('getEmptyWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is not a string array (6)', async function () { fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":1}'); await service.initialize(); assert.deepStrictEqual(service.getEmptyWindowBackupPaths(), []); diff --git a/src/vs/platform/files/test/node/diskFileService.test.ts b/src/vs/platform/files/test/node/diskFileService.test.ts index c76b85fe292..d6cf5c76fa0 100644 --- a/src/vs/platform/files/test/node/diskFileService.test.ts +++ b/src/vs/platform/files/test/node/diskFileService.test.ts @@ -142,8 +142,6 @@ flakySuite('Disk File Service', function () { const disposables = new DisposableStore(); setup(async () => { - DiskFileSystemProvider.configureFlushOnWrite(true); // but enable flushing for the purpose of these tests - const logService = new NullLogService(); service = new FileService(logService); @@ -164,14 +162,10 @@ flakySuite('Disk File Service', function () { await Promises.copy(sourceDir, testDir, { preserveSymlinks: false }); }); - teardown(async () => { - try { - disposables.clear(); + teardown(() => { + disposables.clear(); - await Promises.rm(testDir); - } finally { - DiskFileSystemProvider.configureFlushOnWrite(false); - } + return Promises.rm(testDir); }); test('createFolder', async () => { @@ -1802,6 +1796,15 @@ flakySuite('Disk File Service', function () { return testWriteFile(); }); + test('writeFile - flush on write', async () => { + DiskFileSystemProvider.configureFlushOnWrite(true); + try { + return await testWriteFile(); + } finally { + DiskFileSystemProvider.configureFlushOnWrite(false); + } + }); + test('writeFile - buffered', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); diff --git a/src/vs/workbench/services/storage/test/browser/storageService.test.ts b/src/vs/workbench/services/storage/test/browser/storageService.test.ts index 8491d3f9301..cbe4837bc8a 100644 --- a/src/vs/workbench/services/storage/test/browser/storageService.test.ts +++ b/src/vs/workbench/services/storage/test/browser/storageService.test.ts @@ -89,7 +89,7 @@ flakySuite('StorageService (browser specific)', () => { disposables.clear(); }); - test('clear', () => { + test.skip('clear', () => { // slow test and also only ever being used as a developer action return runWithFakedTimers({ useFakeTimers: true }, async () => { storageService.store('bar', 'foo', StorageScope.APPLICATION, StorageTarget.MACHINE); storageService.store('bar', 3, StorageScope.APPLICATION, StorageTarget.USER); From 5ae2a59a6d4a4a2707bfa2dc7969c1270a36c353 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Jul 2022 14:19:02 +0200 Subject: [PATCH 032/197] smoke test - only warn when `treekill` fails (#155577) --- test/automation/src/code.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index ed88fd3c2b8..f8dfe1db136 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -164,11 +164,6 @@ export class Code { }); } - if (retries === 40) { - done = true; - reject(new Error('Smoke test exit call did not terminate process after 20s, giving up')); - } - try { process.kill(pid, 0); // throws an exception if the process doesn't exist anymore. await new Promise(resolve => setTimeout(resolve, 500)); @@ -176,6 +171,12 @@ export class Code { done = true; resolve(); } + + if (retries === 60) { + done = true; + this.logger.log('Smoke test exit call did not terminate process after 30s, giving up'); + resolve(); + } } })(); }), 'Code#exit()', this.logger); From 54375a03b87cbd97d6fd87cd006d66292d436029 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 19 Jul 2022 14:21:50 +0200 Subject: [PATCH 033/197] Fixes #152175 by limiting bracket query recursion depth to 200. (#155594) --- .../bracketPairsTree/bracketPairsTree.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/bracketPairsTree.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/bracketPairsTree.ts index d4485d47cc4..9ea7e35847c 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/bracketPairsTree.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/bracketPairsTree.ts @@ -223,6 +223,10 @@ function collectBrackets( level: number, levelPerBracketType: Map ): void { + if (level > 200) { + return; + } + if (node.kind === AstNodeKind.List) { for (const child of node.children) { nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length); @@ -333,6 +337,10 @@ function collectBracketPairs( level: number, levelPerBracketType: Map ) { + if (level > 200) { + return; + } + if (node.kind === AstNodeKind.Pair) { let levelPerBracket = 0; if (levelPerBracketType) { From 4b95a2d3edef09299f0f1626b08d8049a91f85dd Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Jul 2022 14:23:05 +0200 Subject: [PATCH 034/197] merge editor - fix regression with editor resolving (#155596) --- .../workbench/contrib/mergeEditor/browser/commands/commands.ts | 3 ++- .../contrib/mergeEditor/browser/commands/devCommands.ts | 3 ++- src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts index 2a0d776fb18..ca56d104c54 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts @@ -9,6 +9,7 @@ import { localize } from 'vs/nls'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { Action2, MenuId } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { EditorResolution } from 'vs/platform/editor/common/editor'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { API_OPEN_DIFF_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; @@ -35,7 +36,7 @@ export class OpenMergeEditor extends Action2 { validatedArgs.input2, validatedArgs.output, ); - accessor.get(IEditorService).openEditor(input, { preserveFocus: true }); + accessor.get(IEditorService).openEditor(input, { preserveFocus: true, override: EditorResolution.DISABLED }); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts index 6728dc93b1a..6fdc90a8b40 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts @@ -19,6 +19,7 @@ import { IWorkbenchFileService } from 'vs/workbench/services/files/common/files' import { URI } from 'vs/base/common/uri'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { ctxIsMergeEditor } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; +import { EditorResolution } from 'vs/platform/editor/common/editor'; interface MergeEditorContents { languageId: string; @@ -160,6 +161,6 @@ export class MergeEditorOpenContents extends Action2 { { uri: input2Uri, title: 'Input 2', description: 'Input 2', detail: '(from JSON)' }, resultUri, ); - editorService.openEditor(input); + editorService.openEditor(input, { override: EditorResolution.DISABLED }); } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 9f113cfabab..32242173816 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -60,6 +60,7 @@ import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/use import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ctxIsMergeEditor } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; +import { EditorResolution } from 'vs/platform/editor/common/editor'; const CONTEXT_CONFLICTS_SOURCES = new RawContextKey('conflictsSources', ''); @@ -736,7 +737,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo { title: localize('Theirs', 'Theirs'), description: remoteResourceName, detail: undefined, uri: conflict.remoteResource }, conflict.previewResource, ); - await this.editorService.openEditor(input); + await this.editorService.openEditor(input, { override: EditorResolution.DISABLED }); } } From dc755925909f87f6fc44c2d29f3334192eba505c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Tue, 19 Jul 2022 14:25:52 +0200 Subject: [PATCH 035/197] fix type issues in h() (#155600) - improve regex with named capture groups - drop $ in favor of inline id - add tests Co-authored-by: Henning Dieterichs Co-authored-by: Henning Dieterichs --- src/vs/base/browser/dom.ts | 111 ++++++++----- src/vs/base/browser/ui/tree/abstractTree.ts | 8 +- src/vs/base/test/browser/dom.test.ts | 157 +++++++++++++++++- .../browser/view/editors/codeEditorView.ts | 12 +- 4 files changed, 230 insertions(+), 58 deletions(-) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 4db47cd947a..4019ea90b61 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -1738,56 +1738,81 @@ type HTMLElementAttributeKeys = Partial<{ [K in keyof T]: T[K] extends Functi type ElementAttributes = HTMLElementAttributeKeys & Record; type RemoveHTMLElement = T extends HTMLElement ? never : T; type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; -type ArrayToObj = UnionToIntersection>; +type ArrayToObj = UnionToIntersection>; +type HHTMLElementTagNameMap = HTMLElementTagNameMap & { '': HTMLDivElement }; -type TagToElement = T extends `.${string}` - ? HTMLDivElement - : T extends `#${string}` - ? HTMLDivElement - : T extends `${infer TStart}#${string}` - ? TStart extends keyof HTMLElementTagNameMap - ? HTMLElementTagNameMap[TStart] +type TagToElement = T extends `${infer TStart}#${string}` + ? TStart extends keyof HHTMLElementTagNameMap + ? HHTMLElementTagNameMap[TStart] : HTMLElement : T extends `${infer TStart}.${string}` - ? TStart extends keyof HTMLElementTagNameMap - ? HTMLElementTagNameMap[TStart] + ? TStart extends keyof HHTMLElementTagNameMap + ? HHTMLElementTagNameMap[TStart] : HTMLElement : T extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[T] : HTMLElement; +type TagToElementAndId = TTag extends `${infer TTag}@${infer TId}` + ? { element: TagToElement; id: TId } + : { element: TagToElement; id: 'root' }; + +type TagToRecord = TagToElementAndId extends { element: infer TElement; id: infer TId } + ? Record<(TId extends string ? TId : never) | 'root', TElement> + : never; + +type Child = HTMLElement | string | Record; +type Children = [] + | [Child] + | [Child, Child] + | [Child, Child, Child] + | [Child, Child, Child, Child] + | [Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child]; + +const H_REGEX = /(?[\w\-]+)?(?:#(?[\w\-]+))?(?(?:\.(?:[\w\-]+))*)(?:@(?(?:[\w\_])+))?/; + /** * A helper function to create nested dom nodes. * * * ```ts - * private readonly htmlElements = h('div.code-view', [ - * h('div.title', { $: 'title' }), + * const elements = h('div.code-view', [ + * h('div.title@title'), * h('div.container', [ - * h('div.gutter', { $: 'gutterDiv' }), - * h('div', { $: 'editor' }), + * h('div.gutter@gutterDiv'), + * h('div@editor'), * ]), * ]); - * private readonly editor = createEditor(this.htmlElements.editor); + * const editor = createEditor(elements.editor); * ``` */ -export function h( - tag: TTag -): (Record<'root', TagToElement>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; -export function h( - tag: TTag, - attributes: { $: TId } & Partial>> -): Record>; -export function h)[]>( - tag: TTag, - children: T -): (ArrayToObj & Record<'root', TagToElement>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; -export function h(tag: TTag, attributes: Partial>>): Record<'root', TagToElement>; -export function h)[]>( - tag: TTag, - attributes: { $: TId } & Partial>>, - children: T -): (ArrayToObj & Record>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; +export function h + (tag: TTag): + TagToRecord extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; + +export function h + (tag: TTag, children: T): + (ArrayToObj & TagToRecord) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; + +export function h + (tag: TTag, attributes: Partial>>): + TagToRecord extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; + +export function h + (tag: TTag, attributes: Partial>>, children: T): + (ArrayToObj & TagToRecord) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; + export function h(tag: string, ...args: [] | [attributes: { $: string } & Partial> | Record, children?: any[]] | [children: any[]]): Record { let attributes: { $?: string } & Partial>; let children: (Record | HTMLElement)[] | undefined; @@ -1800,25 +1825,29 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & Partia children = args[1]; } - const match = SELECTOR_REGEX.exec(tag); + const match = H_REGEX.exec(tag); - if (!match) { + if (!match || !match.groups) { throw new Error('Bad use of h'); } - const tagName = match[1] || 'div'; + const tagName = match.groups['tag'] || 'div'; const el = document.createElement(tagName); - if (match[3]) { - el.id = match[3]; + if (match.groups['id']) { + el.id = match.groups['id']; } - if (match[4]) { - el.className = match[4].replace(/\./g, ' ').trim(); + if (match.groups['class']) { + el.className = match.groups['class'].replace(/\./g, ' ').trim(); } const result: Record = {}; + if (match.groups['name']) { + result[match.groups['name']] = el; + } + if (children) { for (const c of children) { if (c instanceof HTMLElement) { @@ -1833,10 +1862,6 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & Partia } for (const [key, value] of Object.entries(attributes)) { - if (key === '$') { - result[value] = el; - continue; - } if (key === 'style') { for (const [cssKey, cssValue] of Object.entries(value)) { el.style.setProperty( diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index ba4c6ec3af1..95032ae5ee4 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -684,10 +684,10 @@ export enum TreeFindMode { class FindWidget extends Disposable { - private readonly elements = h('div.monaco-tree-type-filter', [ - h('div.monaco-tree-type-filter-grab.codicon.codicon-debug-gripper', { $: 'grab' }), - h('div.monaco-tree-type-filter-input', { $: 'findInput' }), - h('div.monaco-tree-type-filter-actionbar', { $: 'actionbar' }), + private readonly elements = h('.monaco-tree-type-filter', [ + h('.monaco-tree-type-filter-grab.codicon.codicon-debug-gripper@grab'), + h('.monaco-tree-type-filter-input@findInput'), + h('.monaco-tree-type-filter-actionbar@actionbar'), ]); set mode(mode: TreeFindMode) { diff --git a/src/vs/base/test/browser/dom.test.ts b/src/vs/base/test/browser/dom.test.ts index 435f0066b9c..1ad53561927 100644 --- a/src/vs/base/test/browser/dom.test.ts +++ b/src/vs/base/test/browser/dom.test.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as dom from 'vs/base/browser/dom'; -const $ = dom.$; +import { $, h, multibyteAwareBtoa } from 'vs/base/browser/dom'; suite('dom', () => { test('hasClass', () => { @@ -73,9 +72,9 @@ suite('dom', () => { }); test('multibyteAwareBtoa', () => { - assert.ok(dom.multibyteAwareBtoa('hello world').length > 0); - assert.ok(dom.multibyteAwareBtoa('平仮名').length > 0); - assert.ok(dom.multibyteAwareBtoa(new Array(100000).fill('vs').join('')).length > 0); // https://github.com/microsoft/vscode/issues/112013 + assert.ok(multibyteAwareBtoa('hello world').length > 0); + assert.ok(multibyteAwareBtoa('平仮名').length > 0); + assert.ok(multibyteAwareBtoa(new Array(100000).fill('vs').join('')).length > 0); // https://github.com/microsoft/vscode/issues/112013 }); suite('$', () => { @@ -129,4 +128,152 @@ suite('dom', () => { assert.strictEqual(firstChild.textContent, 'foobar'); }); }); + + suite('h', () => { + test('should build simple nodes', () => { + const div = h('div'); + assert(div.root instanceof HTMLElement); + assert.strictEqual(div.root.tagName, 'DIV'); + + const span = h('span'); + assert(span.root instanceof HTMLElement); + assert.strictEqual(span.root.tagName, 'SPAN'); + + const img = h('img'); + assert(img.root instanceof HTMLElement); + assert.strictEqual(img.root.tagName, 'IMG'); + }); + + test('should handle ids and classes', () => { + const divId = h('div#myid'); + assert.strictEqual(divId.root.tagName, 'DIV'); + assert.strictEqual(divId.root.id, 'myid'); + + const divClass = h('div.a'); + assert.strictEqual(divClass.root.tagName, 'DIV'); + assert.strictEqual(divClass.root.classList.length, 1); + assert(divClass.root.classList.contains('a')); + + const divClasses = h('div.a.b.c'); + assert.strictEqual(divClasses.root.tagName, 'DIV'); + assert.strictEqual(divClasses.root.classList.length, 3); + assert(divClasses.root.classList.contains('a')); + assert(divClasses.root.classList.contains('b')); + assert(divClasses.root.classList.contains('c')); + + const divAll = h('div#myid.a.b.c'); + assert.strictEqual(divAll.root.tagName, 'DIV'); + assert.strictEqual(divAll.root.id, 'myid'); + assert.strictEqual(divAll.root.classList.length, 3); + assert(divAll.root.classList.contains('a')); + assert(divAll.root.classList.contains('b')); + assert(divAll.root.classList.contains('c')); + + const spanId = h('span#myid'); + assert.strictEqual(spanId.root.tagName, 'SPAN'); + assert.strictEqual(spanId.root.id, 'myid'); + + const spanClass = h('span.a'); + assert.strictEqual(spanClass.root.tagName, 'SPAN'); + assert.strictEqual(spanClass.root.classList.length, 1); + assert(spanClass.root.classList.contains('a')); + + const spanClasses = h('span.a.b.c'); + assert.strictEqual(spanClasses.root.tagName, 'SPAN'); + assert.strictEqual(spanClasses.root.classList.length, 3); + assert(spanClasses.root.classList.contains('a')); + assert(spanClasses.root.classList.contains('b')); + assert(spanClasses.root.classList.contains('c')); + + const spanAll = h('span#myid.a.b.c'); + assert.strictEqual(spanAll.root.tagName, 'SPAN'); + assert.strictEqual(spanAll.root.id, 'myid'); + assert.strictEqual(spanAll.root.classList.length, 3); + assert(spanAll.root.classList.contains('a')); + assert(spanAll.root.classList.contains('b')); + assert(spanAll.root.classList.contains('c')); + }); + + test('should implicitly handle ids and classes', () => { + const divId = h('#myid'); + assert.strictEqual(divId.root.tagName, 'DIV'); + assert.strictEqual(divId.root.id, 'myid'); + + const divClass = h('.a'); + assert.strictEqual(divClass.root.tagName, 'DIV'); + assert.strictEqual(divClass.root.classList.length, 1); + assert(divClass.root.classList.contains('a')); + + const divClasses = h('.a.b.c'); + assert.strictEqual(divClasses.root.tagName, 'DIV'); + assert.strictEqual(divClasses.root.classList.length, 3); + assert(divClasses.root.classList.contains('a')); + assert(divClasses.root.classList.contains('b')); + assert(divClasses.root.classList.contains('c')); + + const divAll = h('#myid.a.b.c'); + assert.strictEqual(divAll.root.tagName, 'DIV'); + assert.strictEqual(divAll.root.id, 'myid'); + assert.strictEqual(divAll.root.classList.length, 3); + assert(divAll.root.classList.contains('a')); + assert(divAll.root.classList.contains('b')); + assert(divAll.root.classList.contains('c')); + }); + + test('should handle @ identifiers', () => { + const implicit = h('@el'); + assert.strictEqual(implicit.root, implicit.el); + assert.strictEqual(implicit.el.tagName, 'DIV'); + + const explicit = h('div@el'); + assert.strictEqual(explicit.root, explicit.el); + assert.strictEqual(explicit.el.tagName, 'DIV'); + + const implicitId = h('#myid@el'); + assert.strictEqual(implicitId.root, implicitId.el); + assert.strictEqual(implicitId.el.tagName, 'DIV'); + assert.strictEqual(implicitId.root.id, 'myid'); + + const explicitId = h('div#myid@el'); + assert.strictEqual(explicitId.root, explicitId.el); + assert.strictEqual(explicitId.el.tagName, 'DIV'); + assert.strictEqual(explicitId.root.id, 'myid'); + + const implicitClass = h('.a@el'); + assert.strictEqual(implicitClass.root, implicitClass.el); + assert.strictEqual(implicitClass.el.tagName, 'DIV'); + assert.strictEqual(implicitClass.root.classList.length, 1); + assert(implicitClass.root.classList.contains('a')); + + const explicitClass = h('div.a@el'); + assert.strictEqual(explicitClass.root, explicitClass.el); + assert.strictEqual(explicitClass.el.tagName, 'DIV'); + assert.strictEqual(explicitClass.root.classList.length, 1); + assert(explicitClass.root.classList.contains('a')); + }); + }); + + test('should recurse', () => { + const result = h('div.code-view', [ + h('div.title@title'), + h('div.container', [ + h('div.gutter@gutterDiv'), + h('span@editor'), + ]), + ]); + + assert.strictEqual(result.root.tagName, 'DIV'); + assert.strictEqual(result.root.className, 'code-view'); + assert.strictEqual(result.root.childElementCount, 2); + assert.strictEqual(result.root.firstElementChild, result.title); + assert.strictEqual(result.title.tagName, 'DIV'); + assert.strictEqual(result.title.className, 'title'); + assert.strictEqual(result.title.childElementCount, 0); + assert.strictEqual(result.gutterDiv.tagName, 'DIV'); + assert.strictEqual(result.gutterDiv.className, 'gutter'); + assert.strictEqual(result.gutterDiv.childElementCount, 0); + assert.strictEqual(result.editor.tagName, 'SPAN'); + assert.strictEqual(result.editor.className, ''); + assert.strictEqual(result.editor.childElementCount, 0); + }); }); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts index 321d1594dd5..d1988a2cfd9 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts @@ -24,14 +24,14 @@ export abstract class CodeEditorView extends Disposable { readonly model = this._viewModel.map(m => /** @description model */ m?.model); protected readonly htmlElements = h('div.code-view', [ - h('div.title', { $: 'header' }, [ - h('span.title', { $: 'title' }), - h('span.description', { $: 'description' }), - h('span.detail', { $: 'detail' }), + h('div.title@header', [ + h('span.title@title'), + h('span.description@description'), + h('span.detail@detail'), ]), h('div.container', [ - h('div.gutter', { $: 'gutterDiv' }), - h('div', { $: 'editor' }), + h('div.gutter@gutterDiv'), + h('div@editor'), ]), ]); From b840cff00d477c278796dcdf0d1680d187dbad3c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Jul 2022 14:33:03 +0200 Subject: [PATCH 036/197] editors - fix `--wait` support (fix #155595) (#155599) --- src/vs/platform/windows/electron-main/windowsMainService.ts | 4 ++-- .../contrib/tags/electron-sandbox/workspaceTagsService.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 05bbaab8387..8452c1c5fc4 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -323,7 +323,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // When run with --wait, make sure we keep the paths to wait for if (filesToOpen && openConfig.waitMarkerFileURI) { - filesToOpen.filesToWait = { paths: [...filesToOpen.filesToDiff, filesToOpen.filesToMerge[3] /* [3] is the resulting merge file */, ...filesToOpen.filesToOpenOrCreate], waitMarkerFileUri: openConfig.waitMarkerFileURI }; + filesToOpen.filesToWait = { paths: coalesce([...filesToOpen.filesToDiff, filesToOpen.filesToMerge[3] /* [3] is the resulting merge file */, ...filesToOpen.filesToOpenOrCreate]), waitMarkerFileUri: openConfig.waitMarkerFileURI }; } // These are windows to restore because of hot-exit or from previous session (only performed once on startup!) @@ -477,7 +477,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic if (filesToOpen && potentialNewWindowsCount === 0) { // Find suitable window or folder path to open files in - const fileToCheck = filesToOpen.filesToOpenOrCreate[0] || filesToOpen.filesToDiff[0] || filesToOpen.filesToMerge[3] /* [3] is the resulting merge file */; + const fileToCheck: IPath | undefined = filesToOpen.filesToOpenOrCreate[0] || filesToOpen.filesToDiff[0] || filesToOpen.filesToMerge[3] /* [3] is the resulting merge file */; // only look at the windows with correct authority const windows = this.getWindows().filter(window => filesToOpen && isEqualAuthority(window.remoteAuthority, filesToOpen.remoteAuthority)); diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts index d50317952c0..30d07dcc5b3 100644 --- a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts +++ b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts @@ -820,7 +820,7 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { return this.parentURI(filesToOpenOrCreate[0].fileUri); } else if (filesToDiff && filesToDiff.length) { return this.parentURI(filesToDiff[0].fileUri); - } else if (filesToMerge && filesToMerge.length) { + } else if (filesToMerge && filesToMerge.length === 4) { return this.parentURI(filesToMerge[3].fileUri) /* [3] is the resulting merge file */; } return undefined; From 92fd228156aafeb326b23f6604028d342152313b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Tue, 19 Jul 2022 14:33:41 +0200 Subject: [PATCH 037/197] remove unused PR template line (#155601) --- .github/pull_request_template.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 19314029215..5335e645320 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -5,5 +5,3 @@ * Ensure that the code is up-to-date with the `main` branch. * Include a description of the proposed changes and how to test them. --> - -This PR fixes # From 52414c4084de28025dd4cea6e11a5cd25fb6b392 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 19 Jul 2022 05:48:38 -0700 Subject: [PATCH 038/197] Remove --shell-integration from --help See #93241 Part of #153921 --- src/vs/platform/environment/node/argv.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 68769c3d547..c3583c85e1a 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -50,7 +50,6 @@ export const OPTIONS: OptionDescriptions> = { 'waitMarkerFilePath': { type: 'string' }, 'locale': { type: 'string', cat: 'o', args: 'locale', description: localize('locale', "The locale to use (e.g. en-US or zh-TW).") }, 'user-data-dir': { type: 'string', cat: 'o', args: 'dir', description: localize('userDataDir', "Specifies the directory that user data is kept in. Can be used to open multiple distinct instances of Code.") }, - 'shell-integration': { type: 'string', cat: 'o', args: ['bash', 'pwsh', 'zsh'], description: localize('shellIntergation', "Print the shell integration script file path for the specified shell.") }, 'help': { type: 'boolean', cat: 'o', alias: 'h', description: localize('help', "Print usage.") }, 'extensions-dir': { type: 'string', deprecates: ['extensionHomePath'], cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") }, @@ -129,6 +128,7 @@ export const OPTIONS: OptionDescriptions> = { 'logsPath': { type: 'string' }, '__enable-file-policy': { type: 'boolean' }, 'editSessionId': { type: 'string' }, + 'shell-integration': { type: 'string', args: ['bash', 'pwsh', 'zsh'] }, // chromium flags 'no-proxy-server': { type: 'boolean' }, From 0945ef6e358d2bfc3406b43c664014c825f539d2 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 19 Jul 2022 14:49:19 +0200 Subject: [PATCH 039/197] Add comments to properties of telemetry events (#155584) --- src/vs/editor/browser/editorExtensions.ts | 5 ++- .../contrib/remote/browser/remote.ts | 38 +++++++++---------- .../remote/common/remote.contribution.ts | 10 ++--- .../common/abstractExtensionService.ts | 12 +++--- .../extensions/common/extensionHostManager.ts | 12 +++--- .../electronExtensionService.ts | 12 +++--- 6 files changed, 45 insertions(+), 44 deletions(-) diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index 93b20f1d2ed..f61142282d3 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -339,8 +339,9 @@ export abstract class EditorAction extends EditorCommand { protected reportTelemetry(accessor: ServicesAccessor, editor: ICodeEditor) { type EditorActionInvokedClassification = { owner: 'alexdima'; - name: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + comment: 'An editor action has been invoked.'; + name: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The label of the action that was invoked.' }; + id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the action that was invoked.' }; }; type EditorActionInvokedEvent = { name: string; diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index e18a8624103..e5ebb7468a5 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -779,10 +779,10 @@ export class RemoteAgentConnectionStatusListener extends Disposable implements I type ReconnectReloadClassification = { owner: 'alexdima'; comment: 'The reload button in the builtin permanent reconnection failure dialog was pressed'; - remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; + reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the connection.' }; + millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Elapsed time (in ms) since data was last received.' }; + attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The reconnection attempt counter.' }; }; type ReconnectReloadEvent = { remoteName: string | undefined; @@ -825,8 +825,8 @@ export class RemoteAgentConnectionStatusListener extends Disposable implements I type RemoteConnectionLostClassification = { owner: 'alexdima'; comment: 'The remote connection state is now `ConnectionLost`'; - remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; + reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the connection.' }; }; type RemoteConnectionLostEvent = { remoteName: string | undefined; @@ -861,10 +861,10 @@ export class RemoteAgentConnectionStatusListener extends Disposable implements I type RemoteReconnectionRunningClassification = { owner: 'alexdima'; comment: 'The remote connection state is now `ReconnectionRunning`'; - remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; + reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the connection.' }; + millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Elapsed time (in ms) since data was last received.' }; + attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The reconnection attempt counter.' }; }; type RemoteReconnectionRunningEvent = { remoteName: string | undefined; @@ -902,11 +902,11 @@ export class RemoteAgentConnectionStatusListener extends Disposable implements I type RemoteReconnectionPermanentFailureClassification = { owner: 'alexdima'; comment: 'The remote connection state is now `ReconnectionPermanentFailure`'; - remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - handled: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; + reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the connection.' }; + millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Elapsed time (in ms) since data was last received.' }; + attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The reconnection attempt counter.' }; + handled: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error was handled by the resolver.' }; }; type RemoteReconnectionPermanentFailureEvent = { remoteName: string | undefined; @@ -947,10 +947,10 @@ export class RemoteAgentConnectionStatusListener extends Disposable implements I type RemoteConnectionGainClassification = { owner: 'alexdima'; comment: 'The remote connection state is now `ConnectionGain`'; - remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; + reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the connection.' }; + millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Elapsed time (in ms) since data was last received.' }; + attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The reconnection attempt counter.' }; }; type RemoteConnectionGainEvent = { remoteName: string | undefined; diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 0b2e94b01bc..1bf27f3153c 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -190,9 +190,9 @@ class InitialRemoteConnectionHealthContribution implements IWorkbenchContributio type RemoteConnectionSuccessClassification = { owner: 'alexdima'; comment: 'The initial connection succeeded'; - web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Is web ui.' }; connectionTimeMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Time, in ms, until connected'; isMeasurement: true }; - remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; }; type RemoteConnectionSuccessEvent = { web: boolean; @@ -212,10 +212,10 @@ class InitialRemoteConnectionHealthContribution implements IWorkbenchContributio type RemoteConnectionFailureClassification = { owner: 'alexdima'; comment: 'The initial connection failed'; - web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Is web ui.' }; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; connectionTimeMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Time, in ms, until connection failure'; isMeasurement: true }; - message: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + message: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Error message' }; }; type RemoteConnectionFailureEvent = { web: boolean; diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 61a3cfd5de2..24259163505 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -1228,10 +1228,10 @@ export abstract class AbstractExtensionService extends Disposable implements IEx type ExtensionsMessageClassification = { owner: 'alexdima'; comment: 'A validation message for an extension'; - type: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true }; - extensionId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - extensionPointId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - message: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + type: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Severity of problem.'; isMeasurement: true }; + extensionId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the extension that has a problem.' }; + extensionPointId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The extension point that has a problem.' }; + message: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The message of the problem.' }; }; type ExtensionsMessageEvent = { type: Severity; @@ -1304,8 +1304,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx type ExtensionActivationErrorClassification = { owner: 'alexdima'; comment: 'An extension failed to activate'; - extensionId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - error: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' }; + extensionId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the extension.' }; + error: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; comment: 'The error message.' }; }; type ExtensionActivationErrorEvent = { extensionId: string; diff --git a/src/vs/workbench/services/extensions/common/extensionHostManager.ts b/src/vs/workbench/services/extensions/common/extensionHostManager.ts index 7366addd3dd..2aceff7689b 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostManager.ts @@ -66,12 +66,12 @@ export function createExtensionHostManager(instantiationService: IInstantiationS export type ExtensionHostStartupClassification = { owner: 'alexdima'; comment: 'The startup state of the extension host'; - time: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - action: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - kind: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - errorName?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - errorMessage?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - errorStack?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + time: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The time reported by Date.now().' }; + action: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The action: starting, success or error.' }; + kind: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The extension host kind: LocalProcess, LocalWebWorker or Remote.' }; + errorName?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error name.' }; + errorMessage?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error message.' }; + errorStack?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error stack.' }; }; export type ExtensionHostStartupEvent = { diff --git a/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts index 649e83699e8..34cb13522aa 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts @@ -304,9 +304,9 @@ export abstract class ElectronExtensionService extends AbstractExtensionService type ExtensionHostCrashClassification = { owner: 'alexdima'; comment: 'The extension host has terminated unexpectedly'; - code: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - signal: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - extensionIds: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + code: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The exit code of the extension host process.' }; + signal: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The signal that caused the extension host process to exit.' }; + extensionIds: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The list of loaded extensions.' }; }; type ExtensionHostCrashEvent = { code: number; @@ -323,9 +323,9 @@ export abstract class ElectronExtensionService extends AbstractExtensionService type ExtensionHostCrashExtensionClassification = { owner: 'alexdima'; comment: 'The extension host has terminated unexpectedly'; - code: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - signal: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - extensionId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + code: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The exit code of the extension host process.' }; + signal: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The signal that caused the extension host process to exit.' }; + extensionId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the extension.' }; }; type ExtensionHostCrashExtensionEvent = { code: number; From bbe22bcd1597a3eefd708e2b600144dcd044ff1e Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Tue, 19 Jul 2022 14:52:37 +0200 Subject: [PATCH 040/197] add telemetry comments for activatePlugin in workbenchThemeService (#155592) add telemetry comments (for #2762) --- .../services/themes/browser/workbenchThemeService.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 3800c0fbd23..6745ad1bc49 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -581,11 +581,12 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (!this.themeExtensionsActivated.get(key)) { type ActivatePluginClassification = { owner: 'aeschli'; - id: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; - name: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; - isBuiltin: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; - publisherDisplayName: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - themeId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; + comment: 'An event is fired when an color theme extension is first used as it provides the currently shown color theme.'; + id: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The extension id.' }; + name: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The extension name.' }; + isBuiltin: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether the extension is a built-in extension.' }; + publisherDisplayName: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension publisher id.' }; + themeId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The id of the theme that triggered the first extension use.' }; }; type ActivatePluginEvent = { id: string; From 7227d6a7b18ec6cd99ec416b1c6da7c1a5b6c91d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 19 Jul 2022 06:14:15 -0700 Subject: [PATCH 041/197] Don't fill in tasks type/labels These change throughout the lifecycle of the window, require workspace trust and are expensive to fetch. Just having help with autocompleting type/taskName is good enough here. Fixes #155087 --- .../workbench/contrib/tasks/browser/abstractTaskService.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 4bfc497f05a..49a81a5ea7b 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -394,13 +394,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer properties: { type: { type: 'string', - description: nls.localize('runTask.type', "The contributed task type"), - enum: Array.from(this._providerTypes.values()).map(provider => provider) + description: nls.localize('runTask.type', "The contributed task type") }, taskName: { type: 'string', - description: nls.localize('runTask.taskName', "The task's label or a term to filter by"), - enum: await this.tasks().then((tasks) => tasks.map(t => t._label)) + description: nls.localize('runTask.taskName', "The task's label or a term to filter by") } } } From ee636283a916f542094468615be31f8adb4e7d44 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 19 Jul 2022 06:18:59 -0700 Subject: [PATCH 042/197] shell-integration -> locate-shell-integration-path Part of #153921 --- src/vs/code/node/cli.ts | 12 ++++++------ src/vs/platform/environment/common/argv.ts | 2 +- src/vs/platform/environment/node/argv.ts | 2 +- src/vs/server/node/server.cli.ts | 2 +- src/vs/server/node/serverEnvironmentService.ts | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 735a7e39669..c64e6fe0503 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -62,20 +62,20 @@ export async function main(argv: string[]): Promise { } // Shell integration - else if (args['shell-integration']) { + else if (args['locate-shell-integration-path']) { // Silently fail when the terminal is not VS Code's integrated terminal if (process.env['TERM_PROGRAM'] !== 'vscode') { return; } let file: string; - switch (args['shell-integration']) { - // Usage: `[[ "$TERM_PROGRAM" == "vscode" ]] && . "$(code --shell-integration bash)"` + switch (args['locate-shell-integration-path']) { + // Usage: `[[ "$TERM_PROGRAM" == "vscode" ]] && . "$(code --locate-shell-integration-path bash)"` case 'bash': file = 'shellIntegration-bash.sh'; break; - // Usage: `if ($env:TERM_PROGRAM -eq "vscode") { . "$(code --shell-integration pwsh)" }` + // Usage: `if ($env:TERM_PROGRAM -eq "vscode") { . "$(code --locate-shell-integration-path pwsh)" }` case 'pwsh': file = 'shellIntegration.ps1'; break; - // Usage: `[[ "$TERM_PROGRAM" == "vscode" ]] && . "$(code --shell-integration zsh)"` + // Usage: `[[ "$TERM_PROGRAM" == "vscode" ]] && . "$(code --locate-shell-integration-path zsh)"` case 'zsh': file = 'shellIntegration-rc.zsh'; break; - default: throw new Error('Error using --shell-integration: Invalid shell type'); + default: throw new Error('Error using --locate-shell-integration-path: Invalid shell type'); } console.log(join(dirname(FileAccess.asFileUri('', require)).fsPath, 'out', 'vs', 'workbench', 'contrib', 'terminal', 'browser', 'media', file)); } diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index 5424707ace6..71d2d312250 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -90,7 +90,7 @@ export interface NativeParsedArgs { 'logsPath'?: string; '__enable-file-policy'?: boolean; editSessionId?: string; - 'shell-integration'?: string; + 'locate-shell-integration-path'?: string; // chromium command line args: https://electronjs.org/docs/all#supported-chrome-command-line-switches 'no-proxy-server'?: boolean; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index c3583c85e1a..1728cfec19a 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -128,7 +128,7 @@ export const OPTIONS: OptionDescriptions> = { 'logsPath': { type: 'string' }, '__enable-file-policy': { type: 'boolean' }, 'editSessionId': { type: 'string' }, - 'shell-integration': { type: 'string', args: ['bash', 'pwsh', 'zsh'] }, + 'locate-shell-integration-path': { type: 'string', args: ['bash', 'pwsh', 'zsh'] }, // chromium flags 'no-proxy-server': { type: 'boolean' }, diff --git a/src/vs/server/node/server.cli.ts b/src/vs/server/node/server.cli.ts index 56a704050b3..4c5ec7e1e2a 100644 --- a/src/vs/server/node/server.cli.ts +++ b/src/vs/server/node/server.cli.ts @@ -44,7 +44,7 @@ const isSupportedForCmd = (optionId: keyof RemoteParsedArgs) => { case 'enable-smoke-test-driver': case 'extensions-download-dir': case 'builtin-extensions-dir': - case 'shell-integration': + case 'locate-shell-integration-path': case 'telemetry': return false; default: diff --git a/src/vs/server/node/serverEnvironmentService.ts b/src/vs/server/node/serverEnvironmentService.ts index 2efbb4c5c98..c075cf134fd 100644 --- a/src/vs/server/node/serverEnvironmentService.ts +++ b/src/vs/server/node/serverEnvironmentService.ts @@ -81,7 +81,7 @@ export const serverOptions: OptionDescriptions = { 'help': OPTIONS['help'], 'version': OPTIONS['version'], - 'shell-integration': OPTIONS['shell-integration'], + 'locate-shell-integration-path': OPTIONS['locate-shell-integration-path'], 'compatibility': { type: 'string' }, @@ -194,7 +194,7 @@ export interface ServerParsedArgs { /* ----- server cli ----- */ help: boolean; version: boolean; - 'shell-integration'?: string; + 'locate-shell-integration-path'?: string; compatibility: string; From 34f1bc679dadbd07104234ab03eacdd7b8d157f0 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 19 Jul 2022 15:40:33 +0200 Subject: [PATCH 043/197] Engineering - update Code OSS pipeline (#155610) --- .../darwin/product-build-darwin-test.yml | 181 ++++++++------ .../darwin/product-build-darwin.yml | 59 +++-- .../linux/product-build-linux-client-test.yml | 234 ++++++++++++------ .../linux/product-build-linux-client.yml | 104 ++++---- build/azure-pipelines/product-build-pr.yml | 233 ++++++++--------- build/azure-pipelines/product-compile.yml | 33 +-- .../win32/product-build-win32-test.yml | 213 ++++++++++------ .../win32/product-build-win32.yml | 83 ++++--- 8 files changed, 667 insertions(+), 473 deletions(-) diff --git a/build/azure-pipelines/darwin/product-build-darwin-test.yml b/build/azure-pipelines/darwin/product-build-darwin-test.yml index 4c30c5e0b31..1094b41ca21 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-test.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-test.yml @@ -1,4 +1,6 @@ parameters: + - name: VSCODE_QUALITY + type: string - name: VSCODE_RUN_UNIT_TESTS type: boolean - name: VSCODE_RUN_INTEGRATION_TESTS @@ -14,25 +16,43 @@ steps: displayName: Download Electron and Playwright - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - - script: | - set -e - ./scripts/test.sh --build --tfs "Unit Tests" - displayName: Run unit tests (Electron) - timeoutInMinutes: 15 + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + ./scripts/test.sh --tfs "Unit Tests" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 - - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - - script: | - set -e - yarn test-node --build - displayName: Run unit tests (node.js) - timeoutInMinutes: 15 + - script: | + set -e + yarn test-node + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 - - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - - script: | - set -e - DEBUG=*browser* yarn test-browser-no-install --sequential --build --browser chromium --browser webkit --tfs "Browser Unit Tests" - displayName: Run unit tests (Browser, Chromium & Webkit) - timeoutInMinutes: 30 + - script: | + set -e + DEBUG=*browser* yarn test-browser-no-install --sequential --browser chromium --browser webkit --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser, Chromium & Webkit) + timeoutInMinutes: 30 + + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + ./scripts/test.sh --build --tfs "Unit Tests" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + + - script: | + set -e + yarn test-node --build + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + + - script: | + set -e + DEBUG=*browser* yarn test-browser-no-install --sequential --build --browser chromium --browser webkit --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser, Chromium & Webkit) + timeoutInMinutes: 30 - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - script: | @@ -57,38 +77,42 @@ steps: compile-extension:vscode-test-resolver displayName: Build integration tests - - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - - script: | - # Figure out the full absolute path of the product we just built - # including the remote server and configure the integration tests - # to run with these builds instead of running out of sources. - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) - APP_NAME="`ls $APP_ROOT | head -n 1`" - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \ - ./scripts/test-integration.sh --build --tfs "Integration Tests" - displayName: Run integration tests (Electron) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + ./scripts/test-integration.sh --tfs "Integration Tests" + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 - - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - - script: | - set -e - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin-$(VSCODE_ARCH)" \ - ./scripts/test-web-integration.sh --browser webkit - displayName: Run integration tests (Browser, Webkit) - timeoutInMinutes: 20 + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) + APP_NAME="`ls $APP_ROOT | head -n 1`" + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \ + ./scripts/test-integration.sh --build --tfs "Integration Tests" + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 - - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - - script: | - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) - APP_NAME="`ls $APP_ROOT | head -n 1`" - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \ - ./scripts/test-remote-integration.sh - displayName: Run integration tests (Remote) - timeoutInMinutes: 20 + - script: | + set -e + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin-$(VSCODE_ARCH)" \ + ./scripts/test-web-integration.sh --browser webkit + displayName: Run integration tests (Browser, Webkit) + timeoutInMinutes: 20 + + - script: | + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) + APP_NAME="`ls $APP_ROOT | head -n 1`" + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \ + ./scripts/test-remote-integration.sh + displayName: Run integration tests (Remote) + timeoutInMinutes: 20 - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - script: | @@ -98,35 +122,44 @@ steps: continueOnError: true condition: succeededOrFailed() - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - - script: | - set -e - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin-$(VSCODE_ARCH)" \ - yarn smoketest-no-compile --web --tracing --headless - timeoutInMinutes: 20 - displayName: Run smoke tests (Browser, Chromium) + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + yarn --cwd test/smoke compile + displayName: Compile smoke tests - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - - script: | - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) - APP_NAME="`ls $APP_ROOT | head -n 1`" - yarn smoketest-no-compile --tracing --build "$APP_ROOT/$APP_NAME" - timeoutInMinutes: 20 - displayName: Run smoke tests (Electron) + - script: | + set -e + yarn smoketest-no-compile --tracing + timeoutInMinutes: 20 + displayName: Run smoke tests (Electron) - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - - script: | - set -e - yarn gulp compile-extension:vscode-test-resolver - APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) - APP_NAME="`ls $APP_ROOT | head -n 1`" - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \ - yarn smoketest-no-compile --tracing --remote --build "$APP_ROOT/$APP_NAME" - timeoutInMinutes: 20 - displayName: Run smoke tests (Remote) + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) + APP_NAME="`ls $APP_ROOT | head -n 1`" + yarn smoketest-no-compile --tracing --build "$APP_ROOT/$APP_NAME" + timeoutInMinutes: 20 + displayName: Run smoke tests (Electron) + + - script: | + set -e + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin-$(VSCODE_ARCH)" \ + yarn smoketest-no-compile --web --tracing --headless + timeoutInMinutes: 20 + displayName: Run smoke tests (Browser, Chromium) + + - script: | + set -e + yarn gulp compile-extension:vscode-test-resolver + APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) + APP_NAME="`ls $APP_ROOT | head -n 1`" + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \ + yarn smoketest-no-compile --tracing --remote --build "$APP_ROOT/$APP_NAME" + timeoutInMinutes: 20 + displayName: Run smoke tests (Remote) - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - script: | set -e ps -ef @@ -148,7 +181,6 @@ steps: continueOnError: true condition: failed() - - ${{ if or(eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: # In order to properly symbolify above crash reports # (if any), we need the compiled native modules too - task: PublishPipelineArtifact@0 @@ -164,7 +196,6 @@ steps: continueOnError: true condition: failed() - - ${{ if or(eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: - task: PublishPipelineArtifact@0 inputs: targetPath: .build/logs diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index c9503f42a0a..eda79c53cf9 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -11,6 +11,11 @@ parameters: type: boolean steps: + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - checkout: self + fetchDepth: 1 + retryCountOnTaskFailure: 3 + - task: NodeTool@0 inputs: versionSpec: "16.x" @@ -23,16 +28,18 @@ steps: KeyVaultName: vscode SecretsFilter: "github-distro-mixin-password,macos-developer-certificate,macos-developer-certificate-key" - - task: DownloadPipelineArtifact@2 - inputs: - artifact: Compilation - path: $(Build.ArtifactStagingDirectory) - displayName: Download compilation output + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - task: DownloadPipelineArtifact@2 + inputs: + artifact: Compilation + path: $(Build.ArtifactStagingDirectory) + displayName: Download compilation output - - script: | - set -e - tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz - displayName: Extract compilation output + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz + displayName: Extract compilation output - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - script: | @@ -123,11 +130,12 @@ steps: node build/azure-pipelines/mixin displayName: Mix in quality - - script: | - set -e - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-darwin-$(VSCODE_ARCH)-min-ci - displayName: Build client + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp vscode-darwin-$(VSCODE_ARCH)-min-ci + displayName: Build client - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - script: | @@ -135,17 +143,26 @@ steps: node build/azure-pipelines/mixin --server displayName: Mix in server quality - - script: | - set -e - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-reh-darwin-$(VSCODE_ARCH)-min-ci - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-reh-web-darwin-$(VSCODE_ARCH)-min-ci - displayName: Build Server + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp vscode-reh-darwin-$(VSCODE_ARCH)-min-ci + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp vscode-reh-web-darwin-$(VSCODE_ARCH)-min-ci + displayName: Build Server + + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp "transpile-client" "transpile-extensions" + displayName: Transpile - ${{ if or(eq(parameters.VSCODE_RUN_UNIT_TESTS, true), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: - template: product-build-darwin-test.yml parameters: + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} VSCODE_RUN_UNIT_TESTS: ${{ parameters.VSCODE_RUN_UNIT_TESTS }} VSCODE_RUN_INTEGRATION_TESTS: ${{ parameters.VSCODE_RUN_INTEGRATION_TESTS }} VSCODE_RUN_SMOKE_TESTS: ${{ parameters.VSCODE_RUN_SMOKE_TESTS }} diff --git a/build/azure-pipelines/linux/product-build-linux-client-test.yml b/build/azure-pipelines/linux/product-build-linux-client-test.yml index bc9aae42daf..31d477e93aa 100644 --- a/build/azure-pipelines/linux/product-build-linux-client-test.yml +++ b/build/azure-pipelines/linux/product-build-linux-client-test.yml @@ -1,4 +1,6 @@ parameters: + - name: VSCODE_QUALITY + type: string - name: VSCODE_RUN_UNIT_TESTS type: boolean - name: VSCODE_RUN_INTEGRATION_TESTS @@ -13,38 +15,68 @@ steps: yarn npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" displayName: Download Electron and Playwright - - script: | - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - ELECTRON_ROOT=.build/electron - sudo chown root $APP_ROOT/chrome-sandbox - sudo chown root $ELECTRON_ROOT/chrome-sandbox - sudo chmod 4755 $APP_ROOT/chrome-sandbox - sudo chmod 4755 $ELECTRON_ROOT/chrome-sandbox - stat $APP_ROOT/chrome-sandbox - stat $ELECTRON_ROOT/chrome-sandbox - displayName: Change setuid helper binary permission - - - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - script: | set -e - ./scripts/test.sh --build --tfs "Unit Tests" - displayName: Run unit tests (Electron) - timeoutInMinutes: 15 + sudo apt-get update + sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1 + sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb + sudo chmod +x /etc/init.d/xvfb + sudo update-rc.d xvfb defaults + sudo service xvfb start + displayName: Setup build environment - - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - script: | set -e - yarn test-node --build - displayName: Run unit tests (node.js) - timeoutInMinutes: 15 + APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + ELECTRON_ROOT=.build/electron + sudo chown root $APP_ROOT/chrome-sandbox + sudo chown root $ELECTRON_ROOT/chrome-sandbox + sudo chmod 4755 $APP_ROOT/chrome-sandbox + sudo chmod 4755 $ELECTRON_ROOT/chrome-sandbox + stat $APP_ROOT/chrome-sandbox + stat $ELECTRON_ROOT/chrome-sandbox + displayName: Change setuid helper binary permission - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - - script: | - set -e - DEBUG=*browser* yarn test-browser-no-install --build --browser chromium --tfs "Browser Unit Tests" - displayName: Run unit tests (Browser, Chromium) - timeoutInMinutes: 15 + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + DISPLAY=:10 ./scripts/test.sh --tfs "Unit Tests" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + + - script: | + set -e + yarn test-node + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + + - script: | + set -e + DEBUG=*browser* yarn test-browser-no-install --browser chromium --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser, Chromium) + timeoutInMinutes: 15 + + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + ./scripts/test.sh --build --tfs "Unit Tests" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + + - script: | + set -e + yarn test-node --build + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + + - script: | + set -e + DEBUG=*browser* yarn test-browser-no-install --build --browser chromium --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser, Chromium) + timeoutInMinutes: 15 - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - script: | @@ -70,39 +102,57 @@ steps: displayName: Build integration tests - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - - script: | - # Figure out the full absolute path of the product we just built - # including the remote server and configure the integration tests - # to run with these builds instead of running out of sources. - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") - INTEGRATION_TEST_APP_NAME="$APP_NAME" \ - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ - ./scripts/test-integration.sh --build --tfs "Integration Tests" - displayName: Run integration tests (Electron) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + DISPLAY=:10 ./scripts/test-integration.sh --tfs "Integration Tests" + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 - - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - - script: | - set -e - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ - ./scripts/test-web-integration.sh --browser chromium - displayName: Run integration tests (Browser, Chromium) - timeoutInMinutes: 20 + - script: | + set -e + ./scripts/test-web-integration.sh --browser chromium + displayName: Run integration tests (Browser, Chromium) + timeoutInMinutes: 20 - - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - - script: | - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") - INTEGRATION_TEST_APP_NAME="$APP_NAME" \ - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ - ./scripts/test-remote-integration.sh - displayName: Run integration tests (Remote) - timeoutInMinutes: 20 + - script: | + set -e + ./scripts/test-remote-integration.sh + displayName: Run integration tests (Remote) + timeoutInMinutes: 20 + + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") + INTEGRATION_TEST_APP_NAME="$APP_NAME" \ + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ + ./scripts/test-integration.sh --build --tfs "Integration Tests" + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 + + - script: | + set -e + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ + ./scripts/test-web-integration.sh --browser chromium + displayName: Run integration tests (Browser, Chromium) + timeoutInMinutes: 20 + + - script: | + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") + INTEGRATION_TEST_APP_NAME="$APP_NAME" \ + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ + ./scripts/test-remote-integration.sh + displayName: Run integration tests (Remote) + timeoutInMinutes: 20 - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - script: | @@ -114,33 +164,55 @@ steps: continueOnError: true condition: succeededOrFailed() - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - - script: | - set -e - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ - yarn smoketest-no-compile --web --tracing --headless --electronArgs="--disable-dev-shm-usage" - timeoutInMinutes: 20 - displayName: Run smoke tests (Browser, Chromium) + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + yarn --cwd test/smoke compile + displayName: Compile smoke tests - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - - script: | - set -e - APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - yarn smoketest-no-compile --tracing --build "$APP_PATH" - timeoutInMinutes: 20 - displayName: Run smoke tests (Electron) + - script: | + set -e + yarn smoketest-no-compile --tracing + timeoutInMinutes: 20 + displayName: Run smoke tests (Electron) - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - - script: | - set -e - yarn gulp compile-extension:vscode-test-resolver - APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ - yarn smoketest-no-compile --tracing --remote --build "$APP_PATH" - timeoutInMinutes: 20 - displayName: Run smoke tests (Remote) + - script: | + set -e + yarn smoketest-no-compile --web --tracing --headless --electronArgs="--disable-dev-shm-usage" + timeoutInMinutes: 20 + displayName: Run smoke tests (Browser, Chromium) + + - script: | + set -e + yarn gulp compile-extension:vscode-test-resolver + yarn smoketest-no-compile --remote --tracing + timeoutInMinutes: 20 + displayName: Run smoke tests (Remote) + + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + yarn smoketest-no-compile --tracing --build "$APP_PATH" + timeoutInMinutes: 20 + displayName: Run smoke tests (Electron) + + - script: | + set -e + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ + yarn smoketest-no-compile --web --tracing --headless --electronArgs="--disable-dev-shm-usage" + timeoutInMinutes: 20 + displayName: Run smoke tests (Browser, Chromium) + + - script: | + set -e + yarn gulp compile-extension:vscode-test-resolver + APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ + yarn smoketest-no-compile --tracing --remote --build "$APP_PATH" + timeoutInMinutes: 20 + displayName: Run smoke tests (Remote) - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - script: | set -e ps -ef @@ -164,7 +236,6 @@ steps: continueOnError: true condition: failed() - - ${{ if or(eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: # In order to properly symbolify above crash reports # (if any), we need the compiled native modules too - task: PublishPipelineArtifact@0 @@ -180,7 +251,6 @@ steps: continueOnError: true condition: failed() - - ${{ if or(eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: - task: PublishPipelineArtifact@0 inputs: targetPath: .build/logs diff --git a/build/azure-pipelines/linux/product-build-linux-client.yml b/build/azure-pipelines/linux/product-build-linux-client.yml index ed4d853230b..1f57307739a 100644 --- a/build/azure-pipelines/linux/product-build-linux-client.yml +++ b/build/azure-pipelines/linux/product-build-linux-client.yml @@ -11,6 +11,11 @@ parameters: type: boolean steps: + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - checkout: self + fetchDepth: 1 + retryCountOnTaskFailure: 3 + - task: NodeTool@0 inputs: versionSpec: "16.x" @@ -23,33 +28,37 @@ steps: KeyVaultName: vscode SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password" - - task: DownloadPipelineArtifact@2 - inputs: - artifact: Compilation - path: $(Build.ArtifactStagingDirectory) - displayName: Download compilation output + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - task: DownloadPipelineArtifact@2 + inputs: + artifact: Compilation + path: $(Build.ArtifactStagingDirectory) + displayName: Download compilation output - - task: DownloadPipelineArtifact@2 - inputs: - artifact: reh_node_modules-$(VSCODE_ARCH) - path: $(Build.ArtifactStagingDirectory) - displayName: Download server build dependencies - condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'armhf')) + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - task: DownloadPipelineArtifact@2 + inputs: + artifact: reh_node_modules-$(VSCODE_ARCH) + path: $(Build.ArtifactStagingDirectory) + displayName: Download server build dependencies + condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'armhf')) - - script: | - set -e - # Start X server - /etc/init.d/xvfb start - # Start dbus session - DBUS_LAUNCH_RESULT=$(sudo dbus-daemon --config-file=/usr/share/dbus-1/system.conf --print-address) - echo "##vso[task.setvariable variable=DBUS_SESSION_BUS_ADDRESS]$DBUS_LAUNCH_RESULT" - displayName: Setup system services - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64')) + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + # Start X server + /etc/init.d/xvfb start + # Start dbus session + DBUS_LAUNCH_RESULT=$(sudo dbus-daemon --config-file=/usr/share/dbus-1/system.conf --print-address) + echo "##vso[task.setvariable variable=DBUS_SESSION_BUS_ADDRESS]$DBUS_LAUNCH_RESULT" + displayName: Setup system services + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64')) - - script: | - set -e - tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz - displayName: Extract compilation output + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz + displayName: Extract compilation output - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - script: | @@ -169,12 +178,13 @@ steps: displayName: Install dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - script: | - set -e - rm -rf remote/node_modules - tar -xzf $(Build.ArtifactStagingDirectory)/reh_node_modules-$(VSCODE_ARCH).tar.gz --directory $(Build.SourcesDirectory)/remote - displayName: Extract server node_modules output - condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'armhf')) + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + rm -rf remote/node_modules + tar -xzf $(Build.ArtifactStagingDirectory)/reh_node_modules-$(VSCODE_ARCH).tar.gz --directory $(Build.SourcesDirectory)/remote + displayName: Extract server node_modules output + condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'armhf')) - script: | set -e @@ -190,11 +200,12 @@ steps: node build/azure-pipelines/mixin displayName: Mix in quality - - script: | - set -e - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-linux-$(VSCODE_ARCH)-min-ci - displayName: Build + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp vscode-linux-$(VSCODE_ARCH)-min-ci + displayName: Build - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - script: | @@ -202,17 +213,26 @@ steps: node build/azure-pipelines/mixin --server displayName: Mix in server quality - - script: | - set -e - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-reh-linux-$(VSCODE_ARCH)-min-ci - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-reh-web-linux-$(VSCODE_ARCH)-min-ci - displayName: Build Server + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp vscode-reh-linux-$(VSCODE_ARCH)-min-ci + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp vscode-reh-web-linux-$(VSCODE_ARCH)-min-ci + displayName: Build Server + + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp "transpile-client" "transpile-extensions" + displayName: Transpile - ${{ if or(eq(parameters.VSCODE_RUN_UNIT_TESTS, true), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: - template: product-build-linux-client-test.yml parameters: + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} VSCODE_RUN_UNIT_TESTS: ${{ parameters.VSCODE_RUN_UNIT_TESTS }} VSCODE_RUN_INTEGRATION_TESTS: ${{ parameters.VSCODE_RUN_INTEGRATION_TESTS }} VSCODE_RUN_SMOKE_TESTS: ${{ parameters.VSCODE_RUN_SMOKE_TESTS }} diff --git a/build/azure-pipelines/product-build-pr.yml b/build/azure-pipelines/product-build-pr.yml index c8e23162f07..62eb8ca55cb 100644 --- a/build/azure-pipelines/product-build-pr.yml +++ b/build/azure-pipelines/product-build-pr.yml @@ -6,15 +6,6 @@ pr: branches: include: ["main", "release/*"] -resources: - containers: - - container: centos7-devtoolset8-x64 - image: vscodehub.azurecr.io/vscode-linux-build-agent:centos7-devtoolset8-x64 - options: --user 0:0 --cap-add SYS_ADMIN - - container: vscode-bionic-x64 - image: vscodehub.azurecr.io/vscode-linux-build-agent:bionic-x64 - options: --user 0:0 --cap-add SYS_ADMIN - variables: - name: Codeql.SkipTaskAutoInjection value: true @@ -31,9 +22,11 @@ variables: stages: - stage: Compile + displayName: Compile & Hygiene jobs: - job: Compile - pool: vscode-1es-vscode-linux-18.04 + displayName: Compile & Hygiene + pool: vscode-1es-vscode-linux-20.04 variables: VSCODE_ARCH: x64 steps: @@ -41,74 +34,14 @@ stages: parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - - stage: LinuxServerDependencies + - stage: Test dependsOn: [] - pool: vscode-1es-vscode-linux-18.04 - jobs: - - job: x64 - container: centos7-devtoolset8-x64 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - steps: - - template: linux/product-build-linux-server.yml - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - - - stage: Windows - dependsOn: - - Compile - pool: vscode-1es-vscode-windows-2019 - jobs: - - job: WindowsUnitTests - displayName: Unit Tests - timeoutInMinutes: 120 - variables: - VSCODE_ARCH: x64 - steps: - - template: win32/product-build-win32.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: true - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: false - - job: WindowsIntegrationTests - displayName: Integration Tests - timeoutInMinutes: 120 - variables: - VSCODE_ARCH: x64 - steps: - - template: win32/product-build-win32.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: true - VSCODE_RUN_SMOKE_TESTS: false - - job: WindowsSmokeTests - displayName: Smoke Tests - timeoutInMinutes: 120 - variables: - VSCODE_ARCH: x64 - steps: - - template: win32/product-build-win32.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: true - - - stage: Linux - dependsOn: - - Compile - - LinuxServerDependencies - pool: vscode-1es-vscode-linux-18.04 jobs: - job: Linuxx64UnitTest - displayName: Unit Tests - container: vscode-bionic-x64 + displayName: Linux (Unit Tests) + pool: vscode-1es-vscode-linux-20.04 + # container: vscode-bionic-x64 + timeoutInMinutes: 60 variables: VSCODE_ARCH: x64 NPM_ARCH: x64 @@ -122,8 +55,10 @@ stages: VSCODE_RUN_INTEGRATION_TESTS: false VSCODE_RUN_SMOKE_TESTS: false - job: Linuxx64IntegrationTest - displayName: Integration Tests - container: vscode-bionic-x64 + displayName: Linux (Integration Tests) + pool: vscode-1es-vscode-linux-20.04 + # container: vscode-bionic-x64 + timeoutInMinutes: 60 variables: VSCODE_ARCH: x64 NPM_ARCH: x64 @@ -137,8 +72,10 @@ stages: VSCODE_RUN_INTEGRATION_TESTS: true VSCODE_RUN_SMOKE_TESTS: false - job: Linuxx64SmokeTest - displayName: Smoke Tests - container: vscode-bionic-x64 + displayName: Linux (Smoke Tests) + pool: vscode-1es-vscode-linux-20.04 + # container: vscode-bionic-x64 + timeoutInMinutes: 60 variables: VSCODE_ARCH: x64 NPM_ARCH: x64 @@ -152,50 +89,94 @@ stages: VSCODE_RUN_INTEGRATION_TESTS: false VSCODE_RUN_SMOKE_TESTS: true - - stage: macOS - dependsOn: - - Compile - pool: - vmImage: macOS-latest - variables: - BUILDSECMON_OPT_IN: true - jobs: - - job: macOSUnitTest - displayName: Unit Tests - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: x64 - steps: - - template: darwin/product-build-darwin.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: true - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: false - - job: macOSIntegrationTest - displayName: Integration Tests - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: x64 - steps: - - template: darwin/product-build-darwin.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: true - VSCODE_RUN_SMOKE_TESTS: false - - job: macOSSmokeTest - displayName: Smoke Tests - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: x64 - steps: - - template: darwin/product-build-darwin.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: true + # - job: macOSUnitTest + # displayName: macOS (Unit Tests) + # pool: + # vmImage: macOS-latest + # timeoutInMinutes: 60 + # variables: + # BUILDSECMON_OPT_IN: true + # VSCODE_ARCH: x64 + # steps: + # - template: darwin/product-build-darwin.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: true + # VSCODE_RUN_INTEGRATION_TESTS: false + # VSCODE_RUN_SMOKE_TESTS: false + # - job: macOSIntegrationTest + # displayName: macOS (Integration Tests) + # pool: + # vmImage: macOS-latest + # timeoutInMinutes: 60 + # variables: + # BUILDSECMON_OPT_IN: true + # VSCODE_ARCH: x64 + # steps: + # - template: darwin/product-build-darwin.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: false + # VSCODE_RUN_INTEGRATION_TESTS: true + # VSCODE_RUN_SMOKE_TESTS: false + # - job: macOSSmokeTest + # displayName: macOS (Smoke Tests) + # pool: + # vmImage: macOS-latest + # timeoutInMinutes: 60 + # variables: + # BUILDSECMON_OPT_IN: true + # VSCODE_ARCH: x64 + # steps: + # - template: darwin/product-build-darwin.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: false + # VSCODE_RUN_INTEGRATION_TESTS: false + # VSCODE_RUN_SMOKE_TESTS: true + + # - job: WindowsUnitTests + # displayName: Windows (Unit Tests) + # pool: vscode-1es-vscode-windows-2019 + # timeoutInMinutes: 60 + # variables: + # VSCODE_ARCH: x64 + # steps: + # - template: win32/product-build-win32.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: true + # VSCODE_RUN_INTEGRATION_TESTS: false + # VSCODE_RUN_SMOKE_TESTS: false + # - job: WindowsIntegrationTests + # displayName: Windows (Integration Tests) + # pool: vscode-1es-vscode-windows-2019 + # timeoutInMinutes: 60 + # variables: + # VSCODE_ARCH: x64 + # steps: + # - template: win32/product-build-win32.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: false + # VSCODE_RUN_INTEGRATION_TESTS: true + # VSCODE_RUN_SMOKE_TESTS: false + # - job: WindowsSmokeTests + # displayName: Windows (Smoke Tests) + # pool: vscode-1es-vscode-windows-2019 + # timeoutInMinutes: 60 + # variables: + # VSCODE_ARCH: x64 + # steps: + # - template: win32/product-build-win32.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: false + # VSCODE_RUN_INTEGRATION_TESTS: false + # VSCODE_RUN_SMOKE_TESTS: true diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index 1fd9b0441de..381d49ee75a 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -116,12 +116,13 @@ steps: GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Compile & Hygiene - - script: | - set -e - yarn --cwd test/smoke compile - yarn --cwd test/integration/browser compile - displayName: Compile test suites - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + yarn --cwd test/smoke compile + yarn --cwd test/integration/browser compile + displayName: Compile test suites + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - task: AzureCLI@2 @@ -151,16 +152,18 @@ steps: ./build/azure-pipelines/common/extract-telemetry.sh displayName: Extract Telemetry - - script: | - set -e - tar -cz --ignore-failed-read -f $(Build.ArtifactStagingDirectory)/compilation.tar.gz .build out-* test/integration/browser/out test/smoke/out test/automation/out - displayName: Compress compilation artifact + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + tar -cz --ignore-failed-read -f $(Build.ArtifactStagingDirectory)/compilation.tar.gz .build out-* test/integration/browser/out test/smoke/out test/automation/out + displayName: Compress compilation artifact - - task: PublishPipelineArtifact@1 - inputs: - targetPath: $(Build.ArtifactStagingDirectory)/compilation.tar.gz - artifactName: Compilation - displayName: Publish compilation artifact + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - task: PublishPipelineArtifact@1 + inputs: + targetPath: $(Build.ArtifactStagingDirectory)/compilation.tar.gz + artifactName: Compilation + displayName: Publish compilation artifact - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - script: | diff --git a/build/azure-pipelines/win32/product-build-win32-test.yml b/build/azure-pipelines/win32/product-build-win32-test.yml index 9dc50f8bcc4..59c91cd2b13 100644 --- a/build/azure-pipelines/win32/product-build-win32-test.yml +++ b/build/azure-pipelines/win32/product-build-win32-test.yml @@ -1,4 +1,6 @@ parameters: + - name: VSCODE_QUALITY + type: string - name: VSCODE_RUN_UNIT_TESTS type: boolean - name: VSCODE_RUN_INTEGRATION_TESTS @@ -15,29 +17,51 @@ steps: displayName: Download Electron and Playwright - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn electron $(VSCODE_ARCH) } - exec { .\scripts\test.bat --build --tfs "Unit Tests" } - displayName: Run unit tests (Electron) - timeoutInMinutes: 15 + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn electron $(VSCODE_ARCH) } + exec { .\scripts\test.bat --tfs "Unit Tests" } + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 - - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn test-node --build } - displayName: Run unit tests (node.js) - timeoutInMinutes: 15 + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn test-node } + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 - - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn test-browser-no-install --sequential --build --browser chromium --browser firefox --tfs "Browser Unit Tests" } - displayName: Run unit tests (Browser, Chromium & Firefox) - timeoutInMinutes: 20 + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { node test/unit/browser/index.js --sequential --browser chromium --browser firefox --tfs "Browser Unit Tests" } + displayName: Run unit tests (Browser, Chromium & Firefox) + timeoutInMinutes: 20 + + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn electron $(VSCODE_ARCH) } + exec { .\scripts\test.bat --build --tfs "Unit Tests" } + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn test-node --build } + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn test-browser-no-install --sequential --build --browser chromium --browser firefox --tfs "Browser Unit Tests" } + displayName: Run unit tests (Browser, Chromium & Firefox) + timeoutInMinutes: 20 - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - powershell: | @@ -64,38 +88,58 @@ steps: } displayName: Build integration tests - - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - - powershell: | - # Figure out the full absolute path of the product we just built - # including the remote server and configure the integration tests - # to run with these builds instead of running out of sources. - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" - $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json - $AppNameShort = $AppProductJson.nameShort - exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\scripts\test-integration.bat --build --tfs "Integration Tests" } - displayName: Run integration tests (Electron) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { .\scripts\test-integration.bat --tfs "Integration Tests" } + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 - - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)"; .\scripts\test-web-integration.bat --browser firefox } - displayName: Run integration tests (Browser, Firefox) - timeoutInMinutes: 20 + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { .\scripts\test-web-integration.bat --browser firefox } + displayName: Run integration tests (Browser, Firefox) + timeoutInMinutes: 20 - - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" - $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json - $AppNameShort = $AppProductJson.nameShort - exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\scripts\test-remote-integration.bat } - displayName: Run integration tests (Remote) - timeoutInMinutes: 20 + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { .\scripts\test-remote-integration.bat } + displayName: Run integration tests (Remote) + timeoutInMinutes: 20 + + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - powershell: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" + $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json + $AppNameShort = $AppProductJson.nameShort + exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\scripts\test-integration.bat --build --tfs "Integration Tests" } + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)"; .\scripts\test-web-integration.bat --browser firefox } + displayName: Run integration tests (Browser, Firefox) + timeoutInMinutes: 20 + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" + $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json + $AppNameShort = $AppProductJson.nameShort + exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\scripts\test-remote-integration.bat } + displayName: Run integration tests (Remote) + timeoutInMinutes: 20 - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - powershell: | @@ -105,36 +149,47 @@ steps: continueOnError: true condition: succeededOrFailed() - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)" - exec { yarn smoketest-no-compile --web --tracing --headless } - displayName: Run smoke tests (Browser, Chromium) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn --cwd test/smoke compile } + displayName: Compile smoke tests - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" - exec { yarn smoketest-no-compile --tracing --build "$AppRoot" } - displayName: Run smoke tests (Electron) - timeoutInMinutes: 20 + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn smoketest-no-compile --tracing } + displayName: Run smoke tests (Electron) + timeoutInMinutes: 20 - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" - $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)" - exec { yarn gulp compile-extension:vscode-test-resolver } - exec { yarn smoketest-no-compile --tracing --remote --build "$AppRoot" } - displayName: Run smoke tests (Remote) - timeoutInMinutes: 20 + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" + exec { yarn smoketest-no-compile --tracing --build "$AppRoot" } + displayName: Run smoke tests (Electron) + timeoutInMinutes: 20 + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)" + exec { yarn smoketest-no-compile --web --tracing --headless } + displayName: Run smoke tests (Browser, Chromium) + timeoutInMinutes: 20 + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" + $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)" + exec { yarn gulp compile-extension:vscode-test-resolver } + exec { yarn smoketest-no-compile --tracing --remote --build "$AppRoot" } + displayName: Run smoke tests (Remote) + timeoutInMinutes: 20 - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - powershell: | . build/azure-pipelines/win32/exec.ps1 exec {.\build\azure-pipelines\win32\listprocesses.bat } @@ -156,7 +211,6 @@ steps: continueOnError: true condition: failed() - - ${{ if or(eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: # In order to properly symbolify above crash reports # (if any), we need the compiled native modules too - task: PublishPipelineArtifact@0 @@ -172,7 +226,6 @@ steps: continueOnError: true condition: failed() - - ${{ if or(eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: - task: PublishPipelineArtifact@0 inputs: targetPath: .build\logs diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 65504f03ecd..41f0a8da8c2 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -11,6 +11,11 @@ parameters: type: boolean steps: + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - checkout: self + fetchDepth: 1 + retryCountOnTaskFailure: 3 + - task: NodeTool@0 inputs: versionSpec: "16.x" @@ -28,17 +33,19 @@ steps: KeyVaultName: vscode SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password" - - task: DownloadPipelineArtifact@2 - inputs: - artifact: Compilation - path: $(Build.ArtifactStagingDirectory) - displayName: Download compilation output + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - task: DownloadPipelineArtifact@2 + inputs: + artifact: Compilation + path: $(Build.ArtifactStagingDirectory) + displayName: Download compilation output - - task: ExtractFiles@1 - displayName: Extract compilation output - inputs: - archiveFilePatterns: "$(Build.ArtifactStagingDirectory)/compilation.tar.gz" - cleanDestinationFolder: false + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - task: ExtractFiles@1 + displayName: Extract compilation output + inputs: + archiveFilePatterns: "$(Build.ArtifactStagingDirectory)/compilation.tar.gz" + cleanDestinationFolder: false - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - powershell: | @@ -69,6 +76,7 @@ steps: displayName: Merge distro - powershell: | + if (!(Test-Path ".build")) { New-Item -Path ".build" -ItemType Directory } "$(VSCODE_ARCH)" | Out-File -Encoding ascii -NoNewLine .build\arch "$env:ENABLE_TERRAPIN" | Out-File -Encoding ascii -NoNewLine .build\terrapin node build/azure-pipelines/common/computeNodeModulesCacheKey.js > .build/yarnlockhash @@ -127,20 +135,29 @@ steps: exec { node build/azure-pipelines/mixin } displayName: Mix in quality - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { node build\lib\policies } - displayName: Generate Group Policy definitions - condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) + - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { node build\lib\policies } + displayName: Generate Group Policy definitions - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" - exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-min-ci" } - echo "##vso[task.setvariable variable=CodeSigningFolderPath]$(agent.builddirectory)/VSCode-win32-$(VSCODE_ARCH)" - displayName: Build + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" + exec { yarn gulp "transpile-client" "transpile-extensions" } + displayName: Transpile + + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" + exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-min-ci" } + echo "##vso[task.setvariable variable=CodeSigningFolderPath]$(agent.builddirectory)/VSCode-win32-$(VSCODE_ARCH)" + displayName: Build - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - powershell: | @@ -158,19 +175,21 @@ steps: displayName: Mix in quality condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" - exec { yarn gulp "vscode-reh-win32-$(VSCODE_ARCH)-min-ci" } - exec { yarn gulp "vscode-reh-web-win32-$(VSCODE_ARCH)-min-ci" } - echo "##vso[task.setvariable variable=CodeSigningFolderPath]$(CodeSigningFolderPath),$(agent.builddirectory)/vscode-reh-win32-$(VSCODE_ARCH)" - displayName: Build Server - condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" + exec { yarn gulp "vscode-reh-win32-$(VSCODE_ARCH)-min-ci" } + exec { yarn gulp "vscode-reh-web-win32-$(VSCODE_ARCH)-min-ci" } + echo "##vso[task.setvariable variable=CodeSigningFolderPath]$(CodeSigningFolderPath),$(agent.builddirectory)/vscode-reh-win32-$(VSCODE_ARCH)" + displayName: Build Server + condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) - ${{ if or(eq(parameters.VSCODE_RUN_UNIT_TESTS, true), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: - template: product-build-win32-test.yml parameters: + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} VSCODE_RUN_UNIT_TESTS: ${{ parameters.VSCODE_RUN_UNIT_TESTS }} VSCODE_RUN_INTEGRATION_TESTS: ${{ parameters.VSCODE_RUN_INTEGRATION_TESTS }} VSCODE_RUN_SMOKE_TESTS: ${{ parameters.VSCODE_RUN_SMOKE_TESTS }} From a260dc7b3effcbd1a635ee8df49bd968ccb76be5 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 19 Jul 2022 15:54:14 +0200 Subject: [PATCH 044/197] joh/familiar sparrow (#155613) * rename to `isFileTemplate` * add code snippet provider for file templates, fix setting model mode https://github.com/microsoft/vscode/issues/145929 --- .../browser/untitledTextEditorHint.ts | 6 +- ...ileSnippets.ts => fileTemplateSnippets.ts} | 18 +-- .../browser/commands/surroundWithSnippet.ts | 83 +---------- .../browser/snippetCodeActionProvider.ts | 139 ++++++++++++++++++ .../snippets/browser/snippets.contribution.ts | 14 +- .../contrib/snippets/browser/snippets.ts | 2 +- .../contrib/snippets/browser/snippetsFile.ts | 8 +- .../snippets/browser/snippetsService.ts | 2 +- 8 files changed, 166 insertions(+), 106 deletions(-) rename src/vs/workbench/contrib/snippets/browser/commands/{emptyFileSnippets.ts => fileTemplateSnippets.ts} (86%) create mode 100644 src/vs/workbench/contrib/snippets/browser/snippetCodeActionProvider.ts diff --git a/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts b/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts index bac3e753519..99cd7d942c9 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts @@ -20,7 +20,7 @@ import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IContentActionHandler, renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; -import { SelectSnippetForEmptyFile } from 'vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets'; +import { ApplyFileSnippetAction } from 'vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets'; const $ = dom.$; @@ -136,7 +136,7 @@ class UntitledTextEditorHintContentWidget implements IContentWidget { this.domNode.append(hintElement); // ugly way to associate keybindings... - const keybindingsLookup = [ChangeLanguageAction.ID, SelectSnippetForEmptyFile.Id, 'welcome.showNewFileEntries']; + const keybindingsLookup = [ChangeLanguageAction.ID, ApplyFileSnippetAction.Id, 'welcome.showNewFileEntries']; for (const anchor of hintElement.querySelectorAll('A')) { (anchor).style.cursor = 'pointer'; const id = keybindingsLookup.shift(); @@ -156,7 +156,7 @@ class UntitledTextEditorHintContentWidget implements IContentWidget { const snippetOnClickOrTab = async (e: MouseEvent) => { e.stopPropagation(); this.editor.focus(); - this.commandService.executeCommand(SelectSnippetForEmptyFile.Id, { from: 'hint' }); + this.commandService.executeCommand(ApplyFileSnippetAction.Id, { from: 'hint' }); }; const chooseEditorOnClickOrTap = async (e: MouseEvent) => { diff --git a/src/vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets.ts b/src/vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets.ts similarity index 86% rename from src/vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets.ts rename to src/vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets.ts index 963c92e60cb..2074eef20bd 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets.ts @@ -7,6 +7,7 @@ import { groupBy, isFalsyOrEmpty } from 'vs/base/common/arrays'; import { compare } from 'vs/base/common/strings'; import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { ILanguageService } from 'vs/editor/common/languages/language'; +import { IModelService } from 'vs/editor/common/services/model'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { localize } from 'vs/nls'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -16,16 +17,16 @@ import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets import { Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -export class SelectSnippetForEmptyFile extends SnippetsAction { +export class ApplyFileSnippetAction extends SnippetsAction { - static readonly Id = 'workbench.action.populateFromSnippet'; + static readonly Id = 'workbench.action.populateFileFromSnippet'; constructor() { super({ - id: SelectSnippetForEmptyFile.Id, + id: ApplyFileSnippetAction.Id, title: { - value: localize('label', 'Populate from Snippet'), - original: 'Populate from Snippet' + value: localize('label', 'Populate File from Snippet'), + original: 'Populate File from Snippet' }, f1: true, }); @@ -36,13 +37,14 @@ export class SelectSnippetForEmptyFile extends SnippetsAction { const quickInputService = accessor.get(IQuickInputService); const editorService = accessor.get(IEditorService); const langService = accessor.get(ILanguageService); + const modelService = accessor.get(IModelService); const editor = getCodeEditor(editorService.activeTextEditorControl); if (!editor || !editor.hasModel()) { return; } - const snippets = await snippetService.getSnippets(undefined, { topLevelSnippets: true, noRecencySort: true, includeNoPrefixSnippets: true }); + const snippets = await snippetService.getSnippets(undefined, { fileTemplateSnippets: true, noRecencySort: true, includeNoPrefixSnippets: true }); if (snippets.length === 0) { return; } @@ -60,9 +62,7 @@ export class SelectSnippetForEmptyFile extends SnippetsAction { }]); // set language if possible - if (langService.isRegisteredLanguageId(selection.langId)) { - editor.getModel().setMode(selection.langId); - } + modelService.setMode(editor.getModel(), langService.createById(selection.langId)); } } diff --git a/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts b/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts index 7a771724f3a..bdd36872158 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts @@ -3,28 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable } from 'vs/base/common/lifecycle'; 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 { Selection } from 'vs/editor/common/core/selection'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { CodeAction, CodeActionList, CodeActionProvider } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; -import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/browser/types'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { localize } from 'vs/nls'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { SnippetEditorAction } from 'vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions'; import { pickSnippet } from 'vs/workbench/contrib/snippets/browser/snippetPicker'; import { Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { ISnippetsService } from '../snippets'; -async function getSurroundableSnippets(snippetsService: ISnippetsService, model: ITextModel, position: Position, includeDisabledSnippets: boolean): Promise { +export async function getSurroundableSnippets(snippetsService: ISnippetsService, model: ITextModel, position: Position, includeDisabledSnippets: boolean): Promise { const { lineNumber, column } = position; model.tokenization.tokenizeIfCheap(lineNumber); @@ -83,77 +76,3 @@ export class SurroundWithSnippetEditorAction extends SnippetEditorAction { snippetsService.updateUsageTimestamp(snippet); } } - - -export class SurroundWithSnippetCodeActionProvider implements CodeActionProvider, IWorkbenchContribution { - - private static readonly _MAX_CODE_ACTIONS = 4; - - private static readonly _overflowCommandCodeAction: CodeAction = { - kind: CodeActionKind.Refactor.value, - title: SurroundWithSnippetEditorAction.options.title.value, - command: { - id: SurroundWithSnippetEditorAction.options.id, - title: SurroundWithSnippetEditorAction.options.title.value, - }, - }; - - private readonly _registration: IDisposable; - - constructor( - @ISnippetsService private readonly _snippetService: ISnippetsService, - @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, - ) { - this._registration = languageFeaturesService.codeActionProvider.register('*', this); - } - - dispose(): void { - this._registration.dispose(); - } - - async provideCodeActions(model: ITextModel, range: Range | Selection): Promise { - - if (range.isEmpty()) { - return undefined; - } - - const position = Selection.isISelection(range) ? range.getPosition() : range.getStartPosition(); - const snippets = await getSurroundableSnippets(this._snippetService, model, position, false); - if (!snippets.length) { - return undefined; - } - - const actions: CodeAction[] = []; - const hasMore = snippets.length > SurroundWithSnippetCodeActionProvider._MAX_CODE_ACTIONS; - const len = Math.min(snippets.length, SurroundWithSnippetCodeActionProvider._MAX_CODE_ACTIONS); - - for (let i = 0; i < len; i++) { - actions.push(this._makeCodeActionForSnippet(snippets[i], model, range)); - } - if (hasMore) { - actions.push(SurroundWithSnippetCodeActionProvider._overflowCommandCodeAction); - } - return { - actions, - dispose() { } - }; - } - - private _makeCodeActionForSnippet(snippet: Snippet, model: ITextModel, range: IRange): CodeAction { - return { - title: localize('codeAction', "Surround With: {0}", snippet.name), - kind: CodeActionKind.Refactor.value, - edit: { - edits: [{ - versionId: model.getVersionId(), - resource: model.uri, - textEdit: { - range, - text: snippet.body, - insertAsSnippet: true, - } - }] - } - }; - } -} diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCodeActionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCodeActionProvider.ts new file mode 100644 index 00000000000..08afb39eba9 --- /dev/null +++ b/src/vs/workbench/contrib/snippets/browser/snippetCodeActionProvider.ts @@ -0,0 +1,139 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IRange, Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; +import { CodeAction, CodeActionList, CodeActionProvider, WorkspaceEdit } from 'vs/editor/common/languages'; +import { ITextModel } from 'vs/editor/common/model'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/browser/types'; +import { localize } from 'vs/nls'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { ApplyFileSnippetAction } from 'vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets'; +import { getSurroundableSnippets, SurroundWithSnippetEditorAction } from 'vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet'; +import { Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; +import { ISnippetsService } from './snippets'; + +class SurroundWithSnippetCodeActionProvider implements CodeActionProvider { + + private static readonly _MAX_CODE_ACTIONS = 4; + + private static readonly _overflowCommandCodeAction: CodeAction = { + kind: CodeActionKind.Refactor.value, + title: SurroundWithSnippetEditorAction.options.title.value, + command: { + id: SurroundWithSnippetEditorAction.options.id, + title: SurroundWithSnippetEditorAction.options.title.value, + }, + }; + + constructor(@ISnippetsService private readonly _snippetService: ISnippetsService) { } + + async provideCodeActions(model: ITextModel, range: Range | Selection): Promise { + + if (range.isEmpty()) { + return undefined; + } + + const position = Selection.isISelection(range) ? range.getPosition() : range.getStartPosition(); + const snippets = await getSurroundableSnippets(this._snippetService, model, position, false); + if (!snippets.length) { + return undefined; + } + + const actions: CodeAction[] = []; + for (const snippet of snippets) { + if (actions.length >= SurroundWithSnippetCodeActionProvider._MAX_CODE_ACTIONS) { + actions.push(SurroundWithSnippetCodeActionProvider._overflowCommandCodeAction); + break; + } + actions.push({ + title: localize('codeAction', "Surround With: {0}", snippet.name), + kind: CodeActionKind.Refactor.value, + edit: asWorkspaceEdit(model, range, snippet) + }); + } + + return { + actions, + dispose() { } + }; + } +} + +class FileTemplateCodeActionProvider implements CodeActionProvider { + + private static readonly _MAX_CODE_ACTIONS = 4; + + private static readonly _overflowCommandCodeAction: CodeAction = { + title: localize('overflow.start.title', 'Start with Snippet'), + kind: CodeActionKind.Refactor.value, + command: { + id: ApplyFileSnippetAction.Id, + title: '' + } + }; + + readonly providedCodeActionKinds?: readonly string[] = [CodeActionKind.Refactor.value]; + + constructor(@ISnippetsService private readonly _snippetService: ISnippetsService) { } + + async provideCodeActions(model: ITextModel) { + if (model.getValueLength() !== 0) { + return undefined; + } + + const snippets = await this._snippetService.getSnippets(model.getLanguageId(), { fileTemplateSnippets: true, includeNoPrefixSnippets: true }); + const actions: CodeAction[] = []; + for (const snippet of snippets) { + if (actions.length >= FileTemplateCodeActionProvider._MAX_CODE_ACTIONS) { + actions.push(FileTemplateCodeActionProvider._overflowCommandCodeAction); + break; + } + actions.push({ + title: localize('title', 'Start with: {0}', snippet.name), + kind: CodeActionKind.Refactor.value, + edit: asWorkspaceEdit(model, model.getFullModelRange(), snippet) + }); + } + return { + actions, + dispose() { } + }; + } +} + +function asWorkspaceEdit(model: ITextModel, range: IRange, snippet: Snippet): WorkspaceEdit { + return { + edits: [{ + versionId: model.getVersionId(), + resource: model.uri, + textEdit: { + range, + text: snippet.body, + insertAsSnippet: true, + } + }] + }; +} + +export class SnippetCodeActions implements IWorkbenchContribution { + + private readonly _store = new DisposableStore(); + + constructor( + @IInstantiationService instantiationService: IInstantiationService, + @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, + ) { + this._store.add(languageFeaturesService.codeActionProvider.register('*', instantiationService.createInstance(SurroundWithSnippetCodeActionProvider))); + this._store.add(languageFeaturesService.codeActionProvider.register('*', instantiationService.createInstance(FileTemplateCodeActionProvider))); + } + + dispose(): void { + this._store.dispose(); + } +} diff --git a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts index 39f0c8233c5..dfd4bb7e37a 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts @@ -12,9 +12,10 @@ import * as JSONContributionRegistry from 'vs/platform/jsonschemas/common/jsonCo import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { ConfigureSnippets } from 'vs/workbench/contrib/snippets/browser/commands/configureSnippets'; -import { SelectSnippetForEmptyFile } from 'vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets'; +import { ApplyFileSnippetAction } from 'vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets'; import { InsertSnippetAction } from 'vs/workbench/contrib/snippets/browser/commands/insertSnippet'; -import { SurroundWithSnippetCodeActionProvider, SurroundWithSnippetEditorAction } from 'vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet'; +import { SurroundWithSnippetEditorAction } from 'vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet'; +import { SnippetCodeActions } from 'vs/workbench/contrib/snippets/browser/snippetCodeActionProvider'; import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets'; import { SnippetsService } from 'vs/workbench/contrib/snippets/browser/snippetsService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -29,10 +30,11 @@ registerAction2(InsertSnippetAction); CommandsRegistry.registerCommandAlias('editor.action.showSnippets', 'editor.action.insertSnippet'); registerAction2(SurroundWithSnippetEditorAction); registerAction2(ConfigureSnippets); -registerAction2(SelectSnippetForEmptyFile); +registerAction2(ApplyFileSnippetAction); // workbench contribs -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SurroundWithSnippetCodeActionProvider, LifecyclePhase.Restored); +Registry.as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(SnippetCodeActions, LifecyclePhase.Restored); // schema const languageScopeSchemaId = 'vscode://schemas/snippets'; @@ -42,8 +44,8 @@ const snippetSchemaProperties: IJSONSchemaMap = { description: nls.localize('snippetSchema.json.prefix', 'The prefix to use when selecting the snippet in intellisense'), type: ['string', 'array'] }, - isTopLevel: { - description: nls.localize('snippetSchema.json.isTopLevel', 'The snippet is only applicable to empty files.'), + isFileTemplate: { + description: nls.localize('snippetSchema.json.isFileTemplate', 'The snippet is meant to populate or replace a whole file'), type: 'boolean' }, body: { diff --git a/src/vs/workbench/contrib/snippets/browser/snippets.ts b/src/vs/workbench/contrib/snippets/browser/snippets.ts index fa485ab0f2f..a11c4ccf6a8 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippets.ts @@ -12,7 +12,7 @@ export interface ISnippetGetOptions { includeDisabledSnippets?: boolean; includeNoPrefixSnippets?: boolean; noRecencySort?: boolean; - topLevelSnippets?: boolean; + fileTemplateSnippets?: boolean; } export interface ISnippetsService { diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts index b6ce272ef28..070fd986cdf 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts @@ -105,7 +105,7 @@ export class Snippet { readonly prefixLow: string; constructor( - readonly isTopLevel: boolean, + readonly isFileTemplate: boolean, readonly scopes: string[], readonly name: string, readonly prefix: string, @@ -143,7 +143,7 @@ export class Snippet { interface JsonSerializedSnippet { - isTopLevel?: boolean; + isFileTemplate?: boolean; body: string | string[]; scope?: string; prefix: string | string[] | undefined; @@ -261,7 +261,7 @@ export class SnippetFile { private _parseSnippet(name: string, snippet: JsonSerializedSnippet, bucket: Snippet[]): void { - let { isTopLevel, prefix, body, description } = snippet; + let { isFileTemplate, prefix, body, description } = snippet; if (!prefix) { prefix = ''; @@ -306,7 +306,7 @@ export class SnippetFile { for (const _prefix of Array.isArray(prefix) ? prefix : Iterable.single(prefix)) { bucket.push(new Snippet( - Boolean(isTopLevel), + Boolean(isFileTemplate), scopes, name, _prefix, diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index 3c5ed85f665..069f3bf44f3 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -318,7 +318,7 @@ export class SnippetsService implements ISnippetsService { // enabled or disabled wanted continue; } - if (typeof opts?.topLevelSnippets === 'boolean' && opts.topLevelSnippets !== snippet.isTopLevel) { + if (typeof opts?.fileTemplateSnippets === 'boolean' && opts.fileTemplateSnippets !== snippet.isFileTemplate) { // isTopLevel requested but mismatching continue; } From f8aeb2013e6184b64bfd7455f5df6799c7c30a11 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Jul 2022 15:57:35 +0200 Subject: [PATCH 045/197] merge editor - use `basename` when invoked from CLI (#155614) --- .../contrib/mergeEditor/browser/view/mergeEditor.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index d98aa657980..9212069bb61 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -13,7 +13,7 @@ import { BugIndicatingError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { autorunWithStore, IObservable } from 'vs/base/common/observable'; -import { isEqual } from 'vs/base/common/resources'; +import { basename, isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/mergeEditor'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -549,13 +549,13 @@ export class MergeEditorResolverContribution extends Disposable { mergeEditor.base.resource, { uri: mergeEditor.input1.resource, - title: localize('input1Title', "First Version"), + title: basename(mergeEditor.input1.resource), description: '', detail: '' }, { uri: mergeEditor.input2.resource, - title: localize('input2Title', "Second Version"), + title: basename(mergeEditor.input2.resource), description: '', detail: '' }, From 2d38d3f2dc1fc2b20544a882e77cd2c05e9a5efa Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 19 Jul 2022 16:12:55 +0200 Subject: [PATCH 046/197] fix https://github.com/microsoft/vscode/issues/155572 (#155617) --- .../contrib/snippets/browser/commands/insertSnippet.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/snippets/browser/commands/insertSnippet.ts b/src/vs/workbench/contrib/snippets/browser/commands/insertSnippet.ts index aba3f4c5582..5b10727b169 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/insertSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/insertSnippet.ts @@ -79,7 +79,7 @@ export class InsertSnippetAction extends SnippetEditorAction { }); } - async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, arg: any) { + async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any[]) { const languageService = accessor.get(ILanguageService); const snippetService = accessor.get(ISnippetsService); @@ -94,7 +94,7 @@ export class InsertSnippetAction extends SnippetEditorAction { const snippet = await new Promise((resolve, reject) => { const { lineNumber, column } = editor.getPosition(); - const { snippet, name, langId } = Args.fromUser(arg); + const { snippet, name, langId } = Args.fromUser(args[0]); if (snippet) { return resolve(new Snippet( From baf33bd4551101b08fd092f9d9d84662d9732b40 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 19 Jul 2022 07:43:19 -0700 Subject: [PATCH 047/197] Improve command navigation visual feedback Fixes #153591 --- .../terminal/browser/media/terminal.css | 5 +++++ .../browser/xterm/commandNavigationAddon.ts | 20 +++++++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 16275cb5114..b186c032261 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -449,6 +449,11 @@ .terminal-scroll-highlight { left: 0; right: 0; + border-left: 5px solid #ffffff; + border-left-width: 5px !important; +} + +.terminal-scroll-highlight-outline { border: 1px solid #ffffff; } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts index 83de46d909f..7b4caa70342 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts @@ -73,6 +73,8 @@ export class CommandNavigationAddon extends Disposable implements ICommandTracke // Clear the current marker so successive focus/selection actions are performed from the // bottom of the buffer this._currentMarker = Boundary.Bottom; + this._navigationDecoration?.dispose(); + this._navigationDecoration = undefined; this._selectionStart = null; } @@ -110,6 +112,7 @@ export class CommandNavigationAddon extends Disposable implements ICommandTracke if (markerIndex < 0) { this._currentMarker = Boundary.Top; this._terminal.scrollToTop(); + this.clearMarker(); return; } @@ -151,6 +154,7 @@ export class CommandNavigationAddon extends Disposable implements ICommandTracke if (markerIndex >= this._getCommandMarkers().length) { this._currentMarker = Boundary.Bottom; this._terminal.scrollToBottom(); + this.clearMarker(); return; } @@ -178,12 +182,14 @@ export class CommandNavigationAddon extends Disposable implements ICommandTracke }); this._navigationDecoration = decoration; if (decoration) { - const isRendered = false; + let renderedElement: HTMLElement | undefined; + decoration.onRender(element => { - if (!isRendered) { - // TODO: Remove when https://github.com/xtermjs/xterm.js/issues/3686 is fixed - if (!element.classList.contains('xterm-decoration-overview-ruler')) { - element.classList.add('terminal-scroll-highlight'); + if (!renderedElement) { + renderedElement = element; + element.classList.add('terminal-scroll-highlight', 'terminal-scroll-highlight-outline'); + if (this._terminal?.element) { + element.style.marginLeft = `-${getComputedStyle(this._terminal.element).paddingLeft}`; } } }); @@ -194,7 +200,9 @@ export class CommandNavigationAddon extends Disposable implements ICommandTracke }); // Number picked to align with symbol highlight in the editor timeout(350).then(() => { - decoration.dispose(); + if (renderedElement) { + renderedElement.classList.remove('terminal-scroll-highlight-outline'); + } }); } } From becd1f58bcb464277ee3b3c1c38df68e61c97767 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 19 Jul 2022 07:54:18 -0700 Subject: [PATCH 048/197] Fix shell integration left padding on splits and editors Fixes #153570 --- .../contrib/terminal/browser/terminalView.ts | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index bb3b8225a5f..4b2212066f1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -112,11 +112,11 @@ export class TerminalViewPane extends ViewPane { this._terminalTabbedView?.rerenderTabs(); } })); - _configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(TerminalSettingId.ShellIntegrationDecorationsEnabled) || e.affectsConfiguration(TerminalSettingId.ShellIntegrationEnabled)) { - this._updateForShellIntegration(); + this._register(this._configurationService.onDidChangeConfiguration(e => { + if (this._parentDomElement && (e.affectsConfiguration(TerminalSettingId.ShellIntegrationDecorationsEnabled) || e.affectsConfiguration(TerminalSettingId.ShellIntegrationEnabled))) { + this._updateForShellIntegration(this._parentDomElement); } - }); + })); this._register(this._terminalService.onDidCreateInstance((i) => { i.capabilities.onDidAddCapability(c => { if (c === TerminalCapability.CommandDetection && !this._gutterDecorationsEnabled()) { @@ -124,15 +124,10 @@ export class TerminalViewPane extends ViewPane { } }); })); - this._updateForShellIntegration(); } - private _updateForShellIntegration() { - if (this._gutterDecorationsEnabled()) { - this._parentDomElement?.classList.add('shell-integration'); - } else { - this._parentDomElement?.classList.remove('shell-integration'); - } + private _updateForShellIntegration(container: HTMLElement) { + container.classList.toggle('shell-integration', this._gutterDecorationsEnabled()); } private _gutterDecorationsEnabled(): boolean { @@ -143,6 +138,9 @@ export class TerminalViewPane extends ViewPane { override renderBody(container: HTMLElement): void { super.renderBody(container); + if (!this._parentDomElement) { + this._updateForShellIntegration(container); + } this._parentDomElement = container; this._parentDomElement.classList.add('integrated-terminal'); this._fontStyleElement = document.createElement('style'); From ebbde059650a8f1451a0bee0d34aa10062ac1943 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 19 Jul 2022 17:56:51 +0200 Subject: [PATCH 049/197] Batch all code block updates together and emit event ASAP (#155629) Fixes #154577: Batch all code block updates together and emit event ASAP (ref #152010) --- src/vs/base/browser/markdownRenderer.ts | 48 ++++++++----------- .../browser/markdownRenderer.ts | 7 +-- 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 2e809e2ad48..e43d27ac623 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -9,8 +9,6 @@ import { DomEmitter } from 'vs/base/browser/event'; import { createElement, FormattedTextRenderOptions } from 'vs/base/browser/formattedTextRenderer'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; -import { raceCancellation } from 'vs/base/common/async'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; import { IMarkdownString, escapeDoubleQuotes, parseHrefAndDimensions, removeMarkdownEscapes } from 'vs/base/common/htmlContent'; @@ -44,8 +42,6 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende const disposables = new DisposableStore(); let isDisposed = false; - const cts = disposables.add(new CancellationTokenSource()); - const element = createElement(options); const _uriMassage = function (part: string): string { @@ -96,11 +92,6 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende return uri.toString(); }; - // signal to code-block render that the - // element has been created - let signalInnerHTML: () => void; - const withInnerHTML = new Promise(c => signalInnerHTML = c); - const renderer = new marked.Renderer(); renderer.image = (href: string, title: string, text: string) => { @@ -146,24 +137,14 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende return `

${text}

`; }; + // Will collect [id, renderedElement] tuples + const codeBlocks: Promise<[string, HTMLElement]>[] = []; + if (options.codeBlockRenderer) { renderer.code = (code, lang) => { - const value = options.codeBlockRenderer!(lang ?? '', code); - // when code-block rendering is async we return sync - // but update the node with the real result later. const id = defaultGenerator.nextId(); - raceCancellation(Promise.all([value, withInnerHTML]), cts.token).then(values => { - if (!isDisposed && values) { - const span = element.querySelector(`div[data-code="${id}"]`); - if (span) { - DOM.reset(span, values[0]); - } - options.asyncRenderCallback?.(); - } - }).catch(() => { - // ignore - }); - + const value = options.codeBlockRenderer!(lang ?? '', code); + codeBlocks.push(value.then(element => [id, element])); return `
${escape(code)}
`; }; } @@ -277,8 +258,22 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende element.innerHTML = sanitizeRenderedMarkdown(markdown, markdownHtmlDoc.body.innerHTML) as unknown as string; - // signal that async code blocks can be now be inserted - signalInnerHTML!(); + if (codeBlocks.length > 0) { + Promise.all(codeBlocks).then((tuples) => { + if (isDisposed) { + return; + } + const renderedElements = new Map(tuples); + const placeholderElements = element.querySelectorAll(`div[data-code]`); + for (const placeholderElement of placeholderElements) { + const renderedElement = renderedElements.get(placeholderElement.dataset['code'] ?? ''); + if (renderedElement) { + DOM.reset(placeholderElement, renderedElement); + } + } + options.asyncRenderCallback?.(); + }); + } // signal size changes for image tags if (options.asyncRenderCallback) { @@ -294,7 +289,6 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende element, dispose: () => { isDisposed = true; - cts.cancel(); disposables.dispose(); } }; diff --git a/src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts b/src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts index f72e2293aa7..7220dbf1a4f 100644 --- a/src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts +++ b/src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts @@ -10,7 +10,7 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { onUnexpectedError } from 'vs/base/common/errors'; import { tokenizeToString } from 'vs/editor/common/languages/textToHtmlTokenizer'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { DebounceEmitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; @@ -38,10 +38,7 @@ export class MarkdownRenderer { } }); - private readonly _onDidRenderAsync = new DebounceEmitter({ - delay: 50, - merge: arr => { } - }); + private readonly _onDidRenderAsync = new Emitter(); readonly onDidRenderAsync = this._onDidRenderAsync.event; constructor( From 159a10972672fe6e3c874d0ee61741959a2b4f35 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Tue, 19 Jul 2022 12:26:12 -0400 Subject: [PATCH 050/197] Fix #155570 (#155620) --- src/vs/workbench/contrib/files/browser/views/explorerView.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index a9cd6b8a8c9..e90d052ae29 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -310,6 +310,8 @@ export class ExplorerView extends ViewPane implements IExplorerView { if (visible) { // Always refresh explorer when it becomes visible to compensate for missing file events #126817 await this.setTreeInput(); + // Update the collapse / expand button state + this.updateAnyCollapsedContext(); // Find resource to focus from active editor input if set this.selectActiveFile(true); } @@ -505,9 +507,10 @@ export class ExplorerView extends ViewPane implements IExplorerView { const navigationController = this.renderer.getCompressedNavigationController(element instanceof Array ? element[0] : element); navigationController?.updateCollapsed(e.node.collapsed); } + // Update showing expand / collapse button + this.updateAnyCollapsedContext(); })); - this._register(this.tree.onDidChangeCollapseState(() => this.updateAnyCollapsedContext())); this.updateAnyCollapsedContext(); this._register(this.tree.onMouseDblClick(e => { From 76b0152297cbf48e9216b3a28e71654cdd8b85a0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 19 Jul 2022 10:02:37 -0700 Subject: [PATCH 051/197] Show shell integration tooltip regardless of setting This assumption changed recently, we want to show this regardless of the setting. It should instead of driven by whether the terminal actually has activated it. Fixes #153550 --- .../contrib/terminal/browser/terminalTabsList.ts | 2 +- .../contrib/terminal/browser/terminalTooltip.ts | 7 +------ .../workbench/contrib/terminal/browser/terminalView.ts | 10 +++++----- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 14890b1b9bf..3bf39183419 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -308,7 +308,7 @@ class TerminalTabsRenderer implements IListRenderer this.updateLabel(e))); this._register(this._terminalService.onDidChangeInstanceTitle(e => { if (e === this._terminalGroupService.activeInstance) { - this._action.tooltip = getSingleTabTooltip(e, this._terminalService.configHelper.config.tabs.separator, configurationService); + this._action.tooltip = getSingleTabTooltip(e, this._terminalService.configHelper.config.tabs.separator); this.updateLabel(); } })); this._register(this._terminalService.onDidChangeInstanceCapability(e => { - this._action.tooltip = getSingleTabTooltip(e, this._terminalService.configHelper.config.tabs.separator, configurationService); + this._action.tooltip = getSingleTabTooltip(e, this._terminalService.configHelper.config.tabs.separator); this.updateLabel(e); })); @@ -547,11 +547,11 @@ function getSingleTabLabel(accessor: ServicesAccessor, instance: ITerminalInstan return `${label} $(${primaryStatus.icon.id})`; } -function getSingleTabTooltip(instance: ITerminalInstance | undefined, separator: string, configurationService: IConfigurationService): string { +function getSingleTabTooltip(instance: ITerminalInstance | undefined, separator: string): string { if (!instance) { return ''; } - const shellIntegrationString = getShellIntegrationTooltip(instance, false, configurationService); + const shellIntegrationString = getShellIntegrationTooltip(instance, false); const title = getSingleTabTitle(instance, separator); return shellIntegrationString ? title + shellIntegrationString : title; } From 65f7beffc67ab6516d0023b517d9e8d0804d1257 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Tue, 19 Jul 2022 10:31:09 -0700 Subject: [PATCH 052/197] fix on keybinding up and down, fix on keybinding for enter --- .../codeAction/browser/codeActionCommands.ts | 17 ++++++++ .../codeAction/browser/codeActionMenu.ts | 42 +++++++++++-------- .../codeAction/browser/codeActionUi.ts | 6 ++- 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts index 330bc3a8d4b..86e6aeb286b 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts @@ -141,6 +141,10 @@ export class QuickFixController extends Disposable implements IEditorContributio this._ui.getValue().navigateList(navUp); } + public selectedOption() { + this._ui.getValue().onEnter(); + } + public showCodeActions(trigger: CodeActionTrigger, actions: CodeActionSet, at: IAnchor | IPosition) { return this._ui.getValue().showCodeActionList(trigger, actions, at, { includeDisabledActions: false, fromLightbulb: false }); } @@ -543,4 +547,17 @@ registerEditorCommand(new CodeActionContribution({ } })); +registerEditorCommand(new CodeActionContribution({ + id: 'onEnterSelect', + precondition: Context.Visible, + handler(x) { + x.selectedOption(); + }, + kbOpts: { + weight: weight + 100000, + primary: KeyCode.Enter, + // secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow], + } +})); + diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 55e66c8b4c2..3f15040d903 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -167,7 +167,8 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { private _ctxMenuWidgetVisible!: IContextKey; private readonly editor: ICodeEditor; private viewItems: ICodeActionMenuItem[] = []; - private focusedItem!: number | undefined; + private focusedEnabledItem!: number; + private currSelectedItem!: number; public static readonly ID: string = 'editor.contrib.codeActionMenu'; @@ -288,6 +289,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { // resize observer - supports dynamic height but not width this.codeActionList.domFocus(); + this.focusedEnabledItem = 0; this.codeActionList.setFocus([this.viewItems[0].index]); const focusTracker = dom.trackFocus(element); const blurListener = focusTracker.onDidBlur(() => { @@ -297,48 +299,50 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { renderDisposables.add(blurListener); renderDisposables.add(focusTracker); - // this._ctxMenuWidgetVisible = Context.Visible.bindTo(this._contextKeyService.createScoped(element)); this._ctxMenuWidgetVisible.set(true); return renderDisposables; } protected focusPrevious(forceLoop?: Boolean) { - if (typeof this.focusedItem === 'undefined') { - this.focusedItem = this.viewItems[0].index; + if (typeof this.focusedEnabledItem === 'undefined') { + this.focusedEnabledItem = this.viewItems[0].index; } else if (this.viewItems.length <= 1) { return false; } - const startIndex = this.focusedItem; + const startIndex = this.focusedEnabledItem; let item: ICodeActionMenuItem; do { - this.focusedItem = this.focusedItem - 1; - if (this.focusedItem < 0) { - this.focusedItem = this.viewItems.length - 1; + this.focusedEnabledItem = this.focusedEnabledItem - 1; + console.log(this.focusedEnabledItem); + if (this.focusedEnabledItem < 0) { + this.focusedEnabledItem = this.viewItems.length - 1; } - item = this.viewItems[this.focusedItem]; + item = this.viewItems[this.focusedEnabledItem]; this.codeActionList.setFocus([item.index]); - } while (this.focusedItem !== startIndex && ((!item.isEnabled) || item.action.id === Separator.ID)); + this.currSelectedItem = item.index; + } while (this.focusedEnabledItem !== startIndex && ((!item.isEnabled) || item.action.id === Separator.ID)); return true; } protected focusNext(forceLoop?: Boolean) { - if (typeof this.focusedItem === 'undefined') { - this.focusedItem = this.viewItems.length - 1; + if (typeof this.focusedEnabledItem === 'undefined') { + this.focusedEnabledItem = this.viewItems.length - 1; } else if (this.viewItems.length <= 1) { return false; } - const startIndex = this.focusedItem; + const startIndex = this.focusedEnabledItem; let item: ICodeActionMenuItem; do { - this.focusedItem = (this.focusedItem + 1) % this.viewItems.length; - item = this.viewItems[this.focusedItem]; + this.focusedEnabledItem = (this.focusedEnabledItem + 1) % this.viewItems.length; + item = this.viewItems[this.focusedEnabledItem]; this.codeActionList.setFocus([item.index]); - } while (this.focusedItem !== startIndex && ((!item.isEnabled) || item.action.id === Separator.ID)); + this.currSelectedItem = item.index; + } while (this.focusedEnabledItem !== startIndex && ((!item.isEnabled) || item.action.id === Separator.ID)); return true; } @@ -351,6 +355,10 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { this.focusNext(); } + public onEnterSet() { + this.codeActionList.setSelection([this.currSelectedItem]); + } + override dispose() { this.codeActionList.dispose(); this._disposables.dispose(); @@ -360,7 +368,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { this._ctxMenuWidgetVisible.reset(); this.options = []; this.viewItems = []; - this.focusedItem = undefined; + this.focusedEnabledItem = 0; this._contextViewService.hideContextView(); this.dispose(); } diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts index b0086cf99ec..a3b8f57dcc0 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts @@ -63,7 +63,11 @@ export class CodeActionUi extends Disposable implements IEditorContribution { } public hideCodeActionWidget() { - this._codeActionWidget.getValue().dispose(); + this._codeActionWidget.getValue().hideCodeActionWidget(); + } + + public onEnter() { + this._codeActionWidget.getValue().onEnterSet(); } public navigateList(navUp: Boolean) { From 8feb40b9284c339e2d1b0a493641e603b7f84d3d Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Tue, 19 Jul 2022 10:43:05 -0700 Subject: [PATCH 053/197] Generate dependencies per package build for Debian (#147335) Fixes #13089 --- build/gulpfile.vscode.linux.js | 9 +- build/linux/debian/dep-lists.js | 156 ++++++++++++++++++ build/linux/debian/dep-lists.ts | 157 +++++++++++++++++++ build/linux/debian/dependencies-generator.js | 129 +++++++++++++++ build/linux/debian/dependencies-generator.ts | 145 +++++++++++++++++ build/linux/debian/install-sysroot.js | 81 ++++++++++ build/linux/debian/install-sysroot.ts | 90 +++++++++++ build/linux/debian/sysroots.js | 26 +++ build/linux/debian/sysroots.ts | 24 +++ build/linux/debian/types.js | 6 + build/linux/debian/types.ts | 6 + build/linux/rpm/dependencies-generator.js | 2 +- build/linux/rpm/dependencies-generator.ts | 2 +- resources/linux/debian/control.template | 3 +- 14 files changed, 832 insertions(+), 4 deletions(-) create mode 100644 build/linux/debian/dep-lists.js create mode 100644 build/linux/debian/dep-lists.ts create mode 100644 build/linux/debian/dependencies-generator.js create mode 100644 build/linux/debian/dependencies-generator.ts create mode 100644 build/linux/debian/install-sysroot.js create mode 100644 build/linux/debian/install-sysroot.ts create mode 100644 build/linux/debian/sysroots.js create mode 100644 build/linux/debian/sysroots.ts create mode 100644 build/linux/debian/types.js create mode 100644 build/linux/debian/types.ts diff --git a/build/gulpfile.vscode.linux.js b/build/gulpfile.vscode.linux.js index 210556eddb6..489d9ccfabd 100644 --- a/build/gulpfile.vscode.linux.js +++ b/build/gulpfile.vscode.linux.js @@ -16,6 +16,9 @@ const task = require('./lib/task'); const packageJson = require('../package.json'); const product = require('../product.json'); const rpmDependenciesGenerator = require('./linux/rpm/dependencies-generator'); +const debianDependenciesGenerator = require('./linux/debian/dependencies-generator'); +const sysrootInstaller = require('./linux/debian/install-sysroot'); +const debianRecommendedDependencies = require('./linux/debian/dep-lists').recommendedDeps; const path = require('path'); const root = path.dirname(__dirname); const commit = util.getVersion(root); @@ -74,12 +77,16 @@ function prepareDebPackage(arch) { let size = 0; const control = code.pipe(es.through( function (f) { size += f.isDirectory() ? 4096 : f.contents.length; }, - function () { + async function () { const that = this; + const sysroot = await sysrootInstaller.getSysroot(debArch); + const dependencies = debianDependenciesGenerator.getDependencies(binaryDir, product.applicationName, debArch, sysroot); gulp.src('resources/linux/debian/control.template', { base: '.' }) .pipe(replace('@@NAME@@', product.applicationName)) .pipe(replace('@@VERSION@@', packageJson.version + '-' + linuxPackageRevision)) .pipe(replace('@@ARCHITECTURE@@', debArch)) + .pipe(replace('@@DEPENDS@@', dependencies.join(', '))) + .pipe(replace('@@RECOMMENDS@@', debianRecommendedDependencies.join(', '))) .pipe(replace('@@INSTALLEDSIZE@@', Math.ceil(size / 1024))) .pipe(rename('DEBIAN/control')) .pipe(es.through(function (f) { that.emit('data', f); }, function () { that.emit('end'); })); diff --git a/build/linux/debian/dep-lists.js b/build/linux/debian/dep-lists.js new file mode 100644 index 00000000000..3db2ef1abff --- /dev/null +++ b/build/linux/debian/dep-lists.js @@ -0,0 +1,156 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.referenceGeneratedDepsByArch = exports.bundledDeps = exports.recommendedDeps = exports.additionalDeps = void 0; +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/additional_deps +// Additional dependencies not in the dpkg-shlibdeps output. +exports.additionalDeps = [ + 'ca-certificates', + 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libnss3 (>= 3.26)', + 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', + 'xdg-utils (>= 1.0.2)' // OS integration +]; +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/manual_recommends +// Dependencies that we can only recommend +// for now since some of the older distros don't support them. +exports.recommendedDeps = [ + 'libvulkan1' // Move to additionalDeps once support for Trusty and Jessie are dropped. +]; +// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/98.0.4758.109:chrome/installer/linux/BUILD.gn;l=64-80 +// and the Linux Archive build +// Shared library dependencies that we already bundle. +exports.bundledDeps = [ + 'libEGL.so', + 'libGLESv2.so', + 'libvulkan.so.1', + 'swiftshader_libEGL.so', + 'swiftshader_libGLESv2.so', + 'libvk_swiftshader.so', + 'libffmpeg.so' +]; +exports.referenceGeneratedDepsByArch = { + 'amd64': [ + 'ca-certificates', + 'libasound2 (>= 1.0.16)', + 'libatk-bridge2.0-0 (>= 2.5.3)', + 'libatk1.0-0 (>= 2.2.0)', + 'libatspi2.0-0 (>= 2.9.90)', + 'libc6 (>= 2.14)', + 'libc6 (>= 2.17)', + 'libc6 (>= 2.2.5)', + 'libcairo2 (>= 1.6.0)', + 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', + 'libdbus-1-3 (>= 1.5.12)', + 'libdrm2 (>= 2.4.38)', + 'libexpat1 (>= 2.0.1)', + 'libgbm1 (>= 8.1~0)', + 'libgcc1 (>= 1:3.0)', + 'libglib2.0-0 (>= 2.16.0)', + 'libglib2.0-0 (>= 2.39.4)', + 'libgtk-3-0 (>= 3.9.10)', + 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libnspr4 (>= 2:4.9-2~)', + 'libnss3 (>= 2:3.22)', + 'libnss3 (>= 3.26)', + 'libpango-1.0-0 (>= 1.14.0)', + 'libsecret-1-0 (>= 0.18)', + 'libx11-6', + 'libx11-6 (>= 2:1.4.99.1)', + 'libxcb1 (>= 1.9.2)', + 'libxcomposite1 (>= 1:0.4.4-1)', + 'libxdamage1 (>= 1:1.1)', + 'libxext6', + 'libxfixes3', + 'libxkbcommon0 (>= 0.4.1)', + 'libxkbfile1', + 'libxrandr2', + 'xdg-utils (>= 1.0.2)' + ], + 'armhf': [ + 'ca-certificates', + 'libasound2 (>= 1.0.16)', + 'libatk-bridge2.0-0 (>= 2.5.3)', + 'libatk1.0-0 (>= 2.2.0)', + 'libatspi2.0-0 (>= 2.9.90)', + 'libc6 (>= 2.17)', + 'libc6 (>= 2.4)', + 'libc6 (>= 2.9)', + 'libcairo2 (>= 1.6.0)', + 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', + 'libdbus-1-3 (>= 1.5.12)', + 'libdrm2 (>= 2.4.38)', + 'libexpat1 (>= 2.0.1)', + 'libgbm1 (>= 8.1~0)', + 'libgcc1 (>= 1:3.0)', + 'libgcc1 (>= 1:3.5)', + 'libglib2.0-0 (>= 2.16.0)', + 'libglib2.0-0 (>= 2.39.4)', + 'libgtk-3-0 (>= 3.9.10)', + 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libnspr4 (>= 2:4.9-2~)', + 'libnss3 (>= 2:3.22)', + 'libnss3 (>= 3.26)', + 'libpango-1.0-0 (>= 1.14.0)', + 'libsecret-1-0 (>= 0.18)', + 'libstdc++6 (>= 4.1.1)', + 'libstdc++6 (>= 5)', + 'libstdc++6 (>= 5.2)', + 'libstdc++6 (>= 6)', + 'libx11-6', + 'libx11-6 (>= 2:1.4.99.1)', + 'libxcb1 (>= 1.9.2)', + 'libxcomposite1 (>= 1:0.4.4-1)', + 'libxdamage1 (>= 1:1.1)', + 'libxext6', + 'libxfixes3', + 'libxkbcommon0 (>= 0.4.1)', + 'libxkbfile1', + 'libxrandr2', + 'xdg-utils (>= 1.0.2)' + ], + 'arm64': [ + 'ca-certificates', + 'libasound2 (>= 1.0.16)', + 'libatk-bridge2.0-0 (>= 2.5.3)', + 'libatk1.0-0 (>= 2.2.0)', + 'libatspi2.0-0 (>= 2.9.90)', + 'libc6 (>= 2.17)', + 'libcairo2 (>= 1.6.0)', + 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', + 'libdbus-1-3 (>= 1.0.2)', + 'libdrm2 (>= 2.4.38)', + 'libexpat1 (>= 2.0.1)', + 'libgbm1 (>= 8.1~0)', + 'libgcc1 (>= 1:3.0)', + 'libgcc1 (>= 1:4.2)', + 'libgcc1 (>= 1:4.5)', + 'libglib2.0-0 (>= 2.16.0)', + 'libglib2.0-0 (>= 2.39.4)', + 'libgtk-3-0 (>= 3.9.10)', + 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libnspr4 (>= 2:4.9-2~)', + 'libnss3 (>= 2:3.22)', + 'libnss3 (>= 3.26)', + 'libpango-1.0-0 (>= 1.14.0)', + 'libsecret-1-0 (>= 0.18)', + 'libstdc++6 (>= 4.1.1)', + 'libstdc++6 (>= 5)', + 'libstdc++6 (>= 5.2)', + 'libstdc++6 (>= 6)', + 'libx11-6', + 'libx11-6 (>= 2:1.4.99.1)', + 'libxcb1 (>= 1.9.2)', + 'libxcomposite1 (>= 1:0.4.4-1)', + 'libxdamage1 (>= 1:1.1)', + 'libxext6', + 'libxfixes3', + 'libxkbcommon0 (>= 0.4.1)', + 'libxkbfile1', + 'libxrandr2', + 'xdg-utils (>= 1.0.2)' + ] +}; diff --git a/build/linux/debian/dep-lists.ts b/build/linux/debian/dep-lists.ts new file mode 100644 index 00000000000..1e1b0e5b63f --- /dev/null +++ b/build/linux/debian/dep-lists.ts @@ -0,0 +1,157 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/additional_deps +// Additional dependencies not in the dpkg-shlibdeps output. +export const additionalDeps = [ + 'ca-certificates', // Make sure users have SSL certificates. + 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libnss3 (>= 3.26)', + 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', // For Breakpad crash reports. + 'xdg-utils (>= 1.0.2)' // OS integration +]; + +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/manual_recommends +// Dependencies that we can only recommend +// for now since some of the older distros don't support them. +export const recommendedDeps = [ + 'libvulkan1' // Move to additionalDeps once support for Trusty and Jessie are dropped. +]; + +// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/98.0.4758.109:chrome/installer/linux/BUILD.gn;l=64-80 +// and the Linux Archive build +// Shared library dependencies that we already bundle. +export const bundledDeps = [ + 'libEGL.so', + 'libGLESv2.so', + 'libvulkan.so.1', + 'swiftshader_libEGL.so', + 'swiftshader_libGLESv2.so', + 'libvk_swiftshader.so', + 'libffmpeg.so' +]; + +export const referenceGeneratedDepsByArch = { + 'amd64': [ + 'ca-certificates', + 'libasound2 (>= 1.0.16)', + 'libatk-bridge2.0-0 (>= 2.5.3)', + 'libatk1.0-0 (>= 2.2.0)', + 'libatspi2.0-0 (>= 2.9.90)', + 'libc6 (>= 2.14)', + 'libc6 (>= 2.17)', + 'libc6 (>= 2.2.5)', + 'libcairo2 (>= 1.6.0)', + 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', + 'libdbus-1-3 (>= 1.5.12)', + 'libdrm2 (>= 2.4.38)', + 'libexpat1 (>= 2.0.1)', + 'libgbm1 (>= 8.1~0)', + 'libgcc1 (>= 1:3.0)', + 'libglib2.0-0 (>= 2.16.0)', + 'libglib2.0-0 (>= 2.39.4)', + 'libgtk-3-0 (>= 3.9.10)', + 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libnspr4 (>= 2:4.9-2~)', + 'libnss3 (>= 2:3.22)', + 'libnss3 (>= 3.26)', + 'libpango-1.0-0 (>= 1.14.0)', + 'libsecret-1-0 (>= 0.18)', + 'libx11-6', + 'libx11-6 (>= 2:1.4.99.1)', + 'libxcb1 (>= 1.9.2)', + 'libxcomposite1 (>= 1:0.4.4-1)', + 'libxdamage1 (>= 1:1.1)', + 'libxext6', + 'libxfixes3', + 'libxkbcommon0 (>= 0.4.1)', + 'libxkbfile1', + 'libxrandr2', + 'xdg-utils (>= 1.0.2)' + ], + 'armhf': [ + 'ca-certificates', + 'libasound2 (>= 1.0.16)', + 'libatk-bridge2.0-0 (>= 2.5.3)', + 'libatk1.0-0 (>= 2.2.0)', + 'libatspi2.0-0 (>= 2.9.90)', + 'libc6 (>= 2.17)', + 'libc6 (>= 2.4)', + 'libc6 (>= 2.9)', + 'libcairo2 (>= 1.6.0)', + 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', + 'libdbus-1-3 (>= 1.5.12)', + 'libdrm2 (>= 2.4.38)', + 'libexpat1 (>= 2.0.1)', + 'libgbm1 (>= 8.1~0)', + 'libgcc1 (>= 1:3.0)', + 'libgcc1 (>= 1:3.5)', + 'libglib2.0-0 (>= 2.16.0)', + 'libglib2.0-0 (>= 2.39.4)', + 'libgtk-3-0 (>= 3.9.10)', + 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libnspr4 (>= 2:4.9-2~)', + 'libnss3 (>= 2:3.22)', + 'libnss3 (>= 3.26)', + 'libpango-1.0-0 (>= 1.14.0)', + 'libsecret-1-0 (>= 0.18)', + 'libstdc++6 (>= 4.1.1)', + 'libstdc++6 (>= 5)', + 'libstdc++6 (>= 5.2)', + 'libstdc++6 (>= 6)', + 'libx11-6', + 'libx11-6 (>= 2:1.4.99.1)', + 'libxcb1 (>= 1.9.2)', + 'libxcomposite1 (>= 1:0.4.4-1)', + 'libxdamage1 (>= 1:1.1)', + 'libxext6', + 'libxfixes3', + 'libxkbcommon0 (>= 0.4.1)', + 'libxkbfile1', + 'libxrandr2', + 'xdg-utils (>= 1.0.2)' + ], + 'arm64': [ + 'ca-certificates', + 'libasound2 (>= 1.0.16)', + 'libatk-bridge2.0-0 (>= 2.5.3)', + 'libatk1.0-0 (>= 2.2.0)', + 'libatspi2.0-0 (>= 2.9.90)', + 'libc6 (>= 2.17)', + 'libcairo2 (>= 1.6.0)', + 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', + 'libdbus-1-3 (>= 1.0.2)', + 'libdrm2 (>= 2.4.38)', + 'libexpat1 (>= 2.0.1)', + 'libgbm1 (>= 8.1~0)', + 'libgcc1 (>= 1:3.0)', + 'libgcc1 (>= 1:4.2)', + 'libgcc1 (>= 1:4.5)', + 'libglib2.0-0 (>= 2.16.0)', + 'libglib2.0-0 (>= 2.39.4)', + 'libgtk-3-0 (>= 3.9.10)', + 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libnspr4 (>= 2:4.9-2~)', + 'libnss3 (>= 2:3.22)', + 'libnss3 (>= 3.26)', + 'libpango-1.0-0 (>= 1.14.0)', + 'libsecret-1-0 (>= 0.18)', + 'libstdc++6 (>= 4.1.1)', + 'libstdc++6 (>= 5)', + 'libstdc++6 (>= 5.2)', + 'libstdc++6 (>= 6)', + 'libx11-6', + 'libx11-6 (>= 2:1.4.99.1)', + 'libxcb1 (>= 1.9.2)', + 'libxcomposite1 (>= 1:0.4.4-1)', + 'libxdamage1 (>= 1:1.1)', + 'libxext6', + 'libxfixes3', + 'libxkbcommon0 (>= 0.4.1)', + 'libxkbfile1', + 'libxrandr2', + 'xdg-utils (>= 1.0.2)' + ] +}; diff --git a/build/linux/debian/dependencies-generator.js b/build/linux/debian/dependencies-generator.js new file mode 100644 index 00000000000..64fd184aae0 --- /dev/null +++ b/build/linux/debian/dependencies-generator.js @@ -0,0 +1,129 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getDependencies = void 0; +const child_process_1 = require("child_process"); +const fs_1 = require("fs"); +const os_1 = require("os"); +const path = require("path"); +const dep_lists_1 = require("./dep-lists"); +// A flag that can easily be toggled. +// Make sure to compile the build directory after toggling the value. +// If false, we warn about new dependencies if they show up +// while running the Debian prepare package task for a release. +// If true, we fail the build if there are new dependencies found during that task. +// The reference dependencies, which one has to update when the new dependencies +// are valid, are in dep-lists.ts +const FAIL_BUILD_FOR_NEW_DEPENDENCIES = true; +function getDependencies(buildDir, applicationName, arch, sysroot) { + // Get the files for which we want to find dependencies. + const nativeModulesPath = path.join(buildDir, 'resources', 'app', 'node_modules.asar.unpacked'); + const findResult = (0, child_process_1.spawnSync)('find', [nativeModulesPath, '-name', '*.node']); + if (findResult.status) { + console.error('Error finding files:'); + console.error(findResult.stderr.toString()); + return []; + } + const files = findResult.stdout.toString().trimEnd().split('\n'); + const appPath = path.join(buildDir, applicationName); + files.push(appPath); + // Add chrome sandbox and crashpad handler. + files.push(path.join(buildDir, 'chrome-sandbox')); + files.push(path.join(buildDir, 'chrome_crashpad_handler')); + // Generate the dependencies. + const dependencies = files.map((file) => calculatePackageDeps(file, arch, sysroot)); + // Add additional dependencies. + const additionalDepsSet = new Set(dep_lists_1.additionalDeps); + dependencies.push(additionalDepsSet); + // Merge all the dependencies. + const mergedDependencies = mergePackageDeps(dependencies); + let sortedDependencies = []; + for (const dependency of mergedDependencies) { + sortedDependencies.push(dependency); + } + sortedDependencies.sort(); + // Exclude bundled dependencies + sortedDependencies = sortedDependencies.filter(dependency => { + return !dep_lists_1.bundledDeps.some(bundledDep => dependency.startsWith(bundledDep)); + }); + const referenceGeneratedDeps = dep_lists_1.referenceGeneratedDepsByArch[arch]; + if (JSON.stringify(sortedDependencies) !== JSON.stringify(referenceGeneratedDeps)) { + const failMessage = 'The dependencies list has changed.' + + '\nOld:\n' + referenceGeneratedDeps.join('\n') + + '\nNew:\n' + sortedDependencies.join('\n'); + if (FAIL_BUILD_FOR_NEW_DEPENDENCIES) { + throw new Error(failMessage); + } + else { + console.warn(failMessage); + } + } + return sortedDependencies; +} +exports.getDependencies = getDependencies; +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/calculate_package_deps.py. +function calculatePackageDeps(binaryPath, arch, sysroot) { + try { + if (!((0, fs_1.statSync)(binaryPath).mode & fs_1.constants.S_IXUSR)) { + throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); + } + } + catch (e) { + // The package might not exist. Don't re-throw the error here. + console.error('Tried to stat ' + binaryPath + ' but failed.'); + } + // Get the Chromium dpkg-shlibdeps file. + const dpkgShlibdepsUrl = 'https://raw.githubusercontent.com/chromium/chromium/main/third_party/dpkg-shlibdeps/dpkg-shlibdeps.pl'; + const dpkgShlibdepsScriptLocation = `${(0, os_1.tmpdir)()}/dpkg-shlibdeps.pl`; + const result = (0, child_process_1.spawnSync)('curl', [dpkgShlibdepsUrl, '-o', dpkgShlibdepsScriptLocation]); + if (result.status !== 0) { + throw new Error('Cannot retrieve dpkg-shlibdeps. Stderr:\n' + result.stderr); + } + const cmd = [dpkgShlibdepsScriptLocation, '--ignore-weak-undefined']; + switch (arch) { + case 'amd64': + cmd.push(`-l${sysroot}/usr/lib/x86_64-linux-gnu`, `-l${sysroot}/lib/x86_64-linux-gnu`); + break; + case 'armhf': + cmd.push(`-l${sysroot}/usr/lib/arm-linux-gnueabihf`, `-l${sysroot}/lib/arm-linux-gnueabihf`); + break; + case 'arm64': + cmd.push(`-l${sysroot}/usr/lib/aarch64-linux-gnu`, `-l${sysroot}/lib/aarch64-linux-gnu`); + break; + default: + throw new Error('Unsupported architecture ' + arch); + } + cmd.push(`-l${sysroot}/usr/lib`); + cmd.push('-O', '-e', path.resolve(binaryPath)); + const dpkgShlibdepsResult = (0, child_process_1.spawnSync)('perl', cmd, { cwd: sysroot }); + if (dpkgShlibdepsResult.status !== 0) { + throw new Error(`dpkg-shlibdeps failed with exit code ${dpkgShlibdepsResult.status}. stderr:\n${dpkgShlibdepsResult.stderr} `); + } + const shlibsDependsPrefix = 'shlibs:Depends='; + const requiresList = dpkgShlibdepsResult.stdout.toString('utf-8').trimEnd().split('\n'); + let depsStr = ''; + for (const line of requiresList) { + if (line.startsWith(shlibsDependsPrefix)) { + depsStr = line.substring(shlibsDependsPrefix.length); + } + } + const requires = new Set(depsStr.split(', ').sort()); + return requires; +} +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/merge_package_deps.py. +function mergePackageDeps(inputDeps) { + // For now, see if directly appending the dependencies helps. + const requires = new Set(); + for (const depSet of inputDeps) { + for (const dep of depSet) { + const trimmedDependency = dep.trim(); + if (trimmedDependency.length && !trimmedDependency.startsWith('#')) { + requires.add(trimmedDependency); + } + } + } + return requires; +} diff --git a/build/linux/debian/dependencies-generator.ts b/build/linux/debian/dependencies-generator.ts new file mode 100644 index 00000000000..8e1c3fc042f --- /dev/null +++ b/build/linux/debian/dependencies-generator.ts @@ -0,0 +1,145 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { spawnSync } from 'child_process'; +import { constants, statSync } from 'fs'; +import { tmpdir } from 'os'; +import path = require('path'); +import { additionalDeps, bundledDeps, referenceGeneratedDepsByArch } from './dep-lists'; +import { ArchString } from './types'; + +// A flag that can easily be toggled. +// Make sure to compile the build directory after toggling the value. +// If false, we warn about new dependencies if they show up +// while running the Debian prepare package task for a release. +// If true, we fail the build if there are new dependencies found during that task. +// The reference dependencies, which one has to update when the new dependencies +// are valid, are in dep-lists.ts +const FAIL_BUILD_FOR_NEW_DEPENDENCIES: boolean = true; + +export function getDependencies(buildDir: string, applicationName: string, arch: ArchString, sysroot: string): string[] { + // Get the files for which we want to find dependencies. + const nativeModulesPath = path.join(buildDir, 'resources', 'app', 'node_modules.asar.unpacked'); + const findResult = spawnSync('find', [nativeModulesPath, '-name', '*.node']); + if (findResult.status) { + console.error('Error finding files:'); + console.error(findResult.stderr.toString()); + return []; + } + + const files = findResult.stdout.toString().trimEnd().split('\n'); + + const appPath = path.join(buildDir, applicationName); + files.push(appPath); + + // Add chrome sandbox and crashpad handler. + files.push(path.join(buildDir, 'chrome-sandbox')); + files.push(path.join(buildDir, 'chrome_crashpad_handler')); + + // Generate the dependencies. + const dependencies: Set[] = files.map((file) => calculatePackageDeps(file, arch, sysroot)); + // Add additional dependencies. + const additionalDepsSet = new Set(additionalDeps); + dependencies.push(additionalDepsSet); + + // Merge all the dependencies. + const mergedDependencies = mergePackageDeps(dependencies); + let sortedDependencies: string[] = []; + for (const dependency of mergedDependencies) { + sortedDependencies.push(dependency); + } + sortedDependencies.sort(); + + // Exclude bundled dependencies + sortedDependencies = sortedDependencies.filter(dependency => { + return !bundledDeps.some(bundledDep => dependency.startsWith(bundledDep)); + }); + + const referenceGeneratedDeps = referenceGeneratedDepsByArch[arch]; + if (JSON.stringify(sortedDependencies) !== JSON.stringify(referenceGeneratedDeps)) { + const failMessage = 'The dependencies list has changed.' + + '\nOld:\n' + referenceGeneratedDeps.join('\n') + + '\nNew:\n' + sortedDependencies.join('\n'); + if (FAIL_BUILD_FOR_NEW_DEPENDENCIES) { + throw new Error(failMessage); + } else { + console.warn(failMessage); + } + } + + return sortedDependencies; +} + +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/calculate_package_deps.py. +function calculatePackageDeps(binaryPath: string, arch: ArchString, sysroot: string): Set { + try { + if (!(statSync(binaryPath).mode & constants.S_IXUSR)) { + throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); + } + } catch (e) { + // The package might not exist. Don't re-throw the error here. + console.error('Tried to stat ' + binaryPath + ' but failed.'); + } + + // Get the Chromium dpkg-shlibdeps file. + const dpkgShlibdepsUrl = 'https://raw.githubusercontent.com/chromium/chromium/main/third_party/dpkg-shlibdeps/dpkg-shlibdeps.pl'; + const dpkgShlibdepsScriptLocation = `${tmpdir()}/dpkg-shlibdeps.pl`; + const result = spawnSync('curl', [dpkgShlibdepsUrl, '-o', dpkgShlibdepsScriptLocation]); + if (result.status !== 0) { + throw new Error('Cannot retrieve dpkg-shlibdeps. Stderr:\n' + result.stderr); + } + const cmd = [dpkgShlibdepsScriptLocation, '--ignore-weak-undefined']; + switch (arch) { + case 'amd64': + cmd.push(`-l${sysroot}/usr/lib/x86_64-linux-gnu`, + `-l${sysroot}/lib/x86_64-linux-gnu`); + break; + case 'armhf': + cmd.push(`-l${sysroot}/usr/lib/arm-linux-gnueabihf`, + `-l${sysroot}/lib/arm-linux-gnueabihf`); + break; + case 'arm64': + cmd.push(`-l${sysroot}/usr/lib/aarch64-linux-gnu`, + `-l${sysroot}/lib/aarch64-linux-gnu`); + break; + default: + throw new Error('Unsupported architecture ' + arch); + } + cmd.push(`-l${sysroot}/usr/lib`); + cmd.push('-O', '-e', path.resolve(binaryPath)); + + const dpkgShlibdepsResult = spawnSync('perl', cmd, { cwd: sysroot }); + if (dpkgShlibdepsResult.status !== 0) { + throw new Error(`dpkg-shlibdeps failed with exit code ${dpkgShlibdepsResult.status}. stderr:\n${dpkgShlibdepsResult.stderr} `); + } + + const shlibsDependsPrefix = 'shlibs:Depends='; + const requiresList = dpkgShlibdepsResult.stdout.toString('utf-8').trimEnd().split('\n'); + let depsStr = ''; + for (const line of requiresList) { + if (line.startsWith(shlibsDependsPrefix)) { + depsStr = line.substring(shlibsDependsPrefix.length); + } + } + const requires = new Set(depsStr.split(', ').sort()); + return requires; +} + +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/merge_package_deps.py. +function mergePackageDeps(inputDeps: Set[]): Set { + // For now, see if directly appending the dependencies helps. + const requires = new Set(); + for (const depSet of inputDeps) { + for (const dep of depSet) { + const trimmedDependency = dep.trim(); + if (trimmedDependency.length && !trimmedDependency.startsWith('#')) { + requires.add(trimmedDependency); + } + } + } + return requires; +} diff --git a/build/linux/debian/install-sysroot.js b/build/linux/debian/install-sysroot.js new file mode 100644 index 00000000000..c6b57a7b8a3 --- /dev/null +++ b/build/linux/debian/install-sysroot.js @@ -0,0 +1,81 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getSysroot = void 0; +const child_process_1 = require("child_process"); +const crypto_1 = require("crypto"); +const os_1 = require("os"); +const fs = require("fs"); +const https = require("https"); +const path = require("path"); +const sysroots_1 = require("./sysroots"); +// Based on https://source.chromium.org/chromium/chromium/src/+/main:build/linux/sysroot_scripts/install-sysroot.py. +const URL_PREFIX = 'https://msftelectron.blob.core.windows.net'; +const URL_PATH = 'sysroots/toolchain'; +function getSha(filename) { + const hash = (0, crypto_1.createHash)('sha1'); + // Read file 1 MB at a time + const fd = fs.openSync(filename, 'r'); + const buffer = Buffer.alloc(1024 * 1024); + let position = 0; + let bytesRead = 0; + while ((bytesRead = fs.readSync(fd, buffer, 0, buffer.length, position)) === buffer.length) { + hash.update(buffer); + position += bytesRead; + } + hash.update(buffer.slice(0, bytesRead)); + return hash.digest('hex'); +} +async function getSysroot(arch) { + const sysrootDict = sysroots_1.sysrootInfo[arch]; + const tarballFilename = sysrootDict['Tarball']; + const tarballSha = sysrootDict['Sha1Sum']; + const sysroot = path.join((0, os_1.tmpdir)(), sysrootDict['SysrootDir']); + const url = [URL_PREFIX, URL_PATH, tarballSha, tarballFilename].join('/'); + const stamp = path.join(sysroot, '.stamp'); + if (fs.existsSync(stamp) && fs.readFileSync(stamp).toString() === url) { + return sysroot; + } + console.log(`Installing Debian ${arch} root image: ${sysroot}`); + fs.rmSync(sysroot, { recursive: true, force: true }); + fs.mkdirSync(sysroot); + const tarball = path.join(sysroot, tarballFilename); + console.log(`Downloading ${url}`); + let downloadSuccess = false; + for (let i = 0; i < 3 && !downloadSuccess; i++) { + await new Promise((c) => { + https.get(url, (res) => { + const chunks = []; + res.on('data', (chunk) => { + chunks.push(chunk); + }); + res.on('end', () => { + fs.writeFileSync(tarball, Buffer.concat(chunks)); + downloadSuccess = true; + c(); + }); + }).on('error', (err) => { + console.error('Encountered an error during the download attempt: ' + err.message); + c(); + }); + }); + } + if (!downloadSuccess) { + throw new Error('Failed to download ' + url); + } + const sha = getSha(tarball); + if (sha !== tarballSha) { + throw new Error(`Tarball sha1sum is wrong. Expected ${tarballSha}, actual ${sha}`); + } + const proc = (0, child_process_1.spawnSync)('tar', ['xf', tarball, '-C', sysroot]); + if (proc.status) { + throw new Error('Tarball extraction failed with code ' + proc.status); + } + fs.rmSync(tarball); + fs.writeFileSync(stamp, url); + return sysroot; +} +exports.getSysroot = getSysroot; diff --git a/build/linux/debian/install-sysroot.ts b/build/linux/debian/install-sysroot.ts new file mode 100644 index 00000000000..7c04c44f730 --- /dev/null +++ b/build/linux/debian/install-sysroot.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 { spawnSync } from 'child_process'; +import { createHash } from 'crypto'; +import { tmpdir } from 'os'; +import * as fs from 'fs'; +import * as https from 'https'; +import * as path from 'path'; +import { sysrootInfo } from './sysroots'; +import { ArchString } from './types'; + +// Based on https://source.chromium.org/chromium/chromium/src/+/main:build/linux/sysroot_scripts/install-sysroot.py. +const URL_PREFIX = 'https://msftelectron.blob.core.windows.net'; +const URL_PATH = 'sysroots/toolchain'; + +function getSha(filename: fs.PathLike): string { + const hash = createHash('sha1'); + // Read file 1 MB at a time + const fd = fs.openSync(filename, 'r'); + const buffer = Buffer.alloc(1024 * 1024); + let position = 0; + let bytesRead = 0; + while ((bytesRead = fs.readSync(fd, buffer, 0, buffer.length, position)) === buffer.length) { + hash.update(buffer); + position += bytesRead; + } + hash.update(buffer.slice(0, bytesRead)); + return hash.digest('hex'); +} + +type SysrootDictEntry = { + Sha1Sum: string; + SysrootDir: string; + Tarball: string; +}; + +export async function getSysroot(arch: ArchString): Promise { + const sysrootDict: SysrootDictEntry = sysrootInfo[arch]; + const tarballFilename = sysrootDict['Tarball']; + const tarballSha = sysrootDict['Sha1Sum']; + const sysroot = path.join(tmpdir(), sysrootDict['SysrootDir']); + const url = [URL_PREFIX, URL_PATH, tarballSha, tarballFilename].join('/'); + const stamp = path.join(sysroot, '.stamp'); + if (fs.existsSync(stamp) && fs.readFileSync(stamp).toString() === url) { + return sysroot; + } + + console.log(`Installing Debian ${arch} root image: ${sysroot}`); + fs.rmSync(sysroot, { recursive: true, force: true }); + fs.mkdirSync(sysroot); + const tarball = path.join(sysroot, tarballFilename); + console.log(`Downloading ${url}`); + let downloadSuccess = false; + for (let i = 0; i < 3 && !downloadSuccess; i++) { + await new Promise((c) => { + https.get(url, (res) => { + const chunks: Uint8Array[] = []; + res.on('data', (chunk) => { + chunks.push(chunk); + }); + res.on('end', () => { + fs.writeFileSync(tarball, Buffer.concat(chunks)); + downloadSuccess = true; + c(); + }); + }).on('error', (err) => { + console.error('Encountered an error during the download attempt: ' + err.message); + c(); + }); + }); + } + if (!downloadSuccess) { + throw new Error('Failed to download ' + url); + } + const sha = getSha(tarball); + if (sha !== tarballSha) { + throw new Error(`Tarball sha1sum is wrong. Expected ${tarballSha}, actual ${sha}`); + } + + const proc = spawnSync('tar', ['xf', tarball, '-C', sysroot]); + if (proc.status) { + throw new Error('Tarball extraction failed with code ' + proc.status); + } + fs.rmSync(tarball); + fs.writeFileSync(stamp, url); + return sysroot; +} diff --git a/build/linux/debian/sysroots.js b/build/linux/debian/sysroots.js new file mode 100644 index 00000000000..3825de44028 --- /dev/null +++ b/build/linux/debian/sysroots.js @@ -0,0 +1,26 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.sysrootInfo = void 0; +// Based on https://github.com/electron/electron/blob/main/script/sysroots.json, +// which itself is based on https://source.chromium.org/chromium/chromium/src/+/main:build/linux/sysroot_scripts/sysroots.json. +exports.sysrootInfo = { + 'amd64': { + 'Sha1Sum': '7e008cea9eae822d80d55c67fbb5ef4204678e74', + 'SysrootDir': 'debian_sid_amd64-sysroot', + 'Tarball': 'debian_sid_amd64_sysroot.tar.xz' + }, + 'armhf': { + 'Sha1Sum': 'b6f4bb07817bea91b06514a9c1e3832df5a90dbf', + 'SysrootDir': 'debian_sid_arm-sysroot', + 'Tarball': 'debian_sid_arm_sysroot.tar.xz' + }, + 'arm64': { + 'Sha1Sum': '5a56c1ef714154ea5003bcafb16f21b0f8dde023', + 'SysrootDir': 'debian_sid_arm64-sysroot', + 'Tarball': 'debian_sid_arm64_sysroot.tar.xz' + } +}; diff --git a/build/linux/debian/sysroots.ts b/build/linux/debian/sysroots.ts new file mode 100644 index 00000000000..5fda6e6c7bb --- /dev/null +++ b/build/linux/debian/sysroots.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// Based on https://github.com/electron/electron/blob/main/script/sysroots.json, +// which itself is based on https://source.chromium.org/chromium/chromium/src/+/main:build/linux/sysroot_scripts/sysroots.json. +export const sysrootInfo = { + 'amd64': { + 'Sha1Sum': '7e008cea9eae822d80d55c67fbb5ef4204678e74', + 'SysrootDir': 'debian_sid_amd64-sysroot', + 'Tarball': 'debian_sid_amd64_sysroot.tar.xz' + }, + 'armhf': { + 'Sha1Sum': 'b6f4bb07817bea91b06514a9c1e3832df5a90dbf', + 'SysrootDir': 'debian_sid_arm-sysroot', + 'Tarball': 'debian_sid_arm_sysroot.tar.xz' + }, + 'arm64': { + 'Sha1Sum': '5a56c1ef714154ea5003bcafb16f21b0f8dde023', + 'SysrootDir': 'debian_sid_arm64-sysroot', + 'Tarball': 'debian_sid_arm64_sysroot.tar.xz' + } +}; diff --git a/build/linux/debian/types.js b/build/linux/debian/types.js new file mode 100644 index 00000000000..56d4e6c56ce --- /dev/null +++ b/build/linux/debian/types.js @@ -0,0 +1,6 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/build/linux/debian/types.ts b/build/linux/debian/types.ts new file mode 100644 index 00000000000..ae2347cb12b --- /dev/null +++ b/build/linux/debian/types.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export type ArchString = 'amd64' | 'armhf' | 'arm64'; diff --git a/build/linux/rpm/dependencies-generator.js b/build/linux/rpm/dependencies-generator.js index 3b1e404129c..90cfca96791 100644 --- a/build/linux/rpm/dependencies-generator.js +++ b/build/linux/rpm/dependencies-generator.js @@ -81,7 +81,7 @@ function calculatePackageDeps(binaryPath) { const requires = new Set(findRequiresResult.stdout.toString('utf-8').trimEnd().split('\n')); return requires; } -// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/merge_package_deps.py +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/merge_package_deps.py. function mergePackageDeps(inputDeps) { const requires = new Set(); for (const depSet of inputDeps) { diff --git a/build/linux/rpm/dependencies-generator.ts b/build/linux/rpm/dependencies-generator.ts index e33210284dc..4b84640e280 100644 --- a/build/linux/rpm/dependencies-generator.ts +++ b/build/linux/rpm/dependencies-generator.ts @@ -92,7 +92,7 @@ function calculatePackageDeps(binaryPath: string): Set { return requires; } -// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/merge_package_deps.py +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/merge_package_deps.py. function mergePackageDeps(inputDeps: Set[]): Set { const requires = new Set(); for (const depSet of inputDeps) { diff --git a/resources/linux/debian/control.template b/resources/linux/debian/control.template index bfe0aa52da2..1a5981bb219 100644 --- a/resources/linux/debian/control.template +++ b/resources/linux/debian/control.template @@ -1,7 +1,8 @@ Package: @@NAME@@ Version: @@VERSION@@ Section: devel -Depends: libnss3 (>= 2:3.26), gnupg, apt, libxkbfile1, libsecret-1-0, libgtk-3-0 (>= 3.10.0), libxss1, libgbm1 +Depends: @@DEPENDS@@ +Recommends: @@RECOMMENDS@@ Priority: optional Architecture: @@ARCHITECTURE@@ Maintainer: Microsoft Corporation From a87d403eba1bbaf4995c61763cf3e33b1bb49781 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Tue, 19 Jul 2022 10:53:36 -0700 Subject: [PATCH 054/197] more code clean up and fixes - also added better styling --- .../codeAction/browser/codeActionCommands.ts | 6 +- .../codeAction/browser/codeActionMenu.ts | 153 ++++----- .../codeAction/browser/codeActionUi.ts | 5 +- .../codeAction/browser/media/action.css | 299 +----------------- 4 files changed, 63 insertions(+), 400 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts index 86e6aeb286b..2da6f125138 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts @@ -530,7 +530,7 @@ registerEditorCommand(new CodeActionContribution({ kbOpts: { weight: weight + 100000, primary: KeyCode.UpArrow, - // secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow], + secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow], } })); @@ -543,7 +543,7 @@ registerEditorCommand(new CodeActionContribution({ kbOpts: { weight: weight + 100000, primary: KeyCode.DownArrow, - // secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow], + secondary: [KeyMod.CtrlCmd | KeyCode.DownArrow], } })); @@ -556,7 +556,7 @@ registerEditorCommand(new CodeActionContribution({ kbOpts: { weight: weight + 100000, primary: KeyCode.Enter, - // secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow], + secondary: [KeyMod.Shift | KeyCode.Tab], } })); diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 3f15040d903..3d3eae53fed 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -11,12 +11,10 @@ import { Action, IAction, Separator } from 'vs/base/common/actions'; import { canceled } from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, dispose, MutableDisposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import 'vs/css!./media/action'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorCommand, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; @@ -24,32 +22,18 @@ import { CodeAction, Command } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { codeActionCommandId, CodeActionItem, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/browser/codeAction'; -import { QuickFixController } from 'vs/editor/contrib/codeAction/browser/codeActionCommands'; import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionKind, CodeActionTrigger, CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/browser/types'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -// import { Emitter } from 'vs/base/common/event'; - -// const $ = dom.$; export const Context = { - Visible: new RawContextKey('CodeActionMenuVisible', false, localize('CodeActionMenuVisible', "Whether suggestion are visible")) - - // HasFocusedSuggestion: new RawContextKey('suggestWidgetHasFocusedSuggestion', false, localize('suggestWidgetHasSelection', "Whether any suggestion is focused")), - // DetailsVisible: new RawContextKey('suggestWidgetDetailsVisible', false, localize('suggestWidgetDetailsVisible', "Whether suggestion details are visible")), - // MultipleSuggestions: new RawContextKey('suggestWidgetMultipleSuggestions', false, localize('suggestWidgetMultipleSuggestions', "Whether there are multiple suggestions to pick from")), - // MakesTextEdit: new RawContextKey('suggestionMakesTextEdit', true, localize('suggestionMakesTextEdit', "Whether inserting the current suggestion yields in a change or has everything already been typed")), - // AcceptSuggestionsOnEnter: new RawContextKey('acceptSuggestionOnEnter', true, localize('acceptSuggestionOnEnter', "Whether suggestions are inserted when pressing Enter")), - // HasInsertAndReplaceRange: new RawContextKey('suggestionHasInsertAndReplaceRange', false, localize('suggestionHasInsertAndReplaceRange', "Whether the current suggestion has insert and replace behaviour")), - // InsertMode: new RawContextKey<'insert' | 'replace'>('suggestionInsertMode', undefined, { type: 'string', description: localize('suggestionInsertMode', "Whether the default behaviour is to insert or replace") }), - // CanResolve: new RawContextKey('suggestionCanResolve', false, localize('suggestionCanResolve', "Whether the current suggestion supports to resolve further details")), + Visible: new RawContextKey('CodeActionMenuVisible', false, localize('CodeActionMenuVisible', "Whether the code action list widget is visible")) }; interface CodeActionWidgetDelegate { @@ -154,18 +138,16 @@ class CodeMenuRenderer implements IListRenderer; - private options: ICodeActionMenuItem[] = []; - private _visible: boolean = false; + private readonly _ctxMenuWidgetIsFocused?: IContextKey; + private readonly editor: ICodeEditor; private readonly _showingActions = this._register(new MutableDisposable()); private readonly _disposables = new DisposableStore(); private readonly _onDidHideContextMenu = new Emitter(); - // private readonly _onDidCancel = new Emitter(); + private codeActionList!: List; + private options: ICodeActionMenuItem[] = []; + private _visible: boolean = false; readonly onDidHideContextMenu = this._onDidHideContextMenu.event; - private readonly _ctxMenuWidgetIsFocused?: IContextKey; private _ctxMenuWidgetVisible!: IContextKey; - private readonly editor: ICodeEditor; private viewItems: ICodeActionMenuItem[] = []; private focusedEnabledItem!: number; private currSelectedItem!: number; @@ -233,9 +215,6 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { private _onListFocus(e: IListEvent): void { this._ctxMenuWidgetIsFocused?.set(true); - const item = e.elements[0]; - const index = e.indexes[0]; - } private renderCodeActionMenuList(element: HTMLElement, inputArray: IAction[]): IDisposable { @@ -243,7 +222,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { const renderMenu = document.createElement('div'); this.listRenderer = new CodeMenuRenderer(); - const height = inputArray.length * 25; + const height = inputArray.length * 27; renderMenu.style.height = String(height) + 'px'; renderMenu.id = 'codeActionMenuWidget'; @@ -253,7 +232,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { this.codeActionList = new List('codeActionWidget', renderMenu, { getHeight(element) { - return 25; + return 27; }, getTemplateId(element) { return 'codeActionWidget'; @@ -266,6 +245,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { renderDisposables.add(this.codeActionList.onDidChangeFocus(e => this._onListFocus(e))); } + // Populating the list widget and tracking enabled options. inputArray.forEach((item, index) => { const menuItem = { title: item.label, detail: item.tooltip, action: inputArray[index], isEnabled: item.enabled, isSeparator: item.class === 'separator', index }; if (item.enabled) { @@ -277,33 +257,38 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { this.codeActionList.splice(0, this.codeActionList.length, this.options); this.codeActionList.layout(height); + // For finding width dynamically (not using resize observer) const arr: number[] = []; this.options.forEach((item, index) => { const element = document.getElementById(this.codeActionList.getElementID(index))?.getElementsByTagName('span')[0].offsetWidth; arr.push(Number(element)); }); + // resize observer - can be used in the future since list widget supports dynamic height but not width const maxWidth = Math.max(...arr); - renderMenu.style.width = maxWidth + 20 + 'px'; + renderMenu.style.width = maxWidth + 40 + 'px'; this.codeActionList.layout(height, maxWidth); - // resize observer - supports dynamic height but not width - this.codeActionList.domFocus(); + // List selection this.focusedEnabledItem = 0; - this.codeActionList.setFocus([this.viewItems[0].index]); + this.currSelectedItem = this.viewItems[0].index; + this.codeActionList.setFocus([this.currSelectedItem]); + + // List Focus + this.codeActionList.domFocus(); const focusTracker = dom.trackFocus(element); const blurListener = focusTracker.onDidBlur(() => { this.hideCodeActionWidget(); this._contextViewService.hideContextView({ source: this }); }); - renderDisposables.add(blurListener); renderDisposables.add(focusTracker); this._ctxMenuWidgetVisible.set(true); + return renderDisposables; } - protected focusPrevious(forceLoop?: Boolean) { + protected focusPrevious() { if (typeof this.focusedEnabledItem === 'undefined') { this.focusedEnabledItem = this.viewItems[0].index; } else if (this.viewItems.length <= 1) { @@ -315,7 +300,6 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { do { this.focusedEnabledItem = this.focusedEnabledItem - 1; - console.log(this.focusedEnabledItem); if (this.focusedEnabledItem < 0) { this.focusedEnabledItem = this.viewItems.length - 1; } @@ -327,7 +311,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { return true; } - protected focusNext(forceLoop?: Boolean) { + protected focusNext() { if (typeof this.focusedEnabledItem === 'undefined') { this.focusedEnabledItem = this.viewItems.length - 1; } else if (this.viewItems.length <= 1) { @@ -369,6 +353,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { this.options = []; this.viewItems = []; this.focusedEnabledItem = 0; + this.currSelectedItem = 0; this._contextViewService.hideContextView(); this.dispose(); } @@ -436,44 +421,42 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { }, this._editor.getDomNode()!, false, ); + // } else { + // this._contextMenuService.showContextMenu({ + // domForShadowRoot: useShadowDOM ? this._editor.getDomNode()! : undefined, + // getAnchor: () => anchor, + // getActions: () => menuActions, + // onHide: (didCancel) => { + // const openedFromString = (options.fromLightbulb) ? CodeActionTriggerSource.Lightbulb : trigger.triggerAction; + + // type ApplyCodeActionEvent = { + // codeActionFrom: CodeActionTriggerSource; + // validCodeActions: number; + // cancelled: boolean; + // }; + + // type ApplyCodeEventClassification = { + // codeActionFrom: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of action used to opened the code action.' }; + // validCodeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The total number of valid actions that are highlighted and can be used.' }; + // cancelled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The indicator if the menu was selected or cancelled.' }; + // owner: 'mjbvz'; + // comment: 'Event used to gain insights into how code actions are being triggered'; + // }; + + // this._telemetryService.publicLog2('codeAction.applyCodeAction', { + // codeActionFrom: openedFromString, + // validCodeActions: codeActions.validActions.length, + // cancelled: didCancel, + + // }); + + // this._visible = false; + // this._editor.focus(); + // }, + // autoSelectFirstItem: true, + // getKeyBinding: action => action instanceof CodeActionAction ? resolver(action.action) : undefined, + // }); // } - - - - // this._contextMenuService.showContextMenu({ - // domForShadowRoot: useShadowDOM ? this._editor.getDomNode()! : undefined, - // getAnchor: () => anchor, - // getActions: () => menuActions, - // onHide: (didCancel) => { - // const openedFromString = (options.fromLightbulb) ? CodeActionTriggerSource.Lightbulb : trigger.triggerAction; - - // type ApplyCodeActionEvent = { - // codeActionFrom: CodeActionTriggerSource; - // validCodeActions: number; - // cancelled: boolean; - // }; - - // type ApplyCodeEventClassification = { - // codeActionFrom: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of action used to opened the code action.' }; - // validCodeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The total number of valid actions that are highlighted and can be used.' }; - // cancelled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The indicator if the menu was selected or cancelled.' }; - // owner: 'mjbvz'; - // comment: 'Event used to gain insights into how code actions are being triggered'; - // }; - - // this._telemetryService.publicLog2('codeAction.applyCodeAction', { - // codeActionFrom: openedFromString, - // validCodeActions: codeActions.validActions.length, - // cancelled: didCancel, - - // }); - - // this._visible = false; - // this._editor.focus(); - // }, - // autoSelectFirstItem: true, - // getKeyBinding: action => action instanceof CodeActionAction ? resolver(action.action) : undefined, - // }); } private getMenuActions( @@ -598,23 +581,3 @@ export class CodeActionKeybindingResolver { }, undefined as ResolveCodeActionKeybinding | undefined); } } - -// registerEditorContribution(CodeActionMenu.ID, CodeActionMenu); - - -/** - * need to create a new constructor/new class for the code action menu controller? - */ - - - -// KeybindingsRegistry.registerCommandAndKeybindingRule({ -// id: 'codeActionMenu.selectEditor', -// weight: KeybindingWeight.WorkbenchContrib + 1, -// primary: KeyCode.Escape, -// when: ContextKeyExpr.and(Context.Visible), -// handler(accessor) { -// console.log('hello hi'); -// } -// }); - diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts index a3b8f57dcc0..59771bfd5e5 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts @@ -5,19 +5,16 @@ import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorCommand, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IPosition } from 'vs/editor/common/core/position'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { CodeActionTriggerType } from 'vs/editor/common/languages'; import { CodeActionItem, CodeActionSet } from 'vs/editor/contrib/codeAction/browser/codeAction'; import { MessageController } from 'vs/editor/contrib/message/browser/messageController'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { CodeActionMenu, CodeActionShowOptions, Context } from './codeActionMenu'; +import { CodeActionMenu, CodeActionShowOptions } from './codeActionMenu'; import { CodeActionsState } from './codeActionModel'; import { LightBulbWidget } from './lightBulbWidget'; import { CodeActionAutoApply, CodeActionTrigger } from './types'; diff --git a/src/vs/editor/contrib/codeAction/browser/media/action.css b/src/vs/editor/contrib/codeAction/browser/media/action.css index 27863688115..2fcf056c618 100644 --- a/src/vs/editor/contrib/codeAction/browser/media/action.css +++ b/src/vs/editor/contrib/codeAction/browser/media/action.css @@ -3,12 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-menu { - font-size: 13px; - border-radius: 5px; - min-width: 160px; -} - .codeActionMenuWidget { padding: 10px 0px 10px 0px; overflow: auto; @@ -67,7 +61,7 @@ display: flex; -mox-box-sizing: border-box; box-sizing: border-box; - padding: 0px 10px 0px 10px; + padding: 0px 20px 0px 20px; background-repeat: no-repeat; background-position: 2px 2px; white-space: nowrap; @@ -77,7 +71,6 @@ } - .codeActionMenuWidget .monaco-list .monaco-list-row:hover:not(.option-disabled), .codeActionMenuWidget .monaco-list .moncao-list-row.focused:not(.option-disabled) { color: var(--vscode-editorSuggestWidget-selectedForeground); @@ -114,293 +107,3 @@ cursor: pointer; touch-action: none; } - -/* -${formatRule(Codicon.menuSelection)} -${formatRule(Codicon.menuSubmenu)} -*/ - -.monaco-menu .monaco-action-bar { - text-align: right; - overflow: hidden; - white-space: nowrap; -} - -.monaco-menu .monaco-action-bar .actions-container { - display: flex; - margin: 0 auto; - padding: 0; - width: 100%; - justify-content: flex-end; -} - -.monaco-menu .monaco-action-bar.vertical .actions-container { - display: inline-block; -} - -.monaco-menu .monaco-action-bar.reverse .actions-container { - flex-direction: row-reverse; -} - -.monaco-menu .monaco-action-bar .action-item { - cursor: pointer; - display: inline-block; - transition: transform 50ms ease; - position: relative; /* DO NOT REMOVE - this is the key to preventing the ghosting icon bug in Chrome 42 */ -} - -.monaco-menu .monaco-action-bar .action-item.disabled { - cursor: default; -} - -.monaco-menu .monaco-action-bar.animated .action-item.active { - transform: scale(1.272019649, 1.272019649); /* 1.272019649 = √φ */ -} - -.monaco-menu .monaco-action-bar .action-item .icon, -.monaco-menu .monaco-action-bar .action-item .codicon { - display: inline-block; -} - -.monaco-menu .monaco-action-bar .action-item .codicon { - display: flex; - align-items: center; -} - -.monaco-menu .monaco-action-bar .action-label { - font-size: 11px; - margin-right: 4px; -} - -.monaco-menu .monaco-action-bar .action-item.disabled .action-label, -.monaco-menu .monaco-action-bar .action-item.disabled .action-label:hover { - color: var(--vscode-disabledForeground); -} - -/* Vertical actions */ - -.monaco-menu .monaco-action-bar.vertical { - text-align: left; -} - -.monaco-menu .monaco-action-bar.vertical .action-item { - display: block; -} - -.monaco-menu .monaco-action-bar.vertical .action-label.separator { - display: block; - border-bottom: 1px solid var(--vscode-menu-separatorBackground); - padding-top: 1px; - padding: 30px; -} - -.monaco-menu .secondary-actions .monaco-action-bar .action-label { - margin-left: 6px; -} - -/* Action Items */ -.monaco-menu .monaco-action-bar .action-item.select-container { - overflow: hidden; /* somehow the dropdown overflows its container, we prevent it here to not push */ - flex: 1; - max-width: 170px; - min-width: 60px; - display: flex; - align-items: center; - justify-content: center; - margin-right: 10px; -} - -.monaco-menu .monaco-action-bar.vertical { - margin-left: 0; - overflow: visible; -} - -.monaco-menu .monaco-action-bar.vertical .actions-container { - display: block; -} - -.monaco-menu .monaco-action-bar.vertical .action-item { - padding: 0; - transform: none; - display: flex; -} - -.monaco-menu .monaco-action-bar.vertical .action-item.active { - transform: none; -} - -.monaco-menu .monaco-action-bar.vertical .action-menu-item { - flex: 1 1 auto; - display: flex; - height: 2em; - align-items: center; - position: relative; -} - -.monaco-menu .monaco-action-bar.vertical .action-menu-item:hover .keybinding, -.monaco-menu .monaco-action-bar.vertical .action-menu-item:focus .keybinding { - opacity: unset; -} - -.monaco-menu .monaco-action-bar.vertical .action-label { - flex: 1 1 auto; - text-decoration: none; - padding: 0 1em; - background: none; - font-size: 12px; - line-height: 1; -} - -.monaco-menu .monaco-action-bar.vertical .keybinding, -.monaco-menu .monaco-action-bar.vertical .submenu-indicator { - display: inline-block; - flex: 2 1 auto; - padding: 0 1em; - text-align: right; - font-size: 12px; - line-height: 1; -} - -.monaco-menu .monaco-action-bar.vertical .submenu-indicator { - height: 100%; -} - -.monaco-menu .monaco-action-bar.vertical .submenu-indicator.codicon { - font-size: 16px !important; - display: flex; - align-items: center; -} - -.monaco-menu .monaco-action-bar.vertical .submenu-indicator.codicon::before { - margin-left: auto; - margin-right: -20px; -} - -.monaco-menu .monaco-action-bar.vertical .action-item.disabled .keybinding, -.monaco-menu .monaco-action-bar.vertical .action-item.disabled .submenu-indicator { - opacity: 0.4; -} - -.monaco-menu .monaco-action-bar.vertical .action-label:not(.separator) { - display: inline-block; - box-sizing: border-box; - margin: 0; -} - -.monaco-menu .monaco-action-bar.vertical .action-item { - position: static; - overflow: visible; -} - -.monaco-menu .monaco-action-bar.vertical .action-item .monaco-submenu { - position: absolute; -} - -.monaco-menu .monaco-action-bar.vertical .action-label.separator { - width: 100%; - height: 0px !important; - opacity: 1; -} - -.monaco-menu .monaco-action-bar.vertical .action-label.separator.text { - padding: 0.7em 1em 0.1em 1em; - font-weight: bold; - opacity: 1; -} - -.monaco-menu .monaco-action-bar.vertical .action-label:hover { - color: inherit; -} - -.monaco-menu .monaco-action-bar.vertical .menu-item-check { - position: absolute; - visibility: hidden; - width: 1em; - height: 100%; -} - -.monaco-menu .monaco-action-bar.vertical .action-menu-item.checked .menu-item-check { - visibility: visible; - display: flex; - align-items: center; - justify-content: center; -} - -/* Context Menu */ - -.context-view.monaco-menu-container { - outline: 0; - border: none; - animation: fadeIn 0.083s linear; - -webkit-app-region: no-drag; -} - -.context-view.monaco-menu-container :focus, -.context-view.monaco-menu-container .monaco-action-bar.vertical:focus, -.context-view.monaco-menu-container .monaco-action-bar.vertical :focus { - outline: 0; -} - -.hc-black .context-view.monaco-menu-container, -.hc-light .context-view.monaco-menu-container, -:host-context(.hc-black) .context-view.monaco-menu-container, -:host-context(.hc-light) .context-view.monaco-menu-container { - box-shadow: none; -} - -.hc-black .monaco-menu .monaco-action-bar.vertical .action-item.focused, -.hc-light .monaco-menu .monaco-action-bar.vertical .action-item.focused, -:host-context(.hc-black) .monaco-menu .monaco-action-bar.vertical .action-item.focused, -:host-context(.hc-light) .monaco-menu .monaco-action-bar.vertical .action-item.focused { - background: none; -} - -/* Vertical Action Bar Styles */ - -.monaco-menu .monaco-action-bar.vertical { - padding: .6em 0; -} - -.monaco-menu .monaco-action-bar.vertical .action-menu-item { - height: 2em; -} - -.monaco-menu .monaco-action-bar.vertical .action-label:not(.separator), -.monaco-menu .monaco-action-bar.vertical .keybinding { - font-size: inherit; - padding: 0 2em; -} - -.monaco-menu .monaco-action-bar.vertical .menu-item-check { - font-size: inherit; - width: 2em; -} - -.monaco-menu .monaco-action-bar.vertical .action-label.separator { - font-size: inherit; - margin: 5px 0 !important; - padding: 0; - border-radius: 0; -} - -.linux .monaco-menu .monaco-action-bar.vertical .action-label.separator, -:host-context(.linux) .monaco-menu .monaco-action-bar.vertical .action-label.separator { - margin-left: 0; - margin-right: 0; -} - -.monaco-menu .monaco-action-bar.vertical .submenu-indicator { - font-size: 60%; - padding: 0 1.8em; -} - -/* .linux .monaco-menu .monaco-action-bar.vertical .submenu-indicator { -:host-context(.linux) .monaco-menu .monaco-action-bar.vertical .submenu-indicator { - height: 100%; - mask-size: 10px 10px; - -webkit-mask-size: 10px 10px; -} */ - -.monaco-menu .action-item { - cursor: default; -}; From 1aeaa922ffe077b0d63d97c5b356662e11e5594b Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Tue, 19 Jul 2022 11:04:14 -0700 Subject: [PATCH 055/197] more commits for code cleanup --- .../contrib/codeAction/browser/codeActionMenu.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 3d3eae53fed..37d73cbabc5 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -165,23 +165,21 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { private readonly _editor: ICodeEditor, private readonly _delegate: CodeActionWidgetDelegate, @IContextMenuService private readonly _contextMenuService: IContextMenuService, - @IContextViewService private readonly _contextViewService: IContextViewService, - @IContextKeyService private _contextKeyService: IContextKeyService, @IKeybindingService keybindingService: IKeybindingService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @IThemeService _themeService: IThemeService, @IConfigurationService private readonly _configurationService: IConfigurationService, + @IContextViewService private readonly _contextViewService: IContextViewService, + @IContextKeyService private _contextKeyService: IContextKeyService, ) { super(); this.editor = _editor; - this._keybindingResolver = new CodeActionKeybindingResolver({ getKeybindings: () => keybindingService.getKeybindings() }); - if (this.codeActionList && !this.codeActionList.isDOMFocused()) { this.dispose(); } @@ -189,9 +187,6 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { this._ctxMenuWidgetVisible = Context.Visible.bindTo(_contextKeyService); } - allowEditorOverflow?: boolean | undefined; - suppressMouseDown?: boolean | undefined; - get isVisible(): boolean { return this._visible; } @@ -381,7 +376,6 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { const menuActions = this.getMenuActions(trigger, actionsToShow, codeActions.documentation); const anchor = Position.isIPosition(at) ? this._toCoords(at) : at || { x: 0, y: 0 }; - const resolver = this._keybindingResolver.getResolver(); const useShadowDOM = this._editor.getOption(EditorOption.useShadowDOM); @@ -416,7 +410,6 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { }); this._visible = false; this._editor.focus(); - }, }, this._editor.getDomNode()!, false, From 73c0889cb7e134f7753d796238b14ec46c7e0e1e Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Tue, 19 Jul 2022 12:04:19 -0700 Subject: [PATCH 056/197] delay center layout until editor restore (#155647) fixes #155564 --- src/vs/workbench/browser/layout.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 2e997f59f0b..9151c945a6c 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -245,10 +245,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Restore editor part on any editor change this._register(this.editorService.onDidVisibleEditorsChange(showEditorIfHidden)); this._register(this.editorGroupService.onDidActivateGroup(showEditorIfHidden)); - }); - // Revalidate center layout when active editor changes: diff editor quits centered mode. - this._register(this.editorService.onDidActiveEditorChange(() => this.centerEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED)))); + // Revalidate center layout when active editor changes: diff editor quits centered mode. + this._register(this.editorService.onDidActiveEditorChange(() => this.centerEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED)))); + }); // Configuration changes this._register(this.configurationService.onDidChangeConfiguration(() => this.doUpdateLayoutConfiguration())); @@ -347,7 +347,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.updateMenubarVisibility(!!skipLayout); // Centered Layout - this.centerEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED), skipLayout); + this.editorGroupService.whenRestored.then(() => { + this.centerEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED), skipLayout); + }); } private setSideBarPosition(position: Position): void { From 646345fba600383c67b031e81b1154cd2933559a Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Tue, 19 Jul 2022 12:22:14 -0700 Subject: [PATCH 057/197] improve empty workspace detection in web (#155643) * improve empty workspace detection in web fixes #155214 * fix order of eval, unit test failures --- .../workspaces/common/workspaceTrust.ts | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts index 16b1dff68d9..eefe8b7ffcd 100644 --- a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts +++ b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts @@ -15,7 +15,7 @@ import { IRemoteAuthorityResolverService, ResolverResult } from 'vs/platform/rem import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { isVirtualResource } from 'vs/platform/workspace/common/virtualWorkspace'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { ISingleFolderWorkspaceIdentifier, isSavedWorkspace, isSingleFolderWorkspaceIdentifier, IWorkspace, IWorkspaceContextService, IWorkspaceFolder, toWorkspaceIdentifier, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { ISingleFolderWorkspaceIdentifier, isSavedWorkspace, isSingleFolderWorkspaceIdentifier, isTemporaryWorkspace, IWorkspace, IWorkspaceContextService, IWorkspaceFolder, toWorkspaceIdentifier, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { WorkspaceTrustRequestOptions, IWorkspaceTrustManagementService, IWorkspaceTrustInfo, IWorkspaceTrustUriInfo, IWorkspaceTrustRequestService, IWorkspaceTrustTransitionParticipant, WorkspaceTrustUriResponse, IWorkspaceTrustEnablementService } from 'vs/platform/workspace/common/workspaceTrust'; import { Memento, MementoObject } from 'vs/workbench/common/memento'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -132,7 +132,7 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork this._workspaceTrustInitializedPromiseResolve = resolve; }); - this._storedTrustState = new WorkspaceTrustMemento(isWeb && this.workspaceService.getWorkbenchState() === WorkbenchState.EMPTY ? undefined : this.storageService); + this._storedTrustState = new WorkspaceTrustMemento(isWeb && this.isEmptyWorkspace() ? undefined : this.storageService); this._trustTransitionManager = this._register(new WorkspaceTrustTransitionManager()); this._trustStateInfo = this.loadTrustInfo(); @@ -172,7 +172,7 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork } // Empty workspace - save initial state to memento - if (this.workspaceService.getWorkbenchState() === WorkbenchState.EMPTY) { + if (this.isEmptyWorkspace()) { this._workspaceTrustInitializedPromise.then(() => { if (this._storedTrustState.isEmptyWorkspaceTrusted === undefined) { this._storedTrustState.isEmptyWorkspaceTrusted = this.isWorkspaceTrusted(); @@ -307,7 +307,7 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork } // Empty workspace - use memento, open ediors, or user setting - if (this.workspaceService.getWorkbenchState() === WorkbenchState.EMPTY) { + if (this.isEmptyWorkspace()) { // Use memento if present if (this._storedTrustState.isEmptyWorkspaceTrusted !== undefined) { return this._storedTrustState.isEmptyWorkspaceTrusted; @@ -426,6 +426,19 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork } } + private isEmptyWorkspace(): boolean { + if (this.workspaceService.getWorkbenchState() === WorkbenchState.EMPTY) { + return true; + } + + const workspace = this.workspaceService.getWorkspace(); + if (workspace) { + return isTemporaryWorkspace(this.workspaceService.getWorkspace()) && workspace.folders.length === 0; + } + + return false; + } + private isTrustedVirtualResource(uri: URI): boolean { return isVirtualResource(uri) && uri.scheme !== 'vscode-vfs'; } @@ -451,7 +464,7 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork } // Empty workspace - save memento - if (this.workspaceService.getWorkbenchState() === WorkbenchState.EMPTY) { + if (this.isEmptyWorkspace()) { this._storedTrustState.isEmptyWorkspaceTrusted = value; } } @@ -530,7 +543,7 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork } // Empty workspace - if (this.workspaceService.getWorkbenchState() === WorkbenchState.EMPTY) { + if (this.isEmptyWorkspace()) { return true; } @@ -577,7 +590,7 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork async setWorkspaceTrust(trusted: boolean): Promise { // Empty workspace - if (this.workspaceService.getWorkbenchState() === WorkbenchState.EMPTY) { + if (this.isEmptyWorkspace()) { await this.updateWorkspaceTrust(trusted); return; } From ca8c68b380e2a131f4003d188e60b93d81a601b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Tue, 19 Jul 2022 22:46:59 +0200 Subject: [PATCH 058/197] Fixes #155571 (#155608) * Fixes #155571 * fix test --- src/vs/base/browser/ui/list/listWidget.ts | 16 +++++++-- src/vs/base/browser/ui/tree/abstractTree.ts | 5 --- src/vs/platform/list/browser/listService.ts | 35 ++++++++++++------- .../browser/diff/notebookTextDiffList.ts | 4 +-- .../notebook/browser/view/notebookCellList.ts | 6 ++-- .../test/browser/testNotebookEditor.ts | 3 ++ .../terminal/browser/terminalTabsList.ts | 3 +- 7 files changed, 45 insertions(+), 27 deletions(-) diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 7254351f697..b9f3b7c45fa 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -424,6 +424,7 @@ class TypeNavigationController implements IDisposable { private list: List, private view: ListView, private keyboardNavigationLabelProvider: IKeyboardNavigationLabelProvider, + private keyboardNavigationEventFilter: IKeyboardNavigationEventFilter, private delegate: IKeyboardNavigationDelegate ) { this.updateOptions(list.options); @@ -448,12 +449,15 @@ class TypeNavigationController implements IDisposable { return; } + let typing = false; + const onChar = this.enabledDisposables.add(Event.chain(this.enabledDisposables.add(new DomEmitter(this.view.domNode, 'keydown')).event)) .filter(e => !isInputElement(e.target as HTMLElement)) .filter(() => this.mode === TypeNavigationMode.Automatic || this.triggered) .map(event => new StandardKeyboardEvent(event)) + .filter(e => typing || this.keyboardNavigationEventFilter(e)) .filter(e => this.delegate.mightProducePrintableCharacter(e)) - .forEach(e => { e.preventDefault(); e.stopPropagation(); }) + .forEach(stopEvent) .map(event => event.browserEvent.key) .event; @@ -463,6 +467,9 @@ class TypeNavigationController implements IDisposable { onInput(this.onInput, this, this.enabledDisposables); onClear(this.onClear, this, this.enabledDisposables); + onChar(() => typing = true, undefined, this.enabledDisposables); + onClear(() => typing = false, undefined, this.enabledDisposables); + this.enabled = true; this.triggered = false; } @@ -919,6 +926,10 @@ export class DefaultStyleController implements IStyleController { } } +export interface IKeyboardNavigationEventFilter { + (e: StandardKeyboardEvent): boolean; +} + export interface IListOptionsUpdate extends IListViewOptionsUpdate { readonly typeNavigationEnabled?: boolean; readonly typeNavigationMode?: TypeNavigationMode; @@ -934,6 +945,7 @@ export interface IListOptions extends IListOptionsUpdate { readonly multipleSelectionController?: IMultipleSelectionController; readonly styleController?: (suffix: string) => IStyleController; readonly accessibilityProvider?: IListAccessibilityProvider; + readonly keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter; // list view options readonly useShadows?: boolean; @@ -1373,7 +1385,7 @@ export class List implements ISpliceable, IThemable, IDisposable { if (_options.keyboardNavigationLabelProvider) { const delegate = _options.keyboardNavigationDelegate || DefaultKeyboardNavigationDelegate; - this.typeNavigationController = new TypeNavigationController(this, this.view, _options.keyboardNavigationLabelProvider, delegate); + this.typeNavigationController = new TypeNavigationController(this, this.view, _options.keyboardNavigationLabelProvider, _options.keyboardNavigationEventFilter ?? (() => true), delegate); this.disposables.add(this.typeNavigationController); } diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 95032ae5ee4..7a75aa8070a 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -1003,10 +1003,6 @@ function asTreeContextMenuEvent(event: IListContextMenuEvent extends IAbstractTr readonly collapseByDefault?: boolean; // defaults to false readonly filter?: ITreeFilter; readonly dnd?: ITreeDragAndDrop; - readonly keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter; readonly additionalScrollHeight?: number; } diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index eca74c211c7..9716ea6dc75 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -7,10 +7,10 @@ import { createStyleSheet } from 'vs/base/browser/dom'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { IListMouseEvent, IListRenderer, IListTouchEvent, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IPagedListOptions, IPagedRenderer, PagedList } from 'vs/base/browser/ui/list/listPaging'; -import { DefaultStyleController, IListAccessibilityProvider, IListOptions, IListOptionsUpdate, IMultipleSelectionController, isSelectionRangeChangeEvent, isSelectionSingleChangeEvent, List, TypeNavigationMode } from 'vs/base/browser/ui/list/listWidget'; +import { DefaultStyleController, IKeyboardNavigationEventFilter, IListAccessibilityProvider, IListOptions, IListOptionsUpdate, IMultipleSelectionController, isSelectionRangeChangeEvent, isSelectionSingleChangeEvent, List, TypeNavigationMode } from 'vs/base/browser/ui/list/listWidget'; import { ITableColumn, ITableRenderer, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table'; import { ITableOptions, ITableOptionsUpdate, Table } from 'vs/base/browser/ui/table/tableWidget'; -import { TreeFindMode, IAbstractTreeOptions, IAbstractTreeOptionsUpdate, IKeyboardNavigationEventFilter, RenderIndentGuides } from 'vs/base/browser/ui/tree/abstractTree'; +import { TreeFindMode, IAbstractTreeOptions, IAbstractTreeOptionsUpdate, RenderIndentGuides } from 'vs/base/browser/ui/tree/abstractTree'; import { AsyncDataTree, CompressibleAsyncDataTree, IAsyncDataTreeOptions, IAsyncDataTreeOptionsUpdate, ICompressibleAsyncDataTreeOptions, ICompressibleAsyncDataTreeOptionsUpdate, ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree'; import { DataTree, IDataTreeOptions } from 'vs/base/browser/ui/tree/dataTree'; import { CompressibleObjectTree, ICompressibleObjectTreeOptions, ICompressibleObjectTreeOptionsUpdate, ICompressibleTreeRenderer, IObjectTreeOptions, ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; @@ -187,7 +187,14 @@ class MultipleSelectionController extends Disposable implements IMultipleSele } } -function toWorkbenchListOptions(options: IListOptions, configurationService: IConfigurationService, keybindingService: IKeybindingService): [IListOptions, IDisposable] { +function toWorkbenchListOptions( + accessor: ServicesAccessor, + container: HTMLElement, + options: IListOptions, +): [IListOptions, IDisposable] { + const configurationService = accessor.get(IConfigurationService); + const keybindingService = accessor.get(IKeybindingService); + const disposables = new DisposableStore(); const result: IListOptions = { ...options, @@ -195,7 +202,8 @@ function toWorkbenchListOptions(options: IListOptions, configurationServic smoothScrolling: Boolean(configurationService.getValue(listSmoothScrolling)), mouseWheelScrollSensitivity: configurationService.getValue(mouseWheelScrollSensitivityKey), fastScrollSensitivity: configurationService.getValue(fastScrollSensitivityKey), - multipleSelectionController: options.multipleSelectionController ?? disposables.add(new MultipleSelectionController(configurationService)) + multipleSelectionController: options.multipleSelectionController ?? disposables.add(new MultipleSelectionController(configurationService)), + keyboardNavigationEventFilter: createKeyboardNavigationEventFilter(container, keybindingService), }; return [result, disposables]; @@ -233,10 +241,10 @@ export class WorkbenchList extends List { @IListService listService: IListService, @IThemeService themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService, - @IKeybindingService keybindingService: IKeybindingService + @IInstantiationService instantiationService: IInstantiationService ) { const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : Boolean(configurationService.getValue(horizontalScrollingKey)); - const [workbenchListOptions, workbenchListOptionsDisposable] = toWorkbenchListOptions(options, configurationService, keybindingService); + const [workbenchListOptions, workbenchListOptionsDisposable] = instantiationService.invokeFunction(toWorkbenchListOptions, container, options); super(user, container, delegate, renderers, { @@ -373,10 +381,10 @@ export class WorkbenchPagedList extends PagedList { @IListService listService: IListService, @IThemeService themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService, - @IKeybindingService keybindingService: IKeybindingService + @IInstantiationService instantiationService: IInstantiationService ) { const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : Boolean(configurationService.getValue(horizontalScrollingKey)); - const [workbenchListOptions, workbenchListOptionsDisposable] = toWorkbenchListOptions(options, configurationService, keybindingService); + const [workbenchListOptions, workbenchListOptionsDisposable] = instantiationService.invokeFunction(toWorkbenchListOptions, container, options); super(user, container, delegate, renderers, { keyboardSupport: false, @@ -506,10 +514,10 @@ export class WorkbenchTable extends Table { @IListService listService: IListService, @IThemeService themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService, - @IKeybindingService keybindingService: IKeybindingService + @IInstantiationService instantiationService: IInstantiationService ) { const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : Boolean(configurationService.getValue(horizontalScrollingKey)); - const [workbenchListOptions, workbenchListOptionsDisposable] = toWorkbenchListOptions(options, configurationService, keybindingService); + const [workbenchListOptions, workbenchListOptionsDisposable] = instantiationService.invokeFunction(toWorkbenchListOptions, container, options); super(user, container, delegate, columns, renderers, { @@ -819,13 +827,13 @@ function createKeyboardNavigationEventFilter(container: HTMLElement, keybindingS const result = keybindingService.softDispatch(event, container); - if (result && result.enterChord) { + if (result?.enterChord) { inChord = true; return false; } inChord = false; - return true; + return !result; }; } @@ -1076,6 +1084,7 @@ function workbenchTreeDataPreamble { // give priority to the context key value to specify a value @@ -1098,7 +1107,7 @@ function workbenchTreeDataPreamble): MouseController { diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index ace2ec61b1b..ff455936067 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -16,7 +16,6 @@ import { TrackedRangeStickiness } from 'vs/editor/common/model'; import { PrefixSumComputer } from 'vs/editor/common/model/prefixSumComputer'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IListService, IWorkbenchListOptions, WorkbenchList } from 'vs/platform/list/browser/listService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { CursorAtBoundary, ICellViewModel, CellEditState, CellFocusMode, ICellOutputViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -30,6 +29,7 @@ import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/vie import { BaseCellRenderTemplate, INotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; import { FastDomNode } from 'vs/base/browser/fastDomNode'; import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; const enum CellRevealType { Line, @@ -157,9 +157,9 @@ export class NotebookCellList extends WorkbenchList implements ID @IListService listService: IListService, @IThemeService themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService, - @IKeybindingService keybindingService: IKeybindingService + @IInstantiationService instantiationService: IInstantiationService ) { - super(listUser, container, delegate, renderers, options, contextKeyService, listService, themeService, configurationService, keybindingService); + super(listUser, container, delegate, renderers, options, contextKeyService, listService, themeService, configurationService, instantiationService); NOTEBOOK_CELL_LIST_FOCUSED.bindTo(this.contextKeyService).set(true); this._viewContext = viewContext; this._previousFocusedElements = this.getFocusedElements(); diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index 1d667408085..033ad539305 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -54,6 +54,8 @@ import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServic import { ResourceMap } from 'vs/base/common/map'; import { TestClipboardService } from 'vs/platform/clipboard/test/common/testClipboardService'; import { IWorkingCopySaveEvent } from 'vs/workbench/services/workingCopy/common/workingCopy'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; export class TestCell extends NotebookCellTextModel { constructor( @@ -173,6 +175,7 @@ export function setupInstantiationService(disposables = new DisposableStore()) { instantiationService.stub(IStorageService, new TestStorageService()); instantiationService.stub(IWorkspaceTrustRequestService, new TestWorkspaceTrustRequestService(true)); instantiationService.stub(INotebookExecutionStateService, new TestNotebookExecutionStateService()); + instantiationService.stub(IKeybindingService, new MockKeybindingService()); return instantiationService; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 3bf39183419..d262afd51fb 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -71,7 +71,6 @@ export class TerminalTabList extends WorkbenchList { @IListService listService: IListService, @IThemeService themeService: IThemeService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @IKeybindingService keybindingService: IKeybindingService, @ITerminalService private readonly _terminalService: ITerminalService, @ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService, @IInstantiationService instantiationService: IInstantiationService, @@ -103,7 +102,7 @@ export class TerminalTabList extends WorkbenchList { listService, themeService, _configurationService, - keybindingService, + instantiationService, ); const instanceDisposables: IDisposable[] = [ From 4aec22e316f6c2a6505c6e753558b1cc0902cee2 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Tue, 19 Jul 2022 14:28:55 -0700 Subject: [PATCH 059/197] more code clean up --- .../codeAction/browser/codeActionMenu.ts | 130 +++++++++--------- .../codeAction/browser/codeActionUi.ts | 3 +- .../browser/codeActionWidgetContribution.ts | 2 - 3 files changed, 64 insertions(+), 71 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 37d73cbabc5..49e68a2aed4 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -109,7 +109,7 @@ class CodeMenuRenderer implements IListRenderer keybindingService.getKeybindings() }); - if (this.codeActionList && !this.codeActionList.isDOMFocused()) { - this.dispose(); - } - this._ctxMenuWidgetVisible = Context.Visible.bindTo(_contextKeyService); } @@ -274,7 +270,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { const focusTracker = dom.trackFocus(element); const blurListener = focusTracker.onDidBlur(() => { this.hideCodeActionWidget(); - this._contextViewService.hideContextView({ source: this }); + // this._contextViewService.hideContextView({ source: this }); }); renderDisposables.add(blurListener); renderDisposables.add(focusTracker); @@ -349,7 +345,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { this.viewItems = []; this.focusedEnabledItem = 0; this.currSelectedItem = 0; - this._contextViewService.hideContextView(); + this._contextViewService.hideContextView({ source: this }); this.dispose(); } @@ -381,75 +377,75 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { const useShadowDOM = this._editor.getOption(EditorOption.useShadowDOM); - // if (this.isCodeActionWidgetEnabled(model)) { - this._contextViewService.showContextView({ - getAnchor: () => anchor, - render: (container: HTMLElement) => this.renderCodeActionMenuList(container, menuActions), - onHide: (didCancel) => { - const openedFromString = (options.fromLightbulb) ? CodeActionTriggerSource.Lightbulb : trigger.triggerAction; + if (this.isCodeActionWidgetEnabled(model)) { + this._contextViewService.showContextView({ + getAnchor: () => anchor, + render: (container: HTMLElement) => this.renderCodeActionMenuList(container, menuActions), + onHide: (didCancel) => { + const openedFromString = (options.fromLightbulb) ? CodeActionTriggerSource.Lightbulb : trigger.triggerAction; - type ApplyCodeActionEvent = { - codeActionFrom: CodeActionTriggerSource; - validCodeActions: number; - cancelled: boolean; - }; + type ApplyCodeActionEvent = { + codeActionFrom: CodeActionTriggerSource; + validCodeActions: number; + cancelled: boolean; + }; - type ApplyCodeEventClassification = { - codeActionFrom: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of action used to opened the code action.' }; - validCodeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The total number of valid actions that are highlighted and can be used.' }; - cancelled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The indicator if the menu was selected or cancelled.' }; - owner: 'mjbvz'; - comment: 'Event used to gain insights into how code actions are being triggered'; - }; + type ApplyCodeEventClassification = { + codeActionFrom: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of action used to opened the code action.' }; + validCodeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The total number of valid actions that are highlighted and can be used.' }; + cancelled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The indicator if the menu was selected or cancelled.' }; + owner: 'mjbvz'; + comment: 'Event used to gain insights into how code actions are being triggered'; + }; - this._telemetryService.publicLog2('codeAction.applyCodeAction', { - codeActionFrom: openedFromString, - validCodeActions: codeActions.validActions.length, - cancelled: didCancel, + this._telemetryService.publicLog2('codeAction.applyCodeAction', { + codeActionFrom: openedFromString, + validCodeActions: codeActions.validActions.length, + cancelled: didCancel, - }); - this._visible = false; - this._editor.focus(); + }); + this._visible = false; + this._editor.focus(); + }, }, - }, - this._editor.getDomNode()!, false, - ); - // } else { - // this._contextMenuService.showContextMenu({ - // domForShadowRoot: useShadowDOM ? this._editor.getDomNode()! : undefined, - // getAnchor: () => anchor, - // getActions: () => menuActions, - // onHide: (didCancel) => { - // const openedFromString = (options.fromLightbulb) ? CodeActionTriggerSource.Lightbulb : trigger.triggerAction; + this._editor.getDomNode()!, false, + ); + } else { + this._contextMenuService.showContextMenu({ + domForShadowRoot: useShadowDOM ? this._editor.getDomNode()! : undefined, + getAnchor: () => anchor, + getActions: () => menuActions, + onHide: (didCancel) => { + const openedFromString = (options.fromLightbulb) ? CodeActionTriggerSource.Lightbulb : trigger.triggerAction; - // type ApplyCodeActionEvent = { - // codeActionFrom: CodeActionTriggerSource; - // validCodeActions: number; - // cancelled: boolean; - // }; + type ApplyCodeActionEvent = { + codeActionFrom: CodeActionTriggerSource; + validCodeActions: number; + cancelled: boolean; + }; - // type ApplyCodeEventClassification = { - // codeActionFrom: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of action used to opened the code action.' }; - // validCodeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The total number of valid actions that are highlighted and can be used.' }; - // cancelled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The indicator if the menu was selected or cancelled.' }; - // owner: 'mjbvz'; - // comment: 'Event used to gain insights into how code actions are being triggered'; - // }; + type ApplyCodeEventClassification = { + codeActionFrom: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of action used to opened the code action.' }; + validCodeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The total number of valid actions that are highlighted and can be used.' }; + cancelled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The indicator if the menu was selected or cancelled.' }; + owner: 'mjbvz'; + comment: 'Event used to gain insights into how code actions are being triggered'; + }; - // this._telemetryService.publicLog2('codeAction.applyCodeAction', { - // codeActionFrom: openedFromString, - // validCodeActions: codeActions.validActions.length, - // cancelled: didCancel, + this._telemetryService.publicLog2('codeAction.applyCodeAction', { + codeActionFrom: openedFromString, + validCodeActions: codeActions.validActions.length, + cancelled: didCancel, - // }); + }); - // this._visible = false; - // this._editor.focus(); - // }, - // autoSelectFirstItem: true, - // getKeyBinding: action => action instanceof CodeActionAction ? resolver(action.action) : undefined, - // }); - // } + this._visible = false; + this._editor.focus(); + }, + autoSelectFirstItem: true, + getKeyBinding: action => action instanceof CodeActionAction ? resolver(action.action) : undefined, + }); + } } private getMenuActions( diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts index 59771bfd5e5..bfac0671383 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts @@ -9,7 +9,6 @@ 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 { IEditorContribution } from 'vs/editor/common/editorCommon'; import { CodeActionTriggerType } from 'vs/editor/common/languages'; import { CodeActionItem, CodeActionSet } from 'vs/editor/contrib/codeAction/browser/codeAction'; import { MessageController } from 'vs/editor/contrib/message/browser/messageController'; @@ -19,7 +18,7 @@ import { CodeActionsState } from './codeActionModel'; import { LightBulbWidget } from './lightBulbWidget'; import { CodeActionAutoApply, CodeActionTrigger } from './types'; -export class CodeActionUi extends Disposable implements IEditorContribution { +export class CodeActionUi extends Disposable { private readonly _codeActionWidget: Lazy; private readonly _lightBulbWidget: Lazy; diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionWidgetContribution.ts b/src/vs/editor/contrib/codeAction/browser/codeActionWidgetContribution.ts index 86c88df9597..3e2acafae0f 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionWidgetContribution.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionWidgetContribution.ts @@ -3,9 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { editorConfigurationBaseNode } from 'vs/editor/common/config/editorConfigurationSchema'; -import { CodeActionMenu } from 'vs/editor/contrib/codeAction/browser/codeActionMenu'; import * as nls from 'vs/nls'; import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; From f6a193c91954e6f56e40480bbb770889895c756e Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 19 Jul 2022 15:20:26 -0700 Subject: [PATCH 060/197] Don't clear execution order immediately when execution starts (#155357) Don't clear execution order immediately when execution starts. Same for the timer. Also fix spinner delay logic to correctly implement showing for a minimum time. Fixes #150924 --- .../executionStatusBarItemController.ts | 53 +++++++++++++------ .../notebookExecutionStateServiceImpl.ts | 4 ++ .../browser/view/cellParts/cellExecution.ts | 22 ++++++-- .../browser/view/renderers/cellRenderer.ts | 2 +- 4 files changed, 61 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts index 47d41741736..8acf11d042a 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts @@ -93,7 +93,8 @@ class ExecutionStateCellStatusBarItem extends Disposable { private _currentItemIds: string[] = []; - private _currentExecutingStateTimer: IDisposable | undefined; + private _showedExecutingStateTime: number | undefined; + private _clearExecutingStateTimer: IDisposable | undefined; constructor( private readonly _notebookViewModel: INotebookViewModel, @@ -123,23 +124,27 @@ class ExecutionStateCellStatusBarItem extends Disposable { */ private _getItemsForCell(): INotebookCellStatusBarItem[] | undefined { const runState = this._executionStateService.getCellExecution(this._cell.uri); - if (this._currentExecutingStateTimer && !runState?.isPaused) { - return; + // Show the execution spinner for a minimum time + if (runState?.state === NotebookCellExecutionState.Executing && typeof this._showedExecutingStateTime !== 'number') { + this._showedExecutingStateTime = Date.now(); + } else if (runState?.state !== NotebookCellExecutionState.Executing && typeof this._showedExecutingStateTime === 'number') { + const timeUntilMin = ExecutionStateCellStatusBarItem.MIN_SPINNER_TIME - (Date.now() - this._showedExecutingStateTime); + if (timeUntilMin > 0) { + if (!this._clearExecutingStateTimer) { + this._clearExecutingStateTimer = disposableTimeout(() => { + this._showedExecutingStateTime = undefined; + this._clearExecutingStateTimer = undefined; + this._update(); + }, timeUntilMin); + } + + return undefined; + } else { + this._showedExecutingStateTime = undefined; + } } const item = this._getItemForState(runState, this._cell.internalMetadata); - - // Show the execution spinner for a minimum time - if (runState?.state === NotebookCellExecutionState.Executing) { - this._currentExecutingStateTimer = this._register(disposableTimeout(() => { - const runState = this._executionStateService.getCellExecution(this._cell.uri); - this._currentExecutingStateTimer = undefined; - if (runState?.state !== NotebookCellExecutionState.Executing) { - this._update(); - } - }, ExecutionStateCellStatusBarItem.MIN_SPINNER_TIME)); - } - return item ? [item] : []; } @@ -203,12 +208,16 @@ export class TimerCellStatusBarContrib extends Disposable implements INotebookEd } registerNotebookContribution(TimerCellStatusBarContrib.id, TimerCellStatusBarContrib); +const UPDATE_TIMER_GRACE_PERIOD = 200; + class TimerCellStatusBarItem extends Disposable { private static UPDATE_INTERVAL = 100; private _currentItemIds: string[] = []; private _scheduler: RunOnceScheduler; + private _deferredUpdate: IDisposable | undefined; + constructor( private readonly _notebookViewModel: INotebookViewModel, private readonly _cell: ICellViewModel, @@ -243,7 +252,18 @@ class TimerCellStatusBarItem extends Disposable { } const items = item ? [item] : []; - this._currentItemIds = this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this._cell.handle, items }]); + if (!items.length && !!runState) { + if (!this._deferredUpdate) { + this._deferredUpdate = disposableTimeout(() => { + this._deferredUpdate = undefined; + this._currentItemIds = this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this._cell.handle, items }]); + }, UPDATE_TIMER_GRACE_PERIOD); + } + } else { + this._deferredUpdate?.dispose(); + this._deferredUpdate = undefined; + this._currentItemIds = this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this._cell.handle, items }]); + } } private _getTimeItem(startTime: number, endTime: number, adjustment: number = 0): INotebookCellStatusBarItem { @@ -258,6 +278,7 @@ class TimerCellStatusBarItem extends Disposable { override dispose() { super.dispose(); + this._deferredUpdate?.dispose(); this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this._cell.handle, items: [] }]); } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts index 0cf3e56c598..845cc72df8e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts @@ -124,6 +124,7 @@ export class NotebookExecutionStateService extends Disposable implements INotebo if (!exe) { exe = this._createNotebookCellExecution(notebook, cellHandle); notebookExecutionMap.set(cellHandle, exe); + exe.initialize(); this._onDidChangeCellExecution.fire(new NotebookExecutionEvent(notebookUri, cellHandle, exe)); } @@ -305,6 +306,9 @@ class CellExecution extends Disposable implements INotebookCellExecution { ) { super(); this._logService.debug(`CellExecution#ctor ${this.getCellLog()}`); + } + + initialize() { const startExecuteEdit: ICellEditOperation = { editType: CellEditType.PartialInternalMetadata, handle: this.cellHandle, diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts index 1d9865937ea..8497edd6c0f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts @@ -4,18 +4,23 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; +import { disposableTimeout } from 'vs/base/common/async'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellViewModelStateChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart'; import { NotebookCellInternalMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; + +const UPDATE_EXECUTION_ORDER_GRACE_PERIOD = 200; export class CellExecutionPart extends CellPart { private kernelDisposables = this._register(new DisposableStore()); constructor( private readonly _notebookEditor: INotebookEditorDelegate, - private readonly _executionOrderLabel: HTMLElement + private readonly _executionOrderLabel: HTMLElement, + @INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService ) { super(); @@ -37,11 +42,22 @@ export class CellExecutionPart extends CellPart { } protected override didRenderCell(element: ICellViewModel): void { - this.updateExecutionOrder(element.internalMetadata); + this.updateExecutionOrder(element.internalMetadata, true); } - private updateExecutionOrder(internalMetadata: NotebookCellInternalMetadata): void { + private updateExecutionOrder(internalMetadata: NotebookCellInternalMetadata, forceClear = false): void { if (this._notebookEditor.activeKernel?.implementsExecutionOrder) { + // If the executionOrder was just cleared, and the cell is executing, wait just a bit before clearing the view to avoid flashing + if (typeof internalMetadata.executionOrder !== 'number' && !forceClear && !!this._notebookExecutionStateService.getCellExecution(this.currentCell!.uri)) { + const renderingCell = this.currentCell; + this.cellDisposables.add(disposableTimeout(() => { + if (this.currentCell === renderingCell) { + this.updateExecutionOrder(this.currentCell!.internalMetadata, true); + } + }, UPDATE_EXECUTION_ORDER_GRACE_PERIOD)); + return; + } + const executionOrderLabel = typeof internalMetadata.executionOrder === 'number' ? `[${internalMetadata.executionOrder}]` : '[ ]'; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 77ed53a9ce3..949c8dd25b5 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -313,7 +313,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende templateDisposables.add(scopedInstaService.createInstance(RunToolbar, this.notebookEditor, contextKeyService, container, runButtonContainer)), templateDisposables.add(new CellDecorations(rootContainer, decorationContainer)), templateDisposables.add(scopedInstaService.createInstance(CellComments, this.notebookEditor, cellCommentPartContainer)), - templateDisposables.add(new CellExecutionPart(this.notebookEditor, executionOrderLabel)), + templateDisposables.add(scopedInstaService.createInstance(CellExecutionPart, this.notebookEditor, executionOrderLabel)), templateDisposables.add(scopedInstaService.createInstance(CollapsedCellOutput, this.notebookEditor, cellOutputCollapsedContainer)), templateDisposables.add(new CollapsedCellInput(this.notebookEditor, cellInputCollapsedContainer)), templateDisposables.add(new CellFocusPart(container, focusSinkElement, this.notebookEditor)), From db4ba2062da38b0454c0f421ba1b85cb8bb74d93 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 19 Jul 2022 16:22:52 -0700 Subject: [PATCH 061/197] Show progress indicator for long running file drop operations (#155664) This also allows these operations to be easily cancelled. --- .../browser/dropIntoEditorContribution.ts | 58 +++++++++++++------ .../standalone/browser/standaloneServices.ts | 14 ++++- 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts b/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts index 551092627e5..10ed525fc05 100644 --- a/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts +++ b/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { raceCancellation } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { VSDataTransfer } from 'vs/base/common/dataTransfer'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -23,7 +24,9 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState'; import { performSnippetEdit } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser'; +import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -33,10 +36,11 @@ export class DropIntoEditorController extends Disposable implements IEditorContr constructor( editor: ICodeEditor, - @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, - @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, - @IConfigurationService private readonly _configurationService: IConfigurationService, @IBulkEditService private readonly _bulkEditService: IBulkEditService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, + @IProgressService private readonly _progressService: IProgressService, + @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, ) { super(); @@ -66,35 +70,55 @@ export class DropIntoEditorController extends Disposable implements IEditorContr } const model = editor.getModel(); - const modelVersionNow = model.getVersionId(); + const initialModelVersion = model.getVersionId(); const ourDataTransfer = await this.extractDataTransferData(dragEvent); if (ourDataTransfer.size === 0) { return; } - if (editor.getModel().getVersionId() !== modelVersionNow) { + if (editor.getModel().getVersionId() !== initialModelVersion) { return; } const tokenSource = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value); try { const providers = this._languageFeaturesService.documentOnDropEditProvider.ordered(model); - for (const provider of providers) { - const edit = await provider.provideDocumentOnDropEdits(model, position, ourDataTransfer, tokenSource.token); - if (tokenSource.token.isCancellationRequested || editor.getModel().getVersionId() !== modelVersionNow) { - return; - } - if (edit) { - const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); - performSnippetEdit(editor, typeof edit.insertText === 'string' ? SnippetParser.escape(edit.insertText) : edit.insertText.snippet, [Selection.fromRange(range, SelectionDirection.LTR)]); - - if (edit.additionalEdit) { - await this._bulkEditService.apply(ResourceEdit.convert(edit.additionalEdit), { editor }); + const edit = await this._progressService.withProgress({ + location: ProgressLocation.Notification, + delay: 750, + title: localize('dropProgressTitle', "Running drop handlers..."), + cancellable: true, + }, () => { + return raceCancellation((async () => { + for (const provider of providers) { + const edit = await provider.provideDocumentOnDropEdits(model, position, ourDataTransfer, tokenSource.token); + if (tokenSource.token.isCancellationRequested) { + return undefined; + } + if (edit) { + return edit; + } } - return; + return undefined; + })(), tokenSource.token); + }, () => { + tokenSource.cancel(); + }); + + if (tokenSource.token.isCancellationRequested || editor.getModel().getVersionId() !== initialModelVersion) { + return; + } + + if (edit) { + const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); + performSnippetEdit(editor, typeof edit.insertText === 'string' ? SnippetParser.escape(edit.insertText) : edit.insertText.snippet, [Selection.fromRange(range, SelectionDirection.LTR)]); + + if (edit.additionalEdit) { + await this._bulkEditService.apply(ResourceEdit.convert(edit.additionalEdit), { editor }); } + return; } } finally { tokenSource.dispose(); diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 8356bd94844..be6633e6582 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -41,7 +41,7 @@ import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKe import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; import { ILabelService, ResourceLabelFormatter, IFormatterChangeEvent } from 'vs/platform/label/common/label'; import { INotification, INotificationHandle, INotificationService, IPromptChoice, IPromptOptions, NoOpNotification, IStatusMessageOptions } from 'vs/platform/notification/common/notification'; -import { IProgressRunner, IEditorProgressService } from 'vs/platform/progress/common/progress'; +import { IProgressRunner, IEditorProgressService, IProgressService, IProgress, IProgressCompositeOptions, IProgressDialogOptions, IProgressNotificationOptions, IProgressOptions, IProgressStep, IProgressWindowOptions } from 'vs/platform/progress/common/progress'; import { ITelemetryInfo, ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier, IWorkspace, IWorkspaceContextService, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, IWorkspaceFoldersWillChangeEvent, WorkbenchState, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; @@ -188,6 +188,17 @@ class StandaloneEditorProgressService implements IEditorProgressService { } } +class StandaloneProgressService implements IProgressService { + + declare readonly _serviceBrand: undefined; + + withProgress(_options: IProgressOptions | IProgressDialogOptions | IProgressNotificationOptions | IProgressWindowOptions | IProgressCompositeOptions, task: (progress: IProgress) => Promise, onDidCancel?: ((choice?: number | undefined) => void) | undefined): Promise { + return task({ + report: () => { }, + }); + } +} + class StandaloneDialogService implements IDialogService { public _serviceBrand: undefined; @@ -962,6 +973,7 @@ registerSingleton(ILogService, StandaloneLogService); registerSingleton(IModelService, ModelService); registerSingleton(IMarkerDecorationsService, MarkerDecorationsService); registerSingleton(IContextKeyService, ContextKeyService); +registerSingleton(IProgressService, StandaloneProgressService); registerSingleton(IEditorProgressService, StandaloneEditorProgressService); registerSingleton(IStorageService, InMemoryStorageService); registerSingleton(IEditorWorkerService, EditorWorkerService); From 32f5e49082ca834c1caf44075220953689987ed0 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 19 Jul 2022 16:34:09 -0700 Subject: [PATCH 062/197] Move MD diagnostics to language server (#155653) * Move MD diagnostics to language server This switches us to using the LSP pull diagnostic model with a new version of the language service * Bump package version * Delete unused file --- .../markdown-language-features/package.json | 4 +- .../server/.vscode/launch.json | 6 +- .../server/.vscode/settings.json | 2 + .../server/package.json | 2 +- .../server/src/configuration.ts | 59 ++ .../src/languageFeatures/diagnostics.ts | 86 +++ .../server/src/logging.ts | 7 +- .../server/src/protocol.ts | 7 +- .../server/src/server.ts | 76 +- .../server/src/util/dispose.ts | 80 +++ .../server/src/workspace.ts | 58 +- .../server/yarn.lock | 20 +- .../markdown-language-features/src/client.ts | 49 +- .../src/extension.browser.ts | 3 + .../src/extension.shared.ts | 19 +- .../src/extension.ts | 3 + .../src/languageFeatures/diagnostics.ts | 650 +----------------- .../src/languageFeatures/documentLinks.ts | 540 --------------- .../src/languageFeatures/references.ts | 329 --------- .../src/protocol.ts | 6 + .../src/test/diagnostic.test.ts | 591 ---------------- .../src/test/documentInfoCache.test.ts | 33 - .../src/test/tableOfContentsProvider.test.ts | 136 ---- .../src/util/tableOfContentsWatcher.ts | 89 --- .../src/util/workspaceCache.ts | 71 -- .../markdown-language-features/yarn.lock | 23 +- 26 files changed, 475 insertions(+), 2474 deletions(-) create mode 100644 extensions/markdown-language-features/server/.vscode/settings.json create mode 100644 extensions/markdown-language-features/server/src/configuration.ts create mode 100644 extensions/markdown-language-features/server/src/languageFeatures/diagnostics.ts create mode 100644 extensions/markdown-language-features/server/src/util/dispose.ts delete mode 100644 extensions/markdown-language-features/src/languageFeatures/documentLinks.ts delete mode 100644 extensions/markdown-language-features/src/languageFeatures/references.ts delete mode 100644 extensions/markdown-language-features/src/test/diagnostic.test.ts delete mode 100644 extensions/markdown-language-features/src/test/documentInfoCache.test.ts delete mode 100644 extensions/markdown-language-features/src/test/tableOfContentsProvider.test.ts delete mode 100644 extensions/markdown-language-features/src/util/tableOfContentsWatcher.ts diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 0b5d9f8358e..1d5bef40506 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -478,6 +478,7 @@ "type": "string", "scope": "resource", "markdownDescription": "%configuration.markdown.experimental.validate.fileLinks.markdownFragmentLinks.description%", + "default": "ignore", "enum": [ "ignore", "warning", @@ -563,7 +564,8 @@ "@types/vscode-notebook-renderer": "^1.60.0", "@types/vscode-webview": "^1.57.0", "lodash.throttle": "^4.1.1", - "vscode-languageserver-types": "^3.17.2" + "vscode-languageserver-types": "^3.17.2", + "vscode-markdown-languageservice": "^0.0.0-alpha.10" }, "repository": { "type": "git", diff --git a/extensions/markdown-language-features/server/.vscode/launch.json b/extensions/markdown-language-features/server/.vscode/launch.json index a28c18b6973..fd9033bffaa 100644 --- a/extensions/markdown-language-features/server/.vscode/launch.json +++ b/extensions/markdown-language-features/server/.vscode/launch.json @@ -6,9 +6,11 @@ "name": "Attach", "type": "node", "request": "attach", - "port": 7692, + "port": 7997, "sourceMaps": true, - "outFiles": ["${workspaceFolder}/out/**/*.js"] + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ] } ] } \ No newline at end of file diff --git a/extensions/markdown-language-features/server/.vscode/settings.json b/extensions/markdown-language-features/server/.vscode/settings.json new file mode 100644 index 00000000000..7a73a41bfdf --- /dev/null +++ b/extensions/markdown-language-features/server/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/extensions/markdown-language-features/server/package.json b/extensions/markdown-language-features/server/package.json index 9543321edab..4be608a25a4 100644 --- a/extensions/markdown-language-features/server/package.json +++ b/extensions/markdown-language-features/server/package.json @@ -13,7 +13,7 @@ "vscode-languageserver": "^8.0.2-next.5`", "vscode-languageserver-textdocument": "^1.0.5", "vscode-languageserver-types": "^3.17.1", - "vscode-markdown-languageservice": "^0.0.0-alpha.8", + "vscode-markdown-languageservice": "^0.0.0-alpha.10", "vscode-uri": "^3.0.3" }, "devDependencies": { diff --git a/extensions/markdown-language-features/server/src/configuration.ts b/extensions/markdown-language-features/server/src/configuration.ts new file mode 100644 index 00000000000..5066b3110a6 --- /dev/null +++ b/extensions/markdown-language-features/server/src/configuration.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Connection, Emitter } from 'vscode-languageserver'; +import { Disposable } from './util/dispose'; + +export type ValidateEnabled = 'ignore' | 'warning' | 'error'; + +interface Settings { + readonly markdown: { + readonly suggest: { + readonly paths: { + readonly enabled: boolean; + }; + }; + + readonly experimental: { + readonly validate: { + readonly enabled: true; + readonly referenceLinks: { + readonly enabled: ValidateEnabled; + }; + readonly fragmentLinks: { + readonly enabled: ValidateEnabled; + }; + readonly fileLinks: { + readonly enabled: ValidateEnabled; + readonly markdownFragmentLinks: ValidateEnabled; + }; + readonly ignoreLinks: readonly string[]; + }; + }; + }; +} + + +export class ConfigurationManager extends Disposable { + + private readonly _onDidChangeConfiguration = this._register(new Emitter()); + public readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; + + private _settings?: Settings; + + constructor(connection: Connection) { + super(); + + // The settings have changed. Is send on server activation as well. + this._register(connection.onDidChangeConfiguration((change) => { + this._settings = change.settings; + this._onDidChangeConfiguration.fire(this._settings!); + })); + } + + public getSettings(): Settings | undefined { + return this._settings; + } +} \ No newline at end of file diff --git a/extensions/markdown-language-features/server/src/languageFeatures/diagnostics.ts b/extensions/markdown-language-features/server/src/languageFeatures/diagnostics.ts new file mode 100644 index 00000000000..df3d17a9714 --- /dev/null +++ b/extensions/markdown-language-features/server/src/languageFeatures/diagnostics.ts @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Connection, FullDocumentDiagnosticReport, UnchangedDocumentDiagnosticReport } from 'vscode-languageserver'; +import * as md from 'vscode-markdown-languageservice'; +import { Disposable } from 'vscode-notebook-renderer/events'; +import { URI } from 'vscode-uri'; +import { ConfigurationManager, ValidateEnabled } from '../configuration'; +import { VsCodeClientWorkspace } from '../workspace'; + +const defaultDiagnosticOptions: md.DiagnosticOptions = { + validateFileLinks: md.DiagnosticLevel.ignore, + validateReferences: md.DiagnosticLevel.ignore, + validateFragmentLinks: md.DiagnosticLevel.ignore, + validateMarkdownFileLinkFragments: md.DiagnosticLevel.ignore, + ignoreLinks: [], +}; + +function convertDiagnosticLevel(enabled: ValidateEnabled): md.DiagnosticLevel | undefined { + switch (enabled) { + case 'error': return md.DiagnosticLevel.error; + case 'warning': return md.DiagnosticLevel.warning; + case 'ignore': return md.DiagnosticLevel.ignore; + default: return md.DiagnosticLevel.ignore; + } +} + +function getDiagnosticsOptions(config: ConfigurationManager): md.DiagnosticOptions { + const settings = config.getSettings(); + if (!settings) { + return defaultDiagnosticOptions; + } + + return { + validateFileLinks: convertDiagnosticLevel(settings.markdown.experimental.validate.fileLinks.enabled), + validateReferences: convertDiagnosticLevel(settings.markdown.experimental.validate.referenceLinks.enabled), + validateFragmentLinks: convertDiagnosticLevel(settings.markdown.experimental.validate.fragmentLinks.enabled), + validateMarkdownFileLinkFragments: convertDiagnosticLevel(settings.markdown.experimental.validate.fileLinks.markdownFragmentLinks), + ignoreLinks: settings.markdown.experimental.validate.ignoreLinks, + }; +} + +export function registerValidateSupport( + connection: Connection, + workspace: VsCodeClientWorkspace, + ls: md.IMdLanguageService, + config: ConfigurationManager, +): Disposable { + let diagnosticOptions: md.DiagnosticOptions = defaultDiagnosticOptions; + function updateDiagnosticsSetting(): void { + diagnosticOptions = getDiagnosticsOptions(config); + } + + const manager = ls.createPullDiagnosticsManager(); + + connection.languages.diagnostics.on(async (params, token): Promise => { + if (!config.getSettings()?.markdown.experimental.validate.enabled) { + return { kind: 'full', items: [] }; + } + + const document = await workspace.openMarkdownDocument(URI.parse(params.textDocument.uri)); + if (!document) { + return { kind: 'full', items: [] }; + } + + const diagnostics = await manager.computeDiagnostics(document, diagnosticOptions, token); + return { + kind: 'full', + items: diagnostics, + }; + }); + + updateDiagnosticsSetting(); + const configChangeSub = config.onDidChangeConfiguration(() => { + updateDiagnosticsSetting(); + connection.languages.diagnostics.refresh(); + }); + return { + dispose: () => { + manager.dispose(); + configChangeSub.dispose(); + } + }; +} diff --git a/extensions/markdown-language-features/server/src/logging.ts b/extensions/markdown-language-features/server/src/logging.ts index 2a026caf580..2fc08c25b7a 100644 --- a/extensions/markdown-language-features/server/src/logging.ts +++ b/extensions/markdown-language-features/server/src/logging.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ILogger } from 'vscode-markdown-languageservice'; +import { ILogger, LogLevel } from 'vscode-markdown-languageservice'; export class LogFunctionLogger implements ILogger { @@ -31,8 +31,9 @@ export class LogFunctionLogger implements ILogger { private readonly _logFn: typeof console.log ) { } - public verbose(title: string, message: string, data?: any): void { - this.appendLine(`[Verbose ${LogFunctionLogger.now()}] ${title}: ${message}`); + + public log(level: LogLevel, title: string, message: string, data?: any): void { + this.appendLine(`[${level} ${LogFunctionLogger.now()}] ${title}: ${message}`); if (data) { this.appendLine(LogFunctionLogger.data2String(data)); } diff --git a/extensions/markdown-language-features/server/src/protocol.ts b/extensions/markdown-language-features/server/src/protocol.ts index 206e0fbe8c7..5bae3701d8a 100644 --- a/extensions/markdown-language-features/server/src/protocol.ts +++ b/extensions/markdown-language-features/server/src/protocol.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { RequestType } from 'vscode-languageserver'; -import * as md from 'vscode-markdown-languageservice'; import * as lsp from 'vscode-languageserver-types'; +import * as md from 'vscode-markdown-languageservice'; // From server export const parseRequestType: RequestType<{ uri: string }, md.Token[], any> = new RequestType('markdown/parse'); @@ -14,5 +14,10 @@ export const statFileRequestType: RequestType<{ uri: string }, md.FileStat | und export const readDirectoryRequestType: RequestType<{ uri: string }, [string, md.FileStat][], any> = new RequestType('markdown/readDirectory'); export const findFilesRequestTypes: RequestType<{}, string[], any> = new RequestType('markdown/findFiles'); +export const createFileWatcher: RequestType<{ id: number; uri: string; options: md.FileWatcherOptions }, void, any> = new RequestType('markdown/createFileWatcher'); +export const deleteFileWatcher: RequestType<{ id: number }, void, any> = new RequestType('markdown/deleteFileWatcher'); + // To server export const getReferencesToFileInWorkspace = new RequestType<{ uri: string }, lsp.Location[], any>('markdown/getReferencesToFileInWorkspace'); + +export const onWatcherChange: RequestType<{ id: number; uri: string; kind: 'create' | 'change' | 'delete' }, void, any> = new RequestType('markdown/onWatcherChange'); diff --git a/extensions/markdown-language-features/server/src/server.ts b/extensions/markdown-language-features/server/src/server.ts index b5891712533..3b7248830a7 100644 --- a/extensions/markdown-language-features/server/src/server.ts +++ b/extensions/markdown-language-features/server/src/server.ts @@ -7,8 +7,11 @@ import { CancellationToken, Connection, InitializeParams, InitializeResult, Note import { TextDocument } from 'vscode-languageserver-textdocument'; import * as lsp from 'vscode-languageserver-types'; import * as md from 'vscode-markdown-languageservice'; +import { IDisposable } from 'vscode-markdown-languageservice/out/util/dispose'; import { URI } from 'vscode-uri'; import { getLsConfiguration } from './config'; +import { ConfigurationManager } from './configuration'; +import { registerValidateSupport } from './languageFeatures/diagnostics'; import { LogFunctionLogger } from './logging'; import * as protocol from './protocol'; import { VsCodeClientWorkspace } from './workspace'; @@ -17,6 +20,11 @@ export async function startServer(connection: Connection) { const documents = new TextDocuments(TextDocument); const notebooks = new NotebookDocuments(documents); + const configurationManager = new ConfigurationManager(connection); + + let provider: md.IMdLanguageService | undefined; + let workspace: VsCodeClientWorkspace | undefined; + connection.onInitialize((params: InitializeParams): InitializeResult => { const parser = new class implements md.IMdParser { slugifier = md.githubSlugifier; @@ -30,8 +38,8 @@ export async function startServer(connection: Connection) { markdownFileExtensions: params.initializationOptions.markdownFileExtensions, }); - const workspace = new VsCodeClientWorkspace(connection, config, documents, notebooks); const logger = new LogFunctionLogger(connection.console.log.bind(connection.console)); + workspace = new VsCodeClientWorkspace(connection, config, documents, notebooks, logger); provider = md.createLanguageService({ workspace, parser, @@ -39,9 +47,18 @@ export async function startServer(connection: Connection) { markdownFileExtensions: config.markdownFileExtensions, }); + registerCompletionsSupport(connection, documents, provider, configurationManager); + registerValidateSupport(connection, workspace, provider, configurationManager); + workspace.workspaceFolders = (params.workspaceFolders ?? []).map(x => URI.parse(x.uri)); return { capabilities: { + diagnosticProvider: { + documentSelector: null, + identifier: 'markdown', + interFileDependencies: true, + workspaceDiagnostics: false, + }, completionProvider: { triggerCharacters: ['.', '/', '#'] }, definitionProvider: true, documentLinkProvider: { resolveProvider: true }, @@ -61,8 +78,6 @@ export async function startServer(connection: Connection) { }); - let provider: md.IMdLanguageService | undefined; - connection.onDocumentLinks(async (params, token): Promise => { try { const document = documents.get(params.textDocument.uri); @@ -129,18 +144,6 @@ export async function startServer(connection: Connection) { return []; }); - connection.onCompletion(async (params, token): Promise => { - try { - const document = documents.get(params.textDocument.uri); - if (document) { - return await provider!.getCompletionItems(document, params.position, params.context!, token); - } - } catch (e) { - console.error(e.stack); - } - return []; - }); - connection.onReferences(async (params, token): Promise => { try { const document = documents.get(params.textDocument.uri); @@ -204,3 +207,46 @@ export async function startServer(connection: Connection) { notebooks.listen(connection); connection.listen(); } + + +function registerCompletionsSupport( + connection: Connection, + documents: TextDocuments, + ls: md.IMdLanguageService, + config: ConfigurationManager, +): IDisposable { + // let registration: Promise | undefined; + function update() { + // TODO: client still makes the request in this case. Figure our how to properly unregister. + return; + // const settings = config.getSettings(); + // if (settings?.markdown.suggest.paths.enabled) { + // if (!registration) { + // registration = connection.client.register(CompletionRequest.type); + // } + // } else { + // registration?.then(x => x.dispose()); + // registration = undefined; + // } + } + + connection.onCompletion(async (params, token): Promise => { + try { + const settings = config.getSettings(); + if (!settings?.markdown.suggest.paths.enabled) { + return []; + } + + const document = documents.get(params.textDocument.uri); + if (document) { + return await ls.getCompletionItems(document, params.position, params.context!, token); + } + } catch (e) { + console.error(e.stack); + } + return []; + }); + + update(); + return config.onDidChangeConfiguration(() => update()); +} diff --git a/extensions/markdown-language-features/server/src/util/dispose.ts b/extensions/markdown-language-features/server/src/util/dispose.ts new file mode 100644 index 00000000000..eee79003ae9 --- /dev/null +++ b/extensions/markdown-language-features/server/src/util/dispose.ts @@ -0,0 +1,80 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export class MultiDisposeError extends Error { + constructor( + public readonly errors: any[] + ) { + super(`Encountered errors while disposing of store. Errors: [${errors.join(', ')}]`); + } +} + +export function disposeAll(disposables: Iterable) { + const errors: any[] = []; + + for (const disposable of disposables) { + try { + disposable.dispose(); + } catch (e) { + errors.push(e); + } + } + + if (errors.length === 1) { + throw errors[0]; + } else if (errors.length > 1) { + throw new MultiDisposeError(errors); + } +} + +export interface IDisposable { + dispose(): void; +} + +export abstract class Disposable { + private _isDisposed = false; + + protected _disposables: IDisposable[] = []; + + public dispose(): any { + if (this._isDisposed) { + return; + } + this._isDisposed = true; + disposeAll(this._disposables); + } + + protected _register(value: T): T { + if (this._isDisposed) { + value.dispose(); + } else { + this._disposables.push(value); + } + return value; + } + + protected get isDisposed() { + return this._isDisposed; + } +} + +export class DisposableStore extends Disposable { + private readonly items = new Set(); + + public override dispose() { + super.dispose(); + disposeAll(this.items); + this.items.clear(); + } + + public add(item: T): T { + if (this.isDisposed) { + console.warn('Adding to disposed store. Item will be leaked'); + } + + this.items.add(item); + return item; + } +} diff --git a/extensions/markdown-language-features/server/src/workspace.ts b/extensions/markdown-language-features/server/src/workspace.ts index 5847d0c5505..dee32bfe85c 100644 --- a/extensions/markdown-language-features/server/src/workspace.ts +++ b/extensions/markdown-language-features/server/src/workspace.ts @@ -6,7 +6,7 @@ import { Connection, Emitter, FileChangeType, NotebookDocuments, TextDocuments } from 'vscode-languageserver'; import { TextDocument } from 'vscode-languageserver-textdocument'; import * as md from 'vscode-markdown-languageservice'; -import { ContainingDocumentContext } from 'vscode-markdown-languageservice/out/workspace'; +import { ContainingDocumentContext, FileWatcherOptions, IFileSystemWatcher } from 'vscode-markdown-languageservice/out/workspace'; import { URI } from 'vscode-uri'; import { LsConfiguration } from './config'; import * as protocol from './protocol'; @@ -18,7 +18,7 @@ import { Schemes } from './util/schemes'; declare const TextDecoder: any; -export class VsCodeClientWorkspace implements md.IWorkspace { +export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching { private readonly _onDidCreateMarkdownDocument = new Emitter(); public readonly onDidCreateMarkdownDocument = this._onDidCreateMarkdownDocument.event; @@ -33,11 +33,21 @@ export class VsCodeClientWorkspace implements md.IWorkspace { private readonly _utf8Decoder = new TextDecoder('utf-8'); + private _watcherPool = 0; + private readonly _watchers = new Map; + readonly onDidCreate: Emitter; + readonly onDidDelete: Emitter; + }>(); + constructor( private readonly connection: Connection, private readonly config: LsConfiguration, private readonly documents: TextDocuments, private readonly notebooks: NotebookDocuments, + private readonly logger: md.ILogger, ) { documents.onDidOpen(e => { this._documentCache.delete(URI.parse(e.document.uri)); @@ -83,6 +93,18 @@ export class VsCodeClientWorkspace implements md.IWorkspace { } } }); + + connection.onRequest(protocol.onWatcherChange, params => { + const watcher = this._watchers.get(params.id); + if (!watcher) { + return; + } + switch (params.kind) { + case 'create': watcher.onDidCreate.fire(URI.parse(params.uri)); return; + case 'change': watcher.onDidChange.fire(URI.parse(params.uri)); return; + case 'delete': watcher.onDidDelete.fire(URI.parse(params.uri)); return; + } + }); } public listen() { @@ -154,7 +176,7 @@ export class VsCodeClientWorkspace implements md.IWorkspace { // We assume that markdown is in UTF-8 const text = this._utf8Decoder.decode(bytes); - const doc = new md.InMemoryDocument(resource, text, 0); + const doc = TextDocument.create(resource.toString(), 'markdown', 0, text); this._documentCache.set(resource, doc); return doc; } catch (e) { @@ -163,6 +185,7 @@ export class VsCodeClientWorkspace implements md.IWorkspace { } async stat(resource: URI): Promise { + this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: stat', `${resource}`); if (this._documentCache.has(resource) || this.documents.get(resource.toString())) { return { isDirectory: false }; } @@ -170,6 +193,7 @@ export class VsCodeClientWorkspace implements md.IWorkspace { } async readDirectory(resource: URI): Promise<[string, md.FileStat][]> { + this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: readDir', `${resource}`); return this.connection.sendRequest(protocol.readDirectoryRequestType, { uri: resource.toString() }); } @@ -186,6 +210,34 @@ export class VsCodeClientWorkspace implements md.IWorkspace { return undefined; } + watchFile(resource: URI, options: FileWatcherOptions): IFileSystemWatcher { + const entry = { + resource, + options, + onDidCreate: new Emitter(), + onDidChange: new Emitter(), + onDidDelete: new Emitter(), + }; + const id = this._watcherPool++; + this._watchers.set(id, entry); + + this.connection.sendRequest(protocol.createFileWatcher, { + id, + uri: resource.toString(), + options, + }); + + return { + onDidCreate: entry.onDidCreate.event, + onDidChange: entry.onDidChange.event, + onDidDelete: entry.onDidDelete.event, + dispose: () => { + this.connection.sendRequest(protocol.deleteFileWatcher, { id }); + this._watchers.delete(id); + } + }; + } + private isRelevantMarkdownDocument(doc: TextDocument) { return isMarkdownFile(doc) && URI.parse(doc.uri).scheme !== 'vscode-bulkeditpreview'; } diff --git a/extensions/markdown-language-features/server/yarn.lock b/extensions/markdown-language-features/server/yarn.lock index e930ffa60ed..feabc56b943 100644 --- a/extensions/markdown-language-features/server/yarn.lock +++ b/extensions/markdown-language-features/server/yarn.lock @@ -7,6 +7,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.43.tgz#555e5a743f76b6b897d47f945305b618525ddbe6" integrity sha512-GqWykok+3uocgfAJM8imbozrqLnPyTrpFlrryURQlw1EesPUCx5XxTiucWDSFF9/NUEXDuD4bnvHm8xfVGWTpQ== +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + vscode-jsonrpc@8.0.2-next.1: version "8.0.2-next.1" resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.2-next.1.tgz#6bdc39fd194782032e34047eeefce562941259c6" @@ -42,15 +47,22 @@ vscode-languageserver@^8.0.2-next.5`: dependencies: vscode-languageserver-protocol "3.17.2-next.6" -vscode-markdown-languageservice@^0.0.0-alpha.8: - version "0.0.0-alpha.8" - resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.0.0-alpha.8.tgz#05d4f86cf0514fd71479847eef742fcc8cdbe87f" - integrity sha512-si8weZsY4LtyonyZwxpFYk8WucRFiKJisErNTt1HDjUCglSDIZqsMNuMIcz3t0nVNfG0LrpdMFVLGhmET5D71Q== +vscode-markdown-languageservice@^0.0.0-alpha.10: + version "0.0.0-alpha.10" + resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.0.0-alpha.10.tgz#53b69c981eed7fd5efa155ab8c0f169995568681" + integrity sha512-rJ85nJ+d45yCz9lBhipavoWXz/vW5FknqqUpLqhe3/2xkrhxt8zcekhSoDepgkKFcTORAFV6g1SnnqxbVhX+uA== dependencies: + picomatch "^2.3.1" vscode-languageserver-textdocument "^1.0.5" vscode-languageserver-types "^3.17.1" + vscode-nls "^5.0.1" vscode-uri "^3.0.3" +vscode-nls@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2" + integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A== + vscode-uri@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.3.tgz#a95c1ce2e6f41b7549f86279d19f47951e4f4d84" diff --git a/extensions/markdown-language-features/src/client.ts b/extensions/markdown-language-features/src/client.ts index 96b43406961..a839131f950 100644 --- a/extensions/markdown-language-features/src/client.ts +++ b/extensions/markdown-language-features/src/client.ts @@ -3,22 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import Token = require('markdown-it/lib/token'); import * as vscode from 'vscode'; -import { BaseLanguageClient, LanguageClientOptions, NotebookDocumentSyncRegistrationType, RequestType } from 'vscode-languageclient'; +import { BaseLanguageClient, LanguageClientOptions, NotebookDocumentSyncRegistrationType } from 'vscode-languageclient'; import * as nls from 'vscode-nls'; import { IMdParser } from './markdownEngine'; -import { markdownFileExtensions } from './util/file'; +import * as proto from './protocol'; +import { looksLikeMarkdownPath, markdownFileExtensions } from './util/file'; import { IMdWorkspace } from './workspace'; const localize = nls.loadMessageBundle(); -const parseRequestType: RequestType<{ uri: string }, Token[], any> = new RequestType('markdown/parse'); -const readFileRequestType: RequestType<{ uri: string }, number[], any> = new RequestType('markdown/readFile'); -const statFileRequestType: RequestType<{ uri: string }, { isDirectory: boolean } | undefined, any> = new RequestType('markdown/statFile'); -const readDirectoryRequestType: RequestType<{ uri: string }, [string, { isDirectory: boolean }][], any> = new RequestType('markdown/readDirectory'); -const findFilesRequestTypes: RequestType<{}, string[], any> = new RequestType('markdown/findFiles'); - export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => BaseLanguageClient; @@ -34,7 +28,16 @@ export async function startClient(factory: LanguageClientConstructor, workspace: }, initializationOptions: { markdownFileExtensions, - } + }, + diagnosticPullOptions: { + onChange: true, + onSave: true, + onTabs: true, + match(_documentSelector, resource) { + return looksLikeMarkdownPath(resource); + }, + }, + }; const client = factory('markdown', localize('markdownServer.name', 'Markdown Language Server'), clientOptions); @@ -54,7 +57,7 @@ export async function startClient(factory: LanguageClientConstructor, workspace: }); } - client.onRequest(parseRequestType, async (e) => { + client.onRequest(proto.parseRequestType, async (e) => { const uri = vscode.Uri.parse(e.uri); const doc = await workspace.getOrLoadMarkdownDocument(uri); if (doc) { @@ -64,12 +67,12 @@ export async function startClient(factory: LanguageClientConstructor, workspace: } }); - client.onRequest(readFileRequestType, async (e): Promise => { + client.onRequest(proto.readFileRequestType, async (e): Promise => { const uri = vscode.Uri.parse(e.uri); return Array.from(await vscode.workspace.fs.readFile(uri)); }); - client.onRequest(statFileRequestType, async (e): Promise<{ isDirectory: boolean } | undefined> => { + client.onRequest(proto.statFileRequestType, async (e): Promise<{ isDirectory: boolean } | undefined> => { const uri = vscode.Uri.parse(e.uri); try { const stat = await vscode.workspace.fs.stat(uri); @@ -79,16 +82,32 @@ export async function startClient(factory: LanguageClientConstructor, workspace: } }); - client.onRequest(readDirectoryRequestType, async (e): Promise<[string, { isDirectory: boolean }][]> => { + client.onRequest(proto.readDirectoryRequestType, async (e): Promise<[string, { isDirectory: boolean }][]> => { const uri = vscode.Uri.parse(e.uri); const result = await vscode.workspace.fs.readDirectory(uri); return result.map(([name, type]) => [name, { isDirectory: type === vscode.FileType.Directory }]); }); - client.onRequest(findFilesRequestTypes, async (): Promise => { + client.onRequest(proto.findFilesRequestTypes, async (): Promise => { return (await vscode.workspace.findFiles(mdFileGlob, '**/node_modules/**')).map(x => x.toString()); }); + const watchers = new Map(); + + client.onRequest(proto.createFileWatcher, async (params): Promise => { + const id = params.id; + const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(vscode.Uri.parse(params.uri), '*'), params.options.ignoreCreate, params.options.ignoreChange, params.options.ignoreDelete); + watchers.set(id, watcher); + watcher.onDidCreate(() => { client.sendRequest(proto.onWatcherChange, { id, uri: params.uri, kind: 'create' }); }); + watcher.onDidChange(() => { client.sendRequest(proto.onWatcherChange, { id, uri: params.uri, kind: 'change' }); }); + watcher.onDidDelete(() => { client.sendRequest(proto.onWatcherChange, { id, uri: params.uri, kind: 'delete' }); }); + }); + + client.onRequest(proto.deleteFileWatcher, async (params): Promise => { + watchers.get(params.id)?.dispose(); + watchers.delete(params.id); + }); + await client.start(); return client; diff --git a/extensions/markdown-language-features/src/extension.browser.ts b/extensions/markdown-language-features/src/extension.browser.ts index d582a33606b..456a3811e45 100644 --- a/extensions/markdown-language-features/src/extension.browser.ts +++ b/extensions/markdown-language-features/src/extension.browser.ts @@ -26,6 +26,9 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(workspace); const client = await startServer(context, workspace, engine); + context.subscriptions.push({ + dispose: () => client.stop() + }); activateShared(context, client, workspace, engine, logger, contributions); } diff --git a/extensions/markdown-language-features/src/extension.shared.ts b/extensions/markdown-language-features/src/extension.shared.ts index e3a2e2bd253..4a8e043b520 100644 --- a/extensions/markdown-language-features/src/extension.shared.ts +++ b/extensions/markdown-language-features/src/extension.shared.ts @@ -9,12 +9,10 @@ import { CommandManager } from './commandManager'; import * as commands from './commands/index'; import { registerPasteSupport } from './languageFeatures/copyPaste'; import { registerDiagnosticSupport } from './languageFeatures/diagnostics'; -import { MdLinkProvider } from './languageFeatures/documentLinks'; import { registerDropIntoEditorSupport } from './languageFeatures/dropIntoEditor'; import { registerFindFileReferenceSupport } from './languageFeatures/fileReferences'; -import { MdReferencesProvider } from './languageFeatures/references'; import { ILogger } from './logging'; -import { IMdParser, MarkdownItEngine, MdParsingProvider } from './markdownEngine'; +import { MarkdownItEngine, MdParsingProvider } from './markdownEngine'; import { MarkdownContributionProvider } from './markdownExtensions'; import { MdDocumentRenderer } from './preview/documentRenderer'; import { MarkdownPreviewManager } from './preview/previewManager'; @@ -45,7 +43,7 @@ export function activateShared( const previewManager = new MarkdownPreviewManager(contentProvider, workspace, logger, contributions, tocProvider); context.subscriptions.push(previewManager); - context.subscriptions.push(registerMarkdownLanguageFeatures(client, parser, workspace, commandManager, tocProvider, logger)); + context.subscriptions.push(registerMarkdownLanguageFeatures(client, commandManager)); context.subscriptions.push(registerMarkdownCommands(commandManager, previewManager, telemetryReporter, cspArbiter, engine, tocProvider)); context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => { @@ -55,23 +53,12 @@ export function activateShared( function registerMarkdownLanguageFeatures( client: BaseLanguageClient, - parser: IMdParser, - workspace: IMdWorkspace, commandManager: CommandManager, - tocProvider: MdTableOfContentsProvider, - logger: ILogger, ): vscode.Disposable { const selector: vscode.DocumentSelector = { language: 'markdown', scheme: '*' }; - - const linkProvider = new MdLinkProvider(parser, workspace, logger); - const referencesProvider = new MdReferencesProvider(parser, workspace, tocProvider, logger); - return vscode.Disposable.from( - linkProvider, - referencesProvider, - // Language features - registerDiagnosticSupport(selector, workspace, linkProvider, commandManager, referencesProvider, tocProvider, logger), + registerDiagnosticSupport(selector, commandManager), registerDropIntoEditorSupport(selector), registerFindFileReferenceSupport(commandManager, client), registerPasteSupport(selector), diff --git a/extensions/markdown-language-features/src/extension.ts b/extensions/markdown-language-features/src/extension.ts index ff591b3bd2f..9f68ef2c1f2 100644 --- a/extensions/markdown-language-features/src/extension.ts +++ b/extensions/markdown-language-features/src/extension.ts @@ -26,6 +26,9 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(workspace); const client = await startServer(context, workspace, engine); + context.subscriptions.push({ + dispose: () => client.stop() + }); activateShared(context, client, workspace, engine, logger, contributions); } diff --git a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts index f403b8ec7d6..6ae36b84aaf 100644 --- a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts +++ b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts @@ -3,609 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as picomatch from 'picomatch'; import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import { CommandManager } from '../commandManager'; -import { ILogger } from '../logging'; -import { MdTableOfContentsProvider } from '../tableOfContents'; -import { ITextDocument } from '../types/textDocument'; -import { Delayer } from '../util/async'; -import { noopToken } from '../util/cancellation'; -import { Disposable } from '../util/dispose'; -import { isMarkdownFile, looksLikeMarkdownPath } from '../util/file'; -import { Limiter } from '../util/limiter'; -import { ResourceMap } from '../util/resourceMap'; -import { MdTableOfContentsWatcher } from '../util/tableOfContentsWatcher'; -import { IMdWorkspace } from '../workspace'; -import { InternalHref, LinkDefinitionSet, MdLink, MdLinkProvider, MdLinkSource } from './documentLinks'; -import { MdReferencesProvider, tryResolveLinkPath } from './references'; const localize = nls.loadMessageBundle(); -export interface DiagnosticConfiguration { - /** - * Fired when the configuration changes. - */ - readonly onDidChange: vscode.Event; - - getOptions(resource: vscode.Uri): DiagnosticOptions; +// Copied from markdown language service +export enum DiagnosticCode { + link_noSuchReferences = 'link.no-such-reference', + link_noSuchHeaderInOwnFile = 'link.no-such-header-in-own-file', + link_noSuchFile = 'link.no-such-file', + link_noSuchHeaderInFile = 'link.no-such-header-in-file', } -export enum DiagnosticLevel { - ignore = 'ignore', - warning = 'warning', - error = 'error', -} - -export interface DiagnosticOptions { - readonly enabled: boolean; - readonly validateReferences: DiagnosticLevel | undefined; - readonly validateFragmentLinks: DiagnosticLevel | undefined; - readonly validateFileLinks: DiagnosticLevel | undefined; - readonly validateMarkdownFileLinkFragments: DiagnosticLevel | undefined; - readonly ignoreLinks: readonly string[]; -} - -function toSeverity(level: DiagnosticLevel | undefined): vscode.DiagnosticSeverity | undefined { - switch (level) { - case DiagnosticLevel.error: return vscode.DiagnosticSeverity.Error; - case DiagnosticLevel.warning: return vscode.DiagnosticSeverity.Warning; - case DiagnosticLevel.ignore: return undefined; - case undefined: return undefined; - } -} - -class VSCodeDiagnosticConfiguration extends Disposable implements DiagnosticConfiguration { - - private readonly _onDidChange = this._register(new vscode.EventEmitter()); - public readonly onDidChange = this._onDidChange.event; - - constructor() { - super(); - - this._register(vscode.workspace.onDidChangeConfiguration(e => { - if ( - e.affectsConfiguration('markdown.experimental.validate.enabled') - || e.affectsConfiguration('markdown.experimental.validate.referenceLinks.enabled') - || e.affectsConfiguration('markdown.experimental.validate.fragmentLinks.enabled') - || e.affectsConfiguration('markdown.experimental.validate.fileLinks.enabled') - || e.affectsConfiguration('markdown.experimental.validate.fileLinks.markdownFragmentLinks') - || e.affectsConfiguration('markdown.experimental.validate.ignoreLinks') - ) { - this._onDidChange.fire(); - } - })); - } - - public getOptions(resource: vscode.Uri): DiagnosticOptions { - const config = vscode.workspace.getConfiguration('markdown', resource); - const validateFragmentLinks = config.get('experimental.validate.fragmentLinks.enabled'); - return { - enabled: config.get('experimental.validate.enabled', false), - validateReferences: config.get('experimental.validate.referenceLinks.enabled'), - validateFragmentLinks, - validateFileLinks: config.get('experimental.validate.fileLinks.enabled'), - validateMarkdownFileLinkFragments: config.get('markdown.experimental.validate.fileLinks.markdownFragmentLinks', validateFragmentLinks), - ignoreLinks: config.get('experimental.validate.ignoreLinks', []), - }; - } -} - -class InflightDiagnosticRequests { - - private readonly inFlightRequests = new ResourceMap<{ readonly cts: vscode.CancellationTokenSource }>(); - - public async trigger(resource: vscode.Uri, compute: (token: vscode.CancellationToken) => Promise): Promise { - this.cancel(resource); - - const cts = new vscode.CancellationTokenSource(); - const entry = { cts }; - this.inFlightRequests.set(resource, entry); - - try { - return await compute(cts.token); - } finally { - if (this.inFlightRequests.get(resource) === entry) { - this.inFlightRequests.delete(resource); - } - cts.dispose(); - } - } - - public cancel(resource: vscode.Uri) { - const existing = this.inFlightRequests.get(resource); - if (existing) { - existing.cts.cancel(); - this.inFlightRequests.delete(resource); - } - } - - public dispose() { - this.clear(); - } - - public clear() { - for (const { cts } of this.inFlightRequests.values()) { - cts.dispose(); - } - this.inFlightRequests.clear(); - } -} - -class LinkWatcher extends Disposable { - - private readonly _onDidChangeLinkedToFile = this._register(new vscode.EventEmitter>); - /** - * Event fired with a list of document uri when one of the links in the document changes - */ - public readonly onDidChangeLinkedToFile = this._onDidChangeLinkedToFile.event; - - private readonly _watchers = new ResourceMap<{ - /** - * Watcher for this link path - */ - readonly watcher: vscode.Disposable; - - /** - * List of documents that reference the link - */ - readonly documents: ResourceMap; - }>(); - - override dispose() { - super.dispose(); - - for (const entry of this._watchers.values()) { - entry.watcher.dispose(); - } - this._watchers.clear(); - } - - /** - * Set the known links in a markdown document, adding and removing file watchers as needed - */ - updateLinksForDocument(document: vscode.Uri, links: readonly MdLink[]) { - const linkedToResource = new Set( - links - .filter(link => link.href.kind === 'internal') - .map(link => (link.href as InternalHref).path)); - - // First decrement watcher counter for previous document state - for (const entry of this._watchers.values()) { - entry.documents.delete(document); - } - - // Then create/update watchers for new document state - for (const path of linkedToResource) { - let entry = this._watchers.get(path); - if (!entry) { - entry = { - watcher: this.startWatching(path), - documents: new ResourceMap(), - }; - this._watchers.set(path, entry); - } - - entry.documents.set(document, document); - } - - // Finally clean up watchers for links that are no longer are referenced anywhere - for (const [key, value] of this._watchers) { - if (value.documents.size === 0) { - value.watcher.dispose(); - this._watchers.delete(key); - } - } - } - - deleteDocument(resource: vscode.Uri) { - this.updateLinksForDocument(resource, []); - } - - private startWatching(path: vscode.Uri): vscode.Disposable { - const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(path, '*'), false, true, false); - const handler = (resource: vscode.Uri) => this.onLinkedResourceChanged(resource); - return vscode.Disposable.from( - watcher, - watcher.onDidDelete(handler), - watcher.onDidCreate(handler), - ); - } - - private onLinkedResourceChanged(resource: vscode.Uri) { - const entry = this._watchers.get(resource); - if (entry) { - this._onDidChangeLinkedToFile.fire(entry.documents.values()); - } - } -} - -class LinkDoesNotExistDiagnostic extends vscode.Diagnostic { - - public readonly link: string; - - constructor(range: vscode.Range, message: string, severity: vscode.DiagnosticSeverity, link: string) { - super(range, message, severity); - this.link = link; - } -} - -export abstract class DiagnosticReporter extends Disposable { - private readonly pending = new Set>(); - - public clear(): void { - this.pending.clear(); - } - - public abstract set(uri: vscode.Uri, diagnostics: readonly vscode.Diagnostic[]): void; - - public abstract delete(uri: vscode.Uri): void; - - public abstract isOpen(uri: vscode.Uri): boolean; - - public abstract getOpenDocuments(): ITextDocument[]; - - public addWorkItem(promise: Promise): Promise { - this.pending.add(promise); - promise.finally(() => this.pending.delete(promise)); - return promise; - } - - public async waitPendingWork(): Promise { - await Promise.all([...this.pending.values()]); - } -} - -export class DiagnosticCollectionReporter extends DiagnosticReporter { - - private readonly collection: vscode.DiagnosticCollection; - - constructor() { - super(); - this.collection = this._register(vscode.languages.createDiagnosticCollection('markdown')); - } - - public override clear(): void { - super.clear(); - this.collection.clear(); - } - - public set(uri: vscode.Uri, diagnostics: readonly vscode.Diagnostic[]): void { - this.collection.set(uri, this.isOpen(uri) ? diagnostics : []); - } - - public isOpen(uri: vscode.Uri): boolean { - const tabs = this.getTabResources(); - return tabs.has(uri); - } - - public delete(uri: vscode.Uri): void { - this.collection.delete(uri); - } - - public getOpenDocuments(): ITextDocument[] { - const tabs = this.getTabResources(); - return vscode.workspace.textDocuments.filter(doc => tabs.has(doc.uri)); - } - - private getTabResources(): ResourceMap { - const openedTabDocs = new ResourceMap(); - for (const group of vscode.window.tabGroups.all) { - for (const tab of group.tabs) { - if (tab.input instanceof vscode.TabInputText) { - openedTabDocs.set(tab.input.uri); - } - } - } - return openedTabDocs; - } -} - -export class DiagnosticManager extends Disposable { - - private readonly diagnosticDelayer: Delayer; - private readonly pendingDiagnostics = new Set(); - private readonly inFlightDiagnostics = this._register(new InflightDiagnosticRequests()); - - private readonly linkWatcher = this._register(new LinkWatcher()); - private readonly tableOfContentsWatcher: MdTableOfContentsWatcher; - - public readonly ready: Promise; - - constructor( - private readonly workspace: IMdWorkspace, - private readonly computer: DiagnosticComputer, - private readonly configuration: DiagnosticConfiguration, - private readonly reporter: DiagnosticReporter, - private readonly referencesProvider: MdReferencesProvider, - tocProvider: MdTableOfContentsProvider, - private readonly logger: ILogger, - delay = 300, - ) { - super(); - - this.diagnosticDelayer = this._register(new Delayer(delay)); - - this._register(this.configuration.onDidChange(() => { - this.rebuild(); - })); - - this._register(workspace.onDidCreateMarkdownDocument(doc => { - this.triggerDiagnostics(doc.uri); - // Links in other files may have become valid - this.triggerForReferencingFiles(doc.uri); - })); - - this._register(workspace.onDidChangeMarkdownDocument(doc => { - this.triggerDiagnostics(doc.uri); - })); - - this._register(workspace.onDidDeleteMarkdownDocument(uri => { - this.triggerForReferencingFiles(uri); - })); - - this._register(vscode.workspace.onDidCloseTextDocument(({ uri }) => { - this.pendingDiagnostics.delete(uri); - this.inFlightDiagnostics.cancel(uri); - this.linkWatcher.deleteDocument(uri); - this.reporter.delete(uri); - })); - - this._register(this.linkWatcher.onDidChangeLinkedToFile(changedDocuments => { - for (const resource of changedDocuments) { - const doc = vscode.workspace.textDocuments.find(doc => doc.uri.toString() === resource.toString()); - if (doc && isMarkdownFile(doc)) { - this.triggerDiagnostics(doc.uri); - } - } - })); - - this.tableOfContentsWatcher = this._register(new MdTableOfContentsWatcher(workspace, tocProvider, delay / 2)); - this._register(this.tableOfContentsWatcher.onTocChanged(e => { - return this.triggerForReferencingFiles(e.uri); - })); - - this.ready = this.rebuild(); - } - - private triggerForReferencingFiles(uri: vscode.Uri): Promise { - return this.reporter.addWorkItem( - (async () => { - const triggered = new ResourceMap>(); - for (const ref of await this.referencesProvider.getReferencesToFileInDocs(uri, this.reporter.getOpenDocuments(), noopToken)) { - const file = ref.location.uri; - if (!triggered.has(file)) { - triggered.set(file, this.triggerDiagnostics(file)); - } - } - await Promise.all(triggered.values()); - })()); - } - - public override dispose() { - super.dispose(); - this.pendingDiagnostics.clear(); - } - - private async recomputeDiagnosticState(doc: ITextDocument, token: vscode.CancellationToken): Promise<{ diagnostics: readonly vscode.Diagnostic[]; links: readonly MdLink[]; config: DiagnosticOptions }> { - this.logger.verbose('DiagnosticManager', `recomputeDiagnosticState - ${doc.uri}`); - - const config = this.configuration.getOptions(doc.uri); - if (!config.enabled) { - return { diagnostics: [], links: [], config }; - } - return { ...await this.computer.getDiagnostics(doc, config, token), config }; - } - - private async recomputePendingDiagnostics(): Promise { - const pending = [...this.pendingDiagnostics]; - this.pendingDiagnostics.clear(); - - await Promise.all(pending.map(async resource => { - const doc = await this.workspace.getOrLoadMarkdownDocument(resource); - if (doc) { - await this.inFlightDiagnostics.trigger(doc.uri, async (token) => { - if (this.reporter.isOpen(doc.uri)) { - const state = await this.recomputeDiagnosticState(doc, token); - this.linkWatcher.updateLinksForDocument(doc.uri, state.config.enabled && state.config.validateFileLinks ? state.links : []); - this.reporter.set(doc.uri, state.diagnostics); - } else { - this.linkWatcher.deleteDocument(doc.uri); - this.reporter.delete(doc.uri); - } - }); - } - })); - } - - private rebuild(): Promise { - this.reporter.clear(); - this.pendingDiagnostics.clear(); - this.inFlightDiagnostics.clear(); - - return this.reporter.addWorkItem( - Promise.all(Array.from(this.reporter.getOpenDocuments(), doc => this.triggerDiagnostics(doc.uri))) - ); - } - - private async triggerDiagnostics(uri: vscode.Uri): Promise { - this.inFlightDiagnostics.cancel(uri); - - this.pendingDiagnostics.add(uri); - return this.reporter.addWorkItem( - this.diagnosticDelayer.trigger(() => this.recomputePendingDiagnostics()) - ); - } -} - -/** - * Map of file paths to markdown links to that file. - */ -class FileLinkMap { - - private readonly _filesToLinksMap = new ResourceMap<{ - readonly outgoingLinks: Array<{ - readonly source: MdLinkSource; - readonly fragment: string; - }>; - }>(); - - constructor(links: Iterable) { - for (const link of links) { - if (link.href.kind !== 'internal') { - continue; - } - - const existingFileEntry = this._filesToLinksMap.get(link.href.path); - const linkData = { source: link.source, fragment: link.href.fragment }; - if (existingFileEntry) { - existingFileEntry.outgoingLinks.push(linkData); - } else { - this._filesToLinksMap.set(link.href.path, { outgoingLinks: [linkData] }); - } - } - } - - public get size(): number { - return this._filesToLinksMap.size; - } - - public entries() { - return this._filesToLinksMap.entries(); - } -} - -export class DiagnosticComputer { - - constructor( - private readonly workspace: IMdWorkspace, - private readonly linkProvider: MdLinkProvider, - private readonly tocProvider: MdTableOfContentsProvider, - ) { } - - public async getDiagnostics(doc: ITextDocument, options: DiagnosticOptions, token: vscode.CancellationToken): Promise<{ readonly diagnostics: vscode.Diagnostic[]; readonly links: readonly MdLink[] }> { - const { links, definitions } = await this.linkProvider.getLinks(doc); - if (token.isCancellationRequested || !options.enabled) { - return { links, diagnostics: [] }; - } - - return { - links, - diagnostics: (await Promise.all([ - this.validateFileLinks(options, links, token), - Array.from(this.validateReferenceLinks(options, links, definitions)), - this.validateFragmentLinks(doc, options, links, token), - ])).flat() - }; - } - - private async validateFragmentLinks(doc: ITextDocument, options: DiagnosticOptions, links: readonly MdLink[], token: vscode.CancellationToken): Promise { - const severity = toSeverity(options.validateFragmentLinks); - if (typeof severity === 'undefined') { - return []; - } - - const toc = await this.tocProvider.getForDocument(doc); - if (token.isCancellationRequested) { - return []; - } - - const diagnostics: vscode.Diagnostic[] = []; - for (const link of links) { - if (link.href.kind === 'internal' - && link.source.hrefText.startsWith('#') - && link.href.path.toString() === doc.uri.toString() - && link.href.fragment - && !toc.lookup(link.href.fragment) - ) { - if (!this.isIgnoredLink(options, link.source.hrefText)) { - diagnostics.push(new LinkDoesNotExistDiagnostic( - link.source.hrefRange, - localize('invalidHeaderLink', 'No header found: \'{0}\'', link.href.fragment), - severity, - link.source.hrefText)); - } - } - } - - return diagnostics; - } - - private *validateReferenceLinks(options: DiagnosticOptions, links: readonly MdLink[], definitions: LinkDefinitionSet): Iterable { - const severity = toSeverity(options.validateReferences); - if (typeof severity === 'undefined') { - return []; - } - - for (const link of links) { - if (link.href.kind === 'reference' && !definitions.lookup(link.href.ref)) { - yield new vscode.Diagnostic( - link.source.hrefRange, - localize('invalidReferenceLink', 'No link definition found: \'{0}\'', link.href.ref), - severity); - } - } - } - - private async validateFileLinks(options: DiagnosticOptions, links: readonly MdLink[], token: vscode.CancellationToken): Promise { - const pathErrorSeverity = toSeverity(options.validateFileLinks); - if (typeof pathErrorSeverity === 'undefined') { - return []; - } - const fragmentErrorSeverity = toSeverity(typeof options.validateMarkdownFileLinkFragments === 'undefined' ? options.validateFragmentLinks : options.validateMarkdownFileLinkFragments); - - // We've already validated our own fragment links in `validateOwnHeaderLinks` - const linkSet = new FileLinkMap(links.filter(link => !link.source.hrefText.startsWith('#'))); - if (linkSet.size === 0) { - return []; - } - - const limiter = new Limiter(10); - - const diagnostics: vscode.Diagnostic[] = []; - await Promise.all( - Array.from(linkSet.entries()).map(([path, { outgoingLinks: links }]) => { - return limiter.queue(async () => { - if (token.isCancellationRequested) { - return; - } - - const resolvedHrefPath = await tryResolveLinkPath(path, this.workspace); - if (!resolvedHrefPath) { - const msg = localize('invalidPathLink', 'File does not exist at path: {0}', path.fsPath); - for (const link of links) { - if (!this.isIgnoredLink(options, link.source.pathText)) { - diagnostics.push(new LinkDoesNotExistDiagnostic(link.source.hrefRange, msg, pathErrorSeverity, link.source.pathText)); - } - } - } else if (typeof fragmentErrorSeverity !== 'undefined' && this.isMarkdownPath(resolvedHrefPath)) { - // Validate each of the links to headers in the file - const fragmentLinks = links.filter(x => x.fragment); - if (fragmentLinks.length) { - const toc = await this.tocProvider.get(resolvedHrefPath); - for (const link of fragmentLinks) { - if (!toc.lookup(link.fragment) && !this.isIgnoredLink(options, link.source.pathText) && !this.isIgnoredLink(options, link.source.hrefText)) { - const msg = localize('invalidLinkToHeaderInOtherFile', 'Header does not exist in file: {0}', link.fragment); - const range = link.source.fragmentRange?.with({ start: link.source.fragmentRange.start.translate(0, -1) }) ?? link.source.hrefRange; - diagnostics.push(new LinkDoesNotExistDiagnostic(range, msg, fragmentErrorSeverity, link.source.hrefText)); - } - } - } - } - }); - })); - return diagnostics; - } - - private isMarkdownPath(resolvedHrefPath: vscode.Uri) { - return this.workspace.hasMarkdownDocument(resolvedHrefPath) || looksLikeMarkdownPath(resolvedHrefPath); - } - - private isIgnoredLink(options: DiagnosticOptions, link: string): boolean { - return options.ignoreLinks.some(glob => picomatch.isMatch(link, glob)); - } -} class AddToIgnoreLinksQuickFixProvider implements vscode.CodeActionProvider { @@ -636,17 +47,26 @@ class AddToIgnoreLinksQuickFixProvider implements vscode.CodeActionProvider { const fixes: vscode.CodeAction[] = []; for (const diagnostic of context.diagnostics) { - if (diagnostic instanceof LinkDoesNotExistDiagnostic) { - const fix = new vscode.CodeAction( - localize('ignoreLinksQuickFix.title', "Exclude '{0}' from link validation.", diagnostic.link), - vscode.CodeActionKind.QuickFix); + switch (diagnostic.code) { + case DiagnosticCode.link_noSuchReferences: + case DiagnosticCode.link_noSuchHeaderInOwnFile: + case DiagnosticCode.link_noSuchFile: + case DiagnosticCode.link_noSuchHeaderInFile: { + const hrefText = (diagnostic as any).data?.hrefText; + if (hrefText) { + const fix = new vscode.CodeAction( + localize('ignoreLinksQuickFix.title', "Exclude '{0}' from link validation.", hrefText), + vscode.CodeActionKind.QuickFix); - fix.command = { - command: AddToIgnoreLinksQuickFixProvider._addToIgnoreLinksCommandId, - title: '', - arguments: [document.uri, diagnostic.link] - }; - fixes.push(fix); + fix.command = { + command: AddToIgnoreLinksQuickFixProvider._addToIgnoreLinksCommandId, + title: '', + arguments: [document.uri, hrefText], + }; + fixes.push(fix); + } + break; + } } } @@ -654,26 +74,10 @@ class AddToIgnoreLinksQuickFixProvider implements vscode.CodeActionProvider { } } + export function registerDiagnosticSupport( selector: vscode.DocumentSelector, - workspace: IMdWorkspace, - linkProvider: MdLinkProvider, commandManager: CommandManager, - referenceProvider: MdReferencesProvider, - tocProvider: MdTableOfContentsProvider, - logger: ILogger, ): vscode.Disposable { - const configuration = new VSCodeDiagnosticConfiguration(); - const manager = new DiagnosticManager( - workspace, - new DiagnosticComputer(workspace, linkProvider, tocProvider), - configuration, - new DiagnosticCollectionReporter(), - referenceProvider, - tocProvider, - logger); - return vscode.Disposable.from( - configuration, - manager, - AddToIgnoreLinksQuickFixProvider.register(selector, commandManager)); + return AddToIgnoreLinksQuickFixProvider.register(selector, commandManager); } diff --git a/extensions/markdown-language-features/src/languageFeatures/documentLinks.ts b/extensions/markdown-language-features/src/languageFeatures/documentLinks.ts deleted file mode 100644 index 449be42595b..00000000000 --- a/extensions/markdown-language-features/src/languageFeatures/documentLinks.ts +++ /dev/null @@ -1,540 +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 vscode from 'vscode'; -import * as uri from 'vscode-uri'; -import { ILogger } from '../logging'; -import { IMdParser } from '../markdownEngine'; -import { getLine, ITextDocument } from '../types/textDocument'; -import { noopToken } from '../util/cancellation'; -import { Disposable } from '../util/dispose'; -import { Schemes } from '../util/schemes'; -import { MdDocumentInfoCache } from '../util/workspaceCache'; -import { IMdWorkspace } from '../workspace'; - -export interface ExternalHref { - readonly kind: 'external'; - readonly uri: vscode.Uri; -} - -export interface InternalHref { - readonly kind: 'internal'; - readonly path: vscode.Uri; - readonly fragment: string; -} - -export interface ReferenceHref { - readonly kind: 'reference'; - readonly ref: string; -} - -export type LinkHref = ExternalHref | InternalHref | ReferenceHref; - - -function resolveLink( - document: ITextDocument, - link: string, -): ExternalHref | InternalHref | undefined { - const cleanLink = stripAngleBrackets(link); - - if (/^[a-z\-][a-z\-]+:/i.test(cleanLink)) { - // Looks like a uri - return { kind: 'external', uri: vscode.Uri.parse(cleanLink) }; - } - - // Assume it must be an relative or absolute file path - // Use a fake scheme to avoid parse warnings - const tempUri = vscode.Uri.parse(`vscode-resource:${link}`); - - let resourceUri: vscode.Uri | undefined; - if (!tempUri.path) { - resourceUri = document.uri; - } else if (tempUri.path[0] === '/') { - const root = getWorkspaceFolder(document); - if (root) { - resourceUri = vscode.Uri.joinPath(root, tempUri.path); - } - } else { - if (document.uri.scheme === Schemes.untitled) { - const root = getWorkspaceFolder(document); - if (root) { - resourceUri = vscode.Uri.joinPath(root, tempUri.path); - } - } else { - const base = uri.Utils.dirname(document.uri); - resourceUri = vscode.Uri.joinPath(base, tempUri.path); - } - } - - if (!resourceUri) { - return undefined; - } - - // If we are in a notebook cell, resolve relative to notebook instead - if (resourceUri.scheme === Schemes.notebookCell) { - const notebook = vscode.workspace.notebookDocuments - .find(notebook => notebook.getCells().some(cell => cell.document === document)); - - if (notebook) { - resourceUri = resourceUri.with({ scheme: notebook.uri.scheme }); - } - } - - return { - kind: 'internal', - path: resourceUri.with({ fragment: '' }), - fragment: tempUri.fragment, - }; -} - -function getWorkspaceFolder(document: ITextDocument) { - return vscode.workspace.getWorkspaceFolder(document.uri)?.uri - || vscode.workspace.workspaceFolders?.[0]?.uri; -} - -export interface MdLinkSource { - /** - * The full range of the link. - */ - readonly range: vscode.Range; - - /** - * The file where the link is defined. - */ - readonly resource: vscode.Uri; - - /** - * The original text of the link destination in code. - */ - readonly hrefText: string; - - /** - * The original text of just the link's path in code. - */ - readonly pathText: string; - - /** - * The range of the path. - */ - readonly hrefRange: vscode.Range; - - /** - * The range of the fragment within the path. - */ - readonly fragmentRange: vscode.Range | undefined; -} - -export interface MdInlineLink { - readonly kind: 'link'; - readonly source: MdLinkSource; - readonly href: LinkHref; -} - -export interface MdLinkDefinition { - readonly kind: 'definition'; - readonly source: MdLinkSource; - readonly ref: { - readonly range: vscode.Range; - readonly text: string; - }; - readonly href: ExternalHref | InternalHref; -} - -export type MdLink = MdInlineLink | MdLinkDefinition; - -function extractDocumentLink( - document: ITextDocument, - pre: string, - rawLink: string, - matchIndex: number, - fullMatch: string, -): MdLink | undefined { - const isAngleBracketLink = rawLink.startsWith('<'); - const link = stripAngleBrackets(rawLink); - - let linkTarget: ExternalHref | InternalHref | undefined; - try { - linkTarget = resolveLink(document, link); - } catch { - return undefined; - } - if (!linkTarget) { - return undefined; - } - - const linkStart = document.positionAt(matchIndex); - const linkEnd = linkStart.translate(0, fullMatch.length); - const hrefStart = linkStart.translate(0, pre.length + (isAngleBracketLink ? 1 : 0)); - const hrefEnd = hrefStart.translate(0, link.length); - return { - kind: 'link', - href: linkTarget, - source: { - hrefText: link, - resource: document.uri, - range: new vscode.Range(linkStart, linkEnd), - hrefRange: new vscode.Range(hrefStart, hrefEnd), - ...getLinkSourceFragmentInfo(document, link, hrefStart, hrefEnd), - } - }; -} - -function getFragmentRange(text: string, start: vscode.Position, end: vscode.Position): vscode.Range | undefined { - const index = text.indexOf('#'); - if (index < 0) { - return undefined; - } - return new vscode.Range(start.translate({ characterDelta: index + 1 }), end); -} - -function getLinkSourceFragmentInfo(document: ITextDocument, link: string, linkStart: vscode.Position, linkEnd: vscode.Position): { fragmentRange: vscode.Range | undefined; pathText: string } { - const fragmentRange = getFragmentRange(link, linkStart, linkEnd); - return { - pathText: document.getText(new vscode.Range(linkStart, fragmentRange ? fragmentRange.start.translate(0, -1) : linkEnd)), - fragmentRange, - }; -} - -const angleBracketLinkRe = /^<(.*)>$/; - -/** - * Used to strip brackets from the markdown link - * - * will be transformed to http://example.com -*/ -function stripAngleBrackets(link: string) { - return link.replace(angleBracketLinkRe, '$1'); -} - -const r = String.raw; - -/** - * Matches `[text](link)` or `[text]()` - */ -const linkPattern = new RegExp( - // text - r`(\[` + // open prefix match --> - /**/r`(?:` + - /*****/r`[^\[\]\\]|` + // Non-bracket chars, or... - /*****/r`\\.|` + // Escaped char, or... - /*****/r`\[[^\[\]]*\]` + // Matched bracket pair - /**/r`)*` + - r`\]` + - - // Destination - r`\(\s*)` + // <-- close prefix match - /**/r`(` + - /*****/r`[^\s\(\)\<](?:[^\s\(\)]|\([^\s\(\)]*?\))*|` + // Link without whitespace, or... - /*****/r`<[^<>]+>` + // In angle brackets - /**/r`)` + - - // Title - /**/r`\s*(?:"[^"]*"|'[^']*'|\([^\(\)]*\))?\s*` + - r`\)`, - 'g'); - -/** -* Matches `[text][ref]` or `[shorthand]` -*/ -const referenceLinkPattern = /(^|[^\]\\])(?:(?:(\[((?:\\\]|[^\]])+)\]\[\s*?)([^\s\]]*?)\]|\[\s*?([^\s\\\]]*?)\])(?![\:\(]))/gm; - -/** - * Matches `` - */ -const autoLinkPattern = /\<(\w+:[^\>\s]+)\>/g; - -/** - * Matches `[text]: link` - */ -const definitionPattern = /^([\t ]*\[(?!\^)((?:\\\]|[^\]])+)\]:\s*)([^<]\S*|<[^>]+>)/gm; - -const inlineCodePattern = /(?:^|[^`])(`+)(?:.+?|.*?(?:(?:\r?\n).+?)*?)(?:\r?\n)?\1(?:$|[^`])/gm; - -class NoLinkRanges { - public static async compute(tokenizer: IMdParser, document: ITextDocument): Promise { - const tokens = await tokenizer.tokenize(document); - const multiline = tokens.filter(t => (t.type === 'code_block' || t.type === 'fence' || t.type === 'html_block') && !!t.map).map(t => t.map) as [number, number][]; - - const inlineRanges = new Map(); - const text = document.getText(); - for (const match of text.matchAll(inlineCodePattern)) { - const startOffset = match.index ?? 0; - const startPosition = document.positionAt(startOffset); - - const range = new vscode.Range(startPosition, document.positionAt(startOffset + match[0].length)); - for (let line = range.start.line; line <= range.end.line; ++line) { - let entry = inlineRanges.get(line); - if (!entry) { - entry = []; - inlineRanges.set(line, entry); - } - entry.push(range); - } - } - - return new NoLinkRanges(multiline, inlineRanges); - } - - private constructor( - /** - * code blocks and fences each represented by [line_start,line_end). - */ - public readonly multiline: ReadonlyArray<[number, number]>, - - /** - * Inline code spans where links should not be detected - */ - public readonly inline: Map - ) { } - - contains(position: vscode.Position): boolean { - return this.multiline.some(interval => position.line >= interval[0] && position.line < interval[1]) || - !!this.inline.get(position.line)?.some(inlineRange => inlineRange.contains(position)); - } - - concatInline(inlineRanges: Iterable): NoLinkRanges { - const newInline = new Map(this.inline); - for (const range of inlineRanges) { - for (let line = range.start.line; line <= range.end.line; ++line) { - let entry = newInline.get(line); - if (!entry) { - entry = []; - newInline.set(line, entry); - } - entry.push(range); - } - } - return new NoLinkRanges(this.multiline, newInline); - } -} - -/** - * Stateless object that extracts link information from markdown files. - */ -export class MdLinkComputer { - - constructor( - private readonly tokenizer: IMdParser, - ) { } - - public async getAllLinks(document: ITextDocument, token: vscode.CancellationToken): Promise { - const noLinkRanges = await NoLinkRanges.compute(this.tokenizer, document); - if (token.isCancellationRequested) { - return []; - } - - const inlineLinks = Array.from(this.getInlineLinks(document, noLinkRanges)); - return Array.from([ - ...inlineLinks, - ...this.getReferenceLinks(document, noLinkRanges.concatInline(inlineLinks.map(x => x.source.range))), - ...this.getLinkDefinitions(document, noLinkRanges), - ...this.getAutoLinks(document, noLinkRanges), - ]); - } - - private *getInlineLinks(document: ITextDocument, noLinkRanges: NoLinkRanges): Iterable { - const text = document.getText(); - for (const match of text.matchAll(linkPattern)) { - const matchLinkData = extractDocumentLink(document, match[1], match[2], match.index ?? 0, match[0]); - if (matchLinkData && !noLinkRanges.contains(matchLinkData.source.hrefRange.start)) { - yield matchLinkData; - - // Also check link destination for links - for (const innerMatch of match[1].matchAll(linkPattern)) { - const innerData = extractDocumentLink(document, innerMatch[1], innerMatch[2], (match.index ?? 0) + (innerMatch.index ?? 0), innerMatch[0]); - if (innerData) { - yield innerData; - } - } - } - } - } - - private *getAutoLinks(document: ITextDocument, noLinkRanges: NoLinkRanges): Iterable { - const text = document.getText(); - for (const match of text.matchAll(autoLinkPattern)) { - const linkOffset = (match.index ?? 0); - const linkStart = document.positionAt(linkOffset); - if (noLinkRanges.contains(linkStart)) { - continue; - } - - const link = match[1]; - const linkTarget = resolveLink(document, link); - if (!linkTarget) { - continue; - } - - const linkEnd = linkStart.translate(0, match[0].length); - const hrefStart = linkStart.translate(0, 1); - const hrefEnd = hrefStart.translate(0, link.length); - yield { - kind: 'link', - href: linkTarget, - source: { - hrefText: link, - resource: document.uri, - hrefRange: new vscode.Range(hrefStart, hrefEnd), - range: new vscode.Range(linkStart, linkEnd), - ...getLinkSourceFragmentInfo(document, link, hrefStart, hrefEnd), - } - }; - } - } - - private *getReferenceLinks(document: ITextDocument, noLinkRanges: NoLinkRanges): Iterable { - const text = document.getText(); - for (const match of text.matchAll(referenceLinkPattern)) { - const linkStartOffset = (match.index ?? 0) + match[1].length; - const linkStart = document.positionAt(linkStartOffset); - if (noLinkRanges.contains(linkStart)) { - continue; - } - - let hrefStart: vscode.Position; - let hrefEnd: vscode.Position; - let reference = match[4]; - if (reference === '') { // [ref][], - reference = match[3]; - const offset = linkStartOffset + 1; - hrefStart = document.positionAt(offset); - hrefEnd = document.positionAt(offset + reference.length); - } else if (reference) { // [text][ref] - const pre = match[2]; - const offset = linkStartOffset + pre.length; - hrefStart = document.positionAt(offset); - hrefEnd = document.positionAt(offset + reference.length); - } else if (match[5]) { // [ref] - reference = match[5]; - const offset = linkStartOffset + 1; - hrefStart = document.positionAt(offset); - const line = getLine(document, hrefStart.line); - // See if link looks like a checkbox - const checkboxMatch = line.match(/^\s*[\-\*]\s*\[x\]/i); - if (checkboxMatch && hrefStart.character <= checkboxMatch[0].length) { - continue; - } - hrefEnd = document.positionAt(offset + reference.length); - } else { - continue; - } - - const linkEnd = linkStart.translate(0, match[0].length - match[1].length); - yield { - kind: 'link', - source: { - hrefText: reference, - pathText: reference, - resource: document.uri, - range: new vscode.Range(linkStart, linkEnd), - hrefRange: new vscode.Range(hrefStart, hrefEnd), - fragmentRange: undefined, - }, - href: { - kind: 'reference', - ref: reference, - } - }; - } - } - - private *getLinkDefinitions(document: ITextDocument, noLinkRanges: NoLinkRanges): Iterable { - const text = document.getText(); - for (const match of text.matchAll(definitionPattern)) { - const offset = (match.index ?? 0); - const linkStart = document.positionAt(offset); - if (noLinkRanges.contains(linkStart)) { - continue; - } - - const pre = match[1]; - const reference = match[2]; - const rawLinkText = match[3].trim(); - const target = resolveLink(document, rawLinkText); - if (!target) { - continue; - } - - const isAngleBracketLink = angleBracketLinkRe.test(rawLinkText); - const linkText = stripAngleBrackets(rawLinkText); - const hrefStart = linkStart.translate(0, pre.length + (isAngleBracketLink ? 1 : 0)); - const hrefEnd = hrefStart.translate(0, linkText.length); - const hrefRange = new vscode.Range(hrefStart, hrefEnd); - - const refStart = linkStart.translate(0, 1); - const refRange = new vscode.Range(refStart, refStart.translate({ characterDelta: reference.length })); - const linkEnd = linkStart.translate(0, match[0].length); - yield { - kind: 'definition', - source: { - hrefText: linkText, - resource: document.uri, - range: new vscode.Range(linkStart, linkEnd), - hrefRange, - ...getLinkSourceFragmentInfo(document, rawLinkText, hrefStart, hrefEnd), - }, - ref: { text: reference, range: refRange }, - href: target, - }; - } - } -} - -interface MdDocumentLinks { - readonly links: readonly MdLink[]; - readonly definitions: LinkDefinitionSet; -} - -/** - * Stateful object which provides links for markdown files the workspace. - */ -export class MdLinkProvider extends Disposable { - - private readonly _linkCache: MdDocumentInfoCache; - - private readonly linkComputer: MdLinkComputer; - - constructor( - tokenizer: IMdParser, - workspace: IMdWorkspace, - logger: ILogger, - ) { - super(); - this.linkComputer = new MdLinkComputer(tokenizer); - this._linkCache = this._register(new MdDocumentInfoCache(workspace, async doc => { - logger.verbose('LinkProvider', `compute - ${doc.uri}`); - - const links = await this.linkComputer.getAllLinks(doc, noopToken); - return { - links, - definitions: new LinkDefinitionSet(links), - }; - })); - } - - public async getLinks(document: ITextDocument): Promise { - return this._linkCache.getForDocument(document); - } -} - -export class LinkDefinitionSet implements Iterable<[string, MdLinkDefinition]> { - private readonly _map = new Map(); - - constructor(links: Iterable) { - for (const link of links) { - if (link.kind === 'definition') { - this._map.set(link.ref.text, link); - } - } - } - - public [Symbol.iterator](): Iterator<[string, MdLinkDefinition]> { - return this._map.entries(); - } - - public lookup(ref: string): MdLinkDefinition | undefined { - return this._map.get(ref); - } -} diff --git a/extensions/markdown-language-features/src/languageFeatures/references.ts b/extensions/markdown-language-features/src/languageFeatures/references.ts deleted file mode 100644 index 5aada05300e..00000000000 --- a/extensions/markdown-language-features/src/languageFeatures/references.ts +++ /dev/null @@ -1,329 +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 vscode from 'vscode'; -import * as uri from 'vscode-uri'; -import { ILogger } from '../logging'; -import { IMdParser } from '../markdownEngine'; -import { MdTableOfContentsProvider, TocEntry } from '../tableOfContents'; -import { ITextDocument } from '../types/textDocument'; -import { noopToken } from '../util/cancellation'; -import { Disposable } from '../util/dispose'; -import { looksLikeMarkdownPath } from '../util/file'; -import { MdWorkspaceInfoCache } from '../util/workspaceCache'; -import { IMdWorkspace } from '../workspace'; -import { InternalHref, MdLink, MdLinkComputer } from './documentLinks'; - - -/** - * A link in a markdown file. - */ -export interface MdLinkReference { - readonly kind: 'link'; - readonly isTriggerLocation: boolean; - readonly isDefinition: boolean; - readonly location: vscode.Location; - - readonly link: MdLink; -} - -/** - * A header in a markdown file. - */ -export interface MdHeaderReference { - readonly kind: 'header'; - - readonly isTriggerLocation: boolean; - readonly isDefinition: boolean; - - /** - * The range of the header. - * - * In `# a b c #` this would be the range of `# a b c #` - */ - readonly location: vscode.Location; - - /** - * The text of the header. - * - * In `# a b c #` this would be `a b c` - */ - readonly headerText: string; - - /** - * The range of the header text itself. - * - * In `# a b c #` this would be the range of `a b c` - */ - readonly headerTextLocation: vscode.Location; -} - -export type MdReference = MdLinkReference | MdHeaderReference; - -/** - * Stateful object that computes references for markdown files. - */ -export class MdReferencesProvider extends Disposable { - - private readonly _linkCache: MdWorkspaceInfoCache; - - public constructor( - private readonly parser: IMdParser, - private readonly workspace: IMdWorkspace, - private readonly tocProvider: MdTableOfContentsProvider, - private readonly logger: ILogger, - ) { - super(); - - const linkComputer = new MdLinkComputer(parser); - this._linkCache = this._register(new MdWorkspaceInfoCache(workspace, doc => linkComputer.getAllLinks(doc, noopToken))); - } - - public async getReferencesAtPosition(document: ITextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { - this.logger.verbose('ReferencesProvider', `getReferencesAtPosition: ${document.uri}`); - - const toc = await this.tocProvider.getForDocument(document); - if (token.isCancellationRequested) { - return []; - } - - const header = toc.entries.find(entry => entry.line === position.line); - if (header) { - return this.getReferencesToHeader(document, header); - } else { - return this.getReferencesToLinkAtPosition(document, position, token); - } - } - - public async getReferencesToFileInWorkspace(resource: vscode.Uri, token: vscode.CancellationToken): Promise { - this.logger.verbose('ReferencesProvider', `getAllReferencesToFileInWorkspace: ${resource}`); - - const allLinksInWorkspace = (await this._linkCache.values()).flat(); - if (token.isCancellationRequested) { - return []; - } - - return Array.from(this.findLinksToFile(resource, allLinksInWorkspace, undefined)); - } - - public async getReferencesToFileInDocs(resource: vscode.Uri, otherDocs: readonly ITextDocument[], token: vscode.CancellationToken): Promise { - this.logger.verbose('ReferencesProvider', `getAllReferencesToFileInFiles: ${resource}`); - - const links = (await this._linkCache.getForDocs(otherDocs)).flat(); - if (token.isCancellationRequested) { - return []; - } - - return Array.from(this.findLinksToFile(resource, links, undefined)); - } - - private async getReferencesToHeader(document: ITextDocument, header: TocEntry): Promise { - const links = (await this._linkCache.values()).flat(); - - const references: MdReference[] = []; - - references.push({ - kind: 'header', - isTriggerLocation: true, - isDefinition: true, - location: header.headerLocation, - headerText: header.text, - headerTextLocation: header.headerTextLocation - }); - - for (const link of links) { - if (link.href.kind === 'internal' - && this.looksLikeLinkToDoc(link.href, document.uri) - && this.parser.slugifier.fromHeading(link.href.fragment).value === header.slug.value - ) { - references.push({ - kind: 'link', - isTriggerLocation: false, - isDefinition: false, - link, - location: new vscode.Location(link.source.resource, link.source.hrefRange), - }); - } - } - - return references; - } - - private async getReferencesToLinkAtPosition(document: ITextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { - const docLinks = (await this._linkCache.getForDocs([document]))[0]; - - for (const link of docLinks) { - if (link.kind === 'definition') { - // We could be in either the ref name or the definition - if (link.ref.range.contains(position)) { - return Array.from(this.getReferencesToLinkReference(docLinks, link.ref.text, { resource: document.uri, range: link.ref.range })); - } else if (link.source.hrefRange.contains(position)) { - return this.getReferencesToLink(link, position, token); - } - } else { - if (link.source.hrefRange.contains(position)) { - return this.getReferencesToLink(link, position, token); - } - } - } - - return []; - } - - private async getReferencesToLink(sourceLink: MdLink, triggerPosition: vscode.Position, token: vscode.CancellationToken): Promise { - const allLinksInWorkspace = (await this._linkCache.values()).flat(); - if (token.isCancellationRequested) { - return []; - } - - if (sourceLink.href.kind === 'reference') { - return Array.from(this.getReferencesToLinkReference(allLinksInWorkspace, sourceLink.href.ref, { resource: sourceLink.source.resource, range: sourceLink.source.hrefRange })); - } - - if (sourceLink.href.kind === 'external') { - const references: MdReference[] = []; - - for (const link of allLinksInWorkspace) { - if (link.href.kind === 'external' && link.href.uri.toString() === sourceLink.href.uri.toString()) { - const isTriggerLocation = sourceLink.source.resource.fsPath === link.source.resource.fsPath && sourceLink.source.hrefRange.isEqual(link.source.hrefRange); - references.push({ - kind: 'link', - isTriggerLocation, - isDefinition: false, - link, - location: new vscode.Location(link.source.resource, link.source.hrefRange), - }); - } - } - return references; - } - - const resolvedResource = await tryResolveLinkPath(sourceLink.href.path, this.workspace); - if (token.isCancellationRequested) { - return []; - } - - const references: MdReference[] = []; - - if (resolvedResource && this.isMarkdownPath(resolvedResource) && sourceLink.href.fragment && sourceLink.source.fragmentRange?.contains(triggerPosition)) { - const toc = await this.tocProvider.get(resolvedResource); - const entry = toc.lookup(sourceLink.href.fragment); - if (entry) { - references.push({ - kind: 'header', - isTriggerLocation: false, - isDefinition: true, - location: entry.headerLocation, - headerText: entry.text, - headerTextLocation: entry.headerTextLocation - }); - } - - for (const link of allLinksInWorkspace) { - if (link.href.kind !== 'internal' || !this.looksLikeLinkToDoc(link.href, resolvedResource)) { - continue; - } - - if (this.parser.slugifier.fromHeading(link.href.fragment).equals(this.parser.slugifier.fromHeading(sourceLink.href.fragment))) { - const isTriggerLocation = sourceLink.source.resource.fsPath === link.source.resource.fsPath && sourceLink.source.hrefRange.isEqual(link.source.hrefRange); - references.push({ - kind: 'link', - isTriggerLocation, - isDefinition: false, - link, - location: new vscode.Location(link.source.resource, link.source.hrefRange), - }); - } - } - } else { // Triggered on a link without a fragment so we only require matching the file and ignore fragments - references.push(...this.findLinksToFile(resolvedResource ?? sourceLink.href.path, allLinksInWorkspace, sourceLink)); - } - - return references; - } - - private isMarkdownPath(resolvedHrefPath: vscode.Uri) { - return this.workspace.hasMarkdownDocument(resolvedHrefPath) || looksLikeMarkdownPath(resolvedHrefPath); - } - - private looksLikeLinkToDoc(href: InternalHref, targetDoc: vscode.Uri) { - return href.path.fsPath === targetDoc.fsPath - || uri.Utils.extname(href.path) === '' && href.path.with({ path: href.path.path + '.md' }).fsPath === targetDoc.fsPath; - } - - private *findLinksToFile(resource: vscode.Uri, links: readonly MdLink[], sourceLink: MdLink | undefined): Iterable { - for (const link of links) { - if (link.href.kind !== 'internal' || !this.looksLikeLinkToDoc(link.href, resource)) { - continue; - } - - // Exclude cases where the file is implicitly referencing itself - if (link.source.hrefText.startsWith('#') && link.source.resource.fsPath === resource.fsPath) { - continue; - } - - const isTriggerLocation = !!sourceLink && sourceLink.source.resource.fsPath === link.source.resource.fsPath && sourceLink.source.hrefRange.isEqual(link.source.hrefRange); - const pathRange = this.getPathRange(link); - yield { - kind: 'link', - isTriggerLocation, - isDefinition: false, - link, - location: new vscode.Location(link.source.resource, pathRange), - }; - } - } - - private *getReferencesToLinkReference(allLinks: Iterable, refToFind: string, from: { resource: vscode.Uri; range: vscode.Range }): Iterable { - for (const link of allLinks) { - let ref: string; - if (link.kind === 'definition') { - ref = link.ref.text; - } else if (link.href.kind === 'reference') { - ref = link.href.ref; - } else { - continue; - } - - if (ref === refToFind && link.source.resource.fsPath === from.resource.fsPath) { - const isTriggerLocation = from.resource.fsPath === link.source.resource.fsPath && ( - (link.href.kind === 'reference' && from.range.isEqual(link.source.hrefRange)) || (link.kind === 'definition' && from.range.isEqual(link.ref.range))); - - const pathRange = this.getPathRange(link); - yield { - kind: 'link', - isTriggerLocation, - isDefinition: link.kind === 'definition', - link, - location: new vscode.Location(from.resource, pathRange), - }; - } - } - } - - /** - * Get just the range of the file path, dropping the fragment - */ - private getPathRange(link: MdLink): vscode.Range { - return link.source.fragmentRange - ? link.source.hrefRange.with(undefined, link.source.fragmentRange.start.translate(0, -1)) - : link.source.hrefRange; - } -} - -export async function tryResolveLinkPath(originalUri: vscode.Uri, workspace: IMdWorkspace): Promise { - if (await workspace.pathExists(originalUri)) { - return originalUri; - } - - // We don't think the file exists. If it doesn't already have an extension, try tacking on a `.md` and using that instead - if (uri.Utils.extname(originalUri) === '') { - const dotMdResource = originalUri.with({ path: originalUri.path + '.md' }); - if (await workspace.pathExists(dotMdResource)) { - return dotMdResource; - } - } - - return undefined; -} diff --git a/extensions/markdown-language-features/src/protocol.ts b/extensions/markdown-language-features/src/protocol.ts index 75b8162cf8c..edcec97381b 100644 --- a/extensions/markdown-language-features/src/protocol.ts +++ b/extensions/markdown-language-features/src/protocol.ts @@ -6,6 +6,7 @@ import Token = require('markdown-it/lib/token'); import { RequestType } from 'vscode-languageclient'; import type * as lsp from 'vscode-languageserver-types'; +import type * as md from 'vscode-markdown-languageservice'; // From server export const parseRequestType: RequestType<{ uri: string }, Token[], any> = new RequestType('markdown/parse'); @@ -14,5 +15,10 @@ export const statFileRequestType: RequestType<{ uri: string }, { isDirectory: bo export const readDirectoryRequestType: RequestType<{ uri: string }, [string, { isDirectory: boolean }][], any> = new RequestType('markdown/readDirectory'); export const findFilesRequestTypes = new RequestType<{}, string[], any>('markdown/findFiles'); +export const createFileWatcher: RequestType<{ id: number; uri: string; options: md.FileWatcherOptions }, void, any> = new RequestType('markdown/createFileWatcher'); +export const deleteFileWatcher: RequestType<{ id: number }, void, any> = new RequestType('markdown/deleteFileWatcher'); + // To server export const getReferencesToFileInWorkspace = new RequestType<{ uri: string }, lsp.Location[], any>('markdown/getReferencesToFileInWorkspace'); + +export const onWatcherChange: RequestType<{ id: number; uri: string; kind: 'create' | 'change' | 'delete' }, void, any> = new RequestType('markdown/onWatcherChange'); diff --git a/extensions/markdown-language-features/src/test/diagnostic.test.ts b/extensions/markdown-language-features/src/test/diagnostic.test.ts deleted file mode 100644 index 020e745a2d3..00000000000 --- a/extensions/markdown-language-features/src/test/diagnostic.test.ts +++ /dev/null @@ -1,591 +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 'mocha'; -import * as vscode from 'vscode'; -import { DiagnosticCollectionReporter, DiagnosticComputer, DiagnosticConfiguration, DiagnosticLevel, DiagnosticManager, DiagnosticOptions, DiagnosticReporter } from '../languageFeatures/diagnostics'; -import { MdLinkProvider } from '../languageFeatures/documentLinks'; -import { MdReferencesProvider } from '../languageFeatures/references'; -import { MdTableOfContentsProvider } from '../tableOfContents'; -import { ITextDocument } from '../types/textDocument'; -import { noopToken } from '../util/cancellation'; -import { DisposableStore } from '../util/dispose'; -import { InMemoryDocument } from '../util/inMemoryDocument'; -import { ResourceMap } from '../util/resourceMap'; -import { IMdWorkspace } from '../workspace'; -import { createNewMarkdownEngine } from './engine'; -import { InMemoryMdWorkspace } from './inMemoryWorkspace'; -import { nulLogger } from './nulLogging'; -import { assertRangeEqual, joinLines, withStore, workspacePath } from './util'; - -const defaultDiagnosticsOptions = Object.freeze({ - enabled: true, - validateFileLinks: DiagnosticLevel.warning, - validateMarkdownFileLinkFragments: undefined, - validateFragmentLinks: DiagnosticLevel.warning, - validateReferences: DiagnosticLevel.warning, - ignoreLinks: [], -}); - -async function getComputedDiagnostics(store: DisposableStore, doc: InMemoryDocument, workspace: IMdWorkspace, options: Partial = {}): Promise { - const engine = createNewMarkdownEngine(); - const linkProvider = store.add(new MdLinkProvider(engine, workspace, nulLogger)); - const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger)); - const computer = new DiagnosticComputer(workspace, linkProvider, tocProvider); - return ( - await computer.getDiagnostics(doc, { ...defaultDiagnosticsOptions, ...options, }, noopToken) - ).diagnostics; -} - -function assertDiagnosticsEqual(actual: readonly vscode.Diagnostic[], expectedRanges: readonly vscode.Range[]) { - assert.strictEqual(actual.length, expectedRanges.length, "Diagnostic count equal"); - - for (let i = 0; i < actual.length; ++i) { - assertRangeEqual(actual[i].range, expectedRanges[i], `Range ${i} to be equal`); - } -} - -function orderDiagnosticsByRange(diagnostics: Iterable): readonly vscode.Diagnostic[] { - return Array.from(diagnostics).sort((a, b) => a.range.start.compareTo(b.range.start)); -} - -class MemoryDiagnosticConfiguration implements DiagnosticConfiguration { - - private readonly _onDidChange = new vscode.EventEmitter(); - public readonly onDidChange = this._onDidChange.event; - - private _options: Partial; - - constructor(options: Partial) { - this._options = options; - } - - public getOptions(_resource: vscode.Uri): DiagnosticOptions { - return { - ...defaultDiagnosticsOptions, - ...this._options, - }; - } - - public update(newOptions: Partial) { - this._options = newOptions; - this._onDidChange.fire(); - } -} - -class MemoryDiagnosticReporter extends DiagnosticReporter { - - private readonly diagnostics = new ResourceMap(); - - constructor( - private readonly workspace: InMemoryMdWorkspace, - ) { - super(); - } - - override dispose(): void { - super.clear(); - this.clear(); - } - - override clear(): void { - super.clear(); - this.diagnostics.clear(); - } - - set(uri: vscode.Uri, diagnostics: readonly vscode.Diagnostic[]): void { - this.diagnostics.set(uri, diagnostics); - } - - isOpen(_uri: vscode.Uri): boolean { - return true; - } - - delete(uri: vscode.Uri): void { - this.diagnostics.delete(uri); - } - - get(uri: vscode.Uri): readonly vscode.Diagnostic[] { - return orderDiagnosticsByRange(this.diagnostics.get(uri) ?? []); - } - - getOpenDocuments(): ITextDocument[] { - return this.workspace.values(); - } -} - -suite('markdown: Diagnostic Computer', () => { - - test('Should not return any diagnostics for empty document', withStore(async (store) => { - const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( - `text`, - )); - const workspace = store.add(new InMemoryMdWorkspace([doc])); - - const diagnostics = await getComputedDiagnostics(store, doc, workspace); - assert.deepStrictEqual(diagnostics, []); - })); - - test('Should generate diagnostic for link to file that does not exist', withStore(async (store) => { - const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( - `[bad](/no/such/file.md)`, - `[good](/doc.md)`, - `[good-ref]: /doc.md`, - `[bad-ref]: /no/such/file.md`, - )); - const workspace = store.add(new InMemoryMdWorkspace([doc])); - - const diagnostics = await getComputedDiagnostics(store, doc, workspace); - assertDiagnosticsEqual(diagnostics, [ - new vscode.Range(0, 6, 0, 22), - new vscode.Range(3, 11, 3, 27), - ]); - })); - - test('Should generate diagnostics for links to header that does not exist in current file', withStore(async (store) => { - const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( - `[good](#good-header)`, - `# Good Header`, - `[bad](#no-such-header)`, - `[good](#good-header)`, - `[good-ref]: #good-header`, - `[bad-ref]: #no-such-header`, - )); - const workspace = store.add(new InMemoryMdWorkspace([doc])); - - const diagnostics = await getComputedDiagnostics(store, doc, workspace); - assertDiagnosticsEqual(diagnostics, [ - new vscode.Range(2, 6, 2, 21), - new vscode.Range(5, 11, 5, 26), - ]); - })); - - test('Should generate diagnostics for links to non-existent headers in other files', withStore(async (store) => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `# My header`, - `[good](#my-header)`, - `[good](/doc1.md#my-header)`, - `[good](doc1.md#my-header)`, - `[good](/doc2.md#other-header)`, - `[bad](/doc2.md#no-such-other-header)`, - )); - - const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines( - `# Other header`, - )); - - const diagnostics = await getComputedDiagnostics(store, doc1, new InMemoryMdWorkspace([doc1, doc2])); - assertDiagnosticsEqual(diagnostics, [ - new vscode.Range(5, 14, 5, 35), - ]); - })); - - test('Should support links both with and without .md file extension', withStore(async (store) => { - const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( - `# My header`, - `[good](#my-header)`, - `[good](/doc.md#my-header)`, - `[good](doc.md#my-header)`, - `[good](/doc#my-header)`, - `[good](doc#my-header)`, - )); - const workspace = store.add(new InMemoryMdWorkspace([doc])); - - const diagnostics = await getComputedDiagnostics(store, doc, workspace); - assertDiagnosticsEqual(diagnostics, []); - })); - - test('Should generate diagnostics for non-existent link reference', withStore(async (store) => { - const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( - `[good link][good]`, - `[bad link][no-such]`, - ``, - `[good]: http://example.com`, - )); - const workspace = store.add(new InMemoryMdWorkspace([doc])); - - const diagnostics = await getComputedDiagnostics(store, doc, workspace); - assertDiagnosticsEqual(diagnostics, [ - new vscode.Range(1, 11, 1, 18), - ]); - })); - - test('Should not generate diagnostics when validate is disabled', withStore(async (store) => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `[text](#no-such-header)`, - `[text][no-such-ref]`, - )); - const workspace = store.add(new InMemoryMdWorkspace([doc1])); - const diagnostics = await getComputedDiagnostics(store, doc1, workspace, new MemoryDiagnosticConfiguration({ enabled: false }).getOptions(doc1.uri)); - assertDiagnosticsEqual(diagnostics, []); - })); - - test('Should not generate diagnostics for email autolink', withStore(async (store) => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `a c`, - )); - - const diagnostics = await getComputedDiagnostics(store, doc1, new InMemoryMdWorkspace([doc1])); - assertDiagnosticsEqual(diagnostics, []); - })); - - test('Should not generate diagnostics for html tag that looks like an autolink', withStore(async (store) => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `a b c`, - `a b c`, - )); - - const diagnostics = await getComputedDiagnostics(store, doc1, new InMemoryMdWorkspace([doc1])); - assertDiagnosticsEqual(diagnostics, []); - })); - - test('Should allow ignoring invalid file link using glob', withStore(async (store) => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `[text](/no-such-file)`, - `![img](/no-such-file)`, - `[text]: /no-such-file`, - )); - const workspace = store.add(new InMemoryMdWorkspace([doc1])); - const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/no-such-file'] }); - assertDiagnosticsEqual(diagnostics, []); - })); - - test('Should be able to disable fragment validation for external files', withStore(async (store) => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `![i](/doc2.md#no-such)`, - )); - const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines('')); - const workspace = new InMemoryMdWorkspace([doc1, doc2]); - - const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { validateMarkdownFileLinkFragments: DiagnosticLevel.ignore }); - assertDiagnosticsEqual(diagnostics, []); - })); - - test('Disabling own fragment validation should also disable path fragment validation by default', withStore(async (store) => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `[b](#no-head)`, - `![i](/doc2.md#no-such)`, - )); - const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines('')); - const workspace = new InMemoryMdWorkspace([doc1, doc2]); - - { - const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { validateFragmentLinks: DiagnosticLevel.ignore }); - assertDiagnosticsEqual(diagnostics, []); - } - { - // But we should be able to override the default - const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { validateFragmentLinks: DiagnosticLevel.ignore, validateMarkdownFileLinkFragments: DiagnosticLevel.warning }); - assertDiagnosticsEqual(diagnostics, [ - new vscode.Range(1, 13, 1, 21), - ]); - } - })); - - test('ignoreLinks should allow skipping link to non-existent file', withStore(async (store) => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `[text](/no-such-file#header)`, - )); - const workspace = store.add(new InMemoryMdWorkspace([doc1])); - - const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/no-such-file'] }); - assertDiagnosticsEqual(diagnostics, []); - })); - - test('ignoreLinks should not consider link fragment', withStore(async (store) => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `[text](/no-such-file#header)`, - )); - const workspace = store.add(new InMemoryMdWorkspace([doc1])); - - const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/no-such-file'] }); - assertDiagnosticsEqual(diagnostics, []); - })); - - test('ignoreLinks should support globs', withStore(async (store) => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `![i](/images/aaa.png)`, - `![i](/images/sub/bbb.png)`, - `![i](/images/sub/sub2/ccc.png)`, - )); - const workspace = store.add(new InMemoryMdWorkspace([doc1])); - - const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/images/**/*.png'] }); - assertDiagnosticsEqual(diagnostics, []); - })); - - test('ignoreLinks should support ignoring header', withStore(async (store) => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `![i](#no-such)`, - )); - const workspace = store.add(new InMemoryMdWorkspace([doc1])); - - const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['#no-such'] }); - assertDiagnosticsEqual(diagnostics, []); - })); - - test('ignoreLinks should support ignoring header in file', withStore(async (store) => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `![i](/doc2.md#no-such)`, - )); - const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines('')); - const workspace = store.add(new InMemoryMdWorkspace([doc1, doc2])); - - { - const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/doc2.md#no-such'] }); - assertDiagnosticsEqual(diagnostics, []); - } - { - const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/doc2.md#*'] }); - assertDiagnosticsEqual(diagnostics, []); - } - })); - - test('ignoreLinks should support ignore header links if file is ignored', withStore(async (store) => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `![i](/doc2.md#no-such)`, - )); - const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines('')); - const workspace = new InMemoryMdWorkspace([doc1, doc2]); - - const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/doc2.md'] }); - assertDiagnosticsEqual(diagnostics, []); - })); - - test('Should not detect checkboxes as invalid links', withStore(async (store) => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `- [x]`, - `- [X]`, - `- [ ]`, - )); - const workspace = store.add(new InMemoryMdWorkspace([doc1])); - - const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/doc2.md'] }); - assertDiagnosticsEqual(diagnostics, []); - })); - - test('Should detect invalid links with titles', withStore(async (store) => { - const doc = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `[link]( "text")`, - `[link]( 'text')`, - `[link]( (text))`, - `[link](no-such.md "text")`, - `[link](no-such.md 'text')`, - `[link](no-such.md (text))`, - )); - const workspace = store.add(new InMemoryMdWorkspace([doc])); - - const diagnostics = await getComputedDiagnostics(store, doc, workspace); - assertDiagnosticsEqual(diagnostics, [ - new vscode.Range(0, 8, 0, 18), - new vscode.Range(1, 8, 1, 18), - new vscode.Range(2, 8, 2, 18), - new vscode.Range(3, 7, 3, 17), - new vscode.Range(4, 7, 4, 17), - new vscode.Range(5, 7, 5, 17), - ]); - })); - - test('Should generate diagnostics for non-existent header using file link to own file', withStore(async (store) => { - const doc = new InMemoryDocument(workspacePath('sub', 'doc.md'), joinLines( - `[bad](doc.md#no-such)`, - `[bad](doc#no-such)`, - `[bad](/sub/doc.md#no-such)`, - `[bad](/sub/doc#no-such)`, - )); - const workspace = store.add(new InMemoryMdWorkspace([doc])); - - const diagnostics = await getComputedDiagnostics(store, doc, workspace); - assertDiagnosticsEqual(orderDiagnosticsByRange(diagnostics), [ - new vscode.Range(0, 12, 0, 20), - new vscode.Range(1, 9, 1, 17), - new vscode.Range(2, 17, 2, 25), - new vscode.Range(3, 14, 3, 22), - ]); - })); - - test('Own header link using file path link should be controlled by "validateMarkdownFileLinkFragments" instead of "validateFragmentLinks"', withStore(async (store) => { - const doc1 = new InMemoryDocument(workspacePath('sub', 'doc.md'), joinLines( - `[bad](doc.md#no-such)`, - `[bad](doc#no-such)`, - `[bad](/sub/doc.md#no-such)`, - `[bad](/sub/doc#no-such)`, - )); - const workspace = store.add(new InMemoryMdWorkspace([doc1])); - - const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { - validateFragmentLinks: DiagnosticLevel.ignore, - validateMarkdownFileLinkFragments: DiagnosticLevel.warning, - }); - assertDiagnosticsEqual(orderDiagnosticsByRange(diagnostics), [ - new vscode.Range(0, 12, 0, 20), - new vscode.Range(1, 9, 1, 17), - new vscode.Range(2, 17, 2, 25), - new vscode.Range(3, 14, 3, 22), - ]); - })); -}); - -suite('Markdown: Diagnostics manager', () => { - - function createDiagnosticsManager( - store: DisposableStore, - workspace: IMdWorkspace, - configuration = new MemoryDiagnosticConfiguration({}), - reporter: DiagnosticReporter = new DiagnosticCollectionReporter(), - ) { - const engine = createNewMarkdownEngine(); - const linkProvider = store.add(new MdLinkProvider(engine, workspace, nulLogger)); - const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger)); - const referencesProvider = store.add(new MdReferencesProvider(engine, workspace, tocProvider, nulLogger)); - const manager = store.add(new DiagnosticManager( - workspace, - new DiagnosticComputer(workspace, linkProvider, tocProvider), - configuration, - reporter, - referencesProvider, - tocProvider, - nulLogger, - 0)); - return manager; - } - - test('Changing enable/disable should recompute diagnostics', withStore(async (store) => { - const doc1Uri = workspacePath('doc1.md'); - const doc2Uri = workspacePath('doc2.md'); - const workspace = store.add(new InMemoryMdWorkspace([ - new InMemoryDocument(doc1Uri, joinLines( - `[text](#no-such-1)`, - )), - new InMemoryDocument(doc2Uri, joinLines( - `[text](#no-such-2)`, - )) - ])); - - const reporter = store.add(new MemoryDiagnosticReporter(workspace)); - const config = new MemoryDiagnosticConfiguration({ enabled: true }); - - const manager = createDiagnosticsManager(store, workspace, config, reporter); - await manager.ready; - - // Check initial state (Enabled) - await reporter.waitPendingWork(); - assertDiagnosticsEqual(reporter.get(doc1Uri), [ - new vscode.Range(0, 7, 0, 17), - ]); - assertDiagnosticsEqual(reporter.get(doc2Uri), [ - new vscode.Range(0, 7, 0, 17), - ]); - - // Disable - config.update({ enabled: false }); - await reporter.waitPendingWork(); - assertDiagnosticsEqual(reporter.get(doc1Uri), []); - assertDiagnosticsEqual(reporter.get(doc2Uri), []); - - // Enable - config.update({ enabled: true }); - await reporter.waitPendingWork(); - assertDiagnosticsEqual(reporter.get(doc1Uri), [ - new vscode.Range(0, 7, 0, 17), - ]); - assertDiagnosticsEqual(reporter.get(doc2Uri), [ - new vscode.Range(0, 7, 0, 17), - ]); - })); - - test('Should revalidate linked files when header changes', withStore(async (store) => { - const doc1Uri = workspacePath('doc1.md'); - const doc1 = new InMemoryDocument(doc1Uri, joinLines( - `[text](#no-such)`, - `[text](/doc2.md#header)`, - )); - const doc2Uri = workspacePath('doc2.md'); - const doc2 = new InMemoryDocument(doc2Uri, joinLines( - `# Header`, - `[text](#header)`, - `[text](#no-such-2)`, - )); - const workspace = store.add(new InMemoryMdWorkspace([doc1, doc2])); - const reporter = store.add(new MemoryDiagnosticReporter(workspace)); - - const manager = createDiagnosticsManager(store, workspace, new MemoryDiagnosticConfiguration({}), reporter); - await manager.ready; - - // Check initial state - await reporter.waitPendingWork(); - assertDiagnosticsEqual(reporter.get(doc1Uri), [ - new vscode.Range(0, 7, 0, 15), - ]); - assertDiagnosticsEqual(reporter.get(doc2Uri), [ - new vscode.Range(2, 7, 2, 17), - ]); - - // Edit header - workspace.updateDocument(new InMemoryDocument(doc2Uri, joinLines( - `# new header`, - `[text](#new-header)`, - `[text](#no-such-2)`, - ))); - await reporter.waitPendingWork(); - assertDiagnosticsEqual(reporter.get(doc1Uri), [ - new vscode.Range(0, 7, 0, 15), - new vscode.Range(1, 15, 1, 22), - ]); - assertDiagnosticsEqual(reporter.get(doc2Uri), [ - new vscode.Range(2, 7, 2, 17), - ]); - - // Revert to original file - workspace.updateDocument(new InMemoryDocument(doc2Uri, joinLines( - `# header`, - `[text](#header)`, - `[text](#no-such-2)`, - ))); - await reporter.waitPendingWork(); - assertDiagnosticsEqual(reporter.get(doc1Uri), [ - new vscode.Range(0, 7, 0, 15) - ]); - assertDiagnosticsEqual(reporter.get(doc2Uri), [ - new vscode.Range(2, 7, 2, 17), - ]); - })); - - test('Should revalidate linked files when file is deleted/created', withStore(async (store) => { - const doc1Uri = workspacePath('doc1.md'); - const doc1 = new InMemoryDocument(doc1Uri, joinLines( - `[text](/doc2.md)`, - `[text](/doc2.md#header)`, - )); - const doc2Uri = workspacePath('doc2.md'); - const doc2 = new InMemoryDocument(doc2Uri, joinLines( - `# Header` - )); - const workspace = store.add(new InMemoryMdWorkspace([doc1, doc2])); - const reporter = store.add(new MemoryDiagnosticReporter(workspace)); - - const manager = createDiagnosticsManager(store, workspace, new MemoryDiagnosticConfiguration({}), reporter); - await manager.ready; - - // Check initial state - await reporter.waitPendingWork(); - assertDiagnosticsEqual(reporter.get(doc1Uri), []); - - // Edit header - workspace.deleteDocument(doc2Uri); - - await reporter.waitPendingWork(); - assertDiagnosticsEqual(reporter.get(doc1Uri), [ - new vscode.Range(0, 7, 0, 15), - new vscode.Range(1, 7, 1, 22), - ]); - - // Revert to original file - workspace.createDocument(doc2); - await reporter.waitPendingWork(); - assertDiagnosticsEqual(reporter.get(doc1Uri), []); - })); -}); diff --git a/extensions/markdown-language-features/src/test/documentInfoCache.test.ts b/extensions/markdown-language-features/src/test/documentInfoCache.test.ts deleted file mode 100644 index a1b291aee0b..00000000000 --- a/extensions/markdown-language-features/src/test/documentInfoCache.test.ts +++ /dev/null @@ -1,33 +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 'mocha'; -import { InMemoryDocument } from '../util/inMemoryDocument'; -import { MdDocumentInfoCache } from '../util/workspaceCache'; -import { InMemoryMdWorkspace } from './inMemoryWorkspace'; -import { workspacePath } from './util'; - -suite('DocumentInfoCache', () => { - test('Repeated calls should only compute value once', async () => { - const doc = workspacePath('doc.md'); - const workspace = new InMemoryMdWorkspace([ - new InMemoryDocument(doc, '') - ]); - - let i = 0; - const cache = new MdDocumentInfoCache(workspace, async () => { - return ++i; - }); - - const a = cache.get(doc); - const b = cache.get(doc); - - assert.strictEqual(await a, 1); - assert.strictEqual(i, 1); - assert.strictEqual(await b, 1); - assert.strictEqual(i, 1); - }); -}); diff --git a/extensions/markdown-language-features/src/test/tableOfContentsProvider.test.ts b/extensions/markdown-language-features/src/test/tableOfContentsProvider.test.ts deleted file mode 100644 index 226ca4437e0..00000000000 --- a/extensions/markdown-language-features/src/test/tableOfContentsProvider.test.ts +++ /dev/null @@ -1,136 +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 'mocha'; -import * as vscode from 'vscode'; -import { TableOfContents } from '../tableOfContents'; -import { ITextDocument } from '../types/textDocument'; -import { InMemoryDocument } from '../util/inMemoryDocument'; -import { createNewMarkdownEngine } from './engine'; - - -const testFileName = vscode.Uri.file('test.md'); - -function createToc(doc: ITextDocument): Promise { - const engine = createNewMarkdownEngine(); - return TableOfContents.create(engine, doc); -} - -suite('markdown.TableOfContentsProvider', () => { - test('Lookup should not return anything for empty document', async () => { - const doc = new InMemoryDocument(testFileName, ''); - const provider = await createToc(doc); - - assert.strictEqual(provider.lookup(''), undefined); - assert.strictEqual(provider.lookup('foo'), undefined); - }); - - test('Lookup should not return anything for document with no headers', async () => { - const doc = new InMemoryDocument(testFileName, 'a *b*\nc'); - const provider = await createToc(doc); - - assert.strictEqual(provider.lookup(''), undefined); - assert.strictEqual(provider.lookup('foo'), undefined); - assert.strictEqual(provider.lookup('a'), undefined); - assert.strictEqual(provider.lookup('b'), undefined); - }); - - test('Lookup should return basic #header', async () => { - const doc = new InMemoryDocument(testFileName, `# a\nx\n# c`); - const provider = await createToc(doc); - - { - const entry = provider.lookup('a'); - assert.ok(entry); - assert.strictEqual(entry!.line, 0); - } - { - assert.strictEqual(provider.lookup('x'), undefined); - } - { - const entry = provider.lookup('c'); - assert.ok(entry); - assert.strictEqual(entry!.line, 2); - } - }); - - test('Lookups should be case in-sensitive', async () => { - const doc = new InMemoryDocument(testFileName, `# fOo\n`); - const provider = await createToc(doc); - - assert.strictEqual((provider.lookup('fOo'))!.line, 0); - assert.strictEqual((provider.lookup('foo'))!.line, 0); - assert.strictEqual((provider.lookup('FOO'))!.line, 0); - }); - - test('Lookups should ignore leading and trailing white-space, and collapse internal whitespace', async () => { - const doc = new InMemoryDocument(testFileName, `# f o o \n`); - const provider = await createToc(doc); - - assert.strictEqual((provider.lookup('f o o'))!.line, 0); - assert.strictEqual((provider.lookup(' f o o'))!.line, 0); - assert.strictEqual((provider.lookup(' f o o '))!.line, 0); - assert.strictEqual((provider.lookup('f o o'))!.line, 0); - assert.strictEqual((provider.lookup('f o o'))!.line, 0); - - assert.strictEqual(provider.lookup('f'), undefined); - assert.strictEqual(provider.lookup('foo'), undefined); - assert.strictEqual(provider.lookup('fo o'), undefined); - }); - - test('should handle special characters #44779', async () => { - const doc = new InMemoryDocument(testFileName, `# Indentação\n`); - const provider = await createToc(doc); - - assert.strictEqual((provider.lookup('indentação'))!.line, 0); - }); - - test('should handle special characters 2, #48482', async () => { - const doc = new InMemoryDocument(testFileName, `# Инструкция - Делай Раз, Делай Два\n`); - const provider = await createToc(doc); - - assert.strictEqual((provider.lookup('инструкция---делай-раз-делай-два'))!.line, 0); - }); - - test('should handle special characters 3, #37079', async () => { - const doc = new InMemoryDocument(testFileName, `## Header 2 -### Header 3 -## Заголовок 2 -### Заголовок 3 -### Заголовок Header 3 -## Заголовок`); - - const provider = await createToc(doc); - - assert.strictEqual((provider.lookup('header-2'))!.line, 0); - assert.strictEqual((provider.lookup('header-3'))!.line, 1); - assert.strictEqual((provider.lookup('Заголовок-2'))!.line, 2); - assert.strictEqual((provider.lookup('Заголовок-3'))!.line, 3); - assert.strictEqual((provider.lookup('Заголовок-header-3'))!.line, 4); - assert.strictEqual((provider.lookup('Заголовок'))!.line, 5); - }); - - test('Lookup should support suffixes for repeated headers', async () => { - const doc = new InMemoryDocument(testFileName, `# a\n# a\n## a`); - const provider = await createToc(doc); - - { - const entry = provider.lookup('a'); - assert.ok(entry); - assert.strictEqual(entry!.line, 0); - } - { - const entry = provider.lookup('a-1'); - assert.ok(entry); - assert.strictEqual(entry!.line, 1); - } - { - const entry = provider.lookup('a-2'); - assert.ok(entry); - assert.strictEqual(entry!.line, 2); - } - }); -}); diff --git a/extensions/markdown-language-features/src/util/tableOfContentsWatcher.ts b/extensions/markdown-language-features/src/util/tableOfContentsWatcher.ts deleted file mode 100644 index 8bbbf26fafb..00000000000 --- a/extensions/markdown-language-features/src/util/tableOfContentsWatcher.ts +++ /dev/null @@ -1,89 +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 vscode from 'vscode'; -import { MdTableOfContentsProvider, TableOfContents } from '../tableOfContents'; -import { ITextDocument } from '../types/textDocument'; -import { IMdWorkspace } from '../workspace'; -import { equals } from './arrays'; -import { Delayer } from './async'; -import { Disposable } from './dispose'; -import { ResourceMap } from './resourceMap'; - -/** - * Check if the items in a table of contents have changed. - * - * This only checks for changes in the entries themselves, not for any changes in their locations. - */ -function hasTableOfContentsChanged(a: TableOfContents, b: TableOfContents): boolean { - const aSlugs = a.entries.map(entry => entry.slug.value).sort(); - const bSlugs = b.entries.map(entry => entry.slug.value).sort(); - return !equals(aSlugs, bSlugs); -} - -export class MdTableOfContentsWatcher extends Disposable { - - private readonly _files = new ResourceMap<{ - readonly toc: TableOfContents; - }>(); - - private readonly _pending = new ResourceMap(); - - private readonly _onTocChanged = this._register(new vscode.EventEmitter<{ readonly uri: vscode.Uri }>); - public readonly onTocChanged = this._onTocChanged.event; - - private readonly delayer: Delayer; - - public constructor( - private readonly workspace: IMdWorkspace, - private readonly tocProvider: MdTableOfContentsProvider, - private readonly delay: number, - ) { - super(); - - this.delayer = this._register(new Delayer(delay)); - - this._register(this.workspace.onDidChangeMarkdownDocument(this.onDidChangeDocument, this)); - this._register(this.workspace.onDidCreateMarkdownDocument(this.onDidCreateDocument, this)); - this._register(this.workspace.onDidDeleteMarkdownDocument(this.onDidDeleteDocument, this)); - } - - private async onDidCreateDocument(document: ITextDocument) { - const toc = await this.tocProvider.getForDocument(document); - this._files.set(document.uri, { toc }); - } - - private async onDidChangeDocument(document: ITextDocument) { - if (this.delay > 0) { - this._pending.set(document.uri); - this.delayer.trigger(() => this.flushPending()); - } else { - this.updateForResource(document.uri); - } - } - - private onDidDeleteDocument(resource: vscode.Uri) { - this._files.delete(resource); - this._pending.delete(resource); - } - - private async flushPending() { - const pending = [...this._pending.keys()]; - this._pending.clear(); - - return Promise.all(pending.map(resource => this.updateForResource(resource))); - } - - private async updateForResource(resource: vscode.Uri) { - const existing = this._files.get(resource); - const newToc = await this.tocProvider.get(resource); - - if (!existing || hasTableOfContentsChanged(existing.toc, newToc)) { - this._onTocChanged.fire({ uri: resource }); - } - - this._files.set(resource, { toc: newToc }); - } -} diff --git a/extensions/markdown-language-features/src/util/workspaceCache.ts b/extensions/markdown-language-features/src/util/workspaceCache.ts index 8432675c87a..2569dbee2b4 100644 --- a/extensions/markdown-language-features/src/util/workspaceCache.ts +++ b/extensions/markdown-language-features/src/util/workspaceCache.ts @@ -114,74 +114,3 @@ export class MdDocumentInfoCache extends Disposable { this._cache.delete(resource); } } - -/** - * Cache of information across all markdown files in the workspace. - * - * Unlike {@link MdDocumentInfoCache}, the entries here are computed eagerly for every file in the workspace. - * However the computation of the values is still lazy. - */ -export class MdWorkspaceInfoCache extends Disposable { - - private readonly _cache = new LazyResourceMap(); - private _init?: Promise; - - public constructor( - private readonly workspace: IMdWorkspace, - private readonly getValue: (document: ITextDocument) => Promise, - ) { - super(); - } - - public async entries(): Promise> { - await this.ensureInit(); - return this._cache.entries(); - } - - public async values(): Promise> { - await this.ensureInit(); - return Array.from(await this._cache.entries(), x => x[1]); - } - - public async getForDocs(docs: readonly ITextDocument[]): Promise { - for (const doc of docs) { - if (!this._cache.has(doc.uri)) { - this.update(doc); - } - } - - return Promise.all(docs.map(doc => this._cache.get(doc.uri) as Promise)); - } - - private async ensureInit(): Promise { - if (!this._init) { - this._init = this.populateCache(); - - this._register(this.workspace.onDidChangeMarkdownDocument(this.onDidChangeDocument, this)); - this._register(this.workspace.onDidCreateMarkdownDocument(this.onDidChangeDocument, this)); - this._register(this.workspace.onDidDeleteMarkdownDocument(this.onDidDeleteDocument, this)); - } - await this._init; - } - - private async populateCache(): Promise { - const markdownDocumentUris = await this.workspace.getAllMarkdownDocuments(); - for (const document of markdownDocumentUris) { - if (!this._cache.has(document.uri)) { - this.update(document); - } - } - } - - private update(document: ITextDocument): void { - this._cache.set(document.uri, lazy(() => this.getValue(document))); - } - - private onDidChangeDocument(document: ITextDocument) { - this.update(document); - } - - private onDidDeleteDocument(resource: vscode.Uri) { - this._cache.delete(resource); - } -} diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock index 12c6af02b5f..3150da7a642 100644 --- a/extensions/markdown-language-features/yarn.lock +++ b/extensions/markdown-language-features/yarn.lock @@ -237,21 +237,42 @@ vscode-languageserver-textdocument@^1.0.4: resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.4.tgz#3cd56dd14cec1d09e86c4bb04b09a246cb3df157" integrity sha512-/xhqXP/2A2RSs+J8JNXpiiNVvvNM0oTosNVmQnunlKvq9o4mupHOBAnnzH0lwIPKazXKvAKsVp1kr+H/K4lgoQ== +vscode-languageserver-textdocument@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.5.tgz#838769940ece626176ec5d5a2aa2d0aa69f5095c" + integrity sha512-1ah7zyQjKBudnMiHbZmxz5bYNM9KKZYz+5VQLj+yr8l+9w3g+WAhCkUkWbhMEdC5u0ub4Ndiye/fDyS8ghIKQg== + vscode-languageserver-types@3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16" integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ== -vscode-languageserver-types@^3.17.2: +vscode-languageserver-types@^3.17.1, vscode-languageserver-types@^3.17.2: version "3.17.2" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz#b2c2e7de405ad3d73a883e91989b850170ffc4f2" integrity sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA== +vscode-markdown-languageservice@^0.0.0-alpha.10: + version "0.0.0-alpha.10" + resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.0.0-alpha.10.tgz#53b69c981eed7fd5efa155ab8c0f169995568681" + integrity sha512-rJ85nJ+d45yCz9lBhipavoWXz/vW5FknqqUpLqhe3/2xkrhxt8zcekhSoDepgkKFcTORAFV6g1SnnqxbVhX+uA== + dependencies: + picomatch "^2.3.1" + vscode-languageserver-textdocument "^1.0.5" + vscode-languageserver-types "^3.17.1" + vscode-nls "^5.0.1" + vscode-uri "^3.0.3" + vscode-nls@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-nls@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2" + integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A== + vscode-uri@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.3.tgz#a95c1ce2e6f41b7549f86279d19f47951e4f4d84" From 3d5662f389a90045e37381547dc7cfc41ca523ff Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 19 Jul 2022 20:10:55 -0700 Subject: [PATCH 063/197] Fix find with multiline regexes (#155672) Fixes #154826 --- src/vs/editor/common/model/textModelSearch.ts | 4 ++++ src/vs/editor/test/common/model/textModelSearch.test.ts | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/vs/editor/common/model/textModelSearch.ts b/src/vs/editor/common/model/textModelSearch.ts index 0d58500a2e6..87c4bd8ecf7 100644 --- a/src/vs/editor/common/model/textModelSearch.ts +++ b/src/vs/editor/common/model/textModelSearch.ts @@ -74,6 +74,10 @@ export function isMultilineRegexSource(searchString: string): boolean { for (let i = 0, len = searchString.length; i < len; i++) { const chCode = searchString.charCodeAt(i); + if (chCode === CharCode.LineFeed) { + return true; + } + if (chCode === CharCode.Backslash) { // move to next char diff --git a/src/vs/editor/test/common/model/textModelSearch.test.ts b/src/vs/editor/test/common/model/textModelSearch.test.ts index 493db522258..555a1f5f59a 100644 --- a/src/vs/editor/test/common/model/textModelSearch.test.ts +++ b/src/vs/editor/test/common/model/textModelSearch.test.ts @@ -747,6 +747,8 @@ suite('TextModelSearch', () => { assert(isMultilineRegexSource('foo\\r\\n')); assert(isMultilineRegexSource('\\n')); assert(isMultilineRegexSource('foo\\W')); + assert(isMultilineRegexSource('foo\n')); + assert(isMultilineRegexSource('foo\r\n')); }); test('issue #74715. \\d* finds empty string and stops searching.', () => { From 08d1824aed426bc928ac1874bbf0193da4a45e19 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Wed, 20 Jul 2022 00:37:39 -0400 Subject: [PATCH 064/197] Use object factory for editor resolvers (#155652) Switch to using object for editor resolver --- .../customEditor/browser/customEditors.ts | 25 +-- .../browser/interactive.contribution.ts | 40 ++--- .../mergeEditor/browser/view/mergeEditor.ts | 92 +++++------ .../notebook/browser/notebookServiceImpl.ts | 47 +++++- .../test/browser/notebookServiceImpl.test.ts | 4 +- .../common/preferencesContribution.ts | 46 +++--- .../browser/searchEditor.contribution.ts | 7 +- .../browser/terminalMainContribution.ts | 35 ++--- .../editor/browser/editorResolverService.ts | 62 ++------ .../editor/common/editorResolverService.ts | 18 +-- .../browser/editorResolverService.test.ts | 144 ++++++++++-------- .../editor/test/browser/editorService.test.ts | 91 ++++++----- .../textfile/common/textEditorService.ts | 8 +- 13 files changed, 333 insertions(+), 286 deletions(-) diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index db0ce90b3b1..3be7a7f1b16 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -26,7 +26,7 @@ import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { CONTEXT_ACTIVE_CUSTOM_EDITOR_ID, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CustomEditorCapabilities, CustomEditorInfo, CustomEditorInfoCollection, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { CustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditorModelManager'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorResolverService, IEditorType, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; +import { DiffEditorInputFactoryFunction, EditorInputFactoryFunction, IEditorResolverService, IEditorType, RegisteredEditorPriority, UntitledEditorInputFactoryFunction } from 'vs/workbench/services/editor/common/editorResolverService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ContributedCustomEditors } from '../common/contributedCustomEditors'; import { CustomEditorInput } from './customEditorInput'; @@ -116,6 +116,17 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ if (!globPattern.filenamePattern) { continue; } + + const editorInputFactory: EditorInputFactoryFunction = ({ resource }, group) => { + return { editor: CustomEditorInput.create(this.instantiationService, resource, contributedEditor.id, group.id) }; + }; + const untitledEditorInputFactory: UntitledEditorInputFactoryFunction = ({ resource }, group) => { + return { editor: CustomEditorInput.create(this.instantiationService, resource ?? URI.from({ scheme: Schemas.untitled, authority: `Untitled-${this._untitledCounter++}` }), contributedEditor.id, group.id) }; + }; + const diffEditorInputFactory: DiffEditorInputFactoryFunction = (diffEditorInput, group) => { + return { editor: this.createDiffEditorInput(diffEditorInput, contributedEditor.id, group) }; + }; + this._editorResolverDisposables.add(this.editorResolverService.registerEditor( globPattern.filenamePattern, { @@ -127,14 +138,10 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ { singlePerResource: () => !this.getCustomEditorCapabilities(contributedEditor.id)?.supportsMultipleEditorsPerDocument ?? true }, - ({ resource }, group) => { - return { editor: CustomEditorInput.create(this.instantiationService, resource, contributedEditor.id, group.id) }; - }, - ({ resource }, group) => { - return { editor: CustomEditorInput.create(this.instantiationService, resource ?? URI.from({ scheme: Schemas.untitled, authority: `Untitled-${this._untitledCounter++}` }), contributedEditor.id, group.id) }; - }, - (diffEditorInput, group) => { - return { editor: this.createDiffEditorInput(diffEditorInput, contributedEditor.id, group) }; + { + createEditorInput: editorInputFactory, + createUntitledEditorInput: untitledEditorInputFactory, + createDiffEditorInput: diffEditorInputFactory, } )); } diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index 9969bd9c645..c6442b9ce9c 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -203,9 +203,11 @@ export class InteractiveDocumentContribution extends Disposable implements IWork canSupportResource: uri => uri.scheme === Schemas.vscodeInteractiveInput, singlePerResource: true }, - ({ resource }) => { - const editorInput = editorService.getEditors(EditorsOrder.SEQUENTIAL).find(editor => editor.editor instanceof InteractiveEditorInput && editor.editor.inputResource.toString() === resource.toString()); - return editorInput!; + { + createEditorInput: ({ resource }) => { + const editorInput = editorService.getEditors(EditorsOrder.SEQUENTIAL).find(editor => editor.editor instanceof InteractiveEditorInput && editor.editor.inputResource.toString() === resource.toString()); + return editorInput!; + } } ); @@ -220,23 +222,25 @@ export class InteractiveDocumentContribution extends Disposable implements IWork canSupportResource: uri => uri.scheme === Schemas.vscodeInteractive || (uri.scheme === Schemas.vscodeNotebookCell && extname(uri) === '.interactive'), singlePerResource: true }, - ({ resource, options }) => { - const data = CellUri.parse(resource); - let notebookUri: URI = resource; - let cellOptions: IResourceEditorInput | undefined; + { + createEditorInput: ({ resource, options }) => { + const data = CellUri.parse(resource); + let notebookUri: URI = resource; + let cellOptions: IResourceEditorInput | undefined; - if (data) { - notebookUri = data.notebook; - cellOptions = { resource, options }; + if (data) { + notebookUri = data.notebook; + cellOptions = { resource, options }; + } + + const notebookOptions = { ...options, cellOptions } as INotebookEditorOptions; + + const editorInput = editorService.getEditors(EditorsOrder.SEQUENTIAL).find(editor => editor.editor instanceof InteractiveEditorInput && editor.editor.resource?.toString() === notebookUri.toString()); + return { + editor: editorInput!.editor, + options: notebookOptions + }; } - - const notebookOptions = { ...options, cellOptions } as INotebookEditorOptions; - - const editorInput = editorService.getEditors(EditorsOrder.SEQUENTIAL).find(editor => editor.editor instanceof InteractiveEditorInput && editor.editor.resource?.toString() === notebookUri.toString()); - return { - editor: editorInput!.editor, - options: notebookOptions - }; } ); } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index 9212069bb61..4f71c690a1b 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -47,7 +47,7 @@ import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/v import { ctxBaseResourceScheme, ctxIsMergeEditor, ctxMergeEditorLayout, MergeEditorLayoutTypes } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; import { settingsSashBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; +import { EditorInputFactoryFunction, IEditorResolverService, MergeEditorInputFactoryFunction, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import './colors'; import { InputCodeEditorView } from './editors/inputCodeEditorView'; @@ -510,6 +510,50 @@ export class MergeEditorResolverContribution extends Disposable { ) { super(); + const editorInputFactory: EditorInputFactoryFunction = (editor) => { + return { + editor: instantiationService.createInstance( + MergeEditorInput, + editor.resource, + { + uri: editor.resource, + title: '', + description: '', + detail: '' + }, + { + uri: editor.resource, + title: '', + description: '', + detail: '' + }, + editor.resource + ) + }; + }; + + const mergeEditorInputFactory: MergeEditorInputFactoryFunction = (mergeEditor: IResourceMergeEditorInput): EditorInputWithOptions => { + return { + editor: instantiationService.createInstance( + MergeEditorInput, + mergeEditor.base.resource, + { + uri: mergeEditor.input1.resource, + title: basename(mergeEditor.input1.resource), + description: '', + detail: '' + }, + { + uri: mergeEditor.input2.resource, + title: basename(mergeEditor.input2.resource), + description: '', + detail: '' + }, + mergeEditor.result.resource + ) + }; + }; + this._register(editorResolverService.registerEditor( `*`, { @@ -519,49 +563,9 @@ export class MergeEditorResolverContribution extends Disposable { priority: RegisteredEditorPriority.option }, {}, - (editor) => { - return { - editor: instantiationService.createInstance( - MergeEditorInput, - editor.resource, - { - uri: editor.resource, - title: '', - description: '', - detail: '' - }, - { - uri: editor.resource, - title: '', - description: '', - detail: '' - }, - editor.resource - ) - }; - }, - undefined, - undefined, - (mergeEditor: IResourceMergeEditorInput): EditorInputWithOptions => { - return { - editor: instantiationService.createInstance( - MergeEditorInput, - mergeEditor.base.resource, - { - uri: mergeEditor.input1.resource, - title: basename(mergeEditor.input1.resource), - description: '', - detail: '' - }, - { - uri: mergeEditor.input2.resource, - title: basename(mergeEditor.input2.resource), - description: '', - detail: '' - }, - mergeEditor.result.resource - ) - }; + { + createEditorInput: editorInputFactory, + createMergeEditorInput: mergeEditorInputFactory } )); } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index e93082dac76..8d10507c874 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -35,7 +35,7 @@ import { updateEditorTopPadding } from 'vs/workbench/contrib/notebook/common/not import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; import { NotebookEditorDescriptor, NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { ComplexNotebookProviderInfo, INotebookContentProvider, INotebookSerializer, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { DiffEditorInputFactoryFunction, EditorInputFactoryFunction, IEditorResolverService, IEditorType, RegisteredEditorInfo, RegisteredEditorPriority, UntitledEditorInputFactoryFunction } from 'vs/workbench/services/editor/common/editorResolverService'; +import { DiffEditorInputFactoryFunction, EditorInputFactoryFunction, EditorInputFactoryObject, IEditorResolverService, IEditorType, RegisteredEditorInfo, RegisteredEditorPriority, UntitledEditorInputFactoryFunction } from 'vs/workbench/services/editor/common/editorResolverService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; @@ -195,23 +195,56 @@ export class NotebookProviderInfoStore extends Disposable { const notebookDiffEditorInputFactory: DiffEditorInputFactoryFunction = ({ modified, original, label, description }) => { return { editor: NotebookDiffEditorInput.create(this._instantiationService, modified.resource!, label, description, original.resource!, notebookProviderInfo.id) }; }; + + const notebookFactoryObject: EditorInputFactoryObject = { + createEditorInput: notebookEditorInputFactory, + createDiffEditorInput: notebookDiffEditorInputFactory, + createUntitledEditorInput: notebookUntitledEditorFactory, + }; + const notebookCellFactoryObject: EditorInputFactoryObject = { + createEditorInput: notebookEditorInputFactory, + createDiffEditorInput: notebookDiffEditorInputFactory, + }; + + // TODO @lramos15 find a better way to toggle handling diff editors than needing these listeners for every registration + // This is a lot of event listeners especially if there are many notebooks + disposables.add(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(NotebookSetting.textDiffEditorPreview)) { + const canHandleDiff = !!this._configurationService.getValue(NotebookSetting.textDiffEditorPreview) && !this._accessibilityService.isScreenReaderOptimized(); + if (canHandleDiff) { + notebookFactoryObject.createDiffEditorInput = notebookDiffEditorInputFactory; + notebookCellFactoryObject.createDiffEditorInput = notebookDiffEditorInputFactory; + } else { + notebookFactoryObject.createDiffEditorInput = undefined; + notebookCellFactoryObject.createDiffEditorInput = undefined; + } + } + })); + + disposables.add(this._accessibilityService.onDidChangeScreenReaderOptimized(() => { + const canHandleDiff = !!this._configurationService.getValue(NotebookSetting.textDiffEditorPreview) && !this._accessibilityService.isScreenReaderOptimized(); + if (canHandleDiff) { + notebookFactoryObject.createDiffEditorInput = notebookDiffEditorInputFactory; + notebookCellFactoryObject.createDiffEditorInput = notebookDiffEditorInputFactory; + } else { + notebookFactoryObject.createDiffEditorInput = undefined; + notebookCellFactoryObject.createDiffEditorInput = undefined; + } + })); + // Register the notebook editor disposables.add(this._editorResolverService.registerEditor( globPattern, notebookEditorInfo, notebookEditorOptions, - notebookEditorInputFactory, - notebookUntitledEditorFactory, - notebookDiffEditorInputFactory + notebookFactoryObject, )); // Then register the schema handler as exclusive for that notebook disposables.add(this._editorResolverService.registerEditor( `${Schemas.vscodeNotebookCell}:/**/${globPattern}`, { ...notebookEditorInfo, priority: RegisteredEditorPriority.exclusive }, notebookEditorOptions, - notebookEditorInputFactory, - undefined, - notebookDiffEditorInputFactory + notebookCellFactoryObject )); } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookServiceImpl.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookServiceImpl.test.ts index 575b935cfc1..32e71906a47 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookServiceImpl.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookServiceImpl.test.ts @@ -35,7 +35,9 @@ suite('NotebookProviderInfoStore', function () { }, instantiationService.createInstance(EditorResolverService), new TestConfigurationService(), - new class extends mock() { }, + new class extends mock() { + override onDidChangeScreenReaderOptimized: Event = Event.None; + }, instantiationService, new class extends mock() { override hasProvider() { return true; } diff --git a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts index be64900c807..b0f2ac1333d 100644 --- a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts +++ b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts @@ -66,35 +66,35 @@ export class PreferencesContribution implements IWorkbenchContribution { label: nls.localize('splitSettingsEditorLabel', "Split Settings Editor"), priority: RegisteredEditorPriority.builtin, }, + {}, { - canHandleDiff: false, - }, - ({ resource, options }): EditorInputWithOptions => { - // Global User Settings File - if (isEqual(resource, this.userDataProfileService.currentProfile.settingsResource)) { - return { editor: this.preferencesService.createSplitJsonEditorInput(ConfigurationTarget.USER_LOCAL, resource), options }; - } - - // Single Folder Workspace Settings File - const state = this.workspaceService.getWorkbenchState(); - if (state === WorkbenchState.FOLDER) { - const folders = this.workspaceService.getWorkspace().folders; - if (isEqual(resource, folders[0].toResource(FOLDER_SETTINGS_PATH))) { - return { editor: this.preferencesService.createSplitJsonEditorInput(ConfigurationTarget.WORKSPACE, resource), options }; + createEditorInput: ({ resource, options }): EditorInputWithOptions => { + // Global User Settings File + if (isEqual(resource, this.userDataProfileService.currentProfile.settingsResource)) { + return { editor: this.preferencesService.createSplitJsonEditorInput(ConfigurationTarget.USER_LOCAL, resource), options }; } - } - // Multi Folder Workspace Settings File - else if (state === WorkbenchState.WORKSPACE) { - const folders = this.workspaceService.getWorkspace().folders; - for (const folder of folders) { - if (isEqual(resource, folder.toResource(FOLDER_SETTINGS_PATH))) { - return { editor: this.preferencesService.createSplitJsonEditorInput(ConfigurationTarget.WORKSPACE_FOLDER, resource), options }; + // Single Folder Workspace Settings File + const state = this.workspaceService.getWorkbenchState(); + if (state === WorkbenchState.FOLDER) { + const folders = this.workspaceService.getWorkspace().folders; + if (isEqual(resource, folders[0].toResource(FOLDER_SETTINGS_PATH))) { + return { editor: this.preferencesService.createSplitJsonEditorInput(ConfigurationTarget.WORKSPACE, resource), options }; } } - } - return { editor: this.textEditorService.createTextEditor({ resource }), options }; + // Multi Folder Workspace Settings File + else if (state === WorkbenchState.WORKSPACE) { + const folders = this.workspaceService.getWorkspace().folders; + for (const folder of folders) { + if (isEqual(resource, folder.toResource(FOLDER_SETTINGS_PATH))) { + return { editor: this.preferencesService.createSplitJsonEditorInput(ConfigurationTarget.WORKSPACE_FOLDER, resource), options }; + } + } + } + + return { editor: this.textEditorService.createTextEditor({ resource }), options }; + } } ); } diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index a75b4184bd2..a8a22c38b18 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -88,11 +88,12 @@ class SearchEditorContribution implements IWorkbenchContribution { }, { singlePerResource: true, - canHandleDiff: false, canSupportResource: resource => (extname(resource) === SEARCH_EDITOR_EXT) }, - ({ resource }) => { - return { editor: instantiationService.invokeFunction(getOrMakeSearchEditorInput, { from: 'existingFile', fileUri: resource }) }; + { + createEditorInput: ({ resource }) => { + return { editor: instantiationService.invokeFunction(getOrMakeSearchEditorInput, { from: 'existingFile', fileUri: resource }) }; + } } ); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMainContribution.ts b/src/vs/workbench/contrib/terminal/browser/terminalMainContribution.ts index edd1bf0aeda..dac305a6c63 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMainContribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMainContribution.ts @@ -46,27 +46,28 @@ export class TerminalMainContribution extends Disposable implements IWorkbenchCo priority: RegisteredEditorPriority.exclusive }, { - canHandleDiff: false, canSupportResource: uri => uri.scheme === Schemas.vscodeTerminal, singlePerResource: true }, - ({ resource, options }) => { - const instance = terminalService.getInstanceFromResource(resource); - if (instance) { - const sourceGroup = terminalGroupService.getGroupForInstance(instance); - sourceGroup?.removeInstance(instance); - } - const resolvedResource = terminalEditorService.resolveResource(instance || resource); - const editor = terminalEditorService.getInputFromResource(resolvedResource) || { editor: resolvedResource }; - return { - editor, - options: { - ...options, - pinned: true, - forceReload: true, - override: terminalEditorId + { + createEditorInput: ({ resource, options }) => { + const instance = terminalService.getInstanceFromResource(resource); + if (instance) { + const sourceGroup = terminalGroupService.getGroupForInstance(instance); + sourceGroup?.removeInstance(instance); } - }; + const resolvedResource = terminalEditorService.resolveResource(instance || resource); + const editor = terminalEditorService.getInputFromResource(resolvedResource) || { editor: resolvedResource }; + return { + editor, + options: { + ...options, + pinned: true, + forceReload: true, + override: terminalEditorId + } + }; + } } ); diff --git a/src/vs/workbench/services/editor/browser/editorResolverService.ts b/src/vs/workbench/services/editor/browser/editorResolverService.ts index 666798d3bea..ec9fc2b563b 100644 --- a/src/vs/workbench/services/editor/browser/editorResolverService.ts +++ b/src/vs/workbench/services/editor/browser/editorResolverService.ts @@ -14,7 +14,7 @@ import { DEFAULT_EDITOR_ASSOCIATION, EditorResourceAccessor, EditorInputWithOpti import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { Schemas } from 'vs/base/common/network'; -import { RegisteredEditorInfo, RegisteredEditorPriority, RegisteredEditorOptions, DiffEditorInputFactoryFunction, EditorAssociation, EditorAssociations, EditorInputFactoryFunction, editorsAssociationsSettingId, globMatchesResource, IEditorResolverService, priorityToRank, ResolvedEditor, ResolvedStatus, UntitledEditorInputFactoryFunction, MergeEditorInputFactoryFunction } from 'vs/workbench/services/editor/common/editorResolverService'; +import { RegisteredEditorInfo, RegisteredEditorPriority, RegisteredEditorOptions, EditorAssociation, EditorAssociations, editorsAssociationsSettingId, globMatchesResource, IEditorResolverService, priorityToRank, ResolvedEditor, ResolvedStatus, EditorInputFactoryObject } from 'vs/workbench/services/editor/common/editorResolverService'; import { IKeyMods, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { localize } from 'vs/nls'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; @@ -33,10 +33,7 @@ interface RegisteredEditor { globPattern: string | glob.IRelativePattern; editorInfo: RegisteredEditorInfo; options?: RegisteredEditorOptions; - createEditorInput: EditorInputFactoryFunction; - createUntitledEditorInput?: UntitledEditorInputFactoryFunction; - createDiffEditorInput?: DiffEditorInputFactoryFunction; - createMergeEditorInput?: MergeEditorInputFactoryFunction; + editorFactoryObject: EditorInputFactoryObject; } type RegisteredEditors = Array; @@ -72,7 +69,6 @@ export class EditorResolverService extends Disposable implements IEditorResolver // Read in the cache on statup this.cache = new Set(JSON.parse(this.storageService.get(EditorResolverService.cacheStorageID, StorageScope.PROFILE, JSON.stringify([])))); this.storageService.remove(EditorResolverService.cacheStorageID, StorageScope.PROFILE); - this.convertOldAssociationFormat(); this._register(this.storageService.onWillSaveState(() => { // We want to store the glob patterns we would activate on, this allows us to know if we need to await the ext host on startup for opening a resource @@ -83,13 +79,6 @@ export class EditorResolverService extends Disposable implements IEditorResolver this.extensionService.onDidRegisterExtensions(() => { this.cache = undefined; }); - - // When the setting changes we want to ensure that it is properly converted - this._register(this.configurationService.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration(editorsAssociationsSettingId)) { - this.convertOldAssociationFormat(); - } - })); } private resolveUntypedInputAndGroup(editor: EditorInputWithOptions | IUntypedEditorInput, preferredGroup: PreferredGroup | undefined): [IUntypedEditorInput, IEditorGroup, EditorActivation | undefined] | undefined { @@ -195,10 +184,8 @@ export class EditorResolverService extends Disposable implements IEditorResolver // If no override we take the selected editor id so that matches works with the isActive check untypedEditor.options = { override: selectedEditor.editorInfo.id, ...untypedEditor.options }; - let handlesDiff = typeof selectedEditor.options?.canHandleDiff === 'function' ? selectedEditor.options.canHandleDiff() : selectedEditor.options?.canHandleDiff; - // Also check that it has a factory function or else it doesn't matter - handlesDiff = handlesDiff && selectedEditor.createDiffEditorInput !== undefined; - if (handlesDiff === false && isResourceDiffEditorInput(untypedEditor)) { + // Check if diff can be created based on prescene of factory function + if (selectedEditor.editorFactoryObject.createDiffEditorInput === undefined && isResourceDiffEditorInput(untypedEditor)) { return ResolvedStatus.NONE; } @@ -244,10 +231,7 @@ export class EditorResolverService extends Disposable implements IEditorResolver globPattern: string | glob.IRelativePattern, editorInfo: RegisteredEditorInfo, options: RegisteredEditorOptions, - createEditorInput: EditorInputFactoryFunction, - createUntitledEditorInput?: UntitledEditorInputFactoryFunction, - createDiffEditorInput?: DiffEditorInputFactoryFunction, - createMergeEditorInput?: MergeEditorInputFactoryFunction + editorFactoryObject: EditorInputFactoryObject ): IDisposable { let registeredEditor = this._editors.get(globPattern); if (registeredEditor === undefined) { @@ -258,10 +242,7 @@ export class EditorResolverService extends Disposable implements IEditorResolver globPattern, editorInfo, options, - createEditorInput, - createUntitledEditorInput, - createDiffEditorInput, - createMergeEditorInput + editorFactoryObject }); this._onDidChangeEditorRegistrations.fire(); return toDisposable(() => { @@ -278,23 +259,6 @@ export class EditorResolverService extends Disposable implements IEditorResolver return matchingAssociations.filter(association => allEditors.find(c => c.editorInfo.id === association.viewType)); } - private convertOldAssociationFormat(): void { - const rawAssociations = this.configurationService.getValue(editorsAssociationsSettingId) || []; - // If it's not an array, then it's the new format - if (!Array.isArray(rawAssociations)) { - return; - } - const newSettingObject = Object.create(null); - // Make the correctly formatted object from the array and then set that object - for (const association of rawAssociations) { - if (association.filenamePattern) { - newSettingObject[association.filenamePattern] = association.viewType; - } - } - this.logService.info(`Migrating ${editorsAssociationsSettingId}`); - this.configurationService.updateValue(editorsAssociationsSettingId, newSettingObject); - } - private getAllUserAssociations(): EditorAssociations { const inspectedEditorAssociations = this.configurationService.inspect<{ [fileNamePattern: string]: string }>(editorsAssociationsSettingId) || {}; const workspaceAssociations = inspectedEditorAssociations.workspaceValue ?? {}; @@ -440,19 +404,19 @@ export class EditorResolverService extends Disposable implements IEditorResolver // If it's a merge editor we trigger the create merge editor input if (isResourceMergeEditorInput(editor)) { - if (!selectedEditor.createMergeEditorInput) { + if (!selectedEditor.editorFactoryObject.createMergeEditorInput) { return; } - const inputWithOptions = await selectedEditor.createMergeEditorInput(editor, group); + const inputWithOptions = await selectedEditor.editorFactoryObject.createMergeEditorInput(editor, group); return { editor: inputWithOptions.editor, options: inputWithOptions.options ?? options }; } // If it's a diff editor we trigger the create diff editor input if (isResourceDiffEditorInput(editor)) { - if (!selectedEditor.createDiffEditorInput) { + if (!selectedEditor.editorFactoryObject.createDiffEditorInput) { return; } - const inputWithOptions = await selectedEditor.createDiffEditorInput(editor, group); + const inputWithOptions = await selectedEditor.editorFactoryObject.createDiffEditorInput(editor, group); return { editor: inputWithOptions.editor, options: inputWithOptions.options ?? options }; } @@ -461,10 +425,10 @@ export class EditorResolverService extends Disposable implements IEditorResolver } if (isUntitledResourceEditorInput(editor)) { - if (!selectedEditor.createUntitledEditorInput) { + if (!selectedEditor.editorFactoryObject.createUntitledEditorInput) { return; } - const inputWithOptions = await selectedEditor.createUntitledEditorInput(editor, group); + const inputWithOptions = await selectedEditor.editorFactoryObject.createUntitledEditorInput(editor, group); return { editor: inputWithOptions.editor, options: inputWithOptions.options ?? options }; } @@ -483,7 +447,7 @@ export class EditorResolverService extends Disposable implements IEditorResolver } // Respect options passed back - const inputWithOptions = await selectedEditor.createEditorInput(editor, group); + const inputWithOptions = await selectedEditor.editorFactoryObject.createEditorInput(editor, group); options = inputWithOptions.options ?? options; const input = inputWithOptions.editor; diff --git a/src/vs/workbench/services/editor/common/editorResolverService.ts b/src/vs/workbench/services/editor/common/editorResolverService.ts index 57f3e2c1508..b4e47e8d4f2 100644 --- a/src/vs/workbench/services/editor/common/editorResolverService.ts +++ b/src/vs/workbench/services/editor/common/editorResolverService.ts @@ -84,10 +84,6 @@ export type RegisteredEditorOptions = { * If your editor cannot be opened in multiple groups for the same resource */ singlePerResource?: boolean | (() => boolean); - /** - * If your editor supports diffs - */ - canHandleDiff?: boolean | (() => boolean); /** * Whether or not you can support opening the given resource. @@ -113,6 +109,13 @@ export type DiffEditorInputFactoryFunction = (diffEditorInput: IResourceDiffEdit export type MergeEditorInputFactoryFunction = (mergeEditorInput: IResourceMergeEditorInput, group: IEditorGroup) => EditorInputFactoryResult; +export type EditorInputFactoryObject = { + createEditorInput: EditorInputFactoryFunction; + createUntitledEditorInput?: UntitledEditorInputFactoryFunction; + createDiffEditorInput?: DiffEditorInputFactoryFunction; + createMergeEditorInput?: MergeEditorInputFactoryFunction; +}; + export interface IEditorResolverService { readonly _serviceBrand: undefined; /** @@ -139,16 +142,13 @@ export interface IEditorResolverService { * @param globPattern The glob pattern for this registration * @param editorInfo Information about the registration * @param options Specific options which apply to this registration - * @param createEditorInput The factory method for creating inputs + * @param editorFactoryObject The editor input factory functions */ registerEditor( globPattern: string | glob.IRelativePattern, editorInfo: RegisteredEditorInfo, options: RegisteredEditorOptions, - createEditorInput: EditorInputFactoryFunction, - createUntitledEditorInput?: UntitledEditorInputFactoryFunction, - createDiffEditorInput?: DiffEditorInputFactoryFunction, - createMergeEditorInput?: MergeEditorInputFactoryFunction + editorFactoryObject: EditorInputFactoryObject ): IDisposable; /** diff --git a/src/vs/workbench/services/editor/test/browser/editorResolverService.test.ts b/src/vs/workbench/services/editor/test/browser/editorResolverService.test.ts index 3158684613d..5bfe40ad314 100644 --- a/src/vs/workbench/services/editor/test/browser/editorResolverService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorResolverService.test.ts @@ -40,8 +40,10 @@ suite('EditorResolverService', () => { detail: 'Test Editor Details', priority: RegisteredEditorPriority.default }, - { canHandleDiff: false }, - ({ resource, options }, group) => ({ editor: new TestFileEditorInput(URI.parse(resource.toString()), TEST_EDITOR_INPUT_ID) }), + {}, + { + createEditorInput: ({ resource, options }, group) => ({ editor: new TestFileEditorInput(URI.parse(resource.toString()), TEST_EDITOR_INPUT_ID) }), + } ); const resultingResolution = await service.resolveEditor({ resource: URI.file('my://resource-basics.test') }, part.activeGroup); @@ -64,9 +66,11 @@ suite('EditorResolverService', () => { detail: 'Test Editor Details', priority: RegisteredEditorPriority.default }, - { canHandleDiff: false }, - ({ resource, options }, group) => ({ editor: new TestFileEditorInput(URI.parse(resource.toString()), TEST_EDITOR_INPUT_ID) }), - ({ resource, options }, group) => ({ editor: new TestFileEditorInput((resource ? resource : URI.from({ scheme: Schemas.untitled })), UNTITLED_TEST_EDITOR_INPUT_ID) }), + {}, + { + createEditorInput: ({ resource, options }, group) => ({ editor: new TestFileEditorInput(URI.parse(resource.toString()), TEST_EDITOR_INPUT_ID) }), + createUntitledEditorInput: ({ resource, options }, group) => ({ editor: new TestFileEditorInput((resource ? resource : URI.from({ scheme: Schemas.untitled })), UNTITLED_TEST_EDITOR_INPUT_ID) }), + } ); // Untyped untitled - no resource @@ -105,8 +109,10 @@ suite('EditorResolverService', () => { detail: 'Test Editor Details Primary', priority: RegisteredEditorPriority.default }, - { canHandleDiff: false }, - ({ resource, options }, group) => ({ editor: new TestFileEditorInput(URI.parse(resource.toString()), TEST_EDITOR_INPUT_ID) }), + {}, + { + createEditorInput: ({ resource, options }, group) => ({ editor: new TestFileEditorInput(URI.parse(resource.toString()), TEST_EDITOR_INPUT_ID) }), + } ); const registeredEditorSecondary = service.registerEditor('*.test-secondary', @@ -116,8 +122,10 @@ suite('EditorResolverService', () => { detail: 'Test Editor Details Secondary', priority: RegisteredEditorPriority.default }, - { canHandleDiff: false }, - ({ resource, options }, group) => ({ editor: new TestFileEditorInput(URI.parse(resource.toString()), TEST_EDITOR_INPUT_ID) }), + {}, + { + createEditorInput: ({ resource, options }, group) => ({ editor: new TestFileEditorInput(URI.parse(resource.toString()), TEST_EDITOR_INPUT_ID) }), + } ); const resultingResolution = await service.resolveEditor({ @@ -145,18 +153,19 @@ suite('EditorResolverService', () => { detail: 'Test Editor Details', priority: RegisteredEditorPriority.default }, - { canHandleDiff: true }, - ({ resource, options }, group) => ({ editor: new TestFileEditorInput(URI.parse(resource.toString()), TEST_EDITOR_INPUT_ID) }), - undefined, - ({ modified, original, options }, group) => ({ - editor: accessor.instantiationService.createInstance( - DiffEditorInput, - 'name', - 'description', - new TestFileEditorInput(URI.parse(original.toString()), TEST_EDITOR_INPUT_ID), - new TestFileEditorInput(URI.parse(modified.toString()), TEST_EDITOR_INPUT_ID), - undefined) - }) + {}, + { + createEditorInput: ({ resource, options }, group) => ({ editor: new TestFileEditorInput(URI.parse(resource.toString()), TEST_EDITOR_INPUT_ID) }), + createDiffEditorInput: ({ modified, original, options }, group) => ({ + editor: accessor.instantiationService.createInstance( + DiffEditorInput, + 'name', + 'description', + new TestFileEditorInput(URI.parse(original.toString()), TEST_EDITOR_INPUT_ID), + new TestFileEditorInput(URI.parse(modified.toString()), TEST_EDITOR_INPUT_ID), + undefined) + }) + } ); const resultingResolution = await service.resolveEditor({ @@ -186,20 +195,21 @@ suite('EditorResolverService', () => { detail: 'Test Editor Details', priority: RegisteredEditorPriority.default }, - { canHandleDiff: true }, - ({ resource, options }, group) => ({ editor: new TestFileEditorInput(URI.parse(resource.toString()), TEST_EDITOR_INPUT_ID) }), - undefined, - ({ modified, original, options }, group) => { - diffOneCounter++; - return { - editor: accessor.instantiationService.createInstance( - DiffEditorInput, - 'name', - 'description', - new TestFileEditorInput(URI.parse(original.toString()), TEST_EDITOR_INPUT_ID), - new TestFileEditorInput(URI.parse(modified.toString()), TEST_EDITOR_INPUT_ID), - undefined) - }; + {}, + { + createEditorInput: ({ resource, options }, group) => ({ editor: new TestFileEditorInput(URI.parse(resource.toString()), TEST_EDITOR_INPUT_ID) }), + createDiffEditorInput: ({ modified, original, options }, group) => { + diffOneCounter++; + return { + editor: accessor.instantiationService.createInstance( + DiffEditorInput, + 'name', + 'description', + new TestFileEditorInput(URI.parse(original.toString()), TEST_EDITOR_INPUT_ID), + new TestFileEditorInput(URI.parse(modified.toString()), TEST_EDITOR_INPUT_ID), + undefined) + }; + } } ); @@ -210,20 +220,21 @@ suite('EditorResolverService', () => { detail: 'Test Editor Details', priority: RegisteredEditorPriority.default }, - { canHandleDiff: true }, - ({ resource, options }, group) => ({ editor: new TestFileEditorInput(URI.parse(resource.toString()), TEST_EDITOR_INPUT_ID) }), - undefined, - ({ modified, original, options }, group) => { - diffTwoCounter++; - return { - editor: accessor.instantiationService.createInstance( - DiffEditorInput, - 'name', - 'description', - new TestFileEditorInput(URI.parse(original.toString()), TEST_EDITOR_INPUT_ID), - new TestFileEditorInput(URI.parse(modified.toString()), TEST_EDITOR_INPUT_ID), - undefined) - }; + {}, + { + createEditorInput: ({ resource, options }, group) => ({ editor: new TestFileEditorInput(URI.parse(resource.toString()), TEST_EDITOR_INPUT_ID) }), + createDiffEditorInput: ({ modified, original, options }, group) => { + diffTwoCounter++; + return { + editor: accessor.instantiationService.createInstance( + DiffEditorInput, + 'name', + 'description', + new TestFileEditorInput(URI.parse(original.toString()), TEST_EDITOR_INPUT_ID), + new TestFileEditorInput(URI.parse(modified.toString()), TEST_EDITOR_INPUT_ID), + undefined) + }; + } } ); @@ -234,20 +245,21 @@ suite('EditorResolverService', () => { detail: 'Test Editor Details', priority: RegisteredEditorPriority.option }, - { canHandleDiff: true }, - ({ resource, options }, group) => ({ editor: new TestFileEditorInput(URI.parse(resource.toString()), TEST_EDITOR_INPUT_ID) }), - undefined, - ({ modified, original, options }, group) => { - defaultDiffCounter++; - return { - editor: accessor.instantiationService.createInstance( - DiffEditorInput, - 'name', - 'description', - new TestFileEditorInput(URI.parse(original.toString()), TEST_EDITOR_INPUT_ID), - new TestFileEditorInput(URI.parse(modified.toString()), TEST_EDITOR_INPUT_ID), - undefined) - }; + {}, + { + createEditorInput: ({ resource, options }, group) => ({ editor: new TestFileEditorInput(URI.parse(resource.toString()), TEST_EDITOR_INPUT_ID) }), + createDiffEditorInput: ({ modified, original, options }, group) => { + defaultDiffCounter++; + return { + editor: accessor.instantiationService.createInstance( + DiffEditorInput, + 'name', + 'description', + new TestFileEditorInput(URI.parse(original.toString()), TEST_EDITOR_INPUT_ID), + new TestFileEditorInput(URI.parse(modified.toString()), TEST_EDITOR_INPUT_ID), + undefined) + }; + } } ); @@ -354,8 +366,10 @@ suite('EditorResolverService', () => { detail: 'Test Editor Details', priority: RegisteredEditorPriority.default }, - { canHandleDiff: false }, - ({ resource, options }, group) => ({ editor: new TestFileEditorInput(URI.parse(resource.toString()), TEST_EDITOR_INPUT_ID) }), + {}, + { + createEditorInput: ({ resource, options }, group) => ({ editor: new TestFileEditorInput(URI.parse(resource.toString()), TEST_EDITOR_INPUT_ID) }) + } ); assert.strictEqual(eventCounter, 1); 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 f193e7d69b0..e0c3b499650 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -252,7 +252,9 @@ suite('EditorService', () => { '*.editor-service-locked-group-tests', { id: TEST_EDITOR_INPUT_ID, label: 'Label', priority: RegisteredEditorPriority.exclusive }, {}, - editor => ({ editor: new TestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }) + { + createEditorInput: editor => ({ editor: new TestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }) + } )); const input1: IResourceEditorInput = { resource: URI.parse('file://resource-basics.editor-service-locked-group-tests'), options: { pinned: true } }; @@ -388,7 +390,9 @@ suite('EditorService', () => { '*.editor-service-locked-group-tests', { id: TEST_EDITOR_INPUT_ID, label: 'Label', priority: RegisteredEditorPriority.exclusive }, {}, - editor => ({ editor: new TestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }) + { + createEditorInput: editor => ({ editor: new TestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }) + } )); const rootGroup = part.activeGroup; @@ -436,7 +440,9 @@ suite('EditorService', () => { '*.editor-service-locked-group-tests', { id: TEST_EDITOR_INPUT_ID, label: 'Label', priority: RegisteredEditorPriority.exclusive }, {}, - editor => ({ editor: new TestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }) + { + createEditorInput: editor => ({ editor: new TestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }) + } )); const rootGroup = part.activeGroup; @@ -484,7 +490,9 @@ suite('EditorService', () => { '*.editor-service-locked-group-tests', { id: TEST_EDITOR_INPUT_ID, label: 'Label', priority: RegisteredEditorPriority.exclusive }, {}, - editor => ({ editor: new TestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }) + { + createEditorInput: editor => ({ editor: new TestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }) + } )); const rootGroup = part.activeGroup; @@ -549,24 +557,26 @@ suite('EditorService', () => { disposables.add(accessor.editorResolverService.registerEditor( '*.editor-service-override-tests', { id: TEST_EDITOR_INPUT_ID, label: 'Label', priority: RegisteredEditorPriority.exclusive }, - { canHandleDiff: true }, - editor => { - editorFactoryCalled++; - lastEditorFactoryEditor = editor; + {}, + { + createEditorInput: editor => { + editorFactoryCalled++; + lastEditorFactoryEditor = editor; - return { editor: new TestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }; - }, - untitledEditor => { - untitledEditorFactoryCalled++; - lastUntitledEditorFactoryEditor = untitledEditor; + return { editor: new TestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }; + }, + createUntitledEditorInput: untitledEditor => { + untitledEditorFactoryCalled++; + lastUntitledEditorFactoryEditor = untitledEditor; - return { editor: new TestFileEditorInput(untitledEditor.resource ?? URI.parse(`untitled://my-untitled-editor-${untitledEditorFactoryCalled}`), TEST_EDITOR_INPUT_ID) }; - }, - diffEditor => { - diffEditorFactoryCalled++; - lastDiffEditorFactoryEditor = diffEditor; + return { editor: new TestFileEditorInput(untitledEditor.resource ?? URI.parse(`untitled://my-untitled-editor-${untitledEditorFactoryCalled}`), TEST_EDITOR_INPUT_ID) }; + }, + createDiffEditorInput: diffEditor => { + diffEditorFactoryCalled++; + lastDiffEditorFactoryEditor = diffEditor; - return { editor: new TestFileEditorInput(URI.file(`diff-editor-${diffEditorFactoryCalled}`), TEST_EDITOR_INPUT_ID) }; + return { editor: new TestFileEditorInput(URI.file(`diff-editor-${diffEditorFactoryCalled}`), TEST_EDITOR_INPUT_ID) }; + } } )); @@ -1401,7 +1411,9 @@ suite('EditorService', () => { '*.editor-service-override-tests', { id: TEST_EDITOR_INPUT_ID, label: 'Label', priority: RegisteredEditorPriority.exclusive }, {}, - editor => ({ editor: new TestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }) + { + createEditorInput: editor => ({ editor: new TestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }) + } )); // Typed editor @@ -2268,12 +2280,13 @@ suite('EditorService', () => { priority: RegisteredEditorPriority.builtin }, {}, - (editorInput) => { - editorCount++; - return ({ editor: textEditorService.createTextEditor(editorInput) }); - }, - undefined, - diffEditor => ({ editor: textEditorService.createTextEditor(diffEditor) }) + { + createEditorInput: (editorInput) => { + editorCount++; + return ({ editor: textEditorService.createTextEditor(editorInput) }); + }, + createDiffEditorInput: diffEditor => ({ editor: textEditorService.createTextEditor(diffEditor) }) + } ); assert.strictEqual(editorCount, 0); @@ -2311,12 +2324,13 @@ suite('EditorService', () => { priority: RegisteredEditorPriority.builtin }, {}, - (editorInput) => { - editorCount++; - return ({ editor: textEditorService.createTextEditor(editorInput) }); - }, - undefined, - diffEditor => ({ editor: textEditorService.createTextEditor(diffEditor) }) + { + createEditorInput: (editorInput) => { + editorCount++; + return ({ editor: textEditorService.createTextEditor(editorInput) }); + }, + createDiffEditorInput: diffEditor => ({ editor: textEditorService.createTextEditor(diffEditor) }) + } ); assert.strictEqual(editorCount, 0); @@ -2348,12 +2362,13 @@ suite('EditorService', () => { priority: RegisteredEditorPriority.builtin }, {}, - (editorInput) => { - editorCount++; - return ({ editor: textEditorService.createTextEditor(editorInput) }); - }, - undefined, - diffEditor => ({ editor: textEditorService.createTextEditor(diffEditor) }) + { + createEditorInput: (editorInput) => { + editorCount++; + return ({ editor: textEditorService.createTextEditor(editorInput) }); + }, + createDiffEditorInput: diffEditor => ({ editor: textEditorService.createTextEditor(diffEditor) }) + } ); assert.strictEqual(editorCount, 0); diff --git a/src/vs/workbench/services/textfile/common/textEditorService.ts b/src/vs/workbench/services/textfile/common/textEditorService.ts index a4a51339630..886acda45d2 100644 --- a/src/vs/workbench/services/textfile/common/textEditorService.ts +++ b/src/vs/workbench/services/textfile/common/textEditorService.ts @@ -75,9 +75,11 @@ export class TextEditorService extends Disposable implements ITextEditorService priority: RegisteredEditorPriority.builtin }, {}, - editor => ({ editor: this.createTextEditor(editor) }), - untitledEditor => ({ editor: this.createTextEditor(untitledEditor) }), - diffEditor => ({ editor: this.createTextEditor(diffEditor) }) + { + createEditorInput: editor => ({ editor: this.createTextEditor(editor) }), + createUntitledEditorInput: untitledEditor => ({ editor: this.createTextEditor(untitledEditor) }), + createDiffEditorInput: diffEditor => ({ editor: this.createTextEditor(diffEditor) }) + } )); } From 3da2d669a4dcf93938a262ba92f50b128ca7262a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 19 Jul 2022 21:44:01 -0700 Subject: [PATCH 065/197] Show loading for task building status bar icon (#155616) * Show loading for task building status bar icon Fixes #153935 * address my own fedback Co-authored-by: Benjamin Pasero --- src/vs/platform/progress/common/progress.ts | 1 + .../browser/parts/statusbar/statusbarItem.ts | 11 ++++++----- .../contrib/tasks/browser/task.contribution.ts | 2 +- .../services/progress/browser/progressService.ts | 4 ++-- .../workbench/services/statusbar/browser/statusbar.ts | 5 +++-- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index 3b00369640a..adee9697946 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -75,6 +75,7 @@ export interface IProgressDialogOptions extends IProgressOptions { export interface IProgressWindowOptions extends IProgressOptions { readonly location: ProgressLocation.Window; readonly command?: string; + readonly type?: 'syncing' | 'loading'; } export interface IProgressCompositeOptions extends IProgressOptions { diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts index 6b4685f4901..b529800544c 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts @@ -19,7 +19,7 @@ import { Command } from 'vs/editor/common/languages'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { renderIcon, renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; -import { syncing } from 'vs/platform/theme/common/iconRegistry'; +import { spinningLoading, syncing } from 'vs/platform/theme/common/iconRegistry'; import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { isMarkdownString, markdownStringEqual } from 'vs/base/common/htmlContent'; import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; @@ -79,7 +79,7 @@ export class StatusbarEntryItem extends Disposable { update(entry: IStatusbarEntry): void { // Update: Progress - this.label.showProgress = !!entry.showProgress; + this.label.showProgress = entry.showProgress ?? false; // Update: Text if (!this.entry || entry.text !== this.entry.text) { @@ -241,7 +241,7 @@ export class StatusbarEntryItem extends Disposable { class StatusBarCodiconLabel extends SimpleIconLabel { - private readonly progressCodicon = renderIcon(syncing); + private progressCodicon = renderIcon(syncing); private currentText = ''; private currentShowProgress = false; @@ -252,9 +252,10 @@ class StatusBarCodiconLabel extends SimpleIconLabel { super(container); } - set showProgress(showProgress: boolean) { + set showProgress(showProgress: boolean | 'syncing' | 'loading') { if (this.currentShowProgress !== showProgress) { - this.currentShowProgress = showProgress; + this.currentShowProgress = !!showProgress; + this.progressCodicon = renderIcon(showProgress === 'loading' ? spinningLoading : syncing); this.text = this.currentText; } } diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index 9b5df9014e5..658d86f53b2 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -112,7 +112,7 @@ export class TaskStatusBarContributions extends Disposable implements IWorkbench } if (promise && (event.kind === TaskEventKind.Active) && (this._activeTasksCount === 1)) { - this._progressService.withProgress({ location: ProgressLocation.Window, command: 'workbench.action.tasks.showTasks' }, progress => { + this._progressService.withProgress({ location: ProgressLocation.Window, command: 'workbench.action.tasks.showTasks', type: 'loading' }, progress => { progress.report({ message: nls.localize('building', 'Building...') }); return promise!; }).then(() => { diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index 8172f41fa24..c39c0a727da 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -93,7 +93,7 @@ export class ProgressService extends Disposable implements IProgressService { } } - private readonly windowProgressStack: [IProgressOptions, Progress][] = []; + private readonly windowProgressStack: [IProgressWindowOptions, Progress][] = []; private windowProgressStatusEntry: IStatusbarEntryAccessor | undefined = undefined; private withWindowProgress(options: IProgressWindowOptions, callback: (progress: IProgress<{ message?: string }>) => Promise): Promise { @@ -158,7 +158,7 @@ export class ProgressService extends Disposable implements IProgressService { const statusEntryProperties: IStatusbarEntry = { name: localize('status.progress', "Progress Message"), text, - showProgress: true, + showProgress: options.type || true, ariaLabel: text, tooltip: title, command: progressCommand diff --git a/src/vs/workbench/services/statusbar/browser/statusbar.ts b/src/vs/workbench/services/statusbar/browser/statusbar.ts index a4046d9c83d..6b55bb6860d 100644 --- a/src/vs/workbench/services/statusbar/browser/statusbar.ts +++ b/src/vs/workbench/services/statusbar/browser/statusbar.ts @@ -182,9 +182,10 @@ export interface IStatusbarEntry { readonly showBeak?: boolean; /** - * Will enable a spinning icon in front of the text to indicate progress. + * Will enable a spinning icon in front of the text to indicate progress. When `true` is + * specified, `syncing` will be used. */ - readonly showProgress?: boolean; + readonly showProgress?: boolean | 'syncing' | 'loading'; } export interface IStatusbarEntryAccessor extends IDisposable { From 63fc73bb4fa1d72393bd6379b0ab20f7946c56b9 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 19 Jul 2022 22:06:23 -0700 Subject: [PATCH 066/197] Throw if webviewExternalEndpoint is not set (#155667) Fixes #155498 --- src/vs/workbench/contrib/webview/browser/webviewElement.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index fcc0777e76a..42e7569dd34 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -560,7 +560,12 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD } protected webviewContentEndpoint(encodedWebviewOrigin: string): string { - const endpoint = this._environmentService.webviewExternalEndpoint!.replace('{{uuid}}', encodedWebviewOrigin); + const webviewExternalEndpoint = this._environmentService.webviewExternalEndpoint; + if (!webviewExternalEndpoint) { + throw new Error(`'webviewExternalEndpoint' has not been configured. Webviews will not work!`); + } + + const endpoint = webviewExternalEndpoint.replace('{{uuid}}', encodedWebviewOrigin); if (endpoint[endpoint.length - 1] === '/') { return endpoint.slice(0, endpoint.length - 1); } From a3cc2f87ac360eaf9297f339624aef40a9a8074d Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 19 Jul 2022 22:06:45 -0700 Subject: [PATCH 067/197] Pick up latest version of TS for building VS Code (#155668) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 66459bcd116..c86ae74e45b 100644 --- a/package.json +++ b/package.json @@ -199,7 +199,7 @@ "style-loader": "^1.3.0", "ts-loader": "^9.2.7", "tsec": "0.1.4", - "typescript": "^4.8.0-dev.20220711", + "typescript": "^4.8.0-dev.20220719", "typescript-formatter": "7.1.0", "underscore": "^1.12.1", "util": "^0.12.4", diff --git a/yarn.lock b/yarn.lock index 4ec38e4d909..efaf40eebdd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11289,10 +11289,10 @@ typescript@^2.6.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q= -typescript@^4.8.0-dev.20220711: - version "4.8.0-dev.20220711" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.0-dev.20220711.tgz#3d4f68161716cb6cb1ea42fd0c6cc4b842f150b1" - integrity sha512-Nz1HlAkzZJ/OYZxqDEdoNV9GMq61xUss3JjveQqtdTiwhouLMa6D69C5K+P/fZD/hfrkMf/iqaF7xqVtX5KvPg== +typescript@^4.8.0-dev.20220719: + version "4.8.0-dev.20220719" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.0-dev.20220719.tgz#5481fe69ef18473d0da5ed23512d5754a2f998ef" + integrity sha512-IAZp6IDszN9iZi7R5LOqR5j0Ffy737RVQF7IefH1hNtFE+HiTjfsEYtWD2M0X/2feOCESZEKaa+GmuOVFuFhUQ== typical@^4.0.0: version "4.0.0" From 03e58be297a9603f0d59ae4eefe306578b2f0046 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 20 Jul 2022 09:03:08 +0200 Subject: [PATCH 068/197] use ResourceLabels in breadcrumbs control --- .../browser/parts/editor/breadcrumbsControl.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 15973ea64e2..51879dbf0ba 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -26,7 +26,7 @@ import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { ColorIdentifier, ColorTransform } from 'vs/platform/theme/common/colorRegistry'; import { attachBreadcrumbsStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ResourceLabel } from 'vs/workbench/browser/labels'; +import { DEFAULT_LABELS_CONTAINER, ResourceLabels } from 'vs/workbench/browser/labels'; import { BreadcrumbsConfig, IBreadcrumbsService } from 'vs/workbench/browser/parts/editor/breadcrumbs'; import { BreadcrumbsModel, FileElement, OutlineElement2 } from 'vs/workbench/browser/parts/editor/breadcrumbsModel'; import { BreadcrumbsFilePicker, BreadcrumbsOutlinePicker, BreadcrumbsPicker } from 'vs/workbench/browser/parts/editor/breadcrumbsPicker'; @@ -109,7 +109,7 @@ class FileItem extends BreadcrumbsItem { readonly model: BreadcrumbsModel, readonly element: FileElement, readonly options: IBreadcrumbsControlOptions, - @IInstantiationService private readonly _instantiationService: IInstantiationService + private readonly _labels: ResourceLabels ) { super(); } @@ -130,8 +130,8 @@ class FileItem extends BreadcrumbsItem { render(container: HTMLElement): void { // file/folder - const label = this._instantiationService.createInstance(ResourceLabel, container, {}); - label.element.setFile(this.element.uri, { + const label = this._labels.create(container); + label.setFile(this.element.uri, { hidePath: true, hideIcon: this.element.kind === FileKind.FOLDER || !this.options.showFileIcons, fileKind: this.element.kind, @@ -182,6 +182,7 @@ export class BreadcrumbsControl { private readonly _disposables = new DisposableStore(); private readonly _breadcrumbsDisposables = new DisposableStore(); + private readonly _labels: ResourceLabels; private _breadcrumbsPickerShowing = false; private _breadcrumbsPickerIgnoreOnceItem: BreadcrumbsItem | undefined; @@ -208,6 +209,8 @@ export class BreadcrumbsControl { this._cfShowIcons = BreadcrumbsConfig.Icons.bindTo(configurationService); this._cfTitleScrollbarSizing = BreadcrumbsConfig.TitleScrollbarSizing.bindTo(configurationService); + this._labels = this._instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER); + const sizing = this._cfTitleScrollbarSizing.getValue() ?? 'default'; this._widget = new BreadcrumbsWidget(this.domNode, BreadcrumbsControl.SCROLLBAR_SIZES[sizing], separatorIcon); this._widget.onDidSelectItem(this._onSelectEvent, this, this._disposables); @@ -232,6 +235,7 @@ export class BreadcrumbsControl { this._cfUseQuickPick.dispose(); this._cfShowIcons.dispose(); this._widget.dispose(); + this._labels.dispose(); this.domNode.remove(); } @@ -290,7 +294,7 @@ export class BreadcrumbsControl { showFileIcons: this._options.showFileIcons && showIcons, showSymbolIcons: this._options.showSymbolIcons && showIcons }; - const items = model.getElements().map(element => element instanceof FileElement ? new FileItem(model, element, options, this._instantiationService) : new OutlineItem(model, element, options)); + const items = model.getElements().map(element => element instanceof FileElement ? new FileItem(model, element, options, this._labels) : new OutlineItem(model, element, options)); if (items.length === 0) { this._widget.setEnabled(false); this._widget.setItems([new class extends BreadcrumbsItem { From 62075fdca13c6ff68998b79602c7f65f1d04a301 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 20 Jul 2022 09:33:05 +0200 Subject: [PATCH 069/197] don't use generator function --- src/vs/base/common/map.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 4b153336f01..3710827d559 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -715,22 +715,28 @@ export class TernarySearchTree { yield* this._entries(this._root); } - private *_entries(node: TernarySearchTreeNode | undefined): IterableIterator<[K, V]> { + private _entries(node: TernarySearchTreeNode | undefined): IterableIterator<[K, V]> { + const result: [K, V][] = []; + this._dfsEntries(node, result); + return result[Symbol.iterator](); + } + + private _dfsEntries(node: TernarySearchTreeNode | undefined, bucket: [K, V][]) { // DFS if (!node) { return; } if (node.left) { - yield* this._entries(node.left); + this._dfsEntries(node.left, bucket); } if (node.value) { - yield [node.key!, node.value]; + bucket.push([node.key!, node.value]); } if (node.mid) { - yield* this._entries(node.mid); + this._dfsEntries(node.mid, bucket); } if (node.right) { - yield* this._entries(node.right); + this._dfsEntries(node.right, bucket); } } From ab70fb7bf340c20b1a2884adb7786c8895702969 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 20 Jul 2022 09:46:22 +0200 Subject: [PATCH 070/197] ignore `data` uri paths from breadcrumbs file path info --- src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts index e542f84cff4..67668bd3093 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts @@ -17,6 +17,7 @@ import { FileKind } from 'vs/platform/files/common/files'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IOutline, IOutlineService, OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; import { IEditorPane } from 'vs/workbench/common/editor'; +import { matchesSomeScheme } from 'vs/platform/opener/common/opener'; export class FileElement { constructor( @@ -116,7 +117,7 @@ export class BreadcrumbsModel { private _initFilePathInfo(uri: URI): FileInfo { - if (uri.scheme === Schemas.untitled) { + if (matchesSomeScheme(uri, Schemas.untitled, Schemas.data)) { return { folder: undefined, path: [] From 0a8e619cfee8216c780357158b8584fe12dfb799 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 20 Jul 2022 10:41:39 +0200 Subject: [PATCH 071/197] SCM - Add an authority to the URI of the SCM input's text document (#155624) --- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 56be4b4065f..e809b5cb465 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -1789,7 +1789,8 @@ class SCMInputWidget { const uri = URI.from({ scheme: Schemas.vscode, - path: `scm/${input.repository.provider.contextValue}/${input.repository.provider.id}/input`, + authority: 'scm', + path: `/${input.repository.provider.contextValue}/${input.repository.provider.id}/input`, query }); From 28c40a970f6022e313bab06db508f8cccba30860 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 20 Jul 2022 10:42:00 +0200 Subject: [PATCH 072/197] LabelService - custom formatter function (#155641) --- src/vs/platform/label/common/label.ts | 2 +- .../workbench/contrib/scm/browser/activity.ts | 47 +++++++++++++++++++ .../contrib/scm/browser/scm.contribution.ts | 5 +- .../services/label/common/labelService.ts | 47 ++++++++++--------- .../services/label/test/browser/label.test.ts | 12 +++++ 5 files changed, 88 insertions(+), 25 deletions(-) diff --git a/src/vs/platform/label/common/label.ts b/src/vs/platform/label/common/label.ts index e5b6b8a1045..b9ee570c43e 100644 --- a/src/vs/platform/label/common/label.ts +++ b/src/vs/platform/label/common/label.ts @@ -51,7 +51,7 @@ export interface ResourceLabelFormatter { } export interface ResourceLabelFormatting { - label: string; // myLabel:/${path} + label: string | ((resource: URI) => string); // myLabel:/${path} separator: '/' | '\\' | ''; tildify?: boolean; normalizeDriveLetter?: boolean; diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index 75929726a57..57e7afbf13a 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -19,6 +19,10 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { stripIcons } from 'vs/base/common/iconLabels'; import { Schemas } from 'vs/base/common/network'; import { Iterable } from 'vs/base/common/iterator'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { URI } from 'vs/base/common/uri'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; function getCount(repository: ISCMRepository): number { if (typeof repository.provider.count === 'number') { @@ -272,3 +276,46 @@ export class SCMActiveResourceContextKeyController implements IWorkbenchContribu this.repositoryDisposables.clear(); } } + +class SCMInputTextDocumentLabelFormatter { + + readonly separator = '/'; + + readonly label = (resource: URI) => { + const match = /git\/(?scm[\d+])\/input/i.exec(resource.path); + + if (match?.groups === undefined) { + return resource.toString(); + } + + const { repositoryId } = match.groups; + const repository = this.scmService.getRepository(repositoryId); + + if (repository === undefined || repository.provider.rootUri === undefined) { + return resource.toString(); + } + + const folder = this.workspaceContextService.getWorkspaceFolder(repository.provider.rootUri); + const repositoryName = folder?.uri.toString() === repository.provider.rootUri.toString() ? folder.name : basename(repository.provider.rootUri); + + return `${repositoryName} (${repository.provider.label})`; + }; + + constructor( + @ISCMService private readonly scmService: ISCMService, + @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, + ) { } +} + +export class SCMInputTextDocumentContribution implements IWorkbenchContribution { + constructor( + @IInstantiationService instantiationService: IInstantiationService, + @ILabelService labelService: ILabelService + ) { + labelService.registerFormatter({ + scheme: Schemas.vscode, + authority: 'scm', + formatting: instantiationService.createInstance(SCMInputTextDocumentLabelFormatter) + }); + } +} diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 956e867a21f..6c0ceafb8ac 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -10,7 +10,7 @@ import { DirtyDiffWorkbenchController } from './dirtydiffDecorator'; import { VIEWLET_ID, ISCMService, VIEW_PANE_ID, ISCMProvider, ISCMViewService, REPOSITORIES_VIEW_PANE_ID } from 'vs/workbench/contrib/scm/common/scm'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; -import { SCMActiveResourceContextKeyController, SCMStatusController } from './activity'; +import { SCMActiveResourceContextKeyController, SCMInputTextDocumentContribution, SCMStatusController } from './activity'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -115,6 +115,9 @@ Registry.as(WorkbenchExtensions.Workbench) Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(SCMStatusController, LifecyclePhase.Restored); +Registry.as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(SCMInputTextDocumentContribution, LifecyclePhase.Restored); + Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ id: 'scm', order: 5, diff --git a/src/vs/workbench/services/label/common/labelService.ts b/src/vs/workbench/services/label/common/labelService.ts index 4135a251e94..b680533dabc 100644 --- a/src/vs/workbench/services/label/common/labelService.ts +++ b/src/vs/workbench/services/label/common/labelService.ts @@ -402,32 +402,33 @@ export class LabelService extends Disposable implements ILabelService { } private formatUri(resource: URI, formatting: ResourceLabelFormatting, forceNoTildify?: boolean): string { - let label = formatting.label.replace(labelMatchingRegexp, (match, token, qsToken, qsValue) => { - switch (token) { - case 'scheme': return resource.scheme; - case 'authority': return resource.authority; - case 'authoritySuffix': { - const i = resource.authority.indexOf('+'); - return i === -1 ? resource.authority : resource.authority.slice(i + 1); - } - case 'path': - return formatting.stripPathStartingSeparator - ? resource.path.slice(resource.path[0] === formatting.separator ? 1 : 0) - : resource.path; - default: { - if (qsToken === 'query') { - const { query } = resource; - if (query && query[0] === '{' && query[query.length - 1] === '}') { - try { - return JSON.parse(query)[qsValue] || ''; - } catch { } - } + let label = typeof formatting.label !== 'string' ? formatting.label(resource) : + formatting.label.replace(labelMatchingRegexp, (match, token, qsToken, qsValue) => { + switch (token) { + case 'scheme': return resource.scheme; + case 'authority': return resource.authority; + case 'authoritySuffix': { + const i = resource.authority.indexOf('+'); + return i === -1 ? resource.authority : resource.authority.slice(i + 1); } + case 'path': + return formatting.stripPathStartingSeparator + ? resource.path.slice(resource.path[0] === formatting.separator ? 1 : 0) + : resource.path; + default: { + if (qsToken === 'query') { + const { query } = resource; + if (query && query[0] === '{' && query[query.length - 1] === '}') { + try { + return JSON.parse(query)[qsValue] || ''; + } catch { } + } + } - return ''; + return ''; + } } - } - }); + }); // convert \c:\something => C:\something if (formatting.normalizeDriveLetter && hasDriveLetterIgnorePlatform(label)) { diff --git a/src/vs/workbench/services/label/test/browser/label.test.ts b/src/vs/workbench/services/label/test/browser/label.test.ts index 745055c930e..338660d225b 100644 --- a/src/vs/workbench/services/label/test/browser/label.test.ts +++ b/src/vs/workbench/services/label/test/browser/label.test.ts @@ -164,6 +164,18 @@ suite('URI Label', () => { assert.strictEqual(labelService.getUriLabel(uri1, { relative: false }), 'LABEL: /END'); }); + test('custom formatting function', function () { + labelService.registerFormatter({ + scheme: 'vscode', + formatting: { + label: (resource) => { return resource.toString(); }, + separator: '/', + } + }); + + const uri1 = URI.parse('vscode://microsoft.com/1/2/3/4/5'); + assert.strictEqual(labelService.getUriLabel(uri1), uri1.toString()); + }); test('label caching', () => { const m = new Memento('cachedResourceLabelFormatters', storageService).getMemento(StorageScope.PROFILE, StorageTarget.MACHINE); From a237707f58cdae6f7bd243149465a3d9f4373b88 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 20 Jul 2022 11:35:59 +0200 Subject: [PATCH 073/197] editors - limit length of error message (#155337) (#155705) --- .../workbench/browser/parts/editor/editorPlaceholder.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorPlaceholder.ts b/src/vs/workbench/browser/parts/editor/editorPlaceholder.ts index 0464f14265b..d7dc301565a 100644 --- a/src/vs/workbench/browser/parts/editor/editorPlaceholder.ts +++ b/src/vs/workbench/browser/parts/editor/editorPlaceholder.ts @@ -30,6 +30,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { FileChangeType, FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { isErrorWithActions, toErrorMessage } from 'vs/base/common/errorMessage'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { truncate } from 'vs/base/common/strings'; export interface IEditorPlaceholderContents { icon: string; @@ -48,6 +49,8 @@ export interface IErrorEditorPlaceholderOptions extends IEditorOptions { export abstract class EditorPlaceholder extends EditorPane { + private static readonly PLACEHOLDER_LABEL_MAX_LENGTH = 1024; + private container: HTMLElement | undefined; private scrollbar: DomScrollableElement | undefined; private inputDisposable = this._register(new MutableDisposable()); @@ -96,6 +99,7 @@ export abstract class EditorPlaceholder extends EditorPane { // Delegate to implementation for contents const disposables = new DisposableStore(); const { icon, label, actions } = await this.getContents(input, options, disposables); + const truncatedLabel = truncate(label, EditorPlaceholder.PLACEHOLDER_LABEL_MAX_LENGTH); // Icon const iconContainer = container.appendChild($('.editor-placeholder-icon-container')); @@ -105,11 +109,11 @@ export abstract class EditorPlaceholder extends EditorPane { // Label const labelContainer = container.appendChild($('.editor-placeholder-label-container')); const labelWidget = document.createElement('span'); - labelWidget.textContent = label; + labelWidget.textContent = truncatedLabel; labelContainer.appendChild(labelWidget); // ARIA label - container.setAttribute('aria-label', `${computeEditorAriaLabel(input, undefined, this.group, undefined)}, ${label}`); + container.setAttribute('aria-label', `${computeEditorAriaLabel(input, undefined, this.group, undefined)}, ${truncatedLabel}`); // Actions const actionsContainer = container.appendChild($('.editor-placeholder-actions-container')); From 2936058b31acc795e5e4bae0040be40134d771d9 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Wed, 20 Jul 2022 11:36:33 +0200 Subject: [PATCH 074/197] Fixes #154755 (#155706) --- src/vs/editor/browser/widget/codeEditorWidget.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index f4e8c0d10db..0cc2cfa02af 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -1177,9 +1177,10 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE if (!this._modelData || text.length === 0) { return; } - const startPosition = this._modelData.viewModel.getSelection().getStartPosition(); - this._modelData.viewModel.paste(text, pasteOnNewLine, multicursorText, source); - const endPosition = this._modelData.viewModel.getSelection().getStartPosition(); + const viewModel = this._modelData.viewModel; + const startPosition = viewModel.getSelection().getStartPosition(); + viewModel.paste(text, pasteOnNewLine, multicursorText, source); + const endPosition = viewModel.getSelection().getStartPosition(); if (source === 'keyboard') { this._onDidPaste.fire({ range: new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column), From abf921bd9cce74a881608b93b76b5617251af82a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Jul 2022 11:38:23 +0200 Subject: [PATCH 075/197] Bump terser from 4.8.0 to 4.8.1 (#155680) Bumps [terser](https://github.com/terser/terser) from 4.8.0 to 4.8.1. - [Release notes](https://github.com/terser/terser/releases) - [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md) - [Commits](https://github.com/terser/terser/commits) --- updated-dependencies: - dependency-name: terser dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 38 ++++++++++++++------------------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/yarn.lock b/yarn.lock index efaf40eebdd..145260f72ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2684,9 +2684,9 @@ buffer-fill@^1.0.0: integrity sha1-+PeLdniYiO858gXNY39o5wISKyw= buffer-from@^1.0.0: - 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== + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== buffer-xor@^1.0.3: version "1.0.3" @@ -3243,27 +3243,17 @@ command-line-args@^5.2.1: lodash.camelcase "^4.3.0" typical "^4.0.0" -commander@*, commander@^2.11.0: - version "2.15.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.0.tgz#ad2a23a1c3b036e392469b8012cec6b33b4c1322" - integrity sha512-7B1ilBwtYSbetCgTY1NJFg+gVpestg0fdA1MhC1Vs4ssyfSXnCAjFr+QcQM9/RedXC0EaUx1sG8Smgw2VfgKEg== +commander@*, commander@8.3.0, commander@^8.2.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" + integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== commander@2.11.x: version "2.11.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" integrity sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ== -commander@8.3.0, commander@^8.2.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" - integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== - -commander@^2.19.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" - integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== - -commander@^2.20.0, commander@^2.8.1: +commander@^2.11.0, commander@^2.19.0, commander@^2.20.0, commander@^2.8.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== @@ -10309,9 +10299,9 @@ source-map-support@^0.3.2: source-map "0.1.32" source-map-support@~0.5.12, source-map-support@~0.5.19: - version "0.5.19" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" - integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -10938,9 +10928,9 @@ terser-webpack-plugin@^5.1.3: terser "^5.7.0" terser@^4.1.2: - version "4.8.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" - integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== + version "4.8.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.1.tgz#a00e5634562de2239fd404c649051bf6fc21144f" + integrity sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw== dependencies: commander "^2.20.0" source-map "~0.6.1" From c79556aed38346a6d5f515d5006345c49777b313 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Wed, 20 Jul 2022 12:04:01 +0200 Subject: [PATCH 076/197] Also use the first line which has a begin state when tokenizing the viewport (#155698) --- src/vs/editor/common/model/textModelTokens.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/common/model/textModelTokens.ts b/src/vs/editor/common/model/textModelTokens.ts index b504fc14366..120da61f3f5 100644 --- a/src/vs/editor/common/model/textModelTokens.ts +++ b/src/vs/editor/common/model/textModelTokens.ts @@ -472,12 +472,12 @@ export class TextModelTokenization extends Disposable { } if (newNonWhitespaceIndex < nonWhitespaceColumn) { + fakeLines.push(this._textModel.getLineContent(i)); + nonWhitespaceColumn = newNonWhitespaceIndex; initialState = this._tokenizationStateStore.getBeginState(i - 1); if (initialState) { break; } - fakeLines.push(this._textModel.getLineContent(i)); - nonWhitespaceColumn = newNonWhitespaceIndex; } } From 7cdaa5792853b34e99c334ee30c5f8220d0944f7 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 20 Jul 2022 12:36:08 +0200 Subject: [PATCH 077/197] also default to opening merge editor when conflict is about adding (#155711) fixes https://github.com/microsoft/vscode/issues/153737#issuecomment-1189357241 --- extensions/git/src/repository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 995f9579e99..53ae4452973 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -617,7 +617,7 @@ class ResourceCommandResolver { if (!resource.leftUri) { const bothModified = resource.type === Status.BOTH_MODIFIED; - if (resource.rightUri && bothModified && workspace.getConfiguration('git').get('mergeEditor', false)) { + if (resource.rightUri && workspace.getConfiguration('git').get('mergeEditor', false) && (bothModified || resource.type === Status.BOTH_ADDED)) { return { command: '_git.openMergeEditor', title: localize('open.merge', "Open Merge"), From 9794671356c721f704a3dd5cc8ca57ba0a45dd85 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Wed, 20 Jul 2022 13:12:05 +0200 Subject: [PATCH 078/197] Parse markers regular expressions like other regular expressions from the language configuration file (#155688) Fixes #154766: Parse markers regular expressions like other regular expressions from the language configuration file --- .../languageConfigurationExtensionPoint.ts | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts b/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts index bbdbbd99349..dd288894ba7 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts @@ -8,7 +8,7 @@ import { ParseError, parse, getNodeType } from 'vs/base/common/json'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as types from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { CharacterPair, CommentRule, EnterAction, ExplicitLanguageConfiguration, FoldingRules, IAutoClosingPair, IAutoClosingPairConditional, IndentAction, IndentationRule, OnEnterRule } from 'vs/editor/common/languages/languageConfiguration'; +import { CharacterPair, CommentRule, EnterAction, ExplicitLanguageConfiguration, FoldingMarkers, FoldingRules, IAutoClosingPair, IAutoClosingPairConditional, IndentAction, IndentationRule, OnEnterRule } from 'vs/editor/common/languages/languageConfiguration'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; @@ -45,6 +45,9 @@ interface IOnEnterRule { action: IEnterAction; } +/** + * Serialized form of a language configuration + */ interface ILanguageConfiguration { comments?: CommentRule; brackets?: CharacterPair[]; @@ -53,7 +56,13 @@ interface ILanguageConfiguration { colorizedBracketPairs?: Array; wordPattern?: string | IRegExp; indentationRules?: IIndentationRules; - folding?: FoldingRules; + folding?: { + offSide?: boolean; + markers?: { + start?: string | IRegExp; + end?: string | IRegExp; + }; + }; autoCloseBefore?: string; onEnterRules?: IOnEnterRule[]; } @@ -393,10 +402,13 @@ export class LanguageConfigurationFileHandler extends Disposable { const indentationRules = (configuration.indentationRules ? this._mapIndentationRules(languageId, configuration.indentationRules) : undefined); let folding: FoldingRules | undefined = undefined; if (configuration.folding) { - const markers = configuration.folding.markers; + const rawMarkers = configuration.folding.markers; + const startMarker = (rawMarkers && rawMarkers.start ? this._parseRegex(languageId, `folding.markers.start`, rawMarkers.start) : undefined); + const endMarker = (rawMarkers && rawMarkers.end ? this._parseRegex(languageId, `folding.markers.end`, rawMarkers.end) : undefined); + const markers: FoldingMarkers | undefined = (startMarker && endMarker ? { start: startMarker, end: endMarker } : undefined); folding = { offSide: configuration.folding.offSide, - markers: markers ? { start: new RegExp(markers.start), end: new RegExp(markers.end) } : undefined + markers }; } const onEnterRules = this._extractValidOnEnterRules(languageId, configuration); From ce5d92e99851c0d1136b083a04134157a1604965 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 20 Jul 2022 13:49:24 +0200 Subject: [PATCH 079/197] use commit-icon for description, tweak codicon font-size (#155714) https://github.com/microsoft/vscode/issues/150863 --- extensions/git/src/commands.ts | 4 ++-- .../contrib/mergeEditor/browser/view/media/mergeEditor.css | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index e0b7b5d4670..b1bb4907684 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -432,11 +432,11 @@ export class CommandCenter { ]); // ours (current branch and commit) ours.detail = head.refNames.map(s => s.replace(/^HEAD ->/, '')).join(', '); - ours.description = head.hash.substring(0, 7); + ours.description = '$(git-commit) ' + head.hash.substring(0, 7); // theirs theirs.detail = rebaseOrMergeHead.refNames.join(', '); - theirs.description = rebaseOrMergeHead.hash.substring(0, 7); + theirs.description = '$(git-commit) ' + rebaseOrMergeHead.hash.substring(0, 7); } catch (error) { // not so bad, can continue with just uris diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css b/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css index e9c0ada7df3..e8141942cc9 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css @@ -31,6 +31,10 @@ flex-shrink: 0; } +.monaco-workbench .merge-editor .code-view > .title>SPAN.detail .codicon { + font-size: 13px; +} + .monaco-workbench .merge-editor .code-view > .container { display: flex; flex-direction: row; From 28be5d9906a0407da7773ba6f587d46aebda4183 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 20 Jul 2022 14:01:52 +0200 Subject: [PATCH 080/197] Configurations ${WorkSpace} autocomplete broken after update (#155713) Configurations ${WorkSpace} autocomplete broken after update. Fixes #155638 --- .../src/configurationEditingMain.ts | 4 +- .../src/settingsDocumentHelper.ts | 4 +- .../src/test/completion.test.ts | 40 +++++++++++++++++++ 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/extensions/configuration-editing/src/configurationEditingMain.ts b/extensions/configuration-editing/src/configurationEditingMain.ts index f63aa685cbe..224a57370b4 100644 --- a/extensions/configuration-editing/src/configurationEditingMain.ts +++ b/extensions/configuration-editing/src/configurationEditingMain.ts @@ -40,8 +40,8 @@ function registerVariableCompletions(pattern: string): vscode.Disposable { provideCompletionItems(document, position, _token) { const location = getLocation(document.getText(), document.offsetAt(position)); if (isCompletingInsidePropertyStringValue(document, location, position)) { - let range = document.getWordRangeAtPosition(position, /\$\{[^\}]*\}/); - if (!range || range.end.isEqual(position) || range.start.isEqual(position)) { + let range = document.getWordRangeAtPosition(position, /\$\{[^"\}]*\}?/); + if (!range || range.start.isEqual(position) || range.end.isEqual(position) && document.getText(range).endsWith('}')) { range = new vscode.Range(position, position); } diff --git a/extensions/configuration-editing/src/settingsDocumentHelper.ts b/extensions/configuration-editing/src/settingsDocumentHelper.ts index a4c0e3945d8..ab41c8220c2 100644 --- a/extensions/configuration-editing/src/settingsDocumentHelper.ts +++ b/extensions/configuration-editing/src/settingsDocumentHelper.ts @@ -96,8 +96,8 @@ export class SettingsDocument { return completions; } - let range = this.document.getWordRangeAtPosition(pos, /\$\{[^\}]*\}/); - if (!range || range.end.isEqual(pos) || range.start.isEqual(pos)) { + let range = this.document.getWordRangeAtPosition(pos, /\$\{[^"\}]*\}?/); + if (!range || range.start.isEqual(pos) || range.end.isEqual(pos) && this.document.getText(range).endsWith('}')) { range = new vscode.Range(pos, pos); } diff --git a/extensions/configuration-editing/src/test/completion.test.ts b/extensions/configuration-editing/src/test/completion.test.ts index 3df7024fa12..e5aec5fc46a 100644 --- a/extensions/configuration-editing/src/test/completion.test.ts +++ b/extensions/configuration-editing/src/test/completion.test.ts @@ -73,6 +73,20 @@ suite('Completions in settings.json', () => { const expected = { label: '${activeEditorMedium}', resultText }; await testCompletion(testFile, 'jsonc', content, expected); } + { // replacing a partial variable + const content = [ + '{', + ' "window.title": "${a|"', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "window.title": "${dirty}"', + '}', + ].join('\n'); + const expected = { label: '${dirty}', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } { // inserting a literal const content = [ '{', @@ -382,6 +396,32 @@ suite('Completions in launch.json', () => { const expected = { label: '${cwd}', resultText }; await testCompletion(testFile, 'jsonc', content, expected); } + { + const content = [ + '{', + ' "version": "0.2.0",', + ' "configurations": [', + ' {', + ' "name": "Do It",', + ' "program": "${workspace|"', + ' }', + ' ]', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "version": "0.2.0",', + ' "configurations": [', + ' {', + ' "name": "Do It",', + ' "program": "${cwd}"', + ' }', + ' ]', + '}', + ].join('\n'); + const expected = { label: '${cwd}', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } }); }); From fa155a39b75c909c5968414d88eb405e21bce344 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 20 Jul 2022 14:32:09 +0200 Subject: [PATCH 081/197] add more GDPR comments, https://github.com/microsoft/vscode-internalbacklog/issues/2762 (#155724) --- .../api/common/extHostExtensionActivator.ts | 8 ++++---- .../api/common/extHostExtensionService.ts | 20 ++++++++++--------- .../api/common/extHostRequireInterceptor.ts | 9 ++++++--- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/api/common/extHostExtensionActivator.ts b/src/vs/workbench/api/common/extHostExtensionActivator.ts index a0a16865ab2..38856e517e8 100644 --- a/src/vs/workbench/api/common/extHostExtensionActivator.ts +++ b/src/vs/workbench/api/common/extHostExtensionActivator.ts @@ -28,10 +28,10 @@ export interface IExtensionAPI { } export type ExtensionActivationTimesFragment = { - startup?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true }; - codeLoadingTime?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true }; - activateCallTime?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true }; - activateResolvedTime?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true }; + startup?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Activation occurred during startup' }; + codeLoadingTime?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Time it took to load the extension\'s code' }; + activateCallTime?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Time it took to call activate' }; + activateResolvedTime?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Time it took for async-activation to finish' }; }; export class ExtensionActivationTimes { diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 337929b481d..ac865608116 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -62,14 +62,14 @@ export interface IHostUtils { } type TelemetryActivationEventFragment = { - id: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; - name: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; - extensionVersion: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; - publisherDisplayName: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - activationEvents: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - isBuiltin: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; - reason: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - reasonId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; + id: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The identifier of an extension' }; + name: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The name of the extension' }; + extensionVersion: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The version of the extension' }; + publisherDisplayName: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The publisher of the extension' }; + activationEvents: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'All activation events of the extension' }; + isBuiltin: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'If the extension is builtin or git installed' }; + reason: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The activation event' }; + reasonId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The identifier of the activation event' }; }; export abstract class AbstractExtHostExtensionService extends Disposable implements ExtHostExtensionServiceShape { @@ -425,7 +425,8 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme const event = getTelemetryActivationEvent(extensionDescription, reason); type ExtensionActivationTimesClassification = { owner: 'jrieken'; - outcome: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + comment: 'Timestamps for extension activation'; + outcome: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Did extension activation succeed or fail' }; } & TelemetryActivationEventFragment & ExtensionActivationTimesFragment; type ExtensionActivationTimesEvent = { @@ -450,6 +451,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme const event = getTelemetryActivationEvent(extensionDescription, reason); type ActivatePluginClassification = { owner: 'jrieken'; + comment: 'Data about how/why an extension was activated'; } & TelemetryActivationEventFragment; this._mainThreadTelemetryProxy.$publicLog2('activatePlugin', event); const entryPoint = this._getEntryPoint(extensionDescription); diff --git a/src/vs/workbench/api/common/extHostRequireInterceptor.ts b/src/vs/workbench/api/common/extHostRequireInterceptor.ts index 46f3e072488..2a176ad3a91 100644 --- a/src/vs/workbench/api/common/extHostRequireInterceptor.ts +++ b/src/vs/workbench/api/common/extHostRequireInterceptor.ts @@ -250,7 +250,8 @@ class KeytarNodeModuleFactory implements INodeModuleFactory { const ext = this._extensionPaths.findSubstr(parent); type ShimmingKeytarClassification = { owner: 'jrieken'; - extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + comment: 'Know when the keytar-shim was used'; + extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension is question' }; }; this._mainThreadTelemetry.$publicLog2<{ extension: string }, ShimmingKeytarClassification>('shimming.keytar', { extension: ext?.identifier.value ?? 'unknown_extension' }); return this._impl; @@ -348,7 +349,8 @@ class OpenNodeModuleFactory implements INodeModuleFactory { } type ShimmingOpenClassification = { owner: 'jrieken'; - extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + comment: 'Know when the open-shim was used'; + extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension is question' }; }; this._mainThreadTelemetry.$publicLog2<{ extension: string }, ShimmingOpenClassification>('shimming.open', { extension: this._extensionId }); } @@ -359,7 +361,8 @@ class OpenNodeModuleFactory implements INodeModuleFactory { } type ShimmingOpenCallNoForwardClassification = { owner: 'jrieken'; - extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + comment: 'Know when the open-shim was used'; + extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension is question' }; }; this._mainThreadTelemetry.$publicLog2<{ extension: string }, ShimmingOpenCallNoForwardClassification>('shimming.open.call.noForward', { extension: this._extensionId }); } From d0769c7e7f8cfbf6481c9196a197021a833176fc Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 20 Jul 2022 14:38:10 +0200 Subject: [PATCH 082/197] [html] update service and fix url resolving (#155725) --- extensions/html-language-features/server/package.json | 2 +- .../server/src/utils/documentContext.ts | 4 ++++ extensions/html-language-features/server/yarn.lock | 8 ++++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index 322f6b81f85..fc9b3634c9a 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -10,7 +10,7 @@ "main": "./out/node/htmlServerMain", "dependencies": { "vscode-css-languageservice": "^6.0.1", - "vscode-html-languageservice": "^5.0.0", + "vscode-html-languageservice": "^5.0.1", "vscode-languageserver": "^8.0.2-next.4", "vscode-languageserver-textdocument": "^1.0.4", "vscode-nls": "^5.0.1", diff --git a/extensions/html-language-features/server/src/utils/documentContext.ts b/extensions/html-language-features/server/src/utils/documentContext.ts index fb1a6622674..88e3f032885 100644 --- a/extensions/html-language-features/server/src/utils/documentContext.ts +++ b/extensions/html-language-features/server/src/utils/documentContext.ts @@ -24,6 +24,10 @@ export function getDocumentContext(documentUri: string, workspaceFolders: Worksp return { resolveReference: (ref: string, base = documentUri) => { + if (ref.match(/^\w[\w\d+.-]*:/)) { + // starts with a schema + return ref; + } if (ref[0] === '/') { // resolve absolute path against the current workspace folder const folderUri = getRootFolder(); if (folderUri) { diff --git a/extensions/html-language-features/server/yarn.lock b/extensions/html-language-features/server/yarn.lock index 6f3a8b7b5b1..7f6ae130b3f 100644 --- a/extensions/html-language-features/server/yarn.lock +++ b/extensions/html-language-features/server/yarn.lock @@ -22,10 +22,10 @@ vscode-css-languageservice@^6.0.1: vscode-nls "^5.0.1" vscode-uri "^3.0.3" -vscode-html-languageservice@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-5.0.0.tgz#c68613f836d7fcff125183d78e6f1f0ff326fa55" - integrity sha512-KJG13z54aLszskp3ETf8b1EKDypr2Sf5RUsfR6OXmKqEl2ZUfyIxsWz4gbJWjPzoJZx/bGH0ZXVwxJ1rg8OKRQ== +vscode-html-languageservice@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-5.0.1.tgz#bdf7847d27a453a9e98ae2836ead7594784c5c1c" + integrity sha512-OYsyn5HGAhxs0OIG+M0jc34WnftLtD67Wg7+TfrYwvf0waOkkr13zUqtdrVm2JPNQ6fJx+qnuM+vTbq7o1dCdQ== dependencies: vscode-languageserver-textdocument "^1.0.4" vscode-languageserver-types "^3.17.1" From 5465f255388ac2987d559f91e8cd837a9d80d696 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Wed, 20 Jul 2022 08:57:42 -0400 Subject: [PATCH 083/197] Update new file to create a new file (#155544) * Update new file to create a new file * Address PR feedback --- .../contrib/files/browser/fileCommands.ts | 39 ++++++++++++++++--- .../contrib/files/browser/fileConstants.ts | 1 + .../common/newFile.contribution.ts | 8 ++-- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index d3ae1ad9f6b..849c3016594 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -47,7 +47,8 @@ import { hash } from 'vs/base/common/hash'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { ViewContainerLocation } from 'vs/workbench/common/views'; -import { OPEN_TO_SIDE_COMMAND_ID, COMPARE_WITH_SAVED_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, COMPARE_SELECTED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, COPY_PATH_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_WITH_EXPLORER_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_AS_COMMAND_ID, SAVE_ALL_COMMAND_ID, SAVE_ALL_IN_GROUP_COMMAND_ID, SAVE_FILES_COMMAND_ID, REVERT_FILE_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, PREVIOUS_COMPRESSED_FOLDER, NEXT_COMPRESSED_FOLDER, FIRST_COMPRESSED_FOLDER, LAST_COMPRESSED_FOLDER, NEW_UNTITLED_FILE_COMMAND_ID, NEW_UNTITLED_FILE_LABEL } from './fileConstants'; +import { OPEN_TO_SIDE_COMMAND_ID, COMPARE_WITH_SAVED_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, COMPARE_SELECTED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, COPY_PATH_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_WITH_EXPLORER_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_AS_COMMAND_ID, SAVE_ALL_COMMAND_ID, SAVE_ALL_IN_GROUP_COMMAND_ID, SAVE_FILES_COMMAND_ID, REVERT_FILE_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, PREVIOUS_COMPRESSED_FOLDER, NEXT_COMPRESSED_FOLDER, FIRST_COMPRESSED_FOLDER, LAST_COMPRESSED_FOLDER, NEW_UNTITLED_FILE_COMMAND_ID, NEW_UNTITLED_FILE_LABEL, NEW_FILE_COMMAND_ID } from './fileConstants'; +import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; export const openWindowCommand = (accessor: ServicesAccessor, toOpen: IWindowOpenable[], options?: IOpenWindowOptions) => { if (Array.isArray(toOpen)) { @@ -639,19 +640,45 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ 'languageId': { 'type': 'string' }, - 'path': { - 'type': 'string' - } } } } ] }, - handler: async (accessor, args?: { languageId?: string; viewType?: string; path?: string }) => { + handler: async (accessor, args?: { languageId?: string; viewType?: string }) => { const editorService = accessor.get(IEditorService); await editorService.openEditor({ - resource: args?.path ? URI.from({ scheme: Schemas.untitled, path: args.path }) : undefined, + resource: undefined, + options: { + override: args?.viewType, + pinned: true + }, + languageId: args?.languageId, + }); + } +}); + +CommandsRegistry.registerCommand({ + id: NEW_FILE_COMMAND_ID, + handler: async (accessor, args?: { languageId?: string; viewType?: string; fileName?: string }) => { + const editorService = accessor.get(IEditorService); + const dialogService = accessor.get(IFileDialogService); + const fileService = accessor.get(IFileService); + + const createFileLocalized = nls.localize('newFileCommand.saveLabel', "Create File"); + const defaultFileUri = joinPath(await dialogService.defaultFilePath(), args?.fileName ?? 'Untitled.txt'); + + const saveUri = await dialogService.showSaveDialog({ saveLabel: createFileLocalized, title: createFileLocalized, defaultUri: defaultFileUri }); + + if (!saveUri) { + return; + } + + await fileService.createFile(saveUri, undefined, { overwrite: true }); + + await editorService.openEditor({ + resource: saveUri, options: { override: args?.viewType, pinned: true diff --git a/src/vs/workbench/contrib/files/browser/fileConstants.ts b/src/vs/workbench/contrib/files/browser/fileConstants.ts index 4e65b120256..4b9ff14454d 100644 --- a/src/vs/workbench/contrib/files/browser/fileConstants.ts +++ b/src/vs/workbench/contrib/files/browser/fileConstants.ts @@ -46,3 +46,4 @@ export const FIRST_COMPRESSED_FOLDER = 'firstCompressedFolder'; export const LAST_COMPRESSED_FOLDER = 'lastCompressedFolder'; export const NEW_UNTITLED_FILE_COMMAND_ID = 'workbench.action.files.newUntitledFile'; export const NEW_UNTITLED_FILE_LABEL = nls.localize('newUntitledFile', "New Untitled File"); +export const NEW_FILE_COMMAND_ID = 'workbench.action.files.newFile'; diff --git a/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts b/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts index 9bbe2ed1d27..9dc61c27381 100644 --- a/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts +++ b/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts @@ -101,7 +101,9 @@ class NewFileTemplatesManager extends Disposable { const disposables = new DisposableStore(); const qp = this.quickInputService.createQuickPick(); - qp.title = localize('createNew', "Create New..."); + qp.title = localize('selectFileType', "Select File Type..."); + qp.placeholder = qp.title; + qp.sortByLabel = false; qp.matchOnDetail = true; qp.matchOnDescription = true; @@ -168,8 +170,8 @@ class NewFileTemplatesManager extends Disposable { return; } const currentTextEntry: NewFileItem = { - commandID: 'workbench.action.files.newUntitledFile', - commandArgs: { languageId: undefined, viewType: undefined, path: val }, + commandID: 'workbench.action.files.newFile', + commandArgs: { languageId: undefined, viewType: undefined, fileName: val }, title: localize('miNewFileWithName', "New File ({0})", val), group: 'file', from: builtInSource, From 60f09d6f7d1733722b5c14dc5bb15e35ca9260d7 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 20 Jul 2022 06:02:48 -0700 Subject: [PATCH 084/197] Adopt argsCanBeInterpretedByShell from DAP (#155663) For #149910 --- .../workbench/api/node/extHostDebugService.ts | 2 +- .../contrib/debug/browser/debugSession.ts | 3 +- .../contrib/debug/common/debugProtocol.d.ts | 675 +++++++++--------- .../workbench/contrib/debug/node/terminals.ts | 11 +- .../contrib/debug/test/node/terminals.test.ts | 74 +- 5 files changed, 405 insertions(+), 360 deletions(-) diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index d2127334678..f8d52778b0a 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -134,7 +134,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { } } - const command = prepareCommand(shell, args.args, cwdForPrepareCommand, args.env); + const command = prepareCommand(shell, args.args, !!args.argsCanBeInterpretedByShell, cwdForPrepareCommand, args.env); terminal.sendText(command); // Mark terminal as unused when its session ends, see #112055 diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 29a568d478b..99db6b2a7a1 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -296,7 +296,8 @@ export class DebugSession implements IDebugSession { locale: platform.locale, supportsProgressReporting: true, // #92253 supportsInvalidatedEvent: true, // #106745 - supportsMemoryReferences: true //#129684 + supportsMemoryReferences: true, //#129684 + supportsArgsCanBeInterpretedByShell: true // #149910 }); this.initialized = true; diff --git a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts index b5a858f7e52..3e4b703689a 100644 --- a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts +++ b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts @@ -10,7 +10,7 @@ declare module DebugProtocol { /** Base class of requests, responses, and events. */ interface ProtocolMessage { - /** Sequence number (also known as message ID). For protocol messages of type 'request' this ID can be used to cancel the request. */ + /** Sequence number of the message (also known as message ID). The `seq` for the first message sent by a client or debug adapter is 1, and for each subsequent message is 1 greater than the previous message sent by that actor. `seq` can be used to order requests, responses, and events, and to associate requests with their corresponding responses. For protocol messages of type `request` the sequence number can be used to cancel the request. */ seq: number; /** Message type. Values: 'request', 'response', 'event', etc. @@ -42,13 +42,13 @@ declare module DebugProtocol { /** Sequence number of the corresponding request. */ request_seq: number; /** Outcome of the request. - If true, the request was successful and the 'body' attribute may contain the result of the request. - If the value is false, the attribute 'message' contains the error in short form and the 'body' may contain additional information (see 'ErrorResponse.body.error'). + If true, the request was successful and the `body` attribute may contain the result of the request. + If the value is false, the attribute `message` contains the error in short form and the `body` may contain additional information (see `ErrorResponse.body.error`). */ success: boolean; /** The command requested. */ command: string; - /** Contains the raw error in short form if 'success' is false. + /** Contains the raw error in short form if `success` is false. This raw error might be interpreted by the client and is not shown in the UI. Some predefined values exist. Values: @@ -60,7 +60,7 @@ declare module DebugProtocol { body?: any; } - /** On error (whenever 'success' is false), the body can provide more details. */ + /** On error (whenever `success` is false), the body can provide more details. */ interface ErrorResponse extends Response { body: { /** An optional, structured error message. */ @@ -69,48 +69,47 @@ declare module DebugProtocol { } /** Cancel request; value of command field is 'cancel'. - The 'cancel' request is used by the client in two situations: + The `cancel` request is used by the client in two situations: - to indicate that it is no longer interested in the result produced by a specific request issued earlier - - to cancel a progress sequence. Clients should only call this request if the capability 'supportsCancelRequest' is true. + - to cancel a progress sequence. Clients should only call this request if the corresponding capability `supportsCancelRequest` is true. This request has a hint characteristic: a debug adapter can only be expected to make a 'best effort' in honouring this request but there are no guarantees. - The 'cancel' request may return an error if it could not cancel an operation but a client should refrain from presenting this error to end users. - A client should only call this request if the capability 'supportsCancelRequest' is true. - The request that got cancelled still needs to send a response back. This can either be a normal result ('success' attribute true) or an error response ('success' attribute false and the 'message' set to 'cancelled'). + The `cancel` request may return an error if it could not cancel an operation but a client should refrain from presenting this error to end users. + The request that got cancelled still needs to send a response back. This can either be a normal result (`success` attribute true) or an error response (`success` attribute false and the `message` set to `cancelled`). Returning partial results from a cancelled request is possible but please note that a client has no generic way for detecting that a response is partial or not. - The progress that got cancelled still needs to send a 'progressEnd' event back. - A client should not assume that progress just got cancelled after sending the 'cancel' request. + The progress that got cancelled still needs to send a `progressEnd` event back. + A client should not assume that progress just got cancelled after sending the `cancel` request. */ interface CancelRequest extends Request { // command: 'cancel'; arguments?: CancelArguments; } - /** Arguments for 'cancel' request. */ + /** Arguments for `cancel` request. */ interface CancelArguments { - /** The ID (attribute 'seq') of the request to cancel. If missing no request is cancelled. - Both a 'requestId' and a 'progressId' can be specified in one request. + /** The ID (attribute `seq`) of the request to cancel. If missing no request is cancelled. + Both a `requestId` and a `progressId` can be specified in one request. */ requestId?: number; - /** The ID (attribute 'progressId') of the progress to cancel. If missing no progress is cancelled. - Both a 'requestId' and a 'progressId' can be specified in one request. + /** The ID (attribute `progressId`) of the progress to cancel. If missing no progress is cancelled. + Both a `requestId` and a `progressId` can be specified in one request. */ progressId?: string; } - /** Response to 'cancel' request. This is just an acknowledgement, so no body field is required. */ + /** Response to `cancel` request. This is just an acknowledgement, so no body field is required. */ interface CancelResponse extends Response { } /** Event message for 'initialized' event type. This event indicates that the debug adapter is ready to accept configuration requests (e.g. SetBreakpointsRequest, SetExceptionBreakpointsRequest). - A debug adapter is expected to send this event when it is ready to accept configuration requests (but not before the 'initialize' request has finished). + A debug adapter is expected to send this event when it is ready to accept configuration requests (but not before the `initialize` request has finished). The sequence of events/requests is as follows: - - adapters sends 'initialized' event (after the 'initialize' request has returned) - - client sends zero or more 'setBreakpoints' requests - - client sends one 'setFunctionBreakpoints' request (if capability 'supportsFunctionBreakpoints' is true) - - client sends a 'setExceptionBreakpoints' request if one or more 'exceptionBreakpointFilters' have been defined (or if 'supportsConfigurationDoneRequest' is not true) + - adapters sends `initialized` event (after the `initialize` request has returned) + - client sends zero or more `setBreakpoints` requests + - client sends one `setFunctionBreakpoints` request (if corresponding capability `supportsFunctionBreakpoints` is true) + - client sends a `setExceptionBreakpoints` request if one or more `exceptionBreakpointFilters` have been defined (or if `supportsConfigurationDoneRequest` is not true) - client sends other future configuration requests - - client sends one 'configurationDone' request to indicate the end of the configuration. + - client sends one `configurationDone` request to indicate the end of the configuration. */ interface InitializedEvent extends Event { // event: 'initialized'; @@ -124,7 +123,7 @@ declare module DebugProtocol { // event: 'stopped'; body: { /** The reason for the event. - For backward compatibility this string is shown in the UI if the 'description' attribute is missing (but it must not be translated). + For backward compatibility this string is shown in the UI if the `description` attribute is missing (but it must not be translated). Values: 'step', 'breakpoint', 'exception', 'pause', 'entry', 'goto', 'function breakpoint', 'data breakpoint', 'instruction breakpoint', etc. */ reason: 'step' | 'breakpoint' | 'exception' | 'pause' | 'entry' | 'goto' | 'function breakpoint' | 'data breakpoint' | 'instruction breakpoint' | string; @@ -134,14 +133,14 @@ declare module DebugProtocol { threadId?: number; /** A value of true hints to the client that this event should not change the focus. */ preserveFocusHint?: boolean; - /** Additional information. E.g. if reason is 'exception', text contains the exception name. This string is shown in the UI. */ + /** Additional information. E.g. if reason is `exception`, text contains the exception name. This string is shown in the UI. */ text?: string; - /** If 'allThreadsStopped' is true, a debug adapter can announce that all threads have stopped. + /** If `allThreadsStopped` is true, a debug adapter can announce that all threads have stopped. - The client should use this information to enable that all threads can be expanded to access their stacktraces. - If the attribute is missing or false, only the thread with the given threadId can be expanded. */ allThreadsStopped?: boolean; - /** Ids of the breakpoints that triggered the event. In most cases there will be only a single breakpoint but here are some examples for multiple breakpoints: + /** Ids of the breakpoints that triggered the event. In most cases there is only a single breakpoint but here are some examples for multiple breakpoints: - Different types of breakpoints map to the same location. - Multiple source breakpoints get collapsed to the same instruction by the compiler/runtime. - Multiple function breakpoints with different function names map to the same location. @@ -152,15 +151,15 @@ declare module DebugProtocol { /** Event message for 'continued' event type. The event indicates that the execution of the debuggee has continued. - Please note: a debug adapter is not expected to send this event in response to a request that implies that execution continues, e.g. 'launch' or 'continue'. - It is only necessary to send a 'continued' event if there was no previous request that implied this. + Please note: a debug adapter is not expected to send this event in response to a request that implies that execution continues, e.g. `launch` or `continue`. + It is only necessary to send a `continued` event if there was no previous request that implied this. */ interface ContinuedEvent extends Event { // event: 'continued'; body: { /** The thread which was continued. */ threadId: number; - /** If 'allThreadsContinued' is true, a debug adapter can announce that all threads have continued. */ + /** If `allThreadsContinued` is true, a debug adapter can announce that all threads have continued. */ allThreadsContinued?: boolean; }; } @@ -182,8 +181,8 @@ declare module DebugProtocol { interface TerminatedEvent extends Event { // event: 'terminated'; body?: { - /** A debug adapter may set 'restart' to true (or to an arbitrary object) to request that the front end restarts the session. - The value is not interpreted by the client and passed unmodified as an attribute '__restart' to the 'launch' and 'attach' requests. + /** A debug adapter may set `restart` to true (or to an arbitrary object) to request that the front end restarts the session. + The value is not interpreted by the client and passed unmodified as an attribute `__restart` to the `launch` and `attach` requests. */ restart?: any; }; @@ -210,10 +209,10 @@ declare module DebugProtocol { interface OutputEvent extends Event { // event: 'output'; body: { - /** The output category. If not specified or if the category is not understood by the client, 'console' is assumed. + /** The output category. If not specified or if the category is not understood by the client, `console` is assumed. Values: 'console': Show the output in the client's default message UI, e.g. a 'debug console'. This category should only be used for informational output from the debugger (as opposed to the debuggee). - 'important': A hint for the client to show the output in the client's UI for important and highly visible information, e.g. as a popup notification. This category should only be used for important messages from the debugger (as opposed to the debuggee). Since this category value is a hint, clients might ignore the hint and assume the 'console' category. + 'important': A hint for the client to show the output in the client's UI for important and highly visible information, e.g. as a popup notification. This category should only be used for important messages from the debugger (as opposed to the debuggee). Since this category value is a hint, clients might ignore the hint and assume the `console` category. 'stdout': Show the output as normal program output from the debuggee. 'stderr': Show the output as error program output from the debuggee. 'telemetry': Send the output to telemetry instead of showing it to the user. @@ -224,14 +223,14 @@ declare module DebugProtocol { output: string; /** Support for keeping an output log organized by grouping related messages. 'start': Start a new group in expanded mode. Subsequent output events are members of the group and should be shown indented. - The 'output' attribute becomes the name of the group and is not indented. + The `output` attribute becomes the name of the group and is not indented. 'startCollapsed': Start a new group in collapsed mode. Subsequent output events are members of the group and should be shown indented (as soon as the group is expanded). - The 'output' attribute becomes the name of the group and is not indented. + The `output` attribute becomes the name of the group and is not indented. 'end': End the current group and decrease the indentation of subsequent output events. - A nonempty 'output' attribute is shown as the unindented end of the group. + A nonempty `output` attribute is shown as the unindented end of the group. */ group?: 'start' | 'startCollapsed' | 'end'; - /** If an attribute 'variablesReference' exists and its value is > 0, the output contains objects which can be retrieved by passing 'variablesReference' to the 'variables' request. The value should be less than or equal to 2147483647 (2^31-1). */ + /** If an attribute `variablesReference` exists and its value is > 0, the output contains objects which can be retrieved by passing `variablesReference` to the `variables` request. The value should be less than or equal to 2147483647 (2^31-1). */ variablesReference?: number; /** An optional source location where the output was produced. */ source?: Source; @@ -239,7 +238,7 @@ declare module DebugProtocol { line?: number; /** An optional source location column where the output was produced. */ column?: number; - /** Optional data to report. For the 'telemetry' category the data will be sent to telemetry, for the other categories the data is shown in JSON format. */ + /** Optional data to report. For the `telemetry` category the data is sent to telemetry, for the other categories the data is shown in JSON format. */ data?: any; }; } @@ -254,7 +253,7 @@ declare module DebugProtocol { Values: 'changed', 'new', 'removed', etc. */ reason: 'changed' | 'new' | 'removed' | string; - /** The 'id' attribute is used to find the target breakpoint, the other attributes are used as the new values. */ + /** The `id` attribute is used to find the target breakpoint, the other attributes are used as the new values. */ breakpoint: Breakpoint; }; } @@ -267,7 +266,7 @@ declare module DebugProtocol { body: { /** The reason for the event. */ reason: 'new' | 'changed' | 'removed'; - /** The new, changed, or removed module. In case of 'removed' only the module id is used. */ + /** The new, changed, or removed module. In case of `removed` only the module id is used. */ module: Module; }; } @@ -293,7 +292,7 @@ declare module DebugProtocol { body: { /** The logical name of the process. This is usually the full path to process's executable file. Example: /home/example/myproj/program.js. */ name: string; - /** The system process id of the debugged process. This property will be missing for non-system processes. */ + /** The system process id of the debugged process. This property is missing for non-system processes. */ systemProcessId?: number; /** If true, the process is running on the same computer as the debug adapter. */ isLocalProcess?: boolean; @@ -325,12 +324,12 @@ declare module DebugProtocol { /** Event message for 'progressStart' event type. The event signals that a long running operation is about to start and provides additional information for the client to set up a corresponding progress and cancellation UI. The client is free to delay the showing of the UI in order to reduce flicker. - This event should only be sent if the client has passed the value true for the 'supportsProgressReporting' capability of the 'initialize' request. + This event should only be sent if the corresponding capability `supportsProgressReporting` is true */ interface ProgressStartEvent extends Event { // event: 'progressStart'; body: { - /** An ID that must be used in subsequent 'progressUpdate' and 'progressEnd' events to make them refer to the same progress reporting. + /** An ID that must be used in subsequent `progressUpdate` and `progressEnd` events to make them refer to the same progress reporting. IDs must be unique within a debug session. */ progressId: string; @@ -340,14 +339,14 @@ declare module DebugProtocol { If the request ID is omitted, the progress report is assumed to be related to some general activity of the debug adapter. */ requestId?: number; - /** If true, the request that reports progress may be cancelled with a 'cancel' request. + /** If true, the request that reports progress may be cancelled with a `cancel` request. So this property basically controls whether the client should use UX that supports cancellation. Clients that don't support cancellation are allowed to ignore the setting. */ cancellable?: boolean; /** Optional, more detailed progress message. */ message?: string; - /** Optional progress percentage to display (value range: 0 to 100). If omitted no percentage will be shown. */ + /** Optional progress percentage to display (value range: 0 to 100). If omitted no percentage is shown. */ percentage?: number; }; } @@ -355,28 +354,28 @@ declare module DebugProtocol { /** Event message for 'progressUpdate' event type. The event signals that the progress reporting needs to be updated with a new message and/or percentage. The client does not have to update the UI immediately, but the clients needs to keep track of the message and/or percentage values. - This event should only be sent if the client has passed the value true for the 'supportsProgressReporting' capability of the 'initialize' request. + This event should only be sent if the corresponding capability `supportsProgressReporting` is true. */ interface ProgressUpdateEvent extends Event { // event: 'progressUpdate'; body: { - /** The ID that was introduced in the initial 'progressStart' event. */ + /** The ID that was introduced in the initial `progressStart` event. */ progressId: string; /** Optional, more detailed progress message. If omitted, the previous message (if any) is used. */ message?: string; - /** Optional progress percentage to display (value range: 0 to 100). If omitted no percentage will be shown. */ + /** Optional progress percentage to display (value range: 0 to 100). If omitted no percentage is shown. */ percentage?: number; }; } /** Event message for 'progressEnd' event type. The event signals the end of the progress reporting with an optional final message. - This event should only be sent if the client has passed the value true for the 'supportsProgressReporting' capability of the 'initialize' request. + This event should only be sent if the corresponding capability `supportsProgressReporting` is true. */ interface ProgressEndEvent extends Event { // event: 'progressEnd'; body: { - /** The ID that was introduced in the initial 'ProgressStartEvent'. */ + /** The ID that was introduced in the initial `ProgressStartEvent`. */ progressId: string; /** Optional, more detailed progress message. If omitted, the previous message (if any) is used. */ message?: string; @@ -386,22 +385,22 @@ declare module DebugProtocol { /** Event message for 'invalidated' event type. This event signals that some state in the debug adapter has changed and requires that the client needs to re-render the data snapshot previously requested. Debug adapters do not have to emit this event for runtime changes like stopped or thread events because in that case the client refetches the new state anyway. But the event can be used for example to refresh the UI after rendering formatting has changed in the debug adapter. - This event should only be sent if the debug adapter has received a value true for the 'supportsInvalidatedEvent' capability of the 'initialize' request. + This event should only be sent if the corresponding capability `supportsInvalidatedEvent` is true. */ interface InvalidatedEvent extends Event { // event: 'invalidated'; body: { - /** Optional set of logical areas that got invalidated. This property has a hint characteristic: a client can only be expected to make a 'best effort' in honouring the areas but there are no guarantees. If this property is missing, empty, or if values are not understood, the client should assume a single value 'all'. */ + /** Optional set of logical areas that got invalidated. This property has a hint characteristic: a client can only be expected to make a 'best effort' in honouring the areas but there are no guarantees. If this property is missing, empty, or if values are not understood, the client should assume a single value `all`. */ areas?: InvalidatedAreas[]; /** If specified, the client only needs to refetch data related to this thread. */ threadId?: number; - /** If specified, the client only needs to refetch data related to this stack frame (and the 'threadId' is ignored). */ + /** If specified, the client only needs to refetch data related to this stack frame (and the `threadId` is ignored). */ stackFrameId?: number; }; } /** Event message for 'memory' event type. - This event indicates that some memory range has been updated. It should only be sent if the debug adapter has received a value true for the `supportsMemoryEvent` capability of the `initialize` request. + This event indicates that some memory range has been updated. It should only be sent if the corresponding capability `supportsMemoryEvent` is true. Clients typically react to the event by re-issuing a `readMemory` request if they show the memory identified by the `memoryReference` and if the updated memory range overlaps the displayed range. Clients should not make assumptions how individual memory references relate to each other, so they should not assume that they are part of a single continuous address range and might overlap. Debug adapters can use this event to indicate that the contents of a memory range has changed due to some other request like `setVariable` or `setExpression`. Debug adapters are not expected to emit this event for each and every memory change of a running program, because that information is typically not available from debuggers and it would flood clients with too many events. */ @@ -420,14 +419,16 @@ declare module DebugProtocol { /** RunInTerminal request; value of command field is 'runInTerminal'. This optional request is sent from the debug adapter to the client to run a command in a terminal. This is typically used to launch the debuggee in a terminal provided by the client. - This request should only be called if the client has passed the value true for the 'supportsRunInTerminalRequest' capability of the 'initialize' request. + This request should only be called if the corresponding capability `supportsRunInTerminalRequest` is true. + Client implementations of `runInTerminal` are free to run the command however they choose including issuing the command to a command line interpreter (aka 'shell'). Argument strings passed to the `runInTerminal` request must arrive verbatim in the command to be run. As a consequence, clients which use a shell are responsible for escaping any special shell characters in the argument strings to prevent them from being interpreted (and modified) by the shell. + Some users may wish to take advantage of shell processing in the argument strings. For clients which implement `runInTerminal` using an intermediary shell, the `argsCanBeInterpretedByShell` property can be set to true. In this case the client is requested not to escape any special shell characters in the argument strings. */ interface RunInTerminalRequest extends Request { // command: 'runInTerminal'; arguments: RunInTerminalRequestArguments; } - /** Arguments for 'runInTerminal' request. */ + /** Arguments for `runInTerminal` request. */ interface RunInTerminalRequestArguments { /** What kind of terminal to launch. */ kind?: 'integrated' | 'external'; @@ -439,9 +440,11 @@ declare module DebugProtocol { args: string[]; /** Environment key-value pairs that are added to or removed from the default environment. */ env?: { [key: string]: string | null; }; + /** This property should only be set if the corresponding capability `supportsArgsCanBeInterpretedByShell` is true. If the client uses an intermediary shell to launch the application, then the client must not attempt to escape characters with special meanings for the shell. The user is fully responsible for escaping as needed and that arguments using special characters may not be portable across shells. */ + argsCanBeInterpretedByShell?: boolean; } - /** Response to 'runInTerminal' request. */ + /** Response to `runInTerminal` request. */ interface RunInTerminalResponse extends Response { body: { /** The process ID. The value should be less than or equal to 2147483647 (2^31-1). */ @@ -452,17 +455,17 @@ declare module DebugProtocol { } /** Initialize request; value of command field is 'initialize'. - The 'initialize' request is sent as the first request from the client to the debug adapter in order to configure it with client capabilities and to retrieve capabilities from the debug adapter. - Until the debug adapter has responded to with an 'initialize' response, the client must not send any additional requests or events to the debug adapter. - In addition the debug adapter is not allowed to send any requests or events to the client until it has responded with an 'initialize' response. - The 'initialize' request may only be sent once. + The `initialize` request is sent as the first request from the client to the debug adapter in order to configure it with client capabilities and to retrieve capabilities from the debug adapter. + Until the debug adapter has responded to with an `initialize` response, the client must not send any additional requests or events to the debug adapter. + In addition the debug adapter is not allowed to send any requests or events to the client until it has responded with an `initialize` response. + The `initialize` request may only be sent once. */ interface InitializeRequest extends Request { // command: 'initialize'; arguments: InitializeRequestArguments; } - /** Arguments for 'initialize' request. */ + /** Arguments for `initialize` request. */ interface InitializeRequestArguments { /** The ID of the client using this adapter. */ clientID?: string; @@ -476,7 +479,7 @@ declare module DebugProtocol { linesStartAt1?: boolean; /** If true all column numbers are 1-based (default). */ columnsStartAt1?: boolean; - /** Determines in what format paths are specified. The default is 'path', which is the native format. + /** Determines in what format paths are specified. The default is `path`, which is the native format. Values: 'path', 'uri', etc. */ pathFormat?: 'path' | 'uri' | string; @@ -494,9 +497,11 @@ declare module DebugProtocol { supportsInvalidatedEvent?: boolean; /** Client supports the memory event. */ supportsMemoryEvent?: boolean; + /** Client supports the 'argsCanBeInterpretedByShell' attribute on the 'runInTerminal' request. */ + supportsArgsCanBeInterpretedByShell?: boolean; } - /** Response to 'initialize' request. */ + /** Response to `initialize` request. */ interface InitializeResponse extends Response { /** The capabilities of this debug adapter. */ body?: Capabilities; @@ -504,24 +509,24 @@ declare module DebugProtocol { /** ConfigurationDone request; value of command field is 'configurationDone'. This optional request indicates that the client has finished initialization of the debug adapter. - So it is the last request in the sequence of configuration requests (which was started by the 'initialized' event). - Clients should only call this request if the capability 'supportsConfigurationDoneRequest' is true. + So it is the last request in the sequence of configuration requests (which was started by the `initialized` event). + Clients should only call this request if the corresponding capability `supportsConfigurationDoneRequest` is true. */ interface ConfigurationDoneRequest extends Request { // command: 'configurationDone'; arguments?: ConfigurationDoneArguments; } - /** Arguments for 'configurationDone' request. */ + /** Arguments for `configurationDone` request. */ interface ConfigurationDoneArguments { } - /** Response to 'configurationDone' request. This is just an acknowledgement, so no body field is required. */ + /** Response to `configurationDone` request. This is just an acknowledgement, so no body field is required. */ interface ConfigurationDoneResponse extends Response { } /** Launch request; value of command field is 'launch'. - This launch request is sent from the client to the debug adapter to start the debuggee with or without debugging (if 'noDebug' is true). + This launch request is sent from the client to the debug adapter to start the debuggee with or without debugging (if `noDebug` is true). Since launching is debugger/runtime specific, the arguments for this request are not part of this specification. */ interface LaunchRequest extends Request { @@ -529,18 +534,18 @@ declare module DebugProtocol { arguments: LaunchRequestArguments; } - /** Arguments for 'launch' request. Additional attributes are implementation specific. */ + /** Arguments for `launch` request. Additional attributes are implementation specific. */ interface LaunchRequestArguments { /** If noDebug is true, the launch request should launch the program without enabling debugging. */ noDebug?: boolean; /** Optional data from the previous, restarted session. - The data is sent as the 'restart' attribute of the 'terminated' event. + The data is sent as the `restart` attribute of the `terminated` event. The client should leave the data intact. */ __restart?: any; } - /** Response to 'launch' request. This is just an acknowledgement, so no body field is required. */ + /** Response to `launch` request. This is just an acknowledgement, so no body field is required. */ interface LaunchResponse extends Response { } @@ -553,101 +558,101 @@ declare module DebugProtocol { arguments: AttachRequestArguments; } - /** Arguments for 'attach' request. Additional attributes are implementation specific. */ + /** Arguments for `attach` request. Additional attributes are implementation specific. */ interface AttachRequestArguments { /** Optional data from the previous, restarted session. - The data is sent as the 'restart' attribute of the 'terminated' event. + The data is sent as the `restart` attribute of the `terminated` event. The client should leave the data intact. */ __restart?: any; } - /** Response to 'attach' request. This is just an acknowledgement, so no body field is required. */ + /** Response to `attach` request. This is just an acknowledgement, so no body field is required. */ interface AttachResponse extends Response { } /** Restart request; value of command field is 'restart'. - Restarts a debug session. Clients should only call this request if the capability 'supportsRestartRequest' is true. - If the capability is missing or has the value false, a typical client will emulate 'restart' by terminating the debug adapter first and then launching it anew. + Restarts a debug session. Clients should only call this request if the corresponding capability `supportsRestartRequest` is true. + If the capability is missing or has the value false, a typical client emulates `restart` by terminating the debug adapter first and then launching it anew. */ interface RestartRequest extends Request { // command: 'restart'; arguments?: RestartArguments; } - /** Arguments for 'restart' request. */ + /** Arguments for `restart` request. */ interface RestartArguments { - /** The latest version of the 'launch' or 'attach' configuration. */ + /** The latest version of the `launch` or `attach` configuration. */ arguments?: LaunchRequestArguments | AttachRequestArguments; } - /** Response to 'restart' request. This is just an acknowledgement, so no body field is required. */ + /** Response to `restart` request. This is just an acknowledgement, so no body field is required. */ interface RestartResponse extends Response { } /** Disconnect request; value of command field is 'disconnect'. - The 'disconnect' request asks the debug adapter to disconnect from the debuggee (thus ending the debug session) and then to shut down itself (the debug adapter). - In addition, the debug adapter must terminate the debuggee if it was started with the 'launch' request. If an 'attach' request was used to connect to the debuggee, then the debug adapter must not terminate the debuggee. - This implicit behavior of when to terminate the debuggee can be overridden with the optional argument 'terminateDebuggee' (which is only supported by a debug adapter if the corresponding capability 'supportTerminateDebuggee' is true). + The `disconnect` request asks the debug adapter to disconnect from the debuggee (thus ending the debug session) and then to shut down itself (the debug adapter). + In addition, the debug adapter must terminate the debuggee if it was started with the `launch` request. If an `attach` request was used to connect to the debuggee, then the debug adapter must not terminate the debuggee. + This implicit behavior of when to terminate the debuggee can be overridden with the optional argument `terminateDebuggee` (which is only supported by a debug adapter if the corresponding capability `supportTerminateDebuggee` is true). */ interface DisconnectRequest extends Request { // command: 'disconnect'; arguments?: DisconnectArguments; } - /** Arguments for 'disconnect' request. */ + /** Arguments for `disconnect` request. */ interface DisconnectArguments { - /** A value of true indicates that this 'disconnect' request is part of a restart sequence. */ + /** A value of true indicates that this `disconnect` request is part of a restart sequence. */ restart?: boolean; /** Indicates whether the debuggee should be terminated when the debugger is disconnected. If unspecified, the debug adapter is free to do whatever it thinks is best. - The attribute is only honored by a debug adapter if the capability 'supportTerminateDebuggee' is true. + The attribute is only honored by a debug adapter if the corresponding capability `supportTerminateDebuggee` is true. */ terminateDebuggee?: boolean; /** Indicates whether the debuggee should stay suspended when the debugger is disconnected. If unspecified, the debuggee should resume execution. - The attribute is only honored by a debug adapter if the capability 'supportSuspendDebuggee' is true. + The attribute is only honored by a debug adapter if the corresponding capability `supportSuspendDebuggee` is true. */ suspendDebuggee?: boolean; } - /** Response to 'disconnect' request. This is just an acknowledgement, so no body field is required. */ + /** Response to `disconnect` request. This is just an acknowledgement, so no body field is required. */ interface DisconnectResponse extends Response { } /** Terminate request; value of command field is 'terminate'. - The 'terminate' request is sent from the client to the debug adapter in order to shut down the debuggee gracefully. Clients should only call this request if the capability 'supportsTerminateRequest' is true. - Typically a debug adapter implements 'terminate' by sending a software signal which the debuggee intercepts in order to clean things up properly before terminating itself. - Please note that this request does not directly affect the state of the debug session: if the debuggee decides to veto the graceful shutdown for any reason by not terminating itself, then the debug session will just continue. - Clients can surface the 'terminate' request as an explicit command or they can integrate it into a two stage Stop command that first sends 'terminate' to request a graceful shutdown, and if that fails uses 'disconnect' for a forceful shutdown. + The `terminate` request is sent from the client to the debug adapter in order to shut down the debuggee gracefully. Clients should only call this request if the capability `supportsTerminateRequest` is true. + Typically a debug adapter implements `terminate` by sending a software signal which the debuggee intercepts in order to clean things up properly before terminating itself. + Please note that this request does not directly affect the state of the debug session: if the debuggee decides to veto the graceful shutdown for any reason by not terminating itself, then the debug session just continues. + Clients can surface the `terminate` request as an explicit command or they can integrate it into a two stage Stop command that first sends `terminate` to request a graceful shutdown, and if that fails uses `disconnect` for a forceful shutdown. */ interface TerminateRequest extends Request { // command: 'terminate'; arguments?: TerminateArguments; } - /** Arguments for 'terminate' request. */ + /** Arguments for `terminate` request. */ interface TerminateArguments { - /** A value of true indicates that this 'terminate' request is part of a restart sequence. */ + /** A value of true indicates that this `terminate` request is part of a restart sequence. */ restart?: boolean; } - /** Response to 'terminate' request. This is just an acknowledgement, so no body field is required. */ + /** Response to `terminate` request. This is just an acknowledgement, so no body field is required. */ interface TerminateResponse extends Response { } /** BreakpointLocations request; value of command field is 'breakpointLocations'. - The 'breakpointLocations' request returns all possible locations for source breakpoints in a given range. - Clients should only call this request if the capability 'supportsBreakpointLocationsRequest' is true. + The `breakpointLocations` request returns all possible locations for source breakpoints in a given range. + Clients should only call this request if the corresponding capability `supportsBreakpointLocationsRequest` is true. */ interface BreakpointLocationsRequest extends Request { // command: 'breakpointLocations'; arguments?: BreakpointLocationsArguments; } - /** Arguments for 'breakpointLocations' request. */ + /** Arguments for `breakpointLocations` request. */ interface BreakpointLocationsArguments { - /** The source location of the breakpoints; either 'source.path' or 'source.reference' must be specified. */ + /** The source location of the breakpoints; either `source.path` or `source.reference` must be specified. */ source: Source; /** Start line of range to search possible breakpoint locations in. If only the line is specified, the request returns all possible locations in that line. */ line: number; @@ -659,7 +664,7 @@ declare module DebugProtocol { endColumn?: number; } - /** Response to 'breakpointLocations' request. + /** Response to `breakpointLocations` request. Contains possible locations for source breakpoints. */ interface BreakpointLocationsResponse extends Response { @@ -672,16 +677,16 @@ declare module DebugProtocol { /** SetBreakpoints request; value of command field is 'setBreakpoints'. Sets multiple breakpoints for a single source and clears all previous breakpoints in that source. To clear all breakpoint for a source, specify an empty array. - When a breakpoint is hit, a 'stopped' event (with reason 'breakpoint') is generated. + When a breakpoint is hit, a `stopped` event (with reason `breakpoint`) is generated. */ interface SetBreakpointsRequest extends Request { // command: 'setBreakpoints'; arguments: SetBreakpointsArguments; } - /** Arguments for 'setBreakpoints' request. */ + /** Arguments for `setBreakpoints` request. */ interface SetBreakpointsArguments { - /** The source location of the breakpoints; either 'source.path' or 'source.sourceReference' must be specified. */ + /** The source location of the breakpoints; either `source.path` or `source.sourceReference` must be specified. */ source: Source; /** The code locations of the breakpoints. */ breakpoints?: SourceBreakpoint[]; @@ -691,16 +696,16 @@ declare module DebugProtocol { sourceModified?: boolean; } - /** Response to 'setBreakpoints' request. + /** Response to `setBreakpoints` request. Returned is information about each breakpoint created by this request. This includes the actual code location and whether the breakpoint could be verified. - The breakpoints returned are in the same order as the elements of the 'breakpoints' - (or the deprecated 'lines') array in the arguments. + The breakpoints returned are in the same order as the elements of the `breakpoints` + (or the deprecated `lines`) array in the arguments. */ interface SetBreakpointsResponse extends Response { body: { /** Information about the breakpoints. - The array elements are in the same order as the elements of the 'breakpoints' (or the deprecated 'lines') array in the arguments. + The array elements are in the same order as the elements of the `breakpoints` (or the deprecated `lines`) array in the arguments. */ breakpoints: Breakpoint[]; }; @@ -709,61 +714,61 @@ declare module DebugProtocol { /** SetFunctionBreakpoints request; value of command field is 'setFunctionBreakpoints'. Replaces all existing function breakpoints with new function breakpoints. To clear all function breakpoints, specify an empty array. - When a function breakpoint is hit, a 'stopped' event (with reason 'function breakpoint') is generated. - Clients should only call this request if the capability 'supportsFunctionBreakpoints' is true. + When a function breakpoint is hit, a `stopped` event (with reason `function breakpoint`) is generated. + Clients should only call this request if the corresponding capability `supportsFunctionBreakpoints` is true. */ interface SetFunctionBreakpointsRequest extends Request { // command: 'setFunctionBreakpoints'; arguments: SetFunctionBreakpointsArguments; } - /** Arguments for 'setFunctionBreakpoints' request. */ + /** Arguments for `setFunctionBreakpoints` request. */ interface SetFunctionBreakpointsArguments { /** The function names of the breakpoints. */ breakpoints: FunctionBreakpoint[]; } - /** Response to 'setFunctionBreakpoints' request. + /** Response to `setFunctionBreakpoints` request. Returned is information about each breakpoint created by this request. */ interface SetFunctionBreakpointsResponse extends Response { body: { - /** Information about the breakpoints. The array elements correspond to the elements of the 'breakpoints' array. */ + /** Information about the breakpoints. The array elements correspond to the elements of the `breakpoints` array. */ breakpoints: Breakpoint[]; }; } /** SetExceptionBreakpoints request; value of command field is 'setExceptionBreakpoints'. The request configures the debugger's response to thrown exceptions. - If an exception is configured to break, a 'stopped' event is fired (with reason 'exception'). - Clients should only call this request if the capability 'exceptionBreakpointFilters' returns one or more filters. + If an exception is configured to break, a `stopped` event is fired (with reason `exception`). + Clients should only call this request if the corresponding capability `exceptionBreakpointFilters` returns one or more filters. */ interface SetExceptionBreakpointsRequest extends Request { // command: 'setExceptionBreakpoints'; arguments: SetExceptionBreakpointsArguments; } - /** Arguments for 'setExceptionBreakpoints' request. */ + /** Arguments for `setExceptionBreakpoints` request. */ interface SetExceptionBreakpointsArguments { - /** Set of exception filters specified by their ID. The set of all possible exception filters is defined by the 'exceptionBreakpointFilters' capability. The 'filter' and 'filterOptions' sets are additive. */ + /** Set of exception filters specified by their ID. The set of all possible exception filters is defined by the `exceptionBreakpointFilters` capability. The `filter` and `filterOptions` sets are additive. */ filters: string[]; - /** Set of exception filters and their options. The set of all possible exception filters is defined by the 'exceptionBreakpointFilters' capability. This attribute is only honored by a debug adapter if the capability 'supportsExceptionFilterOptions' is true. The 'filter' and 'filterOptions' sets are additive. */ + /** Set of exception filters and their options. The set of all possible exception filters is defined by the `exceptionBreakpointFilters` capability. This attribute is only honored by a debug adapter if the corresponding capability `supportsExceptionFilterOptions` is true. The `filter` and `filterOptions` sets are additive. */ filterOptions?: ExceptionFilterOptions[]; /** Configuration options for selected exceptions. - The attribute is only honored by a debug adapter if the capability 'supportsExceptionOptions' is true. + The attribute is only honored by a debug adapter if the corresponding capability `supportsExceptionOptions` is true. */ exceptionOptions?: ExceptionOptions[]; } - /** Response to 'setExceptionBreakpoints' request. - The response contains an array of Breakpoint objects with information about each exception breakpoint or filter. The Breakpoint objects are in the same order as the elements of the 'filters', 'filterOptions', 'exceptionOptions' arrays given as arguments. If both 'filters' and 'filterOptions' are given, the returned array must start with 'filters' information first, followed by 'filterOptions' information. - The mandatory 'verified' property of a Breakpoint object signals whether the exception breakpoint or filter could be successfully created and whether the optional condition or hit count expressions are valid. In case of an error the 'message' property explains the problem. An optional 'id' property can be used to introduce a unique ID for the exception breakpoint or filter so that it can be updated subsequently by sending breakpoint events. - For backward compatibility both the 'breakpoints' array and the enclosing 'body' are optional. If these elements are missing a client will not be able to show problems for individual exception breakpoints or filters. + /** Response to `setExceptionBreakpoints` request. + The response contains an array of `Breakpoint` objects with information about each exception breakpoint or filter. The `Breakpoint` objects are in the same order as the elements of the `filters`, `filterOptions`, `exceptionOptions` arrays given as arguments. If both `filters` and `filterOptions` are given, the returned array must start with `filters` information first, followed by `filterOptions` information. + The mandatory `verified` property of a `Breakpoint` object signals whether the exception breakpoint or filter could be successfully created and whether the optional condition or hit count expressions are valid. In case of an error the `message` property explains the problem. An optional `id` property can be used to introduce a unique ID for the exception breakpoint or filter so that it can be updated subsequently by sending breakpoint events. + For backward compatibility both the `breakpoints` array and the enclosing `body` are optional. If these elements are missing a client is not able to show problems for individual exception breakpoints or filters. */ interface SetExceptionBreakpointsResponse extends Response { body?: { /** Information about the exception breakpoints or filters. - The breakpoints returned are in the same order as the elements of the 'filters', 'filterOptions', 'exceptionOptions' arrays in the arguments. If both 'filters' and 'filterOptions' are given, the returned array must start with 'filters' information first, followed by 'filterOptions' information. + The breakpoints returned are in the same order as the elements of the `filters`, `filterOptions`, `exceptionOptions` arrays in the arguments. If both `filters` and `filterOptions` are given, the returned array must start with `filters` information first, followed by `filterOptions` information. */ breakpoints?: Breakpoint[]; }; @@ -771,24 +776,24 @@ declare module DebugProtocol { /** DataBreakpointInfo request; value of command field is 'dataBreakpointInfo'. Obtains information on a possible data breakpoint that could be set on an expression or variable. - Clients should only call this request if the capability 'supportsDataBreakpoints' is true. + Clients should only call this request if the corresponding capability `supportsDataBreakpoints` is true. */ interface DataBreakpointInfoRequest extends Request { // command: 'dataBreakpointInfo'; arguments: DataBreakpointInfoArguments; } - /** Arguments for 'dataBreakpointInfo' request. */ + /** Arguments for `dataBreakpointInfo` request. */ interface DataBreakpointInfoArguments { /** Reference to the Variable container if the data breakpoint is requested for a child of the container. */ variablesReference?: number; - /** The name of the Variable's child to obtain data breakpoint information for. + /** The name of the variable's child to obtain data breakpoint information for. If variablesReference isn't provided, this can be an expression. */ name: string; } - /** Response to 'dataBreakpointInfo' request. */ + /** Response to `dataBreakpointInfo` request. */ interface DataBreakpointInfoResponse extends Response { body: { /** An identifier for the data on which a data breakpoint can be registered with the setDataBreakpoints request or null if no data breakpoint is available. */ @@ -805,26 +810,26 @@ declare module DebugProtocol { /** SetDataBreakpoints request; value of command field is 'setDataBreakpoints'. Replaces all existing data breakpoints with new data breakpoints. To clear all data breakpoints, specify an empty array. - When a data breakpoint is hit, a 'stopped' event (with reason 'data breakpoint') is generated. - Clients should only call this request if the capability 'supportsDataBreakpoints' is true. + When a data breakpoint is hit, a `stopped` event (with reason `data breakpoint`) is generated. + Clients should only call this request if the corresponding capability `supportsDataBreakpoints` is true. */ interface SetDataBreakpointsRequest extends Request { // command: 'setDataBreakpoints'; arguments: SetDataBreakpointsArguments; } - /** Arguments for 'setDataBreakpoints' request. */ + /** Arguments for `setDataBreakpoints` request. */ interface SetDataBreakpointsArguments { /** The contents of this array replaces all existing data breakpoints. An empty array clears all data breakpoints. */ breakpoints: DataBreakpoint[]; } - /** Response to 'setDataBreakpoints' request. + /** Response to `setDataBreakpoints` request. Returned is information about each breakpoint created by this request. */ interface SetDataBreakpointsResponse extends Response { body: { - /** Information about the data breakpoints. The array elements correspond to the elements of the input argument 'breakpoints' array. */ + /** Information about the data breakpoints. The array elements correspond to the elements of the input argument `breakpoints` array. */ breakpoints: Breakpoint[]; }; } @@ -832,45 +837,45 @@ declare module DebugProtocol { /** SetInstructionBreakpoints request; value of command field is 'setInstructionBreakpoints'. Replaces all existing instruction breakpoints. Typically, instruction breakpoints would be set from a disassembly window. To clear all instruction breakpoints, specify an empty array. - When an instruction breakpoint is hit, a 'stopped' event (with reason 'instruction breakpoint') is generated. - Clients should only call this request if the capability 'supportsInstructionBreakpoints' is true. + When an instruction breakpoint is hit, a `stopped` event (with reason `instruction breakpoint`) is generated. + Clients should only call this request if the corresponding capability `supportsInstructionBreakpoints` is true. */ interface SetInstructionBreakpointsRequest extends Request { // command: 'setInstructionBreakpoints'; arguments: SetInstructionBreakpointsArguments; } - /** Arguments for 'setInstructionBreakpoints' request */ + /** Arguments for `setInstructionBreakpoints` request */ interface SetInstructionBreakpointsArguments { /** The instruction references of the breakpoints */ breakpoints: InstructionBreakpoint[]; } - /** Response to 'setInstructionBreakpoints' request */ + /** Response to `setInstructionBreakpoints` request */ interface SetInstructionBreakpointsResponse extends Response { body: { - /** Information about the breakpoints. The array elements correspond to the elements of the 'breakpoints' array. */ + /** Information about the breakpoints. The array elements correspond to the elements of the `breakpoints` array. */ breakpoints: Breakpoint[]; }; } /** Continue request; value of command field is 'continue'. - The request resumes execution of all threads. If the debug adapter supports single thread execution (see capability 'supportsSingleThreadExecutionRequests'), setting the 'singleThread' argument to true resumes only the specified thread. If not all threads were resumed, the 'allThreadsContinued' attribute of the response must be set to false. + The request resumes execution of all threads. If the debug adapter supports single thread execution (see capability `supportsSingleThreadExecutionRequests`), setting the `singleThread` argument to true resumes only the specified thread. If not all threads were resumed, the `allThreadsContinued` attribute of the response must be set to false. */ interface ContinueRequest extends Request { // command: 'continue'; arguments: ContinueArguments; } - /** Arguments for 'continue' request. */ + /** Arguments for `continue` request. */ interface ContinueArguments { - /** Specifies the active thread. If the debug adapter supports single thread execution (see 'supportsSingleThreadExecutionRequests') and the optional argument 'singleThread' is true, only the thread with this ID is resumed. */ + /** Specifies the active thread. If the debug adapter supports single thread execution (see `supportsSingleThreadExecutionRequests`) and the optional argument `singleThread` is true, only the thread with this ID is resumed. */ threadId: number; - /** If this optional flag is true, execution is resumed only for the thread with given 'threadId'. */ + /** If this optional flag is true, execution is resumed only for the thread with given `threadId`. */ singleThread?: boolean; } - /** Response to 'continue' request. */ + /** Response to `continue` request. */ interface ContinueResponse extends Response { body: { /** The value true (or a missing property) signals to the client that all threads have been resumed. The value false must be returned if not all threads were resumed. */ @@ -880,43 +885,43 @@ declare module DebugProtocol { /** Next request; value of command field is 'next'. The request executes one step (in the given granularity) for the specified thread and allows all other threads to run freely by resuming them. - If the debug adapter supports single thread execution (see capability 'supportsSingleThreadExecutionRequests'), setting the 'singleThread' argument to true prevents other suspended threads from resuming. - The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. + If the debug adapter supports single thread execution (see capability `supportsSingleThreadExecutionRequests`), setting the `singleThread` argument to true prevents other suspended threads from resuming. + The debug adapter first sends the response and then a `stopped` event (with reason `step`) after the step has completed. */ interface NextRequest extends Request { // command: 'next'; arguments: NextArguments; } - /** Arguments for 'next' request. */ + /** Arguments for `next` request. */ interface NextArguments { /** Specifies the thread for which to resume execution for one step (of the given granularity). */ threadId: number; /** If this optional flag is true, all other suspended threads are not resumed. */ singleThread?: boolean; - /** Optional granularity to step. If no granularity is specified, a granularity of 'statement' is assumed. */ + /** Optional granularity to step. If no granularity is specified, a granularity of `statement` is assumed. */ granularity?: SteppingGranularity; } - /** Response to 'next' request. This is just an acknowledgement, so no body field is required. */ + /** Response to `next` request. This is just an acknowledgement, so no body field is required. */ interface NextResponse extends Response { } /** StepIn request; value of command field is 'stepIn'. The request resumes the given thread to step into a function/method and allows all other threads to run freely by resuming them. - If the debug adapter supports single thread execution (see capability 'supportsSingleThreadExecutionRequests'), setting the 'singleThread' argument to true prevents other suspended threads from resuming. - If the request cannot step into a target, 'stepIn' behaves like the 'next' request. - The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. + If the debug adapter supports single thread execution (see capability `supportsSingleThreadExecutionRequests`), setting the `singleThread` argument to true prevents other suspended threads from resuming. + If the request cannot step into a target, `stepIn` behaves like the `next` request. + The debug adapter first sends the response and then a `stopped` event (with reason `step`) after the step has completed. If there are multiple function/method calls (or other targets) on the source line, - the optional argument 'targetId' can be used to control into which target the 'stepIn' should occur. - The list of possible targets for a given source line can be retrieved via the 'stepInTargets' request. + the optional argument `targetId` can be used to control into which target the `stepIn` should occur. + The list of possible targets for a given source line can be retrieved via the `stepInTargets` request. */ interface StepInRequest extends Request { // command: 'stepIn'; arguments: StepInArguments; } - /** Arguments for 'stepIn' request. */ + /** Arguments for `stepIn` request. */ interface StepInArguments { /** Specifies the thread for which to resume execution for one step-into (of the given granularity). */ threadId: number; @@ -924,101 +929,101 @@ declare module DebugProtocol { singleThread?: boolean; /** Optional id of the target to step into. */ targetId?: number; - /** Optional granularity to step. If no granularity is specified, a granularity of 'statement' is assumed. */ + /** Optional granularity to step. If no granularity is specified, a granularity of `statement` is assumed. */ granularity?: SteppingGranularity; } - /** Response to 'stepIn' request. This is just an acknowledgement, so no body field is required. */ + /** Response to `stepIn` request. This is just an acknowledgement, so no body field is required. */ interface StepInResponse extends Response { } /** StepOut request; value of command field is 'stepOut'. The request resumes the given thread to step out (return) from a function/method and allows all other threads to run freely by resuming them. - If the debug adapter supports single thread execution (see capability 'supportsSingleThreadExecutionRequests'), setting the 'singleThread' argument to true prevents other suspended threads from resuming. - The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. + If the debug adapter supports single thread execution (see capability `supportsSingleThreadExecutionRequests`), setting the `singleThread` argument to true prevents other suspended threads from resuming. + The debug adapter first sends the response and then a `stopped` event (with reason `step`) after the step has completed. */ interface StepOutRequest extends Request { // command: 'stepOut'; arguments: StepOutArguments; } - /** Arguments for 'stepOut' request. */ + /** Arguments for `stepOut` request. */ interface StepOutArguments { /** Specifies the thread for which to resume execution for one step-out (of the given granularity). */ threadId: number; /** If this optional flag is true, all other suspended threads are not resumed. */ singleThread?: boolean; - /** Optional granularity to step. If no granularity is specified, a granularity of 'statement' is assumed. */ + /** Optional granularity to step. If no granularity is specified, a granularity of `statement` is assumed. */ granularity?: SteppingGranularity; } - /** Response to 'stepOut' request. This is just an acknowledgement, so no body field is required. */ + /** Response to `stepOut` request. This is just an acknowledgement, so no body field is required. */ interface StepOutResponse extends Response { } /** StepBack request; value of command field is 'stepBack'. The request executes one backward step (in the given granularity) for the specified thread and allows all other threads to run backward freely by resuming them. - If the debug adapter supports single thread execution (see capability 'supportsSingleThreadExecutionRequests'), setting the 'singleThread' argument to true prevents other suspended threads from resuming. - The debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. - Clients should only call this request if the capability 'supportsStepBack' is true. + If the debug adapter supports single thread execution (see capability `supportsSingleThreadExecutionRequests`), setting the `singleThread` argument to true prevents other suspended threads from resuming. + The debug adapter first sends the response and then a `stopped` event (with reason `step`) after the step has completed. + Clients should only call this request if the corresponding capability `supportsStepBack` is true. */ interface StepBackRequest extends Request { // command: 'stepBack'; arguments: StepBackArguments; } - /** Arguments for 'stepBack' request. */ + /** Arguments for `stepBack` request. */ interface StepBackArguments { /** Specifies the thread for which to resume execution for one step backwards (of the given granularity). */ threadId: number; /** If this optional flag is true, all other suspended threads are not resumed. */ singleThread?: boolean; - /** Optional granularity to step. If no granularity is specified, a granularity of 'statement' is assumed. */ + /** Optional granularity to step. If no granularity is specified, a granularity of `statement` is assumed. */ granularity?: SteppingGranularity; } - /** Response to 'stepBack' request. This is just an acknowledgement, so no body field is required. */ + /** Response to `stepBack` request. This is just an acknowledgement, so no body field is required. */ interface StepBackResponse extends Response { } /** ReverseContinue request; value of command field is 'reverseContinue'. - The request resumes backward execution of all threads. If the debug adapter supports single thread execution (see capability 'supportsSingleThreadExecutionRequests'), setting the 'singleThread' argument to true resumes only the specified thread. If not all threads were resumed, the 'allThreadsContinued' attribute of the response must be set to false. - Clients should only call this request if the capability 'supportsStepBack' is true. + The request resumes backward execution of all threads. If the debug adapter supports single thread execution (see capability `supportsSingleThreadExecutionRequests`), setting the `singleThread` argument to true resumes only the specified thread. If not all threads were resumed, the `allThreadsContinued` attribute of the response must be set to false. + Clients should only call this request if the corresponding capability `supportsStepBack` is true. */ interface ReverseContinueRequest extends Request { // command: 'reverseContinue'; arguments: ReverseContinueArguments; } - /** Arguments for 'reverseContinue' request. */ + /** Arguments for `reverseContinue` request. */ interface ReverseContinueArguments { - /** Specifies the active thread. If the debug adapter supports single thread execution (see 'supportsSingleThreadExecutionRequests') and the optional argument 'singleThread' is true, only the thread with this ID is resumed. */ + /** Specifies the active thread. If the debug adapter supports single thread execution (see `supportsSingleThreadExecutionRequests`) and the optional argument `singleThread` is true, only the thread with this ID is resumed. */ threadId: number; - /** If this optional flag is true, backward execution is resumed only for the thread with given 'threadId'. */ + /** If this optional flag is true, backward execution is resumed only for the thread with given `threadId`. */ singleThread?: boolean; } - /** Response to 'reverseContinue' request. This is just an acknowledgement, so no body field is required. */ + /** Response to `reverseContinue` request. This is just an acknowledgement, so no body field is required. */ interface ReverseContinueResponse extends Response { } /** RestartFrame request; value of command field is 'restartFrame'. The request restarts execution of the specified stackframe. - The debug adapter first sends the response and then a 'stopped' event (with reason 'restart') after the restart has completed. - Clients should only call this request if the capability 'supportsRestartFrame' is true. + The debug adapter first sends the response and then a `stopped` event (with reason `restart`) after the restart has completed. + Clients should only call this request if the corresponding capability `supportsRestartFrame` is true. */ interface RestartFrameRequest extends Request { // command: 'restartFrame'; arguments: RestartFrameArguments; } - /** Arguments for 'restartFrame' request. */ + /** Arguments for `restartFrame` request. */ interface RestartFrameArguments { /** Restart this stackframe. */ frameId: number; } - /** Response to 'restartFrame' request. This is just an acknowledgement, so no body field is required. */ + /** Response to `restartFrame` request. This is just an acknowledgement, so no body field is required. */ interface RestartFrameResponse extends Response { } @@ -1026,15 +1031,15 @@ declare module DebugProtocol { The request sets the location where the debuggee will continue to run. This makes it possible to skip the execution of code or to execute code again. The code between the current location and the goto target is not executed but skipped. - The debug adapter first sends the response and then a 'stopped' event with reason 'goto'. - Clients should only call this request if the capability 'supportsGotoTargetsRequest' is true (because only then goto targets exist that can be passed as arguments). + The debug adapter first sends the response and then a `stopped` event with reason `goto`. + Clients should only call this request if the corresponding capability `supportsGotoTargetsRequest` is true (because only then goto targets exist that can be passed as arguments). */ interface GotoRequest extends Request { // command: 'goto'; arguments: GotoArguments; } - /** Arguments for 'goto' request. */ + /** Arguments for `goto` request. */ interface GotoArguments { /** Set the goto target for this thread. */ threadId: number; @@ -1042,39 +1047,39 @@ declare module DebugProtocol { targetId: number; } - /** Response to 'goto' request. This is just an acknowledgement, so no body field is required. */ + /** Response to `goto` request. This is just an acknowledgement, so no body field is required. */ interface GotoResponse extends Response { } /** Pause request; value of command field is 'pause'. The request suspends the debuggee. - The debug adapter first sends the response and then a 'stopped' event (with reason 'pause') after the thread has been paused successfully. + The debug adapter first sends the response and then a `stopped` event (with reason `pause`) after the thread has been paused successfully. */ interface PauseRequest extends Request { // command: 'pause'; arguments: PauseArguments; } - /** Arguments for 'pause' request. */ + /** Arguments for `pause` request. */ interface PauseArguments { /** Pause execution for this thread. */ threadId: number; } - /** Response to 'pause' request. This is just an acknowledgement, so no body field is required. */ + /** Response to `pause` request. This is just an acknowledgement, so no body field is required. */ interface PauseResponse extends Response { } /** StackTrace request; value of command field is 'stackTrace'. The request returns a stacktrace from the current execution state of a given thread. - A client can request all stack frames by omitting the startFrame and levels arguments. For performance-conscious clients and if the debug adapter's 'supportsDelayedStackTraceLoading' capability is true, stack frames can be retrieved in a piecemeal way with the startFrame and levels arguments. The response of the stackTrace request may contain a totalFrames property that hints at the total number of frames in the stack. If a client needs this total number upfront, it can issue a request for a single (first) frame and depending on the value of totalFrames decide how to proceed. In any case a client should be prepared to receive fewer frames than requested, which is an indication that the end of the stack has been reached. + A client can request all stack frames by omitting the startFrame and levels arguments. For performance-conscious clients and if the corresponding capability `supportsDelayedStackTraceLoading` is true, stack frames can be retrieved in a piecemeal way with the startFrame and levels arguments. The response of the stackTrace request may contain a totalFrames property that hints at the total number of frames in the stack. If a client needs this total number upfront, it can issue a request for a single (first) frame and depending on the value of totalFrames decide how to proceed. In any case a client should be prepared to receive fewer frames than requested, which is an indication that the end of the stack has been reached. */ interface StackTraceRequest extends Request { // command: 'stackTrace'; arguments: StackTraceArguments; } - /** Arguments for 'stackTrace' request. */ + /** Arguments for `stackTrace` request. */ interface StackTraceArguments { /** Retrieve the stacktrace for this thread. */ threadId: number; @@ -1083,12 +1088,12 @@ declare module DebugProtocol { /** The maximum number of frames to return. If levels is not specified or 0, all frames are returned. */ levels?: number; /** Specifies details on how to format the stack frames. - The attribute is only honored by a debug adapter if the capability 'supportsValueFormattingOptions' is true. + The attribute is only honored by a debug adapter if the corresponding capability `supportsValueFormattingOptions` is true. */ format?: StackFrameFormat; } - /** Response to 'stackTrace' request. */ + /** Response to `stackTrace` request. */ interface StackTraceResponse extends Response { body: { /** The frames of the stackframe. If the array has length zero, there are no stackframes available. @@ -1108,13 +1113,13 @@ declare module DebugProtocol { arguments: ScopesArguments; } - /** Arguments for 'scopes' request. */ + /** Arguments for `scopes` request. */ interface ScopesArguments { /** Retrieve the scopes for this stackframe. */ frameId: number; } - /** Response to 'scopes' request. */ + /** Response to `scopes` request. */ interface ScopesResponse extends Response { body: { /** The scopes of the stackframe. If the array has length zero, there are no scopes available. */ @@ -1131,7 +1136,7 @@ declare module DebugProtocol { arguments: VariablesArguments; } - /** Arguments for 'variables' request. */ + /** Arguments for `variables` request. */ interface VariablesArguments { /** The Variable reference. */ variablesReference: number; @@ -1142,12 +1147,12 @@ declare module DebugProtocol { /** The number of variables to return. If count is missing or 0, all variables are returned. */ count?: number; /** Specifies details on how to format the Variable values. - The attribute is only honored by a debug adapter if the capability 'supportsValueFormattingOptions' is true. + The attribute is only honored by a debug adapter if the corresponding capability `supportsValueFormattingOptions` is true. */ format?: ValueFormat; } - /** Response to 'variables' request. */ + /** Response to `variables` request. */ interface VariablesResponse extends Response { body: { /** All (or a range) of variables for the given variable reference. */ @@ -1156,15 +1161,15 @@ declare module DebugProtocol { } /** SetVariable request; value of command field is 'setVariable'. - Set the variable with the given name in the variable container to a new value. Clients should only call this request if the capability 'supportsSetVariable' is true. - If a debug adapter implements both setVariable and setExpression, a client will only use setExpression if the variable has an evaluateName property. + Set the variable with the given name in the variable container to a new value. Clients should only call this request if the corresponding capability `supportsSetVariable` is true. + If a debug adapter implements both `setVariable` and `setExpression`, a client will only use `setExpression` if the variable has an `evaluateName` property. */ interface SetVariableRequest extends Request { // command: 'setVariable'; arguments: SetVariableArguments; } - /** Arguments for 'setVariable' request. */ + /** Arguments for `setVariable` request. */ interface SetVariableArguments { /** The reference of the variable container. */ variablesReference: number; @@ -1176,7 +1181,7 @@ declare module DebugProtocol { format?: ValueFormat; } - /** Response to 'setVariable' request. */ + /** Response to `setVariable` request. */ interface SetVariableResponse extends Response { body: { /** The new value of the variable. */ @@ -1208,17 +1213,17 @@ declare module DebugProtocol { arguments: SourceArguments; } - /** Arguments for 'source' request. */ + /** Arguments for `source` request. */ interface SourceArguments { /** Specifies the source content to load. Either source.path or source.sourceReference must be specified. */ source?: Source; /** The reference to the source. This is the same as source.sourceReference. - This is provided for backward compatibility since old backends do not understand the 'source' attribute. + This is provided for backward compatibility since old clients do not understand the `source` attribute. */ sourceReference: number; } - /** Response to 'source' request. */ + /** Response to `source` request. */ interface SourceResponse extends Response { body: { /** Content of the source reference. */ @@ -1235,7 +1240,7 @@ declare module DebugProtocol { // command: 'threads'; } - /** Response to 'threads' request. */ + /** Response to `threads` request. */ interface ThreadsResponse extends Response { body: { /** All threads. */ @@ -1245,33 +1250,33 @@ declare module DebugProtocol { /** TerminateThreads request; value of command field is 'terminateThreads'. The request terminates the threads with the given ids. - Clients should only call this request if the capability 'supportsTerminateThreadsRequest' is true. + Clients should only call this request if the corresponding capability `supportsTerminateThreadsRequest` is true. */ interface TerminateThreadsRequest extends Request { // command: 'terminateThreads'; arguments: TerminateThreadsArguments; } - /** Arguments for 'terminateThreads' request. */ + /** Arguments for `terminateThreads` request. */ interface TerminateThreadsArguments { /** Ids of threads to be terminated. */ threadIds?: number[]; } - /** Response to 'terminateThreads' request. This is just an acknowledgement, so no body field is required. */ + /** Response to `terminateThreads` request. This is just an acknowledgement, so no body field is required. */ interface TerminateThreadsResponse extends Response { } /** Modules request; value of command field is 'modules'. Modules can be retrieved from the debug adapter with this request which can either return all modules or a range of modules to support paging. - Clients should only call this request if the capability 'supportsModulesRequest' is true. + Clients should only call this request if the corresponding capability `supportsModulesRequest` is true. */ interface ModulesRequest extends Request { // command: 'modules'; arguments: ModulesArguments; } - /** Arguments for 'modules' request. */ + /** Arguments for `modules` request. */ interface ModulesArguments { /** The index of the first module to return; if omitted modules start at 0. */ startModule?: number; @@ -1279,7 +1284,7 @@ declare module DebugProtocol { moduleCount?: number; } - /** Response to 'modules' request. */ + /** Response to `modules` request. */ interface ModulesResponse extends Response { body: { /** All modules or range of modules. */ @@ -1291,18 +1296,18 @@ declare module DebugProtocol { /** LoadedSources request; value of command field is 'loadedSources'. Retrieves the set of all sources currently loaded by the debugged process. - Clients should only call this request if the capability 'supportsLoadedSourcesRequest' is true. + Clients should only call this request if the corresponding capability `supportsLoadedSourcesRequest` is true. */ interface LoadedSourcesRequest extends Request { // command: 'loadedSources'; arguments?: LoadedSourcesArguments; } - /** Arguments for 'loadedSources' request. */ + /** Arguments for `loadedSources` request. */ interface LoadedSourcesArguments { } - /** Response to 'loadedSources' request. */ + /** Response to `loadedSources` request. */ interface LoadedSourcesResponse extends Response { body: { /** Set of loaded sources. */ @@ -1319,7 +1324,7 @@ declare module DebugProtocol { arguments: EvaluateArguments; } - /** Arguments for 'evaluate' request. */ + /** Arguments for `evaluate` request. */ interface EvaluateArguments { /** The expression to evaluate. */ expression: string; @@ -1331,25 +1336,25 @@ declare module DebugProtocol { 'watch': evaluate is called from a watch view context. 'repl': evaluate is called from a REPL context. 'hover': evaluate is called to generate the debug hover contents. - This value should only be used if the capability 'supportsEvaluateForHovers' is true. + This value should only be used if the corresponding capability `supportsEvaluateForHovers` is true. 'clipboard': evaluate is called to generate clipboard contents. - This value should only be used if the capability 'supportsClipboardContext' is true. + This value should only be used if the corresponding capability `supportsClipboardContext` is true. etc. */ context?: 'variables' | 'watch' | 'repl' | 'hover' | 'clipboard' | string; /** Specifies details on how to format the result. - The attribute is only honored by a debug adapter if the capability 'supportsValueFormattingOptions' is true. + The attribute is only honored by a debug adapter if the corresponding capability `supportsValueFormattingOptions` is true. */ format?: ValueFormat; } - /** Response to 'evaluate' request. */ + /** Response to `evaluate` request. */ interface EvaluateResponse extends Response { body: { /** The result of the evaluate request. */ result: string; /** The optional type of the evaluate result. - This attribute should only be returned by a debug adapter if the client has passed the value true for the 'supportsVariableType' capability of the 'initialize' request. + This attribute should only be returned by a debug adapter if the ccorresponding capability `supportsVariableType` is true. */ type?: string; /** Properties of an evaluate result that can be used to determine how to render the result in the UI. */ @@ -1370,24 +1375,24 @@ declare module DebugProtocol { indexedVariables?: number; /** Optional memory reference to a location appropriate for this result. For pointer type eval results, this is generally a reference to the memory address contained in the pointer. - This attribute should be returned by a debug adapter if the client has passed the value true for the 'supportsMemoryReferences' capability of the 'initialize' request. + This attribute should be returned by a debug adapter if corresponding capability `supportsMemoryReferences` is true. */ memoryReference?: string; }; } /** SetExpression request; value of command field is 'setExpression'. - Evaluates the given 'value' expression and assigns it to the 'expression' which must be a modifiable l-value. + Evaluates the given `value` expression and assigns it to the `expression` which must be a modifiable l-value. The expressions have access to any variables and arguments that are in scope of the specified frame. - Clients should only call this request if the capability 'supportsSetExpression' is true. - If a debug adapter implements both setExpression and setVariable, a client will only use setExpression if the variable has an evaluateName property. + Clients should only call this request if the corresponding capability `supportsSetExpression` is true. + If a debug adapter implements both setExpression and setVariable, a client uses `setExpression` if the variable has an `evaluateName` property. */ interface SetExpressionRequest extends Request { // command: 'setExpression'; arguments: SetExpressionArguments; } - /** Arguments for 'setExpression' request. */ + /** Arguments for `setExpression` request. */ interface SetExpressionArguments { /** The l-value expression to assign to. */ expression: string; @@ -1399,13 +1404,13 @@ declare module DebugProtocol { format?: ValueFormat; } - /** Response to 'setExpression' request. */ + /** Response to `setExpression` request. */ interface SetExpressionResponse extends Response { body: { /** The new value of the expression. */ value: string; /** The optional type of the value. - This attribute should only be returned by a debug adapter if the client has passed the value true for the 'supportsVariableType' capability of the 'initialize' request. + This attribute should only be returned by a debug adapter if the corresponding capability `supportsVariableType` is true. */ type?: string; /** Properties of a value that can be used to determine how to render the result in the UI. */ @@ -1429,22 +1434,21 @@ declare module DebugProtocol { /** StepInTargets request; value of command field is 'stepInTargets'. This request retrieves the possible stepIn targets for the specified stack frame. - These targets can be used in the 'stepIn' request. - The StepInTargets may only be called if the 'supportsStepInTargetsRequest' capability exists and is true. - Clients should only call this request if the capability 'supportsStepInTargetsRequest' is true. + These targets can be used in the `stepIn` request. + Clients should only call this request if the corresponding capability `supportsStepInTargetsRequest` is true. */ interface StepInTargetsRequest extends Request { // command: 'stepInTargets'; arguments: StepInTargetsArguments; } - /** Arguments for 'stepInTargets' request. */ + /** Arguments for `stepInTargets` request. */ interface StepInTargetsArguments { /** The stack frame for which to retrieve the possible stepIn targets. */ frameId: number; } - /** Response to 'stepInTargets' request. */ + /** Response to `stepInTargets` request. */ interface StepInTargetsResponse extends Response { body: { /** The possible stepIn targets of the specified source location. */ @@ -1454,15 +1458,15 @@ declare module DebugProtocol { /** GotoTargets request; value of command field is 'gotoTargets'. This request retrieves the possible goto targets for the specified source location. - These targets can be used in the 'goto' request. - Clients should only call this request if the capability 'supportsGotoTargetsRequest' is true. + These targets can be used in the `goto` request. + Clients should only call this request if the corresponding capability `supportsGotoTargetsRequest` is true. */ interface GotoTargetsRequest extends Request { // command: 'gotoTargets'; arguments: GotoTargetsArguments; } - /** Arguments for 'gotoTargets' request. */ + /** Arguments for `gotoTargets` request. */ interface GotoTargetsArguments { /** The source location for which the goto targets are determined. */ source: Source; @@ -1472,7 +1476,7 @@ declare module DebugProtocol { column?: number; } - /** Response to 'gotoTargets' request. */ + /** Response to `gotoTargets` request. */ interface GotoTargetsResponse extends Response { body: { /** The possible goto targets of the specified location. */ @@ -1482,14 +1486,14 @@ declare module DebugProtocol { /** Completions request; value of command field is 'completions'. Returns a list of possible completions for a given caret position and text. - Clients should only call this request if the capability 'supportsCompletionsRequest' is true. + Clients should only call this request if the corresponding capability `supportsCompletionsRequest` is true. */ interface CompletionsRequest extends Request { // command: 'completions'; arguments: CompletionsArguments; } - /** Arguments for 'completions' request. */ + /** Arguments for `completions` request. */ interface CompletionsArguments { /** Returns completions in the scope of this stack frame. If not specified, the completions are returned for the global scope. */ frameId?: number; @@ -1501,7 +1505,7 @@ declare module DebugProtocol { line?: number; } - /** Response to 'completions' request. */ + /** Response to `completions` request. */ interface CompletionsResponse extends Response { body: { /** The possible completions for . */ @@ -1511,25 +1515,25 @@ declare module DebugProtocol { /** ExceptionInfo request; value of command field is 'exceptionInfo'. Retrieves the details of the exception that caused this event to be raised. - Clients should only call this request if the capability 'supportsExceptionInfoRequest' is true. + Clients should only call this request if the corresponding capability `supportsExceptionInfoRequest` is true. */ interface ExceptionInfoRequest extends Request { // command: 'exceptionInfo'; arguments: ExceptionInfoArguments; } - /** Arguments for 'exceptionInfo' request. */ + /** Arguments for `exceptionInfo` request. */ interface ExceptionInfoArguments { /** Thread for which exception information should be retrieved. */ threadId: number; } - /** Response to 'exceptionInfo' request. */ + /** Response to `exceptionInfo` request. */ interface ExceptionInfoResponse extends Response { body: { /** ID of the exception that was thrown. */ exceptionId: string; - /** Descriptive text for the exception provided by the debug adapter. */ + /** Descriptive text for the exception. */ description?: string; /** Mode that caused the exception notification to be raised. */ breakMode: ExceptionBreakMode; @@ -1540,14 +1544,14 @@ declare module DebugProtocol { /** ReadMemory request; value of command field is 'readMemory'. Reads bytes from memory at the provided location. - Clients should only call this request if the capability 'supportsReadMemoryRequest' is true. + Clients should only call this request if the corresponding capability `supportsReadMemoryRequest` is true. */ interface ReadMemoryRequest extends Request { // command: 'readMemory'; arguments: ReadMemoryArguments; } - /** Arguments for 'readMemory' request. */ + /** Arguments for `readMemory` request. */ interface ReadMemoryArguments { /** Memory reference to the base location from which data should be read. */ memoryReference: string; @@ -1557,15 +1561,15 @@ declare module DebugProtocol { count: number; } - /** Response to 'readMemory' request. */ + /** Response to `readMemory` request. */ interface ReadMemoryResponse extends Response { body?: { /** The address of the first byte of data returned. - Treated as a hex value if prefixed with '0x', or as a decimal value otherwise. + Treated as a hex value if prefixed with `0x`, or as a decimal value otherwise. */ address: string; /** The number of unreadable bytes encountered after the last successfully read byte. - This can be used to determine the number of bytes that must be skipped before a subsequent 'readMemory' request will succeed. + This can be used to determine the number of bytes that must be skipped before a subsequent `readMemory` request succeeds. */ unreadableBytes?: number; /** The bytes read from memory, encoded using base64. */ @@ -1575,20 +1579,20 @@ declare module DebugProtocol { /** WriteMemory request; value of command field is 'writeMemory'. Writes bytes to memory at the provided location. - Clients should only call this request if the capability 'supportsWriteMemoryRequest' is true. + Clients should only call this request if the corresponding capability `supportsWriteMemoryRequest` is true. */ interface WriteMemoryRequest extends Request { // command: 'writeMemory'; arguments: WriteMemoryArguments; } - /** Arguments for 'writeMemory' request. */ + /** Arguments for `writeMemory` request. */ interface WriteMemoryArguments { /** Memory reference to the base location to which data should be written. */ memoryReference: string; /** Optional offset (in bytes) to be applied to the reference location before writing data. Can be negative. */ offset?: number; - /** Optional property to control partial writes. If true, the debug adapter should attempt to write memory even if the entire memory region is not writable. In such a case the debug adapter should stop after hitting the first byte of memory that cannot be written and return the number of bytes written in the response via the 'offset' and 'bytesWritten' properties. + /** Optional property to control partial writes. If true, the debug adapter should attempt to write memory even if the entire memory region is not writable. In such a case the debug adapter should stop after hitting the first byte of memory that cannot be written and return the number of bytes written in the response via the `offset` and `bytesWritten` properties. If false or missing, a debug adapter should attempt to verify the region is writable before writing, and fail the response if it is not. */ allowPartial?: boolean; @@ -1596,26 +1600,26 @@ declare module DebugProtocol { data: string; } - /** Response to 'writeMemory' request. */ + /** Response to `writeMemory` request. */ interface WriteMemoryResponse extends Response { body?: { - /** Optional property that should be returned when 'allowPartial' is true to indicate the offset of the first byte of data successfully written. Can be negative. */ + /** Optional property that should be returned when `allowPartial` is true to indicate the offset of the first byte of data successfully written. Can be negative. */ offset?: number; - /** Optional property that should be returned when 'allowPartial' is true to indicate the number of bytes starting from address that were successfully written. */ + /** Optional property that should be returned when `allowPartial` is true to indicate the number of bytes starting from address that were successfully written. */ bytesWritten?: number; }; } /** Disassemble request; value of command field is 'disassemble'. Disassembles code stored at the provided location. - Clients should only call this request if the capability 'supportsDisassembleRequest' is true. + Clients should only call this request if the corresponding capability `supportsDisassembleRequest` is true. */ interface DisassembleRequest extends Request { // command: 'disassemble'; arguments: DisassembleArguments; } - /** Arguments for 'disassemble' request. */ + /** Arguments for `disassemble` request. */ interface DisassembleArguments { /** Memory reference to the base location containing the instructions to disassemble. */ memoryReference: string; @@ -1624,14 +1628,14 @@ declare module DebugProtocol { /** Optional offset (in instructions) to be applied after the byte offset (if any) before disassembling. Can be negative. */ instructionOffset?: number; /** Number of instructions to disassemble starting at the specified location and offset. - An adapter must return exactly this number of instructions - any unavailable instructions should be replaced with an implementation-defined 'invalid instruction' value. + An adapter must return exactly this number of instructions - any unavailable instructions should be replaced with an implementation-defined `invalid instruction` value. */ instructionCount: number; /** If true, the adapter should attempt to resolve memory addresses and other values to symbolic names. */ resolveSymbols?: boolean; } - /** Response to 'disassemble' request. */ + /** Response to `disassemble` request. */ interface DisassembleResponse extends Response { body?: { /** The list of disassembled instructions. */ @@ -1641,7 +1645,7 @@ declare module DebugProtocol { /** Information about the capabilities of a debug adapter. */ interface Capabilities { - /** The debug adapter supports the 'configurationDone' request. */ + /** The debug adapter supports the `configurationDone` request. */ supportsConfigurationDoneRequest?: boolean; /** The debug adapter supports function breakpoints. */ supportsFunctionBreakpoints?: boolean; @@ -1651,85 +1655,85 @@ declare module DebugProtocol { supportsHitConditionalBreakpoints?: boolean; /** The debug adapter supports a (side effect free) evaluate request for data hovers. */ supportsEvaluateForHovers?: boolean; - /** Available exception filter options for the 'setExceptionBreakpoints' request. */ + /** Available exception filter options for the `setExceptionBreakpoints` request. */ exceptionBreakpointFilters?: ExceptionBreakpointsFilter[]; - /** The debug adapter supports stepping back via the 'stepBack' and 'reverseContinue' requests. */ + /** The debug adapter supports stepping back via the `stepBack` and `reverseContinue` requests. */ supportsStepBack?: boolean; /** The debug adapter supports setting a variable to a value. */ supportsSetVariable?: boolean; /** The debug adapter supports restarting a frame. */ supportsRestartFrame?: boolean; - /** The debug adapter supports the 'gotoTargets' request. */ + /** The debug adapter supports the `gotoTargets` request. */ supportsGotoTargetsRequest?: boolean; - /** The debug adapter supports the 'stepInTargets' request. */ + /** The debug adapter supports the `stepInTargets` request. */ supportsStepInTargetsRequest?: boolean; - /** The debug adapter supports the 'completions' request. */ + /** The debug adapter supports the `completions` request. */ supportsCompletionsRequest?: boolean; - /** The set of characters that should trigger completion in a REPL. If not specified, the UI should assume the '.' character. */ + /** The set of characters that should trigger completion in a REPL. If not specified, the UI should assume the `.` character. */ completionTriggerCharacters?: string[]; - /** The debug adapter supports the 'modules' request. */ + /** The debug adapter supports the `modules` request. */ supportsModulesRequest?: boolean; /** The set of additional module information exposed by the debug adapter. */ additionalModuleColumns?: ColumnDescriptor[]; /** Checksum algorithms supported by the debug adapter. */ supportedChecksumAlgorithms?: ChecksumAlgorithm[]; - /** The debug adapter supports the 'restart' request. In this case a client should not implement 'restart' by terminating and relaunching the adapter but by calling the RestartRequest. */ + /** The debug adapter supports the `restart` request. In this case a client should not implement `restart` by terminating and relaunching the adapter but by calling the RestartRequest. */ supportsRestartRequest?: boolean; - /** The debug adapter supports 'exceptionOptions' on the setExceptionBreakpoints request. */ + /** The debug adapter supports `exceptionOptions` on the setExceptionBreakpoints request. */ supportsExceptionOptions?: boolean; - /** The debug adapter supports a 'format' attribute on the stackTraceRequest, variablesRequest, and evaluateRequest. */ + /** The debug adapter supports a `format` attribute on the stackTraceRequest, variablesRequest, and evaluateRequest. */ supportsValueFormattingOptions?: boolean; - /** The debug adapter supports the 'exceptionInfo' request. */ + /** The debug adapter supports the `exceptionInfo` request. */ supportsExceptionInfoRequest?: boolean; - /** The debug adapter supports the 'terminateDebuggee' attribute on the 'disconnect' request. */ + /** The debug adapter supports the `terminateDebuggee` attribute on the `disconnect` request. */ supportTerminateDebuggee?: boolean; - /** The debug adapter supports the 'suspendDebuggee' attribute on the 'disconnect' request. */ + /** The debug adapter supports the `suspendDebuggee` attribute on the `disconnect` request. */ supportSuspendDebuggee?: boolean; - /** The debug adapter supports the delayed loading of parts of the stack, which requires that both the 'startFrame' and 'levels' arguments and an optional 'totalFrames' result of the 'StackTrace' request are supported. */ + /** The debug adapter supports the delayed loading of parts of the stack, which requires that both the `startFrame` and `levels` arguments and an optional `totalFrames` result of the `StackTrace` request are supported. */ supportsDelayedStackTraceLoading?: boolean; - /** The debug adapter supports the 'loadedSources' request. */ + /** The debug adapter supports the `loadedSources` request. */ supportsLoadedSourcesRequest?: boolean; - /** The debug adapter supports logpoints by interpreting the 'logMessage' attribute of the SourceBreakpoint. */ + /** The debug adapter supports logpoints by interpreting the `logMessage` attribute of the `SourceBreakpoint`. */ supportsLogPoints?: boolean; - /** The debug adapter supports the 'terminateThreads' request. */ + /** The debug adapter supports the `terminateThreads` request. */ supportsTerminateThreadsRequest?: boolean; - /** The debug adapter supports the 'setExpression' request. */ + /** The debug adapter supports the `setExpression` request. */ supportsSetExpression?: boolean; - /** The debug adapter supports the 'terminate' request. */ + /** The debug adapter supports the `terminate` request. */ supportsTerminateRequest?: boolean; /** The debug adapter supports data breakpoints. */ supportsDataBreakpoints?: boolean; - /** The debug adapter supports the 'readMemory' request. */ + /** The debug adapter supports the `readMemory` request. */ supportsReadMemoryRequest?: boolean; - /** The debug adapter supports the 'writeMemory' request. */ + /** The debug adapter supports the `writeMemory` request. */ supportsWriteMemoryRequest?: boolean; - /** The debug adapter supports the 'disassemble' request. */ + /** The debug adapter supports the `disassemble` request. */ supportsDisassembleRequest?: boolean; - /** The debug adapter supports the 'cancel' request. */ + /** The debug adapter supports the `cancel` request. */ supportsCancelRequest?: boolean; - /** The debug adapter supports the 'breakpointLocations' request. */ + /** The debug adapter supports the `breakpointLocations` request. */ supportsBreakpointLocationsRequest?: boolean; - /** The debug adapter supports the 'clipboard' context value in the 'evaluate' request. */ + /** The debug adapter supports the `clipboard` context value in the `evaluate` request. */ supportsClipboardContext?: boolean; - /** The debug adapter supports stepping granularities (argument 'granularity') for the stepping requests. */ + /** The debug adapter supports stepping granularities (argument `granularity`) for the stepping requests. */ supportsSteppingGranularity?: boolean; /** The debug adapter supports adding breakpoints based on instruction references. */ supportsInstructionBreakpoints?: boolean; - /** The debug adapter supports 'filterOptions' as an argument on the 'setExceptionBreakpoints' request. */ + /** The debug adapter supports `filterOptions` as an argument on the `setExceptionBreakpoints` request. */ supportsExceptionFilterOptions?: boolean; - /** The debug adapter supports the 'singleThread' property on the execution requests ('continue', 'next', 'stepIn', 'stepOut', 'reverseContinue', 'stepBack'). */ + /** The debug adapter supports the `singleThread` property on the execution requests (`continue`, `next`, `stepIn`, `stepOut`, `reverseContinue`, `stepBack`). */ supportsSingleThreadExecutionRequests?: boolean; } /** An ExceptionBreakpointsFilter is shown in the UI as an filter option for configuring how exceptions are dealt with. */ interface ExceptionBreakpointsFilter { - /** The internal ID of the filter option. This value is passed to the 'setExceptionBreakpoints' request. */ + /** The internal ID of the filter option. This value is passed to the `setExceptionBreakpoints` request. */ filter: string; - /** The name of the filter option. This will be shown in the UI. */ + /** The name of the filter option. This is shown in the UI. */ label: string; /** An optional help text providing additional information about the exception filter. This string is typically shown as a hover and must be translated. */ description?: string; - /** Initial value of the filter option. If not specified a value 'false' is assumed. */ + /** Initial value of the filter option. If not specified a value `false` is assumed. */ default?: boolean; /** Controls whether a condition can be specified for this filter option. If false or missing, a condition can not be set. */ supportsCondition?: boolean; @@ -1741,7 +1745,7 @@ declare module DebugProtocol { interface Message { /** Unique identifier for the message. */ id: number; - /** A format string for the message. Embedded variables have the form '{name}'. + /** A format string for the message. Embedded variables have the form `{name}`. If variable name starts with an underscore character, the variable does not contain user data (PII) and can be safely used for telemetry purposes. */ format: string; @@ -1761,7 +1765,7 @@ declare module DebugProtocol { Two attributes are mandatory: an id identifies a module in the modules view and is used in a ModuleEvent for identifying a module for adding, updating or deleting. The name is used to minimally render the module in the UI. - Additional attributes can be added to the module. They will show up in the module View if they have a corresponding ColumnDescriptor. + Additional attributes can be added to the module. They show up in the module View if they have a corresponding ColumnDescriptor. To avoid an unnecessary proliferation of additional attributes with similar semantics but different names, we recommend to re-use attributes from the 'recommended' list below first, and only introduce new attributes if nothing appropriate could be found. */ @@ -1770,11 +1774,7 @@ declare module DebugProtocol { id: number | string; /** A name of the module. */ name: string; - /** optional but recommended attributes. - always try to use these first before introducing additional attributes. - - Logical full path to the module. The exact definition is implementation defined, but usually this would be a full path to the on-disk file for the module. - */ + /** Logical full path to the module. The exact definition is implementation defined, but usually this would be a full path to the on-disk file for the module. */ path?: string; /** True if the module is optimized. */ isOptimized?: boolean; @@ -1786,7 +1786,7 @@ declare module DebugProtocol { symbolStatus?: string; /** Logical full path to the symbol file. The exact definition is implementation defined. */ symbolFilePath?: string; - /** Module created or modified. */ + /** Module created or modified, encoded as a RFC 3339 timestamp. */ dateTimeStamp?: string; /** Address range covered by this module. */ addressRange?: string; @@ -1803,7 +1803,7 @@ declare module DebugProtocol { label: string; /** Format to use for the rendered values in this column. TBD how the format strings looks like. */ format?: string; - /** Datatype of values in this column. Defaults to 'string' if not specified. */ + /** Datatype of values in this column. Defaults to `string` if not specified. */ type?: 'string' | 'number' | 'boolean' | 'unixTimestampUTC'; /** Width of this column in characters (hint only). */ width?: number; @@ -1842,10 +1842,10 @@ declare module DebugProtocol { */ sourceReference?: number; /** An optional hint for how to present the source in the UI. - A value of 'deemphasize' can be used to indicate that the source is not available or that it is skipped on stepping. + A value of `deemphasize` can be used to indicate that the source is not available or that it is skipped on stepping. */ presentationHint?: 'normal' | 'emphasize' | 'deemphasize'; - /** The (optional) origin of this source: possible values 'internal module', 'inlined content from source map', etc. */ + /** The optional origin of this source. For example, 'internal module', 'inlined content from source map', etc. */ origin?: string; /** An optional list of sources that are related to this source. These may be the source that generated this source. */ sources?: Source[]; @@ -1860,7 +1860,7 @@ declare module DebugProtocol { /** A Stackframe contains the source location. */ interface StackFrame { /** An identifier for the stack frame. It must be unique across all threads. - This id can be used to retrieve the scopes of the frame with the 'scopesRequest' or to restart the execution of a stackframe. + This id can be used to retrieve the scopes of the frame with the `scopes` request or to restart the execution of a stackframe. */ id: number; /** The name of the stack frame, typically a method name. */ @@ -1875,14 +1875,14 @@ declare module DebugProtocol { endLine?: number; /** An optional end column of the range covered by the stack frame. */ endColumn?: number; - /** Indicates whether this frame can be restarted with the 'restart' request. Clients should only use this if the debug adapter supports the 'restart' request (capability 'supportsRestartRequest' is true). */ + /** Indicates whether this frame can be restarted with the `restart` request. Clients should only use this if the debug adapter supports the `restart` request and the corresponding capability `supportsRestartRequest` is true. */ canRestart?: boolean; /** Optional memory reference for the current instruction pointer in this frame. */ instructionPointerReference?: string; /** The module associated with this frame, if any. */ moduleId?: number | string; /** An optional hint for how to present this frame in the UI. - A value of 'label' can be used to indicate that the frame is an artificial frame that is used as a visual label or separator. A value of 'subtle' can be used to change the appearance of a frame in a 'subtle' way. + A value of `label` can be used to indicate that the frame is an artificial frame that is used as a visual label or separator. A value of `subtle` can be used to change the appearance of a frame in a 'subtle' way. */ presentationHint?: 'normal' | 'label' | 'subtle'; } @@ -1895,7 +1895,7 @@ declare module DebugProtocol { Values: 'arguments': Scope contains method arguments. 'locals': Scope contains local variables. - 'registers': Scope contains registers. Only a single 'registers' scope should be returned from a 'scopes' request. + 'registers': Scope contains registers. Only a single `registers` scope should be returned from a `scopes` request. etc. */ presentationHint?: 'arguments' | 'locals' | 'registers' | string; @@ -1924,10 +1924,10 @@ declare module DebugProtocol { } /** A Variable is a name/value pair. - Optionally a variable can have a 'type' that is shown if space permits or when hovering over the variable's name. - An optional 'kind' is used to render additional properties of the variable, e.g. different icons can be used to indicate that a variable is public or private. + Optionally a variable can have a `type` that is shown if space permits or when hovering over the variable's name. + An optional `kind` is used to render additional properties of the variable, e.g. different icons can be used to indicate that a variable is public or private. If the value is structured (has children), a handle is provided to retrieve the children with the VariablesRequest. - If the number of named or indexed children is large, the numbers should be returned via the optional 'namedVariables' and 'indexedVariables' attributes. + If the number of named or indexed children is large, the numbers should be returned via the optional `namedVariables` and `indexedVariables` attributes. The client can use this optional information to present the children in a paged UI and fetch them in chunks. */ interface Variable { @@ -1940,12 +1940,12 @@ declare module DebugProtocol { */ value: string; /** The type of the variable's value. Typically shown in the UI when hovering over the value. - This attribute should only be returned by a debug adapter if the client has passed the value true for the 'supportsVariableType' capability of the 'initialize' request. + This attribute should only be returned by a debug adapter if the corresponding capability `supportsVariableType` is true. */ type?: string; /** Properties of a variable that can be used to determine how to render the variable in the UI. */ presentationHint?: VariablePresentationHint; - /** Optional evaluatable name of this variable which can be passed to the 'EvaluateRequest' to fetch the variable's value. */ + /** Optional evaluatable name of this variable which can be passed to the `evaluate` request to fetch the variable's value. */ evaluateName?: string; /** If variablesReference is > 0, the variable is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. */ variablesReference: number; @@ -1958,7 +1958,7 @@ declare module DebugProtocol { */ indexedVariables?: number; /** Optional memory reference for the variable if the variable represents executable code, such as a function pointer. - This attribute is only required if the client has passed the value true for the 'supportsMemoryReferences' capability of the 'initialize' request. + This attribute is only required if the corresponding capability `supportsMemoryReferences` is true. */ memoryReference?: string; } @@ -1977,7 +1977,7 @@ declare module DebugProtocol { 'interface': Indicates that the object is an interface. 'mostDerivedClass': Indicates that the object is the most derived class. 'virtual': Indicates that the object is virtual, that means it is a synthetic object introduced by the adapter for rendering purposes, e.g. an index range for large arrays. - 'dataBreakpoint': Deprecated: Indicates that a data breakpoint is registered for the object. The 'hasDataBreakpoint' attribute should generally be used instead. + 'dataBreakpoint': Deprecated: Indicates that a data breakpoint is registered for the object. The `hasDataBreakpoint` attribute should generally be used instead. etc. */ kind?: 'property' | 'method' | 'class' | 'data' | 'event' | 'baseClass' | 'innerClass' | 'interface' | 'mostDerivedClass' | 'virtual' | 'dataBreakpoint' | string; @@ -2000,12 +2000,12 @@ declare module DebugProtocol { visibility?: 'public' | 'private' | 'protected' | 'internal' | 'final' | string; /** If true, clients can present the variable with a UI that supports a specific gesture to trigger its evaluation. This mechanism can be used for properties that require executing code when retrieving their value and where the code execution can be expensive and/or produce side-effects. A typical example are properties based on a getter function. - Please note that in addition to the 'lazy' flag, the variable's 'variablesReference' must refer to a variable that will provide the value through another 'variable' request. + Please note that in addition to the `lazy` flag, the variable's `variablesReference` must refer to a variable that will provide the value through another `variable` request. */ lazy?: boolean; } - /** Properties of a breakpoint location returned from the 'breakpointLocations' request. */ + /** Properties of a breakpoint location returned from the `breakpointLocations` request. */ interface BreakpointLocation { /** Start line of breakpoint location. */ line: number; @@ -2024,17 +2024,17 @@ declare module DebugProtocol { /** An optional source column of the breakpoint. */ column?: number; /** An optional expression for conditional breakpoints. - It is only honored by a debug adapter if the capability 'supportsConditionalBreakpoints' is true. + It is only honored by a debug adapter if the corresponding capability `supportsConditionalBreakpoints` is true. */ condition?: string; /** An optional expression that controls how many hits of the breakpoint are ignored. The debug adapter is expected to interpret the expression as needed. - The attribute is only honored by a debug adapter if the capability 'supportsHitConditionalBreakpoints' is true. + The attribute is only honored by a debug adapter if the corresponding capability `supportsHitConditionalBreakpoints` is true. */ hitCondition?: string; /** If this attribute exists and is non-empty, the debug adapter must not 'break' (stop) - but log the message instead. Expressions within {} are interpolated. - The attribute is only honored by a debug adapter if the capability 'supportsLogPoints' is true. + but log the message instead. Expressions within `{}` are interpolated. + The attribute is only honored by a debug adapter if the corresponding capability `supportsLogPoints` is true. */ logMessage?: string; } @@ -2044,12 +2044,12 @@ declare module DebugProtocol { /** The name of the function. */ name: string; /** An optional expression for conditional breakpoints. - It is only honored by a debug adapter if the capability 'supportsConditionalBreakpoints' is true. + It is only honored by a debug adapter if the corresponding capability `supportsConditionalBreakpoints` is true. */ condition?: string; /** An optional expression that controls how many hits of the breakpoint are ignored. The debug adapter is expected to interpret the expression as needed. - The attribute is only honored by a debug adapter if the capability 'supportsHitConditionalBreakpoints' is true. + The attribute is only honored by a debug adapter if the corresponding capability `supportsHitConditionalBreakpoints` is true. */ hitCondition?: string; } @@ -2082,12 +2082,12 @@ declare module DebugProtocol { */ offset?: number; /** An optional expression for conditional breakpoints. - It is only honored by a debug adapter if the capability 'supportsConditionalBreakpoints' is true. + It is only honored by a debug adapter if the corresponding capability `supportsConditionalBreakpoints` is true. */ condition?: string; /** An optional expression that controls how many hits of the breakpoint are ignored. The debug adapter is expected to interpret the expression as needed. - The attribute is only honored by a debug adapter if the capability 'supportsHitConditionalBreakpoints' is true. + The attribute is only honored by a debug adapter if the corresponding capability `supportsHitConditionalBreakpoints` is true. */ hitCondition?: string; } @@ -2122,16 +2122,16 @@ declare module DebugProtocol { offset?: number; } - /** The granularity of one 'step' in the stepping requests 'next', 'stepIn', 'stepOut', and 'stepBack'. + /** The granularity of one 'step' in the stepping requests `next`, `stepIn`, `stepOut`, and `stepBack`. 'statement': The step should allow the program to run until the current statement has finished executing. The meaning of a statement is determined by the adapter and it may be considered equivalent to a line. - For example 'for(int i = 0; i < 10; i++) could be considered to have 3 statements 'int i = 0', 'i < 10', and 'i++'. + For example 'for(int i = 0; i < 10; i++)' could be considered to have 3 statements 'int i = 0', 'i < 10', and 'i++'. 'line': The step should allow the program to run until the current source line has executed. 'instruction': The step should allow one instruction to execute (e.g. one x86 instruction). */ type SteppingGranularity = 'statement' | 'line' | 'instruction'; - /** A StepInTarget can be used in the 'stepIn' request and determines into which single target the stepIn request should step. */ + /** A StepInTarget can be used in the `stepIn` request and determines into which single target the stepIn request should step. */ interface StepInTarget { /** Unique identifier for a stepIn target. */ id: number; @@ -2147,8 +2147,8 @@ declare module DebugProtocol { endColumn?: number; } - /** A GotoTarget describes a code location that can be used as a target in the 'goto' request. - The possible goto targets can be determined via the 'gotoTargets' request. + /** A GotoTarget describes a code location that can be used as a target in the `goto` request. + The possible goto targets can be determined via the `gotoTargets` request. */ interface GotoTarget { /** Unique identifier for a goto target. This is used in the goto request. */ @@ -2171,16 +2171,16 @@ declare module DebugProtocol { interface CompletionItem { /** The label of this completion item. By default this is also the text that is inserted when selecting this completion. */ label: string; - /** If text is not falsy then it is inserted instead of the label. */ + /** If text is returned and not an empty string, then it is inserted instead of the label. */ text?: string; - /** A string that should be used when comparing this item with other items. When `falsy` the label is used. */ + /** A string that should be used when comparing this item with other items. If not returned or an empty string, the `label` is used instead. */ sortText?: string; /** A human-readable string with additional information about this item, like type or symbol information. */ detail?: string; /** The item's type. Typically the client uses this information to render the item in the UI with an icon. */ type?: CompletionItemType; - /** This value determines the location (in the CompletionsRequest's 'text' attribute) where the completion text is added. - If missing the text is added at the location specified by the CompletionsRequest's 'column' attribute. + /** This value determines the location (in the CompletionsRequest's `text` attribute) where the completion text is added. + If missing the text is added at the location specified by the CompletionsRequest's `column` attribute. */ start?: number; /** This value determines how many characters are overwritten by the completion text. @@ -2209,7 +2209,7 @@ declare module DebugProtocol { interface Checksum { /** The algorithm used to calculate this checksum. */ algorithm: ChecksumAlgorithm; - /** Value of the checksum. */ + /** Value of the checksum, encoded as a hexadecimal value. */ checksum: string; } @@ -2237,19 +2237,19 @@ declare module DebugProtocol { includeAll?: boolean; } - /** An ExceptionFilterOptions is used to specify an exception filter together with a condition for the 'setExceptionBreakpoints' request. */ + /** An ExceptionFilterOptions is used to specify an exception filter together with a condition for the `setExceptionBreakpoints` request. */ interface ExceptionFilterOptions { - /** ID of an exception filter returned by the 'exceptionBreakpointFilters' capability. */ + /** ID of an exception filter returned by the `exceptionBreakpointFilters` capability. */ filterId: string; /** An optional expression for conditional exceptions. - The exception will break into the debugger if the result of the condition is true. + The exception breaks into the debugger if the result of the condition is true. */ condition?: string; } /** An ExceptionOptions assigns configuration options to a set of exceptions. */ interface ExceptionOptions { - /** A path that selects a single or multiple exceptions in a tree. If 'path' is missing, the whole tree is selected. + /** A path that selects a single or multiple exceptions in a tree. If `path` is missing, the whole tree is selected. By convention the first segment of the path is a category that is used to group exceptions in the UI. */ path?: ExceptionPathSegment[]; @@ -2266,12 +2266,12 @@ declare module DebugProtocol { type ExceptionBreakMode = 'never' | 'always' | 'unhandled' | 'userUnhandled'; /** An ExceptionPathSegment represents a segment in a path that is used to match leafs or nodes in a tree of exceptions. - If a segment consists of more than one name, it matches the names provided if 'negate' is false or missing, or it matches anything except the names provided if 'negate' is true. + If a segment consists of more than one name, it matches the names provided if `negate` is false or missing, or it matches anything except the names provided if `negate` is true. */ interface ExceptionPathSegment { /** If false or missing this segment matches the names provided, otherwise it matches anything except the names provided. */ negate?: boolean; - /** Depending on the value of 'negate' the names that should match or not match. */ + /** Depending on the value of `negate` the names that should match or not match. */ names: string[]; } @@ -2293,7 +2293,7 @@ declare module DebugProtocol { /** Represents a single disassembled instruction. */ interface DisassembledInstruction { - /** The address of the instruction. Treated as a hex value if prefixed with '0x', or as a decimal value otherwise. */ + /** The address of the instruction. Treated as a hex value if prefixed with `0x`, or as a decimal value otherwise. */ address: string; /** Optional raw bytes representing the instruction and its operands, in an implementation-defined format. */ instructionBytes?: string; @@ -2316,7 +2316,7 @@ declare module DebugProtocol { endColumn?: number; } - /** Logical areas that can be invalidated by the 'invalidated' event. + /** Logical areas that can be invalidated by the `invalidated` event. Values: 'all': All previously fetched data has become invalid and needs to be refetched. 'stacks': Previously fetched stack related data has become invalid and needs to be refetched. @@ -2326,3 +2326,4 @@ declare module DebugProtocol { */ type InvalidatedAreas = 'all' | 'stacks' | 'threads' | 'variables' | string; } + diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index 3e9f0e81234..59d8a169b47 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -57,7 +57,7 @@ export async function hasChildProcesses(processId: number | undefined): Promise< const enum ShellType { cmd, powershell, bash } -export function prepareCommand(shell: string, args: string[], cwd?: string, env?: { [key: string]: string | null }): string { +export function prepareCommand(shell: string, args: string[], argsCanBeInterpretedByShell: boolean, cwd?: string, env?: { [key: string]: string | null }): string { shell = shell.trim().toLowerCase(); @@ -109,10 +109,11 @@ export function prepareCommand(shell: string, args: string[], cwd?: string, env? } } if (args.length > 0) { - const cmd = quote(args.shift()!); + const arg = args.shift()!; + const cmd = argsCanBeInterpretedByShell ? arg : quote(arg); command += (cmd[0] === '\'') ? `& ${cmd} ` : `${cmd} `; for (const a of args) { - command += (a === '<' || a === '>') ? a : quote(a); + command += (a === '<' || a === '>' || argsCanBeInterpretedByShell) ? a : quote(a); command += ' '; } } @@ -150,7 +151,7 @@ export function prepareCommand(shell: string, args: string[], cwd?: string, env? } } for (const a of args) { - command += (a === '<' || a === '>') ? a : quote(a); + command += (a === '<' || a === '>' || argsCanBeInterpretedByShell) ? a : quote(a); command += ' '; } if (env) { @@ -185,7 +186,7 @@ export function prepareCommand(shell: string, args: string[], cwd?: string, env? command += ' '; } for (const a of args) { - command += (a === '<' || a === '>') ? a : quote(a); + command += (a === '<' || a === '>' || argsCanBeInterpretedByShell) ? a : quote(a); command += ' '; } break; diff --git a/src/vs/workbench/contrib/debug/test/node/terminals.test.ts b/src/vs/workbench/contrib/debug/test/node/terminals.test.ts index 71abb2e2b89..07a16c2245f 100644 --- a/src/vs/workbench/contrib/debug/test/node/terminals.test.ts +++ b/src/vs/workbench/contrib/debug/test/node/terminals.test.ts @@ -10,67 +10,109 @@ import { prepareCommand } from 'vs/workbench/contrib/debug/node/terminals'; suite('Debug - prepareCommand', () => { test('bash', () => { assert.strictEqual( - prepareCommand('bash', ['{$} (']).trim(), + prepareCommand('bash', ['{$} ('], false).trim(), '\\{\\$\\}\\ \\('); assert.strictEqual( - prepareCommand('bash', ['hello', 'world', '--flag=true']).trim(), + prepareCommand('bash', ['hello', 'world', '--flag=true'], false).trim(), 'hello world --flag=true'); assert.strictEqual( - prepareCommand('bash', [' space arg ']).trim(), + prepareCommand('bash', [' space arg '], false).trim(), '\\ space\\ arg\\'); + + assert.strictEqual( + prepareCommand('bash', ['{$} ('], true).trim(), + '{$} ('); + assert.strictEqual( + prepareCommand('bash', ['hello', 'world', '--flag=true'], true).trim(), + 'hello world --flag=true'); + assert.strictEqual( + prepareCommand('bash', [' space arg '], true).trim(), + 'space arg'); }); test('bash - do not escape > and <', () => { assert.strictEqual( - prepareCommand('bash', ['arg1', '>', '> hello.txt', '<', '', '> hello.txt', '<', ' \\>\\ hello.txt < \\ { assert.strictEqual( - prepareCommand('cmd.exe', ['^!< ']).trim(), + prepareCommand('cmd.exe', ['^!< '], false).trim(), '"^^^!^< "'); assert.strictEqual( - prepareCommand('cmd.exe', ['hello', 'world', '--flag=true']).trim(), + prepareCommand('cmd.exe', ['hello', 'world', '--flag=true'], false).trim(), 'hello world --flag=true'); assert.strictEqual( - prepareCommand('cmd.exe', [' space arg ']).trim(), + prepareCommand('cmd.exe', [' space arg '], false).trim(), '" space arg "'); assert.strictEqual( - prepareCommand('cmd.exe', ['"A>0"']).trim(), + prepareCommand('cmd.exe', ['"A>0"'], false).trim(), '"""A^>0"""'); assert.strictEqual( - prepareCommand('cmd.exe', ['']).trim(), + prepareCommand('cmd.exe', [''], false).trim(), '""'); + + assert.strictEqual( + prepareCommand('cmd.exe', ['^!< '], true).trim(), + '^!<'); + assert.strictEqual( + prepareCommand('cmd.exe', ['hello', 'world', '--flag=true'], true).trim(), + 'hello world --flag=true'); + assert.strictEqual( + prepareCommand('cmd.exe', [' space arg '], true).trim(), + 'space arg'); + assert.strictEqual( + prepareCommand('cmd.exe', ['"A>0"'], true).trim(), + '"A>0"'); + assert.strictEqual( + prepareCommand('cmd.exe', [''], true).trim(), + ''); }); test('cmd - do not escape > and <', () => { assert.strictEqual( - prepareCommand('cmd.exe', ['arg1', '>', '> hello.txt', '<', '', '> hello.txt', '<', ' "^> hello.txt" < ^ { assert.strictEqual( - prepareCommand('powershell', ['!< ']).trim(), + prepareCommand('powershell', ['!< '], false).trim(), `& '!< '`); assert.strictEqual( - prepareCommand('powershell', ['hello', 'world', '--flag=true']).trim(), + prepareCommand('powershell', ['hello', 'world', '--flag=true'], false).trim(), `& 'hello' 'world' '--flag=true'`); assert.strictEqual( - prepareCommand('powershell', [' space arg ']).trim(), + prepareCommand('powershell', [' space arg '], false).trim(), `& ' space arg '`); assert.strictEqual( - prepareCommand('powershell', ['"A>0"']).trim(), + prepareCommand('powershell', ['"A>0"'], false).trim(), `& '"A>0"'`); assert.strictEqual( - prepareCommand('powershell', ['']).trim(), + prepareCommand('powershell', [''], false).trim(), `& ''`); + + assert.strictEqual( + prepareCommand('powershell', ['!< '], true).trim(), + '!<'); + assert.strictEqual( + prepareCommand('powershell', ['hello', 'world', '--flag=true'], true).trim(), + 'hello world --flag=true'); + assert.strictEqual( + prepareCommand('powershell', [' space arg '], true).trim(), + 'space arg'); + assert.strictEqual( + prepareCommand('powershell', ['"A>0"'], true).trim(), + '"A>0"'); + assert.strictEqual( + prepareCommand('powershell', [''], true).trim(), + ``); }); test('powershell - do not escape > and <', () => { assert.strictEqual( - prepareCommand('powershell', ['arg1', '>', '> hello.txt', '<', '', '> hello.txt', '<', ' '> hello.txt' < ' Date: Wed, 20 Jul 2022 21:03:43 +0800 Subject: [PATCH 085/197] Fix #114461 (#151824) suppress the editor during git rebase, fix #114461 Co-authored-by: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> --- extensions/git/src/git.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 0ccf22301c4..0efd437faff 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1472,7 +1472,7 @@ export class Repository { } async rebaseContinue(): Promise { - const args = ['rebase', '--continue']; + const args = ['-c', 'core.editor=true', 'rebase', '--continue']; try { await this.exec(args); From fbb7f4188e35c9dbd3c16322777c5956b8b2983b Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Wed, 20 Jul 2022 16:19:58 +0200 Subject: [PATCH 086/197] Don't poll for parent process existance in the sandboxed case (#155737) Fixes #154235: `process.pid` is always `1` on a sandboxed renderer, so avoid polling for the renderer process still running from the extension host, since the extension host is a `UtilityProcess` anyways, with a lifecycle managed by Electron. --- .../api/node/extensionHostProcess.ts | 58 ++++++++++--------- .../browser/webWorkerExtensionHost.ts | 2 +- .../common/extensionHostProtocol.ts | 5 +- .../localProcessExtensionHost.ts | 2 +- 4 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/api/node/extensionHostProcess.ts b/src/vs/workbench/api/node/extensionHostProcess.ts index bad55575c87..f19fd73d9e2 100644 --- a/src/vs/workbench/api/node/extensionHostProcess.ts +++ b/src/vs/workbench/api/node/extensionHostProcess.ts @@ -286,37 +286,39 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise= 3) { - onTerminate(`parent process ${initData.parentPid} does not exist anymore (3 x EPERM): ${e.message} (code: ${e.code}) (errno: ${e.errno})`); + if (initData.parentPid) { + // Kill oneself if one's parent dies. Much drama. + let epermErrors = 0; + setInterval(function () { + try { + process.kill(initData.parentPid, 0); // throws an exception if the main process doesn't exist anymore. + epermErrors = 0; + } catch (e) { + if (e && e.code === 'EPERM') { + // Even if the parent process is still alive, + // some antivirus software can lead to an EPERM error to be thrown here. + // Let's terminate only if we get 3 consecutive EPERM errors. + epermErrors++; + if (epermErrors >= 3) { + onTerminate(`parent process ${initData.parentPid} does not exist anymore (3 x EPERM): ${e.message} (code: ${e.code}) (errno: ${e.errno})`); + } + } else { + onTerminate(`parent process ${initData.parentPid} does not exist anymore: ${e.message} (code: ${e.code}) (errno: ${e.errno})`); } - } else { - onTerminate(`parent process ${initData.parentPid} does not exist anymore: ${e.message} (code: ${e.code}) (errno: ${e.errno})`); } - } - }, 1000); + }, 1000); - // In certain cases, the event loop can become busy and never yield - // e.g. while-true or process.nextTick endless loops - // So also use the native node module to do it from a separate thread - let watchdog: typeof nativeWatchdog; - try { - watchdog = require.__$__nodeRequire('native-watchdog'); - watchdog.start(initData.parentPid); - } catch (err) { - // no problem... - onUnexpectedError(err); + // In certain cases, the event loop can become busy and never yield + // e.g. while-true or process.nextTick endless loops + // So also use the native node module to do it from a separate thread + let watchdog: typeof nativeWatchdog; + try { + watchdog = require.__$__nodeRequire('native-watchdog'); + watchdog.start(initData.parentPid); + } catch (err) { + // no problem... + onUnexpectedError(err); + } } // Tell the outside that we are initialized diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts index fc9dcd1125e..00e360f20c2 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts @@ -281,7 +281,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost return { commit: this._productService.commit, version: this._productService.version, - parentPid: -1, + parentPid: 0, environment: { isExtensionDevelopmentDebug: this._environmentService.debugRenderer, appName: this._productService.nameLong, diff --git a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts index cc654df1787..285ab4355c2 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts @@ -20,7 +20,10 @@ export interface IExtensionDescriptionDelta { export interface IExtensionHostInitData { version: string; commit?: string; - parentPid: number; + /** + * When set to `0`, no polling for the parent process still running will happen. + */ + parentPid: number | 0; environment: IEnvironment; workspace?: IStaticWorkspaceData | null; allExtensions: IExtensionDescription[]; diff --git a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts index 97eeb78e28e..8d240be7670 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts @@ -437,7 +437,7 @@ export class SandboxLocalProcessExtensionHost implements IExtensionHost { return { commit: this._productService.commit, version: this._productService.version, - parentPid: process.pid, + parentPid: process.sandboxed ? 0 : process.pid, environment: { isExtensionDevelopmentDebug: this._isExtensionDevDebug, appRoot: this._environmentService.appRoot ? URI.file(this._environmentService.appRoot) : undefined, From 0cf530cd3cc29d1695dd60a67082e413698990f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Wed, 20 Jul 2022 16:33:38 +0200 Subject: [PATCH 087/197] AsyncDataTree: calling `getChildren` on collapsed element should not trigger `getChildren` (#155729) fixes #121567 --- src/vs/base/browser/ui/tree/asyncDataTree.ts | 10 ++++ .../browser/ui/tree/asyncDataTree.test.ts | 57 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 48cc343fc1b..a8b84679303 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -742,6 +742,16 @@ export class AsyncDataTree implements IDisposable return result; } + if (node !== this.root) { + const treeNode = this.tree.getNode(node); + + if (treeNode.collapsed) { + node.hasChildren = !!this.dataSource.hasChildren(node.element!); + node.stale = true; + return; + } + } + return this.doRefreshSubTree(node, recursive, viewStateContext); } diff --git a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts index 2fc37090431..d10d76b0b85 100644 --- a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts +++ b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts @@ -8,6 +8,7 @@ import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; import { IAsyncDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { timeout } from 'vs/base/common/async'; +import { Iterable } from 'vs/base/common/iterator'; interface Element { id: string; @@ -435,4 +436,60 @@ suite('AsyncDataTree', function () { assert.deepStrictEqual(Array.from(container.querySelectorAll('.monaco-list-row')).map(e => e.textContent), ['a', 'b2']); }); + + test('issue #121567', async () => { + const container = document.createElement('div'); + + const calls: Element[] = []; + const dataSource = new class implements IAsyncDataSource { + hasChildren(element: Element): boolean { + return !!element.children && element.children.length > 0; + } + async getChildren(element: Element) { + calls.push(element); + return element.children ?? Iterable.empty(); + } + }; + + const model = new Model({ + id: 'root', + children: [{ + id: 'a', children: [{ + id: 'aa' + }] + }] + }); + const a = model.get('a'); + + const tree = new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() }); + tree.layout(200); + + await tree.setInput(model.root); + assert.strictEqual(calls.length, 1, 'There should be a single getChildren call for the root'); + assert(tree.isCollapsible(a), 'a is collapsible'); + assert(tree.isCollapsed(a), 'a is collapsed'); + + await tree.updateChildren(a, false); + assert.strictEqual(calls.length, 1, 'There should be no changes to the calls list, since a was collapsed'); + assert(tree.isCollapsible(a), 'a is collapsible'); + assert(tree.isCollapsed(a), 'a is collapsed'); + + const children = a.children; + a.children = []; + await tree.updateChildren(a, false); + assert.strictEqual(calls.length, 1, 'There should still be no changes to the calls list, since a was collapsed'); + assert(!tree.isCollapsible(a), 'a is no longer collapsible'); + assert(tree.isCollapsed(a), 'a is collapsed'); + + a.children = children; + await tree.updateChildren(a, false); + assert.strictEqual(calls.length, 1, 'There should still be no changes to the calls list, since a was collapsed'); + assert(tree.isCollapsible(a), 'a is collapsible again'); + assert(tree.isCollapsed(a), 'a is collapsed'); + + await tree.expand(a); + assert.strictEqual(calls.length, 2, 'Finally, there should be a getChildren call for a'); + assert(tree.isCollapsible(a), 'a is still collapsible'); + assert(!tree.isCollapsed(a), 'a is expanded'); + }); }); From dfc99af92ae923841caa055260deac9b61570725 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 20 Jul 2022 07:38:47 -0700 Subject: [PATCH 088/197] Simplify bash PROMPT_COMMAND handling This change attempts to evaluate PROMPT_COMMAND in the same way that bash would, adding support or many more cases and removing the check that disables unsupported PROMPT_COMMANDS. Fixes #155221 Fixes #146197 --- .../browser/media/shellIntegration-bash.sh | 41 +++++-------------- 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh index a2f4afa1400..a7b1e8acf36 100755 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh @@ -33,12 +33,6 @@ if [ "$VSCODE_INJECTION" == "1" ]; then builtin unset VSCODE_INJECTION fi -# Disable shell integration if PROMPT_COMMAND is 2+ function calls since that is not handled. -if [[ "$PROMPT_COMMAND" =~ .*(' '.*\;)|(\;.*' ').* ]]; then - builtin unset VSCODE_SHELL_INTEGRATION - builtin return -fi - if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then builtin return fi @@ -155,25 +149,16 @@ __vsc_update_prompt __vsc_prompt_cmd_original() { __vsc_status="$?" - if [[ ${IFS+set} ]]; then - __vsc_original_ifs="$IFS" - fi - if [[ "$__vsc_original_prompt_command" =~ .+\;.+ ]]; then - IFS=';' + # Evaluate the original PROMPT_COMMAND similarly to how bash would normally + # See https://unix.stackexchange.com/a/672843 for technique + if [[ ${#__vsc_original_prompt_command[@]} -gt 1 ]]; then + for cmd in "${__vsc_original_prompt_command[@]}"; do + __vsc_status="$?" + eval "${cmd:-}" + done else - IFS=' ' + eval "${__vsc_original_prompt_command:-}" fi - builtin read -ra ADDR <<<"$__vsc_original_prompt_command" - if [[ ${__vsc_original_ifs+set} ]]; then - IFS="$__vsc_original_ifs" - unset __vsc_original_ifs - else - unset IFS - fi - for ((i = 0; i < ${#ADDR[@]}; i++)); do - (exit ${__vsc_status}) - builtin eval ${ADDR[i]} - done __vsc_precmd } @@ -182,13 +167,9 @@ __vsc_prompt_cmd() { __vsc_precmd } -if [[ "$PROMPT_COMMAND" =~ (.+\;.+) ]]; then - # item1;item2... - __vsc_original_prompt_command="$PROMPT_COMMAND" -else - # (item1, item2...) - __vsc_original_prompt_command=${PROMPT_COMMAND[@]} -fi +# PROMPT_COMMAND arrays and strings seem to be handled the same (handling only the first entry of +# the array?) +__vsc_original_prompt_command=$PROMPT_COMMAND if [[ -z "${bash_preexec_imported:-}" ]]; then if [[ -n "$__vsc_original_prompt_command" && "$__vsc_original_prompt_command" != "__vsc_prompt_cmd" ]]; then From f19251a4caaa3152b60c7914258228b313921fb1 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 20 Jul 2022 17:52:56 +0200 Subject: [PATCH 089/197] Git - Use GIT_EDITOR environment variable to suppress the git commit editor during rebase (#155748) Use GIT_EDITOR environment variable to suppress the git commit editor during rebase --- extensions/git/src/git.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 0efd437faff..92e16934334 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1472,10 +1472,10 @@ export class Repository { } async rebaseContinue(): Promise { - const args = ['-c', 'core.editor=true', 'rebase', '--continue']; + const args = ['rebase', '--continue']; try { - await this.exec(args); + await this.exec(args, { env: { GIT_EDITOR: 'true' } }); } catch (commitErr) { await this.handleCommitError(commitErr); } From 6ec95c9cb48799a2c562a08af5a22509c2481b75 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 20 Jul 2022 17:55:46 +0200 Subject: [PATCH 090/197] add menus for input1 and input2, render toolbar for them respectively (#155749) fixes https://github.com/microsoft/vscode/issues/153489 --- src/vs/platform/actions/common/actions.ts | 2 ++ .../mergeEditor/browser/commands/commands.ts | 4 ++++ .../browser/view/editors/codeEditorView.ts | 1 + .../view/editors/inputCodeEditorView.ts | 20 +++++++++++++++++++ .../mergeEditor/browser/view/mergeEditor.ts | 4 ++-- 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index a11e1185e92..5a37764affb 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -164,6 +164,8 @@ export class MenuId { static readonly InlineCompletionsActions = new MenuId('InlineCompletionsActions'); static readonly NewFile = new MenuId('NewFile'); static readonly MergeToolbar = new MenuId('MergeToolbar'); + static readonly MergeInput1Toolbar = new MenuId('MergeToolbar1Toolbar'); + static readonly MergeInput2Toolbar = new MenuId('MergeToolbar2Toolbar'); /** * Create or reuse a `MenuId` with the given identifier diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts index ca56d104c54..ef089d335b2 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts @@ -332,8 +332,10 @@ export class CompareInput1WithBaseCommand extends Action2 { ), original: 'Compare Input 1 With Base', }, + shortTitle: localize('mergeEditor.compareWithBase', 'Compare With Base'), f1: true, precondition: ctxIsMergeEditor, + menu: { id: MenuId.MergeInput1Toolbar } }); } run(accessor: ServicesAccessor, ...args: unknown[]): void { @@ -355,8 +357,10 @@ export class CompareInput2WithBaseCommand extends Action2 { ), original: 'Compare Input 2 With Base', }, + shortTitle: localize('mergeEditor.compareWithBase', 'Compare With Base'), f1: true, precondition: ctxIsMergeEditor, + menu: { id: MenuId.MergeInput2Toolbar } }); } run(accessor: ServicesAccessor, ...args: unknown[]): void { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts index d1988a2cfd9..5ca3ec0e935 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts @@ -28,6 +28,7 @@ export abstract class CodeEditorView extends Disposable { h('span.title@title'), h('span.description@description'), h('span.detail@detail'), + h('span.toolbar@toolbar'), ]), h('div.container', [ h('div.gutter@gutterDiv'), diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts index abadef681ba..14e739314c3 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts @@ -5,6 +5,7 @@ import * as dom from 'vs/base/browser/dom'; import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; +import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { Action, IAction, Separator } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -15,6 +16,9 @@ import { EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/edi import { IModelDeltaDecoration, MinimapPosition, OverviewRulerLane } from 'vs/editor/common/model'; import { CodeLensContribution } from 'vs/editor/contrib/codelens/browser/codelensController'; import { localize } from 'vs/nls'; +import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { attachToggleStyler } from 'vs/platform/theme/common/styler'; @@ -231,9 +235,12 @@ export class InputCodeEditorView extends CodeEditorView { constructor( public readonly inputNumber: 1 | 2, + titleMenuId: MenuId, @IInstantiationService instantiationService: IInstantiationService, @IContextMenuService contextMenuService: IContextMenuService, @IThemeService themeService: IThemeService, + @IMenuService menuService: IMenuService, + @IContextKeyService contextKeyService: IContextKeyService, ) { super(instantiationService); @@ -247,6 +254,19 @@ export class InputCodeEditorView extends CodeEditorView { createView: (item, target) => new MergeConflictGutterItemView(item, target, contextMenuService, themeService), }) ); + + // title menu + const titleMenu = menuService.createMenu(titleMenuId, contextKeyService); + const toolBar = new ToolBar(this.htmlElements.toolbar, contextMenuService); + const toolBarUpdate = () => { + const secondary: IAction[] = []; + createAndFillInActionBarActions(titleMenu, { renderShortTitle: true }, secondary); + toolBar.setActions([], secondary); + }; + this._store.add(toolBar); + this._store.add(titleMenu); + this._store.add(titleMenu.onDidChange(toolBarUpdate)); + toolBarUpdate(); } protected override getEditorContributions(): IEditorContributionDescription[] | undefined { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index 4f71c690a1b..8cb9a104237 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -87,8 +87,8 @@ export class MergeEditor extends AbstractTextEditor { private readonly _sessionDisposables = new DisposableStore(); private _grid!: Grid; - private readonly input1View = this._register(this.instantiationService.createInstance(InputCodeEditorView, 1)); - private readonly input2View = this._register(this.instantiationService.createInstance(InputCodeEditorView, 2)); + private readonly input1View = this._register(this.instantiationService.createInstance(InputCodeEditorView, 1, MenuId.MergeInput1Toolbar)); + private readonly input2View = this._register(this.instantiationService.createInstance(InputCodeEditorView, 2, MenuId.MergeInput2Toolbar)); private readonly inputResultView = this._register(this.instantiationService.createInstance(ResultCodeEditorView)); private readonly _layoutMode: MergeEditorLayout; From 1c1c0d50625978e5455acf4ff5118d2549c67420 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 20 Jul 2022 17:57:48 +0200 Subject: [PATCH 091/197] sandbox - deprecate `process.pid` (#155750) --- src/vs/base/parts/sandbox/electron-sandbox/globals.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts index 7d9b29a6e3a..d89c22a4f0d 100644 --- a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts +++ b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts @@ -36,6 +36,10 @@ export interface ISandboxNodeProcess extends INodeProcess { /** * The `process.pid` property returns the PID of the process. + * + * @deprecated this property will be removed once sandbox is enabled. + * + * TODO@bpasero remove this property when sandbox is on */ readonly pid: number; From 843fd7ff9b79a435e0b78734a737c10e2bca4b8e Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Tue, 19 Jul 2022 16:13:47 -0700 Subject: [PATCH 092/197] fix on some theming, added experimental setting --- .../contrib/codeAction/browser/codeActionContributions.ts | 2 +- src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts | 2 +- .../codeAction/browser/codeActionWidgetContribution.ts | 2 +- src/vs/editor/contrib/codeAction/browser/media/action.css | 5 +++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionContributions.ts b/src/vs/editor/contrib/codeAction/browser/codeActionContributions.ts index 359bbf7941f..59cd3327bea 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionContributions.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionContributions.ts @@ -5,7 +5,7 @@ import { registerEditorAction, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { AutoFixAction, CodeActionCommand, FixAllAction, OrganizeImportsAction, QuickFixAction, QuickFixController, RefactorAction, RefactorPreview, SourceAction } from 'vs/editor/contrib/codeAction/browser/codeActionCommands'; - +import 'vs/editor/contrib/codeAction/browser/codeActionWidgetContribution'; registerEditorContribution(QuickFixController.ID, QuickFixController); registerEditorAction(QuickFixAction); diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 49e68a2aed4..7815014655b 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -188,7 +188,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { } private isCodeActionWidgetEnabled(model: ITextModel): boolean { - return this._configurationService.getValue('editor.econtrib.codeAction.enabled', { + return this._configurationService.getValue('editor.contrib.experimental.codeActionWidget.enabled', { resource: model.uri }); } diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionWidgetContribution.ts b/src/vs/editor/contrib/codeAction/browser/codeActionWidgetContribution.ts index 3e2acafae0f..9530c5d0e69 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionWidgetContribution.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionWidgetContribution.ts @@ -11,7 +11,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; Registry.as(Extensions.Configuration).registerConfiguration({ ...editorConfigurationBaseNode, properties: { - 'editor.experimental.codeActionWidget.enabled': { + 'editor.contrib.experimental.codeActionWidget.enabled': { type: 'boolean', scope: ConfigurationScope.LANGUAGE_OVERRIDABLE, description: nls.localize('codeActionWidget', "Enable/disable opening the experimental Code Action Widget."), diff --git a/src/vs/editor/contrib/codeAction/browser/media/action.css b/src/vs/editor/contrib/codeAction/browser/media/action.css index 2fcf056c618..a037d9e0660 100644 --- a/src/vs/editor/contrib/codeAction/browser/media/action.css +++ b/src/vs/editor/contrib/codeAction/browser/media/action.css @@ -41,6 +41,7 @@ -ms-user-select: none; border: none !important; border-width: 0px !important; + color: var(red) !important; } /* .codeActionMenuWidget .monaco-list:not(.element-focus) { @@ -73,8 +74,7 @@ .codeActionMenuWidget .monaco-list .monaco-list-row:hover:not(.option-disabled), .codeActionMenuWidget .monaco-list .moncao-list-row.focused:not(.option-disabled) { - color: var(--vscode-editorSuggestWidget-selectedForeground); - background-color: rgb(4, 57, 94) !important; + background-color: var(--vscode-menu-selectionBackground) !important; } .codeActionMenuWidget .monaco-list .option-disabled, @@ -86,6 +86,7 @@ -moz-user-select: none; -ms-user-select: none; user-select: none; + /* color: var(--vscode-list-inactiveSelectionBackground) !important; */ } .codeActionMenuWidget .monaco-list .separator { From 7d352660c2d165bf5fb6a9ec273ba0a167932716 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Wed, 20 Jul 2022 09:05:33 -0700 Subject: [PATCH 093/197] code clean up and addressing comments --- .../codeAction/browser/codeActionCommands.ts | 18 ++-- .../codeAction/browser/codeActionMenu.ts | 102 +++++++----------- .../browser/codeActionWidgetContribution.ts | 5 +- .../codeAction/browser/media/action.css | 2 +- 4 files changed, 53 insertions(+), 74 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts index 2da6f125138..62647863cf5 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts @@ -134,15 +134,21 @@ export class QuickFixController extends Disposable implements IEditorContributio } public hideCodeActionMenu() { - this._ui.getValue().hideCodeActionWidget(); + if (this._ui.hasValue()) { + this._ui.getValue().hideCodeActionWidget(); + } } public navigateCodeActionList(navUp: Boolean) { - this._ui.getValue().navigateList(navUp); + if (this._ui.hasValue()) { + this._ui.getValue().navigateList(navUp); + } } public selectedOption() { - this._ui.getValue().onEnter(); + if (this._ui.hasValue()) { + this._ui.getValue().onEnter(); + } } public showCodeActions(trigger: CodeActionTrigger, actions: CodeActionSet, at: IAnchor | IPosition) { @@ -522,7 +528,7 @@ registerEditorCommand(new CodeActionContribution({ })); registerEditorCommand(new CodeActionContribution({ - id: 'navigatePrevious', + id: 'focusPreviousCodeAction', precondition: Context.Visible, handler(x) { x.navigateCodeActionList(true); @@ -535,7 +541,7 @@ registerEditorCommand(new CodeActionContribution({ })); registerEditorCommand(new CodeActionContribution({ - id: 'navigateNext', + id: 'focusNextCodeAction', precondition: Context.Visible, handler(x) { x.navigateCodeActionList(false); @@ -548,7 +554,7 @@ registerEditorCommand(new CodeActionContribution({ })); registerEditorCommand(new CodeActionContribution({ - id: 'onEnterSelect', + id: 'onEnterSelectCodeAction', precondition: Context.Visible, handler(x) { x.selectedOption(); diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 7815014655b..760261d5aec 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -120,8 +120,8 @@ class CodeMenuRenderer implements IListRenderer; - private readonly editor: ICodeEditor; private readonly _showingActions = this._register(new MutableDisposable()); private readonly _disposables = new DisposableStore(); - private readonly _onDidHideContextMenu = new Emitter(); private codeActionList!: List; private options: ICodeActionMenuItem[] = []; private _visible: boolean = false; - readonly onDidHideContextMenu = this._onDidHideContextMenu.event; - private _ctxMenuWidgetVisible!: IContextKey; + private _ctxMenuWidgetVisible: IContextKey; private viewItems: ICodeActionMenuItem[] = []; - private focusedEnabledItem!: number; - private currSelectedItem!: number; + private focusedEnabledItem: number | undefined; + private currSelectedItem: number = 0; public static readonly ID: string = 'editor.contrib.codeActionMenu'; @@ -159,7 +155,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { } private readonly _keybindingResolver: CodeActionKeybindingResolver; - listRenderer: any; + private listRenderer: CodeMenuRenderer = new CodeMenuRenderer(); constructor( private readonly _editor: ICodeEditor, @@ -175,12 +171,11 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { ) { super(); - this.editor = _editor; this._keybindingResolver = new CodeActionKeybindingResolver({ getKeybindings: () => keybindingService.getKeybindings() }); - this._ctxMenuWidgetVisible = Context.Visible.bindTo(_contextKeyService); + this._ctxMenuWidgetVisible = Context.Visible.bindTo(this._contextKeyService); } get isVisible(): boolean { @@ -188,7 +183,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { } private isCodeActionWidgetEnabled(model: ITextModel): boolean { - return this._configurationService.getValue('editor.contrib.experimental.codeActionWidget.enabled', { + return this._configurationService.getValue('editor.experimental.useCustomCodeActionMenu', { resource: model.uri }); } @@ -204,14 +199,10 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { } } - private _onListFocus(e: IListEvent): void { - this._ctxMenuWidgetIsFocused?.set(true); - } private renderCodeActionMenuList(element: HTMLElement, inputArray: IAction[]): IDisposable { const renderDisposables = new DisposableStore(); const renderMenu = document.createElement('div'); - this.listRenderer = new CodeMenuRenderer(); const height = inputArray.length * 27; renderMenu.style.height = String(height) + 'px'; @@ -231,10 +222,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { }, [this.listRenderer], { keyboardSupport: false } ); - if (this.codeActionList) { - renderDisposables.add(this.codeActionList.onDidChangeSelection(e => this._onListSelection(e))); - renderDisposables.add(this.codeActionList.onDidChangeFocus(e => this._onListFocus(e))); - } + renderDisposables.add(this.codeActionList.onDidChangeSelection(e => this._onListSelection(e))); // Populating the list widget and tracking enabled options. inputArray.forEach((item, index) => { @@ -257,6 +245,8 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { // resize observer - can be used in the future since list widget supports dynamic height but not width const maxWidth = Math.max(...arr); + + // 40 is the additional padding for the list widget (20 left, 20 right) renderMenu.style.width = maxWidth + 40 + 'px'; this.codeActionList.layout(height, maxWidth); @@ -349,8 +339,31 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { this.dispose(); } + codeActionTelemetry(openedFromString: CodeActionTriggerSource, didCancel: boolean, CodeActions: CodeActionSet) { + type ApplyCodeActionEvent = { + codeActionFrom: CodeActionTriggerSource; + validCodeActions: number; + cancelled: boolean; + }; + + type ApplyCodeEventClassification = { + codeActionFrom: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of action used to opened the code action.' }; + validCodeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The total number of valid actions that are highlighted and can be used.' }; + cancelled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The indicator if the menu was selected or cancelled.' }; + owner: 'mjbvz'; + comment: 'Event used to gain insights into how code actions are being triggered'; + }; + + this._telemetryService.publicLog2('codeAction.applyCodeAction', { + codeActionFrom: openedFromString, + validCodeActions: CodeActions.validActions.length, + cancelled: didCancel, + + }); + } + public async show(trigger: CodeActionTrigger, codeActions: CodeActionSet, at: IAnchor | IPosition, options: CodeActionShowOptions): Promise { - const model = this.editor.getModel(); + const model = this._editor.getModel(); if (!model) { return; } @@ -383,27 +396,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { render: (container: HTMLElement) => this.renderCodeActionMenuList(container, menuActions), onHide: (didCancel) => { const openedFromString = (options.fromLightbulb) ? CodeActionTriggerSource.Lightbulb : trigger.triggerAction; - - type ApplyCodeActionEvent = { - codeActionFrom: CodeActionTriggerSource; - validCodeActions: number; - cancelled: boolean; - }; - - type ApplyCodeEventClassification = { - codeActionFrom: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of action used to opened the code action.' }; - validCodeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The total number of valid actions that are highlighted and can be used.' }; - cancelled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The indicator if the menu was selected or cancelled.' }; - owner: 'mjbvz'; - comment: 'Event used to gain insights into how code actions are being triggered'; - }; - - this._telemetryService.publicLog2('codeAction.applyCodeAction', { - codeActionFrom: openedFromString, - validCodeActions: codeActions.validActions.length, - cancelled: didCancel, - - }); + this.codeActionTelemetry(openedFromString, didCancel, codeActions); this._visible = false; this._editor.focus(); }, @@ -417,28 +410,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { getActions: () => menuActions, onHide: (didCancel) => { const openedFromString = (options.fromLightbulb) ? CodeActionTriggerSource.Lightbulb : trigger.triggerAction; - - type ApplyCodeActionEvent = { - codeActionFrom: CodeActionTriggerSource; - validCodeActions: number; - cancelled: boolean; - }; - - type ApplyCodeEventClassification = { - codeActionFrom: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of action used to opened the code action.' }; - validCodeActions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The total number of valid actions that are highlighted and can be used.' }; - cancelled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The indicator if the menu was selected or cancelled.' }; - owner: 'mjbvz'; - comment: 'Event used to gain insights into how code actions are being triggered'; - }; - - this._telemetryService.publicLog2('codeAction.applyCodeAction', { - codeActionFrom: openedFromString, - validCodeActions: codeActions.validActions.length, - cancelled: didCancel, - - }); - + this.codeActionTelemetry(openedFromString, didCancel, codeActions); this._visible = false; this._editor.focus(); }, diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionWidgetContribution.ts b/src/vs/editor/contrib/codeAction/browser/codeActionWidgetContribution.ts index 9530c5d0e69..3e7c2df8fda 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionWidgetContribution.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionWidgetContribution.ts @@ -11,10 +11,11 @@ import { Registry } from 'vs/platform/registry/common/platform'; Registry.as(Extensions.Configuration).registerConfiguration({ ...editorConfigurationBaseNode, properties: { - 'editor.contrib.experimental.codeActionWidget.enabled': { + 'editor.experimental.useCustomCodeActionMenu': { type: 'boolean', + tags: ['experimental'], scope: ConfigurationScope.LANGUAGE_OVERRIDABLE, - description: nls.localize('codeActionWidget', "Enable/disable opening the experimental Code Action Widget."), + description: nls.localize('codeActionWidget', "Enabling this adjusts how the code action menu is rendered."), default: false, }, } diff --git a/src/vs/editor/contrib/codeAction/browser/media/action.css b/src/vs/editor/contrib/codeAction/browser/media/action.css index a037d9e0660..b47e05831b4 100644 --- a/src/vs/editor/contrib/codeAction/browser/media/action.css +++ b/src/vs/editor/contrib/codeAction/browser/media/action.css @@ -86,7 +86,7 @@ -moz-user-select: none; -ms-user-select: none; user-select: none; - /* color: var(--vscode-list-inactiveSelectionBackground) !important; */ + color: var(--vscode-list-inactiveSelectionBackground) !important; } .codeActionMenuWidget .monaco-list .separator { From 09259abf3c10b61d881e468b6eba7d6589879ed3 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 20 Jul 2022 18:08:54 +0200 Subject: [PATCH 094/197] Comments panel: css issue causing the position of "Ln" to be shifted (#155751) Fixes #155198 --- src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 42ebc26458d..477ed5f7cee 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -219,7 +219,7 @@ export class CommentNodeRenderer implements IListRenderer templateData.disposables.push(disposables); const renderedComment = this.getRenderedComment(originalComment.comment.body, disposables); templateData.disposables.push(renderedComment); - templateData.threadMetadata.commentPreview.appendChild(renderedComment.element); + templateData.threadMetadata.commentPreview.appendChild(renderedComment.element.firstElementChild ?? renderedComment.element); templateData.threadMetadata.commentPreview.title = renderedComment.element.textContent ?? ''; } From e88e58055662793eba9b7313ab38001aec3bca0a Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 20 Jul 2022 09:18:18 -0700 Subject: [PATCH 095/197] Bump distro (#155754) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c86ae74e45b..3fa8af2a900 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.70.0", - "distro": "1a72c46622967eab6ea48516a2153c55d7e18e53", + "distro": "1988bf7b08d3ad34aa3ff8f9e51d6e310b001c4f", "author": { "name": "Microsoft Corporation" }, From 3d12b57d82b2f97fa24a9a585a3e1e347295a538 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 20 Jul 2022 18:51:39 +0200 Subject: [PATCH 096/197] Fix tree error from telemetry (#155719) Fixes #155496 --- .../workbench/browser/parts/views/treeView.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 67efd200c19..4f8aa7d5b3a 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -226,7 +226,8 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @IHoverService private readonly hoverService: IHoverService, @IContextKeyService contextKeyService: IContextKeyService, - @IActivityService private readonly activityService: IActivityService + @IActivityService private readonly activityService: IActivityService, + @ILogService private readonly logService: ILogService ) { super(); this.root = new Root(); @@ -799,7 +800,14 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { const tree = this.tree; if (tree && this.visible) { this.refreshing = true; - await Promise.all(elements.map(element => tree.updateChildren(element, true, true))); + try { + await Promise.all(elements.map(element => tree.updateChildren(element, true, true))); + } catch (e) { + // When multiple calls are made to refresh the tree in quick succession, + // we can get a "Tree element not found" error. This is expected. + // Ideally this is fixable, so log instead of ignoring so the error is preserved. + this.logService.error(e); + } this.refreshing = false; this._onDidCompleteRefresh.fire(); this.updateContentAreas(); @@ -1283,9 +1291,10 @@ export class CustomTreeView extends AbstractTreeView { @IHoverService hoverService: IHoverService, @IExtensionService private readonly extensionService: IExtensionService, @IActivityService activityService: IActivityService, - @ITelemetryService private readonly telemetryService: ITelemetryService + @ITelemetryService private readonly telemetryService: ITelemetryService, + @ILogService logService: ILogService, ) { - super(id, title, themeService, instantiationService, commandService, configurationService, progressService, contextMenuService, keybindingService, notificationService, viewDescriptorService, hoverService, contextKeyService, activityService); + super(id, title, themeService, instantiationService, commandService, configurationService, progressService, contextMenuService, keybindingService, notificationService, viewDescriptorService, hoverService, contextKeyService, activityService, logService); } protected activate() { From 4d215cb441b213ccc5e284d842eb93e8b5771f44 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Wed, 20 Jul 2022 09:55:57 -0700 Subject: [PATCH 097/197] comment telemetry events (#155758) --- .../browser/languageDetectionWorkerServiceImpl.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts index 4eebf46840b..b2a2279b3c5 100644 --- a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts +++ b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts @@ -204,9 +204,10 @@ export class LanguageDetectionWorkerHost { type LanguageDetectionStats = { languages: string; confidences: string; timeSpent: number }; type LanguageDetectionStatsClassification = { owner: 'TylerLeonhardt'; - languages: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - confidences: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - timeSpent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + comment: 'Helps understand how effective language detection is via confidences and how long it takes to run'; + languages: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The languages that are guessed' }; + confidences: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The confidences of each langauge guessed' }; + timeSpent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The time it took to run language detection' }; }; this._telemetryService.publicLog2('automaticlanguagedetection.stats', { @@ -339,8 +340,9 @@ export class LanguageDetectionWorkerClient extends EditorWorkerClient { type LanguageDetectionPerfClassification = { owner: 'TylerLeonhardt'; - timeSpent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; - detection: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + comment: 'Helps understand how effective language detection and how long it takes to run'; + timeSpent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The time it took to run language detection' }; + detection: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The language that was detected' }; }; this._telemetryService.publicLog2(LanguageDetectionStatsId, { From b419fdfe3f500519c647b90b7f56d357df218df0 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Wed, 20 Jul 2022 11:52:29 -0700 Subject: [PATCH 098/197] allow changes in shadow root for same context view container (#155771) --- src/vs/platform/contextview/browser/contextViewService.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/contextview/browser/contextViewService.ts b/src/vs/platform/contextview/browser/contextViewService.ts index 57be989bcc4..3a2d4a47dbc 100644 --- a/src/vs/platform/contextview/browser/contextViewService.ts +++ b/src/vs/platform/contextview/browser/contextViewService.ts @@ -14,6 +14,7 @@ export class ContextViewService extends Disposable implements IContextViewServic private currentViewDisposable: IDisposable = Disposable.None; private contextView: ContextView; private container: HTMLElement | null; + private shadowRoot: boolean | undefined; constructor( @ILayoutService readonly layoutService: ILayoutService @@ -35,7 +36,7 @@ export class ContextViewService extends Disposable implements IContextViewServic showContextView(delegate: IContextViewDelegate, container?: HTMLElement, shadowRoot?: boolean): IDisposable { if (container) { - if (container !== this.container) { + if (container !== this.container || this.shadowRoot !== shadowRoot) { this.container = container; this.setContainer(container, shadowRoot ? ContextViewDOMPosition.FIXED_SHADOW : ContextViewDOMPosition.FIXED); } @@ -46,6 +47,8 @@ export class ContextViewService extends Disposable implements IContextViewServic } } + this.shadowRoot = shadowRoot; + this.contextView.show(delegate); const disposable = toDisposable(() => { From 0fa857c9a16309cff0fe46d888c3edc8bf2ba528 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Tue, 19 Jul 2022 11:00:26 -0700 Subject: [PATCH 099/197] okay now things are actually fresh and working --- extensions/ipynb/package-lock.json | 247 ++++++++++++++++++ extensions/ipynb/package.json | 7 +- .../ipynb/src/cellAttachmentRenderer.ts | 149 +++++++++++ extensions/ipynb/yarn.lock | 96 +++++-- 4 files changed, 477 insertions(+), 22 deletions(-) create mode 100644 extensions/ipynb/package-lock.json create mode 100644 extensions/ipynb/src/cellAttachmentRenderer.ts diff --git a/extensions/ipynb/package-lock.json b/extensions/ipynb/package-lock.json new file mode 100644 index 00000000000..6e11a31cf9b --- /dev/null +++ b/extensions/ipynb/package-lock.json @@ -0,0 +1,247 @@ +{ + "name": "ipynb", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "ipynb", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@enonic/fnv-plus": "^1.3.0", + "detect-indent": "^6.0.0", + "markdown-it": "^12.3.2", + "uuid": "^8.3.2" + }, + "devDependencies": { + "@jupyterlab/nbformat": "^3.2.9", + "@types/markdown-it": "12.2.3", + "@types/uuid": "^8.3.1" + }, + "engines": { + "vscode": "^1.57.0" + } + }, + "node_modules/@enonic/fnv-plus": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@enonic/fnv-plus/-/fnv-plus-1.3.0.tgz", + "integrity": "sha512-BCN9uNWH8AmiP7BXBJqEinUY9KXalmRzo+L0cB/mQsmFfzODxwQrbvxCHXUNH2iP+qKkWYtB4vyy8N62PViMFw==", + "license": "MIT" + }, + "node_modules/@jupyterlab/nbformat": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/@jupyterlab/nbformat/-/nbformat-3.2.9.tgz", + "integrity": "sha512-WSf9OQo8yfFjyodbXRdFoaNwMkaAL5jFZiD6V2f8HqI380ipansWrrV7R9CGzPfgKHpUGZMO1tYKmUwzMhvZ4w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@lumino/coreutils": "^1.5.3" + } + }, + "node_modules/@lumino/coreutils": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@lumino/coreutils/-/coreutils-1.12.0.tgz", + "integrity": "sha512-DSglh4ylmLi820CNx9soJmDJCpUgymckdWeGWuN0Ash5g60oQvrQDfosVxEhzmNvtvXv45WZEqSBzDP6E5SEmQ==", + "dev": true, + "license": "BSD-3-Clause", + "peerDependencies": { + "crypto": "1.0.1" + } + }, + "node_modules/@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "dev": true + }, + "node_modules/@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "dev": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "dev": true + }, + "node_modules/@types/uuid": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz", + "integrity": "sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + } + }, + "dependencies": { + "@enonic/fnv-plus": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@enonic/fnv-plus/-/fnv-plus-1.3.0.tgz", + "integrity": "sha512-BCN9uNWH8AmiP7BXBJqEinUY9KXalmRzo+L0cB/mQsmFfzODxwQrbvxCHXUNH2iP+qKkWYtB4vyy8N62PViMFw==" + }, + "@jupyterlab/nbformat": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/@jupyterlab/nbformat/-/nbformat-3.2.9.tgz", + "integrity": "sha512-WSf9OQo8yfFjyodbXRdFoaNwMkaAL5jFZiD6V2f8HqI380ipansWrrV7R9CGzPfgKHpUGZMO1tYKmUwzMhvZ4w==", + "dev": true, + "requires": { + "@lumino/coreutils": "^1.5.3" + } + }, + "@lumino/coreutils": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@lumino/coreutils/-/coreutils-1.12.0.tgz", + "integrity": "sha512-DSglh4ylmLi820CNx9soJmDJCpUgymckdWeGWuN0Ash5g60oQvrQDfosVxEhzmNvtvXv45WZEqSBzDP6E5SEmQ==", + "dev": true, + "requires": {} + }, + "@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "dev": true + }, + "@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "dev": true, + "requires": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "dev": true + }, + "@types/uuid": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz", + "integrity": "sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==", + "dev": true + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==" + }, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" + }, + "linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "requires": { + "uc.micro": "^1.0.1" + } + }, + "markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "requires": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + } + } +} diff --git a/extensions/ipynb/package.json b/extensions/ipynb/package.json index 1e251a5a19b..0c8a77aa95d 100644 --- a/extensions/ipynb/package.json +++ b/extensions/ipynb/package.json @@ -70,16 +70,19 @@ } }, "scripts": { - "compile": "npx gulp compile-extension:ipynb", - "watch": "npx gulp watch-extension:ipynb" + "compile": "npx gulp compile-extension:ipynb && npm run build-notebook", + "watch": "npx gulp watch-extension:ipynb", + "build-notebook": "node ./esbuild" }, "dependencies": { "@enonic/fnv-plus": "^1.3.0", "detect-indent": "^6.0.0", + "markdown-it": "^12.3.2", "uuid": "^8.3.2" }, "devDependencies": { "@jupyterlab/nbformat": "^3.2.9", + "@types/markdown-it": "12.2.3", "@types/uuid": "^8.3.1" }, "repository": { diff --git a/extensions/ipynb/src/cellAttachmentRenderer.ts b/extensions/ipynb/src/cellAttachmentRenderer.ts new file mode 100644 index 00000000000..262df2cb572 --- /dev/null +++ b/extensions/ipynb/src/cellAttachmentRenderer.ts @@ -0,0 +1,149 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import type * as MarkdownIt from 'markdown-it'; +// import type * as MarkdownItToken from 'markdown-it/lib/token'; +import type { RendererContext } from 'vscode-notebook-renderer'; + +interface MarkdownItRenderer { + extendMarkdownIt(fn: (md: MarkdownIt) => void): void; +} + +export async function activate(ctx: RendererContext) { + console.log('CellAttachmentRenderer activation'); + const markdownItRenderer = (await ctx.getRenderer('vscode.markdown-it-renderer')) as MarkdownItRenderer | any; + if (!markdownItRenderer) { + throw new Error(`Could not load 'vscode.markdown-it-renderer'`); + } + markdownItRenderer.extendMarkdownIt((md: MarkdownIt) => { + // addCellAttachmentRendering(md); + // const originalRender = md.render; + + md.renderInline = function () { + const outputInfo = arguments[1].outputItem; + + + const text = outputInfo.text(); + let markdownText = outputInfo.mime.startsWith('text/x-') ? `\`\`\`${outputInfo.mime.substr(7)}\n${text}\n\`\`\`` + : (outputInfo.mime.startsWith('application/') ? `\`\`\`${outputInfo.mime.substr(12)}\n${text}\n\`\`\`` : text); + const attachments: Record> = (outputInfo.metadata as any).custom.attachments; + if (attachments) { + let attachmentName: keyof typeof attachments; + for (attachmentName in attachments) { + const [attachmentKey, attachmentVal] = Object.entries(attachments[attachmentName])[0]; + const attachmentData = 'data:' + attachmentKey + ';base64,' + attachmentVal; + markdownText = markdownText.replace(`attachment:${attachmentName}`, attachmentData); + } + } + // const unsanitizedRenderedMarkdown = + return markdownText; + }; + }); +} + +// function addCellAttachmentRendering(md: MarkdownIt): void { +// console.log('cell render fxn'); +// const cell_attachment = md.renderer.rules.cell_attachment; +// md.renderer.rules.cell_attachment = (tokens: MarkdownItToken[], idx: number, options, env, self) => { +// // const token = tokens[idx]; +// console.log('rule fxn'); +// const outputInfo = env.outputItem; +// const text = outputInfo.text(); +// let markdownText = outputInfo.mime.startsWith('text/x-') ? `\`\`\`${outputInfo.mime.substr(7)}\n${text}\n\`\`\`` +// : (outputInfo.mime.startsWith('application/') ? `\`\`\`${outputInfo.mime.substr(12)}\n${text}\n\`\`\`` : text); +// const attachments: Record> = (outputInfo.metadata as any).custom.attachments; +// if (attachments) { +// let attachmentName: keyof typeof attachments; +// for (attachmentName in attachments) { +// const [attachmentKey, attachmentVal] = Object.entries(attachments[attachmentName])[0]; +// const attachmentData = 'data:' + attachmentKey + ';base64,' + attachmentVal; +// markdownText = markdownText.replace(`attachment:${attachmentName}`, attachmentData); +// } +// } +// // const unsanitizedRenderedMarkdown = +// return md.render(markdownText, { outputItem: outputInfo, }); +// // return self.renderToken(tokens, idx, options); +// }; + +// const originalRender = md.render; +// md.render = function () { +// return originalRender.apply(this, arguments as any); +// }; +// } + + + + // md.renderer.rules.attachmentRender = (tokens: MarkdownItToken[], idx: number, options, env, self) => { + // console.log('rule fxn'); + // const outputInfo = env.outputItem; + // const text = outputInfo.text(); + // let markdownText = outputInfo.mime.startsWith('text/x-') ? `\`\`\`${outputInfo.mime.substr(7)}\n${text}\n\`\`\`` + // : (outputInfo.mime.startsWith('application/') ? `\`\`\`${outputInfo.mime.substr(12)}\n${text}\n\`\`\`` : text); + // const attachments: Record> = (outputInfo.metadata as any).custom.attachments; + // if (attachments) { + // let attachmentName: keyof typeof attachments; + // for (attachmentName in attachments) { + // const [attachmentKey, attachmentVal] = Object.entries(attachments[attachmentName])[0]; + // const attachmentData = 'data:' + attachmentKey + ';base64,' + attachmentVal; + // markdownText = markdownText.replace(`attachment:${attachmentName}`, attachmentData); + // } + // } + // // const unsanitizedRenderedMarkdown = + // const unsanitizedRenderedMarkdown = md.render(markdownText, { outputItem: outputInfo, }); + + + // return self.renderToken(tokens, idx, options); + // }; + + // const originalRender = md.render; + // md.render = function () { + // return originalRender.apply(this, arguments as any); + // }; + + +// FIXME: kinda works. data is there at least +// function addCellAttachmentRendering(md: MarkdownIt): void { +// console.log('cell render fxn'); +// md.renderer.rules.attachmentRender = (tokens: MarkdownItToken[], idx: number, options, env, self) => { +// console.log('rule fxn'); +// const outputInfo = env.outputItem; +// const text = outputInfo.text(); +// let markdownText = outputInfo.mime.startsWith('text/x-') ? `\`\`\`${outputInfo.mime.substr(7)}\n${text}\n\`\`\`` +// : (outputInfo.mime.startsWith('application/') ? `\`\`\`${outputInfo.mime.substr(12)}\n${text}\n\`\`\`` : text); +// const attachments: Record> = (outputInfo.metadata as any).custom.attachments; +// if (attachments) { +// let attachmentName: keyof typeof attachments; +// for (attachmentName in attachments) { +// const [attachmentKey, attachmentVal] = Object.entries(attachments[attachmentName])[0]; +// const attachmentData = 'data:' + attachmentKey + ';base64,' + attachmentVal; +// markdownText = markdownText.replace(`attachment:${attachmentName}`, attachmentData); +// } +// } +// // const unsanitizedRenderedMarkdown = +// const unsanitizedRenderedMarkdown = md.render(markdownText, { outputItem: outputInfo, }); + + +// return self.renderToken(tokens, idx, options); +// }; + +// const originalRender = md.render; +// md.render = function () { +// return originalRender.apply(this, arguments as any); +// }; +// } + + +// implement code below to render cell attachments +// fIXME: metadata needs typing other than 'unknonw', rather than cast as any +// tODO: put attachments field as top level, instead of metadata->custom->attachments (match jupyter) ??? Maybe -- meeting +// const attachments = outputInfo.metadata.custom.attachments; // fIXME: might be messy, but I put a field "custom" within the NotebookCellMetadata interface. meeting with Peng/Matt to decide how to handle this +// const attachments: Record> = (outputInfo.metadata as any).custom.attachments; +// if (attachments) { +// let attachmentName: keyof typeof attachments; +// for (attachmentName in attachments) { +// const [attachmentKey, attachmentVal] = Object.entries(attachments[attachmentName])[0]; +// const attachmentData = 'data:' + attachmentKey + ';base64,' + attachmentVal; +// markdownText = markdownText.replace(`attachment:${attachmentName}`, attachmentData); +// } +// } diff --git a/extensions/ipynb/yarn.lock b/extensions/ipynb/yarn.lock index c932570ecc0..72ebfaafea6 100644 --- a/extensions/ipynb/yarn.lock +++ b/extensions/ipynb/yarn.lock @@ -3,33 +3,89 @@ "@enonic/fnv-plus@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@enonic/fnv-plus/-/fnv-plus-1.3.0.tgz#be65a7b128a3b544f60aea3ef978d938e85869f3" - integrity sha512-BCN9uNWH8AmiP7BXBJqEinUY9KXalmRzo+L0cB/mQsmFfzODxwQrbvxCHXUNH2iP+qKkWYtB4vyy8N62PViMFw== + "integrity" "sha512-BCN9uNWH8AmiP7BXBJqEinUY9KXalmRzo+L0cB/mQsmFfzODxwQrbvxCHXUNH2iP+qKkWYtB4vyy8N62PViMFw==" + "resolved" "https://registry.npmjs.org/@enonic/fnv-plus/-/fnv-plus-1.3.0.tgz" + "version" "1.3.0" "@jupyterlab/nbformat@^3.2.9": - version "3.2.9" - resolved "https://registry.yarnpkg.com/@jupyterlab/nbformat/-/nbformat-3.2.9.tgz#e7d854719612133498af4280d9a8caa0873205b0" - integrity sha512-WSf9OQo8yfFjyodbXRdFoaNwMkaAL5jFZiD6V2f8HqI380ipansWrrV7R9CGzPfgKHpUGZMO1tYKmUwzMhvZ4w== + "integrity" "sha512-WSf9OQo8yfFjyodbXRdFoaNwMkaAL5jFZiD6V2f8HqI380ipansWrrV7R9CGzPfgKHpUGZMO1tYKmUwzMhvZ4w==" + "resolved" "https://registry.npmjs.org/@jupyterlab/nbformat/-/nbformat-3.2.9.tgz" + "version" "3.2.9" dependencies: "@lumino/coreutils" "^1.5.3" "@lumino/coreutils@^1.5.3": - version "1.12.0" - resolved "https://registry.yarnpkg.com/@lumino/coreutils/-/coreutils-1.12.0.tgz#fbdef760f736eaf2bd396a5c6fc3a68a4b449b15" - integrity sha512-DSglh4ylmLi820CNx9soJmDJCpUgymckdWeGWuN0Ash5g60oQvrQDfosVxEhzmNvtvXv45WZEqSBzDP6E5SEmQ== + "integrity" "sha512-DSglh4ylmLi820CNx9soJmDJCpUgymckdWeGWuN0Ash5g60oQvrQDfosVxEhzmNvtvXv45WZEqSBzDP6E5SEmQ==" + "resolved" "https://registry.npmjs.org/@lumino/coreutils/-/coreutils-1.12.0.tgz" + "version" "1.12.0" + +"@types/linkify-it@*": + "integrity" "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==" + "resolved" "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz" + "version" "3.0.2" + +"@types/markdown-it@12.2.3": + "integrity" "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==" + "resolved" "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz" + "version" "12.2.3" + dependencies: + "@types/linkify-it" "*" + "@types/mdurl" "*" + +"@types/mdurl@*": + "integrity" "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==" + "resolved" "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz" + "version" "1.0.2" "@types/uuid@^8.3.1": - version "8.3.1" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f" - integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg== + "integrity" "sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==" + "resolved" "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz" + "version" "8.3.1" -detect-indent@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" - integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== +"argparse@^2.0.1": + "integrity" "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + "resolved" "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" + "version" "2.0.1" -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +"detect-indent@^6.0.0": + "integrity" "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==" + "resolved" "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz" + "version" "6.1.0" + +"entities@~2.1.0": + "integrity" "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" + "resolved" "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz" + "version" "2.1.0" + +"linkify-it@^3.0.1": + "integrity" "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==" + "resolved" "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz" + "version" "3.0.3" + dependencies: + "uc.micro" "^1.0.1" + +"markdown-it@^12.3.2": + "integrity" "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==" + "resolved" "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz" + "version" "12.3.2" + dependencies: + "argparse" "^2.0.1" + "entities" "~2.1.0" + "linkify-it" "^3.0.1" + "mdurl" "^1.0.1" + "uc.micro" "^1.0.5" + +"mdurl@^1.0.1": + "integrity" "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + "resolved" "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz" + "version" "1.0.1" + +"uc.micro@^1.0.1", "uc.micro@^1.0.5": + "integrity" "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + "resolved" "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz" + "version" "1.0.6" + +"uuid@^8.3.2": + "integrity" "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + "resolved" "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" + "version" "8.3.2" From 3981a5804c83d14c8f4940b936ee7163832a0a20 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Tue, 19 Jul 2022 12:40:24 -0700 Subject: [PATCH 100/197] added build stuff --- extensions/ipynb/esbuild.js | 52 +++++++++++++++++++++++++++++++++++ extensions/ipynb/package.json | 10 +++++++ 2 files changed, 62 insertions(+) create mode 100644 extensions/ipynb/esbuild.js diff --git a/extensions/ipynb/esbuild.js b/extensions/ipynb/esbuild.js new file mode 100644 index 00000000000..3f6365e4f1b --- /dev/null +++ b/extensions/ipynb/esbuild.js @@ -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. + *--------------------------------------------------------------------------------------------*/ +//@ts-check + +const path = require('path'); +const fse = require('fs-extra'); +const esbuild = require('esbuild'); + +const args = process.argv.slice(2); + +const isWatch = args.indexOf('--watch') >= 0; + +let outputRoot = __dirname; +const outputRootIndex = args.indexOf('--outputRoot'); +if (outputRootIndex >= 0) { + outputRoot = args[outputRootIndex + 1]; +} + +const srcDir = path.join(__dirname, 'src'); +const outDir = path.join(outputRoot, 'out'); + +async function build() { + await esbuild.build({ + entryPoints: [ + path.join(srcDir, 'cellAttachmentRenderer.ts'), + ], + bundle: true, + minify: false, + sourcemap: false, + format: 'esm', + outdir: outDir, + platform: 'browser', + target: ['es2020'], + }); + + // fse.copySync( + // path.join(__dirname, 'node_modules', 'katex', 'dist'), + // path.join(outDir, 'katex.min.css')); + +} + + +build().catch(() => process.exit(1)); + +if (isWatch) { + const watcher = require('@parcel/watcher'); + watcher.subscribe(srcDir, () => { + return build(); + }); +} diff --git a/extensions/ipynb/package.json b/extensions/ipynb/package.json index 0c8a77aa95d..496fb2cb3c7 100644 --- a/extensions/ipynb/package.json +++ b/extensions/ipynb/package.json @@ -51,6 +51,16 @@ "priority": "default" } ], + "notebookRenderer": [ + { + "id": "vscode.markdown-it-cell-attachment-renderer-extension", + "displayName": "Markdown it ipynb Cell Attachment renderer", + "entrypoint": { + "extends": "vscode.markdown-it-renderer", + "path": "./out/cellAttachmentRenderer.js" + } + } + ], "menus": { "file/newFile": [ { From 735ead82f5eb7ed296f7dd4db2e6d0b8243f7fdd Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Tue, 19 Jul 2022 13:13:18 -0700 Subject: [PATCH 101/197] added attachment and metadata support back in... rebasing is scary --- extensions/ipynb/src/cellAttachmentRenderer.ts | 3 +-- .../contrib/notebook/browser/notebookEditorWidget.ts | 2 ++ .../notebook/browser/view/renderers/webviewMessages.ts | 2 ++ .../notebook/browser/view/renderers/webviewPreloads.ts | 7 ++++--- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/extensions/ipynb/src/cellAttachmentRenderer.ts b/extensions/ipynb/src/cellAttachmentRenderer.ts index 262df2cb572..102b5e297ca 100644 --- a/extensions/ipynb/src/cellAttachmentRenderer.ts +++ b/extensions/ipynb/src/cellAttachmentRenderer.ts @@ -20,10 +20,9 @@ export async function activate(ctx: RendererContext) { // addCellAttachmentRendering(md); // const originalRender = md.render; - md.renderInline = function () { + md.render = function () { const outputInfo = arguments[1].outputItem; - const text = outputInfo.text(); let markdownText = outputInfo.mime.startsWith('text/x-') ? `\`\`\`${outputInfo.mime.substr(7)}\n${text}\n\`\`\`` : (outputInfo.mime.startsWith('application/') ? `\`\`\`${outputInfo.mime.substr(12)}\n${text}\n\`\`\`` : text); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index a3f275365dd..fc6500ceba8 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1650,6 +1650,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD content: model.getText(), offset: offset, visible: false, + metadata: model.metadata, }); } @@ -2587,6 +2588,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD content: cell.getText(), offset: cellTop + top, visible: true, + metadata: cell.metadata, }); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts index 4888d56bd8f..08b8489dcb3 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts @@ -5,6 +5,7 @@ import type { RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import type { PreloadOptions } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads'; +import { NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; interface BaseToWebviewMessage { readonly __vscode_notebook_message: true; @@ -329,6 +330,7 @@ export interface IMarkupCellInitialization { content: string; offset: number; visible: boolean; + metadata: NotebookCellMetadata; } export interface IInitializeMarkupCells { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index f0eb46d08bb..dea460bd27d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -6,6 +6,7 @@ import type { Event } from 'vs/base/common/event'; import type { IDisposable } from 'vs/base/common/lifecycle'; import type * as webviewMessages from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages'; +import { NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import type * as rendererApi from 'vscode-notebook-renderer'; // !! IMPORTANT !! ---------------------------------------------------------------------------------- @@ -1435,7 +1436,7 @@ async function webviewPreloads(ctx: PreloadContext) { return existing; } - const cell = new MarkupCell(init.cellId, init.mime, init.content, top); + const cell = new MarkupCell(init.cellId, init.mime, init.content, top, init.metadata); cell.element.style.visibility = visible ? 'visible' : 'hidden'; this._markupCells.set(init.cellId, cell); @@ -1634,7 +1635,7 @@ async function webviewPreloads(ctx: PreloadContext) { /// Internal field that holds text content private _content: { readonly value: string; readonly version: number }; - constructor(id: string, mime: string, content: string, top: number) { + constructor(id: string, mime: string, content: string, top: number, metadata: NotebookCellMetadata) { this.id = id; this._content = { value: content, version: 0 }; @@ -1645,7 +1646,7 @@ async function webviewPreloads(ctx: PreloadContext) { this.outputItem = Object.freeze({ id, mime, - metadata: undefined, + metadata, text: (): string => { return this._content.value; From 1b59f566bbc99617970ac9d0b092f0524c58b557 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Tue, 19 Jul 2022 16:07:50 -0700 Subject: [PATCH 102/197] rendering of attachment images complete via tokens --- .../ipynb/src/cellAttachmentRenderer.ts | 143 +++--------------- 1 file changed, 18 insertions(+), 125 deletions(-) diff --git a/extensions/ipynb/src/cellAttachmentRenderer.ts b/extensions/ipynb/src/cellAttachmentRenderer.ts index 102b5e297ca..09ce8921cb3 100644 --- a/extensions/ipynb/src/cellAttachmentRenderer.ts +++ b/extensions/ipynb/src/cellAttachmentRenderer.ts @@ -2,8 +2,9 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + import type * as MarkdownIt from 'markdown-it'; -// import type * as MarkdownItToken from 'markdown-it/lib/token'; +import type * as MarkdownItToken from 'markdown-it/lib/token'; import type { RendererContext } from 'vscode-notebook-renderer'; interface MarkdownItRenderer { @@ -11,138 +12,30 @@ interface MarkdownItRenderer { } export async function activate(ctx: RendererContext) { - console.log('CellAttachmentRenderer activation'); const markdownItRenderer = (await ctx.getRenderer('vscode.markdown-it-renderer')) as MarkdownItRenderer | any; if (!markdownItRenderer) { throw new Error(`Could not load 'vscode.markdown-it-renderer'`); } + markdownItRenderer.extendMarkdownIt((md: MarkdownIt) => { - // addCellAttachmentRendering(md); - // const originalRender = md.render; - - md.render = function () { - const outputInfo = arguments[1].outputItem; - - const text = outputInfo.text(); - let markdownText = outputInfo.mime.startsWith('text/x-') ? `\`\`\`${outputInfo.mime.substr(7)}\n${text}\n\`\`\`` - : (outputInfo.mime.startsWith('application/') ? `\`\`\`${outputInfo.mime.substr(12)}\n${text}\n\`\`\`` : text); - const attachments: Record> = (outputInfo.metadata as any).custom.attachments; + const original = md.renderer.rules.image; + md.renderer.rules.image = (tokens: MarkdownItToken[], idx: number, options, env, self) => { + const token = tokens[idx]; + const src = token.attrGet('src'); + const attachments: Record> = (env.outputItem.metadata as any).custom.attachments; if (attachments) { - let attachmentName: keyof typeof attachments; - for (attachmentName in attachments) { - const [attachmentKey, attachmentVal] = Object.entries(attachments[attachmentName])[0]; - const attachmentData = 'data:' + attachmentKey + ';base64,' + attachmentVal; - markdownText = markdownText.replace(`attachment:${attachmentName}`, attachmentData); + if (src) { + const [attachmentKey, attachmentVal] = Object.entries(attachments[src.replace('attachment:', '')])[0]; + const b64Markdown = 'data:' + attachmentKey + ';base64,' + attachmentVal; + token.attrSet('src', b64Markdown); } } - // const unsanitizedRenderedMarkdown = - return markdownText; + + if (original) { + return original(tokens, idx, options, env, self); + } else { + return self.renderToken(tokens, idx, options); + } }; }); } - -// function addCellAttachmentRendering(md: MarkdownIt): void { -// console.log('cell render fxn'); -// const cell_attachment = md.renderer.rules.cell_attachment; -// md.renderer.rules.cell_attachment = (tokens: MarkdownItToken[], idx: number, options, env, self) => { -// // const token = tokens[idx]; -// console.log('rule fxn'); -// const outputInfo = env.outputItem; -// const text = outputInfo.text(); -// let markdownText = outputInfo.mime.startsWith('text/x-') ? `\`\`\`${outputInfo.mime.substr(7)}\n${text}\n\`\`\`` -// : (outputInfo.mime.startsWith('application/') ? `\`\`\`${outputInfo.mime.substr(12)}\n${text}\n\`\`\`` : text); -// const attachments: Record> = (outputInfo.metadata as any).custom.attachments; -// if (attachments) { -// let attachmentName: keyof typeof attachments; -// for (attachmentName in attachments) { -// const [attachmentKey, attachmentVal] = Object.entries(attachments[attachmentName])[0]; -// const attachmentData = 'data:' + attachmentKey + ';base64,' + attachmentVal; -// markdownText = markdownText.replace(`attachment:${attachmentName}`, attachmentData); -// } -// } -// // const unsanitizedRenderedMarkdown = -// return md.render(markdownText, { outputItem: outputInfo, }); -// // return self.renderToken(tokens, idx, options); -// }; - -// const originalRender = md.render; -// md.render = function () { -// return originalRender.apply(this, arguments as any); -// }; -// } - - - - // md.renderer.rules.attachmentRender = (tokens: MarkdownItToken[], idx: number, options, env, self) => { - // console.log('rule fxn'); - // const outputInfo = env.outputItem; - // const text = outputInfo.text(); - // let markdownText = outputInfo.mime.startsWith('text/x-') ? `\`\`\`${outputInfo.mime.substr(7)}\n${text}\n\`\`\`` - // : (outputInfo.mime.startsWith('application/') ? `\`\`\`${outputInfo.mime.substr(12)}\n${text}\n\`\`\`` : text); - // const attachments: Record> = (outputInfo.metadata as any).custom.attachments; - // if (attachments) { - // let attachmentName: keyof typeof attachments; - // for (attachmentName in attachments) { - // const [attachmentKey, attachmentVal] = Object.entries(attachments[attachmentName])[0]; - // const attachmentData = 'data:' + attachmentKey + ';base64,' + attachmentVal; - // markdownText = markdownText.replace(`attachment:${attachmentName}`, attachmentData); - // } - // } - // // const unsanitizedRenderedMarkdown = - // const unsanitizedRenderedMarkdown = md.render(markdownText, { outputItem: outputInfo, }); - - - // return self.renderToken(tokens, idx, options); - // }; - - // const originalRender = md.render; - // md.render = function () { - // return originalRender.apply(this, arguments as any); - // }; - - -// FIXME: kinda works. data is there at least -// function addCellAttachmentRendering(md: MarkdownIt): void { -// console.log('cell render fxn'); -// md.renderer.rules.attachmentRender = (tokens: MarkdownItToken[], idx: number, options, env, self) => { -// console.log('rule fxn'); -// const outputInfo = env.outputItem; -// const text = outputInfo.text(); -// let markdownText = outputInfo.mime.startsWith('text/x-') ? `\`\`\`${outputInfo.mime.substr(7)}\n${text}\n\`\`\`` -// : (outputInfo.mime.startsWith('application/') ? `\`\`\`${outputInfo.mime.substr(12)}\n${text}\n\`\`\`` : text); -// const attachments: Record> = (outputInfo.metadata as any).custom.attachments; -// if (attachments) { -// let attachmentName: keyof typeof attachments; -// for (attachmentName in attachments) { -// const [attachmentKey, attachmentVal] = Object.entries(attachments[attachmentName])[0]; -// const attachmentData = 'data:' + attachmentKey + ';base64,' + attachmentVal; -// markdownText = markdownText.replace(`attachment:${attachmentName}`, attachmentData); -// } -// } -// // const unsanitizedRenderedMarkdown = -// const unsanitizedRenderedMarkdown = md.render(markdownText, { outputItem: outputInfo, }); - - -// return self.renderToken(tokens, idx, options); -// }; - -// const originalRender = md.render; -// md.render = function () { -// return originalRender.apply(this, arguments as any); -// }; -// } - - -// implement code below to render cell attachments -// fIXME: metadata needs typing other than 'unknonw', rather than cast as any -// tODO: put attachments field as top level, instead of metadata->custom->attachments (match jupyter) ??? Maybe -- meeting -// const attachments = outputInfo.metadata.custom.attachments; // fIXME: might be messy, but I put a field "custom" within the NotebookCellMetadata interface. meeting with Peng/Matt to decide how to handle this -// const attachments: Record> = (outputInfo.metadata as any).custom.attachments; -// if (attachments) { -// let attachmentName: keyof typeof attachments; -// for (attachmentName in attachments) { -// const [attachmentKey, attachmentVal] = Object.entries(attachments[attachmentName])[0]; -// const attachmentData = 'data:' + attachmentKey + ';base64,' + attachmentVal; -// markdownText = markdownText.replace(`attachment:${attachmentName}`, attachmentData); -// } -// } From 6a73a0d22dae70166b8958c462c1c44d5db65884 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Wed, 20 Jul 2022 12:25:57 -0700 Subject: [PATCH 103/197] rm package-lock.json --- extensions/ipynb/package-lock.json | 247 ----------------------------- 1 file changed, 247 deletions(-) delete mode 100644 extensions/ipynb/package-lock.json diff --git a/extensions/ipynb/package-lock.json b/extensions/ipynb/package-lock.json deleted file mode 100644 index 6e11a31cf9b..00000000000 --- a/extensions/ipynb/package-lock.json +++ /dev/null @@ -1,247 +0,0 @@ -{ - "name": "ipynb", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "ipynb", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "@enonic/fnv-plus": "^1.3.0", - "detect-indent": "^6.0.0", - "markdown-it": "^12.3.2", - "uuid": "^8.3.2" - }, - "devDependencies": { - "@jupyterlab/nbformat": "^3.2.9", - "@types/markdown-it": "12.2.3", - "@types/uuid": "^8.3.1" - }, - "engines": { - "vscode": "^1.57.0" - } - }, - "node_modules/@enonic/fnv-plus": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@enonic/fnv-plus/-/fnv-plus-1.3.0.tgz", - "integrity": "sha512-BCN9uNWH8AmiP7BXBJqEinUY9KXalmRzo+L0cB/mQsmFfzODxwQrbvxCHXUNH2iP+qKkWYtB4vyy8N62PViMFw==", - "license": "MIT" - }, - "node_modules/@jupyterlab/nbformat": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/@jupyterlab/nbformat/-/nbformat-3.2.9.tgz", - "integrity": "sha512-WSf9OQo8yfFjyodbXRdFoaNwMkaAL5jFZiD6V2f8HqI380ipansWrrV7R9CGzPfgKHpUGZMO1tYKmUwzMhvZ4w==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@lumino/coreutils": "^1.5.3" - } - }, - "node_modules/@lumino/coreutils": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@lumino/coreutils/-/coreutils-1.12.0.tgz", - "integrity": "sha512-DSglh4ylmLi820CNx9soJmDJCpUgymckdWeGWuN0Ash5g60oQvrQDfosVxEhzmNvtvXv45WZEqSBzDP6E5SEmQ==", - "dev": true, - "license": "BSD-3-Clause", - "peerDependencies": { - "crypto": "1.0.1" - } - }, - "node_modules/@types/linkify-it": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", - "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", - "dev": true - }, - "node_modules/@types/markdown-it": { - "version": "12.2.3", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", - "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", - "dev": true, - "dependencies": { - "@types/linkify-it": "*", - "@types/mdurl": "*" - } - }, - "node_modules/@types/mdurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", - "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", - "dev": true - }, - "node_modules/@types/uuid": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "node_modules/detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", - "dependencies": { - "uc.micro": "^1.0.1" - } - }, - "node_modules/markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", - "dependencies": { - "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - }, - "bin": { - "markdown-it": "bin/markdown-it.js" - } - }, - "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" - }, - "node_modules/uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - } - }, - "dependencies": { - "@enonic/fnv-plus": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@enonic/fnv-plus/-/fnv-plus-1.3.0.tgz", - "integrity": "sha512-BCN9uNWH8AmiP7BXBJqEinUY9KXalmRzo+L0cB/mQsmFfzODxwQrbvxCHXUNH2iP+qKkWYtB4vyy8N62PViMFw==" - }, - "@jupyterlab/nbformat": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/@jupyterlab/nbformat/-/nbformat-3.2.9.tgz", - "integrity": "sha512-WSf9OQo8yfFjyodbXRdFoaNwMkaAL5jFZiD6V2f8HqI380ipansWrrV7R9CGzPfgKHpUGZMO1tYKmUwzMhvZ4w==", - "dev": true, - "requires": { - "@lumino/coreutils": "^1.5.3" - } - }, - "@lumino/coreutils": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@lumino/coreutils/-/coreutils-1.12.0.tgz", - "integrity": "sha512-DSglh4ylmLi820CNx9soJmDJCpUgymckdWeGWuN0Ash5g60oQvrQDfosVxEhzmNvtvXv45WZEqSBzDP6E5SEmQ==", - "dev": true, - "requires": {} - }, - "@types/linkify-it": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", - "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", - "dev": true - }, - "@types/markdown-it": { - "version": "12.2.3", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", - "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", - "dev": true, - "requires": { - "@types/linkify-it": "*", - "@types/mdurl": "*" - } - }, - "@types/mdurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", - "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", - "dev": true - }, - "@types/uuid": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==", - "dev": true - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==" - }, - "entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" - }, - "linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", - "requires": { - "uc.micro": "^1.0.1" - } - }, - "markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", - "requires": { - "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - } - }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" - }, - "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - } - } -} From d7b6596808f7620a5ee07f01e8f302a806572eeb Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Wed, 20 Jul 2022 12:35:42 -0700 Subject: [PATCH 104/197] yarn.lock with yarnpkg instead of npm --- extensions/ipynb/yarn.lock | 122 ++++++++++++++++++------------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/extensions/ipynb/yarn.lock b/extensions/ipynb/yarn.lock index 72ebfaafea6..d1e10e42789 100644 --- a/extensions/ipynb/yarn.lock +++ b/extensions/ipynb/yarn.lock @@ -3,89 +3,89 @@ "@enonic/fnv-plus@^1.3.0": - "integrity" "sha512-BCN9uNWH8AmiP7BXBJqEinUY9KXalmRzo+L0cB/mQsmFfzODxwQrbvxCHXUNH2iP+qKkWYtB4vyy8N62PViMFw==" - "resolved" "https://registry.npmjs.org/@enonic/fnv-plus/-/fnv-plus-1.3.0.tgz" - "version" "1.3.0" + version "1.3.0" + resolved "https://registry.yarnpkg.com/@enonic/fnv-plus/-/fnv-plus-1.3.0.tgz#be65a7b128a3b544f60aea3ef978d938e85869f3" + integrity sha512-BCN9uNWH8AmiP7BXBJqEinUY9KXalmRzo+L0cB/mQsmFfzODxwQrbvxCHXUNH2iP+qKkWYtB4vyy8N62PViMFw== "@jupyterlab/nbformat@^3.2.9": - "integrity" "sha512-WSf9OQo8yfFjyodbXRdFoaNwMkaAL5jFZiD6V2f8HqI380ipansWrrV7R9CGzPfgKHpUGZMO1tYKmUwzMhvZ4w==" - "resolved" "https://registry.npmjs.org/@jupyterlab/nbformat/-/nbformat-3.2.9.tgz" - "version" "3.2.9" + version "3.4.3" + resolved "https://registry.yarnpkg.com/@jupyterlab/nbformat/-/nbformat-3.4.3.tgz#cbab1bf507677b7f0f309d8353fc83fe5a973c82" + integrity sha512-i/yADrwhhAJJCUOTa+fEBMyJO7fvX9Y73I0B7V6dQhGcrmrEKLC3wk4yOo63+jRntd5+dupbiOtz3w1ncIXwIA== dependencies: - "@lumino/coreutils" "^1.5.3" + "@lumino/coreutils" "^1.11.0" -"@lumino/coreutils@^1.5.3": - "integrity" "sha512-DSglh4ylmLi820CNx9soJmDJCpUgymckdWeGWuN0Ash5g60oQvrQDfosVxEhzmNvtvXv45WZEqSBzDP6E5SEmQ==" - "resolved" "https://registry.npmjs.org/@lumino/coreutils/-/coreutils-1.12.0.tgz" - "version" "1.12.0" +"@lumino/coreutils@^1.11.0": + version "1.12.0" + resolved "https://registry.yarnpkg.com/@lumino/coreutils/-/coreutils-1.12.0.tgz#fbdef760f736eaf2bd396a5c6fc3a68a4b449b15" + integrity sha512-DSglh4ylmLi820CNx9soJmDJCpUgymckdWeGWuN0Ash5g60oQvrQDfosVxEhzmNvtvXv45WZEqSBzDP6E5SEmQ== "@types/linkify-it@*": - "integrity" "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==" - "resolved" "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz" - "version" "3.0.2" + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.2.tgz#fd2cd2edbaa7eaac7e7f3c1748b52a19143846c9" + integrity sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA== "@types/markdown-it@12.2.3": - "integrity" "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==" - "resolved" "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz" - "version" "12.2.3" + version "12.2.3" + resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-12.2.3.tgz#0d6f6e5e413f8daaa26522904597be3d6cd93b51" + integrity sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ== dependencies: "@types/linkify-it" "*" "@types/mdurl" "*" "@types/mdurl@*": - "integrity" "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==" - "resolved" "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz" - "version" "1.0.2" + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9" + integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA== "@types/uuid@^8.3.1": - "integrity" "sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==" - "resolved" "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz" - "version" "8.3.1" + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== -"argparse@^2.0.1": - "integrity" "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - "resolved" "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" - "version" "2.0.1" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -"detect-indent@^6.0.0": - "integrity" "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==" - "resolved" "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz" - "version" "6.1.0" +detect-indent@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" + integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== -"entities@~2.1.0": - "integrity" "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" - "resolved" "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz" - "version" "2.1.0" +entities@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" + integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== -"linkify-it@^3.0.1": - "integrity" "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==" - "resolved" "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz" - "version" "3.0.3" +linkify-it@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e" + integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ== dependencies: - "uc.micro" "^1.0.1" + uc.micro "^1.0.1" -"markdown-it@^12.3.2": - "integrity" "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==" - "resolved" "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz" - "version" "12.3.2" +markdown-it@^12.3.2: + version "12.3.2" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.3.2.tgz#bf92ac92283fe983fe4de8ff8abfb5ad72cd0c90" + integrity sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg== dependencies: - "argparse" "^2.0.1" - "entities" "~2.1.0" - "linkify-it" "^3.0.1" - "mdurl" "^1.0.1" - "uc.micro" "^1.0.5" + argparse "^2.0.1" + entities "~2.1.0" + linkify-it "^3.0.1" + mdurl "^1.0.1" + uc.micro "^1.0.5" -"mdurl@^1.0.1": - "integrity" "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" - "resolved" "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz" - "version" "1.0.1" +mdurl@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== -"uc.micro@^1.0.1", "uc.micro@^1.0.5": - "integrity" "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" - "resolved" "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz" - "version" "1.0.6" +uc.micro@^1.0.1, uc.micro@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== -"uuid@^8.3.2": - "integrity" "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - "resolved" "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" - "version" "8.3.2" +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== From 82c6c5547f901f6a416279702f08d2d4c4cf50d1 Mon Sep 17 00:00:00 2001 From: David Dossett Date: Wed, 20 Jul 2022 12:49:01 -0700 Subject: [PATCH 105/197] Show only one active focused tab across editor groups in hc themes (Fix #145563) (#155776) * Show only one active & focused tab across editor groups in hc themes * Use dotted line for active, dashed for hover --- .../workbench/browser/parts/editor/tabsTitleControl.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 386e13b2e0b..facf063df26 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -1910,12 +1910,17 @@ registerThemingParticipant((theme, collector) => { const activeContrastBorderColor = theme.getColor(activeContrastBorder); if (activeContrastBorderColor) { collector.addRule(` - .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active, - .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active:hover { + .monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active, + .monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active:hover { outline: 1px solid; outline-offset: -5px; } + .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active { + outline: 1px dotted; + outline-offset: -5px; + } + .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover { outline: 1px dashed; outline-offset: -5px; From d7ff038767a8b15b94e7dc4bfd6faa47f1d06ee2 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Wed, 20 Jul 2022 13:31:22 -0700 Subject: [PATCH 106/197] fix on mutable disposables and context view --- .../codeAction/browser/codeActionMenu.ts | 57 +++++++++++-------- .../codeAction/browser/codeActionUi.ts | 19 ++++--- .../codeAction/browser/media/action.css | 2 +- .../contextview/browser/contextViewService.ts | 2 +- 4 files changed, 48 insertions(+), 32 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 760261d5aec..83ee09cbc0d 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -9,7 +9,6 @@ import { IListEvent, IListRenderer } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { Action, IAction, Separator } from 'vs/base/common/actions'; import { canceled } from 'vs/base/common/errors'; -import { Emitter } from 'vs/base/common/event'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, dispose, MutableDisposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -91,6 +90,8 @@ export interface ICodeActionMenuTemplateData { } const TEMPLATE_ID = 'codeActionWidget'; +const codeActionLineHeight = 27; + class CodeMenuRenderer implements IListRenderer { get templateId(): string { return TEMPLATE_ID; } @@ -120,8 +121,6 @@ class CodeMenuRenderer implements IListRenderer()); private readonly _disposables = new DisposableStore(); - private codeActionList!: List; + private codeActionList = this._register(new MutableDisposable>()); private options: ICodeActionMenuItem[] = []; private _visible: boolean = false; private _ctxMenuWidgetVisible: IContextKey; private viewItems: ICodeActionMenuItem[] = []; private focusedEnabledItem: number | undefined; private currSelectedItem: number = 0; + private hasSeperator: boolean = false; public static readonly ID: string = 'editor.contrib.codeActionMenu'; @@ -195,7 +195,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { element.action.run(); } }); - this.dispose(); + this.hideCodeActionWidget(); } } @@ -204,17 +204,17 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { const renderDisposables = new DisposableStore(); const renderMenu = document.createElement('div'); - const height = inputArray.length * 27; - renderMenu.style.height = String(height) + 'px'; - renderMenu.id = 'codeActionMenuWidget'; renderMenu.classList.add('codeActionMenuWidget'); element.appendChild(renderMenu); - this.codeActionList = new List('codeActionWidget', renderMenu, { + this.codeActionList.value = new List('codeActionWidget', renderMenu, { getHeight(element) { - return 27; + if (element.isSeparator) { + return 10; + } + return codeActionLineHeight; }, getTemplateId(element) { return 'codeActionWidget'; @@ -222,24 +222,35 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { }, [this.listRenderer], { keyboardSupport: false } ); - renderDisposables.add(this.codeActionList.onDidChangeSelection(e => this._onListSelection(e))); + renderDisposables.add(this.codeActionList.value.onDidChangeSelection(e => this._onListSelection(e))); // Populating the list widget and tracking enabled options. inputArray.forEach((item, index) => { - const menuItem = { title: item.label, detail: item.tooltip, action: inputArray[index], isEnabled: item.enabled, isSeparator: item.class === 'separator', index }; + const currIsSeparator = item.class === 'separator'; + if (currIsSeparator) { + // set to true forever + this.hasSeperator = true; + } + const menuItem = { title: item.label, detail: item.tooltip, action: inputArray[index], isEnabled: item.enabled, isSeparator: currIsSeparator, index }; if (item.enabled) { this.viewItems.push(menuItem); } this.options.push(menuItem); }); - this.codeActionList.splice(0, this.codeActionList.length, this.options); - this.codeActionList.layout(height); + this.codeActionList.value.splice(0, this.codeActionList.value.length, this.options); + + const height = this.hasSeperator ? (inputArray.length - 1) * codeActionLineHeight + 10 : inputArray.length * codeActionLineHeight; + renderMenu.style.height = String(height) + 'px'; + this.codeActionList.value.layout(height); // For finding width dynamically (not using resize observer) const arr: number[] = []; this.options.forEach((item, index) => { - const element = document.getElementById(this.codeActionList.getElementID(index))?.getElementsByTagName('span')[0].offsetWidth; + if (!this.codeActionList.value) { + return; + } + const element = document.getElementById(this.codeActionList.value?.getElementID(index))?.getElementsByTagName('span')[0].offsetWidth; arr.push(Number(element)); }); @@ -248,15 +259,15 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { // 40 is the additional padding for the list widget (20 left, 20 right) renderMenu.style.width = maxWidth + 40 + 'px'; - this.codeActionList.layout(height, maxWidth); + this.codeActionList.value?.layout(height, maxWidth); // List selection this.focusedEnabledItem = 0; this.currSelectedItem = this.viewItems[0].index; - this.codeActionList.setFocus([this.currSelectedItem]); + this.codeActionList.value.setFocus([this.currSelectedItem]); // List Focus - this.codeActionList.domFocus(); + this.codeActionList.value.domFocus(); const focusTracker = dom.trackFocus(element); const blurListener = focusTracker.onDidBlur(() => { this.hideCodeActionWidget(); @@ -285,7 +296,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { this.focusedEnabledItem = this.viewItems.length - 1; } item = this.viewItems[this.focusedEnabledItem]; - this.codeActionList.setFocus([item.index]); + this.codeActionList.value?.setFocus([item.index]); this.currSelectedItem = item.index; } while (this.focusedEnabledItem !== startIndex && ((!item.isEnabled) || item.action.id === Separator.ID)); @@ -305,7 +316,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { do { this.focusedEnabledItem = (this.focusedEnabledItem + 1) % this.viewItems.length; item = this.viewItems[this.focusedEnabledItem]; - this.codeActionList.setFocus([item.index]); + this.codeActionList.value?.setFocus([item.index]); this.currSelectedItem = item.index; } while (this.focusedEnabledItem !== startIndex && ((!item.isEnabled) || item.action.id === Separator.ID)); @@ -321,11 +332,11 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { } public onEnterSet() { - this.codeActionList.setSelection([this.currSelectedItem]); + this.codeActionList.value?.setSelection([this.currSelectedItem]); } override dispose() { - this.codeActionList.dispose(); + super.dispose(); this._disposables.dispose(); } @@ -335,8 +346,8 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { this.viewItems = []; this.focusedEnabledItem = 0; this.currSelectedItem = 0; + this.hasSeperator = false; this._contextViewService.hideContextView({ source: this }); - this.dispose(); } codeActionTelemetry(openedFromString: CodeActionTriggerSource, didCancel: boolean, CodeActions: CodeActionSet) { diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts index bfac0671383..e42d043ab5e 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts @@ -59,20 +59,25 @@ export class CodeActionUi extends Disposable { } public hideCodeActionWidget() { - this._codeActionWidget.getValue().hideCodeActionWidget(); + if (this._codeActionWidget.hasValue()) { + this._codeActionWidget.getValue().hideCodeActionWidget(); + } } public onEnter() { - this._codeActionWidget.getValue().onEnterSet(); + if (this._codeActionWidget.hasValue()) { + this._codeActionWidget.getValue().onEnterSet(); + } } public navigateList(navUp: Boolean) { - if (navUp) { - this._codeActionWidget.getValue().navigateListWithKeysUp(); - } else { - this._codeActionWidget.getValue().navigateListWithKeysDown(); + if (this._codeActionWidget.hasValue()) { + if (navUp) { + this._codeActionWidget.getValue().navigateListWithKeysUp(); + } else { + this._codeActionWidget.getValue().navigateListWithKeysDown(); + } } - } public async update(newState: CodeActionsState.State): Promise { diff --git a/src/vs/editor/contrib/codeAction/browser/media/action.css b/src/vs/editor/contrib/codeAction/browser/media/action.css index b47e05831b4..504955b5844 100644 --- a/src/vs/editor/contrib/codeAction/browser/media/action.css +++ b/src/vs/editor/contrib/codeAction/browser/media/action.css @@ -91,7 +91,7 @@ .codeActionMenuWidget .monaco-list .separator { border-bottom: 1px solid var(--vscode-menu-separatorBackground); - padding-top: 10px !important; + padding-top: 0px !important; /* padding: 30px; */ width: 100%; height: 0px !important; diff --git a/src/vs/platform/contextview/browser/contextViewService.ts b/src/vs/platform/contextview/browser/contextViewService.ts index 00b107b0921..57be989bcc4 100644 --- a/src/vs/platform/contextview/browser/contextViewService.ts +++ b/src/vs/platform/contextview/browser/contextViewService.ts @@ -35,7 +35,7 @@ export class ContextViewService extends Disposable implements IContextViewServic showContextView(delegate: IContextViewDelegate, container?: HTMLElement, shadowRoot?: boolean): IDisposable { if (container) { - if (container !== this.container || true) { + if (container !== this.container) { this.container = container; this.setContainer(container, shadowRoot ? ContextViewDOMPosition.FIXED_SHADOW : ContextViewDOMPosition.FIXED); } From 3a23dda32c0ea3697c43fe3e0497b3903f576ecb Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Wed, 20 Jul 2022 13:47:26 -0700 Subject: [PATCH 107/197] removed commented code --- extensions/ipynb/esbuild.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/extensions/ipynb/esbuild.js b/extensions/ipynb/esbuild.js index 3f6365e4f1b..444ec268030 100644 --- a/extensions/ipynb/esbuild.js +++ b/extensions/ipynb/esbuild.js @@ -34,11 +34,6 @@ async function build() { platform: 'browser', target: ['es2020'], }); - - // fse.copySync( - // path.join(__dirname, 'node_modules', 'katex', 'dist'), - // path.join(outDir, 'katex.min.css')); - } From 892b716803ab6e2b75b928f3b280fe2d6abf62b2 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Wed, 20 Jul 2022 13:52:49 -0700 Subject: [PATCH 108/197] removed disposables unused, still shadowroot issue --- src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 83ee09cbc0d..69f3ec06536 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -138,7 +138,6 @@ class CodeMenuRenderer implements IListRenderer()); - private readonly _disposables = new DisposableStore(); private codeActionList = this._register(new MutableDisposable>()); private options: ICodeActionMenuItem[] = []; private _visible: boolean = false; @@ -337,7 +336,6 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { override dispose() { super.dispose(); - this._disposables.dispose(); } hideCodeActionWidget() { From d745b88c2da765b53cc88efc20e61d5fc82e3194 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Wed, 20 Jul 2022 15:01:07 -0700 Subject: [PATCH 109/197] Add Settings indicator for application-scoped settings (#155212) --- .../configuration/common/configuration.ts | 4 +- .../browser/preferences.contribution.ts | 122 ++++++++++++++---- .../browser/preferencesRenderers.ts | 23 +++- .../preferences/browser/preferencesWidgets.ts | 22 ++-- .../preferences/browser/settingsEditor2.ts | 35 +++-- .../settingsEditorSettingIndicators.ts | 24 +++- .../preferences/browser/settingsTree.ts | 21 ++- .../preferences/browser/settingsTreeModels.ts | 60 +++++---- .../contrib/preferences/common/preferences.ts | 1 + .../configuration/common/configuration.ts | 3 +- .../preferences/browser/preferencesService.ts | 14 +- .../preferences/common/preferences.ts | 1 + 12 files changed, 256 insertions(+), 74 deletions(-) diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 5c27c738322..4ea7a9bce8d 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -34,7 +34,8 @@ export function isConfigurationUpdateOverrides(thing: any): thing is IConfigurat export type IConfigurationUpdateOverrides = Omit & { overrideIdentifiers?: string[] | null }; export const enum ConfigurationTarget { - USER = 1, + APPLICATION = 1, + USER, USER_LOCAL, USER_REMOTE, WORKSPACE, @@ -44,6 +45,7 @@ export const enum ConfigurationTarget { } export function ConfigurationTargetToString(configurationTarget: ConfigurationTarget) { switch (configurationTarget) { + case ConfigurationTarget.APPLICATION: return 'APPLICATION'; case ConfigurationTarget.USER: return 'USER'; case ConfigurationTarget.USER_LOCAL: return 'USER_LOCAL'; case ConfigurationTarget.USER_REMOTE: return 'USER_REMOTE'; diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index f401ea19b4d..bf42a279f34 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isObject } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -34,7 +34,7 @@ import { ConfigureLanguageBasedSettingsAction } from 'vs/workbench/contrib/prefe import { SettingsEditorContribution } from 'vs/workbench/contrib/preferences/browser/preferencesEditor'; import { preferencesOpenSettingsIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; import { SettingsEditor2, SettingsFocusContext } from 'vs/workbench/contrib/preferences/browser/settingsEditor2'; -import { CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, CONTEXT_KEYBINDING_FOCUS, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, CONTEXT_WHEN_FOCUS, KEYBINDINGS_EDITOR_COMMAND_ADD, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_HISTORY, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND_TITLE, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_SEARCH, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_EXTENSION_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; +import { CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, CONTEXT_KEYBINDING_FOCUS, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_EDITOR_IN_USER_TAB, CONTEXT_SETTINGS_JSON_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, CONTEXT_WHEN_FOCUS, KEYBINDINGS_EDITOR_COMMAND_ADD, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_HISTORY, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND_TITLE, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_SEARCH, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_EXTENSION_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; import { PreferencesContribution } from 'vs/workbench/contrib/preferences/common/preferencesContribution'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -44,6 +44,7 @@ import { KeybindingsEditorInput } from 'vs/workbench/services/preferences/browse import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; const SETTINGS_EDITOR_COMMAND_SEARCH = 'settings.action.search'; @@ -55,6 +56,8 @@ const SETTINGS_EDITOR_COMMAND_FOCUS_CONTROL = 'settings.action.focusSettingContr const SETTINGS_EDITOR_COMMAND_FOCUS_UP = 'settings.action.focusLevelUp'; const SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON = 'settings.switchToJSON'; +const SETTINGS_EDITOR_COMMAND_SWITCH_TO_APPLICATION_JSON = 'settings.switchToApplicationJSON'; +const SETTINGS_EDITOR_COMMAND_SWITCH_TO_CURRENT_PROFILE_JSON = 'settings.switchToCurrentProfileJSON'; const SETTINGS_EDITOR_COMMAND_FILTER_ONLINE = 'settings.filterByOnline'; const SETTINGS_EDITOR_COMMAND_FILTER_TELEMETRY = 'settings.filterByTelemetry'; const SETTINGS_EDITOR_COMMAND_FILTER_UNTRUSTED = 'settings.filterUntrusted'; @@ -145,6 +148,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @ILabelService private readonly labelService: ILabelService, @IExtensionService private readonly extensionService: IExtensionService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, ) { super(); @@ -157,7 +161,6 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon } private registerSettingsActions() { - const that = this; registerAction2(class extends Action2 { constructor() { super({ @@ -247,6 +250,12 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon const registerOpenUserSettingsEditorFromJsonActionDisposable = this._register(new MutableDisposable()); const registerOpenUserSettingsEditorFromJsonAction = () => { + let when = ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.userDataProfileService.currentProfile.settingsResource.toString()), ContextKeyExpr.not('isInDiffEditor')); + if (!this.userDataProfileService.currentProfile.isDefault) { + // If the default profile is not active, also show the action when we're in the + // default profile JSON file, which contains the application-scoped settings. + when = ContextKeyExpr.or(when, ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.userDataProfilesService.defaultProfile.settingsResource.toString()), ContextKeyExpr.not('isInDiffEditor'))); + } registerOpenUserSettingsEditorFromJsonActionDisposable.value = registerAction2(class extends Action2 { constructor() { super({ @@ -255,7 +264,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon icon: preferencesOpenSettingsIcon, menu: [{ id: MenuId.EditorTitle, - when: ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(that.userDataProfileService.currentProfile.settingsResource.toString()), ContextKeyExpr.not('isInDiffEditor')), + when, group: 'navigation', order: 1 }] @@ -267,31 +276,96 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon } }); }; - registerOpenUserSettingsEditorFromJsonAction(); - this._register(this.userDataProfileService.onDidChangeCurrentProfile(() => registerOpenUserSettingsEditorFromJsonAction())); - registerAction2(class extends Action2 { - constructor() { - super({ - id: SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON, + const openJsonFromSettingsEditorDisposableStore = this._register(new DisposableStore()); + const registerOpenJsonFromSettingsEditorAction = () => { + openJsonFromSettingsEditorDisposableStore.clear(); + if (!this.userDataProfileService.currentProfile.isDefault) { + // When the default profile is not active, the action for the User tab needs a dropdown + // because User tab settings in that case are actually saved in two separate files. + const submenuId = MenuId.for('PreferencesSubMenu'); + openJsonFromSettingsEditorDisposableStore.add(registerAction2(class extends Action2 { + constructor() { + super({ + id: SETTINGS_EDITOR_COMMAND_SWITCH_TO_CURRENT_PROFILE_JSON, + title: { value: nls.localize('openCurrentProfileSettings', "Open Current Profile Settings (JSON)"), original: 'Open Current Profile Settings (JSON)' }, + f1: true, + menu: [{ id: submenuId, order: 1 }] + }); + } + run(accessor: ServicesAccessor) { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof SettingsEditor2) { + return editorPane.switchToSettingsFile(); + } + return null; + } + })); + openJsonFromSettingsEditorDisposableStore.add(registerAction2(class extends Action2 { + constructor() { + super({ + id: SETTINGS_EDITOR_COMMAND_SWITCH_TO_APPLICATION_JSON, + title: { value: nls.localize('openApplicationSettings', "Open User Settings (JSON)"), original: 'Open User Settings (JSON)' }, + f1: true, + menu: [{ id: submenuId, order: 2 }] + }); + } + run(accessor: ServicesAccessor) { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof SettingsEditor2) { + return editorPane.switchToApplicationSettingsFile(); + } + return null; + } + })); + openJsonFromSettingsEditorDisposableStore.add(MenuRegistry.appendMenuItem(MenuId.EditorTitle, { title: { value: nls.localize('openSettingsJson', "Open Settings (JSON)"), original: 'Open Settings (JSON)' }, + submenu: submenuId, icon: preferencesOpenSettingsIcon, - menu: [{ - id: MenuId.EditorTitle, - when: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR.toNegated()), - group: 'navigation', - order: 1 - }] - }); + when: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_EDITOR_IN_USER_TAB, CONTEXT_SETTINGS_JSON_EDITOR.toNegated()), + group: 'navigation', + order: 1 + })); } - run(accessor: ServicesAccessor) { - const editorPane = accessor.get(IEditorService).activeEditorPane; - if (editorPane instanceof SettingsEditor2) { - return editorPane.switchToSettingsFile(); + + let openSettingsJsonWhen = ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR.toNegated()); + if (!this.userDataProfileService.currentProfile.isDefault) { + // If we're not in the default profile, we already created the action for the User tab above, + // so we want to make sure the user is not in the User tab for this more general action. + openSettingsJsonWhen = ContextKeyExpr.and(openSettingsJsonWhen, CONTEXT_SETTINGS_EDITOR_IN_USER_TAB.toNegated()); + } + openJsonFromSettingsEditorDisposableStore.add(registerAction2(class extends Action2 { + constructor() { + super({ + id: SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON, + title: { value: nls.localize('openSettingsJson', "Open Settings (JSON)"), original: 'Open Settings (JSON)' }, + icon: preferencesOpenSettingsIcon, + menu: [{ + id: MenuId.EditorTitle, + when: openSettingsJsonWhen, + group: 'navigation', + order: 1 + }] + }); } - return null; - } - }); + run(accessor: ServicesAccessor) { + const editorPane = accessor.get(IEditorService).activeEditorPane; + if (editorPane instanceof SettingsEditor2) { + return editorPane.switchToSettingsFile(); + } + return null; + } + })); + }; + + registerOpenUserSettingsEditorFromJsonAction(); + registerOpenJsonFromSettingsEditorAction(); + + this._register(this.userDataProfileService.onDidChangeCurrentProfile(() => { + registerOpenUserSettingsEditorFromJsonAction(); + registerOpenJsonFromSettingsEditorAction(); + })); + registerAction2(class extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index a2aa81b94c1..b64e07f7172 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -40,6 +40,9 @@ import { IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditor import { DefaultSettingsEditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { isEqual } from 'vs/base/common/resources'; +import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; export interface IPreferencesRenderer extends IDisposable { render(): void; @@ -481,6 +484,8 @@ class UnsupportedSettingsRenderer extends Disposable implements languages.CodeAc @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, ) { super(); this._register(this.editor.getModel()!.onDidChangeContent(() => this.delayedRender())); @@ -577,6 +582,22 @@ class UnsupportedSettingsRenderer extends Disposable implements languages.CodeAc } private handleLocalUserConfiguration(setting: ISetting, configuration: IConfigurationPropertySchema, markerData: IMarkerData[]): void { + if (!this.userDataProfileService.currentProfile.isDefault) { + if (isEqual(this.userDataProfilesService.defaultProfile.settingsResource, this.settingsEditorModel.uri) && configuration.scope !== ConfigurationScope.APPLICATION) { + // If we're in the default profile setting file, and the setting is not + // application-scoped, fade it out. + markerData.push({ + severity: MarkerSeverity.Hint, + tags: [MarkerTag.Unnecessary], + ...setting.range, + message: nls.localize('defaultProfileSettingWhileNonDefaultActive', "This setting cannot be applied while a non-default profile is active. It will be applied when the default profile is active.") + }); + } else if (isEqual(this.userDataProfileService.currentProfile.settingsResource, this.settingsEditorModel.uri) && configuration.scope === ConfigurationScope.APPLICATION) { + // If we're in a profile setting file, and the setting is + // application-scoped, fade it out. + markerData.push(this.generateUnsupportedApplicationSettingMarker(setting)); + } + } if (this.environmentService.remoteAuthority && (configuration.scope === ConfigurationScope.MACHINE || configuration.scope === ConfigurationScope.MACHINE_OVERRIDABLE)) { markerData.push({ severity: MarkerSeverity.Hint, @@ -641,7 +662,7 @@ class UnsupportedSettingsRenderer extends Disposable implements languages.CodeAc severity: MarkerSeverity.Hint, tags: [MarkerTag.Unnecessary], ...setting.range, - message: nls.localize('unsupportedApplicationSetting', "This setting can be applied only in application user settings") + message: nls.localize('unsupportedApplicationSetting', "This setting has an application scope and can be set only in the user settings file.") }; } diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index cab53e0d6a6..596899b518b 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -37,6 +37,7 @@ import { settingsEditIcon, settingsScopeDropDownIcon } from 'vs/workbench/contri import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { ILanguageService } from 'vs/editor/common/languages/language'; +import { CONTEXT_SETTINGS_EDITOR_IN_USER_TAB } from 'vs/workbench/contrib/preferences/common/preferences'; export class FolderSettingsActionViewItem extends BaseActionViewItem { @@ -205,7 +206,7 @@ export class FolderSettingsActionViewItem extends BaseActionViewItem { } } -export type SettingsTarget = ConfigurationTarget.USER_LOCAL | ConfigurationTarget.USER_REMOTE | ConfigurationTarget.WORKSPACE | URI; +export type SettingsTarget = ConfigurationTarget.APPLICATION | ConfigurationTarget.USER_LOCAL | ConfigurationTarget.USER_REMOTE | ConfigurationTarget.WORKSPACE | URI; export interface ISettingsTargetsWidgetOptions { enableRemoteSettings?: boolean; @@ -220,6 +221,7 @@ export class SettingsTargetsWidget extends Widget { private folderSettingsAction!: Action; private folderSettings!: FolderSettingsActionViewItem; private options: ISettingsTargetsWidgetOptions; + private inUserTab: IContextKey; private _settingsTarget: SettingsTarget | null = null; @@ -235,12 +237,14 @@ export class SettingsTargetsWidget extends Widget { @ILabelService private readonly labelService: ILabelService, @IPreferencesService private readonly preferencesService: IPreferencesService, @ILanguageService private readonly languageService: ILanguageService, + @IContextKeyService contextKeyService: IContextKeyService, ) { super(); - this.options = options || {}; + this.options = options ?? {}; this.create(parent); this._register(this.contextService.onDidChangeWorkbenchState(() => this.onWorkbenchStateChanged())); this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.update())); + this.inUserTab = CONTEXT_SETTINGS_EDITOR_IN_USER_TAB.bindTo(contextKeyService); } private resetLabels() { @@ -265,12 +269,12 @@ export class SettingsTargetsWidget extends Widget { this.userLocalSettings = new Action('userSettings', '', '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_LOCAL)); this.preferencesService.getEditableSettingsURI(ConfigurationTarget.USER_LOCAL).then(uri => { // Don't wait to create UI on resolving remote - this.userLocalSettings.tooltip = uri?.fsPath || ''; + this.userLocalSettings.tooltip = uri?.fsPath ?? ''; }); this.userRemoteSettings = new Action('userSettingsRemote', '', '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_REMOTE)); this.preferencesService.getEditableSettingsURI(ConfigurationTarget.USER_REMOTE).then(uri => { - this.userRemoteSettings.tooltip = uri?.fsPath || ''; + this.userRemoteSettings.tooltip = uri?.fsPath ?? ''; }); this.workspaceSettings = new Action('workspaceSettings', '', '.settings-tab', false, () => this.updateTarget(ConfigurationTarget.WORKSPACE)); @@ -301,6 +305,7 @@ export class SettingsTargetsWidget extends Widget { } else { this.folderSettings.getAction().checked = false; } + this.inUserTab.set(this.userLocalSettings.checked); } setResultCount(settingsTarget: SettingsTarget, count: number): void { @@ -328,10 +333,11 @@ export class SettingsTargetsWidget extends Widget { if (filter) { const languageToUse = this.languageService.getLanguageName(filter); if (languageToUse) { - this.userLocalSettings.label += ` [${languageToUse}]`; - this.userRemoteSettings.label += ` [${languageToUse}]`; - this.workspaceSettings.label += ` [${languageToUse}]`; - this.folderSettingsAction.label += ` [${languageToUse}]`; + const languageSuffix = ` [${languageToUse}]`; + this.userLocalSettings.label += languageSuffix; + this.userRemoteSettings.label += languageSuffix; + this.workspaceSettings.label += languageSuffix; + this.folderSettingsAction.label += languageSuffix; } } } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 83d1addd26d..20b0969540c 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -61,6 +61,8 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { SettingsSearchFilterDropdownMenuActionViewItem } from 'vs/workbench/contrib/preferences/browser/settingsSearchMenu'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ISettingOverrideClickEvent } from 'vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators'; +import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; export const enum SettingsFocusContext { Search, @@ -686,16 +688,31 @@ export class SettingsEditor2 extends EditorPane { } } + switchToApplicationSettingsFile(): Promise { + const query = parseQuery(this.searchWidget.getValue()).query; + return this.openSettingsFile({ query }, true); + } + switchToSettingsFile(): Promise { const query = parseQuery(this.searchWidget.getValue()).query; return this.openSettingsFile({ query }); } - private async openSettingsFile(options?: ISettingsEditorOptions): Promise { + private async openSettingsFile(options?: ISettingsEditorOptions, forceOpenApplicationSettings?: boolean): Promise { const currentSettingsTarget = this.settingsTargetsWidget.settingsTarget; const openOptions: IOpenSettingsOptions = { jsonEditor: true, ...options }; if (currentSettingsTarget === ConfigurationTarget.USER_LOCAL) { + if (options?.revealSetting) { + const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); + const configurationScope = configurationProperties[options?.revealSetting.key]?.scope; + if (configurationScope === ConfigurationScope.APPLICATION) { + return this.preferencesService.openApplicationSettings(openOptions); + } + } + if (forceOpenApplicationSettings) { + return this.preferencesService.openApplicationSettings(openOptions); + } return this.preferencesService.openUserSettings(openOptions); } else if (currentSettingsTarget === ConfigurationTarget.USER_REMOTE) { return this.preferencesService.openRemoteSettings(openOptions); @@ -852,7 +869,7 @@ export class SettingsEditor2 extends EditorPane { private createSettingsTree(container: HTMLElement): void { this.settingRenderers = this.instantiationService.createInstance(SettingTreeRenderers); - this._register(this.settingRenderers.onDidChangeSetting(e => this.onDidChangeSetting(e.key, e.value, e.type, e.manualReset))); + this._register(this.settingRenderers.onDidChangeSetting(e => this.onDidChangeSetting(e.key, e.value, e.type, e.manualReset, e.scope))); this._register(this.settingRenderers.onDidOpenSettings(settingKey => { this.openSettingsFile({ revealSetting: { key: settingKey, edit: true } }); })); @@ -939,18 +956,18 @@ export class SettingsEditor2 extends EditorPane { })); } - private onDidChangeSetting(key: string, value: any, type: SettingValueType | SettingValueType[], manualReset: boolean): void { + private onDidChangeSetting(key: string, value: any, type: SettingValueType | SettingValueType[], manualReset: boolean, scope: ConfigurationScope | undefined): void { const parsedQuery = parseQuery(this.searchWidget.getValue()); const languageFilter = parsedQuery.languageFilter; if (this.pendingSettingUpdate && this.pendingSettingUpdate.key !== key) { - this.updateChangedSetting(key, value, manualReset, languageFilter); + this.updateChangedSetting(key, value, manualReset, languageFilter, scope); } this.pendingSettingUpdate = { key, value, languageFilter }; if (SettingsEditor2.shouldSettingUpdateFast(type)) { - this.settingFastUpdateDelayer.trigger(() => this.updateChangedSetting(key, value, manualReset, languageFilter)); + this.settingFastUpdateDelayer.trigger(() => this.updateChangedSetting(key, value, manualReset, languageFilter, scope)); } else { - this.settingSlowUpdateDelayer.trigger(() => this.updateChangedSetting(key, value, manualReset, languageFilter)); + this.settingSlowUpdateDelayer.trigger(() => this.updateChangedSetting(key, value, manualReset, languageFilter, scope)); } } @@ -1020,12 +1037,12 @@ export class SettingsEditor2 extends EditorPane { return ancestors.reverse(); } - private updateChangedSetting(key: string, value: any, manualReset: boolean, languageFilter: string | undefined): Promise { + private updateChangedSetting(key: string, value: any, manualReset: boolean, languageFilter: string | undefined, scope: ConfigurationScope | undefined): Promise { // ConfigurationService displays the error if this fails. - // Force a render afterwards because onDidConfigurationUpdate doesn't fire if the update doesn't result in an effective setting value change + // Force a render afterwards because onDidConfigurationUpdate doesn't fire if the update doesn't result in an effective setting value change. const settingsTarget = this.settingsTargetsWidget.settingsTarget; const resource = URI.isUri(settingsTarget) ? settingsTarget : undefined; - const configurationTarget = (resource ? ConfigurationTarget.WORKSPACE_FOLDER : settingsTarget); + const configurationTarget = (resource ? ConfigurationTarget.WORKSPACE_FOLDER : settingsTarget) ?? ConfigurationTarget.USER_LOCAL; const overrides: IConfigurationUpdateOverrides = { resource, overrideIdentifiers: languageFilter ? [languageFilter] : undefined }; const configurationTargetIsWorkspace = configurationTarget === ConfigurationTarget.WORKSPACE || configurationTarget === ConfigurationTarget.WORKSPACE_FOLDER; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts index ae71ad571f6..3ece07a23b6 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -12,7 +12,7 @@ import { Emitter } from 'vs/base/common/event'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { localize } from 'vs/nls'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; import { getDefaultIgnoredSettings, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; @@ -44,7 +44,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { constructor( container: HTMLElement, - @IConfigurationService configurationService: IConfigurationService, + @IConfigurationService private readonly configurationService: IConfigurationService, @IHoverService hoverService: IHoverService, @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @ILanguageService private readonly languageService: ILanguageService) { @@ -133,7 +133,20 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { updateScopeOverrides(element: SettingsTreeSettingElement, elementDisposables: DisposableStore, onDidClickOverrideElement: Emitter) { this.scopeOverridesElement.innerText = ''; this.scopeOverridesElement.style.display = 'none'; - if (element.overriddenScopeList.length || element.overriddenDefaultsLanguageList.length) { + const profileFeatureEnabled = this.configurationService.getValue('workbench.experimental.settingsProfiles.enabled'); + if (profileFeatureEnabled && element.matchesScope(ConfigurationTarget.APPLICATION, false)) { + // If the setting is an application-scoped setting, there are no overrides so we can use this + // indicator to display that information instead. + this.scopeOverridesElement.style.display = 'inline'; + this.scopeOverridesElement.classList.add('with-custom-hover'); + + const applicationSettingText = localize('applicationSetting', "Applies to all profiles"); + this.scopeOverridesLabel.text = applicationSettingText; + + const content = localize('applicationSettingDescription', "The setting is not specific to the current profile, and will retain its value when switching profiles."); + this.hover?.dispose(); + this.hover = setupCustomHover(this.hoverDelegate, this.scopeOverridesElement, content); + } else if (element.overriddenScopeList.length || element.overriddenDefaultsLanguageList.length) { if ((MODIFIED_INDICATOR_USE_INLINE_ONLY && element.overriddenScopeList.length) || (element.overriddenScopeList.length === 1 && !element.overriddenDefaultsLanguageList.length)) { // Render inline if we have the flag and there are scope overrides to render, @@ -281,6 +294,11 @@ function getAccessibleScopeDisplayMidSentenceText(completeScope: string, languag export function getIndicatorsLabelAriaLabel(element: SettingsTreeSettingElement, configurationService: IConfigurationService, languageService: ILanguageService): string { const ariaLabelSections: string[] = []; + const profileFeatureEnabled = configurationService.getValue('workbench.experimental.settingsProfiles.enabled'); + if (profileFeatureEnabled && element.matchesScope(ConfigurationTarget.APPLICATION, false)) { + ariaLabelSections.push(localize('applicationSettingDescriptionAccessible', "Setting value retained when switching profiles")); + } + // Add other overrides text const otherOverridesStart = element.isConfigured ? localize('alsoConfiguredIn', "Also modified in") : diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 39df9facda5..90a8b212b6c 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -63,6 +63,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { focusedRowBackground, focusedRowBorder, rowHoverBackground, settingsHeaderForeground, settingsNumberInputBackground, settingsNumberInputBorder, settingsNumberInputForeground, settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsSelectListBorder, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; import { getIndicatorsLabelAriaLabel, ISettingOverrideClickEvent, SettingsTreeIndicatorsLabel } from 'vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators'; import { ILanguageService } from 'vs/editor/common/languages/language'; +import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; const $ = DOM.$; @@ -666,6 +667,7 @@ export interface ISettingChangeEvent { value: any; // undefined => reset/unconfigure type: SettingValueType | SettingValueType[]; manualReset: boolean; + scope: ConfigurationScope | undefined; } export interface ISettingLinkClickEvent { @@ -921,7 +923,13 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre template.indicatorsLabel.updateScopeOverrides(element, template.elementDisposables, this._onDidClickOverrideElement); - const onChange = (value: any) => this._onDidChangeSetting.fire({ key: element.setting.key, value, type: template.context!.valueType, manualReset: false }); + const onChange = (value: any) => this._onDidChangeSetting.fire({ + key: element.setting.key, + value, + type: template.context!.valueType, + manualReset: false, + scope: element.setting.scope + }); const deprecationText = element.setting.deprecationMessage || ''; if (deprecationText && element.setting.deprecationMessageIsMarkdown) { const disposables = new DisposableStore(); @@ -1505,7 +1513,8 @@ export class SettingExcludeRenderer extends AbstractSettingRenderer implements I key: template.context.setting.key, value: Object.keys(newValue).length === 0 ? undefined : sortKeys(newValue), type: template.context.valueType, - manualReset: false + manualReset: false, + scope: template.context.setting.scope }); } } @@ -1970,7 +1979,13 @@ export class SettingTreeRenderers { new Action('settings.resetSetting', localize('resetSettingLabel', "Reset Setting"), undefined, undefined, async context => { if (context instanceof SettingsTreeSettingElement) { if (!context.isUntrusted) { - this._onDidChangeSetting.fire({ key: context.setting.key, value: undefined, type: context.setting.type as SettingValueType, manualReset: true }); + this._onDidChangeSetting.fire({ + key: context.setting.key, + value: undefined, + type: context.setting.type as SettingValueType, + manualReset: true, + scope: context.setting.scope + }); } } }), diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index 8f7f3f598b8..8238b7b7ee2 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -13,7 +13,7 @@ import { ITOCEntry, knownAcronyms, knownTermMappings, tocData } from 'vs/workben import { ENABLE_LANGUAGE_FILTER, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; import { IExtensionSetting, ISearchResult, ISetting, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { FOLDER_SCOPES, WORKSPACE_SCOPES, REMOTE_MACHINE_SCOPES, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, LOCAL_MACHINE_PROFILE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; +import { FOLDER_SCOPES, WORKSPACE_SCOPES, REMOTE_MACHINE_SCOPES, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, APPLICATION_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; @@ -167,8 +167,7 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { parent: SettingsTreeGroupElement, inspectResult: IInspectResult, isWorkspaceTrusted: boolean, - private readonly languageService: ILanguageService, - private readonly userDataProfileService: IUserDataProfileService, + private readonly languageService: ILanguageService ) { super(sanitizeId(parent.id + '_' + setting.key)); this.setting = setting; @@ -366,24 +365,25 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { return true; } + if (configTarget === ConfigurationTarget.APPLICATION) { + return APPLICATION_SCOPES.includes(this.setting.scope); + } + if (configTarget === ConfigurationTarget.WORKSPACE_FOLDER) { - return FOLDER_SCOPES.indexOf(this.setting.scope) !== -1; + return FOLDER_SCOPES.includes(this.setting.scope); } if (configTarget === ConfigurationTarget.WORKSPACE) { - return WORKSPACE_SCOPES.indexOf(this.setting.scope) !== -1; + return WORKSPACE_SCOPES.includes(this.setting.scope); } if (configTarget === ConfigurationTarget.USER_REMOTE) { - return REMOTE_MACHINE_SCOPES.indexOf(this.setting.scope) !== -1; + return REMOTE_MACHINE_SCOPES.includes(this.setting.scope); } if (configTarget === ConfigurationTarget.USER_LOCAL) { - if (!this.userDataProfileService.currentProfile.isDefault) { - return LOCAL_MACHINE_PROFILE_SCOPES.indexOf(this.setting.scope) !== -1; - } if (isRemote) { - return LOCAL_MACHINE_SCOPES.indexOf(this.setting.scope) !== -1; + return LOCAL_MACHINE_SCOPES.includes(this.setting.scope); } } @@ -531,9 +531,18 @@ export class SettingsTreeModel { this.updateSettings(arrays.flatten([...this._treeElementsBySettingName.values()]).filter(s => s.isUntrusted)); } + private getTargetToInspect(settingScope: ConfigurationScope | undefined): SettingsTarget { + if (!this._userDataProfileService.currentProfile.isDefault && settingScope === ConfigurationScope.APPLICATION) { + return ConfigurationTarget.APPLICATION; + } else { + return this._viewState.settingsTarget; + } + } + private updateSettings(settings: SettingsTreeSettingElement[]): void { settings.forEach(element => { - const inspectResult = inspectSetting(element.setting.key, this._viewState.settingsTarget, this._viewState.languageFilter, this._configurationService); + const target = this.getTargetToInspect(element.setting.scope); + const inspectResult = inspectSetting(element.setting.key, target, this._viewState.languageFilter, this._configurationService); element.update(inspectResult, this._isWorkspaceTrusted); }); } @@ -569,8 +578,9 @@ export class SettingsTreeModel { } private createSettingsTreeSettingElement(setting: ISetting, parent: SettingsTreeGroupElement): SettingsTreeSettingElement { - const inspectResult = inspectSetting(setting.key, this._viewState.settingsTarget, this._viewState.languageFilter, this._configurationService); - const element = new SettingsTreeSettingElement(setting, parent, inspectResult, this._isWorkspaceTrusted, this._languageService, this._userDataProfileService); + const target = this.getTargetToInspect(setting.scope); + const inspectResult = inspectSetting(setting.key, target, this._viewState.languageFilter, this._configurationService); + const element = new SettingsTreeSettingElement(setting, parent, inspectResult, this._isWorkspaceTrusted, this._languageService); const nameElements = this._treeElementsBySettingName.get(setting.key) || []; nameElements.push(element); @@ -582,7 +592,7 @@ export class SettingsTreeModel { interface IInspectResult { isConfigured: boolean; inspected: IConfigurationValue; - targetSelector: 'userLocalValue' | 'userRemoteValue' | 'workspaceValue' | 'workspaceFolderValue'; + targetSelector: 'applicationValue' | 'userLocalValue' | 'userRemoteValue' | 'workspaceValue' | 'workspaceFolderValue'; inspectedLanguageOverrides: Map>; languageSelector: string | undefined; } @@ -590,17 +600,21 @@ interface IInspectResult { export function inspectSetting(key: string, target: SettingsTarget, languageFilter: string | undefined, configurationService: IWorkbenchConfigurationService): IInspectResult { const inspectOverrides = URI.isUri(target) ? { resource: target } : undefined; const inspected = configurationService.inspect(key, inspectOverrides); - const targetSelector = target === ConfigurationTarget.USER_LOCAL ? 'userLocalValue' : - target === ConfigurationTarget.USER_REMOTE ? 'userRemoteValue' : - target === ConfigurationTarget.WORKSPACE ? 'workspaceValue' : - 'workspaceFolderValue'; - const targetOverrideSelector = target === ConfigurationTarget.USER_LOCAL ? 'userLocal' : - target === ConfigurationTarget.USER_REMOTE ? 'userRemote' : - target === ConfigurationTarget.WORKSPACE ? 'workspace' : - 'workspaceFolder'; + const targetSelector = target === ConfigurationTarget.APPLICATION ? 'applicationValue' : + target === ConfigurationTarget.USER_LOCAL ? 'userLocalValue' : + target === ConfigurationTarget.USER_REMOTE ? 'userRemoteValue' : + target === ConfigurationTarget.WORKSPACE ? 'workspaceValue' : + 'workspaceFolderValue'; + const targetOverrideSelector = target === ConfigurationTarget.APPLICATION ? 'application' : + target === ConfigurationTarget.USER_LOCAL ? 'userLocal' : + target === ConfigurationTarget.USER_REMOTE ? 'userRemote' : + target === ConfigurationTarget.WORKSPACE ? 'workspace' : + 'workspaceFolder'; let isConfigured = typeof inspected[targetSelector] !== 'undefined'; if (!isConfigured) { - if (target === ConfigurationTarget.USER_LOCAL) { + if (target === ConfigurationTarget.APPLICATION) { + isConfigured = !!configurationService.restrictedSettings.application?.includes(key); + } else if (target === ConfigurationTarget.USER_LOCAL) { isConfigured = !!configurationService.restrictedSettings.userLocal?.includes(key); } else if (target === ConfigurationTarget.USER_REMOTE) { isConfigured = !!configurationService.restrictedSettings.userRemote?.includes(key); diff --git a/src/vs/workbench/contrib/preferences/common/preferences.ts b/src/vs/workbench/contrib/preferences/common/preferences.ts index 39a4cc0d044..fbfc94fa983 100644 --- a/src/vs/workbench/contrib/preferences/common/preferences.ts +++ b/src/vs/workbench/contrib/preferences/common/preferences.ts @@ -53,6 +53,7 @@ export const CONTEXT_KEYBINDINGS_EDITOR = new RawContextKey('inKeybindi export const CONTEXT_KEYBINDINGS_SEARCH_FOCUS = new RawContextKey('inKeybindingsSearch', false); export const CONTEXT_KEYBINDING_FOCUS = new RawContextKey('keybindingFocus', false); export const CONTEXT_WHEN_FOCUS = new RawContextKey('whenFocus', false); +export const CONTEXT_SETTINGS_EDITOR_IN_USER_TAB = new RawContextKey('inSettingsEditorUserTab', false); export const KEYBINDINGS_EDITOR_COMMAND_SEARCH = 'keybindings.editor.searchKeybindings'; export const KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS = 'keybindings.editor.clearSearchResults'; diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index 26e00008c87..8713a124b0d 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -24,6 +24,7 @@ export const folderSettingsSchemaId = 'vscode://schemas/settings/folder'; export const launchSchemaId = 'vscode://schemas/launch'; export const tasksSchemaId = 'vscode://schemas/tasks'; +export const APPLICATION_SCOPES = [ConfigurationScope.APPLICATION]; export const PROFILE_SCOPES = [ConfigurationScope.MACHINE, ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE, ConfigurationScope.LANGUAGE_OVERRIDABLE]; export const LOCAL_MACHINE_PROFILE_SCOPES = [ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE, ConfigurationScope.LANGUAGE_OVERRIDABLE]; export const LOCAL_MACHINE_SCOPES = [ConfigurationScope.APPLICATION, ...LOCAL_MACHINE_PROFILE_SCOPES]; @@ -63,7 +64,7 @@ export type RestrictedSettings = { export const IWorkbenchConfigurationService = refineServiceDecorator(IConfigurationService); export interface IWorkbenchConfigurationService extends IConfigurationService { /** - * Restricted settings defined in each configuraiton target + * Restricted settings defined in each configuration target */ readonly restrictedSettings: RestrictedSettings; diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index fa8078055ce..fa24a64d7af 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -45,6 +45,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { isArray, isObject } from 'vs/base/common/types'; import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; const emptyEditableSettingsContent = '{\n}'; @@ -67,6 +68,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, @ITextModelService private readonly textModelResolverService: ITextModelService, @IKeybindingService keybindingService: IKeybindingService, @IModelService private readonly modelService: IModelService, @@ -166,7 +168,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.createDefaultSettingsEditorModel(uri); } - if (this.userSettingsResource.toString() === uri.toString()) { + if (this.userSettingsResource.toString() === uri.toString() || this.userDataProfilesService.defaultProfile.settingsResource.toString() === uri.toString()) { return this.createEditableSettingsEditorModel(ConfigurationTarget.USER_LOCAL, uri); } @@ -248,6 +250,14 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.editorGroupService.activeGroup.activeEditorPane!; } + openApplicationSettings(options: IOpenSettingsOptions = {}): Promise { + options = { + ...options, + target: ConfigurationTarget.USER_LOCAL, + }; + return this.open(this.userDataProfilesService.defaultProfile.settingsResource, options); + } + openUserSettings(options: IOpenSettingsOptions = {}): Promise { options = { ...options, @@ -450,6 +460,8 @@ export class PreferencesService extends Disposable implements IPreferencesServic public async getEditableSettingsURI(configurationTarget: ConfigurationTarget, resource?: URI): Promise { switch (configurationTarget) { + case ConfigurationTarget.APPLICATION: + return this.userDataProfilesService.defaultProfile.settingsResource; case ConfigurationTarget.USER: case ConfigurationTarget.USER_LOCAL: return this.userSettingsResource; diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index 06b43562999..c82d622e95f 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -233,6 +233,7 @@ export interface IPreferencesService { openRawDefaultSettings(): Promise; openSettings(options?: IOpenSettingsOptions): Promise; + openApplicationSettings(options?: IOpenSettingsOptions): Promise; openUserSettings(options?: IOpenSettingsOptions): Promise; openRemoteSettings(options?: IOpenSettingsOptions): Promise; openWorkspaceSettings(options?: IOpenSettingsOptions): Promise; From ff31f6b577f47147664880a90931b10f6b43e2ca Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Wed, 20 Jul 2022 15:01:40 -0700 Subject: [PATCH 110/197] Update package.json --- extensions/ipynb/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/ipynb/package.json b/extensions/ipynb/package.json index 496fb2cb3c7..725b86330ad 100644 --- a/extensions/ipynb/package.json +++ b/extensions/ipynb/package.json @@ -53,7 +53,7 @@ ], "notebookRenderer": [ { - "id": "vscode.markdown-it-cell-attachment-renderer-extension", + "id": "vscode.markdown-it-cell-attachment-renderer", "displayName": "Markdown it ipynb Cell Attachment renderer", "entrypoint": { "extends": "vscode.markdown-it-renderer", From 1998b33403d1b12e1c5e82226143a8f99bb5cbe4 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Wed, 20 Jul 2022 15:26:26 -0700 Subject: [PATCH 111/197] consistent verb for show menu bar in titlebar context (#155784) --- src/vs/workbench/browser/actions/layoutActions.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 3890ccfb5ba..150c192a1b8 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -583,9 +583,6 @@ if (isWindows || isLinux || isWeb) { id: MenuId.MenubarAppearanceMenu, group: '2_workbench_layout', order: 0 - }, { - id: MenuId.TitleBarContext, - order: 0 }] }); } @@ -594,6 +591,16 @@ if (isWindows || isLinux || isWeb) { return accessor.get(IWorkbenchLayoutService).toggleMenuBar(); } }); + + // Add separately to title bar context menu so we can use a different title + MenuRegistry.appendMenuItem(MenuId.TitleBarContext, { + command: { + id: 'workbench.action.toggleMenuBar', + title: localize('miShowMenuBarNoMnemonic', "Show Menu Bar"), + toggled: ContextKeyExpr.and(IsMacNativeContext.toNegated(), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'hidden'), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'toggle'), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'compact')) + }, + order: 0 + }); } // --- Reset View Locations From 85e47d315f43e6251674e14e7110a0a6364120b4 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 21 Jul 2022 00:29:40 +0200 Subject: [PATCH 112/197] Action button - remove button borders that are adjacent to the separator. (#155779) --- src/vs/base/browser/ui/button/button.css | 8 ++++++++ src/vs/base/browser/ui/button/button.ts | 10 ++++++++++ src/vs/platform/theme/common/colorRegistry.ts | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/button/button.css b/src/vs/base/browser/ui/button/button.css index 98a1c8aadfa..4d612f9f674 100644 --- a/src/vs/base/browser/ui/button/button.css +++ b/src/vs/base/browser/ui/button/button.css @@ -52,6 +52,10 @@ opacity: 0.4 !important; } +.monaco-button-dropdown > .monaco-button.monaco-text-button { + border-right-width: 0 !important; +} + .monaco-button-dropdown .monaco-button-dropdown-separator { padding: 4px 0; cursor: default; @@ -62,6 +66,10 @@ width: 1px; } +.monaco-button-dropdown > .monaco-button.monaco-dropdown-button { + border-left-width: 0 !important; +} + .monaco-description-button { flex-direction: column; } diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index 920160b9a15..855ba9e0c90 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -316,6 +316,16 @@ export class ButtonWithDropdown extends Disposable implements IButton { this.dropdownButton.style(styles); // Separator + const border = styles.buttonBorder ? styles.buttonBorder.toString() : ''; + + this.separatorContainer.style.borderTopWidth = border ? '1px' : ''; + this.separatorContainer.style.borderTopStyle = border ? 'solid' : ''; + this.separatorContainer.style.borderTopColor = border; + + this.separatorContainer.style.borderBottomWidth = border ? '1px' : ''; + this.separatorContainer.style.borderBottomStyle = border ? 'solid' : ''; + this.separatorContainer.style.borderBottomColor = border; + this.separatorContainer.style.backgroundColor = styles.buttonBackground?.toString() ?? ''; this.separator.style.backgroundColor = styles.buttonSeparator?.toString() ?? ''; } diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index b26f20b529d..9bfb2dfc45a 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -266,7 +266,7 @@ export const checkboxForeground = registerColor('checkbox.foreground', { dark: s export const checkboxBorder = registerColor('checkbox.border', { dark: selectBorder, light: selectBorder, hcDark: selectBorder, hcLight: selectBorder }, nls.localize('checkbox.border', "Border color of checkbox widget.")); export const buttonForeground = registerColor('button.foreground', { dark: Color.white, light: Color.white, hcDark: Color.white, hcLight: Color.white }, nls.localize('buttonForeground', "Button foreground color.")); -export const buttonSeparator = registerColor('button.separator', { dark: transparent(buttonForeground, .4), light: transparent(buttonForeground, .4), hcDark: null, hcLight: transparent(buttonForeground, .4) }, nls.localize('buttonSeparator', "Button separator color.")); +export const buttonSeparator = registerColor('button.separator', { dark: transparent(buttonForeground, .4), light: transparent(buttonForeground, .4), hcDark: transparent(buttonForeground, .4), hcLight: transparent(buttonForeground, .4) }, nls.localize('buttonSeparator', "Button separator color.")); export const buttonBackground = registerColor('button.background', { dark: '#0E639C', light: '#007ACC', hcDark: null, hcLight: '#0F4A85' }, nls.localize('buttonBackground', "Button background color.")); export const buttonHoverBackground = registerColor('button.hoverBackground', { dark: lighten(buttonBackground, 0.2), light: darken(buttonBackground, 0.2), hcDark: null, hcLight: null }, nls.localize('buttonHoverBackground', "Button background color when hovering.")); export const buttonBorder = registerColor('button.border', { dark: contrastBorder, light: contrastBorder, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('buttonBorder', "Button border color.")); From eaeb3a74ab80384a3cd2c924d73721a290bb0f99 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Wed, 20 Jul 2022 19:02:25 -0400 Subject: [PATCH 113/197] Ensure extensions are properly deregistered from walkthrough (#155770) Ensure extensions are properly deregistered --- .../welcomeGettingStarted/browser/gettingStartedService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts index 210f171d0a8..1cdd953a342 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts @@ -435,7 +435,7 @@ export class WalkthroughsService extends Disposable implements IWalkthroughsServ } extension.contributes?.walkthroughs?.forEach(section => { - const categoryID = extension.identifier.value + '#walkthrough#' + section.id; + const categoryID = extension.identifier.value + '#' + section.id; section.steps.forEach(step => { const fullyQualifiedID = extension.identifier.value + '#' + section.id + '#' + step.id; this.steps.delete(fullyQualifiedID); From 63142212a1c7ab5230b9bae011629b5ccebaaaaf Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Wed, 20 Jul 2022 16:10:54 -0700 Subject: [PATCH 114/197] remove md-it dependency, added undefined check to attachments --- extensions/ipynb/package.json | 1 - extensions/ipynb/src/cellAttachmentRenderer.ts | 16 +++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/extensions/ipynb/package.json b/extensions/ipynb/package.json index 725b86330ad..3b6ed828519 100644 --- a/extensions/ipynb/package.json +++ b/extensions/ipynb/package.json @@ -87,7 +87,6 @@ "dependencies": { "@enonic/fnv-plus": "^1.3.0", "detect-indent": "^6.0.0", - "markdown-it": "^12.3.2", "uuid": "^8.3.2" }, "devDependencies": { diff --git a/extensions/ipynb/src/cellAttachmentRenderer.ts b/extensions/ipynb/src/cellAttachmentRenderer.ts index 09ce8921cb3..bcedce3d5d5 100644 --- a/extensions/ipynb/src/cellAttachmentRenderer.ts +++ b/extensions/ipynb/src/cellAttachmentRenderer.ts @@ -22,14 +22,16 @@ export async function activate(ctx: RendererContext) { md.renderer.rules.image = (tokens: MarkdownItToken[], idx: number, options, env, self) => { const token = tokens[idx]; const src = token.attrGet('src'); - const attachments: Record> = (env.outputItem.metadata as any).custom.attachments; - if (attachments) { - if (src) { - const [attachmentKey, attachmentVal] = Object.entries(attachments[src.replace('attachment:', '')])[0]; - const b64Markdown = 'data:' + attachmentKey + ';base64,' + attachmentVal; - token.attrSet('src', b64Markdown); - } + // if (env.outputItem.metadata?.custom?.attachments) { + // const attachments: Record> = (env.outputItem.metadata as any).custom.attachments; + const attachments: Record> = env.outputItem.metadata?.custom?.attachments; + if (attachments && src) { + const [attachmentKey, attachmentVal] = Object.entries(attachments[src.replace('attachment:', '')])[0]; + const b64Markdown = 'data:' + attachmentKey + ';base64,' + attachmentVal; + token.attrSet('src', b64Markdown); } + // } + // if (original) { return original(tokens, idx, options, env, self); From eaf321d50dc9a336bc8fd66f4814e3df08018d33 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Wed, 20 Jul 2022 16:12:32 -0700 Subject: [PATCH 115/197] formatting --- extensions/ipynb/src/cellAttachmentRenderer.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/extensions/ipynb/src/cellAttachmentRenderer.ts b/extensions/ipynb/src/cellAttachmentRenderer.ts index bcedce3d5d5..7e05834dbcb 100644 --- a/extensions/ipynb/src/cellAttachmentRenderer.ts +++ b/extensions/ipynb/src/cellAttachmentRenderer.ts @@ -22,16 +22,12 @@ export async function activate(ctx: RendererContext) { md.renderer.rules.image = (tokens: MarkdownItToken[], idx: number, options, env, self) => { const token = tokens[idx]; const src = token.attrGet('src'); - // if (env.outputItem.metadata?.custom?.attachments) { - // const attachments: Record> = (env.outputItem.metadata as any).custom.attachments; const attachments: Record> = env.outputItem.metadata?.custom?.attachments; if (attachments && src) { const [attachmentKey, attachmentVal] = Object.entries(attachments[src.replace('attachment:', '')])[0]; const b64Markdown = 'data:' + attachmentKey + ';base64,' + attachmentVal; token.attrSet('src', b64Markdown); } - // } - // if (original) { return original(tokens, idx, options, env, self); From 9776b9d4378ad95aa7b815a3413eed003cb6024b Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Wed, 20 Jul 2022 17:01:34 -0700 Subject: [PATCH 116/197] Add file decorations to search results (#155549) * added file decoration args to search --- src/vs/workbench/contrib/search/browser/searchResultsView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/searchResultsView.ts b/src/vs/workbench/contrib/search/browser/searchResultsView.ts index 976f351c60e..68ae67e464c 100644 --- a/src/vs/workbench/contrib/search/browser/searchResultsView.ts +++ b/src/vs/workbench/contrib/search/browser/searchResultsView.ts @@ -184,7 +184,7 @@ export class FileMatchRenderer extends Disposable implements ITreeRenderer, index: number, templateData: IFileMatchTemplate): void { const fileMatch = node.element; templateData.el.setAttribute('data-resource', fileMatch.resource.toString()); - templateData.label.setFile(fileMatch.resource, { hideIcon: false }); + templateData.label.setFile(fileMatch.resource, { hideIcon: false, fileDecorations: { colors: true, badges: true } }); const count = fileMatch.count(); templateData.badge.setCount(count); templateData.badge.setTitleFormat(count > 1 ? nls.localize('searchMatches', "{0} matches found", count) : nls.localize('searchMatch', "{0} match found", count)); From 62469028a6dce9db5b7c4639be7f447f4ab0e35c Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Wed, 20 Jul 2022 21:20:23 -0700 Subject: [PATCH 117/197] make system context menu code more understandable (#155747) * removing unnecessary enablements refs #155276 * add some comments * address feedback, bring back enablements but comment them --- src/vs/platform/windows/electron-main/window.ts | 9 +++++++++ .../parts/titlebar/titlebarPart.ts | 17 ++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts index 85fc9e0c27e..6c64ff8c565 100644 --- a/src/vs/platform/windows/electron-main/window.ts +++ b/src/vs/platform/windows/electron-main/window.ts @@ -292,12 +292,21 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Windows Custom System Context Menu // See https://github.com/electron/electron/issues/24893 + // + // The purpose of this is to allow for the context menu in the Windows Title Bar + // + // Currently, all mouse events in the title bar are captured by the OS + // thus we need to capture them here with a window hook specific to Windows + // and then forward them to the correct window. if (isWindows && useCustomTitleStyle) { + // https://docs.microsoft.com/en-us/windows/win32/menurc/wm-initmenu const WM_INITMENU = 0x0116; + // This sets up a listener for the window hook. This is a Windows-only API provided by electron. this._win.hookWindowMessage(WM_INITMENU, () => { const [x, y] = this._win.getPosition(); const cursorPos = screen.getCursorScreenPoint(); + // This is necessary to make sure the native system context menu does not show up. this._win.setEnabled(false); this._win.setEnabled(true); diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index 4720f73a32a..f6f5b60e313 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -207,7 +207,22 @@ export class TitlebarPart extends BrowserTitleBarPart { } const zoomFactor = getZoomFactor(); - this.onContextMenu(new MouseEvent('mouseup', { clientX: x / zoomFactor, clientY: y / zoomFactor }), MenuId.TitleBarContext); + const boundingRect = this.rootContainer.getBoundingClientRect(); + const eventPosition = { x, y }; + const relativeCoordinates = { x, y }; + // When comparing the coordinates with the title bar, account for zoom level if not using counter zoom. + if (!this.useCounterZoom) { + relativeCoordinates.x /= zoomFactor; + relativeCoordinates.y /= zoomFactor; + } + + // Don't trigger the menu if the click is not over the title bar + if (relativeCoordinates.x < boundingRect.left || relativeCoordinates.x > boundingRect.right || + relativeCoordinates.y < boundingRect.top || relativeCoordinates.y > boundingRect.bottom) { + return; + } + + this.onContextMenu(new MouseEvent('mouseup', { clientX: eventPosition.x / zoomFactor, clientY: eventPosition.y / zoomFactor }), MenuId.TitleBarContext); })); } From e0e7ad368e5c2a0ae28469d0292ef994d7a49b97 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 21 Jul 2022 08:35:05 +0200 Subject: [PATCH 118/197] Git - Enable action button while rebasing (#155744) Enable action button while rebasing --- extensions/git/src/actionButton.ts | 79 +++++++++++++++++------------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/extensions/git/src/actionButton.ts b/extensions/git/src/actionButton.ts index 63ee474707b..527b5b1476c 100644 --- a/extensions/git/src/actionButton.ts +++ b/extensions/git/src/actionButton.ts @@ -17,6 +17,7 @@ interface ActionButtonState { readonly HEAD: Branch | undefined; readonly isCommitInProgress: boolean; readonly isMergeInProgress: boolean; + readonly isRebaseInProgress: boolean; readonly isSyncInProgress: boolean; readonly repositoryHasChangesToCommit: boolean; } @@ -43,6 +44,7 @@ export class ActionButtonCommand { HEAD: undefined, isCommitInProgress: false, isMergeInProgress: false, + isRebaseInProgress: false, isSyncInProgress: false, repositoryHasChangesToCommit: false }; @@ -70,7 +72,7 @@ export class ActionButtonCommand { } get button(): SourceControlActionButton | undefined { - if (!this.state.HEAD || !this.state.HEAD.name) { return undefined; } + if (!this.state.HEAD) { return undefined; } let actionButton: SourceControlActionButton | undefined; @@ -90,7 +92,25 @@ export class ActionButtonCommand { // The button is disabled if (!showActionButton.commit) { return undefined; } - let title: string, tooltip: string, commandArg: string; + return { + command: this.getCommitActionButtonPrimaryCommand(), + secondaryCommands: this.getCommitActionButtonSecondaryCommands(), + enabled: this.state.repositoryHasChangesToCommit && !this.state.isCommitInProgress && !this.state.isMergeInProgress + }; + } + + private getCommitActionButtonPrimaryCommand(): Command { + let commandArg = ''; + let title = localize('scm button commit title', "{0} Commit", '$(check)'); + let tooltip = this.state.isCommitInProgress ? localize('scm button committing tooltip', "Committing Changes...") : localize('scm button commit tooltip', "Commit Changes"); + + // Rebase Continue + if (this.state.isRebaseInProgress) { + return { command: 'git.commit', title, tooltip, arguments: [this.repository.sourceControl, commandArg] }; + } + + // Commit + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); const postCommitCommand = config.get('postCommitCommand'); // Branch protection @@ -133,49 +153,36 @@ export class ActionButtonCommand { break; } default: { - commandArg = ''; - title = localize('scm button commit title', "{0} Commit", icon ?? '$(check)'); if (alwaysCommitToNewBranch) { tooltip = this.state.isCommitInProgress ? localize('scm button committing to new branch tooltip', "Committing Changes to New Branch...") : localize('scm button commit to new branch tooltip', "Commit Changes to New Branch"); - } else { - tooltip = this.state.isCommitInProgress ? - localize('scm button committing tooltip', "Committing Changes...") : - localize('scm button commit tooltip', "Commit Changes"); } break; } } - return { - command: { - command: 'git.commit', - title: title, - tooltip: tooltip, - arguments: [this.repository.sourceControl, commandArg], - }, - secondaryCommands: this.getCommitActionButtonSecondaryCommands(), - enabled: this.state.repositoryHasChangesToCommit && !this.state.isCommitInProgress && !this.state.isMergeInProgress - }; + return { command: 'git.commit', title, tooltip, arguments: [this.repository.sourceControl, commandArg] }; } private getCommitActionButtonSecondaryCommands(): Command[][] { const commandGroups: Command[][] = []; - for (const provider of this.postCommitCommandsProviderRegistry.getPostCommitCommandsProviders()) { - const commands = provider.getCommands(new ApiRepository(this.repository)); - commandGroups.push((commands ?? []).map(c => { - return { - command: 'git.commit', - title: c.title, - arguments: [this.repository.sourceControl, c.command] - }; - })); - } + if (!this.state.isRebaseInProgress) { + for (const provider of this.postCommitCommandsProviderRegistry.getPostCommitCommandsProviders()) { + const commands = provider.getCommands(new ApiRepository(this.repository)); + commandGroups.push((commands ?? []).map(c => { + return { + command: 'git.commit', + title: c.title, + arguments: [this.repository.sourceControl, c.command] + }; + })); + } - if (commandGroups.length > 0) { - commandGroups[0].splice(0, 0, { command: 'git.commit', title: localize('scm secondary button commit', "Commit") }); + if (commandGroups.length > 0) { + commandGroups[0].splice(0, 0, { command: 'git.commit', title: localize('scm secondary button commit', "Commit") }); + } } return commandGroups; @@ -185,8 +192,8 @@ export class ActionButtonCommand { const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); const showActionButton = config.get<{ publish: boolean }>('showActionButton', { publish: true }); - // Branch does have an upstream, commit/merge is in progress, or the button is disabled - if (this.state.HEAD?.upstream || this.state.isCommitInProgress || this.state.isMergeInProgress || !showActionButton.publish) { return undefined; } + // Branch does have an upstream, commit/merge/rebase is in progress, or the button is disabled + if (this.state.HEAD?.upstream || this.state.isCommitInProgress || this.state.isMergeInProgress || this.state.isRebaseInProgress || !showActionButton.publish) { return undefined; } return { command: { @@ -206,8 +213,8 @@ export class ActionButtonCommand { const showActionButton = config.get<{ sync: boolean }>('showActionButton', { sync: true }); const branchIsAheadOrBehind = (this.state.HEAD?.behind ?? 0) > 0 || (this.state.HEAD?.ahead ?? 0) > 0; - // Branch does not have an upstream, branch is not ahead/behind the remote branch, commit/merge is in progress, or the button is disabled - if (!this.state.HEAD?.upstream || !branchIsAheadOrBehind || this.state.isCommitInProgress || this.state.isMergeInProgress || !showActionButton.sync) { return undefined; } + // Branch does not have an upstream, branch is not ahead/behind the remote branch, commit/merge/rebase is in progress, or the button is disabled + if (!this.state.HEAD?.upstream || !branchIsAheadOrBehind || this.state.isCommitInProgress || this.state.isMergeInProgress || this.state.isRebaseInProgress || !showActionButton.sync) { return undefined; } const ahead = this.state.HEAD.ahead ? ` ${this.state.HEAD.ahead}$(arrow-up)` : ''; const behind = this.state.HEAD.behind ? ` ${this.state.HEAD.behind}$(arrow-down)` : ''; @@ -229,7 +236,8 @@ export class ActionButtonCommand { private onDidChangeOperations(): void { const isCommitInProgress = - this.repository.operations.isRunning(Operation.Commit); + this.repository.operations.isRunning(Operation.Commit) || + this.repository.operations.isRunning(Operation.RebaseContinue); const isSyncInProgress = this.repository.operations.isRunning(Operation.Sync) || @@ -251,6 +259,7 @@ export class ActionButtonCommand { ...this.state, HEAD: this.repository.HEAD, isMergeInProgress: this.repository.mergeGroup.resourceStates.length !== 0, + isRebaseInProgress: !!this.repository.rebaseCommit, repositoryHasChangesToCommit: this.repositoryHasChangesToCommit() }; } From cd90b2b5ad534facf037db45fb0486f9a39b8a40 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 21 Jul 2022 09:34:19 +0200 Subject: [PATCH 119/197] Git - Add telemetry comments (#155811) Add telemetry comments --- extensions/git/src/commands.ts | 14 +++++++------- extensions/git/src/repository.ts | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index b1bb4907684..c2e7ae8f46f 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -469,7 +469,7 @@ export class CommandCenter { /* __GDPR__ "clone" : { "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } } */ this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_URL' }); @@ -495,7 +495,7 @@ export class CommandCenter { /* __GDPR__ "clone" : { "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } } */ this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_directory' }); @@ -554,8 +554,8 @@ export class CommandCenter { /* __GDPR__ "clone" : { "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true } + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }, + "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true, "comment": "Indicates whether the folder is opened following the clone operation" } } */ this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: action === PostCloneAction.Open || action === PostCloneAction.OpenNewWindow ? 1 : 0 }); @@ -574,7 +574,7 @@ export class CommandCenter { /* __GDPR__ "clone" : { "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } } */ this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'directory_not_empty' }); @@ -584,7 +584,7 @@ export class CommandCenter { /* __GDPR__ "clone" : { "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } } */ this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'error' }); @@ -3066,7 +3066,7 @@ export class CommandCenter { /* __GDPR__ "git.command" : { "owner": "lszomoru", - "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The command id of the command being executed" } } */ this.telemetryReporter.sendTelemetryEvent('git.command', { command: id }); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 53ae4452973..7869e17eae5 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1894,9 +1894,9 @@ export class Repository implements Disposable { /* __GDPR__ "statusLimit" : { "owner": "lszomoru", - "ignoreSubmodules": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "limit": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "statusLength": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "ignoreSubmodules": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Setting indicating whether submodules are ignored" }, + "limit": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Setting indicating the limit of status entries" }, + "statusLength": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Total number of status entries" } } */ this.telemetryReporter.sendTelemetryEvent('statusLimit', { ignoreSubmodules: String(ignoreSubmodules) }, { limit, statusLength }); From 477af8c842a9aee2a48a42ca7b23e619f1e8aa0f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 21 Jul 2022 11:46:51 +0200 Subject: [PATCH 120/197] skip flaky test (#152145) (#155809) --- .../vscode-api-tests/src/singlefolder-tests/notebook.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts index 88eb9d8a2bb..1f114771b14 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts @@ -258,7 +258,7 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { // }); }); - test('edit API batch edits', async function () { + test.skip('edit API batch edits', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/155808 const notebook = await openRandomNotebookDocument(); const editor = await vscode.window.showNotebookDocument(notebook); From e7bf4c6a75934088e2d4315f6fcd760f99ea6f02 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Thu, 21 Jul 2022 11:47:25 +0200 Subject: [PATCH 121/197] Clarify that size is about the minimap's vertical size and remove rarely used scale (#155805) Fixes #154418: Clarify that size is about the minimap's vertical size and remove rarely used scale --- .../contrib/contextmenu/browser/contextmenu.ts | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts b/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts index 0a3945bc290..0cd4c1e73b0 100644 --- a/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts @@ -327,7 +327,7 @@ export class ContextMenuController implements IEditorContribution { } })); actions.push(createEnumAction<'proportional' | 'fill' | 'fit'>( - nls.localize('context.minimap.size', "Size"), + nls.localize('context.minimap.size', "Vertical size"), minimapOptions.enabled, 'editor.minimap.size', minimapOptions.size, @@ -342,22 +342,6 @@ export class ContextMenuController implements IEditorContribution { value: 'fit' }] )); - actions.push(createEnumAction( - nls.localize('context.minimap.scale', "Scale"), - minimapOptions.enabled, - 'editor.minimap.scale', - minimapOptions.scale, - [{ - label: nls.localize('context.minimap.scale.1', "1"), - value: 1 - }, { - label: nls.localize('context.minimap.scale.2', "2"), - value: 2 - }, { - label: nls.localize('context.minimap.scale.3', "3"), - value: 3 - }] - )); actions.push(createEnumAction<'always' | 'mouseover'>( nls.localize('context.minimap.slider', "Slider"), minimapOptions.enabled, From 924dde5c1ee79e54ebead0818cb39a1f1bd94629 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 21 Jul 2022 02:48:30 -0700 Subject: [PATCH 122/197] Documenting markdown LS (#155789) --- .../server/README.md | 120 ++++++++++++++++++ .../server/src/protocol.ts | 28 ++-- .../server/src/server.ts | 2 +- .../server/src/workspace.ts | 14 +- .../markdown-language-features/src/client.ts | 20 +-- .../src/protocol.ts | 24 ++-- 6 files changed, 168 insertions(+), 40 deletions(-) create mode 100644 extensions/markdown-language-features/server/README.md diff --git a/extensions/markdown-language-features/server/README.md b/extensions/markdown-language-features/server/README.md new file mode 100644 index 00000000000..de4e33926c3 --- /dev/null +++ b/extensions/markdown-language-features/server/README.md @@ -0,0 +1,120 @@ +# Markdown Language Server + +> **❗ Import** This is still in development. While the language server is being used by VS Code, it has not yet been tested with other clients. + +The Markdown language server powers VS Code's built-in markdown support, providing tools for writing and browsing Markdown files. It runs as a separate executable and implements the [language server protocol](https://microsoft.github.io/language-server-protocol/overview). + +This server uses the [Markdown Language Service](https://github.com/microsoft/vscode-markdown-languageservice) to implement almost all of the language features. You can use that library if you need a library for working with Markdown instead of a full language server. + + +## Server capabilities + +- [Completions](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion) for Markdown links. + +- [Folding](https://microsoft.github.io/language-server-protocol/specification#textDocument_foldingRange) of Markdown regions, block elements, and header sections. + +- [Smart selection](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_selectionRange) for inline elements, block elements, and header sections. + +- [Document Symbols](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol) for quick navigation to headers in a document. + +- [Workspace Symbols](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_symbol) for quick navigation to headers in the workspace + +- [Document links](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentLink) for making Markdown links in a document clickable. + +- [Find all references](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_references) to headers and links across all Markdown files in the workspace. + +- [Rename](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_rename) of headers and links across all Markdown files in the workspace. + +- [Go to definition](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition) from links to headers or link definitions. + +- (experimental) [Pull diagnostics (validation)](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_pullDiagnostics) for links. + + + +## Client requirements + +### Initialization options + +The client can send the following initialization options to the server: + +- `markdownFileExtensions` Array file extensions that should be considered as Markdown. These should not include the leading `.`. For example: `['md', 'mdown', 'markdown']`. + +### Settings + +Clients may send a `workspace/didChangeConfiguration` notification to notify the server of settings changes. +The server supports the following settings: + +- `markdown` + - `suggest` + - `paths` + - `enabled` — Enable/disable path suggestions. + - `experimental` + - `validate` + - `enabled` — Enable/disable all validation. + - `referenceLinks` + - `enabled` — Enable/disable validation of reference links: `[text][ref]` + - `fragmentLinks` + - `enabled` — Enable/disable validation of links to fragments in the current files: `[text](#head)` + - `fileLinks` + - `enabled` — Enable/disable validation of links to file in the workspace. + - `markdownFragmentLinks` — Enable/disable validation of links to headers in other Markdown files. + - `ignoreLinks` — Array of glob patterns for files that should not be validated. + +### Custom requests + +To support all of the features of the language server, the client needs to implement a few custom request types. The definitions of these request types can be found in [`protocol.ts`](./src/protocol.ts) + +#### `markdown/parse` + +Get the tokens for a Markdown file. Clients are expected to use [Markdown-it](https://github.com/markdown-it/markdown-it) for this. + +We require that clients bring their own version of Markdown-it so that they can customize/extend Markdown-it. + +#### `markdown/fs/readFile` + +Read the contents of a file in the workspace. + +#### `markdown/fs/readDirectory` + +Read the contents of a directory in the workspace. + +#### `markdown/fs/stat` + +Check if a given file/directory exists in the workspace. + +#### `markdown/fs/watcher/create` + +Create a file watcher. This is needed for diagnostics support. + +#### `markdown/fs/watcher/delete` + +Delete a previously created file watcher. + +#### `markdown/findMarkdownFilesInWorkspace` + +Get a list of all markdown files in the workspace. + + +## Contribute + +The source code of the Markdown language server can be found in the [VSCode repository](https://github.com/microsoft/vscode) at [extensions/markdown-language-features/server](https://github.com/microsoft/vscode/tree/master/extensions/markdown-language-features/server). + +File issues and pull requests in the [VSCode GitHub Issues](https://github.com/microsoft/vscode/issues). See the document [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) on how to build and run from source. + +Most of the functionality of the server is located in libraries: + +- [vscode-markdown-languageservice](https://github.com/microsoft/vscode-markdown-languageservice) contains the implementation of all features as a reusable library. +- [vscode-languageserver-node](https://github.com/microsoft/vscode-languageserver-node) contains the implementation of language server for NodeJS. + +Help on any of these projects is very welcome. + +## Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +## License + +Copyright (c) Microsoft Corporation. All rights reserved. + +Licensed under the [MIT](https://github.com/microsoft/vscode/blob/master/LICENSE.txt) License. + diff --git a/extensions/markdown-language-features/server/src/protocol.ts b/extensions/markdown-language-features/server/src/protocol.ts index 5bae3701d8a..4b045dce0d3 100644 --- a/extensions/markdown-language-features/server/src/protocol.ts +++ b/extensions/markdown-language-features/server/src/protocol.ts @@ -4,20 +4,24 @@ *--------------------------------------------------------------------------------------------*/ import { RequestType } from 'vscode-languageserver'; -import * as lsp from 'vscode-languageserver-types'; -import * as md from 'vscode-markdown-languageservice'; +import type * as lsp from 'vscode-languageserver-types'; +import type * as md from 'vscode-markdown-languageservice'; -// From server -export const parseRequestType: RequestType<{ uri: string }, md.Token[], any> = new RequestType('markdown/parse'); -export const readFileRequestType: RequestType<{ uri: string }, number[], any> = new RequestType('markdown/readFile'); -export const statFileRequestType: RequestType<{ uri: string }, md.FileStat | undefined, any> = new RequestType('markdown/statFile'); -export const readDirectoryRequestType: RequestType<{ uri: string }, [string, md.FileStat][], any> = new RequestType('markdown/readDirectory'); -export const findFilesRequestTypes: RequestType<{}, string[], any> = new RequestType('markdown/findFiles'); +//#region From server +export const parse = new RequestType<{ uri: string }, md.Token[], any>('markdown/parse'); -export const createFileWatcher: RequestType<{ id: number; uri: string; options: md.FileWatcherOptions }, void, any> = new RequestType('markdown/createFileWatcher'); -export const deleteFileWatcher: RequestType<{ id: number }, void, any> = new RequestType('markdown/deleteFileWatcher'); +export const fs_readFile = new RequestType<{ uri: string }, number[], any>('markdown/fs/readFile'); +export const fs_readDirectory = new RequestType<{ uri: string }, [string, { isDirectory: boolean }][], any>('markdown/fs/readDirectory'); +export const fs_stat = new RequestType<{ uri: string }, { isDirectory: boolean } | undefined, any>('markdown/fs/stat'); -// To server +export const fs_watcher_create = new RequestType<{ id: number; uri: string; options: md.FileWatcherOptions }, void, any>('markdown/fs/watcher/create'); +export const fs_watcher_delete = new RequestType<{ id: number }, void, any>('markdown/fs/watcher/delete'); + +export const findMarkdownFilesInWorkspace = new RequestType<{}, string[], any>('markdown/findMarkdownFilesInWorkspace'); +//#endregion + +//#region To server export const getReferencesToFileInWorkspace = new RequestType<{ uri: string }, lsp.Location[], any>('markdown/getReferencesToFileInWorkspace'); -export const onWatcherChange: RequestType<{ id: number; uri: string; kind: 'create' | 'change' | 'delete' }, void, any> = new RequestType('markdown/onWatcherChange'); +export const fs_watcher_onChange = new RequestType<{ id: number; uri: string; kind: 'create' | 'change' | 'delete' }, void, any>('markdown/fs/watcher/onChange'); +//#endregion diff --git a/extensions/markdown-language-features/server/src/server.ts b/extensions/markdown-language-features/server/src/server.ts index 3b7248830a7..63a3d5d4b99 100644 --- a/extensions/markdown-language-features/server/src/server.ts +++ b/extensions/markdown-language-features/server/src/server.ts @@ -30,7 +30,7 @@ export async function startServer(connection: Connection) { slugifier = md.githubSlugifier; async tokenize(document: md.ITextDocument): Promise { - return await connection.sendRequest(protocol.parseRequestType, { uri: document.uri.toString() }); + return await connection.sendRequest(protocol.parse, { uri: document.uri.toString() }); } }; diff --git a/extensions/markdown-language-features/server/src/workspace.ts b/extensions/markdown-language-features/server/src/workspace.ts index dee32bfe85c..895fb5f1b8c 100644 --- a/extensions/markdown-language-features/server/src/workspace.ts +++ b/extensions/markdown-language-features/server/src/workspace.ts @@ -94,7 +94,7 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching { } }); - connection.onRequest(protocol.onWatcherChange, params => { + connection.onRequest(protocol.fs_watcher_onChange, params => { const watcher = this._watchers.get(params.id); if (!watcher) { return; @@ -130,7 +130,7 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching { const limiter = new Limiter(maxConcurrent); // Add files on disk - const resources = await this.connection.sendRequest(protocol.findFilesRequestTypes, {}); + const resources = await this.connection.sendRequest(protocol.findMarkdownFilesInWorkspace, {}); const onDiskResults = await Promise.all(resources.map(strResource => { return limiter.queue(async () => { const resource = URI.parse(strResource); @@ -170,7 +170,7 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching { } try { - const response = await this.connection.sendRequest(protocol.readFileRequestType, { uri: resource.toString() }); + const response = await this.connection.sendRequest(protocol.fs_readFile, { uri: resource.toString() }); // TODO: LSP doesn't seem to handle Array buffers well const bytes = new Uint8Array(response); @@ -189,12 +189,12 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching { if (this._documentCache.has(resource) || this.documents.get(resource.toString())) { return { isDirectory: false }; } - return this.connection.sendRequest(protocol.statFileRequestType, { uri: resource.toString() }); + return this.connection.sendRequest(protocol.fs_stat, { uri: resource.toString() }); } async readDirectory(resource: URI): Promise<[string, md.FileStat][]> { this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: readDir', `${resource}`); - return this.connection.sendRequest(protocol.readDirectoryRequestType, { uri: resource.toString() }); + return this.connection.sendRequest(protocol.fs_readDirectory, { uri: resource.toString() }); } getContainingDocument(resource: URI): ContainingDocumentContext | undefined { @@ -221,7 +221,7 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching { const id = this._watcherPool++; this._watchers.set(id, entry); - this.connection.sendRequest(protocol.createFileWatcher, { + this.connection.sendRequest(protocol.fs_watcher_create, { id, uri: resource.toString(), options, @@ -232,7 +232,7 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching { onDidChange: entry.onDidChange.event, onDidDelete: entry.onDidDelete.event, dispose: () => { - this.connection.sendRequest(protocol.deleteFileWatcher, { id }); + this.connection.sendRequest(protocol.fs_watcher_delete, { id }); this._watchers.delete(id); } }; diff --git a/extensions/markdown-language-features/src/client.ts b/extensions/markdown-language-features/src/client.ts index a839131f950..553859bb541 100644 --- a/extensions/markdown-language-features/src/client.ts +++ b/extensions/markdown-language-features/src/client.ts @@ -57,7 +57,7 @@ export async function startClient(factory: LanguageClientConstructor, workspace: }); } - client.onRequest(proto.parseRequestType, async (e) => { + client.onRequest(proto.parse, async (e) => { const uri = vscode.Uri.parse(e.uri); const doc = await workspace.getOrLoadMarkdownDocument(uri); if (doc) { @@ -67,12 +67,12 @@ export async function startClient(factory: LanguageClientConstructor, workspace: } }); - client.onRequest(proto.readFileRequestType, async (e): Promise => { + client.onRequest(proto.fs_readFile, async (e): Promise => { const uri = vscode.Uri.parse(e.uri); return Array.from(await vscode.workspace.fs.readFile(uri)); }); - client.onRequest(proto.statFileRequestType, async (e): Promise<{ isDirectory: boolean } | undefined> => { + client.onRequest(proto.fs_stat, async (e): Promise<{ isDirectory: boolean } | undefined> => { const uri = vscode.Uri.parse(e.uri); try { const stat = await vscode.workspace.fs.stat(uri); @@ -82,28 +82,28 @@ export async function startClient(factory: LanguageClientConstructor, workspace: } }); - client.onRequest(proto.readDirectoryRequestType, async (e): Promise<[string, { isDirectory: boolean }][]> => { + client.onRequest(proto.fs_readDirectory, async (e): Promise<[string, { isDirectory: boolean }][]> => { const uri = vscode.Uri.parse(e.uri); const result = await vscode.workspace.fs.readDirectory(uri); return result.map(([name, type]) => [name, { isDirectory: type === vscode.FileType.Directory }]); }); - client.onRequest(proto.findFilesRequestTypes, async (): Promise => { + client.onRequest(proto.findMarkdownFilesInWorkspace, async (): Promise => { return (await vscode.workspace.findFiles(mdFileGlob, '**/node_modules/**')).map(x => x.toString()); }); const watchers = new Map(); - client.onRequest(proto.createFileWatcher, async (params): Promise => { + client.onRequest(proto.fs_watcher_create, async (params): Promise => { const id = params.id; const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(vscode.Uri.parse(params.uri), '*'), params.options.ignoreCreate, params.options.ignoreChange, params.options.ignoreDelete); watchers.set(id, watcher); - watcher.onDidCreate(() => { client.sendRequest(proto.onWatcherChange, { id, uri: params.uri, kind: 'create' }); }); - watcher.onDidChange(() => { client.sendRequest(proto.onWatcherChange, { id, uri: params.uri, kind: 'change' }); }); - watcher.onDidDelete(() => { client.sendRequest(proto.onWatcherChange, { id, uri: params.uri, kind: 'delete' }); }); + watcher.onDidCreate(() => { client.sendRequest(proto.fs_watcher_onChange, { id, uri: params.uri, kind: 'create' }); }); + watcher.onDidChange(() => { client.sendRequest(proto.fs_watcher_onChange, { id, uri: params.uri, kind: 'change' }); }); + watcher.onDidDelete(() => { client.sendRequest(proto.fs_watcher_onChange, { id, uri: params.uri, kind: 'delete' }); }); }); - client.onRequest(proto.deleteFileWatcher, async (params): Promise => { + client.onRequest(proto.fs_watcher_delete, async (params): Promise => { watchers.get(params.id)?.dispose(); watchers.delete(params.id); }); diff --git a/extensions/markdown-language-features/src/protocol.ts b/extensions/markdown-language-features/src/protocol.ts index edcec97381b..53bb27b9822 100644 --- a/extensions/markdown-language-features/src/protocol.ts +++ b/extensions/markdown-language-features/src/protocol.ts @@ -8,17 +8,21 @@ import { RequestType } from 'vscode-languageclient'; import type * as lsp from 'vscode-languageserver-types'; import type * as md from 'vscode-markdown-languageservice'; -// From server -export const parseRequestType: RequestType<{ uri: string }, Token[], any> = new RequestType('markdown/parse'); -export const readFileRequestType: RequestType<{ uri: string }, number[], any> = new RequestType('markdown/readFile'); -export const statFileRequestType: RequestType<{ uri: string }, { isDirectory: boolean } | undefined, any> = new RequestType('markdown/statFile'); -export const readDirectoryRequestType: RequestType<{ uri: string }, [string, { isDirectory: boolean }][], any> = new RequestType('markdown/readDirectory'); -export const findFilesRequestTypes = new RequestType<{}, string[], any>('markdown/findFiles'); +//#region From server +export const parse = new RequestType<{ uri: string }, Token[], any>('markdown/parse'); -export const createFileWatcher: RequestType<{ id: number; uri: string; options: md.FileWatcherOptions }, void, any> = new RequestType('markdown/createFileWatcher'); -export const deleteFileWatcher: RequestType<{ id: number }, void, any> = new RequestType('markdown/deleteFileWatcher'); +export const fs_readFile = new RequestType<{ uri: string }, number[], any>('markdown/fs/readFile'); +export const fs_readDirectory = new RequestType<{ uri: string }, [string, { isDirectory: boolean }][], any>('markdown/fs/readDirectory'); +export const fs_stat = new RequestType<{ uri: string }, { isDirectory: boolean } | undefined, any>('markdown/fs/stat'); -// To server +export const fs_watcher_create = new RequestType<{ id: number; uri: string; options: md.FileWatcherOptions }, void, any>('markdown/fs/watcher/create'); +export const fs_watcher_delete = new RequestType<{ id: number }, void, any>('markdown/fs/watcher/delete'); + +export const findMarkdownFilesInWorkspace = new RequestType<{}, string[], any>('markdown/findMarkdownFilesInWorkspace'); +//#endregion + +//#region To server export const getReferencesToFileInWorkspace = new RequestType<{ uri: string }, lsp.Location[], any>('markdown/getReferencesToFileInWorkspace'); -export const onWatcherChange: RequestType<{ id: number; uri: string; kind: 'create' | 'change' | 'delete' }, void, any> = new RequestType('markdown/onWatcherChange'); +export const fs_watcher_onChange = new RequestType<{ id: number; uri: string; kind: 'create' | 'change' | 'delete' }, void, any>('markdown/fs/watcher/onChange'); +//#endregion From ace330ba2cf74bd3b98013a01a037f48ff611a2b Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 21 Jul 2022 12:25:15 +0200 Subject: [PATCH 123/197] SCM - Fixed a regression due to which post-commit commands were not being executed (#155824) Fixed a regression due to which post-commit commands were not being executed --- extensions/git/src/commands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index c2e7ae8f46f..560734f6316 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1524,7 +1524,7 @@ export class CommandCenter { } if (opts.all === undefined) { - opts = { all: noStagedChanges }; + opts = { ...opts, all: noStagedChanges }; } else if (!opts.all && noStagedChanges && !opts.empty) { opts = { ...opts, all: true }; } From 6cad4b94943fb36cb1d0a7fd7b9c10e65a152f98 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 21 Jul 2022 12:47:23 +0200 Subject: [PATCH 124/197] skip test & :lipstick: (#155826) --- .../vscode-api-tests/src/singlefolder-tests/notebook.test.ts | 2 +- src/vs/platform/windows/electron-main/window.ts | 5 +++-- .../browser/parts/statusbar/media/statusbarpart.css | 4 ++-- src/vs/workbench/browser/parts/statusbar/statusbarPart.ts | 4 +++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts index 1f114771b14..5ffcebf945c 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts @@ -284,7 +284,7 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { assert.ok(cell.metadata.extraCellMetadata, `Test cell metdata not found`); }); - test('edit API batch edits undo/redo', async function () { + test.skip('edit API batch edits undo/redo', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/155825 const notebook = await openRandomNotebookDocument(); const editor = await vscode.window.showNotebookDocument(notebook); diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts index 6c64ff8c565..fce751e1f2d 100644 --- a/src/vs/platform/windows/electron-main/window.ts +++ b/src/vs/platform/windows/electron-main/window.ts @@ -299,8 +299,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { // thus we need to capture them here with a window hook specific to Windows // and then forward them to the correct window. if (isWindows && useCustomTitleStyle) { - // https://docs.microsoft.com/en-us/windows/win32/menurc/wm-initmenu - const WM_INITMENU = 0x0116; + const WM_INITMENU = 0x0116; // https://docs.microsoft.com/en-us/windows/win32/menurc/wm-initmenu + // This sets up a listener for the window hook. This is a Windows-only API provided by electron. this._win.hookWindowMessage(WM_INITMENU, () => { const [x, y] = this._win.getPosition(); @@ -311,6 +311,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { this._win.setEnabled(true); this._onDidTriggerSystemContextMenu.fire({ x: cursorPos.x - x, y: cursorPos.y - y }); + return 0; // skip native menu }); } diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index 8363eed17f6..45ccb1ffda5 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -32,8 +32,8 @@ } .monaco-workbench .part.statusbar > .right-items { - flex-direction: row-reverse; - flex-wrap: wrap /* ensures that the most right elements wrap last when space is little */; + flex-wrap: wrap ; /* overflow elements by wrapping */ + flex-direction: row-reverse; /* let the elements to the left wrap first */ } .monaco-workbench .part.statusbar > .left-items { diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index ecee307637e..607ba09354b 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -531,6 +531,7 @@ export class StatusbarPart extends Part implements IStatusbarService { } this.styleElement.textContent = ` + /* Status bar focus outline */ .monaco-workbench .part.statusbar:focus { outline-color: ${statusBarFocusColor}; @@ -541,10 +542,11 @@ export class StatusbarPart extends Part implements IStatusbarService { outline: 1px solid ${this.getColor(activeContrastBorder) ?? itemBorderColor}; outline-offset: ${borderColor ? '-2px' : '-1px'}; } + /* Notification Beak */ .monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor}; - } + } `; } From cef02dae8d29da2388b0f43221ae79f5a86a3bf3 Mon Sep 17 00:00:00 2001 From: Angelo Date: Thu, 21 Jul 2022 13:19:01 +0200 Subject: [PATCH 125/197] Contribute to html language server with a custom language. (#146731) * Contribute to html language server with a custom language. Fixes #146730 Signed-off-by: azerr * refactor out LanguageParticipants * restart client on language selector change * htmlLanguage -> htmlLanguageParticipants * tune autoInsert wording * tune autoInsert description Co-authored-by: azerr Co-authored-by: Martin Aeschlimann --- extensions/handlebars/package.json | 6 + .../client/src/autoInsertion.ts | 5 +- .../client/src/browser/htmlClientMain.ts | 8 +- .../client/src/customData.ts | 2 +- .../client/src/htmlClient.ts | 107 +++++++++++++----- .../client/src/languageParticipants.ts | 87 ++++++++++++++ .../client/src/node/htmlClientMain.ts | 8 +- .../client/tsconfig.json | 3 +- .../html-language-features/package.json | 3 + .../schemas/package.schema.json | 17 +++ 10 files changed, 205 insertions(+), 41 deletions(-) create mode 100644 extensions/html-language-features/client/src/languageParticipants.ts diff --git a/extensions/handlebars/package.json b/extensions/handlebars/package.json index 0eb6cb2eb2b..88976cf36ff 100644 --- a/extensions/handlebars/package.json +++ b/extensions/handlebars/package.json @@ -36,6 +36,12 @@ "scopeName": "text.html.handlebars", "path": "./syntaxes/Handlebars.tmLanguage.json" } + ], + "htmlLanguageParticipants": [ + { + "languageId": "handlebars", + "autoInsert": true + } ] }, "repository": { diff --git a/extensions/html-language-features/client/src/autoInsertion.ts b/extensions/html-language-features/client/src/autoInsertion.ts index 170afa46c02..e95e6a64a09 100644 --- a/extensions/html-language-features/client/src/autoInsertion.ts +++ b/extensions/html-language-features/client/src/autoInsertion.ts @@ -5,8 +5,9 @@ import { window, workspace, Disposable, TextDocument, Position, SnippetString, TextDocumentChangeEvent, TextDocumentChangeReason, TextDocumentContentChangeEvent } from 'vscode'; import { Runtime } from './htmlClient'; +import { LanguageParticipants } from './languageParticipants'; -export function activateAutoInsertion(provider: (kind: 'autoQuote' | 'autoClose', document: TextDocument, position: Position) => Thenable, supportedLanguages: { [id: string]: boolean }, runtime: Runtime): Disposable { +export function activateAutoInsertion(provider: (kind: 'autoQuote' | 'autoClose', document: TextDocument, position: Position) => Thenable, languageParticipants: LanguageParticipants, runtime: Runtime): Disposable { const disposables: Disposable[] = []; workspace.onDidChangeTextDocument(onDidChangeTextDocument, null, disposables); @@ -33,7 +34,7 @@ export function activateAutoInsertion(provider: (kind: 'autoQuote' | 'autoClose' return; } const document = editor.document; - if (!supportedLanguages[document.languageId]) { + if (!languageParticipants.useAutoInsert(document.languageId)) { return; } const configurations = workspace.getConfiguration(undefined, document.uri); diff --git a/extensions/html-language-features/client/src/browser/htmlClientMain.ts b/extensions/html-language-features/client/src/browser/htmlClientMain.ts index ab23520fe79..fb8cc9071a8 100644 --- a/extensions/html-language-features/client/src/browser/htmlClientMain.ts +++ b/extensions/html-language-features/client/src/browser/htmlClientMain.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, ExtensionContext, Uri } from 'vscode'; -import { BaseLanguageClient, LanguageClientOptions } from 'vscode-languageclient'; -import { startClient, LanguageClientConstructor } from '../htmlClient'; +import { LanguageClientOptions } from 'vscode-languageclient'; +import { startClient, LanguageClientConstructor, AsyncDisposable } from '../htmlClient'; import { LanguageClient } from 'vscode-languageclient/browser'; declare const Worker: { @@ -15,7 +15,7 @@ declare const TextDecoder: { new(encoding?: string): { decode(buffer: ArrayBuffer): string }; }; -let client: BaseLanguageClient | undefined; +let client: AsyncDisposable | undefined; // this method is called when vs code is activated export async function activate(context: ExtensionContext) { @@ -42,7 +42,7 @@ export async function activate(context: ExtensionContext) { export async function deactivate(): Promise { if (client) { - await client.stop(); + await client.dispose(); client = undefined; } } diff --git a/extensions/html-language-features/client/src/customData.ts b/extensions/html-language-features/client/src/customData.ts index 80e5f2f04d9..71d847a4f39 100644 --- a/extensions/html-language-features/client/src/customData.ts +++ b/extensions/html-language-features/client/src/customData.ts @@ -125,7 +125,7 @@ function collectInWorkspaces(workspaceUris: Set): Set { } function collectInExtensions(localExtensionUris: Set, externalUris: Set): void { - for (const extension of extensions.all) { + for (const extension of extensions.allAcrossExtensionHosts) { const customData = extension.packageJSON?.contributes?.html?.customData; if (Array.isArray(customData)) { for (const uriOrPath of customData) { diff --git a/extensions/html-language-features/client/src/htmlClient.ts b/extensions/html-language-features/client/src/htmlClient.ts index 60accb3870b..d5cf527713f 100644 --- a/extensions/html-language-features/client/src/htmlClient.ts +++ b/extensions/html-language-features/client/src/htmlClient.ts @@ -9,7 +9,7 @@ const localize = nls.loadMessageBundle(); import { languages, ExtensionContext, Position, TextDocument, Range, CompletionItem, CompletionItemKind, SnippetString, workspace, extensions, Disposable, FormattingOptions, CancellationToken, ProviderResult, TextEdit, CompletionContext, CompletionList, SemanticTokensLegend, - DocumentSemanticTokensProvider, DocumentRangeSemanticTokensProvider, SemanticTokens, window, commands + DocumentSemanticTokensProvider, DocumentRangeSemanticTokensProvider, SemanticTokens, window, commands, OutputChannel } from 'vscode'; import { LanguageClientOptions, RequestType, DocumentRangeFormattingParams, @@ -18,6 +18,7 @@ import { import { FileSystemProvider, serveFileSystemRequests } from './requests'; import { getCustomDataSource } from './customData'; import { activateAutoInsertion } from './autoInsertion'; +import { getLanguageParticipants, LanguageParticipants } from './languageParticipants'; namespace CustomDataChangedNotification { export const type: NotificationType = new NotificationType('html/customDataChanged'); @@ -74,6 +75,8 @@ export interface TelemetryReporter { export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => BaseLanguageClient; +export const languageServerDescription = localize('htmlserver.name', 'HTML Language Server'); + export interface Runtime { TextDecoder: { new(encoding?: string): { decode(buffer: ArrayBuffer): string } }; fileFs?: FileSystemProvider; @@ -83,11 +86,69 @@ export interface Runtime { }; } -export async function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise { +export interface AsyncDisposable { + dispose(): Promise; +} - const toDispose = context.subscriptions; +export async function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise { - const documentSelector = ['html', 'handlebars']; + const outputChannel = window.createOutputChannel(languageServerDescription); + + const languageParticipants = getLanguageParticipants(); + context.subscriptions.push(languageParticipants); + + let client: Disposable | undefined = await startClientWithParticipants(languageParticipants, newLanguageClient, outputChannel, runtime); + + const promptForLinkedEditingKey = 'html.promptForLinkedEditing'; + if (extensions.getExtension('formulahendry.auto-rename-tag') !== undefined && (context.globalState.get(promptForLinkedEditingKey) !== false)) { + const config = workspace.getConfiguration('editor', { languageId: 'html' }); + if (!config.get('linkedEditing') && !config.get('renameOnType')) { + const activeEditorListener = window.onDidChangeActiveTextEditor(async e => { + if (e && languageParticipants.hasLanguage(e.document.languageId)) { + context.globalState.update(promptForLinkedEditingKey, false); + activeEditorListener.dispose(); + const configure = localize('configureButton', 'Configure'); + const res = await window.showInformationMessage(localize('linkedEditingQuestion', 'VS Code now has built-in support for auto-renaming tags. Do you want to enable it?'), configure); + if (res === configure) { + commands.executeCommand('workbench.action.openSettings', SettingIds.linkedEditing); + } + } + }); + context.subscriptions.push(activeEditorListener); + } + } + + let restartTrigger: Disposable | undefined; + languageParticipants.onDidChange(() => { + if (restartTrigger) { + restartTrigger.dispose(); + } + restartTrigger = runtime.timer.setTimeout(async () => { + if (client) { + outputChannel.appendLine('Extensions have changed, restarting HTML server...'); + outputChannel.appendLine(''); + const oldClient = client; + client = undefined; + await oldClient.dispose(); + client = await startClientWithParticipants(languageParticipants, newLanguageClient, outputChannel, runtime); + } + }, 2000); + }); + + return { + dispose: async () => { + restartTrigger?.dispose(); + await client?.dispose(); + outputChannel.dispose(); + } + }; +} + +async function startClientWithParticipants(languageParticipants: LanguageParticipants, newLanguageClient: LanguageClientConstructor, outputChannel: OutputChannel, runtime: Runtime): Promise { + + const toDispose: Disposable[] = []; + + const documentSelector = languageParticipants.documentSelector; const embeddedLanguages = { css: true, javascript: true }; let rangeFormatting: Disposable | undefined = undefined; @@ -129,22 +190,23 @@ export async function startClient(context: ExtensionContext, newLanguageClient: } } }; + clientOptions.outputChannel = outputChannel; // Create the language client and start the client. - const client = newLanguageClient('html', localize('htmlserver.name', 'HTML Language Server'), clientOptions); + const client = newLanguageClient('html', languageServerDescription, clientOptions); client.registerProposedFeatures(); await client.start(); toDispose.push(serveFileSystemRequests(client, runtime)); - const customDataSource = getCustomDataSource(runtime, context.subscriptions); + const customDataSource = getCustomDataSource(runtime, toDispose); client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); customDataSource.onDidChange(() => { client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); - }); - client.onRequest(CustomDataContent.type, customDataSource.getContent); + }, undefined, toDispose); + toDispose.push(client.onRequest(CustomDataContent.type, customDataSource.getContent)); const insertRequestor = (kind: 'autoQuote' | 'autoClose', document: TextDocument, position: Position): Promise => { @@ -155,7 +217,8 @@ export async function startClient(context: ExtensionContext, newLanguageClient: }; return client.sendRequest(AutoInsertRequest.type, param); }; - const disposable = activateAutoInsertion(insertRequestor, { html: true, handlebars: true }, runtime); + + const disposable = activateAutoInsertion(insertRequestor, languageParticipants, runtime); toDispose.push(disposable); const disposable2 = client.onTelemetry(e => { @@ -193,7 +256,6 @@ export async function startClient(context: ExtensionContext, newLanguageClient: } }); - function updateFormatterRegistration() { const formatEnabled = workspace.getConfiguration().get(SettingIds.formatEnable); if (!formatEnabled && rangeFormatting) { @@ -278,25 +340,12 @@ export async function startClient(context: ExtensionContext, newLanguageClient: } })); - const promptForLinkedEditingKey = 'html.promptForLinkedEditing'; - if (extensions.getExtension('formulahendry.auto-rename-tag') !== undefined && (context.globalState.get(promptForLinkedEditingKey) !== false)) { - const config = workspace.getConfiguration('editor', { languageId: 'html' }); - if (!config.get('linkedEditing') && !config.get('renameOnType')) { - const activeEditorListener = window.onDidChangeActiveTextEditor(async e => { - if (e && documentSelector.indexOf(e.document.languageId) !== -1) { - context.globalState.update(promptForLinkedEditingKey, false); - activeEditorListener.dispose(); - const configure = localize('configureButton', 'Configure'); - const res = await window.showInformationMessage(localize('linkedEditingQuestion', 'VS Code now has built-in support for auto-renaming tags. Do you want to enable it?'), configure); - if (res === configure) { - commands.executeCommand('workbench.action.openSettings', SettingIds.linkedEditing); - } - } - }); - toDispose.push(activeEditorListener); + return { + dispose: async () => { + await client.stop(); + toDispose.forEach(d => d.dispose()); + rangeFormatting?.dispose(); } - } - - return client; + }; } diff --git a/extensions/html-language-features/client/src/languageParticipants.ts b/extensions/html-language-features/client/src/languageParticipants.ts new file mode 100644 index 00000000000..6abe49237c8 --- /dev/null +++ b/extensions/html-language-features/client/src/languageParticipants.ts @@ -0,0 +1,87 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DocumentSelector } from 'vscode-languageclient'; +import { Event, EventEmitter, extensions } from 'vscode'; + +/** + * HTML language participant contribution. + */ +interface LanguageParticipantContribution { + /** + * The id of the language which participates with the HTML language server. + */ + languageId: string; + /** + * true if the language activates the auto insertion and false otherwise. + */ + autoInsert?: boolean; +} + +export interface LanguageParticipants { + readonly onDidChange: Event; + readonly documentSelector: DocumentSelector; + hasLanguage(languageId: string): boolean; + useAutoInsert(languageId: string): boolean; + dispose(): void; +} + +export function getLanguageParticipants(): LanguageParticipants { + const onDidChangeEmmiter = new EventEmitter(); + let languages = new Set(); + let autoInsert = new Set(); + + function update() { + const oldLanguages = languages, oldAutoInsert = autoInsert; + + languages = new Set(); + languages.add('html'); + autoInsert = new Set(); + autoInsert.add('html'); + + for (const extension of extensions.allAcrossExtensionHosts) { + const htmlLanguageParticipants = extension.packageJSON?.contributes?.htmlLanguageParticipants as LanguageParticipantContribution[]; + if (Array.isArray(htmlLanguageParticipants)) { + for (const htmlLanguageParticipant of htmlLanguageParticipants) { + const languageId = htmlLanguageParticipant.languageId; + if (typeof languageId === 'string') { + languages.add(languageId); + if (htmlLanguageParticipant.autoInsert !== false) { + autoInsert.add(languageId); + } + } + } + } + } + return !isEqualSet(languages, oldLanguages) || !isEqualSet(oldLanguages, oldAutoInsert); + } + update(); + + const changeListener = extensions.onDidChange(_ => { + if (update()) { + onDidChangeEmmiter.fire(); + } + }); + + return { + onDidChange: onDidChangeEmmiter.event, + get documentSelector() { return Array.from(languages); }, + hasLanguage(languageId: string) { return languages.has(languageId); }, + useAutoInsert(languageId: string) { return autoInsert.has(languageId); }, + dispose: () => changeListener.dispose() + }; +} + +function isEqualSet(s1: Set, s2: Set) { + if (s1.size !== s2.size) { + return false; + } + for (const e of s1) { + if (!s2.has(e)) { + return false; + } + } + return true; +} diff --git a/extensions/html-language-features/client/src/node/htmlClientMain.ts b/extensions/html-language-features/client/src/node/htmlClientMain.ts index 11ca6f254f9..c3e8c85cf4f 100644 --- a/extensions/html-language-features/client/src/node/htmlClientMain.ts +++ b/extensions/html-language-features/client/src/node/htmlClientMain.ts @@ -5,15 +5,15 @@ import { getNodeFileFS } from './nodeFs'; import { Disposable, ExtensionContext } from 'vscode'; -import { startClient, LanguageClientConstructor } from '../htmlClient'; -import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient, BaseLanguageClient } from 'vscode-languageclient/node'; +import { startClient, LanguageClientConstructor, AsyncDisposable } from '../htmlClient'; +import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient } from 'vscode-languageclient/node'; import { TextDecoder } from 'util'; import * as fs from 'fs'; import TelemetryReporter from '@vscode/extension-telemetry'; let telemetry: TelemetryReporter | undefined; -let client: BaseLanguageClient | undefined; +let client: AsyncDisposable | undefined; // this method is called when vs code is activated export async function activate(context: ExtensionContext) { @@ -50,7 +50,7 @@ export async function activate(context: ExtensionContext) { export async function deactivate(): Promise { if (client) { - await client.stop(); + await client.dispose(); client = undefined; } } diff --git a/extensions/html-language-features/client/tsconfig.json b/extensions/html-language-features/client/tsconfig.json index 573b24b4aa6..8f5cef74fd3 100644 --- a/extensions/html-language-features/client/tsconfig.json +++ b/extensions/html-language-features/client/tsconfig.json @@ -5,6 +5,7 @@ }, "include": [ "src/**/*", - "../../../src/vscode-dts/vscode.d.ts" + "../../../src/vscode-dts/vscode.d.ts", + "../../../src/vscode-dts/vscode.proposed.extensionsAny.d.ts" ] } diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index b3731cd0acd..7e4120eeb05 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -14,6 +14,9 @@ "onLanguage:html", "onLanguage:handlebars" ], + "enabledApiProposals": [ + "extensionsAny" + ], "main": "./client/out/node/htmlClientMain", "browser": "./client/dist/browser/htmlClientMain", "capabilities": { diff --git a/extensions/html-language-features/schemas/package.schema.json b/extensions/html-language-features/schemas/package.schema.json index a4d8715b918..ef717dbd1d1 100644 --- a/extensions/html-language-features/schemas/package.schema.json +++ b/extensions/html-language-features/schemas/package.schema.json @@ -13,6 +13,23 @@ "type": "string", "description": "Relative path to a HTML custom data file" } + }, + "htmlLanguageParticipants": { + "type": "array", + "description": "A list of languages that participate with the HTML language server.", + "items": { + "type": "object", + "properties": { + "languageId": { + "type": "string", + "description": "The id of the language that participates with HTML language server." + }, + "autoInsert": { + "type": "boolean", + "description": "Whether the language participates with HTML auto insertions. If not specified, defaults to true." + } + } + } } } } From bbbae594da8035c790c7f62bb8a80fe588daaecd Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 21 Jul 2022 13:25:23 +0200 Subject: [PATCH 126/197] show "Accept Merge" only for files currently under conflict. (#155822) While the merge editor shows users can handle merge conflicts outside of it, e.g on the console via `git add `. The merge editor should have this graceful and step one is to hide the "Accept Merge" command when the file isn't conflicting anymore * Adds a git-context key that contains all resource-uri-strings under conflict * Enable/placement of the Accept Merge command is driven by that * some merge editor context key sugar --- extensions/git/package.json | 4 ++-- extensions/git/src/repository.ts | 3 +++ .../mergeEditor/browser/view/mergeEditor.ts | 20 ++++++++++++++----- .../contrib/mergeEditor/common/mergeEditor.ts | 8 +++++--- .../userDataSync/browser/userDataSync.ts | 6 +++--- 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 8a0a8cf7ed1..831c0319cd2 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -601,7 +601,7 @@ "command": "git.acceptMerge", "title": "%command.git.acceptMerge%", "category": "Git", - "enablement": "isMergeEditor" + "enablement": "isMergeEditor && mergeEditorResultUri in git.mergeChanges" } ], "keybindings": [ @@ -1549,7 +1549,7 @@ "merge/toolbar": [ { "command": "git.acceptMerge", - "when": "isMergeEditor && baseResourceScheme =~ /^git$|^file$/" + "when": "isMergeEditor && mergeEditorBaseUri =~ /^(git|file):/ && mergeEditorResultUri in git.mergeChanges" } ], "scm/change/title": [ diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 7869e17eae5..2cbddcc0caf 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -2028,6 +2028,9 @@ export class Repository implements Disposable { // set count badge this.setCountBadge(); + // set mergeChanges context + commands.executeCommand('setContext', 'git.mergeChanges', merge.map(item => item.resourceUri.toString())); + this._onDidChangeStatus.fire(); this._sourceControl.commitTemplate = await this.getInputTemplate(); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index 8cb9a104237..84c2b3f80b1 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -11,7 +11,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Color } from 'vs/base/common/color'; import { BugIndicatingError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { autorunWithStore, IObservable } from 'vs/base/common/observable'; import { basename, isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; @@ -44,7 +44,7 @@ import { DocumentMapping, getOppositeDirection, MappingDirection } from 'vs/work import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; import { deepMerge, ReentrancyBarrier, thenIfNotDisposed } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/view/viewModel'; -import { ctxBaseResourceScheme, ctxIsMergeEditor, ctxMergeEditorLayout, MergeEditorLayoutTypes } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; +import { ctxMergeBaseUri, ctxIsMergeEditor, ctxMergeEditorLayout, ctxMergeResultUri, MergeEditorLayoutTypes } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; import { settingsSashBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorInputFactoryFunction, IEditorResolverService, MergeEditorInputFactoryFunction, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; @@ -94,7 +94,8 @@ export class MergeEditor extends AbstractTextEditor { private readonly _layoutMode: MergeEditorLayout; private readonly _ctxIsMergeEditor: IContextKey; private readonly _ctxUsesColumnLayout: IContextKey; - private readonly _ctxBaseResourceScheme: IContextKey; + private readonly _ctxResultUri: IContextKey; + private readonly _ctxBaseUri: IContextKey; private _model: MergeEditorModel | undefined; public get model(): MergeEditorModel | undefined { return this._model; } @@ -121,7 +122,8 @@ export class MergeEditor extends AbstractTextEditor { this._ctxIsMergeEditor = ctxIsMergeEditor.bindTo(_contextKeyService); this._ctxUsesColumnLayout = ctxMergeEditorLayout.bindTo(_contextKeyService); - this._ctxBaseResourceScheme = ctxBaseResourceScheme.bindTo(_contextKeyService); + this._ctxBaseUri = ctxMergeBaseUri.bindTo(_contextKeyService); + this._ctxResultUri = ctxMergeResultUri.bindTo(_contextKeyService); this._layoutMode = instantiation.createInstance(MergeEditorLayout); this._ctxUsesColumnLayout.set(this._layoutMode.value); @@ -205,6 +207,7 @@ export class MergeEditor extends AbstractTextEditor { override dispose(): void { this._sessionDisposables.dispose(); this._ctxIsMergeEditor.reset(); + this._ctxUsesColumnLayout.reset(); super.dispose(); } @@ -305,7 +308,14 @@ export class MergeEditor extends AbstractTextEditor { this.input1View.setModel(viewModel, model.input1, model.input1Title || localize('input1', 'Input 1'), model.input1Detail, model.input1Description); this.input2View.setModel(viewModel, model.input2, model.input2Title || localize('input2', 'Input 2',), model.input2Detail, model.input2Description); this.inputResultView.setModel(viewModel, model.result, localize('result', 'Result',), this._labelService.getUriLabel(model.result.uri, { relative: true }), undefined); - this._ctxBaseResourceScheme.set(model.base.uri.scheme); + + // Set/unset context keys based on input + this._ctxResultUri.set(model.result.uri.toString()); + this._ctxBaseUri.set(model.base.uri.toString()); + this._sessionDisposables.add(toDisposable(() => { + this._ctxBaseUri.reset(); + this._ctxResultUri.reset(); + })); const viewState = this.loadEditorViewState(input, context); if (viewState) { diff --git a/src/vs/workbench/contrib/mergeEditor/common/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/common/mergeEditor.ts index 9ec46266ae2..e5757fcc8fa 100644 --- a/src/vs/workbench/contrib/mergeEditor/common/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/common/mergeEditor.ts @@ -3,10 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export type MergeEditorLayoutTypes = 'mixed' | 'columns'; -export const ctxIsMergeEditor = new RawContextKey('isMergeEditor', false); -export const ctxMergeEditorLayout = new RawContextKey('mergeEditorLayout', 'mixed'); -export const ctxBaseResourceScheme = new RawContextKey('baseResourceScheme', ''); +export const ctxIsMergeEditor = new RawContextKey('isMergeEditor', false, { type: 'boolean', description: localize('is', 'The editor is a merge editor') }); +export const ctxMergeEditorLayout = new RawContextKey('mergeEditorLayout', 'mixed', { type: 'string', description: localize('editorLayout', 'The layout mode of a merge editor') }); +export const ctxMergeBaseUri = new RawContextKey('mergeEditorBaseUri', '', { type: 'string', description: localize('baseUri', 'The uri of the baser of a merge editor') }); +export const ctxMergeResultUri = new RawContextKey('mergeEditorResultUri', '', { type: 'string', description: localize('resultUri', 'The uri of the result of a merge editor') }); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 32242173816..55f61b3c685 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -18,7 +18,7 @@ import { localize } from 'vs/nls'; import { MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ContextKeyEqualsExpr, ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; @@ -59,7 +59,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { ctxIsMergeEditor } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; +import { ctxIsMergeEditor, ctxMergeBaseUri } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; import { EditorResolution } from 'vs/platform/editor/common/editor'; const CONTEXT_CONFLICTS_SOURCES = new RawContextKey('conflictsSources', ''); @@ -1292,7 +1292,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo title: localize('accept merges title', "Accept Merge"), menu: [{ id: MenuId.MergeToolbar, - when: ContextKeyExpr.and(ctxIsMergeEditor, ContextKeyEqualsExpr.create('baseResourceScheme', USER_DATA_SYNC_SCHEME)), + when: ContextKeyExpr.and(ctxIsMergeEditor, ContextKeyExpr.regex(ctxMergeBaseUri.key, new RegExp(`^${USER_DATA_SYNC_SCHEME}:`))), }], }); } From 36a09b3cd2dacf0d17d671a36049ff4dd039c465 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 21 Jul 2022 13:30:17 +0200 Subject: [PATCH 127/197] Engineering - Tactical change so that we do not run the Test stage for CodeOSS CI builds (#155829) --- .../product-build-pr-cache.yml | 59 ++++ build/azure-pipelines/product-build-pr.yml | 325 +++++++++--------- 2 files changed, 228 insertions(+), 156 deletions(-) create mode 100644 build/azure-pipelines/product-build-pr-cache.yml diff --git a/build/azure-pipelines/product-build-pr-cache.yml b/build/azure-pipelines/product-build-pr-cache.yml new file mode 100644 index 00000000000..067afa7492d --- /dev/null +++ b/build/azure-pipelines/product-build-pr-cache.yml @@ -0,0 +1,59 @@ +steps: + - checkout: self + fetchDepth: 1 + retryCountOnTaskFailure: 3 + + - task: NodeTool@0 + inputs: + versionSpec: "16.x" + + - script: | + mkdir -p .build + node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash + displayName: Prepare yarn cache flags + + - task: Cache@2 + inputs: + key: "genericNodeModules | $(Agent.OS) | .build/yarnlockhash" + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache + + - script: | + set -e + tar -xzf .build/node_modules_cache/cache.tgz + condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Extract node_modules cache + + - script: | + set -e + npx https://aka.ms/enablesecurefeed standAlone + timeoutInMinutes: 5 + retryCountOnTaskFailure: 3 + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true')) + displayName: Switch to Terrapin packages + + - script: | + set -e + for i in {1..3}; do # try 3 times, for Terrapin + yarn --frozen-lockfile --check-files && break + if [ $i -eq 3 ]; then + echo "Yarn failed too many times" >&2 + exit 1 + fi + echo "Yarn failed $i, trying again..." + done + env: + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: | + set -e + node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt + mkdir -p .build/node_modules_cache + tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Create node_modules archive diff --git a/build/azure-pipelines/product-build-pr.yml b/build/azure-pipelines/product-build-pr.yml index 62eb8ca55cb..8f06bcfd09c 100644 --- a/build/azure-pipelines/product-build-pr.yml +++ b/build/azure-pipelines/product-build-pr.yml @@ -13,6 +13,8 @@ variables: value: true - name: ENABLE_TERRAPIN value: false + - name: VSCODE_CIBUILD + value: ${{ in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') }} - name: VSCODE_PUBLISH value: false - name: VSCODE_QUALITY @@ -21,162 +23,173 @@ variables: value: false stages: - - stage: Compile - displayName: Compile & Hygiene - jobs: - - job: Compile - displayName: Compile & Hygiene - pool: vscode-1es-vscode-linux-20.04 - variables: - VSCODE_ARCH: x64 - steps: - - template: product-compile.yml - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: + - stage: MaintainNodeModulesCache + displayName: Maintain node_modules cache + jobs: + - job: MaintainNodeModulesCache + displayName: Maintain node_modules cache + pool: vscode-1es-vscode-linux-20.04 + steps: + - template: product-build-pr-cache.yml - - stage: Test - dependsOn: [] - jobs: - - job: Linuxx64UnitTest - displayName: Linux (Unit Tests) - pool: vscode-1es-vscode-linux-20.04 - # container: vscode-bionic-x64 - timeoutInMinutes: 60 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: linux/product-build-linux-client.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: true - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: false - - job: Linuxx64IntegrationTest - displayName: Linux (Integration Tests) - pool: vscode-1es-vscode-linux-20.04 - # container: vscode-bionic-x64 - timeoutInMinutes: 60 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: linux/product-build-linux-client.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: true - VSCODE_RUN_SMOKE_TESTS: false - - job: Linuxx64SmokeTest - displayName: Linux (Smoke Tests) - pool: vscode-1es-vscode-linux-20.04 - # container: vscode-bionic-x64 - timeoutInMinutes: 60 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: linux/product-build-linux-client.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: true + - ${{ if ne(variables['VSCODE_CIBUILD'], true) }}: + - stage: Compile + displayName: Compile & Hygiene + jobs: + - job: Compile + displayName: Compile & Hygiene + pool: vscode-1es-vscode-linux-20.04 + variables: + VSCODE_ARCH: x64 + steps: + - template: product-compile.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - # - job: macOSUnitTest - # displayName: macOS (Unit Tests) - # pool: - # vmImage: macOS-latest - # timeoutInMinutes: 60 - # variables: - # BUILDSECMON_OPT_IN: true - # VSCODE_ARCH: x64 - # steps: - # - template: darwin/product-build-darwin.yml - # parameters: - # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - # VSCODE_RUN_UNIT_TESTS: true - # VSCODE_RUN_INTEGRATION_TESTS: false - # VSCODE_RUN_SMOKE_TESTS: false - # - job: macOSIntegrationTest - # displayName: macOS (Integration Tests) - # pool: - # vmImage: macOS-latest - # timeoutInMinutes: 60 - # variables: - # BUILDSECMON_OPT_IN: true - # VSCODE_ARCH: x64 - # steps: - # - template: darwin/product-build-darwin.yml - # parameters: - # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - # VSCODE_RUN_UNIT_TESTS: false - # VSCODE_RUN_INTEGRATION_TESTS: true - # VSCODE_RUN_SMOKE_TESTS: false - # - job: macOSSmokeTest - # displayName: macOS (Smoke Tests) - # pool: - # vmImage: macOS-latest - # timeoutInMinutes: 60 - # variables: - # BUILDSECMON_OPT_IN: true - # VSCODE_ARCH: x64 - # steps: - # - template: darwin/product-build-darwin.yml - # parameters: - # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - # VSCODE_RUN_UNIT_TESTS: false - # VSCODE_RUN_INTEGRATION_TESTS: false - # VSCODE_RUN_SMOKE_TESTS: true + - stage: Test + dependsOn: [] + jobs: + - job: Linuxx64UnitTest + displayName: Linux (Unit Tests) + pool: vscode-1es-vscode-linux-20.04 + # container: vscode-bionic-x64 + timeoutInMinutes: 60 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + DISPLAY: ":10" + steps: + - template: linux/product-build-linux-client.yml + parameters: + VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: true + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: false + - job: Linuxx64IntegrationTest + displayName: Linux (Integration Tests) + pool: vscode-1es-vscode-linux-20.04 + # container: vscode-bionic-x64 + timeoutInMinutes: 60 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + DISPLAY: ":10" + steps: + - template: linux/product-build-linux-client.yml + parameters: + VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: true + VSCODE_RUN_SMOKE_TESTS: false + - job: Linuxx64SmokeTest + displayName: Linux (Smoke Tests) + pool: vscode-1es-vscode-linux-20.04 + # container: vscode-bionic-x64 + timeoutInMinutes: 60 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + DISPLAY: ":10" + steps: + - template: linux/product-build-linux-client.yml + parameters: + VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: true - # - job: WindowsUnitTests - # displayName: Windows (Unit Tests) - # pool: vscode-1es-vscode-windows-2019 - # timeoutInMinutes: 60 - # variables: - # VSCODE_ARCH: x64 - # steps: - # - template: win32/product-build-win32.yml - # parameters: - # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - # VSCODE_RUN_UNIT_TESTS: true - # VSCODE_RUN_INTEGRATION_TESTS: false - # VSCODE_RUN_SMOKE_TESTS: false - # - job: WindowsIntegrationTests - # displayName: Windows (Integration Tests) - # pool: vscode-1es-vscode-windows-2019 - # timeoutInMinutes: 60 - # variables: - # VSCODE_ARCH: x64 - # steps: - # - template: win32/product-build-win32.yml - # parameters: - # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - # VSCODE_RUN_UNIT_TESTS: false - # VSCODE_RUN_INTEGRATION_TESTS: true - # VSCODE_RUN_SMOKE_TESTS: false - # - job: WindowsSmokeTests - # displayName: Windows (Smoke Tests) - # pool: vscode-1es-vscode-windows-2019 - # timeoutInMinutes: 60 - # variables: - # VSCODE_ARCH: x64 - # steps: - # - template: win32/product-build-win32.yml - # parameters: - # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - # VSCODE_RUN_UNIT_TESTS: false - # VSCODE_RUN_INTEGRATION_TESTS: false - # VSCODE_RUN_SMOKE_TESTS: true + # - job: macOSUnitTest + # displayName: macOS (Unit Tests) + # pool: + # vmImage: macOS-latest + # timeoutInMinutes: 60 + # variables: + # BUILDSECMON_OPT_IN: true + # VSCODE_ARCH: x64 + # steps: + # - template: darwin/product-build-darwin.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: true + # VSCODE_RUN_INTEGRATION_TESTS: false + # VSCODE_RUN_SMOKE_TESTS: false + # - job: macOSIntegrationTest + # displayName: macOS (Integration Tests) + # pool: + # vmImage: macOS-latest + # timeoutInMinutes: 60 + # variables: + # BUILDSECMON_OPT_IN: true + # VSCODE_ARCH: x64 + # steps: + # - template: darwin/product-build-darwin.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: false + # VSCODE_RUN_INTEGRATION_TESTS: true + # VSCODE_RUN_SMOKE_TESTS: false + # - job: macOSSmokeTest + # displayName: macOS (Smoke Tests) + # pool: + # vmImage: macOS-latest + # timeoutInMinutes: 60 + # variables: + # BUILDSECMON_OPT_IN: true + # VSCODE_ARCH: x64 + # steps: + # - template: darwin/product-build-darwin.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: false + # VSCODE_RUN_INTEGRATION_TESTS: false + # VSCODE_RUN_SMOKE_TESTS: true + + # - job: WindowsUnitTests + # displayName: Windows (Unit Tests) + # pool: vscode-1es-vscode-windows-2019 + # timeoutInMinutes: 60 + # variables: + # VSCODE_ARCH: x64 + # steps: + # - template: win32/product-build-win32.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: true + # VSCODE_RUN_INTEGRATION_TESTS: false + # VSCODE_RUN_SMOKE_TESTS: false + # - job: WindowsIntegrationTests + # displayName: Windows (Integration Tests) + # pool: vscode-1es-vscode-windows-2019 + # timeoutInMinutes: 60 + # variables: + # VSCODE_ARCH: x64 + # steps: + # - template: win32/product-build-win32.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: false + # VSCODE_RUN_INTEGRATION_TESTS: true + # VSCODE_RUN_SMOKE_TESTS: false + # - job: WindowsSmokeTests + # displayName: Windows (Smoke Tests) + # pool: vscode-1es-vscode-windows-2019 + # timeoutInMinutes: 60 + # variables: + # VSCODE_ARCH: x64 + # steps: + # - template: win32/product-build-win32.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: false + # VSCODE_RUN_INTEGRATION_TESTS: false + # VSCODE_RUN_SMOKE_TESTS: true From c09b8cf584f176aed22c1f7f30bc04858d86c9c7 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 21 Jul 2022 13:32:12 +0200 Subject: [PATCH 128/197] disable configurable menus again (#155830) this removes how hiding is currently done but also refactorings the internal into a better shape so that this can be picked up again https://github.com/microsoft/vscode/issues/154804 --- .../browser/menuEntryActionViewItem.ts | 23 ----- src/vs/platform/actions/common/actions.ts | 21 +--- src/vs/platform/actions/common/menuService.ts | 97 ++++++------------- 3 files changed, 33 insertions(+), 108 deletions(-) diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 4c98b03d76a..a12393b04cc 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -130,23 +130,6 @@ export interface IMenuEntryActionViewItemOptions { hoverDelegate?: IHoverDelegate; } -function registerConfigureMenu(contextMenuService: IContextMenuService, item: BaseActionViewItem, action: MenuItemAction | SubmenuItemAction): IDisposable { - assertType(item.element); - return addDisposableListener(item.element, 'contextmenu', event => { - if (!action.hideActions) { - return; - } - - event.preventDefault(); - event.stopPropagation(); - - contextMenuService.showContextMenu({ - getAnchor: () => item.element!, - getActions: () => action.hideActions!.asList() - }); - }, true); -} - export class MenuEntryActionViewItem extends ActionViewItem { private _wantsAltCommand: boolean = false; @@ -221,8 +204,6 @@ export class MenuEntryActionViewItem extends ActionViewItem { mouseOver = true; updateAltState(); })); - - this._register(registerConfigureMenu(this._contextMenuService, this, this._menuItemAction)); } override updateLabel(): void { @@ -348,8 +329,6 @@ export class SubmenuEntryActionViewItem extends DropdownMenuActionViewItem { setBackgroundImage(); })); } - - this._register(registerConfigureMenu(this._contextMenuService, this, action)); } } @@ -469,8 +448,6 @@ export class DropdownWithDefaultActionViewItem extends BaseActionViewItem { event.stopPropagation(); } })); - - this._register(registerConfigureMenu(this._contextMenuService, this, (this.action))); } override focus(fromRight?: boolean): void { diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 5a37764affb..62446fbc2ca 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -355,7 +355,6 @@ export class SubmenuItemAction extends SubmenuAction { constructor( readonly item: ISubmenuItem, - readonly hideActions: MenuItemActionManageActions, private readonly _menuService: IMenuService, private readonly _contextKeyService: IContextKeyService, private readonly _options?: IMenuActionOptions @@ -381,20 +380,10 @@ export class SubmenuItemAction extends SubmenuAction { } } -export class MenuItemActionManageActions { - constructor( - readonly hideThis: IAction, - readonly toggleAny: readonly IAction[][], - ) { } - - asList(): IAction[] { - let result: IAction[] = [this.hideThis]; - for (const n of this.toggleAny) { - result.push(new Separator()); - result = result.concat(n); - } - return result; - } +export interface IMenuItemHide { + readonly isHidden: boolean; + readonly hide: IAction; + readonly toggle: IAction; } // implements IAction, does NOT extend Action, so that no one @@ -417,7 +406,7 @@ export class MenuItemAction implements IAction { item: ICommandAction, alt: ICommandAction | undefined, options: IMenuActionOptions | undefined, - readonly hideActions: MenuItemActionManageActions | undefined, + readonly hideActions: IMenuItemHide | undefined, @IContextKeyService contextKeyService: IContextKeyService, @ICommandService private _commandService: ICommandService ) { diff --git a/src/vs/platform/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts index fce79d84ad9..44f48f3c46c 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -6,11 +6,11 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IMenu, IMenuActionOptions, IMenuCreateOptions, IMenuItem, IMenuService, isIMenuItem, isISubmenuItem, ISubmenuItem, MenuId, MenuItemAction, MenuItemActionManageActions, MenuRegistry, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { IMenu, IMenuActionOptions, IMenuCreateOptions, IMenuItem, IMenuItemHide, IMenuService, isIMenuItem, ISubmenuItem, MenuId, MenuItemAction, MenuRegistry, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { ICommandAction, ILocalizedString } from 'vs/platform/action/common/action'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IAction, SubmenuAction } from 'vs/base/common/actions'; +import { IAction, toAction } from 'vs/base/common/actions'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { removeFastWithoutKeepingOrder } from 'vs/base/common/arrays'; import { localize } from 'vs/nls'; @@ -252,33 +252,17 @@ class Menu implements IMenu { if (this._contextKeyService.contextMatchesRules(item.when)) { let action: MenuItemAction | SubmenuItemAction | undefined; const isMenuItem = isIMenuItem(item); - const hideActions = new MenuItemActionManageActions(new HideMenuItemAction(this._id, isMenuItem ? item.command : item, this._hiddenStates), allToggleActions); if (isMenuItem) { - if (!this._hiddenStates.isHidden(this._id, item.command.id)) { - action = new MenuItemAction(item.command, item.alt, options, hideActions, this._contextKeyService, this._commandService); - } - // add toggle commmand - toggleActions.push(new ToggleMenuItemAction(this._id, item.command, this._hiddenStates)); + const menuHide = createMenuHide(this._id, item.command, this._hiddenStates); + action = new MenuItemAction(item.command, item.alt, options, menuHide, this._contextKeyService, this._commandService); + } else { - action = new SubmenuItemAction(item, hideActions, this._menuService, this._contextKeyService, options); + action = new SubmenuItemAction(item, this._menuService, this._contextKeyService, options); if (action.actions.length === 0) { action.dispose(); action = undefined; } - // add toggle submenu - this re-creates ToggleMenuItemAction-instances for submenus but that's OK... - if (action) { - const makeToggleCommand = (id: MenuId, action: IAction): IAction => { - if (action instanceof SubmenuItemAction) { - return new SubmenuAction(action.id, action.label, action.actions.map(a => makeToggleCommand(action.item.submenu, a))); - } else if (action instanceof MenuItemAction) { - return new ToggleMenuItemAction(id, action.item, this._hiddenStates); - } else { - return action; - } - }; - toggleActions.push(makeToggleCommand(this._id, action)); - } } if (action) { @@ -355,55 +339,30 @@ class Menu implements IMenu { } } -class ToggleMenuItemAction implements IAction { +function createMenuHide(menu: MenuId, command: ICommandAction, states: PersistedMenuHideState): IMenuItemHide { - readonly id: string; - readonly label: string; - readonly enabled: boolean = true; - readonly tooltip: string = ''; + const id = `${menu.id}/${command.id}`; + const title = typeof command.title === 'string' ? command.title : command.title.value; - readonly checked: boolean; - readonly class: undefined; + const hide = toAction({ + id, + label: localize('hide.label', 'Hide \'{0}\'', title), + run() { states.updateHidden(menu, command.id, true); } + }); - run: () => void; + const toggle = toAction({ + id, + label: title, + get checked() { return !states.isHidden(menu, command.id); }, + run() { + const newValue = !states.isHidden(menu, command.id); + states.updateHidden(menu, command.id, newValue); + } + }); - constructor(id: MenuId, command: ICommandAction, hiddenStates: PersistedMenuHideState) { - this.id = `toggle/${id.id}/${command.id}`; - this.label = typeof command.title === 'string' ? command.title : command.title.value; - - let isHidden = hiddenStates.isHidden(id, command.id); - this.checked = !isHidden; - this.run = () => { - isHidden = !isHidden; - hiddenStates.updateHidden(id, command.id, isHidden); - }; - } - - dispose(): void { - // NOTHING - } -} - -class HideMenuItemAction implements IAction { - - readonly id: string; - readonly label: string; - readonly enabled: boolean = true; - readonly tooltip: string = ''; - - readonly checked: undefined; - readonly class: undefined; - - run: () => void; - - constructor(menu: MenuId, command: ICommandAction | ISubmenuItem, hiddenStates: PersistedMenuHideState) { - const id = isISubmenuItem(command) ? command.submenu.id : command.id; - this.id = `hide/${menu.id}/${id}`; - this.label = localize('hide.label', 'Hide \'{0}\'', typeof command.title === 'string' ? command.title : command.title.value); - this.run = () => { hiddenStates.updateHidden(menu, id, true); }; - } - - dispose(): void { - // NOTHING - } + return { + hide, + toggle, + get isHidden() { return !toggle.checked; }, + }; } From 43acfddd5f2e9212b730e8a7570e35d70f70e9d0 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 21 Jul 2022 14:41:33 +0200 Subject: [PATCH 129/197] Remove discussion icon from comments view (#155839) --- .../contrib/comments/browser/commentsTreeViewer.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 477ed5f7cee..3d8c19fce39 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -186,13 +186,11 @@ export class CommentNodeRenderer implements IListRenderer return renderedComment; } - private getIcon(commentCount: number, threadState?: CommentThreadState): Codicon { + private getIcon(threadState?: CommentThreadState): Codicon { if (threadState === CommentThreadState.Unresolved) { return Codicon.commentUnresolved; - } else if (commentCount === 1) { - return Codicon.comment; } else { - return Codicon.commentDiscussion; + return Codicon.comment; } } @@ -200,7 +198,7 @@ export class CommentNodeRenderer implements IListRenderer const commentCount = node.element.replies.length + 1; templateData.threadMetadata.icon.classList.remove(...Array.from(templateData.threadMetadata.icon.classList.values()) .filter(value => value.startsWith('codicon'))); - templateData.threadMetadata.icon.classList.add(...ThemeIcon.asClassNameArray(this.getIcon(commentCount, node.element.threadState))); + templateData.threadMetadata.icon.classList.add(...ThemeIcon.asClassNameArray(this.getIcon(node.element.threadState))); if (node.element.threadState !== undefined) { const color = this.getCommentThreadWidgetStateColor(node.element.threadState, this.themeService.getColorTheme()); templateData.threadMetadata.icon.style.setProperty(commentViewThreadStateColorVar, `${color}`); From d7534854eb9290c19114d2f13c0950f42d61247b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 21 Jul 2022 14:54:39 +0200 Subject: [PATCH 130/197] don't use opener service for `OpenResultResource` because that ends up using the code editor service (#155840) --- .../contrib/mergeEditor/browser/commands/commands.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts index ef089d335b2..9e015121818 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts @@ -189,10 +189,9 @@ export class OpenResultResource extends Action2 { } async run(accessor: ServicesAccessor): Promise { - const opener = accessor.get(IOpenerService); - const { activeEditor } = accessor.get(IEditorService); - if (activeEditor instanceof MergeEditorInput) { - await opener.open(activeEditor.result); + const editorService = accessor.get(IEditorService); + if (editorService.activeEditor instanceof MergeEditorInput) { + editorService.openEditor({ resource: editorService.activeEditor.result }); } } } From 293d9f8e21002b61f5ddf208aca6743a859cac60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Thu, 21 Jul 2022 15:51:28 +0200 Subject: [PATCH 131/197] remove unnecessary info message (#155851) fixes #153577 --- src/vs/workbench/services/actions/common/menusExtensionPoint.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 001cf0f3815..6a4a9f761ac 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -768,7 +768,6 @@ menusExtensionPoint.setHandler(extensions => { } if (!menu) { - collector.info(localize('menuId.invalid', "`{0}` is not a valid menu identifier", entry[0])); continue; } From cddfa5d5bc5ef761809771c21d7873eae46a1b6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Thu, 21 Jul 2022 15:55:27 +0200 Subject: [PATCH 132/197] tree filter: increase height in macOS (#155850) fixes #155575 --- src/vs/base/browser/ui/tree/media/tree.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/base/browser/ui/tree/media/tree.css b/src/vs/base/browser/ui/tree/media/tree.css index f44e796a572..f6dceb39e95 100644 --- a/src/vs/base/browser/ui/tree/media/tree.css +++ b/src/vs/base/browser/ui/tree/media/tree.css @@ -97,6 +97,10 @@ flex: 1; } +.monaco-tree-type-filter-input .monaco-inputbox { + height: 26px; +} + .monaco-tree-type-filter-input .monaco-inputbox > .ibwrapper > .input, .monaco-tree-type-filter-input .monaco-inputbox > .ibwrapper > .mirror { padding: 2px; From e77fa1fb8bc6b239479d664d8db613761736311a Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 21 Jul 2022 15:58:50 +0200 Subject: [PATCH 133/197] Problems view - Fix issue with the SCM input label provider when problems are displayed in a tree view (#155853) Fix issue with the SCM input label provider when problems are displayed in a tree view --- src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 1652365c097..778ae2c5497 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -19,7 +19,7 @@ import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { QuickFixAction, QuickFixActionViewItem } from 'vs/workbench/contrib/markers/browser/markersViewActions'; import { ILabelService } from 'vs/platform/label/common/label'; -import { dirname, basename, isEqual } from 'vs/base/common/resources'; +import { basename, isEqual } from 'vs/base/common/resources'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeFilter, TreeVisibility, TreeFilterResult, ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilterOptions'; @@ -185,7 +185,7 @@ export class ResourceMarkersRenderer implements ITreeRenderer Date: Thu, 21 Jul 2022 16:00:10 +0200 Subject: [PATCH 134/197] Add "Developer: Apply Update..." command (#155846) introduce "Developer: Apply Update..." --- src/vs/platform/update/common/update.ts | 1 + src/vs/platform/update/common/updateIpc.ts | 5 +++ .../electron-main/abstractUpdateService.ts | 4 ++ .../electron-main/updateService.snap.ts | 5 +++ .../electron-main/updateService.win32.ts | 22 +++++++++++ .../update/browser/update.contribution.ts | 39 ++++++++++++++++++- .../services/update/browser/updateService.ts | 4 ++ 7 files changed, 79 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/update/common/update.ts b/src/vs/platform/update/common/update.ts index 9d8a09a7e89..7cd4a84084e 100644 --- a/src/vs/platform/update/common/update.ts +++ b/src/vs/platform/update/common/update.ts @@ -92,4 +92,5 @@ export interface IUpdateService { quitAndInstall(): Promise; isLatestVersion(): Promise; + _applySpecificUpdate(packagePath: string): Promise; } diff --git a/src/vs/platform/update/common/updateIpc.ts b/src/vs/platform/update/common/updateIpc.ts index 55369d4a098..20a1041730e 100644 --- a/src/vs/platform/update/common/updateIpc.ts +++ b/src/vs/platform/update/common/updateIpc.ts @@ -27,6 +27,7 @@ export class UpdateChannel implements IServerChannel { case 'quitAndInstall': return this.service.quitAndInstall(); case '_getInitialState': return Promise.resolve(this.service.state); case 'isLatestVersion': return this.service.isLatestVersion(); + case '_applySpecificUpdate': return this.service._applySpecificUpdate(arg); } throw new Error(`Call not found: ${command}`); @@ -71,4 +72,8 @@ export class UpdateChannelClient implements IUpdateService { isLatestVersion(): Promise { return this.channel.call('isLatestVersion'); } + + _applySpecificUpdate(packagePath: string): Promise { + return this.channel.call('_applySpecificUpdate', packagePath); + } } diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index b451afc5eb2..ff13b16482e 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -201,6 +201,10 @@ export abstract class AbstractUpdateService implements IUpdateService { return context.res.statusCode === 204; } + async _applySpecificUpdate(packagePath: string): Promise { + // noop + } + protected getUpdateType(): UpdateType { return UpdateType.Archive; } diff --git a/src/vs/platform/update/electron-main/updateService.snap.ts b/src/vs/platform/update/electron-main/updateService.snap.ts index 9d202c60aa8..cf54be65d45 100644 --- a/src/vs/platform/update/electron-main/updateService.snap.ts +++ b/src/vs/platform/update/electron-main/updateService.snap.ts @@ -129,6 +129,11 @@ abstract class AbstractUpdateService implements IUpdateService { } abstract isLatestVersion(): Promise; + + async _applySpecificUpdate(packagePath: string): Promise { + // noop + } + protected abstract doCheckForUpdates(context: any): void; } diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index fa2490cdfca..2aaf4680bdd 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -242,4 +242,26 @@ export class Win32UpdateService extends AbstractUpdateService { protected override getUpdateType(): UpdateType { return getUpdateType(); } + + override async _applySpecificUpdate(packagePath: string): Promise { + if (this.state.type !== StateType.Idle) { + return; + } + + const fastUpdatesEnabled = this.configurationService.getValue('update.enableWindowsBackgroundUpdates'); + const update: IUpdate = { version: 'unknown', productVersion: 'unknown', supportsFastUpdate: !!fastUpdatesEnabled }; + + this.setState(State.Downloading(update)); + this.availableUpdate = { packagePath }; + + if (fastUpdatesEnabled) { + if (this.productService.target === 'user') { + this.doApplyUpdate(); + } else { + this.setState(State.Downloaded(update)); + } + } else { + this.setState(State.Ready(update)); + } + } } diff --git a/src/vs/workbench/contrib/update/browser/update.contribution.ts b/src/vs/workbench/contrib/update/browser/update.contribution.ts index 1ff72278d33..78296bf7b6e 100644 --- a/src/vs/workbench/contrib/update/browser/update.contribution.ts +++ b/src/vs/workbench/contrib/update/browser/update.contribution.ts @@ -7,13 +7,16 @@ import 'vs/platform/update/common/update.config.contribution'; import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; +import { IWorkbenchActionRegistry, Extensions as ActionExtensions, CATEGORIES } from 'vs/workbench/common/actions'; import { SyncActionDescriptor, MenuRegistry, MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { ShowCurrentReleaseNotesAction, ProductContribution, UpdateContribution, CheckForVSCodeUpdateAction, CONTEXT_UPDATE_STATE, SwitchProductQualityContribution } from 'vs/workbench/contrib/update/browser/update'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import product from 'vs/platform/product/common/product'; import { IUpdateService, StateType } from 'vs/platform/update/common/update'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { isWindows } from 'vs/base/common/platform'; +import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { mnemonicButtonLabel } from 'vs/base/common/labels'; const workbench = Registry.as(WorkbenchExtensions.Workbench); @@ -93,3 +96,37 @@ if (ShowCurrentReleaseNotesAction.AVAILABE) { order: 5 }); } + +if (isWindows) { + class DeveloperApplyUpdateAction extends Action2 { + constructor() { + super({ + id: '_update.applyupdate', + title: { value: localize('applyUpdate', "Apply Update..."), original: 'Apply Update...' }, + category: CATEGORIES.Developer, + f1: true, + precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Idle) + }); + } + + async run(accessor: ServicesAccessor): Promise { + const updateService = accessor.get(IUpdateService); + const fileDialogService = accessor.get(IFileDialogService); + + const updatePath = await fileDialogService.showOpenDialog({ + title: localize('pickUpdate', "Apply Update"), + filters: [{ name: 'Setup', extensions: ['exe'] }], + canSelectFiles: true, + openLabel: mnemonicButtonLabel(localize({ key: 'updateButton', comment: ['&& denotes a mnemonic'] }, "&&Update")) + }); + + if (!updatePath || !updatePath[0]) { + return; + } + + await updateService._applySpecificUpdate(updatePath[0].fsPath); + } + } + + registerAction2(DeveloperApplyUpdateAction); +} diff --git a/src/vs/workbench/services/update/browser/updateService.ts b/src/vs/workbench/services/update/browser/updateService.ts index 37814d5b856..fdfabe6f12b 100644 --- a/src/vs/workbench/services/update/browser/updateService.ts +++ b/src/vs/workbench/services/update/browser/updateService.ts @@ -90,6 +90,10 @@ export class BrowserUpdateService extends Disposable implements IUpdateService { async quitAndInstall(): Promise { this.hostService.reload(); } + + async _applySpecificUpdate(packagePath: string): Promise { + // noop + } } registerSingleton(IUpdateService, BrowserUpdateService); From 1a3d550dfaa969fa01db834fee9bd2016d4f2b6a Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 21 Jul 2022 17:08:10 +0200 Subject: [PATCH 135/197] update @vscode/test-web (#155858) * update @vscode/test-web --- package.json | 2 +- yarn.lock | 103 ++++++++++++++++++++++----------------------------- 2 files changed, 45 insertions(+), 60 deletions(-) diff --git a/package.json b/package.json index 3fa8af2a900..8043773dacf 100644 --- a/package.json +++ b/package.json @@ -124,7 +124,7 @@ "@typescript-eslint/eslint-plugin": "^5.10.0", "@typescript-eslint/parser": "^5.10.0", "@vscode/telemetry-extractor": "^1.9.6", - "@vscode/test-web": "^0.0.22", + "@vscode/test-web": "^0.0.29", "ansi-colors": "^3.2.3", "asar": "^3.0.3", "chromium-pickle-js": "^0.2.0", diff --git a/yarn.lock b/yarn.lock index 145260f72ac..c9e570c8798 100644 --- a/yarn.lock +++ b/yarn.lock @@ -821,6 +821,13 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" +"@koa/cors@^3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@koa/cors/-/cors-3.3.0.tgz#b4c1c7ee303b7c968c8727f2a638a74675b50bb2" + integrity sha512-lzlkqLlL5Ond8jb6JLnVVDmD2OPym0r5kvZlMgAWiS9xle+Q5ulw1T358oW+RVguxUkANquZQz82i/STIRmsqQ== + dependencies: + vary "^1.1.2" + "@koa/router@^10.1.1": version "10.1.1" resolved "https://registry.yarnpkg.com/@koa/router/-/router-10.1.1.tgz#8e5a85c9b243e0bc776802c0de564561e57a5f78" @@ -1580,22 +1587,24 @@ command-line-args "^5.2.1" ts-morph "^13.0.3" -"@vscode/test-web@^0.0.22": - version "0.0.22" - resolved "https://registry.yarnpkg.com/@vscode/test-web/-/test-web-0.0.22.tgz#8767c80e7b16e73e78cf30da93d4dff5d4db148a" - integrity sha512-sm4WYidw26eFb1AReC8w5y4aOMdBb5ma5x3ukRJcun9iUB1ajz2nM18rxiYAVimUzrIMQHr9WqC8HYBYP8aNKQ== +"@vscode/test-web@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@vscode/test-web/-/test-web-0.0.29.tgz#00f19159cf3ae70fdfae4a909df66e407c8b5e56" + integrity sha512-QJwu3F6U+IT/X6UiRVQEe1tKSB1aRVDlWi5jAfnbXaAH8Gk4NrUFLxAB33mms82XQK4PuCTXAqNd/eC8v3ZQDA== dependencies: + "@koa/cors" "^3.3.0" "@koa/router" "^10.1.1" decompress "^4.2.1" decompress-targz "^4.1.1" + get-stream "6.0.1" http-proxy-agent "^5.0.0" - https-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.1" koa "^2.13.4" koa-morgan "^1.0.1" koa-mount "^4.0.0" koa-static "^5.0.0" - minimist "^1.2.5" - playwright "^1.18.1" + minimist "^1.2.6" + playwright "^1.23.1" vscode-uri "^3.0.3" "@vscode/vscode-languagedetection@1.0.21": @@ -3243,7 +3252,7 @@ command-line-args@^5.2.1: lodash.camelcase "^4.3.0" typical "^4.0.0" -commander@*, commander@8.3.0, commander@^8.2.0: +commander@*, commander@8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== @@ -4814,7 +4823,7 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" -extract-zip@2.0.1, extract-zip@^2.0.1: +extract-zip@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== @@ -5356,6 +5365,11 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.1" +get-stream@6.0.1, get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + get-stream@^2.2.0: version "2.3.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" @@ -5378,11 +5392,6 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - 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" @@ -6144,6 +6153,14 @@ https-proxy-agent@^2.2.3: agent-base "^4.3.0" debug "^3.1.0" +https-proxy-agent@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" @@ -6896,7 +6913,7 @@ jest-worker@^27.0.2: merge-stream "^2.0.0" supports-color "^8.0.0" -jpeg-js@0.4.3, jpeg-js@^0.4.2: +jpeg-js@0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b" integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q== @@ -7677,11 +7694,6 @@ mime@^1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^2.4.6: - version "2.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" - integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== - mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" @@ -8855,34 +8867,17 @@ playwright-core@1.21.0: yauzl "2.10.0" yazl "2.5.1" -playwright-core@=1.18.1: - version "1.18.1" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.18.1.tgz#a5cf3f212d10692382e2acd1f7bc8c9ff9bbb849" - integrity sha512-NALGl8R1GHzGLlhUApmpmfh6M1rrrPcDTygWvhTbprxwGB9qd/j9DRwyn4HTQcUB6o0/VOpo46fH9ez3+D/Rog== - dependencies: - commander "^8.2.0" - debug "^4.1.1" - extract-zip "^2.0.1" - https-proxy-agent "^5.0.0" - jpeg-js "^0.4.2" - mime "^2.4.6" - pngjs "^5.0.0" - progress "^2.0.3" - proper-lockfile "^4.1.1" - proxy-from-env "^1.1.0" - rimraf "^3.0.2" - socks-proxy-agent "^6.1.0" - stack-utils "^2.0.3" - ws "^7.4.6" - yauzl "^2.10.0" - yazl "^2.5.1" +playwright-core@1.23.4: + version "1.23.4" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.23.4.tgz#e8a45e549faf6bfad24a0e9998e451979514d41e" + integrity sha512-h5V2yw7d8xIwotjyNrkLF13nV9RiiZLHdXeHo+nVJIYGVlZ8U2qV0pMxNJKNTvfQVT0N8/A4CW6/4EW2cOcTiA== -playwright@^1.18.1: - version "1.18.1" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.18.1.tgz#45c2ca6ee25c44e336985de9b51955727b5f17cf" - integrity sha512-8EaX9EtbtAoMq5tnzIsoA3b/V86V/6Mq2skuOU4qEw+5OVxs1lwesDwmjy/RVU1Qfx5UuwSQzhp45wyH22oa+A== +playwright@^1.23.1: + version "1.23.4" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.23.4.tgz#a9641a8d523fafdc58a5a2b59efe3496dec49c8d" + integrity sha512-NUPOLMpd8WydmwZFllST/YZ7cImgDDDrvcaq7Gj2vAjNg0jYCndFJt6HHtbkOPSIlRo4BaQYlbFx6meq1r1FXQ== dependencies: - playwright-core "=1.18.1" + playwright-core "1.23.4" plist@^3.0.1: version "3.0.5" @@ -8923,11 +8918,6 @@ pngjs@^4.0.1: resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-4.0.1.tgz#f803869bb2fc1bfe1bf99aa4ec21c108117cfdbe" integrity sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg== -pngjs@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" - integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== - posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" @@ -9339,7 +9329,7 @@ promise-inflight@^1.0.1: resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= -proper-lockfile@4.1.2, proper-lockfile@^4.1.1: +proper-lockfile@4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== @@ -10234,7 +10224,7 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" -socks-proxy-agent@6.1.1, socks-proxy-agent@^6.1.0: +socks-proxy-agent@6.1.1: version "6.1.1" resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz#e664e8f1aaf4e1fb3df945f09e3d94f911137f87" integrity sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew== @@ -12034,11 +12024,6 @@ ws@^7.2.0: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== -ws@^7.4.6: - version "7.5.6" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" - integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== - xml2js@^0.4.17: version "0.4.23" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" @@ -12278,7 +12263,7 @@ yauzl@^2.2.1: buffer-crc32 "~0.2.3" fd-slicer "~1.0.1" -yazl@2.5.1, yazl@^2.5.1: +yazl@2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35" integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== From 81fff43fd6f9f5dd9c01508da30b16f35ed61933 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Thu, 21 Jul 2022 08:11:02 -0700 Subject: [PATCH 136/197] bump distro (#155790) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8043773dacf..a98a29120f5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.70.0", - "distro": "1988bf7b08d3ad34aa3ff8f9e51d6e310b001c4f", + "distro": "7ad1a3d2f12ec7ebec23697d390c279f6352d60c", "author": { "name": "Microsoft Corporation" }, From 9c413ba10557db5a1f7b3bdf1d2185a99c2eeb09 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Thu, 21 Jul 2022 09:42:31 -0700 Subject: [PATCH 137/197] Use fixed commit hash, ref #13089 (#155786) --- build/linux/debian/dependencies-generator.js | 2 +- build/linux/debian/dependencies-generator.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/linux/debian/dependencies-generator.js b/build/linux/debian/dependencies-generator.js index 64fd184aae0..235ca268531 100644 --- a/build/linux/debian/dependencies-generator.js +++ b/build/linux/debian/dependencies-generator.js @@ -76,7 +76,7 @@ function calculatePackageDeps(binaryPath, arch, sysroot) { console.error('Tried to stat ' + binaryPath + ' but failed.'); } // Get the Chromium dpkg-shlibdeps file. - const dpkgShlibdepsUrl = 'https://raw.githubusercontent.com/chromium/chromium/main/third_party/dpkg-shlibdeps/dpkg-shlibdeps.pl'; + const dpkgShlibdepsUrl = 'https://raw.githubusercontent.com/chromium/chromium/100.0.4896.160/third_party/dpkg-shlibdeps/dpkg-shlibdeps.pl'; const dpkgShlibdepsScriptLocation = `${(0, os_1.tmpdir)()}/dpkg-shlibdeps.pl`; const result = (0, child_process_1.spawnSync)('curl', [dpkgShlibdepsUrl, '-o', dpkgShlibdepsScriptLocation]); if (result.status !== 0) { diff --git a/build/linux/debian/dependencies-generator.ts b/build/linux/debian/dependencies-generator.ts index 8e1c3fc042f..9e3d466cfe0 100644 --- a/build/linux/debian/dependencies-generator.ts +++ b/build/linux/debian/dependencies-generator.ts @@ -86,7 +86,7 @@ function calculatePackageDeps(binaryPath: string, arch: ArchString, sysroot: str } // Get the Chromium dpkg-shlibdeps file. - const dpkgShlibdepsUrl = 'https://raw.githubusercontent.com/chromium/chromium/main/third_party/dpkg-shlibdeps/dpkg-shlibdeps.pl'; + const dpkgShlibdepsUrl = 'https://raw.githubusercontent.com/chromium/chromium/100.0.4896.160/third_party/dpkg-shlibdeps/dpkg-shlibdeps.pl'; const dpkgShlibdepsScriptLocation = `${tmpdir()}/dpkg-shlibdeps.pl`; const result = spawnSync('curl', [dpkgShlibdepsUrl, '-o', dpkgShlibdepsScriptLocation]); if (result.status !== 0) { From b2daf1af82acb4cd78f86356152ef61aac530f1b Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 21 Jul 2022 10:07:06 -0700 Subject: [PATCH 138/197] Add MD server tracing and update diagnostics on files changes (#155797) --- .../markdown-language-features/package.json | 15 +++++++++++++-- .../markdown-language-features/package.nls.json | 3 ++- .../server/package.json | 2 +- .../server/src/languageFeatures/diagnostics.ts | 17 +++++++++++++---- .../markdown-language-features/server/yarn.lock | 8 ++++---- .../markdown-language-features/src/logging.ts | 2 +- 6 files changed, 34 insertions(+), 13 deletions(-) diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 1d5bef40506..952abb1a463 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -398,16 +398,27 @@ "description": "%configuration.markdown.suggest.paths.enabled.description%", "scope": "resource" }, - "markdown.trace": { + "markdown.trace.extension": { "type": "string", "enum": [ "off", "verbose" ], "default": "off", - "description": "%markdown.trace.desc%", + "description": "%markdown.trace.extension.desc%", "scope": "window" }, + "markdown.trace.server": { + "type": "string", + "scope": "window", + "enum": [ + "off", + "messages", + "verbose" + ], + "default": "off", + "description": "%markdown.trace.server.desc%" + }, "markdown.editor.drop.enabled": { "type": "boolean", "default": true, diff --git a/extensions/markdown-language-features/package.nls.json b/extensions/markdown-language-features/package.nls.json index 7b815072397..062c6ac899f 100644 --- a/extensions/markdown-language-features/package.nls.json +++ b/extensions/markdown-language-features/package.nls.json @@ -17,7 +17,8 @@ "markdown.showSource.title": "Show Source", "markdown.styles.dec": "A list of URLs or local paths to CSS style sheets to use from the Markdown preview. Relative paths are interpreted relative to the folder open in the Explorer. If there is no open folder, they are interpreted relative to the location of the Markdown file. All '\\' need to be written as '\\\\'.", "markdown.showPreviewSecuritySelector.title": "Change Preview Security Settings", - "markdown.trace.desc": "Enable debug logging for the Markdown extension.", + "markdown.trace.extension.desc": "Enable debug logging for the Markdown extension.", + "markdown.trace.server.desc": "Traces the communication between VS Code and the Markdown language server.", "markdown.preview.refresh.title": "Refresh Preview", "markdown.preview.toggleLock.title": "Toggle Preview Locking", "markdown.findAllFileReferences": "Find File References", diff --git a/extensions/markdown-language-features/server/package.json b/extensions/markdown-language-features/server/package.json index 4be608a25a4..c999e13b723 100644 --- a/extensions/markdown-language-features/server/package.json +++ b/extensions/markdown-language-features/server/package.json @@ -13,7 +13,7 @@ "vscode-languageserver": "^8.0.2-next.5`", "vscode-languageserver-textdocument": "^1.0.5", "vscode-languageserver-types": "^3.17.1", - "vscode-markdown-languageservice": "^0.0.0-alpha.10", + "vscode-markdown-languageservice": "^0.0.0-alpha.11", "vscode-uri": "^3.0.3" }, "devDependencies": { diff --git a/extensions/markdown-language-features/server/src/languageFeatures/diagnostics.ts b/extensions/markdown-language-features/server/src/languageFeatures/diagnostics.ts index df3d17a9714..6a51b2e986d 100644 --- a/extensions/markdown-language-features/server/src/languageFeatures/diagnostics.ts +++ b/extensions/markdown-language-features/server/src/languageFeatures/diagnostics.ts @@ -5,6 +5,7 @@ import { Connection, FullDocumentDiagnosticReport, UnchangedDocumentDiagnosticReport } from 'vscode-languageserver'; import * as md from 'vscode-markdown-languageservice'; +import { disposeAll } from 'vscode-markdown-languageservice/out/util/dispose'; import { Disposable } from 'vscode-notebook-renderer/events'; import { URI } from 'vscode-uri'; import { ConfigurationManager, ValidateEnabled } from '../configuration'; @@ -53,7 +54,15 @@ export function registerValidateSupport( diagnosticOptions = getDiagnosticsOptions(config); } + + const subs: Disposable[] = []; const manager = ls.createPullDiagnosticsManager(); + subs.push(manager); + + subs.push(manager.onLinkedToFileChanged(() => { + // TODO: We only need to refresh certain files + connection.languages.diagnostics.refresh(); + })); connection.languages.diagnostics.on(async (params, token): Promise => { if (!config.getSettings()?.markdown.experimental.validate.enabled) { @@ -73,14 +82,14 @@ export function registerValidateSupport( }); updateDiagnosticsSetting(); - const configChangeSub = config.onDidChangeConfiguration(() => { + subs.push(config.onDidChangeConfiguration(() => { updateDiagnosticsSetting(); connection.languages.diagnostics.refresh(); - }); + })); + return { dispose: () => { - manager.dispose(); - configChangeSub.dispose(); + disposeAll(subs); } }; } diff --git a/extensions/markdown-language-features/server/yarn.lock b/extensions/markdown-language-features/server/yarn.lock index feabc56b943..7a44774a574 100644 --- a/extensions/markdown-language-features/server/yarn.lock +++ b/extensions/markdown-language-features/server/yarn.lock @@ -47,10 +47,10 @@ vscode-languageserver@^8.0.2-next.5`: dependencies: vscode-languageserver-protocol "3.17.2-next.6" -vscode-markdown-languageservice@^0.0.0-alpha.10: - version "0.0.0-alpha.10" - resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.0.0-alpha.10.tgz#53b69c981eed7fd5efa155ab8c0f169995568681" - integrity sha512-rJ85nJ+d45yCz9lBhipavoWXz/vW5FknqqUpLqhe3/2xkrhxt8zcekhSoDepgkKFcTORAFV6g1SnnqxbVhX+uA== +vscode-markdown-languageservice@^0.0.0-alpha.11: + version "0.0.0-alpha.11" + resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.0.0-alpha.11.tgz#9dcdfc23f9dcaeaca3b2a2c76fc504619eaeb284" + integrity sha512-syFamf99xx+Q9DkA66+8fbSz2A2LJkyOV+nSziGgAzdDPv4jkb7eWF6l+nUteHTGbRLQ+q0tfkj0A7OovueCSQ== dependencies: picomatch "^2.3.1" vscode-languageserver-textdocument "^1.0.5" diff --git a/extensions/markdown-language-features/src/logging.ts b/extensions/markdown-language-features/src/logging.ts index 48a2f3a7709..5947af62409 100644 --- a/extensions/markdown-language-features/src/logging.ts +++ b/extensions/markdown-language-features/src/logging.ts @@ -70,7 +70,7 @@ export class VsCodeOutputLogger extends Disposable implements ILogger { } private readTrace(): Trace { - return Trace.fromString(vscode.workspace.getConfiguration().get('markdown.trace', 'off')); + return Trace.fromString(vscode.workspace.getConfiguration().get('markdown.trace.extension', 'off')); } private static data2String(data: any): string { From 38d026d11e0f691cda30149154c74e4d3ff4ce6d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 21 Jul 2022 10:56:25 -0700 Subject: [PATCH 139/197] Visualize new line in run recent command Fixes #155871 --- .../contrib/terminal/browser/terminalInstance.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index bbfb4d97207..c6ff0b60c86 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -849,9 +849,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (executingCommand) { commandMap.add(executingCommand); } + function formatLabel(label: string) { + return label.replace(/\r?\n/g, '\u21B5'); + } if (commands && commands.length > 0) { for (const entry of commands) { - // trim off any whitespace and/or line endings + // Trim off any whitespace and/or line endings, replace new lines with the + // Downwards Arrow with Corner Leftwards symbol const label = entry.command.trim(); if (label.length === 0 || commandMap.has(label)) { continue; @@ -881,7 +885,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { continue; } items.push({ - label, + label: formatLabel(label), description, id: entry.timestamp.toString(), command: entry, @@ -893,7 +897,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } if (executingCommand) { items.unshift({ - label: executingCommand, + label: formatLabel(executingCommand), description: cmdDetection.cwd }); } @@ -908,7 +912,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Only add previous session item if it's not in this session if (!commandMap.has(label) && info.shellType === this.shellType) { previousSessionItems.unshift({ - label, + label: formatLabel(label), buttons: [removeFromCommandHistoryButton] }); commandMap.add(label); @@ -927,7 +931,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const dedupedShellFileItems: IQuickPickItem[] = []; for (const label of shellFileHistory) { if (!commandMap.has(label)) { - dedupedShellFileItems.unshift({ label }); + dedupedShellFileItems.unshift({ + label: formatLabel(label) + }); } } if (dedupedShellFileItems.length > 0) { From 295770003c1911a27d8eda87c242448aaaef6e9f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 21 Jul 2022 11:02:43 -0700 Subject: [PATCH 140/197] Send \n char instead of enter symbol --- .../terminal/browser/terminalInstance.ts | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index c6ff0b60c86..d12578dfd6f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -831,8 +831,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return; } let placeholder: string; - type Item = IQuickPickItem & { command?: ITerminalCommand }; - let items: (Item | IQuickPickItem | IQuickPickSeparator)[] = []; + type Item = IQuickPickItem & { command?: ITerminalCommand; rawLabel: string }; + let items: (Item | IQuickPickItem & { rawLabel: string } | IQuickPickSeparator)[] = []; const commandMap: Set = new Set(); const removeFromCommandHistoryButton: IQuickInputButton = { @@ -886,6 +886,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } items.push({ label: formatLabel(label), + rawLabel: label, description, id: entry.timestamp.toString(), command: entry, @@ -898,6 +899,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (executingCommand) { items.unshift({ label: formatLabel(executingCommand), + rawLabel: executingCommand, description: cmdDetection.cwd }); } @@ -907,12 +909,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Gather previous session history const history = this._instantiationService.invokeFunction(getCommandHistory); - const previousSessionItems: IQuickPickItem[] = []; + const previousSessionItems: (IQuickPickItem & { rawLabel: string })[] = []; for (const [label, info] of history.entries) { // Only add previous session item if it's not in this session if (!commandMap.has(label) && info.shellType === this.shellType) { previousSessionItems.unshift({ label: formatLabel(label), + rawLabel: label, buttons: [removeFromCommandHistoryButton] }); commandMap.add(label); @@ -928,11 +931,12 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Gather shell file history const shellFileHistory = await this._instantiationService.invokeFunction(getShellFileHistory, this._shellType); - const dedupedShellFileItems: IQuickPickItem[] = []; + const dedupedShellFileItems: (IQuickPickItem & { rawLabel: string })[] = []; for (const label of shellFileHistory) { if (!commandMap.has(label)) { dedupedShellFileItems.unshift({ - label: formatLabel(label) + label: formatLabel(label), + rawLabel: label }); } } @@ -949,7 +953,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const cwds = this.capabilities.get(TerminalCapability.CwdDetection)?.cwds || []; if (cwds && cwds.length > 0) { for (const label of cwds) { - items.push({ label }); + items.push({ label, rawLabel: label }); } items = items.reverse(); items.unshift({ type: 'separator', label: terminalStrings.currentSessionCategory }); @@ -957,12 +961,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Gather previous session history const history = this._instantiationService.invokeFunction(getDirectoryHistory); - const previousSessionItems: IQuickPickItem[] = []; + const previousSessionItems: (IQuickPickItem & { rawLabel: string })[] = []; // Only add previous session item if it's not in this session and it matches the remote authority for (const [label, info] of history.entries) { if ((info === null || info.remoteAuthority === this.remoteAuthority) && !cwds.includes(label)) { previousSessionItems.unshift({ label, + rawLabel: label, buttons: [removeFromCommandHistoryButton] }); } @@ -978,7 +983,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return; } const outputProvider = this._instantiationService.createInstance(TerminalOutputProvider); - const quickPick = this._quickInputService.createQuickPick(); + const quickPick = this._quickInputService.createQuickPick(); const originalItems = items; quickPick.items = [...originalItems]; quickPick.sortByLabel = false; @@ -1027,7 +1032,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }); quickPick.onDidAccept(() => { const result = quickPick.activeItems[0]; - this.sendText(type === 'cwd' ? `cd ${result.label}` : result.label, !quickPick.keyMods.alt); + this.sendText(type === 'cwd' ? `cd ${result.rawLabel}` : result.rawLabel, !quickPick.keyMods.alt); quickPick.hide(); }); if (value) { From 65f97ed1d96989e64c57a689dae062579c75d5f3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 21 Jul 2022 11:09:23 -0700 Subject: [PATCH 141/197] Use bracketed paste mode when running recent commands Fixes #155872 --- .../contrib/terminal/browser/terminalInstance.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index bbfb4d97207..b846d0a8222 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1021,7 +1021,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }); quickPick.onDidAccept(() => { const result = quickPick.activeItems[0]; - this.sendText(type === 'cwd' ? `cd ${result.label}` : result.label, !quickPick.keyMods.alt); + this.sendText(type === 'cwd' ? `cd ${result.label}` : result.label, !quickPick.keyMods.alt, true); quickPick.hide(); }); if (value) { @@ -1486,7 +1486,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this.xterm.raw.paste(currentText); } - async sendText(text: string, addNewLine: boolean): Promise { + async sendText(text: string, addNewLine: boolean, bracketedPasteMode?: boolean): Promise { + // Apply bracketed paste sequences if the terminal has the mode enabled, this will prevent + // the text from triggering keybindings and ensure new lines are handled properly + if (bracketedPasteMode && this.xterm?.raw.modes.bracketedPasteMode) { + text = `\x1b[200~${text}\x1b[201~`; + } + // Normalize line endings to 'enter' press. text = text.replace(/\r?\n/g, '\r'); if (addNewLine && text[text.length - 1] !== '\r') { From 2cb7539f3a56860ebdf896ec84d94efec21564e4 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 21 Jul 2022 20:27:39 +0200 Subject: [PATCH 142/197] bump distro to solve merge conflict (#155876) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a98a29120f5..2015820bddc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.70.0", - "distro": "7ad1a3d2f12ec7ebec23697d390c279f6352d60c", + "distro": "ea95f2cf38dce77935c7240a566e00b7123d545d", "author": { "name": "Microsoft Corporation" }, From b51955e4c878c8facdd775709740c8aa5d1192d6 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Thu, 21 Jul 2022 11:48:09 -0700 Subject: [PATCH 143/197] pipe workspace boolean for opener service validator (#155545) * pipe workspace boolean for opener service validator fixes #150828 * add fromWorkspace to notebook backlayer webview --- src/vs/editor/browser/services/openerService.ts | 2 +- src/vs/editor/contrib/links/browser/links.ts | 2 +- src/vs/platform/opener/common/opener.ts | 3 ++- src/vs/workbench/api/browser/mainThreadWebviews.ts | 2 +- .../notebook/browser/view/renderers/backLayerWebView.ts | 2 +- .../contrib/url/browser/trustedDomainsValidator.ts | 8 ++++---- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index 56870b5170c..8b751e86828 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -163,7 +163,7 @@ export class OpenerService implements IOpenerService { // validate against the original URI that this URI resolves to, if one exists const validationTarget = this._resolvedUriTargets.get(targetURI) ?? target; for (const validator of this._validators) { - if (!(await validator.shouldOpen(validationTarget))) { + if (!(await validator.shouldOpen(validationTarget, options))) { return false; } } diff --git a/src/vs/editor/contrib/links/browser/links.ts b/src/vs/editor/contrib/links/browser/links.ts index adcb05a6a1f..22b081f3931 100644 --- a/src/vs/editor/contrib/links/browser/links.ts +++ b/src/vs/editor/contrib/links/browser/links.ts @@ -248,7 +248,7 @@ export class LinkDetector extends Disposable implements IEditorContribution { } } - return this.openerService.open(uri, { openToSide, fromUserGesture, allowContributedOpeners: true, allowCommands: true }); + return this.openerService.open(uri, { openToSide, fromUserGesture, allowContributedOpeners: true, allowCommands: true, fromWorkspace: true }); }, err => { const messageOrError = diff --git a/src/vs/platform/opener/common/opener.ts b/src/vs/platform/opener/common/opener.ts index 50c312b8bf3..ce203854888 100644 --- a/src/vs/platform/opener/common/opener.ts +++ b/src/vs/platform/opener/common/opener.ts @@ -41,6 +41,7 @@ export type OpenExternalOptions = { readonly openExternal?: boolean; readonly allowTunneling?: boolean; readonly allowContributedOpeners?: boolean | string; + readonly fromWorkspace?: boolean; }; export type OpenOptions = OpenInternalOptions & OpenExternalOptions; @@ -61,7 +62,7 @@ export interface IExternalOpener { } export interface IValidator { - shouldOpen(resource: URI | string): Promise; + shouldOpen(resource: URI | string, openOptions?: OpenOptions): Promise; } export interface IExternalUriResolver { diff --git a/src/vs/workbench/api/browser/mainThreadWebviews.ts b/src/vs/workbench/api/browser/mainThreadWebviews.ts index ef088e5c054..55a20124434 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviews.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviews.ts @@ -89,7 +89,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma private onDidClickLink(handle: extHostProtocol.WebviewHandle, link: string): void { const webview = this.getWebview(handle); if (this.isSupportedLink(webview, URI.parse(link))) { - this._openerService.open(link, { fromUserGesture: true, allowContributedOpeners: true, allowCommands: true }); + this._openerService.open(link, { fromUserGesture: true, allowContributedOpeners: true, allowCommands: true, fromWorkspace: true }); } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 29537af1c02..faa5dabcc3f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -715,7 +715,7 @@ var requirejs = (function() { } if (linkToOpen) { - this.openerService.open(linkToOpen, { fromUserGesture: true, allowCommands: true }); + this.openerService.open(linkToOpen, { fromUserGesture: true, allowCommands: true, fromWorkspace: true }); } break; } diff --git a/src/vs/workbench/contrib/url/browser/trustedDomainsValidator.ts b/src/vs/workbench/contrib/url/browser/trustedDomainsValidator.ts index dbaf96b6c27..46cea815e4c 100644 --- a/src/vs/workbench/contrib/url/browser/trustedDomainsValidator.ts +++ b/src/vs/workbench/contrib/url/browser/trustedDomainsValidator.ts @@ -8,7 +8,7 @@ import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener'; +import { IOpenerService, matchesScheme, OpenOptions } from 'vs/platform/opener/common/opener'; import { IProductService } from 'vs/platform/product/common/productService'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -45,7 +45,7 @@ export class OpenerValidatorContributions implements IWorkbenchContribution { @IConfigurationService private readonly _configurationService: IConfigurationService, @IWorkspaceTrustManagementService private readonly _workspaceTrustService: IWorkspaceTrustManagementService, ) { - this._openerService.registerValidator({ shouldOpen: r => this.validateLink(r) }); + this._openerService.registerValidator({ shouldOpen: (uri, options) => this.validateLink(uri, options) }); this._readAuthenticationTrustedDomainsResult = new IdleValue(() => this._instantiationService.invokeFunction(readAuthenticationTrustedDomains)); @@ -64,12 +64,12 @@ export class OpenerValidatorContributions implements IWorkbenchContribution { }); } - async validateLink(resource: URI | string): Promise { + async validateLink(resource: URI | string, openOptions?: OpenOptions): Promise { if (!matchesScheme(resource, Schemas.http) && !matchesScheme(resource, Schemas.https)) { return true; } - if (this._workspaceTrustService.isWorkspaceTrusted() && !this._configurationService.getValue('workbench.trustedDomains.promptInTrustedWorkspace')) { + if (openOptions?.fromWorkspace && this._workspaceTrustService.isWorkspaceTrusted() && !this._configurationService.getValue('workbench.trustedDomains.promptInTrustedWorkspace')) { return true; } From 78da5e3df40a794b3b546dc34404edcfcf543a26 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Thu, 21 Jul 2022 15:13:48 -0400 Subject: [PATCH 144/197] Closes #54285 adds webview/context contribution (#154524) * Closes #54285 adds webview/context contribution * Adds additional context support for context menus Adds `webViewItem` read from nearest `data-vscode-context-menu-item` Adds `webViewItemElement` read from `data-vscode-context-menu-item-element` * Adds `preventDefaultContextMenuItems` option This allows webviews to opt-out of the default (cut/copy/paste) context menu items * Adds optional `itemValue` context value for handlers * Adds missing proposed check * Adds webview command to generate csp hash * Switches to use single JSON data attr for context Aggregates all ancestor context * Revert "Adds `preventDefaultContextMenuItems` option" This reverts commit 71f0180504c1e2ce399bb0af908418bc32965838. Also renames proposal from webviewContextMenus to contribWebviewContext to align with other empty contribution point proposals --- package.json | 3 ++- .../api/browser/mainThreadWebviewPanels.ts | 1 + .../customEditor/browser/customEditorInput.ts | 1 + .../browser/customEditorInputFactory.ts | 6 +++-- .../contrib/webview/browser/overlayWebview.ts | 3 +++ .../webview/browser/pre/index-no-csp.html | 24 +++++++++++++++++ .../contrib/webview/browser/pre/index.html | 26 ++++++++++++++++++- .../webview/browser/webview.contribution.ts | 9 +++++++ .../contrib/webview/browser/webviewElement.ts | 24 ++++++++++++++--- .../webviewView/browser/webviewViewPane.ts | 1 + .../actions/common/menusExtensionPoint.ts | 9 ++++++- .../common/extensionsApiProposals.ts | 1 + ...vscode.proposed.contribWebviewContext.d.ts | 6 +++++ 13 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.contribWebviewContext.d.ts diff --git a/package.json b/package.json index 2015820bddc..19b7b991454 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,8 @@ "minify-vscode-reh-web": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js minify-vscode-reh-web", "hygiene": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js hygiene", "core-ci": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js core-ci", - "extensions-ci": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js extensions-ci" + "extensions-ci": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js extensions-ci", + "webview-generate-csp-hash": "npx github:apaatsio/csp-hash-from-html csp-hash ./src/vs/workbench/contrib/webview/browser/pre/index.html" }, "dependencies": { "@microsoft/1ds-core-js": "^3.2.2", diff --git a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts index 5737029a6a4..c8dd0761ef1 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts @@ -167,6 +167,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc const webview = this._webviewWorkbenchService.createWebview({ id: handle, + providedId: viewType, options: reviveWebviewOptions(initData.panelOptions), contentOptions: reviveWebviewContentOptions(initData.webviewOptions), extension diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index af5bc6b10aa..116c74df092 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -40,6 +40,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { const id = generateUuid(); const webview = accessor.get(IWebviewService).createWebviewOverlay({ id, + providedId: viewType, options: { customClasses: options?.customClasses }, contentOptions: {}, extension: undefined, diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index a1670e79b80..9bc10254662 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -108,14 +108,15 @@ export class CustomEditorInputSerializer extends WebviewEditorInputSerializer { } } -function reviveWebview(webviewService: IWebviewService, data: { id: string; origin: string | undefined; state: any; webviewOptions: WebviewOptions; contentOptions: WebviewContentOptions; extension?: WebviewExtensionDescription }) { +function reviveWebview(webviewService: IWebviewService, data: { id: string; origin: string | undefined; viewType: string; state: any; webviewOptions: WebviewOptions; contentOptions: WebviewContentOptions; extension?: WebviewExtensionDescription }) { const webview = webviewService.createWebviewOverlay({ id: data.id, + providedId: data.viewType, origin: data.origin, options: { purpose: WebviewContentPurpose.CustomEditor, enableFindWidget: data.webviewOptions.enableFindWidget, - retainContextWhenHidden: data.webviewOptions.retainContextWhenHidden + retainContextWhenHidden: data.webviewOptions.retainContextWhenHidden, }, contentOptions: data.contentOptions, extension: data.extension, @@ -187,6 +188,7 @@ export class ComplexCustomWorkingCopyEditorHandler extends Disposable implements const extension = reviveWebviewExtensionDescription(backupData.extension?.id, backupData.extension?.location); const webview = reviveWebview(this._webviewService, { id, + viewType: backupData.viewType, origin: backupData.webview.origin, webviewOptions: restoreWebviewOptions(backupData.webview.options), contentOptions: restoreWebviewContentOptions(backupData.webview.options), diff --git a/src/vs/workbench/contrib/webview/browser/overlayWebview.ts b/src/vs/workbench/contrib/webview/browser/overlayWebview.ts index d9cb718cefc..81499a931b6 100644 --- a/src/vs/workbench/contrib/webview/browser/overlayWebview.ts +++ b/src/vs/workbench/contrib/webview/browser/overlayWebview.ts @@ -44,6 +44,7 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { private _findWidgetEnabled: IContextKey | undefined; public readonly id: string; + public readonly providedId?: string; public readonly origin: string; public constructor( @@ -55,6 +56,7 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { super(); this.id = initInfo.id; + this.providedId = initInfo.providedId; this.origin = initInfo.origin ?? generateUuid(); this._extension = initInfo.extension; @@ -192,6 +194,7 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { if (!this._webview.value) { const webview = this._webviewService.createWebviewElement({ id: this.id, + providedId: this.providedId, origin: this.origin, options: this._options, contentOptions: this._contentOptions, diff --git a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html index 6469cf31e74..8bba582ed17 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html @@ -1053,9 +1053,33 @@ } e.preventDefault(); + + let context = {}; + + /** @type HTMLElement */ + let el = e.target; + while (true) { + if (!el) { + break; + } + + // Search self/ancestors for the closest context data attribute + el = el.closest('[data-vscode-context]'); + if (!el) { + break; + } + + try { + context = { ...JSON.parse(el.getAttribute('data-vscode-context')), ...context }; + } catch {} + + el = el.parentElement; + } + hostMessaging.postMessage('did-context-menu', { clientX: e.clientX, clientY: e.clientY, + context: context }); }); diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html index 965b90ace22..52276ac415c 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index.html @@ -5,7 +5,7 @@ + content="default-src 'none'; script-src 'sha256-31a+qtoOhPC136ibtip6gpqSiW72yo48kmtxyi0Qptc=' 'self'; frame-src 'self'; style-src 'unsafe-inline';"> webview.copy()); overrideCommandForWebview(PasteAction, webview => webview.paste()); overrideCommandForWebview(CutAction, webview => webview.cut()); +export const PreventDefaultContextMenuItemsContextKeyName = 'preventDefaultContextMenuItems'; + if (CutAction) { MenuRegistry.appendMenuItem(MenuId.WebviewContext, { command: { id: CutAction.id, title: nls.localize('cut', "Cut"), }, + group: '5_cutcopypaste', order: 1, + when: ContextKeyExpr.not(PreventDefaultContextMenuItemsContextKeyName), }); } @@ -60,7 +65,9 @@ if (CopyAction) { id: CopyAction.id, title: nls.localize('copy', "Copy"), }, + group: '5_cutcopypaste', order: 2, + when: ContextKeyExpr.not(PreventDefaultContextMenuItemsContextKeyName), }); } @@ -70,6 +77,8 @@ if (PasteAction) { id: PasteAction.id, title: nls.localize('paste', "Paste"), }, + group: '5_cutcopypaste', order: 3, + when: ContextKeyExpr.not(PreventDefaultContextMenuItemsContextKeyName), }); } diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 42e7569dd34..465ab3b4681 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -102,6 +102,7 @@ namespace WebviewState { export interface WebviewInitInfo { readonly id: string; + readonly providedId?: string; readonly origin?: string; readonly options: WebviewOptions; @@ -110,6 +111,10 @@ export interface WebviewInitInfo { readonly extension: WebviewExtensionDescription | undefined; } +interface WebviewActionContext { + webview?: string; + [key: string]: unknown; +} export class WebviewElement extends Disposable implements IWebview, WebviewFindDelegate { @@ -118,6 +123,12 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD */ public readonly id: string; + + /** + * The provided identifier of this webview. + */ + public readonly providedId?: string; + /** * The origin this webview itself is loaded from. May not be unique */ @@ -199,6 +210,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD super(); this.id = initInfo.id; + this.providedId = initInfo.providedId; this.iframeId = generateUuid(); this.origin = initInfo.origin ?? this.iframeId; @@ -319,7 +331,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD this.handleKeyEvent('keyup', data); })); - this._register(this.on(WebviewMessageChannels.didContextMenu, (data: { clientX: number; clientY: number }) => { + this._register(this.on(WebviewMessageChannels.didContextMenu, (data: { clientX: number; clientY: number; context: { [key: string]: unknown } }) => { if (!this.element) { return; } @@ -329,12 +341,18 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD const elementBox = this.element.getBoundingClientRect(); contextMenuService.showContextMenu({ getActions: () => { + const contextKeyService = this._contextKeyService!.createOverlay([ + ...Object.entries(data.context), + ['webview', this.providedId], + ]); + const result: IAction[] = []; - const menu = menuService.createMenu(MenuId.WebviewContext, this._contextKeyService!); - createAndFillInContextMenuActions(menu, undefined, result); + const menu = menuService.createMenu(MenuId.WebviewContext, contextKeyService); + createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result); menu.dispose(); return result; }, + getActionsContext: (): WebviewActionContext => ({ ...data.context, webview: this.providedId }), getAnchor: () => ({ x: elementBox.x + data.clientX, y: elementBox.y + data.clientY diff --git a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts index 37b194916ec..9a149dca7ee 100644 --- a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts +++ b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts @@ -169,6 +169,7 @@ export class WebviewViewPane extends ViewPane { const webviewId = generateUuid(); const webview = this.webviewService.createWebviewOverlay({ id: webviewId, + providedId: this.id, options: { purpose: WebviewContentPurpose.WebviewView }, contentOptions: {}, extension: this.extensionId ? { id: this.extensionId } : undefined diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 6a4a9f761ac..b9be48d2fc1 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -272,7 +272,14 @@ const apiMenus: IAPIMenu[] = [ id: MenuId.MergeToolbar, description: localize('merge.toolbar', "The prominent botton in the merge editor"), proposed: 'contribMergeEditorToolbar' - } + }, + { + key: 'webview/context', + id: MenuId.WebviewContext, + description: localize('webview.context', "The webview context menu"), + proposed: 'contribWebviewContext' + }, + ]; namespace schema { diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index ca5260d65af..8301ad0885f 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -17,6 +17,7 @@ export const allApiProposals = Object.freeze({ contribShareMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribShareMenu.d.ts', contribViewsRemote: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsRemote.d.ts', contribViewsWelcome: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsWelcome.d.ts', + contribWebviewContext: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribWebviewContext.d.ts', customEditorMove: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.customEditorMove.d.ts', diffCommand: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.diffCommand.d.ts', documentFiltersExclusive: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.documentFiltersExclusive.d.ts', diff --git a/src/vscode-dts/vscode.proposed.contribWebviewContext.d.ts b/src/vscode-dts/vscode.proposed.contribWebviewContext.d.ts new file mode 100644 index 00000000000..52037dc65b0 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribWebviewContext.d.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `webview/context`-menu contribution point From 7fdb9cfb07ec2625d43af679d155cda4c3bb3e0b Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Thu, 21 Jul 2022 12:46:43 -0700 Subject: [PATCH 145/197] fix WCO color updates with high contrast themes (#155886) fixes #150098 --- src/vs/platform/native/electron-main/nativeHostMainService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index 5fd1d4d738c..a4b9b303b87 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -219,8 +219,8 @@ export class NativeHostMainService extends Disposable implements INativeHostMain const window = this.windowById(windowId); if (window?.win) { window.win.setTitleBarOverlay({ - color: options.backgroundColor, - symbolColor: options.foregroundColor, + color: options.backgroundColor?.trim() === '' ? undefined : options.backgroundColor, + symbolColor: options.foregroundColor?.trim() === '' ? undefined : options.foregroundColor, height: options.height ? options.height - 1 : undefined // account for window border }); } From ffd74383b63d0e0af5ee5c3389f438cf60be07f8 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Thu, 21 Jul 2022 13:12:21 -0700 Subject: [PATCH 146/197] Debug sessions command (#153727) Fixes #153347 --- .../debug/browser/debug.contribution.ts | 4 +- .../contrib/debug/browser/debugCommands.ts | 10 ++ .../debug/browser/debugSessionPicker.ts | 116 ++++++++++++++++++ .../debug/common/loadedScriptsPicker.ts | 16 ++- 4 files changed, 135 insertions(+), 11 deletions(-) create mode 100644 src/vs/workbench/contrib/debug/browser/debugSessionPicker.ts diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 54d0f0a0e7c..ebedc061c54 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -20,7 +20,7 @@ import { } from 'vs/workbench/contrib/debug/common/debug'; import { DebugToolBar } from 'vs/workbench/contrib/debug/browser/debugToolBar'; import { DebugService } from 'vs/workbench/contrib/debug/browser/debugService'; -import { ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_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, DEBUG_START_LABEL, DEBUG_START_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_RUN_COMMAND_ID, EDIT_EXPRESSION_COMMAND_ID, REMOVE_EXPRESSION_COMMAND_ID, SELECT_AND_START_ID, SELECT_AND_START_LABEL, SET_EXPRESSION_COMMAND_ID, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL, PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL, OPEN_LOADED_SCRIPTS_LABEL, SHOW_LOADED_SCRIPTS_ID, DEBUG_QUICK_ACCESS_PREFIX, DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL, STEP_INTO_TARGET_LABEL, STEP_INTO_TARGET_ID, CALLSTACK_TOP_ID, CALLSTACK_TOP_LABEL, CALLSTACK_BOTTOM_LABEL, CALLSTACK_UP_LABEL, CALLSTACK_BOTTOM_ID, CALLSTACK_UP_ID, CALLSTACK_DOWN_ID, CALLSTACK_DOWN_LABEL, DEBUG_COMMAND_CATEGORY } from 'vs/workbench/contrib/debug/browser/debugCommands'; +import { ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_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, DEBUG_START_LABEL, DEBUG_START_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_RUN_COMMAND_ID, EDIT_EXPRESSION_COMMAND_ID, REMOVE_EXPRESSION_COMMAND_ID, SELECT_AND_START_ID, SELECT_AND_START_LABEL, SET_EXPRESSION_COMMAND_ID, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL, PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL, OPEN_LOADED_SCRIPTS_LABEL, SHOW_LOADED_SCRIPTS_ID, DEBUG_QUICK_ACCESS_PREFIX, DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL, STEP_INTO_TARGET_LABEL, STEP_INTO_TARGET_ID, CALLSTACK_TOP_ID, CALLSTACK_TOP_LABEL, CALLSTACK_BOTTOM_LABEL, CALLSTACK_UP_LABEL, CALLSTACK_BOTTOM_ID, CALLSTACK_UP_ID, CALLSTACK_DOWN_ID, CALLSTACK_DOWN_LABEL, DEBUG_COMMAND_CATEGORY, SELECT_DEBUG_SESSION_ID, SELECT_DEBUG_SESSION_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { StatusBarColorProvider } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider'; import { IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views'; import { isMacintosh, isWeb } from 'vs/base/common/platform'; @@ -93,7 +93,6 @@ Registry.as(QuickAccessExtensions.Quickaccess).registerQui helpEntries: [{ description: nls.localize('tasksQuickAccessHelp', "Show All Debug Consoles"), commandId: SELECT_DEBUG_CONSOLE_ID }] }); - registerEditorContribution('editor.contrib.callStack', CallStackEditorContribution); registerEditorContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID, BreakpointEditorContribution); registerEditorContribution(EDITOR_CONTRIBUTION_ID, DebugEditorContribution); @@ -136,6 +135,7 @@ registerDebugCommandPaletteItem(NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL) registerDebugCommandPaletteItem(PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL); registerDebugCommandPaletteItem(SHOW_LOADED_SCRIPTS_ID, OPEN_LOADED_SCRIPTS_LABEL, CONTEXT_IN_DEBUG_MODE); registerDebugCommandPaletteItem(SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL); +registerDebugCommandPaletteItem(SELECT_DEBUG_SESSION_ID, SELECT_DEBUG_SESSION_LABEL); registerDebugCommandPaletteItem(CALLSTACK_TOP_ID, CALLSTACK_TOP_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); registerDebugCommandPaletteItem(CALLSTACK_BOTTOM_ID, CALLSTACK_BOTTOM_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); registerDebugCommandPaletteItem(CALLSTACK_UP_ID, CALLSTACK_UP_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 92b3b970f07..425e5c70f68 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -32,6 +32,7 @@ import { isWeb, isWindows } from 'vs/base/common/platform'; import { saveAllBeforeDebugStart } from 'vs/workbench/contrib/debug/common/debugUtils'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { showLoadedScriptMenu } from 'vs/workbench/contrib/debug/common/loadedScriptsPicker'; +import { showDebugSessionMenu } from 'vs/workbench/contrib/debug/browser/debugSessionPicker'; export const ADD_CONFIGURATION_ID = 'debug.addConfiguration'; export const TOGGLE_INLINE_BREAKPOINT_ID = 'editor.debug.action.toggleInlineBreakpoint'; @@ -55,6 +56,7 @@ export const JUMP_TO_CURSOR_ID = 'debug.jumpToCursor'; export const FOCUS_SESSION_ID = 'workbench.action.debug.focusProcess'; export const SELECT_AND_START_ID = 'workbench.action.debug.selectandstart'; export const SELECT_DEBUG_CONSOLE_ID = 'workbench.action.debug.selectDebugConsole'; +export const SELECT_DEBUG_SESSION_ID = 'workbench.action.debug.selectDebugSession'; export const DEBUG_CONFIGURE_COMMAND_ID = 'workbench.action.debug.configure'; export const DEBUG_START_COMMAND_ID = 'workbench.action.debug.start'; export const DEBUG_RUN_COMMAND_ID = 'workbench.action.debug.run'; @@ -94,6 +96,7 @@ export const CALLSTACK_UP_LABEL = { value: nls.localize('callStackUp', "Navigate export const CALLSTACK_DOWN_LABEL = { value: nls.localize('callStackDown', "Navigate Down Call Stack"), original: 'Navigate Down Call Stack' }; export const SELECT_DEBUG_CONSOLE_LABEL = { value: nls.localize('selectDebugConsole', "Select Debug Console"), original: 'Select Debug Console' }; +export const SELECT_DEBUG_SESSION_LABEL = { value: nls.localize('selectDebugSession', "Select Debug Session"), original: 'Select Debug Session' }; export const DEBUG_QUICK_ACCESS_PREFIX = 'debug '; export const DEBUG_CONSOLE_QUICK_ACCESS_PREFIX = 'debug consoles '; @@ -710,6 +713,13 @@ CommandsRegistry.registerCommand({ } }); +CommandsRegistry.registerCommand({ + id: SELECT_DEBUG_SESSION_ID, + handler: async (accessor: ServicesAccessor) => { + showDebugSessionMenu(accessor, SELECT_AND_START_ID); + } +}); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: DEBUG_START_COMMAND_ID, weight: KeybindingWeight.WorkbenchContrib, diff --git a/src/vs/workbench/contrib/debug/browser/debugSessionPicker.ts b/src/vs/workbench/contrib/debug/browser/debugSessionPicker.ts new file mode 100644 index 00000000000..98dfb2e82f5 --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/debugSessionPicker.ts @@ -0,0 +1,116 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; +import { matchesFuzzy } from 'vs/base/common/filters'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IDebugService, IDebugSession, REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; + +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IPickerDebugItem } from 'vs/workbench/contrib/debug/common/loadedScriptsPicker'; +import { IViewsService } from 'vs/workbench/common/views'; +import { ICommandService } from 'vs/platform/commands/common/commands'; + + +export async function showDebugSessionMenu(accessor: ServicesAccessor, selectAndStartID: string) { + const quickInputService = accessor.get(IQuickInputService); + const debugService = accessor.get(IDebugService); + const viewsService = accessor.get(IViewsService); + const commandService = accessor.get(ICommandService); + + const localDisposableStore = new DisposableStore(); + const quickPick = quickInputService.createQuickPick(); + localDisposableStore.add(quickPick); + quickPick.matchOnLabel = quickPick.matchOnDescription = quickPick.matchOnDetail = quickPick.sortByLabel = false; + quickPick.placeholder = nls.localize('moveFocusedView.selectView', 'Search for debug session by name'); + quickPick.items = _getPicks(quickPick.value, selectAndStartID, debugService, viewsService, commandService); + + localDisposableStore.add(quickPick.onDidChangeValue(async () => { + quickPick.items = _getPicks(quickPick.value, selectAndStartID, debugService, viewsService, commandService); + })); + localDisposableStore.add(quickPick.onDidAccept(() => { + const selectedItem = quickPick.selectedItems[0]; + selectedItem.accept(); + quickPick.hide(); + localDisposableStore.dispose(); + })); + quickPick.show(); +} + +function _getPicks(filter: string, selectAndStartID: string, debugService: IDebugService, viewsService: IViewsService, commandService: ICommandService): Array { + const debugConsolePicks: Array = []; + const headerSessions: IDebugSession[] = []; + + const sessions = debugService.getModel().getSessions(false); + + sessions.forEach((session) => { + if (session.compact && session.parentSession) { + headerSessions.push(session.parentSession); + } + }); + + sessions.forEach((session) => { + const isHeader = headerSessions.includes(session); + if (!session.parentSession) { + debugConsolePicks.push({ type: 'separator', label: isHeader ? session.name : undefined }); + } + + if (!isHeader) { + const pick = _createPick(session, filter, debugService, viewsService, commandService); + if (pick) { + debugConsolePicks.push(pick); + } + } + }); + + if (debugConsolePicks.length) { + debugConsolePicks.push({ type: 'separator' }); + } + + const createDebugSessionLabel = nls.localize('workbench.action.debug.startDebug', 'Start a New Debug Session'); + debugConsolePicks.push({ + label: `$(plus) ${createDebugSessionLabel}`, + ariaLabel: createDebugSessionLabel, + accept: () => commandService.executeCommand(selectAndStartID) + }); + + return debugConsolePicks; +} + + +function _getSessionInfo(session: IDebugSession): { label: string; description: string; ariaLabel: string } { + const label = (!session.configuration.name.length) ? session.name : session.configuration.name; + const parentName = session.compact ? undefined : session.parentSession?.configuration.name; + let description = ''; + let ariaLabel = ''; + if (parentName) { + ariaLabel = nls.localize('workbench.action.debug.spawnFrom', 'Session {0} spawned from {1}', label, parentName); + description = parentName; + } + + return { label, description, ariaLabel }; +} + +function _createPick(session: IDebugSession, filter: string, debugService: IDebugService, viewsService: IViewsService, commandService: ICommandService): IPickerDebugItem | undefined { + const pickInfo = _getSessionInfo(session); + const highlights = matchesFuzzy(filter, pickInfo.label, true); + if (highlights) { + return { + label: pickInfo.label, + description: pickInfo.description, + ariaLabel: pickInfo.ariaLabel, + highlights: { label: highlights }, + accept: () => { + debugService.focusStackFrame(undefined, undefined, session, { explicit: true }); + if (!viewsService.isViewVisible(REPL_VIEW_ID)) { + viewsService.openView(REPL_VIEW_ID, true); + } + } + }; + } + return undefined; +} + + diff --git a/src/vs/workbench/contrib/debug/common/loadedScriptsPicker.ts b/src/vs/workbench/contrib/debug/common/loadedScriptsPicker.ts index 51218a3475d..5e9bb1fa33e 100644 --- a/src/vs/workbench/contrib/debug/common/loadedScriptsPicker.ts +++ b/src/vs/workbench/contrib/debug/common/loadedScriptsPicker.ts @@ -17,12 +17,10 @@ import { dirname } from 'vs/base/common/resources'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; - -interface IPickerLoadedScriptItem extends IQuickPickItem { +export interface IPickerDebugItem extends IQuickPickItem { accept(): void; } - /** * This function takes a regular quickpick and makes one for loaded scripts that has persistent headers * e.g. when some picks are filtered out, the ones that are visible still have its header. @@ -37,7 +35,7 @@ export async function showLoadedScriptMenu(accessor: ServicesAccessor) { const labelService = accessor.get(ILabelService); const localDisposableStore = new DisposableStore(); - const quickPick = quickInputService.createQuickPick(); + const quickPick = quickInputService.createQuickPick(); localDisposableStore.add(quickPick); quickPick.matchOnLabel = quickPick.matchOnDescription = quickPick.matchOnDetail = quickPick.sortByLabel = false; quickPick.placeholder = nls.localize('moveFocusedView.selectView', "Search loaded scripts by name"); @@ -55,8 +53,8 @@ export async function showLoadedScriptMenu(accessor: ServicesAccessor) { quickPick.show(); } -async function _getPicksFromSession(session: IDebugSession, filter: string, editorService: IEditorService, modelService: IModelService, languageService: ILanguageService, labelService: ILabelService): Promise> { - const items: Array = []; +async function _getPicksFromSession(session: IDebugSession, filter: string, editorService: IEditorService, modelService: IModelService, languageService: ILanguageService, labelService: ILabelService): Promise> { + const items: Array = []; items.push({ type: 'separator', label: session.name }); const sources = await session.getLoadedSources(); @@ -69,8 +67,8 @@ async function _getPicksFromSession(session: IDebugSession, filter: string, edit }); return items; } -async function _getPicks(filter: string, sessions: IDebugSession[], editorService: IEditorService, modelService: IModelService, languageService: ILanguageService, labelService: ILabelService): Promise> { - const loadedScriptPicks: Array = []; +async function _getPicks(filter: string, sessions: IDebugSession[], editorService: IEditorService, modelService: IModelService, languageService: ILanguageService, labelService: ILabelService): Promise> { + const loadedScriptPicks: Array = []; const picks = await Promise.all( @@ -85,7 +83,7 @@ async function _getPicks(filter: string, sessions: IDebugSession[], editorServic return loadedScriptPicks; } -function _createPick(source: Source, filter: string, editorService: IEditorService, modelService: IModelService, languageService: ILanguageService, labelService: ILabelService): IPickerLoadedScriptItem | undefined { +function _createPick(source: Source, filter: string, editorService: IEditorService, modelService: IModelService, languageService: ILanguageService, labelService: ILabelService): IPickerDebugItem | undefined { const label = labelService.getUriBasenameLabel(source.uri); const desc = labelService.getUriLabel(dirname(source.uri)); From c92bda6ccb425c054599b2c89eb75485e10a3ae9 Mon Sep 17 00:00:00 2001 From: Isidor Nikolic Date: Thu, 21 Jul 2022 22:21:28 +0200 Subject: [PATCH 147/197] underline for hover of all links (#151499) * underline for hover of all links * move link hover css rule to style.css --- src/vs/workbench/browser/media/style.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index 3d61bbcb255..b5efad6ca4e 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -235,3 +235,7 @@ body.web { .monaco-workbench .monaco-list:focus { outline: 0 !important; /* tree indicates focus not via outline but through the focused item */ } + +.monaco-workbench a.monaco-link:hover { + text-decoration: underline; /* render underline on hover for accessibility requirements */ +} From fb1e4351e6f4ff8f7fd5fd4468de231b60e00790 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Thu, 21 Jul 2022 14:09:00 -0700 Subject: [PATCH 148/197] Remove redundant styles for underlined links (#155893) Remove redundant styles for underlined links (Fixes #155891) --- .../workbench/browser/parts/editor/media/editorplaceholder.css | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/media/editorplaceholder.css b/src/vs/workbench/browser/parts/editor/media/editorplaceholder.css index f3bde77fdca..325e348e2e0 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorplaceholder.css +++ b/src/vs/workbench/browser/parts/editor/media/editorplaceholder.css @@ -37,6 +37,5 @@ .monaco-editor-pane-placeholder .monaco-link:hover { font-size: 14px; cursor: pointer; - text-decoration: underline; margin-left: 5px; } From f74d2c0f62276e9d90f37a00b732a8b7fffd57d4 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Thu, 21 Jul 2022 14:48:33 -0700 Subject: [PATCH 149/197] Fix JSON Settings editor commands wrt Setting profiles (#155884) Ref #152888 --- .../browser/preferences.contribution.ts | 53 ++++++++++++++----- .../browser/userDataProfile.ts | 4 +- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index bf42a279f34..e42747beb9d 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -45,6 +45,7 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { CONTEXT_CURRENT_PROFILE } from 'vs/workbench/contrib/userDataProfile/browser/userDataProfile'; const SETTINGS_EDITOR_COMMAND_SEARCH = 'settings.action.search'; @@ -119,8 +120,9 @@ class SettingsEditor2InputSerializer implements IEditorSerializer { Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(KeybindingsEditorInput.ID, KeybindingsEditorInputSerializer); Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(SettingsEditor2Input.ID, SettingsEditor2InputSerializer); -const OPEN_SETTINGS2_ACTION_TITLE = { value: nls.localize('openSettings2', "Open Settings (UI)"), original: 'Open Settings (UI)' }; - +const OPEN_USER_SETTINGS_UI_TITLE = { value: nls.localize('openSettings2', "Open Settings (UI)"), original: 'Open Settings (UI)' }; +const OPEN_USER_SETTINGS_JSON_TITLE = { value: nls.localize('openUserSettingsJson', "Open User Settings (JSON)"), original: 'Open User Settings (JSON)' }; +const OPEN_CURRENT_PROFILE_SETTINGS_JSON_TITLE = { value: nls.localize('openCurrentProfileSettingsJson', "Open Current Profile Settings (JSON)"), original: 'Open Current Profile Settings (JSON)' }; const category = { value: nls.localize('preferences', "Preferences"), original: 'Preferences' }; interface IOpenSettingsActionOptions { @@ -206,20 +208,45 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon return accessor.get(IPreferencesService).openSettings({ jsonEditor: false, ...args }); } }); + + const that = this; + const registerOpenSettingsJsonCommandDisposable = this._register(new MutableDisposable()); + const registerOpenSettingsJsonCommand = () => { + registerOpenSettingsJsonCommandDisposable.value = registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.openSettingsJson', + title: that.userDataProfileService.currentProfile.isDefault ? OPEN_USER_SETTINGS_JSON_TITLE : OPEN_CURRENT_PROFILE_SETTINGS_JSON_TITLE, + category, + f1: true, + }); + } + run(accessor: ServicesAccessor, args: IOpenSettingsActionOptions) { + args = sanitizeOpenSettingsArgs(args); + return accessor.get(IPreferencesService).openSettings({ jsonEditor: true, ...args }); + } + }); + }; + registerAction2(class extends Action2 { constructor() { super({ - id: 'workbench.action.openSettingsJson', - title: { value: nls.localize('openSettingsJson', "Open Settings (JSON)"), original: 'Open Settings (JSON)' }, + id: 'workbench.action.openApplicationSettingsJson', + title: OPEN_USER_SETTINGS_JSON_TITLE, category, - f1: true, + menu: { + id: MenuId.CommandPalette, + when: ContextKeyExpr.notEquals(CONTEXT_CURRENT_PROFILE.key, that.userDataProfilesService.defaultProfile.id) + } }); } run(accessor: ServicesAccessor, args: IOpenSettingsActionOptions) { args = sanitizeOpenSettingsArgs(args); - return accessor.get(IPreferencesService).openSettings({ jsonEditor: true, ...args }); + return accessor.get(IPreferencesService).openApplicationSettings({ jsonEditor: true, ...args }); } }); + + // Opens the User tab of the Settings editor registerAction2(class extends Action2 { constructor() { super({ @@ -260,7 +287,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon constructor() { super({ id: '_workbench.openUserSettingsEditor', - title: OPEN_SETTINGS2_ACTION_TITLE, + title: OPEN_USER_SETTINGS_UI_TITLE, icon: preferencesOpenSettingsIcon, menu: [{ id: MenuId.EditorTitle, @@ -288,8 +315,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon constructor() { super({ id: SETTINGS_EDITOR_COMMAND_SWITCH_TO_CURRENT_PROFILE_JSON, - title: { value: nls.localize('openCurrentProfileSettings', "Open Current Profile Settings (JSON)"), original: 'Open Current Profile Settings (JSON)' }, - f1: true, + title: OPEN_CURRENT_PROFILE_SETTINGS_JSON_TITLE, menu: [{ id: submenuId, order: 1 }] }); } @@ -305,8 +331,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon constructor() { super({ id: SETTINGS_EDITOR_COMMAND_SWITCH_TO_APPLICATION_JSON, - title: { value: nls.localize('openApplicationSettings', "Open User Settings (JSON)"), original: 'Open User Settings (JSON)' }, - f1: true, + title: OPEN_USER_SETTINGS_JSON_TITLE, menu: [{ id: submenuId, order: 2 }] }); } @@ -360,10 +385,12 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon registerOpenUserSettingsEditorFromJsonAction(); registerOpenJsonFromSettingsEditorAction(); + registerOpenSettingsJsonCommand(); this._register(this.userDataProfileService.onDidChangeCurrentProfile(() => { registerOpenUserSettingsEditorFromJsonAction(); registerOpenJsonFromSettingsEditorAction(); + registerOpenSettingsJsonCommand(); })); registerAction2(class extends Action2 { @@ -1183,7 +1210,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: commandId, - title: OPEN_SETTINGS2_ACTION_TITLE, + title: OPEN_USER_SETTINGS_UI_TITLE, icon: preferencesOpenSettingsIcon }, when: ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.preferencesService.workspaceSettingsResource!.toString()), WorkbenchStateContext.isEqualTo('workspace'), ContextKeyExpr.not('isInDiffEditor')), @@ -1208,7 +1235,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: commandId, - title: OPEN_SETTINGS2_ACTION_TITLE, + title: OPEN_USER_SETTINGS_UI_TITLE, icon: preferencesOpenSettingsIcon }, when: ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.preferencesService.getFolderSettingsResource(folder.uri)!.toString()), ContextKeyExpr.not('isInDiffEditor')), diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 0c2b7990a49..2de14ece023 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -23,7 +23,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; import { IUserDataProfileManagementService, IUserDataProfileService, ManageProfilesSubMenu, PROFILES_CATEGORY, PROFILES_ENABLEMENT_CONTEXT, PROFILES_TTILE } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; -const CONTEXT_CURRENT_PROFILE = new RawContextKey('currentUserDataProfile', ''); +export const CONTEXT_CURRENT_PROFILE = new RawContextKey('currentUserDataProfile', ''); export const userDataProfilesIcon = registerIcon('settingsProfiles-icon', Codicon.settings, localize('settingsProfilesIcon', 'Icon for Settings Profiles.')); @@ -46,7 +46,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements this.currentProfileContext = CONTEXT_CURRENT_PROFILE.bindTo(contextKeyService); this.currentProfileContext.set(this.userDataProfileService.currentProfile.id); - this._register(this.userDataProfileService.onDidChangeCurrentProfile(e => this.currentProfileContext.set(this.userDataProfileService.currentProfile.id))); + this._register(this.userDataProfileService.onDidChangeCurrentProfile(() => this.currentProfileContext.set(this.userDataProfileService.currentProfile.id))); this.updateStatus(); this._register(Event.any(this.workspaceContextService.onDidChangeWorkbenchState, this.userDataProfileService.onDidChangeCurrentProfile, this.userDataProfilesService.onDidChangeProfiles)(() => this.updateStatus())); From 0a3ee299cef41da9eb44fa3fef6db0e925124c64 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 21 Jul 2022 15:17:22 -0700 Subject: [PATCH 150/197] Ensure folders with spaces don't impact link line/col Fixes #155532 --- .../browser/links/terminalLinkOpeners.ts | 15 ++++++++-- .../browser/links/terminalLinkOpeners.test.ts | 29 ++++++++++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts index 6fc5f4eb043..0e6e5347e90 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts @@ -23,6 +23,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder'; import { ISearchService } from 'vs/workbench/services/search/common/search'; +import { basename } from 'vs/base/common/path'; export class TerminalLocalFileLinkOpener implements ITerminalLinkOpener { constructor( @@ -35,7 +36,7 @@ export class TerminalLocalFileLinkOpener implements ITerminalLinkOpener { if (!link.uri) { throw new Error('Tried to open file link without a resolved URI'); } - const lineColumnInfo: ILineColumnInfo = this.extractLineColumnInfo(link.text); + const lineColumnInfo: ILineColumnInfo = this.extractLineColumnInfo(link.text, link.uri); const selection: ITextEditorSelection = { startLineNumber: lineColumnInfo.lineNumber, startColumn: lineColumnInfo.columnNumber @@ -51,12 +52,22 @@ export class TerminalLocalFileLinkOpener implements ITerminalLinkOpener { * * @param link Url link which may contain line and column number. */ - extractLineColumnInfo(link: string): ILineColumnInfo { + extractLineColumnInfo(link: string, uri?: URI): ILineColumnInfo { const lineColumnInfo: ILineColumnInfo = { lineNumber: 1, columnNumber: 1 }; + // If a URI was passed in the exact file is known, sanitize the link text such that the + // folders and file name do not contain whitespace. The actual path isn't important in + // extracting the line and column from the regex so this is safe + if (uri) { + const fileName = basename(uri.path); + const index = link.indexOf(fileName); + const endIndex = index + fileName.length; + link = link.slice(0, endIndex).replace(/\s/g, '_') + link.slice(endIndex); + } + // The local link regex only works for non file:// links, check these for a simple // `:line:col` suffix if (link.startsWith('file://')) { diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts index 225dd72bbdc..850f1ed8cec 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts @@ -7,7 +7,7 @@ import { deepStrictEqual } from 'assert'; import { Schemas } from 'vs/base/common/network'; import { OperatingSystem } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; -import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; +import { ITextEditorSelection, ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IFileService, IFileStatWithPartialMetadata } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; @@ -27,6 +27,7 @@ import { Terminal } from 'xterm'; export interface ITerminalLinkActivationResult { source: 'editor' | 'search'; link: string; + selection?: ITextEditorSelection; } class TestCommandDetectionCapability extends CommandDetectionCapability { @@ -79,6 +80,10 @@ suite('Workbench - TerminalLinkOpeners', () => { source: 'editor', link: editor.resource?.toString() }; + // Only assert on selection if it's not the default value + if (editor.options?.selection && (editor.options.selection.startColumn !== 1 || editor.options.selection.startLineNumber !== 1)) { + activationResult.selection = editor.options.selection; + } } } as Partial); // /*editorServiceSpy = */instantiationService.spy(IEditorService, 'openEditor'); @@ -165,6 +170,28 @@ suite('Workbench - TerminalLinkOpeners', () => { }); }); + test('should extract line and column from links in a workspace containing spaces', async () => { + localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux); + const localFolderOpener = instantiationService.createInstance(TerminalLocalFolderInWorkspaceLinkOpener); + opener = instantiationService.createInstance(TerminalSearchLinkOpener, capabilities, Promise.resolve('/space folder'), localFileOpener, localFolderOpener, OperatingSystem.Linux); + fileService.setFiles([ + URI.from({ scheme: Schemas.file, path: '/space folder/foo/bar.txt' }) + ]); + await opener.open({ + text: './foo/bar.txt:10:5', + bufferRange: { start: { x: 1, y: 1 }, end: { x: 8, y: 1 } }, + type: TerminalBuiltinLinkType.Search + }); + deepStrictEqual(activationResult, { + link: 'file:///space%20folder/foo/bar.txt', + source: 'editor', + selection: { + startColumn: 5, + startLineNumber: 10 + }, + }); + }); + suite('macOS/Linux', () => { setup(() => { localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux); From e2006a52da0445f676b331faa1a6d1a3624b54c4 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 21 Jul 2022 16:21:57 -0700 Subject: [PATCH 151/197] Add npmignore to markdown server (#155898) * Add npmignore to markdown server * Update version --- .../markdown-language-features/server/.npmignore | 12 ++++++++++++ .../markdown-language-features/server/package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 extensions/markdown-language-features/server/.npmignore diff --git a/extensions/markdown-language-features/server/.npmignore b/extensions/markdown-language-features/server/.npmignore new file mode 100644 index 00000000000..bfd4215998c --- /dev/null +++ b/extensions/markdown-language-features/server/.npmignore @@ -0,0 +1,12 @@ +.vscode/ +.github/ +out/test/ +src/ +.eslintrc.js +.gitignore +tsconfig*.json +*.tsbuildinfo +*.map +example.cjs +CODE_OF_CONDUCT.md +SECURITY.md \ No newline at end of file diff --git a/extensions/markdown-language-features/server/package.json b/extensions/markdown-language-features/server/package.json index c999e13b723..b0d6f254d90 100644 --- a/extensions/markdown-language-features/server/package.json +++ b/extensions/markdown-language-features/server/package.json @@ -1,7 +1,7 @@ { "name": "vscode-markdown-languageserver", "description": "Markdown language server", - "version": "1.0.0", + "version": "0.0.0-alpha-1", "author": "Microsoft Corporation", "license": "MIT", "engines": { From d2f3f7555b96e63e07738ab51931e1d038edee26 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 21 Jul 2022 16:32:25 -0700 Subject: [PATCH 152/197] Allow continuing on when term kill all doesn't succeed Fixes #155817 --- test/automation/src/terminal.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/automation/src/terminal.ts b/test/automation/src/terminal.ts index 67f012bf483..d088f849520 100644 --- a/test/automation/src/terminal.ts +++ b/test/automation/src/terminal.ts @@ -93,7 +93,14 @@ export class Terminal { await this._waitForTerminal(expectedLocation === 'editor' || commandId === TerminalCommandId.CreateNewEditor ? 'editor' : 'panel'); break; case TerminalCommandId.KillAll: - await this.code.waitForElements(Selector.Xterm, true, e => e.length === 0); + // HACK: Attempt to kill all terminals to clean things up, this is known to be flaky + // but the reason why isn't known. This is typically called in the after each hook, + // Since it's not actually required that all terminals are killed just continue on + // after 2 seconds. + await Promise.race([ + this.code.waitForElements(Selector.Xterm, true, e => e.length === 0), + new Promise(r => setTimeout(r, 2000)) + ]); break; } } From 7f6984e71bd9f05edeb1ecf9762cc0f3c9920feb Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 21 Jul 2022 16:49:25 -0700 Subject: [PATCH 153/197] Fix copy paste for files (#155915) This fixes the copy paste proposed api to correctly support pasting of files and file data --- .../api/browser/mainThreadLanguageFeatures.ts | 112 ++++++++++++------ .../workbench/api/common/extHost.protocol.ts | 5 +- .../api/common/extHostLanguageFeatures.ts | 6 +- 3 files changed, 81 insertions(+), 42 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index bf5de3a19a2..3e537e533af 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -366,45 +366,23 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread // --- copy paste action provider + private readonly _pasteEditProviders = new Map(); + $registerPasteEditProvider(handle: number, selector: IDocumentFilterDto[], supportsCopy: boolean, pasteMimeTypes: readonly string[]): void { - const provider: languages.DocumentPasteEditProvider = { - pasteMimeTypes: pasteMimeTypes, + const provider = new MainThreadPasteEditProvider(handle, this._proxy, supportsCopy, pasteMimeTypes); + this._pasteEditProviders.set(handle, provider); + this._registrations.set(handle, combinedDisposable( + this._languageFeaturesService.documentPasteEditProvider.register(selector, provider), + toDisposable(() => this._pasteEditProviders.delete(handle)), + )); + } - prepareDocumentPaste: supportsCopy - ? async (model: ITextModel, selections: Selection[], dataTransfer: VSDataTransfer, token: CancellationToken): Promise => { - const dataTransferDto = await typeConvert.DataTransfer.toDataTransferDTO(dataTransfer); - if (token.isCancellationRequested) { - return undefined; - } - - const result = await this._proxy.$prepareDocumentPaste(handle, model.uri, selections, dataTransferDto, token); - if (!result) { - return undefined; - } - - const dataTransferOut = new VSDataTransfer(); - result.items.forEach(([type, item]) => { - dataTransferOut.replace(type, createStringDataTransferItem(item.asString)); - }); - return dataTransferOut; - } - : undefined, - - provideDocumentPasteEdits: async (model: ITextModel, selections: Selection[], dataTransfer: VSDataTransfer, token: CancellationToken) => { - const d = await typeConvert.DataTransfer.toDataTransferDTO(dataTransfer); - const result = await this._proxy.$providePasteEdits(handle, model.uri, selections, d, token); - if (!result) { - return undefined; - } - - return { - insertText: result.insertText, - additionalEdit: result.additionalEdit ? reviveWorkspaceEditDto(result.additionalEdit) : undefined, - }; - } - }; - - this._registrations.set(handle, this._languageFeaturesService.documentPasteEditProvider.register(selector, provider)); + $resolvePasteFileData(handle: number, requestId: number, dataIndex: number): Promise { + const provider = this._pasteEditProviders.get(handle); + if (!provider) { + throw new Error('Could not find provider'); + } + return provider.resolveFileData(requestId, dataIndex); } // --- formatting @@ -924,6 +902,66 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread } } +class MainThreadPasteEditProvider implements languages.DocumentPasteEditProvider { + + private readonly dataTransfers = new DataTransferCache(); + + public readonly pasteMimeTypes: readonly string[]; + + readonly prepareDocumentPaste?: (model: ITextModel, ranges: readonly IRange[], dataTransfer: VSDataTransfer, token: CancellationToken) => Promise; + + constructor( + private readonly handle: number, + private readonly _proxy: ExtHostLanguageFeaturesShape, + supportsCopy: boolean, + pasteMimeTypes: readonly string[], + ) { + this.pasteMimeTypes = pasteMimeTypes; + + if (supportsCopy) { + this.prepareDocumentPaste = async (model: ITextModel, selections: readonly IRange[], dataTransfer: VSDataTransfer, token: CancellationToken): Promise => { + const dataTransferDto = await typeConvert.DataTransfer.toDataTransferDTO(dataTransfer); + if (token.isCancellationRequested) { + return undefined; + } + + const result = await this._proxy.$prepareDocumentPaste(handle, model.uri, selections, dataTransferDto, token); + if (!result) { + return undefined; + } + + const dataTransferOut = new VSDataTransfer(); + result.items.forEach(([type, item]) => { + dataTransferOut.replace(type, createStringDataTransferItem(item.asString)); + }); + return dataTransferOut; + }; + } + } + + async provideDocumentPasteEdits(model: ITextModel, selections: Selection[], dataTransfer: VSDataTransfer, token: CancellationToken) { + const request = this.dataTransfers.add(dataTransfer); + try { + const d = await typeConvert.DataTransfer.toDataTransferDTO(dataTransfer); + const result = await this._proxy.$providePasteEdits(this.handle, request.id, model.uri, selections, d, token); + if (!result) { + return undefined; + } + + return { + insertText: result.insertText, + additionalEdit: result.additionalEdit ? reviveWorkspaceEditDto(result.additionalEdit) : undefined, + }; + } finally { + request.dispose(); + } + } + + resolveFileData(requestId: number, dataIndex: number): Promise { + return this.dataTransfers.resolveDropFileData(requestId, dataIndex); + } +} + class MainThreadDocumentOnDropEditProvider implements languages.DocumentOnDropEditProvider { private readonly dataTransfers = new DataTransferCache(); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 16f0fd0ac13..8766b160f4d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -395,6 +395,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerCallHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerTypeHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerDocumentOnDropEditProvider(handle: number, selector: IDocumentFilterDto[]): void; + $resolvePasteFileData(handle: number, requestId: number, dataIndex: number): Promise; $resolveDocumentOnDropFileData(handle: number, requestId: number, dataIndex: number): Promise; $setLanguageConfiguration(handle: number, languageId: string, configuration: ILanguageConfigurationDto): void; } @@ -1724,8 +1725,8 @@ export interface ExtHostLanguageFeaturesShape { $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: languages.CodeActionContext, token: CancellationToken): Promise; $resolveCodeAction(handle: number, id: ChainedCacheId, token: CancellationToken): Promise; $releaseCodeActions(handle: number, cacheId: number): void; - $prepareDocumentPaste(handle: number, uri: UriComponents, ranges: IRange[], dataTransfer: DataTransferDTO, token: CancellationToken): Promise; - $providePasteEdits(handle: number, uri: UriComponents, ranges: IRange[], dataTransfer: DataTransferDTO, token: CancellationToken): Promise; + $prepareDocumentPaste(handle: number, uri: UriComponents, ranges: readonly IRange[], dataTransfer: DataTransferDTO, token: CancellationToken): Promise; + $providePasteEdits(handle: number, requestId: number, uri: UriComponents, ranges: IRange[], dataTransfer: DataTransferDTO, token: CancellationToken): Promise; $provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: languages.FormattingOptions, token: CancellationToken): Promise; $provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: languages.FormattingOptions, token: CancellationToken): Promise; $provideOnTypeFormattingEdits(handle: number, resource: UriComponents, position: IPosition, ch: string, options: languages.FormattingOptions, token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 809b7609426..d9244952ab0 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -512,7 +512,7 @@ class DocumentPasteEditProvider { const vscodeRanges = ranges.map(range => typeConvert.Range.to(range)); const dataTransfer = typeConvert.DataTransfer.toDataTransfer(dataTransferDto, async (index) => { - return (await this._proxy.$resolveDocumentOnDropFileData(this._handle, requestId, index)).buffer; + return (await this._proxy.$resolvePasteFileData(this._handle, requestId, index)).buffer; }); const edit = await this._provider.provideDocumentPasteEdits(doc, vscodeRanges, dataTransfer, token); @@ -2471,8 +2471,8 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.prepareDocumentPaste(URI.revive(resource), ranges, dataTransfer, token), undefined, token); } - $providePasteEdits(handle: number, resource: UriComponents, ranges: IRange[], dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise { - return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.providePasteEdits(0, URI.revive(resource), ranges, dataTransferDto, token), undefined, token); + $providePasteEdits(handle: number, requestId: number, resource: UriComponents, ranges: IRange[], dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise { + return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.providePasteEdits(requestId, URI.revive(resource), ranges, dataTransferDto, token), undefined, token); } // --- configuration From 43fcb56d473297fa50b01cb379a76c0e67ba9cd0 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Thu, 21 Jul 2022 16:50:49 -0700 Subject: [PATCH 154/197] Make search tree context menu multiselect-aware (#154847) * Make search tree context menu multiselect-aware Fixes #47166 * move domFocus call out of loop * PR feedback * resolve strange post-replace bug * cleaned up a bit but needs adjustment on post-replace focus * adjusting ordering * add replace action runner for all replace actions * pr feedback * fix list sorting and multiselect open replace preview * whitespace cleanup * tests and cleanup --- .../search/browser/media/searchview.css | 1 + .../contrib/search/browser/searchActions.ts | 306 +++++++++++------- .../contrib/search/browser/searchView.ts | 13 +- .../contrib/search/common/searchModel.ts | 35 ++ .../search/test/browser/searchViewlet.test.ts | 74 ++++- 5 files changed, 303 insertions(+), 126 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/media/searchview.css b/src/vs/workbench/contrib/search/browser/media/searchview.css index f59811de9a2..02b440bdf1c 100644 --- a/src/vs/workbench/contrib/search/browser/media/searchview.css +++ b/src/vs/workbench/contrib/search/browser/media/searchview.css @@ -243,6 +243,7 @@ } .search-view .monaco-list .monaco-list-row:hover:not(.highlighted) .monaco-action-bar, +.search-view .monaco-list .monaco-list-row.selected .monaco-action-bar, .search-view .monaco-list .monaco-list-row.focused .monaco-action-bar { display: inline-block; } diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index e6be26f64a1..dc0023470ab 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -12,7 +12,7 @@ import * as nls from 'vs/nls'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ILabelService } from 'vs/platform/label/common/label'; import { getSelectionKeyboardEvent, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; @@ -23,13 +23,13 @@ import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; import { ISearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; -import { FileMatch, FolderMatch, FolderMatchWithResource, Match, RenderableMatch, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; +import { FileMatch, FolderMatch, FolderMatchWithResource, Match, RenderableMatch, searchComparer, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; import { OpenEditorCommandId } from 'vs/workbench/contrib/searchEditor/browser/constants'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; import { OpenSearchEditorArgs } from 'vs/workbench/contrib/searchEditor/browser/searchEditor.contribution'; import { SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ISearchConfiguration, VIEW_ID } from 'vs/workbench/services/search/common/search'; +import { ISearchConfiguration, ISearchConfigurationProperties, VIEW_ID } from 'vs/workbench/services/search/common/search'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; export function isSearchViewFocused(viewsService: IViewsService): boolean { @@ -441,143 +441,88 @@ export abstract class AbstractSearchAndReplaceAction extends Action { } } -export class RemoveAction extends AbstractSearchAndReplaceAction { - - static readonly LABEL = nls.localize('RemoveAction.label', "Dismiss"); - +class ReplaceActionRunner { constructor( private viewer: WorkbenchObjectTree, - private element: RenderableMatch, - @IKeybindingService keyBindingService: IKeybindingService - ) { - super(Constants.RemoveActionId, appendKeyBindingLabel(RemoveAction.LABEL, keyBindingService.lookupKeybinding(Constants.RemoveActionId), keyBindingService), ThemeIcon.asClassName(searchRemoveIcon)); - } + private viewlet: SearchView | undefined, + private getElementToFocusAfterRemoved: (viewer: WorkbenchObjectTree, elementToBeRemoved: RenderableMatch) => RenderableMatch, + private getPreviousElementAfterRemoved: (viewer: WorkbenchObjectTree, element: RenderableMatch) => RenderableMatch, + // Services + @IReplaceService private readonly replaceService: IReplaceService, + @IEditorService private readonly editorService: IEditorService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService + ) { } - override run(): Promise { - const currentFocusElement = this.viewer.getFocus()[0]; - const nextFocusElement = !currentFocusElement || currentFocusElement instanceof SearchResult || elementIsEqualOrParent(currentFocusElement, this.element) ? - this.getElementToFocusAfterRemoved(this.viewer, this.element) : - null; + async performReplace(element: FolderMatch | FileMatch | Match): Promise { + // since multiple elements can be selected, we need to check the type of the FolderMatch/FileMatch/Match before we perform the replace. + const elementsToReplace = getElementsToOperateOnInfo(this.viewer, element, this.configurationService.getValue('search')); - if (nextFocusElement) { - this.viewer.reveal(nextFocusElement); - this.viewer.setFocus([nextFocusElement], getSelectionKeyboardEvent()); - this.viewer.setSelection([nextFocusElement], getSelectionKeyboardEvent()); - } + await Promise.all(elementsToReplace.map(async (elem) => { + const parent = elem.parent(); - this.element.parent().remove(this.element); - this.viewer.domFocus(); - - return Promise.resolve(); - } -} - -function elementIsEqualOrParent(element: RenderableMatch, testParent: RenderableMatch | SearchResult): boolean { - do { - if (element === testParent) { - return true; - } - } while (!(element.parent() instanceof SearchResult) && (element = element.parent())); - - return false; -} - -export class ReplaceAllAction extends AbstractSearchAndReplaceAction { - - static readonly LABEL = nls.localize('file.replaceAll.label', "Replace All"); - - constructor( - private viewlet: SearchView, - private fileMatch: FileMatch, - @IKeybindingService keyBindingService: IKeybindingService - ) { - super(Constants.ReplaceAllInFileActionId, appendKeyBindingLabel(ReplaceAllAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceAllInFileActionId), keyBindingService), ThemeIcon.asClassName(searchReplaceAllIcon)); - } - - override run(): Promise { - const tree = this.viewlet.getControl(); - const nextFocusElement = this.getElementToFocusAfterRemoved(tree, this.fileMatch); - return this.fileMatch.parent().replace(this.fileMatch).then(() => { - if (nextFocusElement) { - tree.setFocus([nextFocusElement], getSelectionKeyboardEvent()); - tree.setSelection([nextFocusElement], getSelectionKeyboardEvent()); + if ((parent instanceof FolderMatch || parent instanceof FileMatch) && arrayContainsElementOrParent(parent, elementsToReplace)) { + // skip any children who have parents in the array + return; } - tree.domFocus(); - this.viewlet.open(this.fileMatch, true); - }); - } -} + if (elem instanceof FileMatch) { + elem.parent().replace(elem); + } else if (elem instanceof Match) { + elem.parent().replace(elem); + } else if (elem instanceof FolderMatch) { + await elem.replaceAll(); + } + })); -export class ReplaceAllInFolderAction extends AbstractSearchAndReplaceAction { + const currentBottomFocusElement = elementsToReplace[elementsToReplace.length - 1]; - static readonly LABEL = nls.localize('file.replaceAll.label', "Replace All"); + if (currentBottomFocusElement instanceof Match) { + const elementToFocus = this.getElementToFocusAfterReplace(currentBottomFocusElement); - constructor(private viewer: WorkbenchObjectTree, private folderMatch: FolderMatch, - @IKeybindingService keyBindingService: IKeybindingService - ) { - super(Constants.ReplaceAllInFolderActionId, appendKeyBindingLabel(ReplaceAllInFolderAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceAllInFolderActionId), keyBindingService), ThemeIcon.asClassName(searchReplaceAllIcon)); - } + if (elementToFocus) { + this.viewer.setFocus([elementToFocus], getSelectionKeyboardEvent()); + this.viewer.setSelection([elementToFocus], getSelectionKeyboardEvent()); + } + const elementToShowReplacePreview = this.getElementToShowReplacePreview(elementToFocus, currentBottomFocusElement); + + this.viewer.domFocus(); + + const useReplacePreview = this.configurationService.getValue().search.useReplacePreview; + if (!useReplacePreview || !elementToShowReplacePreview || this.hasToOpenFile(currentBottomFocusElement)) { + this.viewlet?.open(currentBottomFocusElement, true); + } else { + this.replaceService.openReplacePreview(elementToShowReplacePreview, true); + } + return; + } else { + const nextFocusElement = this.getElementToFocusAfterRemoved(this.viewer, currentBottomFocusElement); - override run(): Promise { - const nextFocusElement = this.getElementToFocusAfterRemoved(this.viewer, this.folderMatch); - return this.folderMatch.replaceAll().then(() => { if (nextFocusElement) { this.viewer.setFocus([nextFocusElement], getSelectionKeyboardEvent()); this.viewer.setSelection([nextFocusElement], getSelectionKeyboardEvent()); } + this.viewer.domFocus(); - }); - } -} -export class ReplaceAction extends AbstractSearchAndReplaceAction { + if (element instanceof FileMatch) { + this.viewlet?.open(element, true); + } - static readonly LABEL = nls.localize('match.replace.label', "Replace"); - - static runQ = Promise.resolve(); - - constructor(private viewer: WorkbenchObjectTree, private element: Match, private viewlet: SearchView, - @IReplaceService private readonly replaceService: IReplaceService, - @IKeybindingService keyBindingService: IKeybindingService, - @IEditorService private readonly editorService: IEditorService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService - ) { - super(Constants.ReplaceActionId, appendKeyBindingLabel(ReplaceAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceActionId), keyBindingService), ThemeIcon.asClassName(searchReplaceIcon)); - } - - override async run(): Promise { - this.enabled = false; - - await this.element.parent().replace(this.element); - const elementToFocus = this.getElementToFocusAfterReplace(); - if (elementToFocus) { - this.viewer.setFocus([elementToFocus], getSelectionKeyboardEvent()); - this.viewer.setSelection([elementToFocus], getSelectionKeyboardEvent()); - } - - const elementToShowReplacePreview = this.getElementToShowReplacePreview(elementToFocus); - this.viewer.domFocus(); - - const useReplacePreview = this.configurationService.getValue().search.useReplacePreview; - if (!useReplacePreview || !elementToShowReplacePreview || this.hasToOpenFile()) { - this.viewlet.open(this.element, true); - } else { - this.replaceService.openReplacePreview(elementToShowReplacePreview, true); + return; } } - private getElementToFocusAfterReplace(): RenderableMatch { + private getElementToFocusAfterReplace(currElement: Match): RenderableMatch { const navigator: ITreeNavigator = this.viewer.navigate(); let fileMatched = false; let elementToFocus: RenderableMatch | null = null; do { elementToFocus = navigator.current(); if (elementToFocus instanceof Match) { - if (elementToFocus.parent().id() === this.element.parent().id()) { + if (elementToFocus.parent().id() === currElement.parent().id()) { fileMatched = true; - if (this.element.range().getStartPosition().isBeforeOrEqual(elementToFocus.range().getStartPosition())) { + if (currElement.range().getStartPosition().isBeforeOrEqual(elementToFocus.range().getStartPosition())) { // Closest next match in the same file break; } @@ -595,31 +540,141 @@ export class ReplaceAction extends AbstractSearchAndReplaceAction { return elementToFocus!; } - private getElementToShowReplacePreview(elementToFocus: RenderableMatch): Match | null { - if (this.hasSameParent(elementToFocus)) { + private getElementToShowReplacePreview(elementToFocus: RenderableMatch, currBottomElem: RenderableMatch): Match | null { + if (this.hasSameParent(elementToFocus, currBottomElem)) { return elementToFocus; } - const previousElement = this.getPreviousElementAfterRemoved(this.viewer, this.element); - if (this.hasSameParent(previousElement)) { + const previousElement = this.getPreviousElementAfterRemoved(this.viewer, elementToFocus); + if (this.hasSameParent(previousElement, currBottomElem)) { return previousElement; } return null; } - private hasSameParent(element: RenderableMatch): boolean { - return element && element instanceof Match && this.uriIdentityService.extUri.isEqual(element.parent().resource, this.element.parent().resource); + private hasSameParent(element: RenderableMatch, currBottomElem: RenderableMatch): boolean { + if (!(currBottomElem instanceof Match)) { + return false; + } + return element && element instanceof Match && this.uriIdentityService.extUri.isEqual(element.parent().resource, currBottomElem.parent().resource); } - private hasToOpenFile(): boolean { + private hasToOpenFile(currBottomElem: RenderableMatch): boolean { + if (!(currBottomElem instanceof Match)) { + return false; + } const activeEditor = this.editorService.activeEditor; const file = activeEditor?.resource; if (file) { - return this.uriIdentityService.extUri.isEqual(file, this.element.parent().resource); + return this.uriIdentityService.extUri.isEqual(file, currBottomElem.parent().resource); } return false; } } +export class RemoveAction extends AbstractSearchAndReplaceAction { + + static readonly LABEL = nls.localize('RemoveAction.label', "Dismiss"); + + constructor( + private viewer: WorkbenchObjectTree, + private element: RenderableMatch, + @IKeybindingService keyBindingService: IKeybindingService, + @IConfigurationService private readonly configurationService: IConfigurationService, + ) { + super(Constants.RemoveActionId, appendKeyBindingLabel(RemoveAction.LABEL, keyBindingService.lookupKeybinding(Constants.RemoveActionId), keyBindingService), ThemeIcon.asClassName(searchRemoveIcon)); + } + + override run(): Promise { + const elementsToRemove = getElementsToOperateOnInfo(this.viewer, this.element, this.configurationService.getValue('search')); + + const currentBottomFocusElement = elementsToRemove[elementsToRemove.length - 1]; + + const nextFocusElement = !currentBottomFocusElement || currentBottomFocusElement instanceof SearchResult || arrayContainsElementOrParent(currentBottomFocusElement, elementsToRemove) ? + this.getElementToFocusAfterRemoved(this.viewer, currentBottomFocusElement) : + null; + + if (nextFocusElement) { + this.viewer.reveal(nextFocusElement); + this.viewer.setFocus([nextFocusElement], getSelectionKeyboardEvent()); + this.viewer.setSelection([nextFocusElement], getSelectionKeyboardEvent()); + } + + elementsToRemove.forEach((currentElement) => + currentElement.parent().remove(<(FolderMatch | FileMatch)[] & Match & FileMatch[]>currentElement) + ); + + this.viewer.domFocus(); + return Promise.resolve(); + } +} + +function arrayContainsElementOrParent(element: RenderableMatch, testArray: (RenderableMatch | SearchResult)[]): boolean { + do { + if (testArray.includes(element)) { + return true; + } + } while (!(element.parent() instanceof SearchResult) && (element = element.parent())); + + return false; +} + +export class ReplaceAllAction extends AbstractSearchAndReplaceAction { + + static readonly LABEL = nls.localize('file.replaceAll.label', "Replace All"); + private replaceRunner: ReplaceActionRunner; + constructor( + viewlet: SearchView, + private fileMatch: FileMatch, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IKeybindingService keyBindingService: IKeybindingService, + ) { + super(Constants.ReplaceAllInFileActionId, appendKeyBindingLabel(ReplaceAllAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceAllInFileActionId), keyBindingService), ThemeIcon.asClassName(searchReplaceAllIcon)); + this.replaceRunner = this.instantiationService.createInstance(ReplaceActionRunner, viewlet.getControl(), viewlet, this.getElementToFocusAfterRemoved, this.getPreviousElementAfterRemoved); + } + + override async run(): Promise { + return this.replaceRunner.performReplace(this.fileMatch); + } +} + +export class ReplaceAllInFolderAction extends AbstractSearchAndReplaceAction { + + static readonly LABEL = nls.localize('file.replaceAll.label', "Replace All"); + private replaceRunner: ReplaceActionRunner; + + constructor(viewer: WorkbenchObjectTree, private folderMatch: FolderMatch, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IKeybindingService keyBindingService: IKeybindingService + ) { + super(Constants.ReplaceAllInFolderActionId, appendKeyBindingLabel(ReplaceAllInFolderAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceAllInFolderActionId), keyBindingService), ThemeIcon.asClassName(searchReplaceAllIcon)); + this.replaceRunner = this.instantiationService.createInstance(ReplaceActionRunner, viewer, undefined, this.getElementToFocusAfterRemoved, this.getPreviousElementAfterRemoved); + } + + override run(): Promise { + return this.replaceRunner.performReplace(this.folderMatch); + } +} + +export class ReplaceAction extends AbstractSearchAndReplaceAction { + + static readonly LABEL = nls.localize('match.replace.label', "Replace"); + + static runQ = Promise.resolve(); + private replaceRunner: ReplaceActionRunner; + + constructor(viewer: WorkbenchObjectTree, private element: Match, viewlet: SearchView, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IKeybindingService keyBindingService: IKeybindingService, + ) { + super(Constants.ReplaceActionId, appendKeyBindingLabel(ReplaceAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceActionId), keyBindingService), ThemeIcon.asClassName(searchReplaceIcon)); + this.replaceRunner = this.instantiationService.createInstance(ReplaceActionRunner, viewer, viewlet, this.getElementToFocusAfterRemoved, this.getPreviousElementAfterRemoved); + } + + override async run(): Promise { + return this.replaceRunner.performReplace(this.element); + } +} + export const copyPathCommand: ICommandHandler = async (accessor, fileMatch: FileMatch | FolderMatchWithResource | undefined) => { if (!fileMatch) { const selection = getSelectedRow(accessor); @@ -767,3 +822,14 @@ export const focusSearchListCommand: ICommandHandler = accessor => { } }); }; + +function getElementsToOperateOnInfo(viewer: WorkbenchObjectTree, currElement: RenderableMatch, sortConfig: ISearchConfigurationProperties): RenderableMatch[] { + let elements: RenderableMatch[] = viewer.getSelection().filter((x): x is RenderableMatch => x !== null).sort((a, b) => searchComparer(a, b, sortConfig.sortOrder)); + + // if selection doesn't include multiple elements, just return current focus element. + if (!(elements.length > 1 && elements.includes(currElement))) { + elements = [currElement]; + } + + return elements; +} diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 8f87fe9247d..e4bde4f0d05 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -89,6 +89,7 @@ export enum SearchViewPosition { } const SEARCH_CANCELLED_MESSAGE = nls.localize('searchCanceled', "Search was canceled before any results could be found - "); +const DEBOUNCE_DELAY = 75; export class SearchView extends ViewPane { private static readonly ACTIONS_RIGHT_CLASS_NAME = 'actions-right'; @@ -730,7 +731,7 @@ export class SearchView extends ViewPane { } return null; }), - multipleSelectionSupport: false, + multipleSelectionSupport: true, selectionNavigation: true, overrideStyles: { listBackground: this.getBackgroundColor() @@ -743,7 +744,7 @@ export class SearchView extends ViewPane { this._register(this.viewModel.searchResult.onChange(() => updateHasSomeCollapsible())); this._register(this.tree.onDidChangeCollapseState(() => updateHasSomeCollapsible())); - this._register(Event.debounce(this.tree.onDidOpen, (last, event) => event, 75, true)(options => { + this._register(Event.debounce(this.tree.onDidOpen, (last, event) => event, DEBOUNCE_DELAY, true)(options => { if (options.element instanceof Match) { const selectedMatch: Match = options.element; this.currentSelectedFileMatch?.setSelectedMatch(null); @@ -754,6 +755,14 @@ export class SearchView extends ViewPane { } })); + this._register(Event.debounce(this.tree.onDidChangeFocus, (last, event) => event, DEBOUNCE_DELAY, true)(() => { + const selection = this.tree.getSelection(); + const focus = this.tree.getFocus()[0]; + if (selection.length > 1 && focus instanceof Match) { + this.onFocus(focus, true); + } + })); + this._register(Event.any(this.tree.onDidFocus, this.tree.onDidChangeFocus)(() => { if (this.tree.isDOMFocused()) { const focus = this.tree.getFocus()[0]; diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index 4df55f37483..f1d6d8e3f38 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -712,6 +712,41 @@ export function searchMatchComparer(elementA: RenderableMatch, elementB: Rendera return 0; } +export function searchComparer(elementA: RenderableMatch, elementB: RenderableMatch, sortOrder: SearchSortOrder = SearchSortOrder.Default): number { + const elemAParents = createParentList(elementA); + const elemBParents = createParentList(elementB); + + let i = elemAParents.length - 1; + let j = elemBParents.length - 1; + while (i >= 0 && j >= 0) { + if (elemAParents[i].id() !== elemBParents[j].id()) { + return searchMatchComparer(elemAParents[i], elemBParents[j], sortOrder); + } + i--; + j--; + } + const elemAAtEnd = i === 0; + const elemBAtEnd = j === 0; + + if (elemAAtEnd && !elemBAtEnd) { + return 1; + } else if (!elemAAtEnd && elemBAtEnd) { + return -1; + } + return 0; +} + +function createParentList(element: RenderableMatch): RenderableMatch[] { + const parentArray: RenderableMatch[] = []; + let currElement: RenderableMatch | SearchResult = element; + + while (!(currElement instanceof SearchResult)) { + parentArray.push(currElement); + currElement = currElement.parent(); + } + + return parentArray; +} export class SearchResult extends Disposable { private _onChange = this._register(new Emitter()); diff --git a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts index b0517c6a59f..28cf97e0b77 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { isWindows } from 'vs/base/common/platform'; -import { URI as uri } from 'vs/base/common/uri'; +import { URI, URI as uri } from 'vs/base/common/uri'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { IModelService } from 'vs/editor/common/services/model'; import { ModelService } from 'vs/editor/common/services/modelService'; @@ -21,7 +21,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; -import { FileMatch, Match, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; +import { FileMatch, FolderMatch, Match, searchComparer, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; import { MockLabelService } from 'vs/workbench/services/label/test/common/mockLabelService'; import { IFileMatch, ITextSearchMatch, OneLineRange, QueryType, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -108,12 +108,78 @@ suite('Search - Viewlet', () => { assert(searchMatchComparer(fileMatch3, fileMatch4, SearchSortOrder.Type) < 0); }); - function aFileMatch(path: string, searchResult?: SearchResult, ...lineMatches: ITextSearchMatch[]): FileMatch { + test('Cross-type Comparer', () => { + + const searchResult = aSearchResult(); + const folderMatch1 = aFolderMatch(isWindows ? 'C:\\voo' : '/c/voo', 0, searchResult); + const folderMatch2 = aFolderMatch(isWindows ? 'C:\\with' : '/c/with', 1, searchResult); + + const fileMatch1 = aFileMatch(isWindows ? 'C:\\voo\\foo.a' : '/c/voo/foo.a', folderMatch1); + const fileMatch2 = aFileMatch(isWindows ? 'C:\\with\\path.c' : '/c/with/path.c', folderMatch2); + const fileMatch3 = aFileMatch(isWindows ? 'C:\\with\\path\\bar.b' : '/c/with/path/bar.b', folderMatch2); + + const lineMatch1 = new Match(fileMatch1, ['bar'], new OneLineRange(0, 1, 1), new OneLineRange(0, 1, 1)); + const lineMatch2 = new Match(fileMatch1, ['bar'], new OneLineRange(0, 1, 1), new OneLineRange(2, 1, 1)); + + const lineMatch3 = new Match(fileMatch2, ['barfoo'], new OneLineRange(0, 1, 1), new OneLineRange(0, 1, 1)); + const lineMatch4 = new Match(fileMatch2, ['fooooo'], new OneLineRange(0, 1, 1), new OneLineRange(2, 1, 1)); + + const lineMatch5 = new Match(fileMatch3, ['foobar'], new OneLineRange(0, 1, 1), new OneLineRange(2, 1, 1)); + + /*** + * Structure would take the following form: + * + * folderMatch1 (voo) + * > fileMatch1 (/foo.a) + * >> lineMatch1 + * >> lineMatch2 + * folderMatch2 (with) + * > fileMatch2 (/path.c) + * >> lineMatch4 + * >> lineMatch5 + * > fileMatch3 (/path/bar.b) + * >> lineMatch3 + * + */ + + // for these, refer to diagram above + assert(searchComparer(fileMatch1, fileMatch3) < 0); + assert(searchComparer(fileMatch2, fileMatch3) < 0); + assert(searchComparer(folderMatch2, fileMatch2) < 0); + assert(searchComparer(lineMatch4, lineMatch5) < 0); + assert(searchComparer(lineMatch1, lineMatch3) < 0); + assert(searchComparer(lineMatch2, folderMatch2) < 0); + + // travel up hierarchy and order of folders take precedence. "voo < with" in indices + assert(searchComparer(fileMatch1, fileMatch3, SearchSortOrder.FileNames) < 0); + // bar.b < path.c + assert(searchComparer(fileMatch3, fileMatch2, SearchSortOrder.FileNames) < 0); + // lineMatch4's parent is fileMatch2, "bar.b < path.c" + assert(searchComparer(fileMatch3, lineMatch4, SearchSortOrder.FileNames) < 0); + + // bar.b < path.c + assert(searchComparer(fileMatch3, fileMatch2, SearchSortOrder.Type) < 0); + // lineMatch4's parent is fileMatch2, "bar.b < path.c" + assert(searchComparer(fileMatch3, lineMatch4, SearchSortOrder.Type) < 0); + }); + + function aFileMatch(path: string, parentFolder?: FolderMatch, ...lineMatches: ITextSearchMatch[]): FileMatch { const rawMatch: IFileMatch = { resource: uri.file(path), results: lineMatches }; - return instantiation.createInstance(FileMatch, null, null, null, searchResult, rawMatch); + return instantiation.createInstance(FileMatch, null, null, null, parentFolder, rawMatch); + } + + function aFolderMatch(path: string, index: number, parent?: SearchResult): FolderMatch { + const searchModel = instantiation.createInstance(SearchModel); + return instantiation.createInstance(FolderMatch, uri.file(path), path, index, null, parent, searchModel); + } + + function aSearchResult(): SearchResult { + const searchModel = instantiation.createInstance(SearchModel); + searchModel.searchResult.query = { type: 1, folderQueries: [{ folder: URI.parse('file://c:/') }] }; + return searchModel.searchResult; } function stubModelService(instantiationService: TestInstantiationService): IModelService { From dab051df5cea49eef4166264e683462920f8c5f6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 21 Jul 2022 18:01:56 -0700 Subject: [PATCH 155/197] Skip unit test that's breaking the build Part of #155532 --- .../terminal/test/browser/links/terminalLinkOpeners.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts index 850f1ed8cec..7b368639dfc 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts @@ -170,7 +170,7 @@ suite('Workbench - TerminalLinkOpeners', () => { }); }); - test('should extract line and column from links in a workspace containing spaces', async () => { + test.skip('should extract line and column from links in a workspace containing spaces', async () => { localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux); const localFolderOpener = instantiationService.createInstance(TerminalLocalFolderInWorkspaceLinkOpener); opener = instantiationService.createInstance(TerminalSearchLinkOpener, capabilities, Promise.resolve('/space folder'), localFileOpener, localFolderOpener, OperatingSystem.Linux); From cf0496267d1e036a6dcdc1c7a1696c0da84a4fc0 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 22 Jul 2022 10:32:23 +0200 Subject: [PATCH 156/197] View size API proposal (#155710) * Add a weight/flex size for view contributsions Part of #122283 * Fix errors * Remove 'fit' * Fix typo and remove viewPaneContainer changes * Add view container owner check. --- .../api/browser/viewsExtensionPoint.ts | 25 ++++++++++++++++--- .../common/extensionsApiProposals.ts | 1 + .../vscode.proposed.contribViewSize.d.ts | 17 +++++++++++++ 3 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.contribViewSize.d.ts diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index b94fab104e1..dc9fc1750b9 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -25,7 +25,7 @@ import { VIEWLET_ID as EXPLORER } from 'vs/workbench/contrib/files/common/files' import { VIEWLET_ID as REMOTE } from 'vs/workbench/contrib/remote/browser/remoteExplorer'; import { VIEWLET_ID as SCM } from 'vs/workbench/contrib/scm/common/scm'; import { WebviewViewPane } from 'vs/workbench/contrib/webviewView/browser/webviewViewPane'; -import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -36,6 +36,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; import { ITreeViewsService } from 'vs/workbench/services/views/browser/treeViewsService'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; +import { ILogService } from 'vs/platform/log/common/log'; export interface IUserFriendlyViewsContainerDescriptor { id: string; @@ -97,6 +98,8 @@ interface IUserFriendlyViewDescriptor { contextualTitle?: string; visibility?: string; + size?: number; + // From 'remoteViewDescriptor' type group?: string; remoteName?: string | string[]; @@ -159,6 +162,10 @@ const viewDescriptor: IJSONSchema = { localize('vscode.extension.contributes.view.initialState.hidden', "The view will not be shown in the view container, but will be discoverable through the views menu and other view entry points and can be un-hidden by the user."), localize('vscode.extension.contributes.view.initialState.collapsed', "The view will show in the view container, but will be collapsed.") ] + }, + size: { + type: 'number', + description: localize('vscode.extension.contributs.view.size', "The size of the view. Using a number will behave like the css 'flex' property, and the size will set the initial size when the view is first shown. In the side bar, this is the height of the view."), } } }; @@ -256,7 +263,8 @@ class ViewsExtensionHandler implements IWorkbenchContribution { private viewsRegistry: IViewsRegistry; constructor( - @IInstantiationService private readonly instantiationService: IInstantiationService + @IInstantiationService private readonly instantiationService: IInstantiationService, + @ILogService private readonly logService: ILogService ) { this.viewContainersRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); this.viewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); @@ -499,6 +507,16 @@ class ViewsExtensionHandler implements IWorkbenchContribution { return null; } + let weight: number | undefined = undefined; + if (typeof item.size === 'number') { + checkProposedApiEnabled(extension.description, 'contribViewSize'); + if (container.extensionId?.value === extension.description.identifier.value) { + weight = item.size; + } else { + this.logService.warn(`${extension.description.identifier.value} tried to set the view size of ${item.id} but it was ignored because the view container does not belong to it.`); + } + } + const viewDescriptor = { type: type, ctorDescriptor: type === ViewType.Tree ? new SyncDescriptor(TreeViewPane) : new SyncDescriptor(WebviewViewPane), @@ -517,7 +535,8 @@ class ViewsExtensionHandler implements IWorkbenchContribution { group: item.group, remoteAuthority: item.remoteName || (item).remoteAuthority, // TODO@roblou - delete after remote extensions are updated hideByDefault: initialVisibility === InitialVisibility.Hidden, - workspace: viewContainer?.id === REMOTE ? true : undefined + workspace: viewContainer?.id === REMOTE ? true : undefined, + weight }; diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 8301ad0885f..65419fce737 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -15,6 +15,7 @@ export const allApiProposals = Object.freeze({ contribMergeEditorToolbar: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMergeEditorToolbar.d.ts', contribRemoteHelp: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribRemoteHelp.d.ts', contribShareMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribShareMenu.d.ts', + contribViewSize: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewSize.d.ts', contribViewsRemote: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsRemote.d.ts', contribViewsWelcome: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsWelcome.d.ts', contribWebviewContext: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribWebviewContext.d.ts', diff --git a/src/vscode-dts/vscode.proposed.contribViewSize.d.ts b/src/vscode-dts/vscode.proposed.contribViewSize.d.ts new file mode 100644 index 00000000000..01ec28d95f4 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribViewSize.d.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder for view size + +// https://github.com/microsoft/vscode/issues/122283 @alexr00 + +/** + * View contributions can include a `size`, which can be a number. A number works similar to the css flex property. + * + * For example, if you have 3 views, with sizes 1, 1, and 2, the views of size 1 will together take up the same amount of space as the view of size 2. + * + * A number value will only be used as an initial size. After a user has changed the size of the view, the user's choice will be restored. +*/ + From b8876a2d60a7d521e89f589f6436e1e204c20c93 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 22 Jul 2022 11:28:49 +0200 Subject: [PATCH 157/197] enable merge editor by default (#155937) --- extensions/git/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 831c0319cd2..0b2195c6034 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -2522,7 +2522,7 @@ }, "git.mergeEditor": { "type": "boolean", - "default": false, + "default": true, "markdownDescription": "%config.mergeEditor%", "scope": "window" } From 3842ef9a4af3d164c58facf6641d89be0fe55250 Mon Sep 17 00:00:00 2001 From: nirabhromakhal Date: Fri, 22 Jul 2022 15:10:26 +0530 Subject: [PATCH 158/197] Fixed issue where status bar can overflow without affecting notification beak (#155649) * fixed status bar overflow while keeping beak intact * using overflow: hidden while keeping beak intact * made the notification bell absolute positioned * overflow: hidden but beak is relative to status bar item * using has-beak class name to toggle the beak --- .../parts/statusbar/media/statusbarpart.css | 18 ++++++++++++------ .../browser/parts/statusbar/statusbarItem.ts | 8 ++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index 45ccb1ffda5..24b22dd7c60 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -10,7 +10,7 @@ height: 22px; font-size: 12px; display: flex; - overflow: visible; + overflow: hidden; transition: background-color 0.35s ease-out; } @@ -47,16 +47,22 @@ vertical-align: top; max-width: 40vw; } - .monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak { position: relative; } -.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak:before { - content: ''; +.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak > .status-bar-beak-container { position: absolute; - left: 10px; + left: calc(50% - 5px); /* centering relative to parent */ top: -5px; + width: 10px; + height: 5px; +} + +.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak > .status-bar-beak-container:before { + content: ''; + position: fixed; + color: rgb(0, 122, 204); border-bottom-width: 5px; border-bottom-style: solid; border-left: 5px solid transparent; @@ -83,7 +89,7 @@ } .monaco-workbench .part.statusbar > .items-container > .statusbar-item.right.last-visible-item { - padding-right: 7px; /* Add padding to the most right status bar item */ + margin-right: 7px; /* Add margin to the most right status bar item */ } /* Tweak appearance for items with background to improve hover feedback */ diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts index b529800544c..faae3589cc2 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts @@ -41,6 +41,7 @@ export class StatusbarEntryItem extends Disposable { private hover: ICustomHover | undefined = undefined; readonly labelContainer: HTMLElement; + readonly beakContainer: HTMLElement; get name(): string { return assertIsDefined(this.entry).name; @@ -73,6 +74,13 @@ export class StatusbarEntryItem extends Disposable { // Add to parent this.container.appendChild(this.labelContainer); + // Beak Container + this.beakContainer = document.createElement('div'); + this.beakContainer.className = 'status-bar-beak-container'; + + // Add to parent + this.container.appendChild(this.beakContainer); + this.update(entry); } From f27f620e7797fa6f58629114ae80935c027eb39c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 22 Jul 2022 14:00:03 +0200 Subject: [PATCH 159/197] status - make color dynamic (#155944) --- .../browser/parts/statusbar/media/statusbarpart.css | 5 ++--- src/vs/workbench/browser/parts/statusbar/statusbarItem.ts | 6 +----- src/vs/workbench/browser/parts/statusbar/statusbarPart.ts | 2 +- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index 24b22dd7c60..239a92d2f9a 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -51,7 +51,7 @@ position: relative; } -.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak > .status-bar-beak-container { +.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak > .status-bar-item-beak-container { position: absolute; left: calc(50% - 5px); /* centering relative to parent */ top: -5px; @@ -59,10 +59,9 @@ height: 5px; } -.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak > .status-bar-beak-container:before { +.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak > .status-bar-item-beak-container:before { content: ''; position: fixed; - color: rgb(0, 122, 204); border-bottom-width: 5px; border-bottom-style: solid; border-left: 5px solid transparent; diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts index faae3589cc2..acad14f1802 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts @@ -70,15 +70,11 @@ export class StatusbarEntryItem extends Disposable { // Label (with support for progress) this.label = new StatusBarCodiconLabel(this.labelContainer); - - // Add to parent this.container.appendChild(this.labelContainer); // Beak Container this.beakContainer = document.createElement('div'); - this.beakContainer.className = 'status-bar-beak-container'; - - // Add to parent + this.beakContainer.className = 'status-bar-item-beak-container'; this.container.appendChild(this.beakContainer); this.update(entry); diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 607ba09354b..ed738ef9fc9 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -544,7 +544,7 @@ export class StatusbarPart extends Part implements IStatusbarService { } /* Notification Beak */ - .monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak:before { + .monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak > .status-bar-item-beak-container:before { border-bottom-color: ${backgroundColor}; } `; From c6cf41b83b71188841a4840292bdb2a926b6e4e0 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 22 Jul 2022 14:01:49 +0200 Subject: [PATCH 160/197] (temp fix) detect when base, input1, and input2 become empty (#155941) When aborting or merging while the 3wm editor is showing all its inputs become empty and our reaction is to replace the merge editor then https://github.com/microsoft/vscode/issues/155940 --- .../mergeEditor/browser/view/mergeEditor.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index 84c2b3f80b1..b17a1e8eebf 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -369,6 +369,45 @@ export class MergeEditor extends AbstractTextEditor { } }); }, 'update alignment view zones')); + + + // detect when base, input1, and input2 become empty and replace THIS editor with its result editor + // TODO@jrieken@hediet this needs a better/cleaner solution + // https://github.com/microsoft/vscode/issues/155940 + const that = this; + this._sessionDisposables.add(new class { + + private readonly _disposable = new DisposableStore(); + + constructor() { + for (const model of this.baseInput1Input2()) { + this._disposable.add(model.onDidChangeContent(() => this._checkBaseInput1Input2AllEmpty())); + } + } + + dispose() { + this._disposable.dispose(); + } + + private *baseInput1Input2() { + yield model.base; + yield model.input1; + yield model.input2; + } + + private _checkBaseInput1Input2AllEmpty() { + for (const model of this.baseInput1Input2()) { + if (model.getValueLength() > 0) { + return; + } + } + // all empty -> replace this editor with a normal editor for result + that.editorService.replaceEditors( + [{ editor: input, replacement: { resource: input.result }, forceReplaceDirty: true }], + that.group ?? that.editorGroupService.activeGroup + ); + } + }); } override setOptions(options: ITextEditorOptions | undefined): void { From 052f02175f4752c36024c18cfbca4e13403e10c3 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 22 Jul 2022 14:06:10 +0200 Subject: [PATCH 161/197] Moves the read-only message into its own contribution to enable customization in the monaco editor. (#155951) --- .../message/browser/messageController.ts | 13 ------- .../readOnlyMessage/browser/contribution.ts | 36 +++++++++++++++++++ src/vs/editor/editor.all.ts | 1 + 3 files changed, 37 insertions(+), 13 deletions(-) create mode 100644 src/vs/editor/contrib/readOnlyMessage/browser/contribution.ts diff --git a/src/vs/editor/contrib/message/browser/messageController.ts b/src/vs/editor/contrib/message/browser/messageController.ts index b69ceb315e7..04177380066 100644 --- a/src/vs/editor/contrib/message/browser/messageController.ts +++ b/src/vs/editor/contrib/message/browser/messageController.ts @@ -32,7 +32,6 @@ export class MessageController implements IEditorContribution { private readonly _visible: IContextKey; private readonly _messageWidget = new MutableDisposable(); private readonly _messageListeners = new DisposableStore(); - private readonly _editorListener: IDisposable; constructor( editor: ICodeEditor, @@ -41,11 +40,9 @@ export class MessageController implements IEditorContribution { this._editor = editor; this._visible = MessageController.MESSAGE_VISIBLE.bindTo(contextKeyService); - this._editorListener = this._editor.onDidAttemptReadOnlyEdit(() => this._onDidAttemptReadOnlyEdit()); } dispose(): void { - this._editorListener.dispose(); this._messageListeners.dispose(); this._messageWidget.dispose(); this._visible.reset(); @@ -98,16 +95,6 @@ export class MessageController implements IEditorContribution { this._messageListeners.add(MessageWidget.fadeOut(this._messageWidget.value)); } } - - private _onDidAttemptReadOnlyEdit(): void { - if (this._editor.hasModel()) { - if (this._editor.isSimpleWidget) { - this.showMessage(nls.localize('editor.simple.readonly', "Cannot edit in read-only input"), this._editor.getPosition()); - } else { - this.showMessage(nls.localize('editor.readonly', "Cannot edit in read-only editor"), this._editor.getPosition()); - } - } - } } const MessageCommand = EditorCommand.bindToContribution(MessageController.get); diff --git a/src/vs/editor/contrib/readOnlyMessage/browser/contribution.ts b/src/vs/editor/contrib/readOnlyMessage/browser/contribution.ts new file mode 100644 index 00000000000..c4df6086148 --- /dev/null +++ b/src/vs/editor/contrib/readOnlyMessage/browser/contribution.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { MessageController } from 'vs/editor/contrib/message/browser/messageController'; +import * as nls from 'vs/nls'; + +export class ReadOnlyMessageController extends Disposable implements IEditorContribution { + + public static readonly ID = 'editor.contrib.readOnlyMessageController'; + + constructor( + private readonly editor: ICodeEditor + ) { + super(); + this._register(this.editor.onDidAttemptReadOnlyEdit(() => this._onDidAttemptReadOnlyEdit())); + } + + private _onDidAttemptReadOnlyEdit(): void { + const messageController = MessageController.get(this.editor); + if (messageController && this.editor.hasModel()) { + if (this.editor.isSimpleWidget) { + messageController.showMessage(nls.localize('editor.simple.readonly', "Cannot edit in read-only input"), this.editor.getPosition()); + } else { + messageController.showMessage(nls.localize('editor.readonly', "Cannot edit in read-only editor"), this.editor.getPosition()); + } + } + } +} + +registerEditorContribution(ReadOnlyMessageController.ID, ReadOnlyMessageController); diff --git a/src/vs/editor/editor.all.ts b/src/vs/editor/editor.all.ts index 1168ef7bb16..a33146a005f 100644 --- a/src/vs/editor/editor.all.ts +++ b/src/vs/editor/editor.all.ts @@ -53,6 +53,7 @@ import 'vs/editor/contrib/viewportSemanticTokens/browser/viewportSemanticTokens' import 'vs/editor/contrib/wordHighlighter/browser/wordHighlighter'; import 'vs/editor/contrib/wordOperations/browser/wordOperations'; import 'vs/editor/contrib/wordPartOperations/browser/wordPartOperations'; +import 'vs/editor/contrib/readOnlyMessage/browser/contribution'; // Load up these strings even in VSCode, even if they are not used // in order to get them translated From 5f21c372de5506c8916f3dd1ce9344bfcab85ef5 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 22 Jul 2022 14:07:44 +0200 Subject: [PATCH 162/197] Make treeshaking work on windows (#155952) --- build/lib/treeshaking.js | 2 ++ build/lib/treeshaking.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index 8fc5930c024..9a11c6fd9cc 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -89,6 +89,8 @@ function discoverAndReadFiles(ts, options) { const in_queue = Object.create(null); const queue = []; const enqueue = (moduleId) => { + // To make the treeshaker work on windows... + moduleId = moduleId.replace(/\\/g, '/'); if (in_queue[moduleId]) { return; } diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index 1d2e1bd4381..eb4f6f7767a 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -142,6 +142,8 @@ function discoverAndReadFiles(ts: typeof import('typescript'), options: ITreeSha const queue: string[] = []; const enqueue = (moduleId: string) => { + // To make the treeshaker work on windows... + moduleId = moduleId.replace(/\\/g, '/'); if (in_queue[moduleId]) { return; } From e5e0f9a149fb838ac4a78b66ca6c1122621b2aae Mon Sep 17 00:00:00 2001 From: mingwiki Date: Sat, 12 Mar 2022 21:38:11 +0800 Subject: [PATCH 163/197] Fix Socks5 Proxy Regex Checking Warning --- src/vs/platform/request/common/request.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/request/common/request.ts b/src/vs/platform/request/common/request.ts index 15f9d6d94d0..ede2afcf7f3 100644 --- a/src/vs/platform/request/common/request.ts +++ b/src/vs/platform/request/common/request.ts @@ -87,7 +87,7 @@ function registerProxyConfigurations(scope: ConfigurationScope): void { properties: { 'http.proxy': { type: 'string', - pattern: '^https?://([^:]*(:[^@]*)?@)?([^:]+|\\[[:0-9a-fA-F]+\\])(:\\d+)?/?$|^$', + pattern: '^(https?|socks5?)://([^:]*(:[^@]*)?@)?([^:]+|\\[[:0-9a-fA-F]+\\])(:\\d+)?/?$|^$', markdownDescription: localize('proxy', "The proxy setting to use. If not set, will be inherited from the `http_proxy` and `https_proxy` environment variables."), restricted: true }, From 1f0d221e24ccfd1aa4236ec67a958f789ed660be Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 22 Jul 2022 14:38:11 +0200 Subject: [PATCH 164/197] fix uninstalling extension version (#155953) --- .../common/abstractExtensionManagementService.ts | 6 +++--- .../common/extensionsScannerService.ts | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index adb137c66f7..bdb963ec6d2 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -101,7 +101,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl async uninstall(extension: ILocalExtension, options: ServerUninstallOptions = {}): Promise { this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id); - return this.unininstallExtension(extension, options); + return this.uninstallExtension(extension, options); } async reinstallFromGallery(extension: ILocalExtension): Promise { @@ -423,8 +423,8 @@ export abstract class AbstractExtensionManagementService extends Disposable impl return compatibleExtension; } - private async unininstallExtension(extension: ILocalExtension, options: ServerUninstallOptions): Promise { - const getUninstallExtensionTaskKey = (identifier: IExtensionIdentifier) => `${identifier.id.toLowerCase()}${options.profileLocation ? `@${options.profileLocation.toString()}` : ''}`; + private async uninstallExtension(extension: ILocalExtension, options: ServerUninstallOptions): Promise { + const getUninstallExtensionTaskKey = (identifier: IExtensionIdentifier) => `${identifier.id.toLowerCase()}${options.versionOnly ? `-${extension.manifest.version}` : ''}${options.profileLocation ? `@${options.profileLocation.toString()}` : ''}`; const uninstallExtensionTask = this.uninstallingExtensions.get(getUninstallExtensionTaskKey(extension.identifier)); if (uninstallExtensionTask) { this.logService.info('Extensions is already requested to uninstall', extension.identifier.id); diff --git a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts index 115348c255f..b51075daec1 100644 --- a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts +++ b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts @@ -278,7 +278,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem } private dedupExtensions(system: IScannedExtension[] | undefined, user: IScannedExtension[] | undefined, development: IScannedExtension[] | undefined, targetPlatform: TargetPlatform, pickLatest: boolean): IScannedExtension[] { - const pick = (existing: IScannedExtension, extension: IScannedExtension): boolean => { + const pick = (existing: IScannedExtension, extension: IScannedExtension, isDevelopment: boolean): boolean => { if (existing.isValid && !extension.isValid) { return false; } @@ -298,10 +298,10 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem } } } - if (existing.type === ExtensionType.System) { - this.logService.debug(`Overwriting system extension ${existing.location.path} with ${extension.location.path}.`); - } else { + if (isDevelopment) { this.logService.warn(`Overwriting user extension ${existing.location.path} with ${extension.location.path}.`); + } else { + this.logService.debug(`Overwriting user extension ${existing.location.path} with ${extension.location.path}.`); } return true; }; @@ -309,7 +309,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem system?.forEach((extension) => { const extensionKey = ExtensionIdentifier.toKey(extension.identifier.id); const existing = result.get(extensionKey); - if (!existing || pick(existing, extension)) { + if (!existing || pick(existing, extension, false)) { result.set(extensionKey, extension); } }); @@ -320,14 +320,14 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem this.logService.debug(`Skipping obsolete system extension ${extension.location.path}.`); return; } - if (!existing || pick(existing, extension)) { + if (!existing || pick(existing, extension, false)) { result.set(extensionKey, extension); } }); development?.forEach(extension => { const extensionKey = ExtensionIdentifier.toKey(extension.identifier.id); const existing = result.get(extensionKey); - if (!existing || pick(existing, extension)) { + if (!existing || pick(existing, extension, true)) { result.set(extensionKey, extension); } result.set(extensionKey, extension); From ba8b7694eece504e2fcab8d64a68b3ccc5ca8d4b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 22 Jul 2022 05:42:34 -0700 Subject: [PATCH 165/197] Fix space folder link test Fixes #155532 See #155921 --- .../browser/links/terminalLinkOpeners.ts | 22 +++--- .../browser/links/terminalLinkOpeners.test.ts | 79 +++++++++++++------ 2 files changed, 68 insertions(+), 33 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts index 0e6e5347e90..b087c9d94df 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts @@ -52,21 +52,21 @@ export class TerminalLocalFileLinkOpener implements ITerminalLinkOpener { * * @param link Url link which may contain line and column number. */ - extractLineColumnInfo(link: string, uri?: URI): ILineColumnInfo { + extractLineColumnInfo(link: string, uri: URI): ILineColumnInfo { const lineColumnInfo: ILineColumnInfo = { lineNumber: 1, columnNumber: 1 }; - // If a URI was passed in the exact file is known, sanitize the link text such that the - // folders and file name do not contain whitespace. The actual path isn't important in - // extracting the line and column from the regex so this is safe - if (uri) { - const fileName = basename(uri.path); - const index = link.indexOf(fileName); - const endIndex = index + fileName.length; - link = link.slice(0, endIndex).replace(/\s/g, '_') + link.slice(endIndex); - } + // Calculate the file name end using the URI if possible, this will help with sanitizing the + // link for the match regex. The actual path isn't important in extracting the line and + // column from the regex so modifying the link text before the file name is safe. + const fileName = basename(uri.path); + const index = link.indexOf(fileName); + const fileNameEndIndex: number = index !== -1 ? index + fileName.length : link.length; + + // Sanitize the link text such that the folders and file name do not contain whitespace. + link = link.slice(0, fileNameEndIndex).replace(/\s/g, '_') + link.slice(fileNameEndIndex); // The local link regex only works for non file:// links, check these for a simple // `:line:col` suffix @@ -259,7 +259,7 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener { const { uri, isDirectory } = result; const linkToOpen = { // Use the absolute URI's path here so the optional line/col get detected - text: result.uri.fsPath + (text.match(/:\d+(:\d+)?$/)?.[0] || ''), + text: result.uri.path + (text.match(/:\d+(:\d+)?$/)?.[0] || ''), uri, bufferRange: link.bufferRange, type: link.type diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts index 7b368639dfc..aa801c53f36 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts @@ -170,28 +170,6 @@ suite('Workbench - TerminalLinkOpeners', () => { }); }); - test.skip('should extract line and column from links in a workspace containing spaces', async () => { - localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux); - const localFolderOpener = instantiationService.createInstance(TerminalLocalFolderInWorkspaceLinkOpener); - opener = instantiationService.createInstance(TerminalSearchLinkOpener, capabilities, Promise.resolve('/space folder'), localFileOpener, localFolderOpener, OperatingSystem.Linux); - fileService.setFiles([ - URI.from({ scheme: Schemas.file, path: '/space folder/foo/bar.txt' }) - ]); - await opener.open({ - text: './foo/bar.txt:10:5', - bufferRange: { start: { x: 1, y: 1 }, end: { x: 8, y: 1 } }, - type: TerminalBuiltinLinkType.Search - }); - deepStrictEqual(activationResult, { - link: 'file:///space%20folder/foo/bar.txt', - source: 'editor', - selection: { - startColumn: 5, - startLineNumber: 10 - }, - }); - }); - suite('macOS/Linux', () => { setup(() => { localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux); @@ -239,6 +217,28 @@ suite('Workbench - TerminalLinkOpeners', () => { source: 'search' }); }); + + test('should extract line and column from links in a workspace containing spaces', async () => { + localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux); + const localFolderOpener = instantiationService.createInstance(TerminalLocalFolderInWorkspaceLinkOpener); + opener = instantiationService.createInstance(TerminalSearchLinkOpener, capabilities, Promise.resolve('/space folder'), localFileOpener, localFolderOpener, OperatingSystem.Linux); + fileService.setFiles([ + URI.from({ scheme: Schemas.file, path: '/space folder/foo/bar.txt' }) + ]); + await opener.open({ + text: './foo/bar.txt:10:5', + bufferRange: { start: { x: 1, y: 1 }, end: { x: 8, y: 1 } }, + type: TerminalBuiltinLinkType.Search + }); + deepStrictEqual(activationResult, { + link: 'file:///space%20folder/foo/bar.txt', + source: 'editor', + selection: { + startColumn: 5, + startLineNumber: 10 + }, + }); + }); }); suite('Windows', () => { @@ -288,6 +288,41 @@ suite('Workbench - TerminalLinkOpeners', () => { source: 'search' }); }); + + test('should extract line and column from links in a workspace containing spaces', async () => { + localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Windows); + const localFolderOpener = instantiationService.createInstance(TerminalLocalFolderInWorkspaceLinkOpener); + opener = instantiationService.createInstance(TerminalSearchLinkOpener, capabilities, Promise.resolve('/space folder'), localFileOpener, localFolderOpener, OperatingSystem.Windows); + fileService.setFiles([ + URI.from({ scheme: Schemas.file, path: '/space folder/foo/bar.txt' }) + ]); + await opener.open({ + text: './foo/bar.txt:10:5', + bufferRange: { start: { x: 1, y: 1 }, end: { x: 8, y: 1 } }, + type: TerminalBuiltinLinkType.Search + }); + deepStrictEqual(activationResult, { + link: 'file:///space%20folder/foo/bar.txt', + source: 'editor', + selection: { + startColumn: 5, + startLineNumber: 10 + }, + }); + await opener.open({ + text: '.\\foo\\bar.txt:10:5', + bufferRange: { start: { x: 1, y: 1 }, end: { x: 8, y: 1 } }, + type: TerminalBuiltinLinkType.Search + }); + deepStrictEqual(activationResult, { + link: 'file:///space%20folder/foo/bar.txt', + source: 'editor', + selection: { + startColumn: 5, + startLineNumber: 10 + }, + }); + }); }); }); }); From 44e3fbaa017da9c54b1e652358ccf2a662e9253a Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 22 Jul 2022 14:45:49 +0200 Subject: [PATCH 166/197] install language pack when setting locale (#155954) install lang pack while setting locale --- .../extensions/browser/extensionsWorkbenchService.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 6cf90434c7b..61b55355816 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -1277,6 +1277,17 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (locale === language) { return; } + + // Uninstall existing language pacjk extensions + const languagePackExtensions = this.local.filter(e => e.local && e.gallery && this.languagePackService.getLocale(e.gallery)); + if (languagePackExtensions.length) { + await Promise.all(languagePackExtensions.map(e => this.uninstall(e))); + } + + // Install the extension + await this.install(extension, { isMachineScoped: false }); + + // Set the locale return this.localeService.setLocale({ id: locale, galleryExtension: extension.gallery, extensionId: extension.identifier.id, label: extension.displayName }); } From cccbfcdd57894d168dae919b74950430aa0433f2 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 22 Jul 2022 14:54:10 +0200 Subject: [PATCH 167/197] support removing multiple profiles (#155958) * fix #154178 * fix compilation --- .../browser/preferences.contribution.ts | 5 ++-- .../browser/userDataProfile.ts | 17 ++++++++------ .../common/userDataProfileActions.ts | 23 +++++++++++++------ .../browser/userDataProfileManagement.ts | 11 ++++----- .../userDataProfile/common/userDataProfile.ts | 4 +++- 5 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index e42747beb9d..55c802041c0 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -43,9 +43,8 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { KeybindingsEditorInput } from 'vs/workbench/services/preferences/browser/keybindingsEditorInput'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; -import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IUserDataProfileService, CURRENT_PROFILE_CONTEXT } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { CONTEXT_CURRENT_PROFILE } from 'vs/workbench/contrib/userDataProfile/browser/userDataProfile'; const SETTINGS_EDITOR_COMMAND_SEARCH = 'settings.action.search'; @@ -236,7 +235,7 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon category, menu: { id: MenuId.CommandPalette, - when: ContextKeyExpr.notEquals(CONTEXT_CURRENT_PROFILE.key, that.userDataProfilesService.defaultProfile.id) + when: ContextKeyExpr.notEquals(CURRENT_PROFILE_CONTEXT.key, that.userDataProfilesService.defaultProfile.id) } }); } diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 2de14ece023..1a9933261f6 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -10,7 +10,7 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { Action2, ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; -import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; @@ -21,15 +21,14 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; -import { IUserDataProfileManagementService, IUserDataProfileService, ManageProfilesSubMenu, PROFILES_CATEGORY, PROFILES_ENABLEMENT_CONTEXT, PROFILES_TTILE } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; - -export const CONTEXT_CURRENT_PROFILE = new RawContextKey('currentUserDataProfile', ''); +import { CURRENT_PROFILE_CONTEXT, HAS_PROFILES_CONTEXT, IUserDataProfileManagementService, IUserDataProfileService, ManageProfilesSubMenu, PROFILES_CATEGORY, PROFILES_ENABLEMENT_CONTEXT, PROFILES_TTILE } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; export const userDataProfilesIcon = registerIcon('settingsProfiles-icon', Codicon.settings, localize('settingsProfilesIcon', 'Icon for Settings Profiles.')); export class UserDataProfilesWorkbenchContribution extends Disposable implements IWorkbenchContribution { private readonly currentProfileContext: IContextKey; + private readonly hasProfilesContext: IContextKey; constructor( @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, @@ -44,9 +43,13 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements this.registerConfiguration(); - this.currentProfileContext = CONTEXT_CURRENT_PROFILE.bindTo(contextKeyService); + this.currentProfileContext = CURRENT_PROFILE_CONTEXT.bindTo(contextKeyService); this.currentProfileContext.set(this.userDataProfileService.currentProfile.id); - this._register(this.userDataProfileService.onDidChangeCurrentProfile(() => this.currentProfileContext.set(this.userDataProfileService.currentProfile.id))); + this._register(this.userDataProfileService.onDidChangeCurrentProfile(e => this.currentProfileContext.set(this.userDataProfileService.currentProfile.id))); + + this.hasProfilesContext = HAS_PROFILES_CONTEXT.bindTo(contextKeyService); + this.hasProfilesContext.set(this.userDataProfilesService.profiles.length > 1); + this._register(this.userDataProfilesService.onDidChangeProfiles(e => this.hasProfilesContext.set(this.userDataProfilesService.profiles.length > 1))); this.updateStatus(); this._register(Event.any(this.workspaceContextService.onDidChangeWorkbenchState, this.userDataProfileService.onDidChangeCurrentProfile, this.userDataProfilesService.onDidChangeProfiles)(() => this.updateStatus())); @@ -117,7 +120,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements super({ id: `workbench.profiles.actions.profileEntry.${profile.id}`, title: profile.name, - toggled: ContextKeyExpr.equals(CONTEXT_CURRENT_PROFILE.key, profile.id), + toggled: ContextKeyExpr.equals(CURRENT_PROFILE_CONTEXT.key, profile.id), menu: [ { id: ManageProfilesSubMenu, diff --git a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts index 01a3ef3d346..80e2c378893 100644 --- a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts +++ b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts @@ -14,12 +14,12 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { INotificationService } from 'vs/platform/notification/common/notification'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { asJson, asText, IRequestService } from 'vs/platform/request/common/request'; -import { IUserDataProfileTemplate, isUserDataProfileTemplate, IUserDataProfileManagementService, IUserDataProfileImportExportService, PROFILES_CATEGORY, PROFILE_EXTENSION, PROFILE_FILTER, ManageProfilesSubMenu, IUserDataProfileService, PROFILES_ENABLEMENT_CONTEXT } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IUserDataProfileTemplate, isUserDataProfileTemplate, IUserDataProfileManagementService, IUserDataProfileImportExportService, PROFILES_CATEGORY, PROFILE_EXTENSION, PROFILE_FILTER, ManageProfilesSubMenu, IUserDataProfileService, PROFILES_ENABLEMENT_CONTEXT, HAS_PROFILES_CONTEXT } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { CATEGORIES } from 'vs/workbench/common/actions'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; registerAction2(class CreateFromCurrentProfileAction extends Action2 { constructor() { @@ -101,7 +101,7 @@ registerAction2(class RemoveProfileAction extends Action2 { }, category: PROFILES_CATEGORY, f1: true, - precondition: PROFILES_ENABLEMENT_CONTEXT, + precondition: ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, HAS_PROFILES_CONTEXT), menu: [ { id: ManageProfilesSubMenu, @@ -119,12 +119,21 @@ registerAction2(class RemoveProfileAction extends Action2 { const userDataProfileManagementService = accessor.get(IUserDataProfileManagementService); const notificationService = accessor.get(INotificationService); - const profiles = userDataProfilesService.profiles.filter(p => p.id !== userDataProfileService.currentProfile.id && !p.isDefault); + const profiles = userDataProfilesService.profiles.filter(p => !p.isDefault); if (profiles.length) { - const pick = await quickInputService.pick(profiles.map(profile => ({ label: profile.name, profile })), { placeHolder: localize('pick profile', "Select Settings Profile") }); - if (pick) { + const picks = await quickInputService.pick( + profiles.map(profile => ({ + label: profile.name, + description: profile.id === userDataProfileService.currentProfile.id ? localize('current', "Current") : undefined, + profile + })), + { + placeHolder: localize('pick profile', "Select Settings Profile"), + canPickMany: true + }); + if (picks) { try { - await userDataProfileManagementService.removeProfile(pick.profile); + await Promise.all(picks.map(pick => userDataProfileManagementService.removeProfile(pick.profile))); } catch (error) { notificationService.error(error); } diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts index 3b1578dc366..ce1983fec64 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts @@ -32,7 +32,7 @@ export class UserDataProfileManagementService extends Disposable implements IUse private onDidChangeProfiles(e: DidChangeProfilesEvent): void { if (e.removed.some(profile => profile.id === this.userDataProfileService.currentProfile.id)) { - this.enterProfile(this.userDataProfilesService.defaultProfile, false, localize('reload message when removed', "The current profile has been removed. Please reload to switch back to default profile")); + this.enterProfile(this.userDataProfilesService.defaultProfile, false, localize('reload message when removed', "The current settings profile has been removed. Please reload to switch back to default settings profile")); return; } } @@ -45,13 +45,10 @@ export class UserDataProfileManagementService extends Disposable implements IUse async removeProfile(profile: IUserDataProfile): Promise { if (!this.userDataProfilesService.profiles.some(p => p.id === profile.id)) { - throw new Error(`Profile ${profile.name} does not exist`); + throw new Error(`Settings profile ${profile.name} does not exist`); } if (profile.isDefault) { - throw new Error(localize('cannotDeleteDefaultProfile', "Cannot delete the default profile")); - } - if (profile.id === this.userDataProfileService.currentProfile.id) { - throw new Error(localize('cannotDeleteCurrentProfile', "Cannot delete the current profile")); + throw new Error(localize('cannotDeleteDefaultProfile', "Cannot delete the default settings profile")); } await this.userDataProfilesService.removeProfile(profile); } @@ -83,7 +80,7 @@ export class UserDataProfileManagementService extends Disposable implements IUse if (this.environmentService.remoteAuthority) { const result = await this.dialogService.confirm({ type: 'info', - message: reloadMessage ?? localize('reload message', "Switching a profile requires reloading VS Code."), + message: reloadMessage ?? localize('reload message', "Switching a settings profile requires reloading VS Code."), primaryButton: localize('reload button', "&&Reload"), }); if (result.confirmed) { diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index 6e09fd1d153..3db887030d9 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -9,7 +9,7 @@ import { localize } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IUserDataProfile, PROFILES_ENABLEMENT_CONFIG, UseDefaultProfileFlags } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { ContextKeyDefinedExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyDefinedExpr, ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ProductQualityContext } from 'vs/platform/contextkey/common/contextkeys'; export interface DidChangeUserDataProfileEvent { @@ -74,3 +74,5 @@ export const PROFILES_CATEGORY = PROFILES_TTILE.value; export const PROFILE_EXTENSION = 'code-profile'; export const PROFILE_FILTER = [{ name: localize('profile', "Settings Profile"), extensions: [PROFILE_EXTENSION] }]; export const PROFILES_ENABLEMENT_CONTEXT = ContextKeyExpr.and(ProductQualityContext.notEqualsTo('stable'), ContextKeyDefinedExpr.create(`config.${PROFILES_ENABLEMENT_CONFIG}`)); +export const CURRENT_PROFILE_CONTEXT = new RawContextKey('currentSettingsProfile', ''); +export const HAS_PROFILES_CONTEXT = new RawContextKey('hasSettingsProfiles', false); From 5c6c8c42ae906872c5f5a2db98052c8a365aa231 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 22 Jul 2022 15:05:10 +0200 Subject: [PATCH 168/197] add `editor.snippets.codeActions.enabled` to control code action snippets, fyi @alexdima (#155959) --- .../browser/snippetCodeActionProvider.ts | 18 ++++++++++++++++-- .../snippets/browser/snippets.contribution.ts | 17 +++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCodeActionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCodeActionProvider.ts index 08afb39eba9..cef9225c933 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetCodeActionProvider.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetCodeActionProvider.ts @@ -11,6 +11,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/browser/types'; import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { ApplyFileSnippetAction } from 'vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets'; @@ -128,9 +129,22 @@ export class SnippetCodeActions implements IWorkbenchContribution { constructor( @IInstantiationService instantiationService: IInstantiationService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, + @IConfigurationService configService: IConfigurationService, ) { - this._store.add(languageFeaturesService.codeActionProvider.register('*', instantiationService.createInstance(SurroundWithSnippetCodeActionProvider))); - this._store.add(languageFeaturesService.codeActionProvider.register('*', instantiationService.createInstance(FileTemplateCodeActionProvider))); + + const setting = 'editor.snippets.codeActions.enabled'; + const sessionStore = new DisposableStore(); + const update = () => { + sessionStore.clear(); + if (configService.getValue(setting)) { + sessionStore.add(languageFeaturesService.codeActionProvider.register('*', instantiationService.createInstance(SurroundWithSnippetCodeActionProvider))); + sessionStore.add(languageFeaturesService.codeActionProvider.register('*', instantiationService.createInstance(FileTemplateCodeActionProvider))); + } + }; + + update(); + this._store.add(configService.onDidChangeConfiguration(e => e.affectsConfiguration(setting) && update())); + this._store.add(sessionStore); } dispose(): void { diff --git a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts index dfd4bb7e37a..4da8e2b191d 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts @@ -19,8 +19,10 @@ import { SnippetCodeActions } from 'vs/workbench/contrib/snippets/browser/snippe import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets'; import { SnippetsService } from 'vs/workbench/contrib/snippets/browser/snippetsService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import 'vs/workbench/contrib/snippets/browser/tabCompletion'; +import { editorConfigurationBaseNode } from 'vs/editor/common/config/editorConfigurationSchema'; // service registerSingleton(ISnippetsService, SnippetsService, true); @@ -36,6 +38,21 @@ registerAction2(ApplyFileSnippetAction); Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(SnippetCodeActions, LifecyclePhase.Restored); +// config +Registry + .as(Extensions.Configuration) + .registerConfiguration({ + ...editorConfigurationBaseNode, + 'properties': { + 'editor.snippets.codeActions.enabled': { + 'description': nls.localize('editor.snippets.codeActions.enabled', 'Controls if surround-with-snippets or file template snippets show as code actions.'), + 'type': 'boolean', + 'default': true + } + } + }); + + // schema const languageScopeSchemaId = 'vscode://schemas/snippets'; From 6567f3da698b7fef77a53ce02aaedd067890ec7d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 22 Jul 2022 15:29:44 +0200 Subject: [PATCH 169/197] Revert "install language pack when setting locale (#155954)" (#155963) This reverts commit 44e3fbaa017da9c54b1e652358ccf2a662e9253a. --- .../extensions/browser/extensionsWorkbenchService.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 61b55355816..6cf90434c7b 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -1277,17 +1277,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (locale === language) { return; } - - // Uninstall existing language pacjk extensions - const languagePackExtensions = this.local.filter(e => e.local && e.gallery && this.languagePackService.getLocale(e.gallery)); - if (languagePackExtensions.length) { - await Promise.all(languagePackExtensions.map(e => this.uninstall(e))); - } - - // Install the extension - await this.install(extension, { isMachineScoped: false }); - - // Set the locale return this.localeService.setLocale({ id: locale, galleryExtension: extension.gallery, extensionId: extension.identifier.id, label: extension.displayName }); } From a02eaffdb9441ba08a0f00c081c0d29fcec8c85e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 22 Jul 2022 06:35:12 -0700 Subject: [PATCH 170/197] Use actual return symbol unicode char for new line Fixes #155871 --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 7a7a6146c2c..09736ecd0b0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -850,7 +850,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { commandMap.add(executingCommand); } function formatLabel(label: string) { - return label.replace(/\r?\n/g, '\u21B5'); + return label.replace(/\r?\n/g, '\u23CE'); } if (commands && commands.length > 0) { for (const entry of commands) { From d902fec1d2e50ee4c9ec3db109dd54d6aec8a18c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 22 Jul 2022 15:42:52 +0200 Subject: [PATCH 171/197] Enhance settings profiles management (#155966) * fix #154178 * - Support renaming profile - Refactor profile actions * fix compilation * fix label --- .../contrib/extensionsCleaner.ts | 2 +- .../browser/userDataProfile.ts | 13 +- .../userDataProfile/common/userDataProfile.ts | 37 ++- .../electron-sandbox/userDataProfile.ts | 8 +- .../browser/userDataProfile.ts | 2 +- .../common/userDataProfileActions.ts | 218 +++++++++++++----- .../browser/userDataProfileManagement.ts | 10 + .../userDataProfile/common/userDataProfile.ts | 2 + .../common/userDataProfileService.ts | 12 +- .../test/browser/workbenchTestServices.ts | 1 + 10 files changed, 228 insertions(+), 77 deletions(-) diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts index 0116d6e8aa8..3de8633f588 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts @@ -59,7 +59,7 @@ class ProfileExtensionsCleaner extends Disposable { this.onDidChangeProfiles({ added: this.userDataProfilesService.profiles, removed: [], all: this.userDataProfilesService.profiles }); } - private async onDidChangeProfiles({ added, removed, all }: DidChangeProfilesEvent): Promise { + private async onDidChangeProfiles({ added, removed, all }: Omit): Promise { try { await Promise.all(removed.map(profile => profile.extensionsResource ? this.removeExtensionsFromProfile(profile.extensionsResource) : Promise.resolve())); } catch (error) { diff --git a/src/vs/platform/userDataProfile/browser/userDataProfile.ts b/src/vs/platform/userDataProfile/browser/userDataProfile.ts index b00f89a92b7..782a627770f 100644 --- a/src/vs/platform/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/browser/userDataProfile.ts @@ -31,7 +31,12 @@ export class BrowserUserDataProfilesService extends UserDataProfilesService impl this._register(this.changesBroadcastChannel.onDidReceiveData(changes => { try { this._profilesObject = undefined; - this._onDidChangeProfiles.fire({ added: changes.added.map(p => reviveProfile(p, this.profilesHome.scheme)), removed: changes.removed.map(p => reviveProfile(p, this.profilesHome.scheme)), all: this.profiles }); + this._onDidChangeProfiles.fire({ + added: changes.added.map(p => reviveProfile(p, this.profilesHome.scheme)), + removed: changes.removed.map(p => reviveProfile(p, this.profilesHome.scheme)), + updated: changes.updated.map(p => reviveProfile(p, this.profilesHome.scheme)), + all: this.profiles + }); } catch (error) {/* ignore */ } })); } @@ -54,9 +59,9 @@ export class BrowserUserDataProfilesService extends UserDataProfilesService impl return []; } - protected override triggerProfilesChanges(added: IUserDataProfile[], removed: IUserDataProfile[]) { - super.triggerProfilesChanges(added, removed); - this.changesBroadcastChannel.postData({ added, removed }); + protected override triggerProfilesChanges(added: IUserDataProfile[], removed: IUserDataProfile[], updated: IUserDataProfile[]) { + super.triggerProfilesChanges(added, removed, updated); + this.changesBroadcastChannel.postData({ added, removed, updated }); } protected override saveStoredProfiles(storedProfiles: StoredUserDataProfile[]): void { diff --git a/src/vs/platform/userDataProfile/common/userDataProfile.ts b/src/vs/platform/userDataProfile/common/userDataProfile.ts index 3bc2eb4ea3d..3d0c1ba1b4c 100644 --- a/src/vs/platform/userDataProfile/common/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/common/userDataProfile.ts @@ -19,6 +19,7 @@ import { ResourceMap } from 'vs/base/common/map'; import { IStringDictionary } from 'vs/base/common/collections'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { Promises } from 'vs/base/common/async'; +import { generateUuid } from 'vs/base/common/uuid'; /** * Flags to indicate whether to use the default profile or not. @@ -68,7 +69,7 @@ export const PROFILES_ENABLEMENT_CONFIG = 'workbench.experimental.settingsProfil export type EmptyWindowWorkspaceIdentifier = 'empty-window'; export type WorkspaceIdentifier = ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier | EmptyWindowWorkspaceIdentifier; -export type DidChangeProfilesEvent = { readonly added: IUserDataProfile[]; readonly removed: IUserDataProfile[]; readonly all: IUserDataProfile[] }; +export type DidChangeProfilesEvent = { readonly added: IUserDataProfile[]; readonly removed: IUserDataProfile[]; readonly updated: IUserDataProfile[]; readonly all: IUserDataProfile[] }; export type WillCreateProfileEvent = { profile: IUserDataProfile; @@ -91,6 +92,7 @@ export interface IUserDataProfilesService { readonly profiles: IUserDataProfile[]; createProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier): Promise; + updateProfile(profile: IUserDataProfile, name: string, useDefaultFlags?: UseDefaultProfileFlags): Promise; setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: WorkspaceIdentifier): Promise; getProfile(workspaceIdentifier: WorkspaceIdentifier): IUserDataProfile; removeProfile(profile: IUserDataProfile): Promise; @@ -240,7 +242,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf throw new Error(`Profile with name ${name} already exists`); } - const profile = toUserDataProfile(name, joinPath(this.profilesHome, hash(name).toString(16)), useDefaultFlags); + const profile = toUserDataProfile(name, joinPath(this.profilesHome, hash(generateUuid()).toString(16)), useDefaultFlags); await this.fileService.createFolder(profile.location); const joiners: Promise[] = []; @@ -252,7 +254,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf }); await Promises.settled(joiners); - this.updateProfiles([profile], []); + this.updateProfiles([profile], [], []); if (workspaceIdentifier) { await this.setProfileForWorkspace(profile, workspaceIdentifier); @@ -261,6 +263,22 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf return profile; } + async updateProfile(profileToUpdate: IUserDataProfile, name: string, useDefaultFlags?: UseDefaultProfileFlags): Promise { + if (!this.enabled) { + throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`); + } + + let profile = this.profiles.find(p => p.id === profileToUpdate.id); + if (!profile) { + throw new Error(`Profile '${profileToUpdate.name}' does not exist`); + } + + profile = toUserDataProfile(name, profile.location, useDefaultFlags); + this.updateProfiles([], [], [profile]); + + return profile; + } + async setProfileForWorkspace(profileToSet: IUserDataProfile, workspaceIdentifier: WorkspaceIdentifier): Promise { if (!this.enabled) { throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`); @@ -312,7 +330,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf } this.updateStoredProfileAssociations(); - this.updateProfiles([], [profile]); + this.updateProfiles([], [profile], []); try { if (this.profiles.length === 1) { @@ -325,24 +343,25 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf } } - private updateProfiles(added: IUserDataProfile[], removed: IUserDataProfile[]) { + private updateProfiles(added: IUserDataProfile[], removed: IUserDataProfile[], updated: IUserDataProfile[]) { const storedProfiles: StoredUserDataProfile[] = []; - for (const profile of [...this.profilesObject.profiles, ...added]) { + for (let profile of [...this.profilesObject.profiles, ...added]) { if (profile.isDefault) { continue; } if (removed.some(p => profile.id === p.id)) { continue; } + profile = updated.find(p => profile.id === p.id) ?? profile; storedProfiles.push({ location: profile.location, name: profile.name, useDefaultFlags: profile.useDefaultFlags }); } this.saveStoredProfiles(storedProfiles); this._profilesObject = undefined; - this.triggerProfilesChanges(added, removed); + this.triggerProfilesChanges(added, removed, updated); } - protected triggerProfilesChanges(added: IUserDataProfile[], removed: IUserDataProfile[]) { - this._onDidChangeProfiles.fire({ added, removed, all: this.profiles }); + protected triggerProfilesChanges(added: IUserDataProfile[], removed: IUserDataProfile[], updated: IUserDataProfile[]) { + this._onDidChangeProfiles.fire({ added, removed, updated, all: this.profiles }); } private updateWorkspaceAssociation(workspaceIdentifier: WorkspaceIdentifier, newProfile?: IUserDataProfile) { diff --git a/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts b/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts index 085e035b676..eb662f36339 100644 --- a/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts @@ -41,8 +41,9 @@ export class UserDataProfilesNativeService extends Disposable implements IUserDa this._register(this.channel.listen('onDidChangeProfiles')(e => { const added = e.added.map(profile => reviveProfile(profile, this.profilesHome.scheme)); const removed = e.removed.map(profile => reviveProfile(profile, this.profilesHome.scheme)); + const updated = e.updated.map(profile => reviveProfile(profile, this.profilesHome.scheme)); this._profiles = e.all.map(profile => reviveProfile(profile, this.profilesHome.scheme)); - this._onDidChangeProfiles.fire({ added, removed, all: this.profiles }); + this._onDidChangeProfiles.fire({ added, removed, updated, all: this.profiles }); })); } @@ -59,6 +60,11 @@ export class UserDataProfilesNativeService extends Disposable implements IUserDa return this.channel.call('removeProfile', [profile]); } + async updateProfile(profile: IUserDataProfile, name: string, useDefaultFlags?: UseDefaultProfileFlags): Promise { + const result = await this.channel.call>('updateProfile', [profile, name, useDefaultFlags]); + return reviveProfile(result, this.profilesHome.scheme); + } + getProfile(workspaceIdentifier: WorkspaceIdentifier): IUserDataProfile { throw new Error('Not implemented'); } } diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 1a9933261f6..324069eb10a 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -52,7 +52,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements this._register(this.userDataProfilesService.onDidChangeProfiles(e => this.hasProfilesContext.set(this.userDataProfilesService.profiles.length > 1))); this.updateStatus(); - this._register(Event.any(this.workspaceContextService.onDidChangeWorkbenchState, this.userDataProfileService.onDidChangeCurrentProfile, this.userDataProfilesService.onDidChangeProfiles)(() => this.updateStatus())); + this._register(Event.any(this.workspaceContextService.onDidChangeWorkbenchState, this.userDataProfileService.onDidChangeCurrentProfile, this.userDataProfileService.onDidUpdateCurrentProfile, this.userDataProfilesService.onDidChangeProfiles)(() => this.updateStatus())); this.registerActions(); } diff --git a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts index 80e2c378893..f1d27222b50 100644 --- a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts +++ b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts @@ -20,14 +20,85 @@ import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userData import { CATEGORIES } from 'vs/workbench/common/actions'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ICommandService } from 'vs/platform/commands/common/commands'; -registerAction2(class CreateFromCurrentProfileAction extends Action2 { +class CreateFromCurrentProfileAction extends Action2 { + static readonly ID = 'workbench.profiles.actions.createFromCurrentProfile'; + static readonly TITLE = { + value: localize('save profile as', "Create from Current Settings Profile..."), + original: 'Create from Current Profile...' + }; constructor() { super({ - id: 'workbench.profiles.actions.createFromCurrentProfile', + id: CreateFromCurrentProfileAction.ID, + title: CreateFromCurrentProfileAction.TITLE, + category: PROFILES_CATEGORY, + f1: true, + precondition: PROFILES_ENABLEMENT_CONTEXT + }); + } + + async run(accessor: ServicesAccessor) { + const quickInputService = accessor.get(IQuickInputService); + const notificationService = accessor.get(INotificationService); + const userDataProfileManagementService = accessor.get(IUserDataProfileManagementService); + const name = await quickInputService.input({ + placeHolder: localize('name', "Profile name"), + title: localize('save profile as', "Create from Current Settings Profile..."), + }); + if (name) { + try { + await userDataProfileManagementService.createAndEnterProfile(name, undefined, true); + } catch (error) { + notificationService.error(error); + } + } + } +} +registerAction2(CreateFromCurrentProfileAction); + +class CreateEmptyProfileAction extends Action2 { + static readonly ID = 'workbench.profiles.actions.createEmptyProfile'; + static readonly TITLE = { + value: localize('create empty profile', "Create an Empty Settings Profile..."), + original: 'Create an Empty Settings Profile...' + }; + constructor() { + super({ + id: CreateEmptyProfileAction.ID, + title: CreateEmptyProfileAction.TITLE, + category: PROFILES_CATEGORY, + f1: true, + precondition: PROFILES_ENABLEMENT_CONTEXT + }); + } + + async run(accessor: ServicesAccessor) { + const quickInputService = accessor.get(IQuickInputService); + const userDataProfileManagementService = accessor.get(IUserDataProfileManagementService); + const notificationService = accessor.get(INotificationService); + const name = await quickInputService.input({ + placeHolder: localize('name', "Profile name"), + title: localize('create and enter empty profile', "Create an Empty Profile..."), + }); + if (name) { + try { + await userDataProfileManagementService.createAndEnterProfile(name); + } catch (error) { + notificationService.error(error); + } + } + } +} +registerAction2(CreateEmptyProfileAction); + +registerAction2(class CreateProfileAction extends Action2 { + constructor() { + super({ + id: 'workbench.profiles.actions.createProfile', title: { - value: localize('save profile as', "Create from Current Settings Profile..."), - original: 'Create from Current Profile...' + value: localize('create profile', "Create..."), + original: 'Create...' }, category: PROFILES_CATEGORY, f1: true, @@ -35,7 +106,7 @@ registerAction2(class CreateFromCurrentProfileAction extends Action2 { menu: [ { id: ManageProfilesSubMenu, - group: '1_create_profiles', + group: '2_manage_profiles', when: PROFILES_ENABLEMENT_CONTEXT, order: 1 } @@ -45,59 +116,28 @@ registerAction2(class CreateFromCurrentProfileAction extends Action2 { async run(accessor: ServicesAccessor) { const quickInputService = accessor.get(IQuickInputService); - const userDataProfileManagementService = accessor.get(IUserDataProfileManagementService); - const name = await quickInputService.input({ - placeHolder: localize('name', "Profile name"), - title: localize('save profile as', "Create from Current Settings Profile..."), - }); - if (name) { - await userDataProfileManagementService.createAndEnterProfile(name, undefined, true); + const commandService = accessor.get(ICommandService); + const pick = await quickInputService.pick( + [{ + id: CreateFromCurrentProfileAction.ID, + label: CreateFromCurrentProfileAction.TITLE.value, + }, { + id: CreateEmptyProfileAction.ID, + label: CreateEmptyProfileAction.TITLE.value, + }], { canPickMany: false, title: localize('create settings profile', "{0}: Create...", PROFILES_CATEGORY) }); + if (pick) { + return commandService.executeCommand(pick.id); } } }); -registerAction2(class CreateEmptyProfileAction extends Action2 { +registerAction2(class RenameProfileAction extends Action2 { constructor() { super({ - id: 'workbench.profiles.actions.createProfile', + id: 'workbench.profiles.actions.renameProfile', title: { - value: localize('create profile', "Create an Empty Settings Profile..."), - original: 'Create an Empty Profile...' - }, - category: PROFILES_CATEGORY, - f1: true, - precondition: PROFILES_ENABLEMENT_CONTEXT, - menu: [ - { - id: ManageProfilesSubMenu, - group: '1_create_profiles', - when: PROFILES_ENABLEMENT_CONTEXT, - order: 2 - } - ] - }); - } - - async run(accessor: ServicesAccessor) { - const quickInputService = accessor.get(IQuickInputService); - const userDataProfileManagementService = accessor.get(IUserDataProfileManagementService); - const name = await quickInputService.input({ - placeHolder: localize('name', "Profile name"), - title: localize('create and enter empty profile', "Create an Empty Profile..."), - }); - if (name) { - await userDataProfileManagementService.createAndEnterProfile(name); - } - } -}); - -registerAction2(class RemoveProfileAction extends Action2 { - constructor() { - super({ - id: 'workbench.profiles.actions.removeProfile', - title: { - value: localize('remove profile', "Remove Settings Profile..."), - original: 'Remove Profile...' + value: localize('rename profile', "Rename..."), + original: 'Rename...' }, category: PROFILES_CATEGORY, f1: true, @@ -106,7 +146,65 @@ registerAction2(class RemoveProfileAction extends Action2 { { id: ManageProfilesSubMenu, group: '2_manage_profiles', - when: PROFILES_ENABLEMENT_CONTEXT + when: PROFILES_ENABLEMENT_CONTEXT, + order: 1 + } + ] + }); + } + + async run(accessor: ServicesAccessor) { + const quickInputService = accessor.get(IQuickInputService); + const userDataProfileService = accessor.get(IUserDataProfileService); + const userDataProfilesService = accessor.get(IUserDataProfilesService); + const userDataProfileManagementService = accessor.get(IUserDataProfileManagementService); + const notificationService = accessor.get(INotificationService); + + const profiles = userDataProfilesService.profiles.filter(p => !p.isDefault); + if (profiles.length) { + const pick = await quickInputService.pick( + profiles.map(profile => ({ + label: profile.name, + description: profile.id === userDataProfileService.currentProfile.id ? localize('current', "Current") : undefined, + profile + })), + { + placeHolder: localize('pick profile to rename', "Select Settings Profile to Rename"), + }); + if (pick) { + const name = await quickInputService.input({ + value: pick.profile.name, + title: localize('edit settings profile', "Rename Settings Profile..."), + }); + if (name && name !== pick.profile.name) { + try { + await userDataProfileManagementService.renameProfile(pick.profile, name); + } catch (error) { + notificationService.error(error); + } + } + } + } + } +}); + +registerAction2(class DeleteProfileAction extends Action2 { + constructor() { + super({ + id: 'workbench.profiles.actions.deleteProfile', + title: { + value: localize('delete profile', "Delete..."), + original: 'Delete...' + }, + category: PROFILES_CATEGORY, + f1: true, + precondition: ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, HAS_PROFILES_CONTEXT), + menu: [ + { + id: ManageProfilesSubMenu, + group: '2_manage_profiles', + when: PROFILES_ENABLEMENT_CONTEXT, + order: 2 } ] }); @@ -128,7 +226,7 @@ registerAction2(class RemoveProfileAction extends Action2 { profile })), { - placeHolder: localize('pick profile', "Select Settings Profile"), + placeHolder: localize('pick profile to delete', "Select Settings Profiles to Delete"), canPickMany: true }); if (picks) { @@ -147,12 +245,12 @@ registerAction2(class SwitchProfileAction extends Action2 { super({ id: 'workbench.profiles.actions.switchProfile', title: { - value: localize('switch profile', "Switch Settings Profile..."), - original: 'Switch Settings Profile...' + value: localize('switch profile', "Switch..."), + original: 'Switch...' }, category: PROFILES_CATEGORY, f1: true, - precondition: PROFILES_ENABLEMENT_CONTEXT, + precondition: ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, HAS_PROFILES_CONTEXT), }); } @@ -207,8 +305,8 @@ registerAction2(class ExportProfileAction extends Action2 { super({ id: 'workbench.profiles.actions.exportProfile', title: { - value: localize('export profile', "Export Settings Profile..."), - original: 'Export Settings Profile...' + value: localize('export profile', "Export..."), + original: 'Export...' }, category: PROFILES_CATEGORY, menu: [ @@ -252,8 +350,8 @@ registerAction2(class ImportProfileAction extends Action2 { super({ id: 'workbench.profiles.actions.importProfile', title: { - value: localize('import profile', "Import Settings Profile..."), - original: 'Import Settings Profile...' + value: localize('import profile', "Import..."), + original: 'Import...' }, category: PROFILES_CATEGORY, menu: [ diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts index ce1983fec64..5fab982a11c 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts @@ -43,6 +43,16 @@ export class UserDataProfileManagementService extends Disposable implements IUse return profile; } + async renameProfile(profile: IUserDataProfile, name: string): Promise { + if (!this.userDataProfilesService.profiles.some(p => p.id === profile.id)) { + throw new Error(`Settings profile ${profile.name} does not exist`); + } + if (profile.isDefault) { + throw new Error(localize('cannotRenameDefaultProfile', "Cannot rename the default settings profile")); + } + await this.userDataProfilesService.updateProfile(profile, name); + } + async removeProfile(profile: IUserDataProfile): Promise { if (!this.userDataProfilesService.profiles.some(p => p.id === profile.id)) { throw new Error(`Settings profile ${profile.name} does not exist`); diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index 3db887030d9..054bf392034 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -22,6 +22,7 @@ export interface DidChangeUserDataProfileEvent { export const IUserDataProfileService = createDecorator('IUserDataProfileService'); export interface IUserDataProfileService { readonly _serviceBrand: undefined; + readonly onDidUpdateCurrentProfile: Event; readonly onDidChangeCurrentProfile: Event; readonly currentProfile: IUserDataProfile; updateCurrentProfile(currentProfile: IUserDataProfile, preserveData: boolean): Promise; @@ -33,6 +34,7 @@ export interface IUserDataProfileManagementService { createAndEnterProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, fromExisting?: boolean): Promise; removeProfile(profile: IUserDataProfile): Promise; + renameProfile(profile: IUserDataProfile, name: string): Promise; switchProfile(profile: IUserDataProfile): Promise; } diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts index e03387a0b29..8dc5549e0e9 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts @@ -16,6 +16,9 @@ export class UserDataProfileService extends Disposable implements IUserDataProfi private readonly _onDidChangeCurrentProfile = this._register(new Emitter()); readonly onDidChangeCurrentProfile = this._onDidChangeCurrentProfile.event; + private readonly _onDidUpdateCurrentProfile = this._register(new Emitter()); + readonly onDidUpdateCurrentProfile = this._onDidUpdateCurrentProfile.event; + private _currentProfile: IUserDataProfile; get currentProfile(): IUserDataProfile { return this._currentProfile; } @@ -25,13 +28,20 @@ export class UserDataProfileService extends Disposable implements IUserDataProfi ) { super(); this._currentProfile = currentProfile; - this._register(userDataProfilesService.onDidChangeProfiles(() => { + this._register(userDataProfilesService.onDidChangeProfiles(e => { /** * If the current profile is default profile, then reset it because, * In Desktop the extensions resource will be set/unset in the default profile when profiles are changed. */ if (this._currentProfile.isDefault) { this._currentProfile = userDataProfilesService.defaultProfile; + return; + } + + const updatedCurrentProfile = e.updated.find(p => this._currentProfile.id === p.id); + if (updatedCurrentProfile) { + this._currentProfile = updatedCurrentProfile; + this._onDidUpdateCurrentProfile.fire(); } })); } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index ff291f79a9f..de310cbfb0d 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -2009,6 +2009,7 @@ export class TestWorkbenchExtensionManagementService implements IWorkbenchExtens export class TestUserDataProfileService implements IUserDataProfileService { readonly _serviceBrand: undefined; + readonly onDidUpdateCurrentProfile = Event.None; readonly onDidChangeCurrentProfile = Event.None; readonly currentProfile = toUserDataProfile('test', URI.file('tests').with({ scheme: 'vscode-tests' })); async updateCurrentProfile(): Promise { } From 9e0de4af67842e45a53ea6c022494a3beac2a3bb Mon Sep 17 00:00:00 2001 From: Errietta Erry Kostala Date: Fri, 22 Jul 2022 14:55:53 +0100 Subject: [PATCH 172/197] Add .nvmrc (#152883) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since the contribution guidelines advise on a specific version of node, it adding .nvmrc for those that use nvm will automatically let them use the correct version of node if they do nvm use just a small QOL thing for devs. Co-authored-by: João Moreno --- .nvmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000000..0cf077e6b4c --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +16.14 From 688c30e498f16686cc0c681e9fdc65d770b2c69a Mon Sep 17 00:00:00 2001 From: John Murray Date: Fri, 22 Jul 2022 14:56:33 +0100 Subject: [PATCH 173/197] Warn user when they open a file within the install folder tree (#138815) (#155443) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Warn user when they open a file within the install folder tree (#138815) * Use IEditorService.onDidActiveEditorChange instead * Update src/vs/workbench/contrib/files/electron-sandbox/files.contribution.ts * :lipstick: * Move into existing editor change listener * add scope to neverShowAgain id * align neverShowAgain and message localize key with function name * :lipstick: * 'OK' -> 'Understood' Co-authored-by: João Moreno Co-authored-by: João Moreno Co-authored-by: Benjamin Pasero --- src/vs/workbench/electron-sandbox/window.ts | 54 ++++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index f044363676a..131d7741598 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -65,6 +65,7 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import { registerWindowDriver } from 'vs/platform/driver/electron-sandbox/driver'; import { ILabelService } from 'vs/platform/label/common/label'; import { dirname } from 'vs/base/common/resources'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; export class NativeWindow extends Disposable { @@ -115,7 +116,8 @@ export class NativeWindow extends Disposable { @IInstantiationService private readonly instantiationService: IInstantiationService, @ISharedProcessService private readonly sharedProcessService: ISharedProcessService, @IProgressService private readonly progressService: IProgressService, - @ILabelService private readonly labelService: ILabelService + @ILabelService private readonly labelService: ILabelService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService ) { super(); @@ -129,7 +131,15 @@ export class NativeWindow extends Disposable { this._register(addDisposableListener(window, EventType.RESIZE, e => this.onWindowResize(e, true))); // React to editor input changes - this._register(this.editorService.onDidActiveEditorChange(() => this.updateTouchbarMenu())); + const appRootUri = URI.file(this.environmentService.appRoot); + this._register(this.editorService.onDidActiveEditorChange(() => { + + // Touchbar + this.updateTouchbarMenu(); + + // Potential data loss + this.notifyOnAppRootEditors(appRootUri); + })); // prevent opening a real URL inside the window for (const event of [EventType.DRAG_OVER, EventType.DROP]) { @@ -795,6 +805,46 @@ export class NativeWindow extends Disposable { } } + private notifyOnAppRootEditors(appRootUri: URI): void { + const resourceUri = EditorResourceAccessor.getOriginalUri(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.BOTH }); + const isResourceAppRootedFn = (uri: URI): boolean => this.uriIdentityService.extUri.isEqualOrParent(uri, appRootUri); + let isResourceAppRooted = false; + if (URI.isUri(resourceUri)) { + if (isResourceAppRootedFn(resourceUri)) { + isResourceAppRooted = true; + } + } else if (resourceUri) { + if (resourceUri.primary && isResourceAppRootedFn(resourceUri.primary)) { + isResourceAppRooted = true; + } else if (resourceUri.secondary && isResourceAppRootedFn(resourceUri.secondary)) { + isResourceAppRooted = true; + } + } + + // It is dangerous to edit files in the installation directory of Code because + // an update will remove all files and replace them with the new version. + // As such, we notify the user whenever an editor opens that is located somewhere + // in the installation directory. + // https://github.com/microsoft/vscode/issues/138815 + + if (isResourceAppRooted) { + this.notificationService.prompt( + Severity.Warning, + localize('notifyOnAppRootEditors', "Files within the installation folder of '{0}' ({1}) will be OVERWRITTEN or DELETED IRREVERSIBLY without warning during a future update.", this.productService.nameShort, this.environmentService.appRoot), + [{ + label: localize('understood', 'Understood'), + run: async () => { + // Nothing to do + } + }], + { + neverShowAgain: { id: 'window.notifyOnAppRootEditors', isSecondary: true }, + sticky: true + } + ); + } + } + private onAddFoldersRequest(request: IAddFoldersRequest): void { // Buffer all pending requests From 3558758125976cdc5e15e1da4584ffef55c11f37 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 22 Jul 2022 16:02:28 +0200 Subject: [PATCH 174/197] remove "start with snippet" from new file hint until we have file template snippets, (#155961) https://github.com/microsoft/vscode/issues/145929 --- .../codeEditor/browser/untitledTextEditorHint.ts | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts b/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts index 99cd7d942c9..fa456018f48 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts @@ -108,7 +108,7 @@ class UntitledTextEditorHintContentWidget implements IContentWidget { this.domNode = $('.untitled-hint'); this.domNode.style.width = 'max-content'; - const hintMsg = localize({ key: 'message', comment: ['Presereve double-square brackets and their order'] }, '[[Select a language]], [[start with a snippet]], or [[open a different editor]] to get started.\nStart typing to dismiss or [[don\'t show]] this again.'); + const hintMsg = localize({ key: 'message', comment: ['Presereve double-square brackets and their order'] }, '[[Select a language]], or [[open a different editor]] to get started.\nStart typing to dismiss or [[don\'t show]] this again.'); const hintHandler: IContentActionHandler = { disposables: this.toDispose, callback: (index, event) => { @@ -117,12 +117,9 @@ class UntitledTextEditorHintContentWidget implements IContentWidget { languageOnClickOrTap(event.browserEvent); break; case '1': - snippetOnClickOrTab(event.browserEvent); - break; - case '2': chooseEditorOnClickOrTap(event.browserEvent); break; - case '3': + case '2': dontShowOnClickOrTap(); break; } @@ -153,12 +150,6 @@ class UntitledTextEditorHintContentWidget implements IContentWidget { this.editor.focus(); }; - const snippetOnClickOrTab = async (e: MouseEvent) => { - e.stopPropagation(); - this.editor.focus(); - this.commandService.executeCommand(ApplyFileSnippetAction.Id, { from: 'hint' }); - }; - const chooseEditorOnClickOrTap = async (e: MouseEvent) => { e.stopPropagation(); From 8b7a0d8c25f95e0f80e8932f115b5ee5fefa9b2e Mon Sep 17 00:00:00 2001 From: John Murray Date: Fri, 22 Jul 2022 15:10:53 +0100 Subject: [PATCH 175/197] Try to prevent update of user-type Windows installation when running as admin (#148428) (#155631) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Try to prevent update of user-type Windows installation when running as admin (#148428) * update win32: detect admin mode in initialize Co-authored-by: João Moreno --- src/vs/code/electron-main/app.ts | 2 +- .../update/electron-main/abstractUpdateService.ts | 2 +- .../update/electron-main/updateService.darwin.ts | 4 ++-- .../platform/update/electron-main/updateService.win32.ts | 9 +++++++++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 1da1db197b9..670685ffd9d 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -1181,7 +1181,7 @@ export class CodeApplication extends Disposable { // Initialize update service const updateService = accessor.get(IUpdateService); if (updateService instanceof Win32UpdateService || updateService instanceof LinuxUpdateService || updateService instanceof DarwinUpdateService) { - updateService.initialize(); + await updateService.initialize(); } // Start to fetch shell environment (if needed) after window has opened diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index ff13b16482e..0658b198141 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -59,7 +59,7 @@ export abstract class AbstractUpdateService implements IUpdateService { * optimization, to avoid using extra CPU cycles before first window open. * https://github.com/microsoft/vscode/issues/89784 */ - initialize(): void { + async initialize(): Promise { if (!this.environmentMainService.isBuilt) { return; // updates are never enabled when running out of sources } diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index 82802b4830f..c5a8502f8dc 100644 --- a/src/vs/platform/update/electron-main/updateService.darwin.ts +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -38,8 +38,8 @@ export class DarwinUpdateService extends AbstractUpdateService { super(lifecycleMainService, configurationService, environmentMainService, requestService, logService, productService); } - override initialize(): void { - super.initialize(); + override async initialize(): Promise { + await super.initialize(); this.onRawError(this.onError, this, this.disposables); this.onRawUpdateAvailable(this.onUpdateAvailable, this, this.disposables); this.onRawUpdateDownloaded(this.onUpdateDownloaded, this, this.disposables); diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index 2aaf4680bdd..caecd719334 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -71,6 +71,15 @@ export class Win32UpdateService extends AbstractUpdateService { super(lifecycleMainService, configurationService, environmentMainService, requestService, logService, productService); } + override async initialize(): Promise { + if (this.productService.target === 'user' && await this.nativeHostMainService.isAdmin(undefined)) { + this.logService.info('update#ctor - updates are disabled due to running as Admin in user setup'); + return; + } + + super.initialize(); + } + protected buildUpdateFeedUrl(quality: string): string | undefined { let platform = 'win32'; From 3832b5d451c779f10c153e00af0abe784ff34a36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 22 Jul 2022 16:50:47 +0200 Subject: [PATCH 176/197] icacls: grant current user permissions (#155852) --- build/win32/code.iss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/win32/code.iss b/build/win32/code.iss index e96ca4bde77..192bbb0d2ba 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -1494,7 +1494,7 @@ begin Permissions := '/grant:r "*S-1-5-18:(OI)(CI)F" /grant:r "*S-1-5-32-544:(OI)(CI)F" /grant:r "*S-1-5-11:(OI)(CI)RX" /grant:r "*S-1-5-32-545:(OI)(CI)RX"'; #if "user" == InstallTarget - Permissions := Permissions + ' /grant:r "*S-1-3-0:(OI)(CI)F"'; + Permissions := Permissions + Format(' /grant:r "*S-1-3-0:(OI)(CI)F" /grant:r "%s:(OI)(CI)F"', [GetUserNameString()]); #endif Exec(ExpandConstant('{sys}\icacls.exe'), ExpandConstant('"{app}" /inheritancelevel:r ') + Permissions, '', SW_HIDE, ewWaitUntilTerminated, ResultCode); From 4ee72207f37c5309d0ceecd08ca0c0b0908a823e Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Fri, 22 Jul 2022 00:30:03 -0700 Subject: [PATCH 177/197] fixed custom widget color theme to align with context menu --- src/vs/editor/contrib/codeAction/browser/media/action.css | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/media/action.css b/src/vs/editor/contrib/codeAction/browser/media/action.css index 504955b5844..b688e421e9e 100644 --- a/src/vs/editor/contrib/codeAction/browser/media/action.css +++ b/src/vs/editor/contrib/codeAction/browser/media/action.css @@ -17,7 +17,8 @@ border-style: solid; border-width: 1px; border-color: var(--vscode-editorSuggestWidget-border); - background-color: var(--vscode-editorSuggestWidget-background); + background-color: var(--vscode-menu-background); + color: var(--vscode-menu-foreground); } .codeActionMenuWidget .monaco-list:not(.element-focused):focus:before { @@ -41,7 +42,6 @@ -ms-user-select: none; border: none !important; border-width: 0px !important; - color: var(red) !important; } /* .codeActionMenuWidget .monaco-list:not(.element-focus) { @@ -74,6 +74,7 @@ .codeActionMenuWidget .monaco-list .monaco-list-row:hover:not(.option-disabled), .codeActionMenuWidget .monaco-list .moncao-list-row.focused:not(.option-disabled) { + color: var(--vscode-menu-selectionForeground) !important; background-color: var(--vscode-menu-selectionBackground) !important; } @@ -86,7 +87,7 @@ -moz-user-select: none; -ms-user-select: none; user-select: none; - color: var(--vscode-list-inactiveSelectionBackground) !important; + color: var(--vscode-disabledForeground) !important; } .codeActionMenuWidget .monaco-list .separator { From b05a122339778907f1d7c4f5a468c29ecdb5b7ee Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Fri, 22 Jul 2022 08:14:31 -0700 Subject: [PATCH 178/197] code action widget styling to match context menu --- .../editor/contrib/codeAction/browser/codeActionMenu.ts | 6 +++--- .../editor/contrib/codeAction/browser/media/action.css | 9 ++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 69f3ec06536..0b4b0d1039a 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -90,7 +90,7 @@ export interface ICodeActionMenuTemplateData { } const TEMPLATE_ID = 'codeActionWidget'; -const codeActionLineHeight = 27; +const codeActionLineHeight = 26; class CodeMenuRenderer implements IListRenderer { get templateId(): string { return TEMPLATE_ID; } @@ -256,8 +256,8 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { // resize observer - can be used in the future since list widget supports dynamic height but not width const maxWidth = Math.max(...arr); - // 40 is the additional padding for the list widget (20 left, 20 right) - renderMenu.style.width = maxWidth + 40 + 'px'; + // 40 is the additional padding for the list widget (26px left, 26px right) + renderMenu.style.width = maxWidth + 52 + 'px'; this.codeActionList.value?.layout(height, maxWidth); // List selection diff --git a/src/vs/editor/contrib/codeAction/browser/media/action.css b/src/vs/editor/contrib/codeAction/browser/media/action.css index b688e421e9e..4068820413d 100644 --- a/src/vs/editor/contrib/codeAction/browser/media/action.css +++ b/src/vs/editor/contrib/codeAction/browser/media/action.css @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ .codeActionMenuWidget { - padding: 10px 0px 10px 0px; + padding: 8px 0px 8px 0px; overflow: auto; font-size: 13px; border-radius: 5px; @@ -14,9 +14,8 @@ /* flex-direction: column; flex: 0 1 auto; */ width: 100%; - border-style: solid; - border-width: 1px; - border-color: var(--vscode-editorSuggestWidget-border); + border-width: 0px; + border-color: none; background-color: var(--vscode-menu-background); color: var(--vscode-menu-foreground); } @@ -62,7 +61,7 @@ display: flex; -mox-box-sizing: border-box; box-sizing: border-box; - padding: 0px 20px 0px 20px; + padding: 0px 26px 0px 26px; background-repeat: no-repeat; background-position: 2px 2px; white-space: nowrap; From 187ec0613a7929351e3ad6284ce091b777393529 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Fri, 22 Jul 2022 17:35:53 +0200 Subject: [PATCH 179/197] Skip folded regions when computing relative line numbers (#155970) Fixes #138787: Skip folded regions when computing relative line numbers --- .../viewParts/lineNumbers/lineNumbers.ts | 65 ++++++++++++++++--- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts index 3f5d8862f26..0ac2afa5fa3 100644 --- a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts +++ b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts @@ -27,6 +27,7 @@ export class LineNumbersOverlay extends DynamicViewOverlay { private _lineNumbersLeft!: number; private _lineNumbersWidth!: number; private _lastCursorModelPosition: Position; + private _lastCursorViewPosition: Position; private _renderResult: string[] | null; private _activeLineNumber: number; @@ -37,6 +38,7 @@ export class LineNumbersOverlay extends DynamicViewOverlay { this._readConfig(); this._lastCursorModelPosition = new Position(1, 1); + this._lastCursorViewPosition = new Position(1, 1); this._renderResult = null; this._activeLineNumber = 1; this._context.addEventHandler(this); @@ -68,6 +70,7 @@ export class LineNumbersOverlay extends DynamicViewOverlay { } public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { const primaryViewPosition = e.selections[0].getPosition(); + this._lastCursorViewPosition = primaryViewPosition; this._lastCursorModelPosition = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(primaryViewPosition); let shouldRender = false; @@ -112,14 +115,6 @@ export class LineNumbersOverlay extends DynamicViewOverlay { return this._renderCustomLineNumbers(modelLineNumber); } - if (this._renderLineNumbers === RenderLineNumbersType.Relative) { - const diff = Math.abs(this._lastCursorModelPosition.lineNumber - modelLineNumber); - if (diff === 0) { - return '' + modelLineNumber + ''; - } - return String(diff); - } - if (this._renderLineNumbers === RenderLineNumbersType.Interval) { if (this._lastCursorModelPosition.lineNumber === modelLineNumber) { return String(modelLineNumber); @@ -144,6 +139,45 @@ export class LineNumbersOverlay extends DynamicViewOverlay { const visibleEndLineNumber = ctx.visibleRange.endLineNumber; const common = '
'; + let relativeLineNumbers: number[] | null = null; + if (this._renderLineNumbers === RenderLineNumbersType.Relative) { + relativeLineNumbers = new Array(visibleEndLineNumber - visibleStartLineNumber + 1); + + if (this._lastCursorViewPosition.lineNumber >= visibleStartLineNumber && this._lastCursorViewPosition.lineNumber <= visibleEndLineNumber) { + relativeLineNumbers[this._lastCursorViewPosition.lineNumber - visibleStartLineNumber] = this._lastCursorModelPosition.lineNumber; + } + + // Iterate up to compute relative line numbers + { + let value = 0; + for (let lineNumber = this._lastCursorViewPosition.lineNumber + 1; lineNumber <= visibleEndLineNumber; lineNumber++) { + const modelPosition = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber, 1)); + const isWrappedLine = (modelPosition.column !== 1); + if (!isWrappedLine) { + value++; + } + if (lineNumber >= visibleStartLineNumber) { + relativeLineNumbers[lineNumber - visibleStartLineNumber] = isWrappedLine ? 0 : value; + } + } + } + + // Iterate down to compute relative line numbers + { + let value = 0; + for (let lineNumber = this._lastCursorViewPosition.lineNumber - 1; lineNumber >= visibleStartLineNumber; lineNumber--) { + const modelPosition = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber, 1)); + const isWrappedLine = (modelPosition.column !== 1); + if (!isWrappedLine) { + value++; + } + if (lineNumber <= visibleEndLineNumber) { + relativeLineNumbers[lineNumber - visibleStartLineNumber] = isWrappedLine ? 0 : value; + } + } + } + } + const lineCount = this._context.viewModel.getLineCount(); const output: string[] = []; for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) { @@ -157,7 +191,20 @@ export class LineNumbersOverlay extends DynamicViewOverlay { } } - const renderLineNumber = this._getLineRenderLineNumber(lineNumber); + let renderLineNumber: string; + if (relativeLineNumbers) { + const relativeLineNumber = relativeLineNumbers[lineIndex]; + if (this._lastCursorViewPosition.lineNumber === lineNumber) { + // current line! + renderLineNumber = `${relativeLineNumber}`; + } else if (relativeLineNumber) { + renderLineNumber = String(relativeLineNumber); + } else { + renderLineNumber = ''; + } + } else { + renderLineNumber = this._getLineRenderLineNumber(lineNumber); + } if (renderLineNumber) { if (lineNumber === this._activeLineNumber) { From d35989fd5b67ff06072b2f5e659ed63306f989b3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 22 Jul 2022 08:49:54 -0700 Subject: [PATCH 180/197] xterm@4.20.0-beta.20 Fixes #155232 --- package.json | 10 +++++----- remote/package.json | 10 +++++----- remote/web/package.json | 6 +++--- remote/web/yarn.lock | 24 ++++++++++++------------ remote/yarn.lock | 40 ++++++++++++++++++++-------------------- yarn.lock | 40 ++++++++++++++++++++-------------------- 6 files changed, 65 insertions(+), 65 deletions(-) diff --git a/package.json b/package.json index 19b7b991454..6a8a1921c93 100644 --- a/package.json +++ b/package.json @@ -86,12 +86,12 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", - "xterm": "4.20.0-beta.13", - "xterm-addon-search": "0.10.0-beta.2", - "xterm-addon-serialize": "0.8.0-beta.2", + "xterm": "4.20.0-beta.20", + "xterm-addon-search": "0.10.0-beta.3", + "xterm-addon-serialize": "0.8.0-beta.3", "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.13.0-beta.7", - "xterm-headless": "4.20.0-beta.13", + "xterm-addon-webgl": "0.13.0-beta.9", + "xterm-headless": "4.20.0-beta.20", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/package.json b/remote/package.json index 10c685468d1..6b5622069ed 100644 --- a/remote/package.json +++ b/remote/package.json @@ -24,12 +24,12 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", - "xterm": "4.20.0-beta.13", - "xterm-addon-search": "0.10.0-beta.2", - "xterm-addon-serialize": "0.8.0-beta.2", + "xterm": "4.20.0-beta.20", + "xterm-addon-search": "0.10.0-beta.3", + "xterm-addon-serialize": "0.8.0-beta.3", "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.13.0-beta.7", - "xterm-headless": "4.20.0-beta.13", + "xterm-addon-webgl": "0.13.0-beta.9", + "xterm-headless": "4.20.0-beta.20", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/web/package.json b/remote/web/package.json index b44711e8c37..9e4dc583566 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -11,9 +11,9 @@ "tas-client-umd": "0.1.6", "vscode-oniguruma": "1.6.1", "vscode-textmate": "7.0.1", - "xterm": "4.20.0-beta.13", - "xterm-addon-search": "0.10.0-beta.2", + "xterm": "4.20.0-beta.20", + "xterm-addon-search": "0.10.0-beta.3", "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.13.0-beta.7" + "xterm-addon-webgl": "0.13.0-beta.9" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index ba145029748..a2ae83d5fce 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -68,22 +68,22 @@ vscode-textmate@7.0.1: resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-7.0.1.tgz#8118a32b02735dccd14f893b495fa5389ad7de79" integrity sha512-zQ5U/nuXAAMsh691FtV0wPz89nSkHbs+IQV8FDk+wew9BlSDhf4UmWGlWJfTR2Ti6xZv87Tj5fENzKf6Qk7aLw== -xterm-addon-search@0.10.0-beta.2: - version "0.10.0-beta.2" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0-beta.2.tgz#a937d1e9a70fde8eeb7d1df485039b2d5fc1d707" - integrity sha512-ybafAbX9V4sfkzmUsWmtfEYExG8jj73bTF9pEa/Lhd5q4bviW4LcFaw/n3lKHn/1tSgSVgzoD13u1ZaZR78SfQ== +xterm-addon-search@0.10.0-beta.3: + version "0.10.0-beta.3" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0-beta.3.tgz#5194434d86105637c71f6f20139a9d0b5c1a956a" + integrity sha512-UeGm/ymnp7HUYJJtsP0D+bljOWbdk3MctcLJ+0jv8AmU6YlAzJFtouvYSQrD5SAMyht5CRsvjzFgqic9X02JYg== xterm-addon-unicode11@0.4.0-beta.3: version "0.4.0-beta.3" resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== -xterm-addon-webgl@0.13.0-beta.7: - version "0.13.0-beta.7" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.7.tgz#9b514fa9792ced755c8321ddd06529f9912db2a1" - integrity sha512-U3sKzkziZRwb13MyNp0eMwt5BWyM938epAv0ZOnI5Vjq06S2naS37GgO/FY+0eu5wuBKkZhT9lNDv7n8rMqd+w== +xterm-addon-webgl@0.13.0-beta.9: + version "0.13.0-beta.9" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.9.tgz#66a9ac142ae347d0548abbf4e66bb2f35f415adb" + integrity sha512-x1o1tpCqIsICvhcRsZs+BLcwUIdizYS2G4TIH0KBnUDiSN+oSqpVBQNG8qKg56xbK8WtpdbQ9dLB7JR2W5cX0g== -xterm@4.20.0-beta.13: - version "4.20.0-beta.13" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.13.tgz#7526e19310daa56a11c26eb81a2706593c1378ec" - integrity sha512-ovXGhvM/qDRNGlGO653WY1pxIZrnI4+ayVM7pCnxO/tRwUIym6LGS3NurYrhGYrXOjoLJZxQ4EjToAfHvAg2Gg== +xterm@4.20.0-beta.20: + version "4.20.0-beta.20" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.20.tgz#2979a31839f7b8ee3ffe4f063b40c02facdb0fed" + integrity sha512-ltDtTquH+33tXQPFSDqenbgz6LkvIob6l6Rac85L4aX5Ve7P3ubVLrq+lTFJGQn3iiwGqNmnE1t1EUuGhxsXcQ== diff --git a/remote/yarn.lock b/remote/yarn.lock index 3076135dad1..762745d5927 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -788,35 +788,35 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -xterm-addon-search@0.10.0-beta.2: - version "0.10.0-beta.2" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0-beta.2.tgz#a937d1e9a70fde8eeb7d1df485039b2d5fc1d707" - integrity sha512-ybafAbX9V4sfkzmUsWmtfEYExG8jj73bTF9pEa/Lhd5q4bviW4LcFaw/n3lKHn/1tSgSVgzoD13u1ZaZR78SfQ== +xterm-addon-search@0.10.0-beta.3: + version "0.10.0-beta.3" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0-beta.3.tgz#5194434d86105637c71f6f20139a9d0b5c1a956a" + integrity sha512-UeGm/ymnp7HUYJJtsP0D+bljOWbdk3MctcLJ+0jv8AmU6YlAzJFtouvYSQrD5SAMyht5CRsvjzFgqic9X02JYg== -xterm-addon-serialize@0.8.0-beta.2: - version "0.8.0-beta.2" - resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.8.0-beta.2.tgz#f30656d4ff1570ac105bacffe443385666654598" - integrity sha512-IDaRxO1zwjF9fDJp6u27Lv8852kEZ0HlbB0wLZbcIGZxDuPDLfvw8s/BV7f6MFB+mZq19CjyHGH4oPzZkc0rLQ== +xterm-addon-serialize@0.8.0-beta.3: + version "0.8.0-beta.3" + resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.8.0-beta.3.tgz#47ade3fedacbb75bd26e63cfe0120586623e0e4f" + integrity sha512-gvfempZCYuAhLqN4O6fA2TuoavPjOxFKlh8hLcOzPackiLUhwKr1jQpDXcnq8VgqUiGgb+XNZpPEbI0Q7EhTgA== xterm-addon-unicode11@0.4.0-beta.3: version "0.4.0-beta.3" resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== -xterm-addon-webgl@0.13.0-beta.7: - version "0.13.0-beta.7" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.7.tgz#9b514fa9792ced755c8321ddd06529f9912db2a1" - integrity sha512-U3sKzkziZRwb13MyNp0eMwt5BWyM938epAv0ZOnI5Vjq06S2naS37GgO/FY+0eu5wuBKkZhT9lNDv7n8rMqd+w== +xterm-addon-webgl@0.13.0-beta.9: + version "0.13.0-beta.9" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.9.tgz#66a9ac142ae347d0548abbf4e66bb2f35f415adb" + integrity sha512-x1o1tpCqIsICvhcRsZs+BLcwUIdizYS2G4TIH0KBnUDiSN+oSqpVBQNG8qKg56xbK8WtpdbQ9dLB7JR2W5cX0g== -xterm-headless@4.20.0-beta.13: - version "4.20.0-beta.13" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.20.0-beta.13.tgz#a7d9d8837e3f78e106006cc94cf63ec13a9fd991" - integrity sha512-y4YI+Ogv2R2I++tsyvx5Q7csAaN7mG2yMMMBb/u4dXnrFmSGYs/R8ZFkeHgAW4Ju4uI3Rizb+ZdwtN1uG043Rw== +xterm-headless@4.20.0-beta.20: + version "4.20.0-beta.20" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.20.0-beta.20.tgz#da2d8131b02d6f1e37f47cc17e578f2c2980fbb6" + integrity sha512-JK4jUIiUH7TdzvMrpfDnbGxTuC4s7byjqnMHR8+gIpY8qCFjz0xcMFSbp+ZshxGwVyziI4jtJqTHZjFToT2/kw== -xterm@4.20.0-beta.13: - version "4.20.0-beta.13" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.13.tgz#7526e19310daa56a11c26eb81a2706593c1378ec" - integrity sha512-ovXGhvM/qDRNGlGO653WY1pxIZrnI4+ayVM7pCnxO/tRwUIym6LGS3NurYrhGYrXOjoLJZxQ4EjToAfHvAg2Gg== +xterm@4.20.0-beta.20: + version "4.20.0-beta.20" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.20.tgz#2979a31839f7b8ee3ffe4f063b40c02facdb0fed" + integrity sha512-ltDtTquH+33tXQPFSDqenbgz6LkvIob6l6Rac85L4aX5Ve7P3ubVLrq+lTFJGQn3iiwGqNmnE1t1EUuGhxsXcQ== yallist@^4.0.0: version "4.0.0" diff --git a/yarn.lock b/yarn.lock index c9e570c8798..9121ca37524 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12072,35 +12072,35 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" -xterm-addon-search@0.10.0-beta.2: - version "0.10.0-beta.2" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0-beta.2.tgz#a937d1e9a70fde8eeb7d1df485039b2d5fc1d707" - integrity sha512-ybafAbX9V4sfkzmUsWmtfEYExG8jj73bTF9pEa/Lhd5q4bviW4LcFaw/n3lKHn/1tSgSVgzoD13u1ZaZR78SfQ== +xterm-addon-search@0.10.0-beta.3: + version "0.10.0-beta.3" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0-beta.3.tgz#5194434d86105637c71f6f20139a9d0b5c1a956a" + integrity sha512-UeGm/ymnp7HUYJJtsP0D+bljOWbdk3MctcLJ+0jv8AmU6YlAzJFtouvYSQrD5SAMyht5CRsvjzFgqic9X02JYg== -xterm-addon-serialize@0.8.0-beta.2: - version "0.8.0-beta.2" - resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.8.0-beta.2.tgz#f30656d4ff1570ac105bacffe443385666654598" - integrity sha512-IDaRxO1zwjF9fDJp6u27Lv8852kEZ0HlbB0wLZbcIGZxDuPDLfvw8s/BV7f6MFB+mZq19CjyHGH4oPzZkc0rLQ== +xterm-addon-serialize@0.8.0-beta.3: + version "0.8.0-beta.3" + resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.8.0-beta.3.tgz#47ade3fedacbb75bd26e63cfe0120586623e0e4f" + integrity sha512-gvfempZCYuAhLqN4O6fA2TuoavPjOxFKlh8hLcOzPackiLUhwKr1jQpDXcnq8VgqUiGgb+XNZpPEbI0Q7EhTgA== xterm-addon-unicode11@0.4.0-beta.3: version "0.4.0-beta.3" resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== -xterm-addon-webgl@0.13.0-beta.7: - version "0.13.0-beta.7" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.7.tgz#9b514fa9792ced755c8321ddd06529f9912db2a1" - integrity sha512-U3sKzkziZRwb13MyNp0eMwt5BWyM938epAv0ZOnI5Vjq06S2naS37GgO/FY+0eu5wuBKkZhT9lNDv7n8rMqd+w== +xterm-addon-webgl@0.13.0-beta.9: + version "0.13.0-beta.9" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.9.tgz#66a9ac142ae347d0548abbf4e66bb2f35f415adb" + integrity sha512-x1o1tpCqIsICvhcRsZs+BLcwUIdizYS2G4TIH0KBnUDiSN+oSqpVBQNG8qKg56xbK8WtpdbQ9dLB7JR2W5cX0g== -xterm-headless@4.20.0-beta.13: - version "4.20.0-beta.13" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.20.0-beta.13.tgz#a7d9d8837e3f78e106006cc94cf63ec13a9fd991" - integrity sha512-y4YI+Ogv2R2I++tsyvx5Q7csAaN7mG2yMMMBb/u4dXnrFmSGYs/R8ZFkeHgAW4Ju4uI3Rizb+ZdwtN1uG043Rw== +xterm-headless@4.20.0-beta.20: + version "4.20.0-beta.20" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.20.0-beta.20.tgz#da2d8131b02d6f1e37f47cc17e578f2c2980fbb6" + integrity sha512-JK4jUIiUH7TdzvMrpfDnbGxTuC4s7byjqnMHR8+gIpY8qCFjz0xcMFSbp+ZshxGwVyziI4jtJqTHZjFToT2/kw== -xterm@4.20.0-beta.13: - version "4.20.0-beta.13" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.13.tgz#7526e19310daa56a11c26eb81a2706593c1378ec" - integrity sha512-ovXGhvM/qDRNGlGO653WY1pxIZrnI4+ayVM7pCnxO/tRwUIym6LGS3NurYrhGYrXOjoLJZxQ4EjToAfHvAg2Gg== +xterm@4.20.0-beta.20: + version "4.20.0-beta.20" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.20.tgz#2979a31839f7b8ee3ffe4f063b40c02facdb0fed" + integrity sha512-ltDtTquH+33tXQPFSDqenbgz6LkvIob6l6Rac85L4aX5Ve7P3ubVLrq+lTFJGQn3iiwGqNmnE1t1EUuGhxsXcQ== y18n@^3.2.1: version "3.2.2" From 2f66e194f38f005f4e50fda27a3fbd52abf9f920 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Fri, 22 Jul 2022 09:21:56 -0700 Subject: [PATCH 181/197] Refactor scope overrides indicator (#155912) --- .../browser/settingsEditorSettingIndicators.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts index 3ece07a23b6..f3811ec3287 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -9,7 +9,7 @@ import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLa import { ICustomHover, ITooltipMarkdownString, IUpdatableHoverOptions, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { SimpleIconLabel } from 'vs/base/browser/ui/iconLabel/simpleIconLabel'; import { Emitter } from 'vs/base/common/event'; -import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { localize } from 'vs/nls'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -36,11 +36,11 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { private indicatorsContainerElement: HTMLElement; private scopeOverridesElement: HTMLElement; private scopeOverridesLabel: SimpleIconLabel; + private scopeOverridesHover: MutableDisposable; private syncIgnoredElement: HTMLElement; private syncIgnoredHover: ICustomHover | undefined; private defaultOverrideIndicatorElement: HTMLElement; private hoverDelegate: IHoverDelegate; - private hover: ICustomHover | undefined; constructor( container: HTMLElement, @@ -65,6 +65,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { this.scopeOverridesLabel = scopeOverridesIndicator.label; this.syncIgnoredElement = this.createSyncIgnoredElement(); this.defaultOverrideIndicatorElement = this.createDefaultOverrideIndicator(); + this.scopeOverridesHover = new MutableDisposable(); } private createScopeOverridesIndicator(): { element: HTMLElement; label: SimpleIconLabel } { @@ -126,7 +127,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { } dispose() { - this.hover?.dispose(); + this.scopeOverridesHover.dispose(); this.syncIgnoredHover?.dispose(); } @@ -144,8 +145,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { this.scopeOverridesLabel.text = applicationSettingText; const content = localize('applicationSettingDescription', "The setting is not specific to the current profile, and will retain its value when switching profiles."); - this.hover?.dispose(); - this.hover = setupCustomHover(this.hoverDelegate, this.scopeOverridesElement, content); + this.scopeOverridesHover.value = setupCustomHover(this.hoverDelegate, this.scopeOverridesElement, content); } else if (element.overriddenScopeList.length || element.overriddenDefaultsLanguageList.length) { if ((MODIFIED_INDICATOR_USE_INLINE_ONLY && element.overriddenScopeList.length) || (element.overriddenScopeList.length === 1 && !element.overriddenDefaultsLanguageList.length)) { @@ -153,7 +153,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { // or if there is only one scope override to render and no language overrides. this.scopeOverridesElement.style.display = 'inline'; this.scopeOverridesElement.classList.remove('with-custom-hover'); - this.hover?.dispose(); + this.scopeOverridesHover.value = undefined; // Just show all the text in the label. const prefaceText = element.isConfigured ? @@ -234,11 +234,10 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { scope: scope as ScopeString, language }); - this.hover!.hide(); + this.scopeOverridesHover.value?.hide(); } }; - this.hover?.dispose(); - this.hover = setupCustomHover(this.hoverDelegate, this.scopeOverridesElement, content, options); + this.scopeOverridesHover.value = setupCustomHover(this.hoverDelegate, this.scopeOverridesElement, content, options); } } this.render(); From 86e741027897bde6587d0d13205b88ce2996b53f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 22 Jul 2022 18:50:34 +0200 Subject: [PATCH 182/197] unit tests - help diagnose test failure reason for web tests (#155967) --- test/unit/browser/index.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/test/unit/browser/index.js b/test/unit/browser/index.js index 305d78b819a..8a985c791c9 100644 --- a/test/unit/browser/index.js +++ b/test/unit/browser/index.js @@ -150,6 +150,8 @@ async function runTestsInBrowser(testModules, browserType) { const failingModuleIds = []; const failingTests = []; emitter.on('fail', (test, err) => { + failingTests.push({ title: test.fullTitle, message: err.message }); + if (err.stack) { const regex = /(vs\/.*\.test)\.js/; for (const line of String(err.stack).split('\n')) { @@ -160,9 +162,6 @@ async function runTestsInBrowser(testModules, browserType) { } } } - - // We could not determine the module id - failingTests.push(test.fullTitle); }); try { @@ -176,11 +175,14 @@ async function runTestsInBrowser(testModules, browserType) { } await browser.close(); - if (failingModuleIds.length > 0) { - return `to DEBUG, open ${browserType.toUpperCase()} and navigate to ${target.href}?${failingModuleIds.map(module => `m=${module}`).join('&')}`; - } if (failingTests.length > 0) { - return `The followings tests are failing:\n - ${failingTests.join('\n - ')}`; + let res = `The followings tests are failing:\n - ${failingTests.map(({ title, message }) => `${title} (reason: ${message})`).join('\n - ')}`; + + if (failingModuleIds.length > 0) { + res += `\n\nTo DEBUG, open ${browserType.toUpperCase()} and navigate to ${target.href}?${failingModuleIds.map(module => `m=${module}`).join('&')}`; + } + + return `${res}\n`; } } From 82fda354756a0a38ce94e719213d46121a363f2f Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 22 Jul 2022 19:07:08 +0200 Subject: [PATCH 183/197] Tweak merge editor dev command names (#155978) --- .../mergeEditor/browser/commands/devCommands.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts index 6fdc90a8b40..17df90e7d26 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts @@ -36,10 +36,10 @@ export class MergeEditorCopyContentsToJSON extends Action2 { category: 'Merge Editor (Dev)', title: { value: localize( - 'merge.dev.copyContents', - 'Copy Contents of Inputs, Base and Result as JSON' + 'merge.dev.copyState', + 'Copy Merge Editor State as JSON' ), - original: 'Copy Contents of Inputs, Base and Result as JSON', + original: 'Copy Merge Editor State as JSON', }, icon: Codicon.layoutCentered, f1: true, @@ -75,7 +75,7 @@ export class MergeEditorCopyContentsToJSON extends Action2 { notificationService.info({ name: localize('mergeEditor.name', 'Merge Editor'), - message: localize('mergeEditor.successfullyCopiedMergeEditorContents', "Successfully copied merge editor contents"), + message: localize('mergeEditor.successfullyCopiedMergeEditorContents', "Successfully copied merge editor state"), }); } } @@ -87,10 +87,10 @@ export class MergeEditorOpenContents extends Action2 { category: 'Merge Editor (Dev)', title: { value: localize( - 'merge.dev.openContents', - 'Open Contents of Inputs, Base and Result from JSON' + 'merge.dev.openState', + 'Open Merge Editor State from JSON' ), - original: 'Open Contents of Inputs, Base and Result from JSON', + original: 'Open Merge Editor State from JSON', }, icon: Codicon.layoutCentered, f1: true, From 42e79ca270e61d66e2249b519876ba96b67ed4b4 Mon Sep 17 00:00:00 2001 From: ryuurock Date: Sat, 23 Jul 2022 01:46:48 +0800 Subject: [PATCH 184/197] Update typescriptServiceClient.ts, fix naming errors (#155946) Update typescriptServiceClient.ts fix naming errors --- .../src/typescriptServiceClient.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 0585285b6d7..b09593f6c59 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -554,8 +554,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType } // Reconfigure any plugins - for (const [config, pluginName] of this.pluginManager.configurations()) { - this.configurePlugin(config, pluginName); + for (const [pluginName, config] of this.pluginManager.configurations()) { + this.configurePlugin(pluginName, config); } } From 95fa8f7e77f5b326d8f891fb350becb6f3c61088 Mon Sep 17 00:00:00 2001 From: David Dossett Date: Fri, 22 Jul 2022 10:50:55 -0700 Subject: [PATCH 185/197] Use "Notifications" label for status bar entry when do not disturb is enabled (#155863) --- .../workbench/browser/parts/notifications/notificationsStatus.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts index d3da50394ad..670e5f35cae 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts @@ -86,7 +86,6 @@ export class NotificationsStatus extends Disposable { if (this.notificationService.doNotDisturbMode) { statusProperties = { ...statusProperties, - name: localize('status.doNotDisturb', "Do Not Disturb"), text: `${notificationsInProgress > 0 || this.newNotificationsCount > 0 ? '$(bell-slash-dot)' : '$(bell-slash)'}`, ariaLabel: localize('status.doNotDisturb', "Do Not Disturb"), tooltip: localize('status.doNotDisturbTooltip', "Do Not Disturb Mode is Enabled") From e428279c7a909dc8f21a8f6e41a1338cf5c7e12e Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 22 Jul 2022 14:55:42 -0400 Subject: [PATCH 186/197] Bump distro (#155985) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6a8a1921c93..bdd7a1190ca 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.70.0", - "distro": "ea95f2cf38dce77935c7240a566e00b7123d545d", + "distro": "990065ff739688c0d4ad94e172eeccd6bd5ef124", "author": { "name": "Microsoft Corporation" }, From 1b8e06a9aba8f56966eeb0801e76e5e8cdae25c6 Mon Sep 17 00:00:00 2001 From: Justin Chen Date: Fri, 22 Jul 2022 12:15:23 -0700 Subject: [PATCH 187/197] added fix on menu anchor on resize --- src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts index 0b4b0d1039a..a2d66113907 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts @@ -222,6 +222,8 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { ); renderDisposables.add(this.codeActionList.value.onDidChangeSelection(e => this._onListSelection(e))); + renderDisposables.add(this._editor.onDidLayoutChange(e => this.hideCodeActionWidget())); + // Populating the list widget and tracking enabled options. inputArray.forEach((item, index) => { @@ -256,7 +258,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution { // resize observer - can be used in the future since list widget supports dynamic height but not width const maxWidth = Math.max(...arr); - // 40 is the additional padding for the list widget (26px left, 26px right) + // 40 is the additional padding for the list widget (20 left, 20 right) renderMenu.style.width = maxWidth + 52 + 'px'; this.codeActionList.value?.layout(height, maxWidth); From 448e9cc22dc8bbc0ee561a80f9d90969acb8f31a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 22 Jul 2022 21:36:20 +0200 Subject: [PATCH 188/197] rename `needs more info` to `info-needed` (#155992) --- .github/commands.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/commands.json b/.github/commands.json index 68a50baae95..f67ee72c873 100644 --- a/.github/commands.json +++ b/.github/commands.json @@ -154,7 +154,7 @@ "IllusionMH" ], "action": "updateLabels", - "addLabel": "~needs more info" + "addLabel": "~info-needed" }, { "type": "comment", @@ -165,14 +165,14 @@ "gjsjohnmurray", "IllusionMH" ], - "addLabel": "needs more info", + "addLabel": "info-needed", "comment": "Thanks for creating this issue regarding performance! Please follow this guide to help us diagnose performance issues: https://github.com/microsoft/vscode/wiki/Performance-Issues \n\nHappy Coding!" }, { "type": "comment", "name": "jsDebugLogs", "action": "updateLabels", - "addLabel": "needs more info", + "addLabel": "info-needed", "comment": "Please collect trace logs using the following instructions:\n\n> If you're able to, add `\"trace\": true` to your `launch.json` and reproduce the issue. The location of the log file on your disk will be written to the Debug Console. Share that with us.\n>\n> ⚠️ This log file will not contain source code, but will contain file paths. You can drop it into https://microsoft.github.io/vscode-pwa-analyzer/index.html to see what it contains. If you'd rather not share the log publicly, you can email it to connor@xbox.com" }, { @@ -189,17 +189,17 @@ }, { "type": "label", - "name": "~needs more info", + "name": "~info-needed", "action": "updateLabels", - "addLabel": "needs more info", - "removeLabel": "~needs more info", + "addLabel": "info-needed", + "removeLabel": "~info-needed", "comment": "Thanks for creating this issue! We figured it's missing some basic information or in some other way doesn't follow our [issue reporting guidelines](https://aka.ms/vscodeissuereporting). Please take the time to review these and update the issue.\n\nHappy Coding!" }, { "type": "label", "name": "~needs version info", "action": "updateLabels", - "addLabel": "needs more info", + "addLabel": "info-needed", "removeLabel": "~needs version info", "comment": "Thanks for creating this issue! We figured it's missing some basic information, such as a version number, or in some other way doesn't follow our [issue reporting guidelines](https://aka.ms/vscodeissuereporting). Please take the time to review these and update the issue.\n\nHappy Coding!" }, @@ -416,7 +416,7 @@ "IllusionMH" ], "action": "comment", - "addLabel": "needs more info", + "addLabel": "info-needed", "comment": "Thanks for reporting this issue! Unfortunately, it's hard for us to understand what issue you're seeing. Please help us out by providing a screen recording showing exactly what isn't working as expected. While we can work with most standard formats, `.gif` files are preferred as they are displayed inline on GitHub. You may find https://gifcap.dev helpful as a browser-based gif recording tool.\n\nIf the issue depends on keyboard input, you can help us by enabling screencast mode for the recording (`Developer: Toggle Screencast Mode` in the command palette).\n\nHappy coding!" }, { From 76758809f1d0215c3644b673332902a0916fbf72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 22 Jul 2022 23:21:20 +0200 Subject: [PATCH 189/197] fix api proposal generation EOL (#155991) on Windows, my files are checked out with LF. the script which generates extensionApiProposals.ts always uses os.EOL, so it always is dirty on my repo every time I compile --- build/lib/compilation.js | 11 ++++++++++- build/lib/compilation.ts | 12 +++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/build/lib/compilation.js b/build/lib/compilation.js index 21944c463f9..443073cb039 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -188,6 +188,15 @@ class MonacoGenerator { } } function generateApiProposalNames() { + let eol; + try { + const src = fs.readFileSync('src/vs/workbench/services/extensions/common/extensionsApiProposals.ts', 'utf-8'); + const match = /\r?\n/m.exec(src); + eol = match ? match[0] : os.EOL; + } + catch { + eol = os.EOL; + } const pattern = /vscode\.proposed\.([a-zA-Z]+)\.d\.ts$/; const proposalNames = new Set(); const input = es.through(); @@ -214,7 +223,7 @@ function generateApiProposalNames() { '});', 'export type ApiProposalName = keyof typeof allApiProposals;', '', - ].join(os.EOL); + ].join(eol); this.emit('data', new File({ path: 'vs/workbench/services/extensions/common/extensionsApiProposals.ts', contents: Buffer.from(contents) diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index b737a5e8746..de1947f2ae1 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -226,6 +226,16 @@ class MonacoGenerator { } function generateApiProposalNames() { + let eol: string; + + try { + const src = fs.readFileSync('src/vs/workbench/services/extensions/common/extensionsApiProposals.ts', 'utf-8'); + const match = /\r?\n/m.exec(src); + eol = match ? match[0] : os.EOL; + } catch { + eol = os.EOL; + } + const pattern = /vscode\.proposed\.([a-zA-Z]+)\.d\.ts$/; const proposalNames = new Set(); @@ -254,7 +264,7 @@ function generateApiProposalNames() { '});', 'export type ApiProposalName = keyof typeof allApiProposals;', '', - ].join(os.EOL); + ].join(eol); this.emit('data', new File({ path: 'vs/workbench/services/extensions/common/extensionsApiProposals.ts', From e6ad5f0a6fca483c1760dfb9db7e6f2831ed9c2a Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Fri, 22 Jul 2022 14:26:14 -0700 Subject: [PATCH 190/197] Update ipynb yarn lock (#156004) --- extensions/ipynb/package.json | 2 +- extensions/ipynb/yarn.lock | 38 ----------------------------------- 2 files changed, 1 insertion(+), 39 deletions(-) diff --git a/extensions/ipynb/package.json b/extensions/ipynb/package.json index 3b6ed828519..7e057f605b7 100644 --- a/extensions/ipynb/package.json +++ b/extensions/ipynb/package.json @@ -91,7 +91,7 @@ }, "devDependencies": { "@jupyterlab/nbformat": "^3.2.9", - "@types/markdown-it": "12.2.3", + "@types/markdown-it": "12.2.3", "@types/uuid": "^8.3.1" }, "repository": { diff --git a/extensions/ipynb/yarn.lock b/extensions/ipynb/yarn.lock index d1e10e42789..7b5488e7109 100644 --- a/extensions/ipynb/yarn.lock +++ b/extensions/ipynb/yarn.lock @@ -42,49 +42,11 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - detect-indent@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== -entities@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" - integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== - -linkify-it@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e" - integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ== - dependencies: - uc.micro "^1.0.1" - -markdown-it@^12.3.2: - version "12.3.2" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.3.2.tgz#bf92ac92283fe983fe4de8ff8abfb5ad72cd0c90" - integrity sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg== - dependencies: - argparse "^2.0.1" - entities "~2.1.0" - linkify-it "^3.0.1" - mdurl "^1.0.1" - uc.micro "^1.0.5" - -mdurl@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" - integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== - -uc.micro@^1.0.1, uc.micro@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" - integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== - uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" From a9b387204b9d5693323e4315f796b458d94854e0 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 22 Jul 2022 15:32:10 -0700 Subject: [PATCH 191/197] Add some additional logging to md server (#156007) Logs watcher operations --- .../markdown-language-features/server/src/workspace.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/extensions/markdown-language-features/server/src/workspace.ts b/extensions/markdown-language-features/server/src/workspace.ts index 895fb5f1b8c..09147851016 100644 --- a/extensions/markdown-language-features/server/src/workspace.ts +++ b/extensions/markdown-language-features/server/src/workspace.ts @@ -69,6 +69,7 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching { connection.onDidChangeWatchedFiles(async ({ changes }) => { for (const change of changes) { const resource = URI.parse(change.uri); + this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: onDidChangeWatchedFiles', `${change.type}: ${resource}`); switch (change.type) { case FileChangeType.Changed: { this._documentCache.delete(resource); @@ -95,10 +96,13 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching { }); connection.onRequest(protocol.fs_watcher_onChange, params => { + this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: fs_watcher_onChange', `${params.kind}: ${params.uri}`); + const watcher = this._watchers.get(params.id); if (!watcher) { return; } + switch (params.kind) { case 'create': watcher.onDidCreate.fire(URI.parse(params.uri)); return; case 'change': watcher.onDidChange.fire(URI.parse(params.uri)); return; @@ -211,6 +215,9 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching { } watchFile(resource: URI, options: FileWatcherOptions): IFileSystemWatcher { + const id = this._watcherPool++; + this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: watchFile', `(${id}) ${resource}`); + const entry = { resource, options, @@ -218,7 +225,6 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching { onDidChange: new Emitter(), onDidDelete: new Emitter(), }; - const id = this._watcherPool++; this._watchers.set(id, entry); this.connection.sendRequest(protocol.fs_watcher_create, { @@ -232,6 +238,7 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching { onDidChange: entry.onDidChange.event, onDidDelete: entry.onDidDelete.event, dispose: () => { + this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: disposeWatcher', `(${id}) ${resource}`); this.connection.sendRequest(protocol.fs_watcher_delete, { id }); this._watchers.delete(id); } From f9b9ffe1ef6f9ce5ebde75dec14fdb8700833aeb Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 22 Jul 2022 15:43:27 -0700 Subject: [PATCH 192/197] Fix deprecation message (#156008) --- src/vs/workbench/api/common/extHost.api.impl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 2be02ab4797..073d4e11cdf 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -784,7 +784,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I showNotebookDocument(uriOrDocument, options?) { if (URI.isUri(uriOrDocument)) { extHostApiDeprecation.report('window.showNotebookDocument(uri)', extension, - `Please use 'window.openNotebookDocument' and 'window.showTextDocument'`); + `Please use 'workspace.openNotebookDocument' and 'window.showNotebookDocument'`); } return extHostNotebook.showNotebookDocument(uriOrDocument, options); }, From fe89280fe3b720dfe7f41430b3289aea4be16f7b Mon Sep 17 00:00:00 2001 From: David Dossett Date: Fri, 22 Jul 2022 15:45:08 -0700 Subject: [PATCH 193/197] Update codicons (#156009) --- .../browser/ui/codicons/codicon/codicon.ttf | Bin 72116 -> 72504 bytes src/vs/base/common/codicons.ts | 2 ++ 2 files changed, 2 insertions(+) diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 9d4627dfb643ff05823e1b740e8bae0f0a9f1f93..5abfa748fb56c712c19d127f550727acc3fabe59 100644 GIT binary patch delta 4897 zcmZYDdwi7DnFsLSF9{)pBnBp70*Oh;kP9IfNHV!{2?>PUhyg+fAwfcz+z9~!UWyI^ zDk4?LAfjSxRVY%cXk4V!($rGCv6e;G+Ey)Xt?kZ9ky^0UrMl1Y+5Kxj$@l%uJMTL) z=bZPvPtN3};h<9|gZ8<+g#h;gSku0~Z6M)P`2#@6b|B_ZXWwldul(e4g{RzU2Ng3OlL6ug|D~+Kf>eqA-AywJGh?9xsGeuNDuqi$u)SMtJv*p8TXd=IDUZV zZ~`yk-*5`2@nadbpWs#e9B1(hoWpB)9dFH$qnJG+V8mDsxGrY`VHfJ%1xy+-R z`7GdU&f#3nV=3ozAuG6um8@Yc>v$9Exs=Pef=yh>X0~!QZ)O|Y*}*RMa5D#a2k)ep z+j$pvau@IBJ-nCq^V@uY4|0e;9^gSf%*S|`kMle9ewR=2Y5W0y#Gmj9R-zf*C_)cf z*^4gpp#u#_K`O3c7w*GJnUsVRtMMVnvWWehfM>aYQJ~6&&C`?%{6Uha)UxF$28t@CiQ2?{O<{;jO%lxAR-vf(cm6FdX0`XvYZ7 z<1M_2TGXH#QTQ32#!)Oq8%yvQZbmd-#4sa}fJ7KLxF28R1N=MQ#yj{AT*QCkJ-mvFinBMalgH|5k;0 zlD8;s@C`(`c*u7k!sShu=k8Q;x54jKa^JzfUEwQ9DJhoRi}3$1xhvt{spS5If0x1! zCGS?;CV7vNyBGd@73_QL2E2Ad+vPoq%O&quwBx>4ajoQoij9){6z%ocuP8(4A5yf} z!>71L@_@qgk`F1a!g%-(%8p?7@W>6X1xX2!X{V%^z(`%Rq^!UMC@C;7QtB)zH84R+ ziVn;eCFKW32Go*51T$7iNrDMcwx*kLO3D<>co`78k%|QqrlfYkge$3HFcC`X8O#kz zDjQ6sk{SmyK}ofPkqK`}-Ggx`semx@_rSs>$!H~2(F-$4c7zm27@204lu8)+i(*O9 zgqf_Qe8NmoQb=K9m6TMNI3>ju=0+uD7DnzIOA0Pbf|Ak;lW58OlPU}|RY^UDNm5do zVO&aTG|V)G>ypXJUMZ8JXfIu=qP;|EiuMvsSG1QRUC~|&?+n=p+M&%*w8NIEXooFJ z(GFX-q8&A<8Kf6z%I;6z%I;747R*E85rH ztZ0v~QIz9e(k2<9M-D0O^&O7Q_I@aPwjqLJC2d0l?X7LwA!tYZ2_?%jnC~e(EBU120!iCe!E#C4 zR>AR-M-&?*k1D$cGT)c)4|c;uUwxc&@(#7?%N*cta?HAo>Ta%xy=6y`gC5)|*Ne zv@mZeS=z#!2YkWt6DEhr@eT8LCChM_t3aLI$ch~1nv%6S%;!p0=`jCL zumkdil9fBmKNaj{{ZcW(mzTJx>es+1l`QBn8lYrpk5QR)mMroy8l+^okI^wo7XBCw zR=DCjo0v0cbiC{e`5u5#xk?s}4PQ-MLhrQHuGH&k#c3Z*Z=8N5eN+00^s_TUXJpN2 zp7H36k&O6^x{MPUqnT4O*JO@lUdf8cYRTH3bvb)M_V(-(*SmqH3C*d^ zxjknn=XlQRIahL{a`ST=ayRAf%RQERF88C{>v>Um8F?LfPv%{9=ee8Qr`;Fbm-Bb! zAI?AH&A(F6P_U`sY{8Yn_`-FCdkT*izFK&>D5_{d(bGj|ild6>7T;d{V)17sDJ4xM z_mqs3d^9_0cJ=HFb3*6rn)CMD7w7ut#m(C>@94a%rH!S}m0q1+Ie(-qs;r}ITiLT^ zpOvSVmzDRF?mGLVpy(?dD4r=aiKG=L=RmG|utIoBAv@B@Z)AC+xR_oH%!>jXF z?_INZZQ0uXwL|T3?WOH~?Yr9FTbH)(oClsrPoih0r_}R==Tb*vM^DFz&cx2@&O=@C zU8lSJ-Lt#y6Jp67Z)deeG4dWU*P`$GHPUO#vJrT(V=!~O4Lc+Rw!7JGxf zmitD&15d#XGOZHi`z;Ehow)_>>;PAEt~1)@E^xVB(egMt*PS~yH`-M&HnqT&6dX0F zgwd{~l)U^BrWQC09nVZ0Qy7t)T=dM&WJj|6wzjr9(!yg3!y+TY#*K@Ljg2ad2~XQt z<8T~{C>%4f=vYlnapx2pB0&;kbgH!ks>n*Nyq|{v*{Wu z4NFONxusc?obIvGro2>Xlr&8m=yo}iA{ZYqm+rh2vM^%6;aE2=c=klAA+x=!t-sT=Iqv@* R?)TjCe+OTFA%cP9{|7>xG93T_ delta 4545 zcmXZgdq7oH76$O|14Kkb^MxWJ38o?{Uf$pX5fKp-0bhv9TR=fmGcxk3DPv`ZTr)E> z$}%f+R7lON%*@El%wAU3I1ST-O;)C6jia-6YySA1@1FZQXYaGmx(f~;^k{1Is0(&R z1Ka>0zo@ccabVYtseo5C(5q(t!h1@0bq=`(gp33(Y%VJ)D4zLPL$hm@E_dZDjdR-c!?{Ri#1%pY!)(~ zIV@rYOE?!Va~9{r4IXGG$L|Oqbb>EBqYJvi9|5kF9rinS;|1)&UK~Ip4&pFg#Sy%Q zV|WY4@itE29h}6wxPWi)4}6P%<2!thAJB%M@C$y$Wn9JYxQ;*YCvM^v-RMqFwr3~a z#;)wf?(9iF_F`}Pvk!xKJNq-3AsoOvIFN%G>SP#)ayY{|f(}M7l2MFj0!MNbM{^9v zF`46;!ih{{Ix{$#nViaLoX#1X$=S@~9L{3_i&@Gt-pM7rmo>bPE9vBFuHpS$%XNH! z4{-w@W*s-No}2k7ALlkc!R>TD$*1{@ETMnmUueNBY`gWlN8$54bfa0>6C8R?jWG<3(Ccow^m zg#yOm3Cu$e?8AQgpdW(pBf9Ys{1;#1eVoQe_)r$}$M^)F;XJ;;8GOLSILf|Q4NtU( z7y5L-=QxK?(Fd>KXIw%&dZQQQwxg$Os?QBqzE69nk1Tq0YqDE;ekahHuVh`LwLsx( z$wI|pl0`~ZI9iJpo|i0943I2UTqHSPv7=;}l6wNJ<=yQ@jx6_nYlY$z*TqhL1GdUu zrDCFFm7<(}tNlvB39bO&VD@k&`uYucU7ov3$xVgU!VSsQN^VHBu2DEt-`TJCOR}?0afM{9Vy-K^v!8Q;>^-O`g{<`< zMLUa91TE%EKBAZ-S*K`ccax%>lzK%wyDlXa0Ii!9UY2}RahBv3DT;Q(`HiPLzw04& z00v4bf-GGnwE@OWNtJ++D{e`>fRQP&q;kNtQ&K}x<>n-iv|>LOM9v|y5wuetrf8>gsG^I6~3RphMXoSQDXWXE0LH z&R~>s(oSKtqMec$MLQ$0igrfg6zz<}E7}=JP_#2LQqj)HC`CIXqZREej8U|+kZ7-e zqKw{-e5|4!d6J?X?Knj{+GIsL+VP5Zv?+>qv=bEVXj2vKXeTP#lTTAjkn3+IDcZA7 zSF~rJp=i&3vZ6ivOhtQQQx)xrWhvSd%T}}}mZNA-V49*mf$7328`^`;P_ze~sb~*6 zOVJ)QSJ57Hwxa#QJVpD3a}@3O%~iDDH&4-iE?-eT=QIVfBWOQZsA%6@q-b9+R$^^z+TH%r

Tq#cmpHc2}m!6zi`u>`kEu2P)%q&%>r6MS0Ij!sB#1Y<`ha8c5ZPDsB5 zV@D^XbAqW=XmOqO&t|zRq)#?~b5-{V?$ji&Y*l>FwY$%7=U1|4k0;npa)+Y5<)2ct zSE4~lj|F4TN#I3EdrpE$k~IV zdk`Uw9L!5fS~{3LN}4;EmzA`6Fng8#1vPeGkdph9G=VVt6$VTDXb#AZps!@3qL-^J zV7Sw+w8M%UC0|usFL^}K&h=|bI!TzL3U7Vg69|Z(#GM zVqaHc(1hNf18s8kEou1C=BDtet0Bm*_bam3PRXAEX!BIKzEvDx5Pxp6vouWd=_&fHXt@KHb1sD_F!DkxbV2z zxP5VL@gecW@mu3h#a~ZwBxELRPB=O;X=KgFYoiiJhm6h}y>axFF}=o=j%iK|N-Rx0 zGS)G+cI^4Fmy!aLij%e{H6>k0x-l+qTwSt%a&_{T<9){GjbA_hQc7S-a?19U!zm{x zI9E;hF4a4AZR+WXBPTvH@lsk!+Oo74CwWZDpLAr>x9Ohgq3LPqRq3ZPJTj6qDl&Ft zoSz&$xoq;YQ^Kcg%j}++mw9fg_tfO6JEpc|WoA`nZOC4cy(jy4_76FZoUEL+InC2% zPFpdpY1)R8uCue@tw14&dqr@3z`Zp7J3wJEId&7P2sho)S_+0vBgV@*A&+mKT~|5q-#l5 zNqxzM(*C9OrAOy`&#x;BE6Xd}TXwNLpnOSrQw1u5Dppi%t2n>FW5L`7H476L?yJnI sJXPgel_vi{z^%sekbN1AQA@Aaap$379!zmRRNbDdy$(fnp?k;w0nU!^9RL6T diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index e08e651d91e..da581de1cc6 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -560,6 +560,8 @@ export class Codicon implements CSSIcon { public static readonly bellSlash = new Codicon('bell-slash', { fontCharacter: '\\ec08' }); public static readonly bellSlashDot = new Codicon('bell-slash-dot', { fontCharacter: '\\ec09' }); public static readonly commentUnresolved = new Codicon('comment-unresolved', { fontCharacter: '\\ec0a' }); + public static readonly gitPullRequestGoToChanges = new Codicon('git-pull-request-go-to-changes', { fontCharacter: '\\ec0b' }); + public static readonly gitPullRequestNewChanges = new Codicon('git-pull-request-new-changes', { fontCharacter: '\\ec0c' }); // derived icons, that could become separate icons From a3c4e5ffcee85677f1dd7e208cbd32bc84fee4d5 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Sat, 23 Jul 2022 01:37:12 -0400 Subject: [PATCH 194/197] Support multiple partial registrations to resolver (#155859) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Support multiple partial registrations to resolver * Fix tests * address some of my own feedback * lint sprüngli * Address PR feedback * Add test * Tests * support `detail` property and drop deprecated `openEditor` usages Co-authored-by: Benjamin Pasero --- src/vs/base/common/types.ts | 6 ++ src/vs/workbench/browser/layout.ts | 2 +- src/vs/workbench/common/editor.ts | 6 +- .../mergeEditor/browser/commands/commands.ts | 21 +++---- .../browser/commands/devCommands.ts | 22 +++---- .../mergeEditor/browser/mergeEditorInput.ts | 4 +- .../mergeEditor/browser/view/mergeEditor.ts | 43 +++---------- .../userDataSync/browser/userDataSync.ts | 16 ++--- src/vs/workbench/electron-sandbox/window.ts | 2 +- .../editor/browser/editorResolverService.ts | 61 ++++++++++++++++-- .../editor/common/editorResolverService.ts | 10 ++- .../browser/editorResolverService.test.ts | 63 +++++++++++++++++++ .../host/browser/browserHostService.ts | 2 +- 13 files changed, 175 insertions(+), 83 deletions(-) diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index 3e1cb2a7354..e3844cd39e8 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -275,3 +275,9 @@ export type UriDto = { [K in keyof T]: T[K] extends URI export function assertNever(value: never, message = 'Unreachable'): never { throw new Error(message); } + +/** + * Given an object with all optional properties, requires at least one to be defined. + * i.e. AtLeastOne; + */ +export type AtLeastOne }> = Partial & U[keyof U]; diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 9151c945a6c..d5f339775a3 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -578,7 +578,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi input2: { resource: filesToMerge[1].resource }, base: { resource: filesToMerge[2].resource }, result: { resource: filesToMerge[3].resource }, - options: { pinned: true, override: 'mergeEditor.Input' } // TODO@bpasero remove the override once the resolver is ready + options: { pinned: true } }]; } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 6f6ab509248..a886ffaee13 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -484,6 +484,8 @@ export interface IResourceDiffEditorInput extends IBaseUntypedEditorInput { readonly modified: IResourceEditorInput | ITextResourceEditorInput | IUntitledTextResourceEditorInput; } +export type IResourceMergeEditorInputSide = (IResourceEditorInput | ITextResourceEditorInput) & { detail?: string }; + /** * A resource merge editor input compares multiple editors * highlighting the differences for merging. @@ -496,12 +498,12 @@ export interface IResourceMergeEditorInput extends IBaseUntypedEditorInput { /** * The one changed version of the file. */ - readonly input1: IResourceEditorInput | ITextResourceEditorInput; + readonly input1: IResourceMergeEditorInputSide; /** * The second changed version of the file. */ - readonly input2: IResourceEditorInput | ITextResourceEditorInput; + readonly input2: IResourceMergeEditorInputSide; /** * The base common ancestor of the file to merge. diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts index 9e015121818..3c028ec8ebd 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts @@ -9,10 +9,10 @@ import { localize } from 'vs/nls'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { Action2, MenuId } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { EditorResolution } from 'vs/platform/editor/common/editor'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { API_OPEN_DIFF_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { IResourceMergeEditorInput } from 'vs/workbench/common/editor'; import { MergeEditorInput, MergeEditorInputData } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor'; import { ctxIsMergeEditor, ctxMergeEditorLayout } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; @@ -28,15 +28,14 @@ export class OpenMergeEditor extends Action2 { run(accessor: ServicesAccessor, ...args: unknown[]): void { const validatedArgs = IRelaxedOpenArgs.validate(args[0]); - const instaService = accessor.get(IInstantiationService); - const input = instaService.createInstance( - MergeEditorInput, - validatedArgs.base, - validatedArgs.input1, - validatedArgs.input2, - validatedArgs.output, - ); - accessor.get(IEditorService).openEditor(input, { preserveFocus: true, override: EditorResolution.DISABLED }); + const input: IResourceMergeEditorInput = { + base: { resource: validatedArgs.base }, + input1: { resource: validatedArgs.input1.uri, label: validatedArgs.input1.title, description: validatedArgs.input1.description, detail: validatedArgs.input1.detail }, + input2: { resource: validatedArgs.input2.uri, label: validatedArgs.input2.title, description: validatedArgs.input2.description, detail: validatedArgs.input2.detail }, + result: { resource: validatedArgs.output }, + options: { preserveFocus: true } + }; + accessor.get(IEditorService).openEditor(input); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts index 17df90e7d26..0f2b38ea5b4 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts @@ -10,16 +10,15 @@ import { localize } from 'vs/nls'; import { Action2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchFileService } from 'vs/workbench/services/files/common/files'; import { URI } from 'vs/base/common/uri'; -import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; +import { IResourceMergeEditorInput } from 'vs/workbench/common/editor'; import { ctxIsMergeEditor } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; -import { EditorResolution } from 'vs/platform/editor/common/editor'; interface MergeEditorContents { languageId: string; @@ -99,8 +98,6 @@ export class MergeEditorOpenContents extends Action2 { async run(accessor: ServicesAccessor): Promise { const service = accessor.get(IWorkbenchFileService); - const instaService = accessor.get(IInstantiationService); - const editorService = accessor.get(IEditorService); const inputService = accessor.get(IQuickInputService); const clipboardService = accessor.get(IClipboardService); const textModelService = accessor.get(ITextModelService); @@ -154,13 +151,12 @@ export class MergeEditorOpenContents extends Action2 { setLanguageId(resultUri, content.languageId), ]); - const input = instaService.createInstance( - MergeEditorInput, - baseUri, - { uri: input1Uri, title: 'Input 1', description: 'Input 1', detail: '(from JSON)' }, - { uri: input2Uri, title: 'Input 2', description: 'Input 2', detail: '(from JSON)' }, - resultUri, - ); - editorService.openEditor(input, { override: EditorResolution.DISABLED }); + const input: IResourceMergeEditorInput = { + base: { resource: baseUri }, + input1: { resource: input1Uri, label: 'Input 1', description: 'Input 1', detail: '(from JSON)' }, + input2: { resource: input2Uri, label: 'Input 2', description: 'Input 2', detail: '(from JSON)' }, + result: { resource: resultUri }, + }; + accessor.get(IEditorService).openEditor(input); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts index 9a4286f26b6..50470d8df66 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -140,8 +140,8 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements override toUntyped(): IResourceMergeEditorInput { return { - input1: { resource: this.input1.uri, label: this.input1.title, description: this.input1.description }, - input2: { resource: this.input2.uri, label: this.input2.title, description: this.input2.description }, + input1: { resource: this.input1.uri, label: this.input1.title, description: this.input1.description, detail: this.input1.detail }, + input2: { resource: this.input2.uri, label: this.input2.title, description: this.input2.description, detail: this.input2.detail }, base: { resource: this.base }, result: { resource: this.result }, options: { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index b17a1e8eebf..46aee59e160 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -47,7 +47,7 @@ import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/v import { ctxMergeBaseUri, ctxIsMergeEditor, ctxMergeEditorLayout, ctxMergeResultUri, MergeEditorLayoutTypes } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; import { settingsSashBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { EditorInputFactoryFunction, IEditorResolverService, MergeEditorInputFactoryFunction, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; +import { IEditorResolverService, MergeEditorInputFactoryFunction, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import './colors'; import { InputCodeEditorView } from './editors/inputCodeEditorView'; @@ -559,28 +559,6 @@ export class MergeEditorResolverContribution extends Disposable { ) { super(); - const editorInputFactory: EditorInputFactoryFunction = (editor) => { - return { - editor: instantiationService.createInstance( - MergeEditorInput, - editor.resource, - { - uri: editor.resource, - title: '', - description: '', - detail: '' - }, - { - uri: editor.resource, - title: '', - description: '', - detail: '' - }, - editor.resource - ) - }; - }; - const mergeEditorInputFactory: MergeEditorInputFactoryFunction = (mergeEditor: IResourceMergeEditorInput): EditorInputWithOptions => { return { editor: instantiationService.createInstance( @@ -588,15 +566,15 @@ export class MergeEditorResolverContribution extends Disposable { mergeEditor.base.resource, { uri: mergeEditor.input1.resource, - title: basename(mergeEditor.input1.resource), - description: '', - detail: '' + title: mergeEditor.input1.label ?? basename(mergeEditor.input1.resource), + description: mergeEditor.input1.description ?? '', + detail: mergeEditor.input1.detail }, { uri: mergeEditor.input2.resource, - title: basename(mergeEditor.input2.resource), - description: '', - detail: '' + title: mergeEditor.input2.label ?? basename(mergeEditor.input2.resource), + description: mergeEditor.input2.description ?? '', + detail: mergeEditor.input2.detail }, mergeEditor.result.resource ) @@ -606,14 +584,13 @@ export class MergeEditorResolverContribution extends Disposable { this._register(editorResolverService.registerEditor( `*`, { - id: MergeEditorInput.ID, - label: localize('editor.mergeEditor.label', "Merge Editor"), + id: DEFAULT_EDITOR_ASSOCIATION.id, + label: DEFAULT_EDITOR_ASSOCIATION.displayName, detail: DEFAULT_EDITOR_ASSOCIATION.providerDisplayName, - priority: RegisteredEditorPriority.option + priority: RegisteredEditorPriority.builtin }, {}, { - createEditorInput: editorInputFactory, createMergeEditorInput: mergeEditorInputFactory } )); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 55f61b3c685..d201a99a8a6 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -57,10 +57,8 @@ import { IUserDataInitializationService } from 'vs/workbench/services/userData/b import { MarkdownString } from 'vs/base/common/htmlContent'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ctxIsMergeEditor, ctxMergeBaseUri } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; -import { EditorResolution } from 'vs/platform/editor/common/editor'; const CONTEXT_CONFLICTS_SOURCES = new RawContextKey('conflictsSources', ''); @@ -730,14 +728,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo for (const conflict of conflicts) { const remoteResourceName = localize({ key: 'remoteResourceName', comment: ['remote as in file in cloud'] }, "{0} (Remote)", basename(conflict.remoteResource)); const localResourceName = localize('localResourceName', "{0} (Local)", basename(conflict.remoteResource)); - const input = this.instantiationService.createInstance( - MergeEditorInput, - conflict.baseResource, - { title: localize('Yours', 'Yours'), description: localResourceName, detail: undefined, uri: conflict.localResource }, - { title: localize('Theirs', 'Theirs'), description: remoteResourceName, detail: undefined, uri: conflict.remoteResource }, - conflict.previewResource, - ); - await this.editorService.openEditor(input, { override: EditorResolution.DISABLED }); + await this.editorService.openEditor({ + input1: { resource: conflict.remoteResource, label: localize('Theirs', 'Theirs'), description: remoteResourceName }, + input2: { resource: conflict.localResource, label: localize('Yours', 'Yours'), description: localResourceName }, + base: { resource: conflict.baseResource }, + result: { resource: conflict.previewResource } + }); } } diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 131d7741598..54779255d79 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -912,7 +912,7 @@ export class NativeWindow extends Disposable { input2: { resource: resources[1].resource }, base: { resource: resources[2].resource }, result: { resource: resources[3].resource }, - options: { pinned: true, override: 'mergeEditor.Input' } // TODO@bpasero remove the override once the resolver is ready + options: { pinned: true } }; editors.push(mergeEditor); } else if (diffMode && isResourceEditorInput(resources[0]) && isResourceEditorInput(resources[1])) { diff --git a/src/vs/workbench/services/editor/browser/editorResolverService.ts b/src/vs/workbench/services/editor/browser/editorResolverService.ts index ec9fc2b563b..c00b46869c6 100644 --- a/src/vs/workbench/services/editor/browser/editorResolverService.ts +++ b/src/vs/workbench/services/editor/browser/editorResolverService.ts @@ -51,7 +51,8 @@ export class EditorResolverService extends Disposable implements IEditorResolver private static readonly conflictingDefaultsStorageID = 'editorOverrideService.conflictingDefaults'; // Data Stores - private _editors: Map = new Map(); + private _editors: Map> = new Map>(); + private _flattenedEditors: Map = new Map(); private cache: Set | undefined; constructor( @@ -235,18 +236,29 @@ export class EditorResolverService extends Disposable implements IEditorResolver ): IDisposable { let registeredEditor = this._editors.get(globPattern); if (registeredEditor === undefined) { - registeredEditor = []; + registeredEditor = new Map(); this._editors.set(globPattern, registeredEditor); } - const remove = insert(registeredEditor, { + + let editorsWithId = registeredEditor.get(editorInfo.id); + if (editorsWithId === undefined) { + editorsWithId = []; + } + const remove = insert(editorsWithId, { globPattern, editorInfo, options, editorFactoryObject }); + registeredEditor.set(editorInfo.id, editorsWithId); + this._flattenedEditors = this._flattenEditorsMap(); this._onDidChangeEditorRegistrations.fire(); return toDisposable(() => { remove(); + if (editorsWithId && editorsWithId.length === 0) { + registeredEditor?.delete(editorInfo.id); + } + this._flattenedEditors = this._flattenEditorsMap(); this._onDidChangeEditorRegistrations.fire(); }); } @@ -281,11 +293,43 @@ export class EditorResolverService extends Disposable implements IEditorResolver return associations; } + /** + * Given the nested nature of the editors map, we should merge factories of the same glob and id to make it flat + */ + private _flattenEditorsMap() { + const editors = new Map(); + for (const [glob, value] of this._editors) { + const registeredEditors: RegisteredEditors = []; + for (const editors of value.values()) { + let registeredEditor: RegisteredEditor | undefined = undefined; + // Merge all editors with the same id and glob pattern together + for (const editor of editors) { + if (!registeredEditor) { + registeredEditor = { + editorInfo: editor.editorInfo, + globPattern: editor.globPattern, + options: {}, + editorFactoryObject: {} + }; + } + // Merge options and factories + registeredEditor.options = { ...registeredEditor.options, ...editor.options }; + registeredEditor.editorFactoryObject = { ...registeredEditor.editorFactoryObject, ...editor.editorFactoryObject }; + } + if (registeredEditor) { + registeredEditors.push(registeredEditor); + } + } + editors.set(glob, registeredEditors); + } + return editors; + } + /** * Returns all editors as an array. Possible to contain duplicates */ private get _registeredEditors(): RegisteredEditors { - return flatten(Array.from(this._editors.values())); + return flatten(Array.from(this._flattenedEditors.values())); } updateUserAssociations(globPattern: string, editorID: string): void { @@ -306,7 +350,7 @@ export class EditorResolverService extends Disposable implements IEditorResolver const userSettings = this.getAssociationsForResource(resource); const matchingEditors: RegisteredEditor[] = []; // Then all glob patterns - for (const [key, editors] of this._editors) { + for (const [key, editors] of this._flattenedEditors) { for (const editor of editors) { const foundInSettings = userSettings.find(setting => setting.viewType === editor.editorInfo.id); if ((foundInSettings && editor.editorInfo.priority !== RegisteredEditorPriority.exclusive) || globMatchesResource(key, resource)) { @@ -446,6 +490,11 @@ export class EditorResolverService extends Disposable implements IEditorResolver } } + // If no factory is above, return flow back to caller letting them know we could not resolve it + if (!selectedEditor.editorFactoryObject.createEditorInput) { + return; + } + // Respect options passed back const inputWithOptions = await selectedEditor.editorFactoryObject.createEditorInput(editor, group); options = inputWithOptions.options ?? options; @@ -739,7 +788,7 @@ export class EditorResolverService extends Disposable implements IEditorResolver const cacheStorage: Set = new Set(); // Store just the relative pattern pieces without any path info - for (const [globPattern, contribPoint] of this._editors) { + for (const [globPattern, contribPoint] of this._flattenedEditors) { const nonOptional = !!contribPoint.find(c => c.editorInfo.priority !== RegisteredEditorPriority.option && c.editorInfo.id !== DEFAULT_EDITOR_ASSOCIATION.id); // Don't keep a cache of the optional ones as those wouldn't be opened on start anyways if (!nonOptional) { diff --git a/src/vs/workbench/services/editor/common/editorResolverService.ts b/src/vs/workbench/services/editor/common/editorResolverService.ts index b4e47e8d4f2..9de7b992657 100644 --- a/src/vs/workbench/services/editor/common/editorResolverService.ts +++ b/src/vs/workbench/services/editor/common/editorResolverService.ts @@ -19,6 +19,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { EditorInputWithOptions, EditorInputWithOptionsAndGroup, IResourceDiffEditorInput, IResourceMergeEditorInput, IUntitledTextResourceEditorInput, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { PreferredGroup } from 'vs/workbench/services/editor/common/editorService'; +import { AtLeastOne } from 'vs/base/common/types'; export const IEditorResolverService = createDecorator('editorResolverService'); @@ -109,13 +110,15 @@ export type DiffEditorInputFactoryFunction = (diffEditorInput: IResourceDiffEdit export type MergeEditorInputFactoryFunction = (mergeEditorInput: IResourceMergeEditorInput, group: IEditorGroup) => EditorInputFactoryResult; -export type EditorInputFactoryObject = { - createEditorInput: EditorInputFactoryFunction; +type EditorInputFactories = { + createEditorInput?: EditorInputFactoryFunction; createUntitledEditorInput?: UntitledEditorInputFactoryFunction; createDiffEditorInput?: DiffEditorInputFactoryFunction; createMergeEditorInput?: MergeEditorInputFactoryFunction; }; +export type EditorInputFactoryObject = AtLeastOne; + export interface IEditorResolverService { readonly _serviceBrand: undefined; /** @@ -138,7 +141,8 @@ export interface IEditorResolverService { readonly onDidChangeEditorRegistrations: Event; /** - * Registers a specific editor. + * Registers a specific editor. Editors with the same glob pattern and ID will be grouped together by the resolver. + * This allows for registration of the factories in different locations * @param globPattern The glob pattern for this registration * @param editorInfo Information about the registration * @param options Specific options which apply to this registration diff --git a/src/vs/workbench/services/editor/test/browser/editorResolverService.test.ts b/src/vs/workbench/services/editor/test/browser/editorResolverService.test.ts index 5bfe40ad314..2b3a585ed5c 100644 --- a/src/vs/workbench/services/editor/test/browser/editorResolverService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorResolverService.test.ts @@ -382,4 +382,67 @@ suite('EditorResolverService', () => { assert.strictEqual(service.getEditors().length, editors.length); assert.strictEqual(service.getEditors().some(editor => editor.id === 'TEST_EDITOR'), false); }); + + test('Multiple registrations to same glob and id #155859', async () => { + const [part, service, accessor] = await createEditorResolverService(); + const testEditorInfo = { + id: 'TEST_EDITOR', + label: 'Test Editor Label', + detail: 'Test Editor Details', + priority: RegisteredEditorPriority.default + }; + const registeredSingleEditor = service.registerEditor('*.test', + testEditorInfo, + {}, + { + createEditorInput: ({ resource, options }, group) => ({ editor: new TestFileEditorInput(URI.parse(resource.toString()), TEST_EDITOR_INPUT_ID) }) + } + ); + + const registeredDiffEditor = service.registerEditor('*.test', + testEditorInfo, + {}, + { + createDiffEditorInput: ({ modified, original, options }, group) => ({ + editor: accessor.instantiationService.createInstance( + DiffEditorInput, + 'name', + 'description', + new TestFileEditorInput(URI.parse(original.toString()), TEST_EDITOR_INPUT_ID), + new TestFileEditorInput(URI.parse(modified.toString()), TEST_EDITOR_INPUT_ID), + undefined) + }) + } + ); + + // Resolve a diff + let resultingResolution = await service.resolveEditor({ + original: { resource: URI.file('my://resource-basics.test') }, + modified: { resource: URI.file('my://resource-basics.test') } + }, part.activeGroup); + assert.ok(resultingResolution); + assert.notStrictEqual(typeof resultingResolution, 'number'); + if (resultingResolution !== ResolvedStatus.ABORT && resultingResolution !== ResolvedStatus.NONE) { + assert.strictEqual(resultingResolution.editor.typeId, 'workbench.editors.diffEditorInput'); + resultingResolution.editor.dispose(); + } else { + assert.fail(); + } + + // Remove diff registration + registeredDiffEditor.dispose(); + + // Resolve a diff again, expected failure + resultingResolution = await service.resolveEditor({ + original: { resource: URI.file('my://resource-basics.test') }, + modified: { resource: URI.file('my://resource-basics.test') } + }, part.activeGroup); + assert.ok(resultingResolution); + assert.strictEqual(typeof resultingResolution, 'number'); + if (resultingResolution !== ResolvedStatus.NONE) { + assert.fail(); + } + + registeredSingleEditor.dispose(); + }); }); diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index 0aebd31d5b9..25fca6a0136 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -268,7 +268,7 @@ export class BrowserHostService extends Disposable implements IHostService { input2: { resource: editors[1].resource }, base: { resource: editors[2].resource }, result: { resource: editors[3].resource }, - options: { pinned: true, override: 'mergeEditor.Input' } // TODO@bpasero remove the override once the resolver is ready + options: { pinned: true } }); } From 6ab408190e6a76bf460b4cee205a830b66d0e7c7 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Sun, 24 Jul 2022 14:40:54 +0200 Subject: [PATCH 195/197] Use white as editor background (#155993) Fixes #154257: Use white as editor background --- src/vs/platform/theme/common/colorRegistry.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 9bfb2dfc45a..3305267657d 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -304,10 +304,8 @@ export const sashHoverBorder = registerColor('sash.hoverBorder', { dark: focusBo /** * Editor background color. - * Because of bug https://monacotools.visualstudio.com/DefaultCollection/Monaco/_workitems/edit/13254 - * we are *not* using the color white (or #ffffff, rgba(255,255,255)) but something very close to white. */ -export const editorBackground = registerColor('editor.background', { light: '#fffffe', dark: '#1E1E1E', hcDark: Color.black, hcLight: Color.white }, nls.localize('editorBackground', "Editor background color.")); +export const editorBackground = registerColor('editor.background', { light: '#ffffff', dark: '#1E1E1E', hcDark: Color.black, hcLight: Color.white }, nls.localize('editorBackground', "Editor background color.")); /** * Editor foreground color. From 130a0b774ab71115fb9c94cbfcbb9a80d5074ea7 Mon Sep 17 00:00:00 2001 From: Harald Kirschner Date: Sun, 24 Jul 2022 14:41:15 +0200 Subject: [PATCH 196/197] Adding digitarald owner comment (#155502) * Adding my owner comment. * Adding property comments Co-authored-by: Logan Ramos --- .../contrib/extensions/browser/extensionsWorkbenchService.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 6cf90434c7b..b85bfe7485f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -60,8 +60,9 @@ interface InstalledExtensionsEvent { } interface ExtensionsLoadClassification extends GDPRClassification { owner: 'digitarald'; - readonly extensionIds: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; - readonly count: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; + comment: 'Helps to understand which extensions are the most actively used.', + readonly extensionIds: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The list of extension ids that are installed.' }; + readonly count: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The number of extensions that are installed.' }; } export class Extension implements IExtension { From 0bdda74589cc0dcfcf51698ee6d50ae0a4cacac7 Mon Sep 17 00:00:00 2001 From: Ikko Ashimine Date: Sun, 24 Jul 2022 23:52:37 +0900 Subject: [PATCH 197/197] Fix typo in languageDetectionWorkerServiceImpl.ts (#155923) --- .../browser/languageDetectionWorkerServiceImpl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts index b2a2279b3c5..cf5d6749fa6 100644 --- a/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts +++ b/src/vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl.ts @@ -206,7 +206,7 @@ export class LanguageDetectionWorkerHost { owner: 'TylerLeonhardt'; comment: 'Helps understand how effective language detection is via confidences and how long it takes to run'; languages: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The languages that are guessed' }; - confidences: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The confidences of each langauge guessed' }; + confidences: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The confidences of each language guessed' }; timeSpent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The time it took to run language detection' }; };