Skip to content

Commit

Permalink
Merge pull request #143539 from microsoft/joh/inlayAudioCue
Browse files Browse the repository at this point in the history
  • Loading branch information
jrieken committed Feb 21, 2022
2 parents 53eb878 + 11a84bb commit a00e9d1
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 70 deletions.
4 changes: 4 additions & 0 deletions build/lib/i18n.resources.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@
"name": "vs/workbench/contrib/issue",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/contrib/inlayHints",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/contrib/interactive",
"project": "vscode-workbench"
Expand Down
70 changes: 6 additions & 64 deletions src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,16 @@
import { RunOnceScheduler } from 'vs/base/common/async';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { onUnexpectedError } from 'vs/base/common/errors';
import { KeyCode } from 'vs/base/common/keyCodes';
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { LRUCache } from 'vs/base/common/map';
import { IRange } from 'vs/base/common/range';
import { assertType } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { IActiveCodeEditor, ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
import { ClassNameReference, CssProperties, DynamicCssRules } from 'vs/editor/browser/editorDom';
import { EditorAction2 } from 'vs/editor/browser/editorExtensions';
import { EditorOption, EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions';
import { Range } from 'vs/editor/common/core/range';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import * as languages from 'vs/editor/common/languages';
import { IModelDeltaDecoration, InjectedTextCursorStops, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model';
import { ModelDecorationInjectedTextOptions } from 'vs/editor/common/model/textModel';
Expand All @@ -27,14 +24,10 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { ClickLinkGesture, ClickLinkMouseEvent } from 'vs/editor/contrib/gotoSymbol/browser/link/clickLinkGesture';
import { InlayHintAnchor, InlayHintItem, InlayHintsFragments } from 'vs/editor/contrib/inlayHints/browser/inlayHints';
import { InlayHintsAccessibility } from 'vs/editor/contrib/inlayHints/browser/inlayHintsAccessibility';
import { goToDefinitionWithLocation, showGoToContextMenu } from 'vs/editor/contrib/inlayHints/browser/inlayHintsLocations';
import { localize } from 'vs/nls';
import { registerAction2 } from 'vs/platform/actions/common/actions';
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { createDecorator, IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import * as colors from 'vs/platform/theme/common/colorRegistry';
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
Expand Down Expand Up @@ -98,7 +91,6 @@ export class InlayHintsController implements IEditorContribution {
private readonly _debounceInfo: IFeatureDebounceInformation;
private readonly _decorationsMetadata = new Map<string, { item: InlayHintItem; classNameRef: IDisposable }>();
private readonly _ruleFactory = new DynamicCssRules(this._editor);
private readonly _accessibility: InlayHintsAccessibility;

private _activeInlayHintPart?: RenderedInlayHintLabelPart;

Expand All @@ -111,7 +103,6 @@ export class InlayHintsController implements IEditorContribution {
@INotificationService private readonly _notificationService: INotificationService,
@IInstantiationService private readonly _instaService: IInstantiationService,
) {
this._accessibility = _instaService.createInstance(InlayHintsAccessibility, _editor);
this._debounceInfo = _featureDebounce.for(_languageFeaturesService.inlayHintsProvider, 'InlayHint', { min: 25 });
this._disposables.add(_languageFeaturesService.inlayHintsProvider.onDidChange(() => this._update()));
this._disposables.add(_editor.onDidChangeModel(() => this._update()));
Expand Down Expand Up @@ -536,28 +527,20 @@ export class InlayHintsController implements IEditorContribution {

// --- accessibility

startInlayHintsReading(): void {
getInlayHintsForLine(line: number): InlayHintItem[] {
if (!this._editor.hasModel()) {
return;
return [];
}
const line = this._editor.getPosition().lineNumber;
const set = new Set<languages.InlayHint>();
const items: InlayHintItem[] = [];
const result: InlayHintItem[] = [];
for (let deco of this._editor.getLineDecorations(line)) {
const data = this._decorationsMetadata.get(deco.id);
if (data && !set.has(data.item.hint)) {
set.add(data.item.hint);
items.push(data.item);
result.push(data.item);
}
}
if (set.size > 0) {
this._accessibility.read(line, items);
}
}

stopInlayHintsReading(): void {
this._accessibility.reset();
this._editor.focus();
return result;
}
}

Expand All @@ -568,47 +551,6 @@ function fixSpace(str: string): string {
return str.replace(/[ \t]/g, noBreakWhitespace);
}

registerAction2(class StartReadHints extends EditorAction2 {

constructor() {
super({
id: 'inlayHints.startReadingLineWithHint',
title: localize('read.title', 'Read Line With Inline Hints'),
precondition: EditorContextKeys.hasInlayHintsProvider,
f1: true
});
}

runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor) {
const ctrl = InlayHintsController.get(editor);
if (ctrl) {
ctrl.startInlayHintsReading();
}
}
});

registerAction2(class StopReadHints extends EditorAction2 {

constructor() {
super({
id: 'inlayHints.stopReadingLineWithHint',
title: localize('stop.title', 'Stop Inlay Hints Reading'),
precondition: InlayHintsAccessibility.IsReading,
f1: true,
keybinding: {
weight: KeybindingWeight.EditorContrib,
primary: KeyCode.Escape
}
});
}

runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor) {
const ctrl = InlayHintsController.get(editor);
if (ctrl) {
ctrl.stopInlayHintsReading();
}
}
});


CommandsRegistry.registerCommand('_executeInlayHintProvider', async (accessor, ...args: [URI, IRange]): Promise<languages.InlayHint[]> => {
Expand Down
6 changes: 6 additions & 0 deletions src/vs/workbench/contrib/audioCues/browser/audioCueService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ export class AudioCue {
settingsKey: 'audioCues.debuggerStoppedOnBreakpoint',
});

public static readonly noInlayHints = AudioCue.register({
name: localize('audioClues.noInlayHints', 'No Inlay Hints available for the current line'),
sound: Sound.error,
settingsKey: 'audioClues.noInlayHints'
});

private constructor(
public readonly sound: Sound,
public readonly name: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
'description': localize('audioCues.debuggerStoppedOnBreakpoint', "Plays a sound when the debugger stopped on a breakpoint."),
...audioCueFeatureBase,
},
'audioCues.noInlayHints': {
'description': localize('audioCues.noInlayHints', "Plays a sound when trying to read a line with inlay hints that has no inlay hints."),
...audioCueFeatureBase,
},
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,45 @@

import * as dom from 'vs/base/browser/dom';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { KeyCode } from 'vs/base/common/keyCodes';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction2, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { Command } from 'vs/editor/common/languages';
import { InlayHintItem } from 'vs/editor/contrib/inlayHints/browser/inlayHints';
import { InlayHintsController } from 'vs/editor/contrib/inlayHints/browser/inlayHintsController';
import { localize } from 'vs/nls';
import { registerAction2 } from 'vs/platform/actions/common/actions';
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { Link } from 'vs/platform/opener/browser/link';
import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService';


export class InlayHintsAccessibility {
export class InlayHintsAccessibility implements IEditorContribution {

static readonly IsReading = new RawContextKey<boolean>('isReadingLineWithInlayHints', false, { type: 'boolean', description: localize('isReadingLineWithInlayHints', "Whether the current line and its inlay hints are currently focused") });

static readonly ID: string = 'editor.contrib.InlayHintsAccessibility';

static get(editor: ICodeEditor): InlayHintsAccessibility | undefined {
return editor.getContribution<InlayHintsAccessibility>(InlayHintsAccessibility.ID) ?? undefined;
}

private readonly _ariaElement: HTMLSpanElement;
private readonly _ctxIsReading: IContextKey<boolean>;


private _sessionDispoosables = new DisposableStore();

constructor(
private readonly _editor: ICodeEditor,
@IContextKeyService contextKeyService: IContextKeyService,
@IAudioCueService private readonly _audioCueService: IAudioCueService,
@IInstantiationService private readonly _instaService: IInstantiationService,
) {
this._ariaElement = document.createElement('span');
Expand All @@ -47,13 +61,13 @@ export class InlayHintsAccessibility {
this._ariaElement.remove();
}

reset(): void {
private _reset(): void {
dom.clearNode(this._ariaElement);
this._sessionDispoosables.clear();
this._ctxIsReading.reset();
}

async read(line: number, hints: InlayHintItem[]) {
private async _read(line: number, hints: InlayHintItem[]) {

this._sessionDispoosables.clear();

Expand Down Expand Up @@ -132,7 +146,7 @@ export class InlayHintsAccessibility {

// reset on blur
this._sessionDispoosables.add(dom.addDisposableListener(this._ariaElement, 'focusout', () => {
this.reset();
this._reset();
}));
}

Expand All @@ -143,4 +157,68 @@ export class InlayHintsAccessibility {
query: encodeURIComponent(JSON.stringify(command.arguments))
}).toString();
}


startInlayHintsReading(): void {
if (!this._editor.hasModel()) {
return;
}
const line = this._editor.getPosition().lineNumber;
const hints = InlayHintsController.get(this._editor)?.getInlayHintsForLine(line);
if (!hints || hints.length === 0) {
this._audioCueService.playAudioCue(AudioCue.noInlayHints);
} else {
this._read(line, hints);
}
}

stopInlayHintsReading(): void {
this._reset();
this._editor.focus();
}
}


registerAction2(class StartReadHints extends EditorAction2 {

constructor() {
super({
id: 'inlayHints.startReadingLineWithHint',
title: localize('read.title', 'Read Line With Inline Hints'),
precondition: EditorContextKeys.hasInlayHintsProvider,
f1: true
});
}

runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor) {
const ctrl = InlayHintsAccessibility.get(editor);
if (ctrl) {
ctrl.startInlayHintsReading();
}
}
});

registerAction2(class StopReadHints extends EditorAction2 {

constructor() {
super({
id: 'inlayHints.stopReadingLineWithHint',
title: localize('stop.title', 'Stop Inlay Hints Reading'),
precondition: InlayHintsAccessibility.IsReading,
f1: true,
keybinding: {
weight: KeybindingWeight.EditorContrib,
primary: KeyCode.Escape
}
});
}

runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor) {
const ctrl = InlayHintsAccessibility.get(editor);
if (ctrl) {
ctrl.stopInlayHintsReading();
}
}
});

registerEditorContribution(InlayHintsAccessibility.ID, InlayHintsAccessibility);
3 changes: 3 additions & 0 deletions src/vs/workbench/workbench.common.main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,9 @@ import 'vs/workbench/contrib/snippets/browser/tabCompletion';
// Formatter Help
import 'vs/workbench/contrib/format/browser/format.contribution';

// Inlay Hint Accessibility
import 'vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty';

// Themes
import 'vs/workbench/contrib/themes/browser/themes.contribution';

Expand Down

0 comments on commit a00e9d1

Please sign in to comment.