@@ -29,7 +29,9 @@ import { AccessibilityVerbositySettingId, accessibilityHelpIsShown, accessibleVi
2929import { ILayoutService } from 'vs/platform/layout/browser/layoutService' ;
3030import { Action2 , MenuId , registerAction2 } from 'vs/platform/actions/common/actions' ;
3131import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry' ;
32-
32+ import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput' ;
33+ import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess' ;
34+ import { marked } from 'vs/base/common/marked/marked' ;
3335
3436const enum DIMENSIONS {
3537 MAX_WIDTH = 600
@@ -42,6 +44,10 @@ export interface IAccessibleContentProvider {
4244 onKeyDown ?( e : IKeyboardEvent ) : void ;
4345 previous ?( ) : void ;
4446 next ?( ) : void ;
47+ /**
48+ * When the language is markdown, this is provided by default.
49+ */
50+ getSymbols ?( ) : IAccessibleViewSymbol [ ] ;
4551 options : IAccessibleViewOptions ;
4652}
4753
@@ -52,6 +58,7 @@ export interface IAccessibleViewService {
5258 show ( provider : IAccessibleContentProvider ) : void ;
5359 next ( ) : void ;
5460 previous ( ) : void ;
61+ goToSymbol ( ) : void ;
5562 /**
5663 * If the setting is enabled, provides the open accessible view hint as a localized string.
5764 * @param verbositySettingKey The setting key for the verbosity of the feature
@@ -128,15 +135,21 @@ class AccessibleView extends Disposable {
128135 } ) ) ;
129136 }
130137
131- show ( provider : IAccessibleContentProvider ) : void {
138+ show ( provider ?: IAccessibleContentProvider , symbol ?: IAccessibleViewSymbol ) : void {
139+ if ( ! provider ) {
140+ provider = this . _currentProvider ;
141+ }
142+ if ( ! provider ) {
143+ return ;
144+ }
132145 const delegate : IContextViewDelegate = {
133146 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 } ; } ,
134147 render : ( container ) => {
135148 container . classList . add ( 'accessible-view-container' ) ;
136- return this . _render ( provider , container ) ;
149+ return this . _render ( provider ! , container ) ;
137150 } ,
138151 onHide : ( ) => {
139- if ( provider . options . type === AccessibleViewType . Help ) {
152+ if ( provider ! . options . type === AccessibleViewType . Help ) {
140153 this . _accessiblityHelpIsShown . reset ( ) ;
141154 } else {
142155 this . _accessibleViewIsShown . reset ( ) ;
@@ -150,6 +163,9 @@ class AccessibleView extends Disposable {
150163 } else {
151164 this . _accessibleViewIsShown . set ( true ) ;
152165 }
166+ if ( symbol && this . _currentProvider ) {
167+ this . showSymbol ( this . _currentProvider , symbol ) ;
168+ }
153169 this . _currentProvider = provider ;
154170 }
155171
@@ -167,6 +183,51 @@ class AccessibleView extends Disposable {
167183 this . _currentProvider . next ?.( ) ;
168184 }
169185
186+ goToSymbol ( ) : void {
187+ this . _instantiationService . createInstance ( AccessibleViewSymbolQuickPick , this ) . show ( this . _currentProvider ! ) ;
188+ }
189+
190+ getSymbols ( ) : IAccessibleViewSymbol [ ] | undefined {
191+ if ( ! this . _currentProvider ) {
192+ return ;
193+ }
194+ const tokens = this . _currentProvider . options . language && this . _currentProvider . options . language !== 'markdown' ? this . _currentProvider . getSymbols ?.( ) : marked . lexer ( this . _currentProvider . provideContent ( ) ) ;
195+ if ( ! tokens ) {
196+ return ;
197+ }
198+ const symbols : IAccessibleViewSymbol [ ] = [ ] ;
199+ for ( const token of tokens ) {
200+ let label : string | undefined = undefined ;
201+ if ( 'type' in token ) {
202+ switch ( token . type ) {
203+ case 'heading' :
204+ case 'paragraph' :
205+ case 'code' :
206+ label = token . text ;
207+ break ;
208+ case 'list' :
209+ label = token . items ?. map ( i => i . text ) . join ( ', ' ) ;
210+ break ;
211+ }
212+ } else {
213+ label = token . label ;
214+ }
215+ if ( label ) {
216+ symbols . push ( { info : label , label : localize ( 'symbolLabel' , "({0}) {1}" , token . type , label ) , ariaLabel : localize ( 'symbolLabelAria' , "({0}) {1}" , token . type , label ) } ) ;
217+ }
218+ }
219+ return symbols ;
220+ }
221+
222+ showSymbol ( provider : IAccessibleContentProvider , symbol : IAccessibleViewSymbol ) : void {
223+ const index = provider . provideContent ( ) . split ( '\n' ) . findIndex ( line => line . includes ( symbol . info . split ( '\n' ) [ 0 ] ) ) ?? - 1 ;
224+ if ( index >= 0 ) {
225+ this . show ( provider ) ;
226+ this . _editorWidget . revealLine ( index + 1 ) ;
227+ this . _editorWidget . setSelection ( { startLineNumber : index + 1 , startColumn : 1 , endLineNumber : index + 1 , endColumn : 1 } ) ;
228+ }
229+ }
230+
170231 private _render ( provider : IAccessibleContentProvider , container : HTMLElement ) : IDisposable {
171232 this . _currentProvider = provider ;
172233 const settingKey = `accessibility.verbosity.${ provider . verbositySettingKey } ` ;
@@ -286,6 +347,9 @@ export class AccessibleViewService extends Disposable implements IAccessibleView
286347 previous ( ) : void {
287348 this . _accessibleView ?. previous ( ) ;
288349 }
350+ goToSymbol ( ) : void {
351+ this . _accessibleView ?. goToSymbol ( ) ;
352+ }
289353 getOpenAriaHint ( verbositySettingKey : AccessibilityVerbositySettingId ) : string | null {
290354 if ( ! this . _configurationService . getValue ( verbositySettingKey ) ) {
291355 return null ;
@@ -321,6 +385,30 @@ class AccessibleViewNextAction extends Action2 {
321385registerAction2 ( AccessibleViewNextAction ) ;
322386
323387
388+ class AccessibleViewGoToSymbolAction extends Action2 {
389+ static id : 'editor.action.accessibleViewGoToSymbol' ;
390+ constructor ( ) {
391+ super ( {
392+ id : 'editor.action.accessibleViewGoToSymbol' ,
393+ precondition : accessibleViewIsShown ,
394+ keybinding : {
395+ primary : KeyMod . CtrlCmd | KeyMod . Shift | KeyCode . KeyO ,
396+ weight : KeybindingWeight . WorkbenchContrib + 10
397+ } ,
398+ menu : [ {
399+ id : MenuId . CommandPalette ,
400+ group : '' ,
401+ order : 1
402+ } ] ,
403+ title : localize ( 'editor.action.accessibleViewGoToSymbol' , "Go To Symbol in Accessible View" )
404+ } ) ;
405+ }
406+ run ( accessor : ServicesAccessor , ...args : unknown [ ] ) : void {
407+ accessor . get ( IAccessibleViewService ) . goToSymbol ( ) ;
408+ }
409+ }
410+ registerAction2 ( AccessibleViewGoToSymbolAction ) ;
411+
324412class AccessibleViewPreviousAction extends Action2 {
325413 static id : 'editor.action.accessibleViewPrevious' ;
326414 constructor ( ) {
@@ -389,3 +477,33 @@ export const AccessibleViewAction = registerCommand(new MultiCommand({
389477 } ] ,
390478} ) ) ;
391479
480+
481+ class AccessibleViewSymbolQuickPick {
482+ constructor ( private _accessibleView : AccessibleView , @IQuickInputService private readonly _quickInputService : IQuickInputService ) {
483+
484+ }
485+ show ( provider : IAccessibleContentProvider ) : void {
486+ const quickPick = this . _quickInputService . createQuickPick < IAccessibleViewSymbol > ( ) ;
487+ const picks = [ ] ;
488+ const symbols = this . _accessibleView . getSymbols ( ) ;
489+ if ( ! symbols ) {
490+ return ;
491+ }
492+ for ( const symbol of symbols ) {
493+ picks . push ( {
494+ label : symbol . label ,
495+ ariaLabel : symbol . ariaLabel
496+ } ) ;
497+ }
498+ quickPick . canSelectMany = false ;
499+ quickPick . items = symbols ;
500+ quickPick . show ( ) ;
501+ quickPick . onDidAccept ( ( ) => {
502+ this . _accessibleView . showSymbol ( provider , quickPick . selectedItems [ 0 ] ) ;
503+ } ) ;
504+ }
505+ }
506+
507+ interface IAccessibleViewSymbol extends IPickerQuickAccessItem {
508+ info : string ;
509+ }
0 commit comments