Skip to content

Commit f439a7d

Browse files
committed
wip - use accessible diff viewer for hunk info
1 parent cff1e8e commit f439a7d

File tree

4 files changed

+134
-13
lines changed

4 files changed

+134
-13
lines changed

src/vs/workbench/contrib/inlineChat/browser/inlineChat.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@
307307
padding-top: 6px;
308308
}
309309

310+
.monaco-editor .inline-chat .diff-review.hidden,
310311
.monaco-editor .inline-chat .previewDiff.hidden,
311312
.monaco-editor .inline-chat .previewCreate.hidden,
312313
.monaco-editor .inline-chat .previewCreateTitle.hidden {

src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import { ProviderResult, TextEdit } from 'vs/editor/common/languages';
2727
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
2828
import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController';
2929
import { localize } from 'vs/nls';
30-
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
3130
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
3231
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
3332
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
@@ -145,7 +144,6 @@ export class InlineChatController implements IEditorContribution {
145144
@IConfigurationService private readonly _configurationService: IConfigurationService,
146145
@IDialogService private readonly _dialogService: IDialogService,
147146
@IContextKeyService contextKeyService: IContextKeyService,
148-
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,
149147
@IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService,
150148
@IChatAgentService private readonly _chatAgentService: IChatAgentService,
151149
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
@@ -235,13 +233,7 @@ export class InlineChatController implements IEditorContribution {
235233
}
236234

237235
private _getMode(): EditMode {
238-
const editMode = this._configurationService.inspect<EditMode>(InlineChatConfigKeys.Mode);
239-
let editModeValue = editMode.value;
240-
if (this._accessibilityService.isScreenReaderOptimized() && editModeValue === editMode.defaultValue) {
241-
// By default, use preview mode for screen reader users
242-
editModeValue = EditMode.Preview;
243-
}
244-
return editModeValue!;
236+
return this._configurationService.getValue<EditMode>(InlineChatConfigKeys.Mode);
245237
}
246238

247239
getWidgetPosition(): Position | undefined {

src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { HunkState } from './inlineChatSession';
3636
import { assertType } from 'vs/base/common/types';
3737
import { IModelService } from 'vs/editor/common/services/model';
3838
import { performAsyncTextEdit, asProgressiveEdit } from './utils';
39+
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
3940

4041
export interface IEditObserver {
4142
start(): void;
@@ -404,6 +405,8 @@ type HunkDisplayData = {
404405
toggleDiff?: () => any;
405406
remove(): void;
406407
move: (next: boolean) => void;
408+
409+
hunk: HunkInformation;
407410
};
408411

409412

@@ -441,6 +444,7 @@ export class LiveStrategy extends EditModeStrategy {
441444
zone: InlineChatZoneWidget,
442445
@IContextKeyService contextKeyService: IContextKeyService,
443446
@IEditorWorkerService protected readonly _editorWorkerService: IEditorWorkerService,
447+
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,
444448
@IInstantiationService protected readonly _instaService: IInstantiationService,
445449
) {
446450
super(session, editor, zone);
@@ -652,6 +656,7 @@ export class LiveStrategy extends EditModeStrategy {
652656
: zoneLineNumber - hunkRanges[0].endLineNumber;
653657

654658
data = {
659+
hunk: hunkData,
655660
decorationIds,
656661
viewZoneId: '',
657662
viewZone: viewZoneData,
@@ -661,7 +666,7 @@ export class LiveStrategy extends EditModeStrategy {
661666
discardHunk,
662667
toggleDiff: !hunkData.isInsertion() ? toggleDiff : undefined,
663668
remove,
664-
move
669+
move,
665670
};
666671

667672
this._hunkDisplayData.set(hunkData, data);
@@ -700,6 +705,10 @@ export class LiveStrategy extends EditModeStrategy {
700705
const remainingHunks = this._session.hunkData.pending;
701706
this._updateSummaryMessage(remainingHunks);
702707

708+
if (this._accessibilityService.isScreenReaderOptimized()) {
709+
this._zone.widget.showAccessibleHunk(this._session, widgetData.hunk);
710+
}
711+
703712
this._ctxCurrentChangeHasDiff.set(Boolean(widgetData.toggleDiff));
704713
this.toggleDiff = widgetData.toggleDiff;
705714
this.acceptHunk = async () => widgetData!.acceptHunk();

src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts

Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,21 @@ import { Emitter, Event, MicrotaskEmitter } from 'vs/base/common/event';
1212
import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent';
1313
import { Lazy } from 'vs/base/common/lazy';
1414
import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
15+
import { ISettableObservable, constObservable, derived, observableValue } from 'vs/base/common/observable';
1516
import { assertType } from 'vs/base/common/types';
1617
import { URI } from 'vs/base/common/uri';
1718
import 'vs/css!./inlineChat';
1819
import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration';
1920
import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser';
2021
import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
2122
import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
23+
import { AccessibleDiffViewer, IAccessibleDiffViewerModel } from 'vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer';
2224
import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
23-
import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions';
25+
import { EditorLayoutInfo, EditorOption, IComputedEditorOptions } from 'vs/editor/common/config/editorOptions';
2426
import { LineRange } from 'vs/editor/common/core/lineRange';
2527
import { Position } from 'vs/editor/common/core/position';
2628
import { IRange, Range } from 'vs/editor/common/core/range';
29+
import { DetailedLineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping';
2730
import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon';
2831
import { LanguageSelector } from 'vs/editor/common/languageSelector';
2932
import { CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList, ProviderResult } from 'vs/editor/common/languages';
@@ -58,7 +61,7 @@ import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/cha
5861
import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
5962
import { ChatModel, ChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel';
6063
import { ChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel';
61-
import { ExpansionState, HunkData } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
64+
import { ExpansionState, HunkData, HunkInformation, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
6265
import { asRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils';
6366
import { ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_VISIBLE, IInlineChatFollowup, IInlineChatSlashCommand, MENU_INLINE_CHAT_INPUT, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_FEEDBACK, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, MENU_INLINE_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
6467
import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel';
@@ -182,6 +185,7 @@ export class InlineChatWidget {
182185
h('div.label.status.hidden@statusLabel'),
183186
h('div.actions.hidden@feedbackToolbar'),
184187
]),
188+
h('div.accessibleViewer@accessibleViewer')
185189
]
186190
);
187191

@@ -204,6 +208,8 @@ export class InlineChatWidget {
204208
private readonly _previewDiffEditor: Lazy<EmbeddedDiffEditorWidget>;
205209
private readonly _previewDiffModel = this._store.add(new MutableDisposable());
206210

211+
private readonly _accessibleViewer = this._store.add(new MutableDisposable<HunkAccessibleDiffViewer>());
212+
207213
private readonly _previewCreateTitle: ResourceLabel;
208214
private readonly _previewCreateEditor: Lazy<ICodeEditor>;
209215
private readonly _previewCreateDispoable = this._store.add(new MutableDisposable());
@@ -467,6 +473,9 @@ export class InlineChatWidget {
467473
layout(_dim: Dimension) {
468474
this._isLayouting = true;
469475
try {
476+
if (this._accessibleViewer.value) {
477+
this._accessibleViewer.value.width = _dim.width - 12;
478+
}
470479
const widgetToolbarWidth = getTotalWidth(this._elements.widgetToolbar);
471480
const editorToolbarWidth = getTotalWidth(this._elements.editorToolbar) + 8 /* L/R-padding */;
472481
const innerEditorWidth = _dim.width - editorToolbarWidth - widgetToolbarWidth;
@@ -489,6 +498,7 @@ export class InlineChatWidget {
489498
this._elements.previewCreate.style.height = `${previewCreateDim.height}px`;
490499
}
491500

501+
492502
const lineHeight = this.parentEditor.getOption(EditorOption.lineHeight);
493503
const editorHeight = this.parentEditor.getLayoutInfo().height;
494504
const editorHeightInLines = Math.floor(editorHeight / lineHeight);
@@ -510,7 +520,8 @@ export class InlineChatWidget {
510520
const previewDiffHeight = this._previewDiffEditor.hasValue && this._previewDiffEditor.value.getModel() ? 12 + Math.min(300, Math.max(0, this._previewDiffEditor.value.getContentHeight())) : 0;
511521
const previewCreateTitleHeight = getTotalHeight(this._elements.previewCreateTitle);
512522
const previewCreateHeight = this._previewCreateEditor.hasValue && this._previewCreateEditor.value.getModel() ? 18 + Math.min(300, Math.max(0, this._previewCreateEditor.value.getContentHeight())) : 0;
513-
return base + editorHeight + detectedIntentHeight + followUpsHeight + chatResponseHeight + previewDiffHeight + previewCreateTitleHeight + previewCreateHeight + 18 /* padding */ + 8 /*shadow*/;
523+
const accessibleViewHeight = this._accessibleViewer.value?.height ?? 0;
524+
return base + editorHeight + detectedIntentHeight + followUpsHeight + chatResponseHeight + previewDiffHeight + previewCreateTitleHeight + previewCreateHeight + accessibleViewHeight + 18 /* padding */ + 8 /*shadow*/;
514525
}
515526

516527
updateProgress(show: boolean) {
@@ -735,6 +746,10 @@ export class InlineChatWidget {
735746
this.updateInfo('');
736747
this.hideCreatePreview();
737748
this.hideEditsPreview();
749+
750+
this._accessibleViewer.clear();
751+
this._elements.accessibleViewer.classList.toggle('hidden', true);
752+
738753
this._onDidChangeHeight.fire();
739754
}
740755

@@ -908,6 +923,25 @@ export class InlineChatWidget {
908923
this._slashCommands.add(this._inputEditor.onDidChangeModelContent(updateSlashDecorations));
909924
updateSlashDecorations();
910925
}
926+
927+
928+
// --- accessible viewer
929+
930+
showAccessibleHunk(session: Session, hunkData: HunkInformation): void {
931+
932+
this._elements.accessibleViewer.classList.remove('hidden');
933+
this._accessibleViewer.clear();
934+
935+
this._accessibleViewer.value = this._instantiationService.createInstance(HunkAccessibleDiffViewer,
936+
this._elements.accessibleViewer,
937+
session,
938+
hunkData,
939+
new AccessibleHunk(this.parentEditor, session, hunkData)
940+
);
941+
942+
this._onDidChangeHeight.fire();
943+
944+
}
911945
}
912946

913947
export class InlineChatZoneWidget extends ZoneWidget {
@@ -1063,3 +1097,88 @@ export class InlineChatZoneWidget extends ZoneWidget {
10631097
aria.status(localize('inlineChatClosed', 'Closed inline chat widget'));
10641098
}
10651099
}
1100+
1101+
class HunkAccessibleDiffViewer extends AccessibleDiffViewer {
1102+
1103+
readonly height: number;
1104+
1105+
set width(value: number) {
1106+
this._width2.set(value, undefined);
1107+
}
1108+
1109+
private readonly _width2: ISettableObservable<number>;
1110+
1111+
constructor(
1112+
parentNode: HTMLElement,
1113+
session: Session,
1114+
hunk: HunkInformation,
1115+
models: IAccessibleDiffViewerModel,
1116+
@IInstantiationService instantiationService: IInstantiationService,
1117+
) {
1118+
const width = observableValue('width', 0);
1119+
const diff = observableValue('diff', HunkAccessibleDiffViewer._asMapping(hunk));
1120+
const diffs = derived(r => [diff.read(r)]);
1121+
const lines = Math.min(6, 3 * diff.get().changedLineCount);
1122+
const height = models.getModifiedOptions().get(EditorOption.lineHeight) * lines;
1123+
1124+
super(parentNode, constObservable(true), () => { }, constObservable(false), width, constObservable(height), diffs, models, instantiationService);
1125+
1126+
this.height = height;
1127+
this._width2 = width;
1128+
1129+
this._store.add(session.textModelN.onDidChangeContent(() => {
1130+
diff.set(HunkAccessibleDiffViewer._asMapping(hunk), undefined);
1131+
}));
1132+
}
1133+
1134+
private static _asMapping(hunk: HunkInformation): DetailedLineRangeMapping {
1135+
const ranges0 = hunk.getRanges0();
1136+
const rangesN = hunk.getRangesN();
1137+
const originalLineRange = LineRange.fromRangeInclusive(ranges0[0]);
1138+
const modifiedLineRange = LineRange.fromRangeInclusive(rangesN[0]);
1139+
const innerChanges: RangeMapping[] = [];
1140+
for (let i = 1; i < ranges0.length; i++) {
1141+
innerChanges.push(new RangeMapping(ranges0[i], rangesN[i]));
1142+
}
1143+
return new DetailedLineRangeMapping(originalLineRange, modifiedLineRange, innerChanges);
1144+
}
1145+
1146+
}
1147+
1148+
class AccessibleHunk implements IAccessibleDiffViewerModel {
1149+
1150+
constructor(
1151+
private readonly _editor: ICodeEditor,
1152+
private readonly _session: Session,
1153+
private readonly _hunk: HunkInformation
1154+
) { }
1155+
1156+
getOriginalModel(): ITextModel {
1157+
return this._session.textModel0;
1158+
}
1159+
getModifiedModel(): ITextModel {
1160+
return this._session.textModelN;
1161+
}
1162+
getOriginalOptions(): IComputedEditorOptions {
1163+
return this._editor.getOptions();
1164+
}
1165+
getModifiedOptions(): IComputedEditorOptions {
1166+
return this._editor.getOptions();
1167+
}
1168+
originalReveal(range: Range): void {
1169+
// throw new Error('Method not implemented.');
1170+
}
1171+
modifiedReveal(range?: Range | undefined): void {
1172+
this._editor.revealRangeInCenterIfOutsideViewport(range || this._hunk.getRangesN()[0], ScrollType.Smooth);
1173+
}
1174+
modifiedSetSelection(range: Range): void {
1175+
// this._editor.revealRangeInCenterIfOutsideViewport(range, ScrollType.Smooth);
1176+
// this._editor.setSelection(range);
1177+
}
1178+
modifiedFocus(): void {
1179+
this._editor.focus();
1180+
}
1181+
getModifiedPosition(): Position | undefined {
1182+
return this._hunk.getRangesN()[0].getStartPosition();
1183+
}
1184+
}

0 commit comments

Comments
 (0)