diff --git a/build/azure-pipelines/linux/product-build-linux-test.yml b/build/azure-pipelines/linux/product-build-linux-test.yml index a5a30a340fc..b5fa333311f 100644 --- a/build/azure-pipelines/linux/product-build-linux-test.yml +++ b/build/azure-pipelines/linux/product-build-linux-test.yml @@ -38,6 +38,8 @@ steps: stat $ELECTRON_ROOT/chrome-sandbox displayName: Change setuid helper binary permission + - template: ../common/install-builtin-extensions.yml + - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - script: ./scripts/test.sh --tfs "Unit Tests" diff --git a/extensions/typescript-language-features/media/nodejsWalkthroughIcon.png b/extensions/typescript-language-features/media/nodejsWalkthroughIcon.png deleted file mode 100644 index 10a8e5b822b..00000000000 Binary files a/extensions/typescript-language-features/media/nodejsWalkthroughIcon.png and /dev/null differ diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 30ccbd8558f..9fef982d600 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -1562,59 +1562,6 @@ } ] } - ], - "walkthroughs": [ - { - "id": "nodejsWelcome", - "title": "%walkthroughs.nodejsWelcome.title%", - "icon": "media/nodejsWalkthroughIcon.png", - "description": "%walkthroughs.nodejsWelcome.description%", - "when": "false", - "steps": [ - { - "id": "walkthroughs.nodejsWelcome.downloadNode.forMacOrWindows", - "title": "%walkthroughs.nodejsWelcome.downloadNode.forMacOrWindows.title%", - "description": "%walkthroughs.nodejsWelcome.downloadNode.forMacOrWindows.description%", - "media": { - "svg": "resources/walkthroughs/install-node-js.svg" - }, - "when": "isWindows || isMac" - }, - { - "id": "walkthroughs.nodejsWelcome.downloadNode.forLinux", - "title": "%walkthroughs.nodejsWelcome.downloadNode.forLinux.title%", - "description": "%walkthroughs.nodejsWelcome.downloadNode.forLinux.description%", - "media": { - "svg": "resources/walkthroughs/install-node-js.svg" - }, - "when": "isLinux" - }, - { - "id": "walkthroughs.nodejsWelcome.makeJsFile", - "title": "%walkthroughs.nodejsWelcome.makeJsFile.title%", - "description": "%walkthroughs.nodejsWelcome.makeJsFile.description%", - "media": { - "svg": "resources/walkthroughs/create-a-js-file.svg" - } - }, - { - "id": "walkthroughs.nodejsWelcome.debugJsFile", - "title": "%walkthroughs.nodejsWelcome.debugJsFile.title%", - "description": "%walkthroughs.nodejsWelcome.debugJsFile.description%", - "media": { - "svg": "resources/walkthroughs/debug-and-run.svg" - } - }, - { - "id": "walkthroughs.nodejsWelcome.learnMoreAboutJs", - "title": "%walkthroughs.nodejsWelcome.learnMoreAboutJs.title%", - "description": "%walkthroughs.nodejsWelcome.learnMoreAboutJs.description%", - "media": { - "svg": "resources/walkthroughs/learn-more.svg" - } - } - ] - } ] }, "repository": { diff --git a/extensions/typescript-language-features/src/experimentationService.ts b/extensions/typescript-language-features/src/experimentationService.ts index 86d1fd42b55..5dc458277d4 100644 --- a/extensions/typescript-language-features/src/experimentationService.ts +++ b/extensions/typescript-language-features/src/experimentationService.ts @@ -18,7 +18,7 @@ export class ExperimentationService { constructor(telemetryReporter: IExperimentationTelemetryReporter, id: string, version: string, globalState: vscode.Memento) { this._telemetryReporter = telemetryReporter; - this._experimentationServicePromise = createExperimentationService(this._telemetryReporter, id, version, globalState); + this._experimentationServicePromise = createTasExperimentationService(this._telemetryReporter, id, version, globalState); } public async getTreatmentVariable(name: K, defaultValue: ExperimentTypes[K]): Promise { @@ -32,7 +32,7 @@ export class ExperimentationService { } } -export async function createExperimentationService( +export async function createTasExperimentationService( reporter: IExperimentationTelemetryReporter, id: string, version: string, diff --git a/extensions/typescript-language-features/src/extension.ts b/extensions/typescript-language-features/src/extension.ts index c92617f80a2..22fdd25bb71 100644 --- a/extensions/typescript-language-features/src/extension.ts +++ b/extensions/typescript-language-features/src/extension.ts @@ -17,7 +17,6 @@ import { NodeLogDirectoryProvider } from './tsServer/logDirectoryProvider.electr import { ElectronServiceProcessFactory } from './tsServer/serverProcess.electron'; import { DiskTypeScriptVersionProvider } from './tsServer/versionProvider.electron'; import { ActiveJsTsEditorTracker } from './ui/activeJsTsEditorTracker'; -import { JsWalkthroughState, registerJsNodeWalkthrough } from './ui/jsNodeWalkthrough.electron'; import { ElectronServiceConfigurationProvider } from './configuration/configuration.electron'; import { onCaseInsensitiveFileSystem } from './utils/fs.electron'; import { Logger } from './logging/logger'; @@ -43,9 +42,6 @@ export function activate( const activeJsTsEditorTracker = new ActiveJsTsEditorTracker(); context.subscriptions.push(activeJsTsEditorTracker); - const jsWalkthroughState = new JsWalkthroughState(); - context.subscriptions.push(jsWalkthroughState); - let experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined; const packageInfo = getPackageInfo(context); if (packageInfo) { @@ -77,7 +73,6 @@ export function activate( }); registerBaseCommands(commandManager, lazyClientHost, pluginManager, activeJsTsEditorTracker); - registerJsNodeWalkthrough(commandManager, jsWalkthroughState); import('./task/taskProvider').then(module => { context.subscriptions.push(module.register(lazyClientHost.map(x => x.serviceClient))); diff --git a/extensions/typescript-language-features/src/ui/jsNodeWalkthrough.electron.ts b/extensions/typescript-language-features/src/ui/jsNodeWalkthrough.electron.ts deleted file mode 100644 index 1c9ceeb87ff..00000000000 --- a/extensions/typescript-language-features/src/ui/jsNodeWalkthrough.electron.ts +++ /dev/null @@ -1,198 +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 cp from 'child_process'; -import * as vscode from 'vscode'; - -import { CommandManager } from '../commands/commandManager'; -import { Disposable } from '../utils/dispose'; - - -export async function nodeWasResolvable(): Promise { - let execStr: string; - switch (process.platform) { - case 'win32': - execStr = 'where node'; - break; - case 'aix': - case 'cygwin': - case 'darwin': - case 'freebsd': - case 'haiku': - case 'linux': - case 'netbsd': - case 'openbsd': - case 'sunos': - execStr = 'which node'; - break; - default: - return false; - } - - return new Promise(resolve => { - cp.exec(execStr, { windowsHide: true }, err => { - resolve(!err); - }); - }); -} - -export class JsWalkthroughState extends Disposable { - exampleJsDocument: vscode.TextDocument | undefined = undefined; - - override dispose() { - this.exampleJsDocument = undefined; - } -} - -export class CreateNewJSFileCommand { - public static readonly id = 'javascript-walkthrough.commands.createJsFile'; - public readonly id = CreateNewJSFileCommand.id; - - constructor( - private readonly walkthroughState: JsWalkthroughState - ) { } - - public execute() { - createNewJSFile(this.walkthroughState); - } -} - -export class DebugJsFileCommand { - public static readonly id = 'javascript-walkthrough.commands.debugJsFile'; - public readonly id = DebugJsFileCommand.id; - - constructor( - private readonly walkthroughState: JsWalkthroughState - ) { } - - public execute() { - debugJsFile(this.walkthroughState); - } -} - -export class NodeInstallationFoundCommand { - public static readonly id = 'javascript-walkthrough.commands.nodeInstallationFound'; - public readonly id = NodeInstallationFoundCommand.id; - public execute() { } -} - -async function createNewJSFile(walkthroughState: JsWalkthroughState) { - const newFile = await vscode.workspace.openTextDocument({ - language: 'javascript', - content: `// Write a message to the console.\nconsole.log('hello world!');\n`, - }); - walkthroughState.exampleJsDocument = newFile; - return vscode.window.showTextDocument(newFile, vscode.ViewColumn.Beside); -} - -async function debugJsFile(walkthroughState: JsWalkthroughState) { - const hasNode = await nodeWasResolvable(); - if (!hasNode) { - const reloadResponse = vscode.l10n.t("Reload VS Code"); - const debugAnywayResponse = vscode.l10n.t("Try Debugging Anyway"); - const dismissResponse = vscode.l10n.t("Dismiss"); - const response = await vscode.window.showErrorMessage( - // The message - vscode.l10n.t("We couldn\'t find Node.js on this computer. If you just installed it, you might need to reload VS Code."), - // The options - reloadResponse, - debugAnywayResponse, - dismissResponse, - ); - - if (response === undefined || response === dismissResponse) { - return; - } - if (response === reloadResponse) { - vscode.commands.executeCommand('workbench.action.reloadWindow'); - return; - } - } - tryDebugRelevantDocument(walkthroughState.exampleJsDocument, 'javascript', ['.mjs', '.js'], () => createNewJSFile(walkthroughState)); -} - -type DocSearchResult = - | { kind: 'visible'; editor: vscode.TextEditor } - | { kind: 'hidden'; uri: vscode.Uri } - | { kind: 'not-found' }; - -async function tryDebugRelevantDocument(lastDocument: vscode.TextDocument | undefined, languageId: string, languageExtensions: [string, ...string[]], createFileAndFocus: () => Promise): Promise { - let searchResult!: DocSearchResult; - for (const languageExtension of languageExtensions) { - searchResult = tryFindRelevantDocument(lastDocument, languageId, languageExtension); - if (searchResult.kind !== 'not-found') { - break; - } - } - - let editor: vscode.TextEditor; - // If not, make one. - switch (searchResult.kind) { - case 'visible': - // Focus if necessary. - editor = searchResult.editor; - if (vscode.window.activeTextEditor !== editor) { - await vscode.window.showTextDocument(editor.document, { - viewColumn: vscode.ViewColumn.Beside, - }); - } - break; - case 'hidden': - editor = await vscode.window.showTextDocument(searchResult.uri, { - viewColumn: vscode.ViewColumn.Beside, - }); - break; - case 'not-found': - editor = await createFileAndFocus(); - break; - } - - await Promise.all([ - vscode.commands.executeCommand('workbench.action.debug.start'), - vscode.commands.executeCommand('workbench.debug.action.focusRepl'), - ]); - -} - -/** Tries to find a relevant {@link vscode.TextEditor} or a {@link vscode.Uri} for an open document */ -function tryFindRelevantDocument(lastDocument: vscode.TextDocument | undefined, languageId: string, languageExtension: string): DocSearchResult { - let editor: vscode.TextEditor | undefined; - - // Try to find the document created from the last step. - if (lastDocument) { - editor = vscode.window.visibleTextEditors.find(editor => editor.document === lastDocument); - } - - // If we couldn't find that, find a visible document with the desired language. - editor ??= vscode.window.visibleTextEditors.find(editor => editor.document.languageId === languageId); - if (editor) { - return { - kind: 'visible', - editor, - }; - } - - // If we still couldn't find that, find a possibly not-visible document. - for (const tabGroup of vscode.window.tabGroups.all) { - for (const tab of tabGroup.tabs) { - if (tab.input instanceof vscode.TabInputText && tab.input.uri.path.endsWith(languageExtension)) { - return { - kind: 'hidden', - uri: tab.input.uri, - }; - } - } - } - - return { kind: 'not-found' }; -} - -export function registerJsNodeWalkthrough( - commandManager: CommandManager, - jsWalkthroughState: JsWalkthroughState, -) { - commandManager.register(new CreateNewJSFileCommand(jsWalkthroughState)); - commandManager.register(new DebugJsFileCommand(jsWalkthroughState)); -} diff --git a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts index 7c3aeb8684e..4aa2558480f 100644 --- a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts +++ b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts @@ -12,7 +12,6 @@ import { IQuickInputService, IQuickInputButton, IQuickPickItem, IQuickPick, IInp import { CancellationToken } from 'vs/base/common/cancellation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { EditorScopedLayoutService } from 'vs/editor/standalone/browser/standaloneLayoutService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IQuickInputControllerHost, QuickInputController } from 'vs/platform/quickinput/browser/quickInput'; @@ -29,10 +28,9 @@ class EditorScopedQuickInputService extends QuickInputService { @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, - @IAccessibilityService accessibilityService: IAccessibilityService, @ICodeEditorService codeEditorService: ICodeEditorService ) { - super(instantiationService, contextKeyService, themeService, accessibilityService, new EditorScopedLayoutService(editor.getContainerDomNode(), codeEditorService)); + super(instantiationService, contextKeyService, themeService, new EditorScopedLayoutService(editor.getContainerDomNode(), codeEditorService)); // Use the passed in code editor as host for the quick input widget const contribution = QuickInputEditorContribution.get(editor); diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 685dc0a1341..836844272bc 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -40,7 +40,6 @@ export interface IQuickInputOptions { idPrefix: string; container: HTMLElement; ignoreFocusOut(): boolean; - isScreenReaderOptimized(): boolean; backKeybindingLabel(): string | undefined; setContextKey(id?: string): void; linkOpenerDelegate(content: string): void; @@ -114,10 +113,8 @@ interface QuickInputUI { onDidTriggerButton: Event; ignoreFocusOut: boolean; keyMods: Writeable; - isScreenReaderOptimized(): boolean; show(controller: QuickInput): void; setVisibilities(visibilities: Visibilities): void; - setComboboxAccessibility(enabled: boolean): void; setEnabled(enabled: boolean): void; setContextKey(contextKey?: string): void; linkOpenerDelegate(content: string): void; @@ -928,7 +925,7 @@ class QuickPick extends QuickInput implements IQuickPi this.visibleDisposables.add(this.registerQuickNavigation()); this.valueSelectionUpdated = true; } - super.show(); // TODO: Why have show() bubble up while update() trickles down? (Could move setComboboxAccessibility() here.) + super.show(); // TODO: Why have show() bubble up while update() trickles down? } private handleAccept(inBackground: boolean): void { @@ -1039,8 +1036,8 @@ class QuickPick extends QuickInput implements IQuickPi ariaLabel += ` - ${this.title}`; } } - if (this.ui.inputBox.ariaLabel !== ariaLabel) { - this.ui.inputBox.ariaLabel = ariaLabel; + if (this.ui.list.ariaLabel !== ariaLabel) { + this.ui.list.ariaLabel = ariaLabel; } this.ui.list.matchOnDescription = this.matchOnDescription; this.ui.list.matchOnDetail = this.matchOnDetail; @@ -1054,10 +1051,6 @@ class QuickPick extends QuickInput implements IQuickPi this.ui.checkAll.checked = this.ui.list.getAllVisibleChecked(); this.ui.visibleCount.setCount(this.ui.list.getVisibleCount()); this.ui.count.setCount(this.ui.list.getCheckedCount()); - // Ensure no item is focused when using a screenreader when items update (#57501 & #166920 & #176848) - if (this.ui.isScreenReaderOptimized() && ariaLabel && visibilities.inputBox) { - this._itemActivation = ItemActivation.NONE; - } switch (this._itemActivation) { case ItemActivation.NONE: this._itemActivation = ItemActivation.FIRST; // only valid once, then unset @@ -1104,7 +1097,6 @@ class QuickPick extends QuickInput implements IQuickPi } this.ui.customButton.label = this.customLabel || ''; this.ui.customButton.element.title = this.customHover || ''; - this.ui.setComboboxAccessibility(true); if (!visibilities.inputBox) { // we need to move focus into the tree to detect keybindings // properly when the input box is not visible (quick nav) @@ -1235,7 +1227,6 @@ export class QuickInputController extends Disposable { private ui: QuickInputUI | undefined; private dimension?: dom.IDimension; private titleBarOffset?: number; - private comboboxAccessibility = false; private enabled = true; private readonly onDidAcceptEmitter = this._register(new Emitter()); private readonly onDidCustomEmitter = this._register(new Emitter()); @@ -1345,7 +1336,12 @@ export class QuickInputController extends Disposable { const progressBar = new ProgressBar(container, this.styles.progressBar); progressBar.getContainer().classList.add('quick-input-progress'); - const list = this._register(new QuickInputList(container, this.idPrefix + 'list', this.options)); + const listId = this.idPrefix + 'list'; + const list = this._register(new QuickInputList(container, listId, this.options)); + inputBox.setAttribute('aria-controls', listId); + this._register(list.onDidChangeFocus(() => { + inputBox.setAttribute('aria-activedescendant', list.getActiveDescendant() ?? ''); + })); this._register(list.onChangedAllVisibleChecked(checked => { checkAll.checked = checked; })); @@ -1364,11 +1360,6 @@ export class QuickInputController extends Disposable { } }, 0); })); - this._register(list.onDidChangeFocus(() => { - if (this.comboboxAccessibility) { - this.getUI().inputBox.setAttribute('aria-activedescendant', this.getUI().list.getActiveDescendant() || ''); - } - })); const focusTracker = dom.trackFocus(container); this._register(focusTracker); @@ -1459,11 +1450,9 @@ export class QuickInputController extends Disposable { onDidTriggerButton: this.onDidTriggerButtonEmitter.event, ignoreFocusOut: false, keyMods: this.keyMods, - isScreenReaderOptimized: () => this.options.isScreenReaderOptimized(), show: controller => this.show(controller), hide: () => this.hide(), setVisibilities: visibilities => this.setVisibilities(visibilities), - setComboboxAccessibility: enabled => this.setComboboxAccessibility(enabled), setEnabled: enabled => this.setEnabled(enabled), setContextKey: contextKey => this.options.setContextKey(contextKey), linkOpenerDelegate: content => this.options.linkOpenerDelegate(content) @@ -1694,8 +1683,6 @@ export class QuickInputController extends Disposable { ui.list.matchOnLabel = true; ui.list.sortByLabel = true; ui.ignoreFocusOut = false; - this.setComboboxAccessibility(false); - ui.inputBox.ariaLabel = ''; ui.inputBox.toggles = undefined; const backKeybindingLabel = this.options.backKeybindingLabel(); @@ -1725,24 +1712,6 @@ export class QuickInputController extends Disposable { this.updateLayout(); // TODO } - private setComboboxAccessibility(enabled: boolean) { - if (enabled !== this.comboboxAccessibility) { - const ui = this.getUI(); - this.comboboxAccessibility = enabled; - if (this.comboboxAccessibility) { - ui.inputBox.setAttribute('role', 'combobox'); - ui.inputBox.setAttribute('aria-haspopup', 'true'); - ui.inputBox.setAttribute('aria-autocomplete', 'list'); - ui.inputBox.setAttribute('aria-activedescendant', ui.list.getActiveDescendant() || ''); - } else { - ui.inputBox.removeAttribute('role'); - ui.inputBox.removeAttribute('aria-haspopup'); - ui.inputBox.removeAttribute('aria-autocomplete'); - ui.inputBox.removeAttribute('aria-activedescendant'); - } - } - } - private setEnabled(enabled: boolean) { if (enabled !== this.enabled) { this.enabled = enabled; diff --git a/src/vs/platform/quickinput/browser/quickInputBox.ts b/src/vs/platform/quickinput/browser/quickInputBox.ts index d85d88f212c..cebb5ce460d 100644 --- a/src/vs/platform/quickinput/browser/quickInputBox.ts +++ b/src/vs/platform/quickinput/browser/quickInputBox.ts @@ -28,6 +28,11 @@ export class QuickInputBox extends Disposable { super(); this.container = dom.append(this.parent, $('.quick-input-box')); this.findInput = this._register(new FindInput(this.container, undefined, { label: '', inputBoxStyles, toggleStyles })); + const input = this.findInput.inputBox.inputElement; + input.role = 'combobox'; + input.ariaHasPopup = 'menu'; + input.ariaAutoComplete = 'list'; + input.ariaExpanded = 'true'; } onKeyDown = (handler: (event: StandardKeyboardEvent) => void): IDisposable => { @@ -74,14 +79,6 @@ export class QuickInputBox extends Disposable { this.findInput.inputBox.setPlaceHolder(placeholder); } - get ariaLabel() { - return this.findInput.inputBox.getAriaLabel(); - } - - set ariaLabel(ariaLabel: string) { - this.findInput.inputBox.setAriaLabel(ariaLabel); - } - get password() { return this.findInput.inputBox.inputElement.type === 'password'; } diff --git a/src/vs/platform/quickinput/browser/quickInputList.ts b/src/vs/platform/quickinput/browser/quickInputList.ts index aac73b3a9a1..ca447687db6 100644 --- a/src/vs/platform/quickinput/browser/quickInputList.ts +++ b/src/vs/platform/quickinput/browser/quickInputList.ts @@ -325,7 +325,6 @@ export class QuickInputList { ) { this.id = id; this.container = dom.append(this.parent, $('.quick-input-list')); - const delegate = new ListElementDelegate(); const accessibilityProvider = new QuickInputAccessibilityProvider(); this.list = options.createList('QuickInput', this.container, delegate, [new ListElementRenderer()], { @@ -464,6 +463,14 @@ export class QuickInputList { this.list.scrollTop = scrollTop; } + get ariaLabel() { + return this.list.getHTMLElement().ariaLabel; + } + + set ariaLabel(label: string | null) { + this.list.getHTMLElement().ariaLabel = label; + } + getAllVisibleChecked() { return this.allVisibleChecked(this.elements, false); } diff --git a/src/vs/platform/quickinput/browser/quickInputService.ts b/src/vs/platform/quickinput/browser/quickInputService.ts index 5e7473475d7..4d43602acb6 100644 --- a/src/vs/platform/quickinput/browser/quickInputService.ts +++ b/src/vs/platform/quickinput/browser/quickInputService.ts @@ -7,7 +7,6 @@ import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/lis import { List } from 'vs/base/browser/ui/list/listWidget'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; @@ -60,7 +59,6 @@ export class QuickInputService extends Themable implements IQuickInputService { @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextKeyService protected readonly contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, - @IAccessibilityService private readonly accessibilityService: IAccessibilityService, @ILayoutService protected readonly layoutService: ILayoutService ) { super(themeService); @@ -71,7 +69,6 @@ export class QuickInputService extends Themable implements IQuickInputService { idPrefix: 'quickInput_', container: host.container, ignoreFocusOut: () => false, - isScreenReaderOptimized: () => this.accessibilityService.isScreenReaderOptimized(), backKeybindingLabel: () => undefined, setContextKey: (id?: string) => this.setContextKey(id), linkOpenerDelegate: (content) => { diff --git a/src/vs/platform/quickinput/test/browser/quickinput.test.ts b/src/vs/platform/quickinput/test/browser/quickinput.test.ts index 34a0547388b..48ae15c3a1e 100644 --- a/src/vs/platform/quickinput/test/browser/quickinput.test.ts +++ b/src/vs/platform/quickinput/test/browser/quickinput.test.ts @@ -46,7 +46,6 @@ suite('QuickInput', () => { // https://github.com/microsoft/vscode/issues/147543 container: fixture, idPrefix: 'testQuickInput', ignoreFocusOut() { return true; }, - isScreenReaderOptimized() { return false; }, returnFocus() { }, backKeybindingLabel() { return undefined; }, setContextKey() { return undefined; }, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 16edcbb52f0..fd74bfba4e3 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -957,7 +957,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostNotebookDocuments.onDidChangeNotebookDocument(listener, thisArg, disposables); }, onWillSaveNotebookDocument(listener, thisArg, disposables) { - checkProposedApiEnabled(extension, 'notebookDocumentWillSave'); return extHostNotebookDocumentSaveParticipant.getOnWillSaveNotebookDocumentEvent(extension)(listener, thisArg, disposables); }, get onDidOpenNotebookDocument(): Event { diff --git a/src/vs/workbench/api/common/extHostNotebookDocumentSaveParticipant.ts b/src/vs/workbench/api/common/extHostNotebookDocumentSaveParticipant.ts index e9547b63069..1e6a706a305 100644 --- a/src/vs/workbench/api/common/extHostNotebookDocumentSaveParticipant.ts +++ b/src/vs/workbench/api/common/extHostNotebookDocumentSaveParticipant.ts @@ -53,7 +53,7 @@ export class ExtHostNotebookDocumentSaveParticipant implements ExtHostNotebookDo const edits: WorkspaceEdit[] = []; - await this._onWillSaveNotebookDocumentEvent.fireAsync({ document: document.apiNotebook, reason: TextDocumentSaveReason.to(reason) }, token, async (thenable: Promise, listener) => { + await this._onWillSaveNotebookDocumentEvent.fireAsync({ notebook: document.apiNotebook, reason: TextDocumentSaveReason.to(reason) }, token, async (thenable: Promise, listener) => { const now = Date.now(); const data = await await Promise.resolve(thenable); if (Date.now() - now > this._thresholds.timeout) { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 422ec20e745..bb112d718b1 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2416,9 +2416,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } } - if (includeOutput) { - for (let i = 0; i < this.getLength(); i++) { - const cell = this.cellAt(i); + if (includeOutput && this._list) { + for (let i = 0; i < this._list.length; i++) { + const cell = this._list.element(i); if (cell?.cellKind === CellKind.Code) { requests.push(this._warmupCell((cell as CodeCellViewModel))); 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 ed02a196a8b..940fb0ebb10 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -199,10 +199,9 @@ export class BackLayerWebView extends Themable { } this._register(workspaceTrustManagementService.onDidChangeTrust(e => { - this._sendMessageToWebview({ - type: 'updateWorkspaceTrust', - isTrusted: e, - }); + const baseUrl = this.asWebviewUri(this.getNotebookBaseUri(), undefined); + const htmlContent = this.generateContent(baseUrl.toString()); + this.webview?.setHtml(htmlContent); })); this._register(TokenizationRegistry.onDidChange(() => { 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 405ab9162ec..8f91b711469 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts @@ -378,10 +378,6 @@ export interface INotebookOptionsMessage { readonly renderOptions: RenderOptions; } -export interface INotebookUpdateWorkspaceTrust { - readonly type: 'updateWorkspaceTrust'; - readonly isTrusted: boolean; -} export interface ITokenizedCodeBlockMessage { readonly type: 'tokenizedCodeBlock'; readonly codeBlockId: string; @@ -529,7 +525,6 @@ export type ToWebviewMessage = IClearMessage | IInitializeMarkupCells | INotebookStylesMessage | INotebookOptionsMessage | - INotebookUpdateWorkspaceTrust | ITokenizedCodeBlockMessage | ITokenizedStylesChangedMessage | IFindMessage | 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 eede3d34ff9..c188aa78327 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -90,7 +90,7 @@ async function webviewPreloads(ctx: PreloadContext) { const textDecoder = new TextDecoder(); let currentOptions = ctx.options; - let isWorkspaceTrusted = ctx.isWorkspaceTrusted; + const isWorkspaceTrusted = ctx.isWorkspaceTrusted; let currentRenderOptions = ctx.renderOptions; const settingChange: EmitterLike = createEmitter(); @@ -1397,11 +1397,6 @@ async function webviewPreloads(ctx: PreloadContext) { currentRenderOptions = event.data.renderOptions; settingChange.fire(currentRenderOptions); break; - case 'updateWorkspaceTrust': { - isWorkspaceTrusted = event.data.isTrusted; - viewModel.rerender(); - break; - } case 'tokenizedCodeBlock': { const { codeBlockId, html } = event.data; MarkdownCodeBlock.highlightCodeBlock(codeBlockId, html); @@ -1870,11 +1865,6 @@ async function webviewPreloads(ctx: PreloadContext) { this._outputCells.clear(); } - public rerender() { - this.rerenderMarkupCells(); - this.renderOutputCells(); - } - private async createMarkupCell(init: webviewMessages.IMarkupCellInitialization, top: number, visible: boolean): Promise { const existing = this._markupCells.get(init.cellId); if (existing) { @@ -1929,12 +1919,6 @@ async function webviewPreloads(ctx: PreloadContext) { cell?.unhide(); } - private rerenderMarkupCells() { - for (const cell of this._markupCells.values()) { - cell.rerender(); - } - } - private getExpectedMarkupCell(id: string): MarkupCell | undefined { const cell = this._markupCells.get(id); if (!cell) { @@ -1966,12 +1950,6 @@ async function webviewPreloads(ctx: PreloadContext) { } } - private renderOutputCells() { - for (const outputCell of this._outputCells.values()) { - outputCell.rerender(); - } - } - public async renderOutputCell(data: webviewMessages.ICreationRequestMessage, signal: AbortSignal): Promise { const preloadErrors = await Promise.all( data.requiredPreloads.map(p => kernelPreloads.waitFor(p.uri).then(() => undefined, err => err)) @@ -2266,10 +2244,6 @@ async function webviewPreloads(ctx: PreloadContext) { this.updateMarkupDimensions(); } - public rerender() { - this.updateContentAndRender(this._content.value, this._content.metadata); - } - public remove() { this.element.remove(); } @@ -2378,12 +2352,6 @@ async function webviewPreloads(ctx: PreloadContext) { this.outputElements.get(outputId)?.updateContentAndRender(content); } - public rerender() { - for (const outputElement of this.outputElements.values()) { - outputElement.rerender(); - } - } - public updateOutputHeight(outputId: string, height: number) { this.outputElements.get(outputId)?.updateHeight(height); } @@ -2458,10 +2426,6 @@ async function webviewPreloads(ctx: PreloadContext) { return this._outputNode; } - public rerender() { - this._outputNode?.rerender(); - } - public updateContentAndRender(content: webviewMessages.ICreationContent) { this._outputNode?.updateAndRerender(content); } @@ -2490,7 +2454,6 @@ async function webviewPreloads(ctx: PreloadContext) { class OutputElement { public readonly element: HTMLElement; private _content?: { - readonly content: webviewMessages.ICreationContent; readonly preferredRendererId: string | undefined; readonly preloadErrors: ReadonlyArray; }; @@ -2528,7 +2491,7 @@ async function webviewPreloads(ctx: PreloadContext) { this.renderTaskAbort?.abort(); this.renderTaskAbort = undefined; - this._content = { content, preferredRendererId, preloadErrors }; + this._content = { preferredRendererId, preloadErrors }; if (content.type === 0 /* RenderOutputType.Html */) { const trustedHtml = ttPolicy?.createHTML(content.htmlContent) ?? content.htmlContent; this.element.innerHTML = trustedHtml as string; @@ -2586,16 +2549,9 @@ async function webviewPreloads(ctx: PreloadContext) { } } - public rerender() { - if (this._content) { - this.render(this._content.content, this._content.preferredRendererId, this._content.preloadErrors); - } - } - public updateAndRerender(content: webviewMessages.ICreationContent) { if (this._content) { - this._content = { content, preferredRendererId: this._content.preferredRendererId, preloadErrors: this._content.preloadErrors }; - this.rerender(); + this.render(content, this._content.preferredRendererId, this._content.preloadErrors); } } } diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 6d3ac59883e..5860ada41b2 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -48,7 +48,6 @@ export const allApiProposals = Object.freeze({ notebookCellExecutionState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecutionState.d.ts', notebookControllerAffinityHidden: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookControllerAffinityHidden.d.ts', notebookDeprecated: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookDeprecated.d.ts', - notebookDocumentWillSave: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookDocumentWillSave.d.ts', notebookKernelSource: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts', notebookLiveShare: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookLiveShare.d.ts', notebookMessaging: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookMessaging.d.ts', diff --git a/src/vs/workbench/services/quickinput/browser/quickInputService.ts b/src/vs/workbench/services/quickinput/browser/quickInputService.ts index 4e511787aa4..e47d590694e 100644 --- a/src/vs/workbench/services/quickinput/browser/quickInputService.ts +++ b/src/vs/workbench/services/quickinput/browser/quickInputService.ts @@ -9,7 +9,6 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; 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 { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { QuickInputController } from 'vs/platform/quickinput/browser/quickInput'; import { QuickInputService as BaseQuickInputService } from 'vs/platform/quickinput/browser/quickInputService'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -29,11 +28,10 @@ export class QuickInputService extends BaseQuickInputService { @IKeybindingService private readonly keybindingService: IKeybindingService, @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, - @IAccessibilityService accessibilityService: IAccessibilityService, @ILayoutService layoutService: ILayoutService, @IHoverService private readonly hoverService: IHoverService ) { - super(instantiationService, contextKeyService, themeService, accessibilityService, layoutService); + super(instantiationService, contextKeyService, themeService, layoutService); this.registerListeners(); } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 7a2bcec8f60..efcae100a18 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -320,7 +320,7 @@ export function workbenchInstantiationService( instantiationService.stub(IPaneCompositePartService, new TestPaneCompositeService()); instantiationService.stub(IListService, new TestListService()); const hoverService = instantiationService.stub(IHoverService, instantiationService.createInstance(TestHoverService)); - instantiationService.stub(IQuickInputService, disposables.add(new QuickInputService(configService, instantiationService, keybindingService, contextKeyService, themeService, accessibilityService, layoutService, hoverService))); + instantiationService.stub(IQuickInputService, disposables.add(new QuickInputService(configService, instantiationService, keybindingService, contextKeyService, themeService, layoutService, hoverService))); instantiationService.stub(IWorkspacesService, new TestWorkspacesService()); instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService()); instantiationService.stub(ITerminalInstanceService, new TestTerminalInstanceService()); diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 115daed94aa..6f8088e8aba 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -12489,6 +12489,21 @@ declare module 'vscode' { */ export const onDidChangeNotebookDocument: Event; + /** + * An event that is emitted when a {@link NotebookDocument notebook document} will be saved to disk. + * + * *Note 1:* Subscribers can delay saving by registering asynchronous work. For the sake of data integrity the editor + * might save without firing this event. For instance when shutting down with dirty files. + * + * *Note 2:* Subscribers are called sequentially and they can {@link NotebookDocumentWillSaveEvent.waitUntil delay} saving + * by registering asynchronous work. Protection against misbehaving listeners is implemented as such: + * * there is an overall time budget that all listeners share and if that is exhausted no further listener is called + * * listeners that take a long time or produce errors frequently will not be called anymore + * + * The current thresholds are 1.5 seconds as overall time budget and a listener can misbehave 3 times before being ignored. + */ + export const onWillSaveNotebookDocument: Event; + /** * An event that is emitted when a {@link NotebookDocument notebook} is saved. */ @@ -13558,6 +13573,61 @@ declare module 'vscode' { readonly cellChanges: readonly NotebookDocumentCellChange[]; } + /** + * An event that is fired when a {@link NotebookDocument notebook document} will be saved. + * + * To make modifications to the document before it is being saved, call the + * {@linkcode NotebookDocumentWillSaveEvent.waitUntil waitUntil}-function with a thenable + * that resolves to a {@link WorkspaceEdit workspace edit}. + */ + export interface NotebookDocumentWillSaveEvent { + /** + * A cancellation token. + */ + readonly token: CancellationToken; + + /** + * The {@link NotebookDocument notebook document} that will be saved. + */ + readonly notebook: NotebookDocument; + + /** + * The reason why save was triggered. + */ + readonly reason: TextDocumentSaveReason; + + /** + * Allows to pause the event loop and to apply {@link WorkspaceEdit workspace edit}. + * Edits of subsequent calls to this function will be applied in order. The + * edits will be *ignored* if concurrent modifications of the notebook document happened. + * + * *Note:* This function can only be called during event dispatch and not + * in an asynchronous manner: + * + * ```ts + * workspace.onWillSaveNotebookDocument(event => { + * // async, will *throw* an error + * setTimeout(() => event.waitUntil(promise)); + * + * // sync, OK + * event.waitUntil(promise); + * }) + * ``` + * + * @param thenable A thenable that resolves to {@link WorkspaceEdit workspace edit}. + */ + waitUntil(thenable: Thenable): void; + + /** + * Allows to pause the event loop until the provided thenable resolved. + * + * *Note:* This function can only be called during event dispatch. + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + } + /** * The summary of a notebook cell execution. */ diff --git a/src/vscode-dts/vscode.proposed.notebookDocumentWillSave.d.ts b/src/vscode-dts/vscode.proposed.notebookDocumentWillSave.d.ts deleted file mode 100644 index e751bbcf669..00000000000 --- a/src/vscode-dts/vscode.proposed.notebookDocumentWillSave.d.ts +++ /dev/null @@ -1,39 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode' { - /** - * An event that is fired when a {@link NotebookDocument document} will be saved. - * - * To make modifications to the document before it is being saved, call the - * {@linkcode NotebookDocumentWillSaveEvent.waitUntil waitUntil}-function with a thenable - * that resolves to an array of {@link TextEdit text edits}. - */ - export interface NotebookDocumentWillSaveEvent { - /** - * A cancellation token. - */ - readonly token: CancellationToken; - - /** - * The document that will be saved. - */ - readonly document: NotebookDocument; - - /** - * The reason why save was triggered. - */ - readonly reason: TextDocumentSaveReason; - - waitUntil(thenable: Thenable): void; - - waitUntil(thenable: Thenable): void; - } - - export namespace workspace { - - export const onWillSaveNotebookDocument: Event; - } -}