Merge pull request #129809 from microsoft/hediet/inline-suggestion-screen-reader
Improves inline suggestions to work with screenreaders.
This commit is contained in:
commit
c42de908f0
3 changed files with 72 additions and 14 deletions
|
@ -28,9 +28,9 @@ export class GhostText {
|
|||
this.parts.every((part, index) => part.equals(other.parts[index]));
|
||||
}
|
||||
|
||||
render(text: string, debug: boolean = false): string {
|
||||
render(documentText: string, debug: boolean = false): string {
|
||||
const l = this.lineNumber;
|
||||
return applyEdits(text,
|
||||
return applyEdits(documentText,
|
||||
[
|
||||
...this.parts.map(p => ({
|
||||
range: { startLineNumber: l, endLineNumber: l, startColumn: p.column, endColumn: p.column },
|
||||
|
@ -39,6 +39,23 @@ export class GhostText {
|
|||
]
|
||||
);
|
||||
}
|
||||
|
||||
renderForScreenReader(lineText: string): string {
|
||||
if (this.parts.length === 0) {
|
||||
return '';
|
||||
}
|
||||
const lastPart = this.parts[this.parts.length - 1];
|
||||
|
||||
const cappedLineText = lineText.substr(0, lastPart.column - 1);
|
||||
const text = applyEdits(cappedLineText,
|
||||
this.parts.map(p => ({
|
||||
range: { startLineNumber: 1, endLineNumber: 1, startColumn: p.column, endColumn: p.column },
|
||||
text: p.lines.join('\n')
|
||||
}))
|
||||
);
|
||||
|
||||
return text.substring(this.parts[0].column - 1);
|
||||
}
|
||||
}
|
||||
|
||||
class PositionOffsetTransformer {
|
||||
|
|
|
@ -15,6 +15,7 @@ import * as nls from 'vs/nls';
|
|||
import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { GhostTextModel } from 'vs/editor/contrib/inlineCompletions/ghostTextModel';
|
||||
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
|
||||
export class GhostTextController extends Disposable {
|
||||
public static readonly inlineSuggestionVisible = new RawContextKey<boolean>('inlineSuggestionVisible', false, nls.localize('inlineSuggestionVisible', "Whether an inline suggestion is visible"));
|
||||
|
@ -28,7 +29,7 @@ export class GhostTextController extends Disposable {
|
|||
|
||||
private triggeredExplicitly = false;
|
||||
protected readonly activeController = this._register(new MutableDisposable<ActiveGhostTextController>());
|
||||
private get activeModel(): GhostTextModel | undefined {
|
||||
public get activeModel(): GhostTextModel | undefined {
|
||||
return this.activeController.value?.model;
|
||||
}
|
||||
|
||||
|
@ -168,21 +169,23 @@ const GhostTextCommand = EditorCommand.bindToContribution(GhostTextController.ge
|
|||
|
||||
export const commitInlineSuggestionAction = new GhostTextCommand({
|
||||
id: 'editor.action.inlineSuggest.commit',
|
||||
precondition: ContextKeyExpr.and(
|
||||
GhostTextController.inlineSuggestionVisible,
|
||||
GhostTextController.inlineSuggestionHasIndentation.toNegated(),
|
||||
EditorContextKeys.tabMovesFocus.toNegated()
|
||||
),
|
||||
kbOpts: {
|
||||
weight: 200,
|
||||
primary: KeyCode.Tab,
|
||||
},
|
||||
precondition: GhostTextController.inlineSuggestionVisible,
|
||||
handler(x) {
|
||||
x.commit();
|
||||
x.editor.focus();
|
||||
}
|
||||
});
|
||||
registerEditorCommand(commitInlineSuggestionAction);
|
||||
KeybindingsRegistry.registerKeybindingRule({
|
||||
primary: KeyCode.Tab,
|
||||
weight: 200,
|
||||
id: commitInlineSuggestionAction.id,
|
||||
when: ContextKeyExpr.and(
|
||||
commitInlineSuggestionAction.precondition,
|
||||
EditorContextKeys.tabMovesFocus.toNegated(),
|
||||
GhostTextController.inlineSuggestionHasIndentation.toNegated()
|
||||
),
|
||||
});
|
||||
|
||||
registerEditorCommand(new GhostTextCommand({
|
||||
id: 'editor.action.inlineSuggest.hide',
|
||||
|
|
|
@ -14,6 +14,12 @@ import { ICommandService } from 'vs/platform/commands/common/commands';
|
|||
import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ITextContentData, IViewZoneData } from 'vs/editor/browser/controller/mouseTarget';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer';
|
||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
|
||||
|
||||
export class InlineCompletionsHover implements IHoverPart {
|
||||
constructor(
|
||||
|
@ -38,10 +44,13 @@ export class InlineCompletionsHover implements IHoverPart {
|
|||
export class InlineCompletionsHoverParticipant implements IEditorHoverParticipant<InlineCompletionsHover> {
|
||||
constructor(
|
||||
private readonly _editor: ICodeEditor,
|
||||
hover: IEditorHover,
|
||||
private readonly _hover: IEditorHover,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IMenuService private readonly _menuService: IMenuService,
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IModeService private readonly _modeService: IModeService,
|
||||
@IOpenerService private readonly _openerService: IOpenerService,
|
||||
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
|
||||
) { }
|
||||
|
||||
suggestHoverAnchor(mouseEvent: IEditorMouseEvent): HoverAnchor | null {
|
||||
|
@ -82,6 +91,11 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan
|
|||
|
||||
renderHoverParts(hoverParts: InlineCompletionsHover[], fragment: DocumentFragment, statusBar: IEditorHoverStatusBar): IDisposable {
|
||||
const disposableStore = new DisposableStore();
|
||||
const part = hoverParts[0];
|
||||
|
||||
if (this.accessibilityService.isScreenReaderOptimized()) {
|
||||
this.renderScreenReaderText(part, fragment, disposableStore);
|
||||
}
|
||||
|
||||
const menu = disposableStore.add(this._menuService.createMenu(
|
||||
MenuId.InlineCompletionsActions,
|
||||
|
@ -108,7 +122,7 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan
|
|||
for (const action of actions) {
|
||||
action.setEnabled(false);
|
||||
}
|
||||
hoverParts[0].hasMultipleSuggestions().then(hasMore => {
|
||||
part.hasMultipleSuggestions().then(hasMore => {
|
||||
for (const action of actions) {
|
||||
action.setEnabled(hasMore);
|
||||
}
|
||||
|
@ -128,4 +142,28 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan
|
|||
|
||||
return disposableStore;
|
||||
}
|
||||
|
||||
private renderScreenReaderText(part: InlineCompletionsHover, fragment: DocumentFragment, disposableStore: DisposableStore) {
|
||||
const $ = dom.$;
|
||||
const markdownHoverElement = $('div.hover-row.markdown-hover');
|
||||
const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents'));
|
||||
const renderer = disposableStore.add(new MarkdownRenderer({ editor: this._editor }, this._modeService, this._openerService));
|
||||
const render = (code: string) => {
|
||||
disposableStore.add(renderer.onDidRenderAsync(() => {
|
||||
hoverContentsElement.className = 'hover-contents code-hover-contents';
|
||||
this._hover.onContentsChanged();
|
||||
}));
|
||||
|
||||
const inlineSuggestionAvailable = nls.localize('inlineSuggestionFollows', "Inline Suggestion:");
|
||||
const renderedContents = disposableStore.add(renderer.render(new MarkdownString().appendText(inlineSuggestionAvailable).appendCodeblock('text', code)));
|
||||
hoverContentsElement.replaceChildren(renderedContents.element);
|
||||
};
|
||||
|
||||
const ghostText = part.controller.activeModel?.inlineCompletionsModel?.ghostText;
|
||||
if (ghostText) {
|
||||
const lineText = this._editor.getModel()!.getLineContent(ghostText.lineNumber);
|
||||
render(ghostText.renderForScreenReader(lineText));
|
||||
}
|
||||
fragment.appendChild(markdownHoverElement);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue