Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use audio cue to signal no inlay hints can be read #143539

Merged
merged 2 commits into from
Feb 21, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions build/lib/i18n.resources.json
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
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
Expand Up @@ -165,6 +165,12 @@ export class AudioCue {
settingsKey: 'audioCues.debuggerStoppedOnBreakpoint',
});

public static readonly noInlayHints = AudioCue.register({
name: localize('audioClues.noInlayHints', 'Line has no Inlay Hints'),
jrieken marked this conversation as resolved.
Show resolved Hide resolved
sound: Sound.error,
settingsKey: 'audioClues.noInlayHints'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not audioClues it is audioCues :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in main

});

private constructor(
public readonly sound: Sound,
public readonly name: string,
Expand Down
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
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 _(line: number, hints: InlayHintItem[]) {
jrieken marked this conversation as resolved.
Show resolved Hide resolved

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._(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
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