Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/hover/browser/hoverAccessibleViews.ts
4779 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
import { localize } from '../../../../nls.js';
6
import { EditorContextKeys } from '../../../common/editorContextKeys.js';
7
import { ContentHoverController } from './contentHoverController.js';
8
import { AccessibleViewType, AccessibleViewProviderId, AccessibleContentProvider, IAccessibleViewContentProvider, IAccessibleViewOptions } from '../../../../platform/accessibility/browser/accessibleView.js';
9
import { IAccessibleViewImplementation } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js';
10
import { IContextViewService } from '../../../../platform/contextview/browser/contextView.js';
11
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
12
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
13
import { HoverVerbosityAction } from '../../../common/languages.js';
14
import { DECREASE_HOVER_VERBOSITY_ACCESSIBLE_ACTION_ID, DECREASE_HOVER_VERBOSITY_ACTION_ID, INCREASE_HOVER_VERBOSITY_ACCESSIBLE_ACTION_ID, INCREASE_HOVER_VERBOSITY_ACTION_ID } from './hoverActionIds.js';
15
import { ICodeEditor } from '../../../browser/editorBrowser.js';
16
import { ICodeEditorService } from '../../../browser/services/codeEditorService.js';
17
import { Action, IAction } from '../../../../base/common/actions.js';
18
import { ThemeIcon } from '../../../../base/common/themables.js';
19
import { Codicon } from '../../../../base/common/codicons.js';
20
import { Emitter, Event } from '../../../../base/common/event.js';
21
import { Disposable } from '../../../../base/common/lifecycle.js';
22
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
23
import { labelForHoverVerbosityAction } from './markdownHoverParticipant.js';
24
25
namespace HoverAccessibilityHelpNLS {
26
export const increaseVerbosity = localize('increaseVerbosity', '- The focused hover part verbosity level can be increased with the Increase Hover Verbosity command.', `<keybinding:${INCREASE_HOVER_VERBOSITY_ACTION_ID}>`);
27
export const decreaseVerbosity = localize('decreaseVerbosity', '- The focused hover part verbosity level can be decreased with the Decrease Hover Verbosity command.', `<keybinding:${DECREASE_HOVER_VERBOSITY_ACTION_ID}>`);
28
}
29
30
export class HoverAccessibleView implements IAccessibleViewImplementation {
31
32
public readonly type = AccessibleViewType.View;
33
public readonly priority = 95;
34
public readonly name = 'hover';
35
public readonly when = EditorContextKeys.hoverFocused;
36
37
getProvider(accessor: ServicesAccessor): AccessibleContentProvider | undefined {
38
const codeEditorService = accessor.get(ICodeEditorService);
39
const codeEditor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor();
40
if (!codeEditor) {
41
throw new Error('No active or focused code editor');
42
}
43
const hoverController = ContentHoverController.get(codeEditor);
44
if (!hoverController) {
45
return;
46
}
47
const keybindingService = accessor.get(IKeybindingService);
48
return accessor.get(IInstantiationService).createInstance(HoverAccessibleViewProvider, keybindingService, codeEditor, hoverController);
49
}
50
}
51
52
export class HoverAccessibilityHelp implements IAccessibleViewImplementation {
53
54
public readonly priority = 100;
55
public readonly name = 'hover';
56
public readonly type = AccessibleViewType.Help;
57
public readonly when = EditorContextKeys.hoverVisible;
58
59
getProvider(accessor: ServicesAccessor): AccessibleContentProvider | undefined {
60
const codeEditorService = accessor.get(ICodeEditorService);
61
const codeEditor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor();
62
if (!codeEditor) {
63
throw new Error('No active or focused code editor');
64
}
65
const hoverController = ContentHoverController.get(codeEditor);
66
if (!hoverController) {
67
return;
68
}
69
return accessor.get(IInstantiationService).createInstance(HoverAccessibilityHelpProvider, hoverController);
70
}
71
}
72
73
abstract class BaseHoverAccessibleViewProvider extends Disposable implements IAccessibleViewContentProvider {
74
75
abstract provideContent(): string;
76
abstract options: IAccessibleViewOptions;
77
78
public readonly id = AccessibleViewProviderId.Hover;
79
public readonly verbositySettingKey = 'accessibility.verbosity.hover';
80
81
private readonly _onDidChangeContent: Emitter<void> = this._register(new Emitter<void>());
82
public readonly onDidChangeContent: Event<void> = this._onDidChangeContent.event;
83
84
protected _focusedHoverPartIndex: number = -1;
85
86
constructor(protected readonly _hoverController: ContentHoverController) {
87
super();
88
}
89
90
public onOpen(): void {
91
if (!this._hoverController) {
92
return;
93
}
94
this._hoverController.shouldKeepOpenOnEditorMouseMoveOrLeave = true;
95
this._focusedHoverPartIndex = this._hoverController.focusedHoverPartIndex();
96
this._register(this._hoverController.onHoverContentsChanged(() => {
97
this._onDidChangeContent.fire();
98
}));
99
}
100
101
public onClose(): void {
102
if (!this._hoverController) {
103
return;
104
}
105
if (this._focusedHoverPartIndex === -1) {
106
this._hoverController.focus();
107
} else {
108
this._hoverController.focusHoverPartWithIndex(this._focusedHoverPartIndex);
109
}
110
this._focusedHoverPartIndex = -1;
111
this._hoverController.shouldKeepOpenOnEditorMouseMoveOrLeave = false;
112
}
113
114
provideContentAtIndex(focusedHoverIndex: number, includeVerbosityActions: boolean): string {
115
if (focusedHoverIndex !== -1) {
116
const accessibleContent = this._hoverController.getAccessibleWidgetContentAtIndex(focusedHoverIndex);
117
if (accessibleContent === undefined) {
118
return '';
119
}
120
const contents: string[] = [];
121
if (includeVerbosityActions) {
122
contents.push(...this._descriptionsOfVerbosityActionsForIndex(focusedHoverIndex));
123
}
124
contents.push(accessibleContent);
125
return contents.join('\n');
126
} else {
127
const accessibleContent = this._hoverController.getAccessibleWidgetContent();
128
if (accessibleContent === undefined) {
129
return '';
130
}
131
const contents: string[] = [];
132
contents.push(accessibleContent);
133
return contents.join('\n');
134
}
135
}
136
137
private _descriptionsOfVerbosityActionsForIndex(index: number): string[] {
138
const content: string[] = [];
139
const descriptionForIncreaseAction = this._descriptionOfVerbosityActionForIndex(HoverVerbosityAction.Increase, index);
140
if (descriptionForIncreaseAction !== undefined) {
141
content.push(descriptionForIncreaseAction);
142
}
143
const descriptionForDecreaseAction = this._descriptionOfVerbosityActionForIndex(HoverVerbosityAction.Decrease, index);
144
if (descriptionForDecreaseAction !== undefined) {
145
content.push(descriptionForDecreaseAction);
146
}
147
return content;
148
}
149
150
private _descriptionOfVerbosityActionForIndex(action: HoverVerbosityAction, index: number): string | undefined {
151
const isActionSupported = this._hoverController.doesHoverAtIndexSupportVerbosityAction(index, action);
152
if (!isActionSupported) {
153
return;
154
}
155
switch (action) {
156
case HoverVerbosityAction.Increase:
157
return HoverAccessibilityHelpNLS.increaseVerbosity;
158
case HoverVerbosityAction.Decrease:
159
return HoverAccessibilityHelpNLS.decreaseVerbosity;
160
}
161
}
162
}
163
164
export class HoverAccessibilityHelpProvider extends BaseHoverAccessibleViewProvider implements IAccessibleViewContentProvider {
165
166
public readonly options: IAccessibleViewOptions = { type: AccessibleViewType.Help };
167
168
constructor(hoverController: ContentHoverController) {
169
super(hoverController);
170
}
171
172
provideContent(): string {
173
return this.provideContentAtIndex(this._focusedHoverPartIndex, true);
174
}
175
}
176
177
export class HoverAccessibleViewProvider extends BaseHoverAccessibleViewProvider implements IAccessibleViewContentProvider {
178
179
public readonly options: IAccessibleViewOptions = { type: AccessibleViewType.View };
180
181
constructor(
182
private readonly _keybindingService: IKeybindingService,
183
private readonly _editor: ICodeEditor,
184
hoverController: ContentHoverController,
185
) {
186
super(hoverController);
187
this._initializeOptions(this._editor, hoverController);
188
}
189
190
public provideContent(): string {
191
return this.provideContentAtIndex(this._focusedHoverPartIndex, false);
192
}
193
194
public get actions(): IAction[] {
195
const actions: IAction[] = [];
196
actions.push(this._getActionFor(this._editor, HoverVerbosityAction.Increase));
197
actions.push(this._getActionFor(this._editor, HoverVerbosityAction.Decrease));
198
return actions;
199
}
200
201
private _getActionFor(editor: ICodeEditor, action: HoverVerbosityAction): IAction {
202
let actionId: string;
203
let accessibleActionId: string;
204
let actionCodicon: ThemeIcon;
205
switch (action) {
206
case HoverVerbosityAction.Increase:
207
actionId = INCREASE_HOVER_VERBOSITY_ACTION_ID;
208
accessibleActionId = INCREASE_HOVER_VERBOSITY_ACCESSIBLE_ACTION_ID;
209
actionCodicon = Codicon.add;
210
break;
211
case HoverVerbosityAction.Decrease:
212
actionId = DECREASE_HOVER_VERBOSITY_ACTION_ID;
213
accessibleActionId = DECREASE_HOVER_VERBOSITY_ACCESSIBLE_ACTION_ID;
214
actionCodicon = Codicon.remove;
215
break;
216
}
217
const actionLabel = labelForHoverVerbosityAction(this._keybindingService, action);
218
const actionEnabled = this._hoverController.doesHoverAtIndexSupportVerbosityAction(this._focusedHoverPartIndex, action);
219
return new Action(accessibleActionId, actionLabel, ThemeIcon.asClassName(actionCodicon), actionEnabled, () => {
220
editor.getAction(actionId)?.run({ index: this._focusedHoverPartIndex, focus: false });
221
});
222
}
223
224
private _initializeOptions(editor: ICodeEditor, hoverController: ContentHoverController): void {
225
const helpProvider = this._register(new HoverAccessibilityHelpProvider(hoverController));
226
this.options.language = editor.getModel()?.getLanguageId();
227
this.options.customHelp = () => { return helpProvider.provideContentAtIndex(this._focusedHoverPartIndex, true); };
228
}
229
}
230
231
export class ExtHoverAccessibleView implements IAccessibleViewImplementation {
232
public readonly type = AccessibleViewType.View;
233
public readonly priority = 90;
234
public readonly name = 'extension-hover';
235
236
getProvider(accessor: ServicesAccessor): AccessibleContentProvider | undefined {
237
const contextViewService = accessor.get(IContextViewService);
238
const contextViewElement = contextViewService.getContextViewElement();
239
const extensionHoverContent = contextViewElement?.textContent ?? undefined;
240
const hoverService = accessor.get(IHoverService);
241
242
if (contextViewElement.classList.contains('accessible-view-container') || !extensionHoverContent) {
243
// The accessible view, itself, uses the context view service to display the text. We don't want to read that.
244
return;
245
}
246
return new AccessibleContentProvider(
247
AccessibleViewProviderId.Hover,
248
{ language: 'typescript', type: AccessibleViewType.View },
249
() => { return extensionHoverContent; },
250
() => {
251
hoverService.showAndFocusLastHover();
252
},
253
'accessibility.verbosity.hover',
254
);
255
}
256
}
257
258