Skip to content

Commit

Permalink
Merge pull request #189575 from microsoft/merogge/go-to-symbol-access…
Browse files Browse the repository at this point in the history
…ible-view
  • Loading branch information
meganrogge authored Aug 3, 2023
2 parents 2d039ca + a1f77aa commit f3a8b62
Showing 1 changed file with 122 additions and 4 deletions.
126 changes: 122 additions & 4 deletions src/vs/workbench/contrib/accessibility/browser/accessibleView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ import { AccessibilityVerbositySettingId, accessibilityHelpIsShown, accessibleVi
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';

import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess';
import { marked } from 'vs/base/common/marked/marked';

const enum DIMENSIONS {
MAX_WIDTH = 600
Expand All @@ -42,6 +44,10 @@ export interface IAccessibleContentProvider {
onKeyDown?(e: IKeyboardEvent): void;
previous?(): void;
next?(): void;
/**
* When the language is markdown, this is provided by default.
*/
getSymbols?(): IAccessibleViewSymbol[];
options: IAccessibleViewOptions;
}

Expand All @@ -52,6 +58,7 @@ export interface IAccessibleViewService {
show(provider: IAccessibleContentProvider): void;
next(): void;
previous(): void;
goToSymbol(): void;
/**
* If the setting is enabled, provides the open accessible view hint as a localized string.
* @param verbositySettingKey The setting key for the verbosity of the feature
Expand Down Expand Up @@ -128,15 +135,21 @@ class AccessibleView extends Disposable {
}));
}

show(provider: IAccessibleContentProvider): void {
show(provider?: IAccessibleContentProvider, symbol?: IAccessibleViewSymbol): void {
if (!provider) {
provider = this._currentProvider;
}
if (!provider) {
return;
}
const delegate: IContextViewDelegate = {
getAnchor: () => { return { x: (window.innerWidth / 2) - ((Math.min(this._layoutService.dimension.width * 0.62 /* golden cut */, DIMENSIONS.MAX_WIDTH)) / 2), y: this._layoutService.offset.quickPickTop }; },
render: (container) => {
container.classList.add('accessible-view-container');
return this._render(provider, container);
return this._render(provider!, container);
},
onHide: () => {
if (provider.options.type === AccessibleViewType.Help) {
if (provider!.options.type === AccessibleViewType.Help) {
this._accessiblityHelpIsShown.reset();
} else {
this._accessibleViewIsShown.reset();
Expand All @@ -150,6 +163,9 @@ class AccessibleView extends Disposable {
} else {
this._accessibleViewIsShown.set(true);
}
if (symbol && this._currentProvider) {
this.showSymbol(this._currentProvider, symbol);
}
this._currentProvider = provider;
}

Expand All @@ -167,6 +183,51 @@ class AccessibleView extends Disposable {
this._currentProvider.next?.();
}

goToSymbol(): void {
this._instantiationService.createInstance(AccessibleViewSymbolQuickPick, this).show(this._currentProvider!);
}

getSymbols(): IAccessibleViewSymbol[] | undefined {
if (!this._currentProvider) {
return;
}
const tokens = this._currentProvider.options.language && this._currentProvider.options.language !== 'markdown' ? this._currentProvider.getSymbols?.() : marked.lexer(this._currentProvider.provideContent());
if (!tokens) {
return;
}
const symbols: IAccessibleViewSymbol[] = [];
for (const token of tokens) {
let label: string | undefined = undefined;
if ('type' in token) {
switch (token.type) {
case 'heading':
case 'paragraph':
case 'code':
label = token.text;
break;
case 'list':
label = token.items?.map(i => i.text).join(', ');
break;
}
} else {
label = token.label;
}
if (label) {
symbols.push({ info: label, label: localize('symbolLabel', "({0}) {1}", token.type, label), ariaLabel: localize('symbolLabelAria', "({0}) {1}", token.type, label) });
}
}
return symbols;
}

showSymbol(provider: IAccessibleContentProvider, symbol: IAccessibleViewSymbol): void {
const index = provider.provideContent().split('\n').findIndex(line => line.includes(symbol.info.split('\n')[0])) ?? -1;
if (index >= 0) {
this.show(provider);
this._editorWidget.revealLine(index + 1);
this._editorWidget.setSelection({ startLineNumber: index + 1, startColumn: 1, endLineNumber: index + 1, endColumn: 1 });
}
}

private _render(provider: IAccessibleContentProvider, container: HTMLElement): IDisposable {
this._currentProvider = provider;
const settingKey = `accessibility.verbosity.${provider.verbositySettingKey}`;
Expand Down Expand Up @@ -286,6 +347,9 @@ export class AccessibleViewService extends Disposable implements IAccessibleView
previous(): void {
this._accessibleView?.previous();
}
goToSymbol(): void {
this._accessibleView?.goToSymbol();
}
getOpenAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string | null {
if (!this._configurationService.getValue(verbositySettingKey)) {
return null;
Expand Down Expand Up @@ -321,6 +385,30 @@ class AccessibleViewNextAction extends Action2 {
registerAction2(AccessibleViewNextAction);


class AccessibleViewGoToSymbolAction extends Action2 {
static id: 'editor.action.accessibleViewGoToSymbol';
constructor() {
super({
id: 'editor.action.accessibleViewGoToSymbol',
precondition: accessibleViewIsShown,
keybinding: {
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyO,
weight: KeybindingWeight.WorkbenchContrib + 10
},
menu: [{
id: MenuId.CommandPalette,
group: '',
order: 1
}],
title: localize('editor.action.accessibleViewGoToSymbol', "Go To Symbol in Accessible View")
});
}
run(accessor: ServicesAccessor, ...args: unknown[]): void {
accessor.get(IAccessibleViewService).goToSymbol();
}
}
registerAction2(AccessibleViewGoToSymbolAction);

class AccessibleViewPreviousAction extends Action2 {
static id: 'editor.action.accessibleViewPrevious';
constructor() {
Expand Down Expand Up @@ -389,3 +477,33 @@ export const AccessibleViewAction = registerCommand(new MultiCommand({
}],
}));


class AccessibleViewSymbolQuickPick {
constructor(private _accessibleView: AccessibleView, @IQuickInputService private readonly _quickInputService: IQuickInputService) {

}
show(provider: IAccessibleContentProvider): void {
const quickPick = this._quickInputService.createQuickPick<IAccessibleViewSymbol>();
const picks = [];
const symbols = this._accessibleView.getSymbols();
if (!symbols) {
return;
}
for (const symbol of symbols) {
picks.push({
label: symbol.label,
ariaLabel: symbol.ariaLabel
});
}
quickPick.canSelectMany = false;
quickPick.items = symbols;
quickPick.show();
quickPick.onDidAccept(() => {
this._accessibleView.showSymbol(provider, quickPick.selectedItems[0]);
});
}
}

interface IAccessibleViewSymbol extends IPickerQuickAccessItem {
info: string;
}

0 comments on commit f3a8b62

Please sign in to comment.