support screen reader reading the line when go to next/previous diff are used (#170985)

This commit is contained in:
Megan Rogge 2023-01-11 05:53:32 -08:00 committed by GitHub
parent 2fae780e49
commit c717e011fa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 50 additions and 17 deletions

View file

@ -466,6 +466,10 @@ export class TextAreaHandler extends ViewPart {
}));
}
public writeScreenReaderContent(reason: string): void {
this._textAreaInput.writeScreenReaderContent(reason);
}
public override dispose(): void {
super.dispose();
}

View file

@ -929,6 +929,11 @@ export interface ICodeEditor extends editorCommon.IEditor {
*/
setAriaOptions(options: IEditorAriaOptions): void;
/**
* Write the screen reader content to be the current selection
*/
writeScreenReaderContent(reason: string): void;
/**
* @internal
*/

View file

@ -487,6 +487,10 @@ export class View extends ViewEventHandler {
}
}
public writeScreenReaderContent(reason: string): void {
this._textAreaHandler.writeScreenReaderContent(reason);
}
public focus(): void {
this._textAreaHandler.focusTextArea();
}

View file

@ -398,6 +398,10 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
this._codeEditorService.addCodeEditor(this);
}
public writeScreenReaderContent(reason: string): void {
this._modelData?.view.writeScreenReaderContent(reason);
}
protected _createConfiguration(isSimpleWidget: boolean, options: Readonly<IEditorConstructionOptions>, accessibilityService: IAccessibilityService): EditorConfiguration {
return new EditorConfiguration(isSimpleWidget, options, this._domElement, accessibilityService);
}

4
src/vs/monaco.d.ts vendored
View file

@ -5631,6 +5631,10 @@ declare namespace monaco.editor {
* Get the vertical position (top offset) for the position w.r.t. to the first line.
*/
getTopForPosition(lineNumber: number, column: number): number;
/**
* Write the screen reader content to be the current selection
*/
writeScreenReaderContent(reason: string): void;
/**
* Returns the editor's container dom node
*/

View file

@ -69,7 +69,7 @@ export class AudioCueService extends Disposable implements IAudioCueService {
}
this.playingSounds.add(sound);
console.log('playing', sound);
const url = FileAccess.asBrowserUri(
`vs/platform/audioCues/browser/media/${sound.fileName}`
).toString();
@ -77,6 +77,7 @@ export class AudioCueService extends Disposable implements IAudioCueService {
audio.volume = this.getVolumeInPercent() / 100;
audio.addEventListener('ended', () => {
this.playingSounds.delete(sound);
console.log('ending', sound);
});
try {
try {

View file

@ -57,6 +57,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor';
import { FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files';
import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
class DiffActionRunner extends ActionRunner {
@ -464,9 +465,11 @@ export class GotoPreviousChangeAction extends EditorAction {
});
}
run(accessor: ServicesAccessor): void {
async run(accessor: ServicesAccessor): Promise<void> {
const outerEditor = getOuterEditorFromDiffEditor(accessor);
const audioCueService = accessor.get(IAudioCueService);
const accessibilityService = accessor.get(IAccessibilityService);
const codeEditorService = accessor.get(ICodeEditorService);
if (!outerEditor || !outerEditor.hasModel()) {
return;
@ -486,11 +489,9 @@ export class GotoPreviousChangeAction extends EditorAction {
const index = model.findPreviousClosestChange(lineNumber, false);
const change = model.changes[index];
playAudioCueForChange(change, audioCueService);
const position = new Position(change.modifiedStartLineNumber, 1);
outerEditor.setPosition(position);
outerEditor.revealPositionInCenter(position);
await playAudioCueForChange(change, audioCueService);
// The audio cue can take up to a second to load. Give it a chance to play before we read the line content
await setTimeout(() => setPositionAndSelection(change, outerEditor, accessibilityService, codeEditorService), 500);
}
}
registerEditorAction(GotoPreviousChangeAction);
@ -507,9 +508,11 @@ export class GotoNextChangeAction extends EditorAction {
});
}
run(accessor: ServicesAccessor): void {
async run(accessor: ServicesAccessor): Promise<void> {
const audioCueService = accessor.get(IAudioCueService);
const outerEditor = getOuterEditorFromDiffEditor(accessor);
const accessibilityService = accessor.get(IAccessibilityService);
const codeEditorService = accessor.get(ICodeEditorService);
if (!outerEditor || !outerEditor.hasModel()) {
return;
@ -530,23 +533,31 @@ export class GotoNextChangeAction extends EditorAction {
const index = model.findNextClosestChange(lineNumber, false);
const change = model.changes[index];
playAudioCueForChange(change, audioCueService);
const position = new Position(change.modifiedStartLineNumber, 1);
outerEditor.setPosition(position);
outerEditor.revealPositionInCenter(position);
await playAudioCueForChange(change, audioCueService);
// The audio cue can take up to a second to load. Give it a chance to play before we read the line content
await setTimeout(() => setPositionAndSelection(change, outerEditor, accessibilityService, codeEditorService), 500);
}
}
function playAudioCueForChange(change: IChange, audioCueService: IAudioCueService) {
function setPositionAndSelection(change: IChange, editor: ICodeEditor, accessibilityService: IAccessibilityService, codeEditorService: ICodeEditorService) {
const position = new Position(change.modifiedStartLineNumber, 1);
editor.setPosition(position);
editor.revealPositionInCenter(position);
if (accessibilityService.isScreenReaderOptimized()) {
editor.setSelection({ startLineNumber: change.modifiedStartLineNumber, startColumn: 0, endLineNumber: change.modifiedStartLineNumber, endColumn: Number.MAX_VALUE });
codeEditorService.getActiveCodeEditor()?.writeScreenReaderContent('diff-navigation');
}
}
async function playAudioCueForChange(change: IChange, audioCueService: IAudioCueService) {
const changeType = getChangeType(change);
switch (changeType) {
case ChangeType.Add:
audioCueService.playAudioCue(AudioCue.diffLineInserted);
audioCueService.playAudioCue(AudioCue.diffLineInserted, true);
case ChangeType.Delete:
audioCueService.playAudioCue(AudioCue.diffLineDeleted);
audioCueService.playAudioCue(AudioCue.diffLineDeleted, true);
case ChangeType.Modify:
audioCueService.playAudioCue(AudioCue.diffLineModified);
audioCueService.playAudioCue(AudioCue.diffLineModified, true);
}
}