mirror of
https://github.com/microsoft/vscode.git
synced 2026-08-13 01:04:23 +01:00
Merge pull request #205514 from microsoft/joh/awful-quelea
Show accessible diff viewer in line chat widget
This commit is contained in:
@@ -3,53 +3,57 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-diff-editor .diff-review-line-number {
|
||||
text-align: right;
|
||||
display: inline-block;
|
||||
color: var(--vscode-editorLineNumber-foreground);
|
||||
}
|
||||
|
||||
.monaco-diff-editor .diff-review {
|
||||
position: absolute;
|
||||
.monaco-component.diff-review {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.monaco-diff-editor .diff-review-summary {
|
||||
.monaco-diff-editor .diff-review {
|
||||
position: absolute;
|
||||
|
||||
}
|
||||
|
||||
.monaco-component.diff-review .diff-review-line-number {
|
||||
text-align: right;
|
||||
display: inline-block;
|
||||
color: var(--vscode-editorLineNumber-foreground);
|
||||
}
|
||||
|
||||
.monaco-component.diff-review .diff-review-summary {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.monaco-diff-editor .diff-review-shadow {
|
||||
.monaco-component.diff-review .diff-review-shadow {
|
||||
position: absolute;
|
||||
box-shadow: var(--vscode-scrollbar-shadow) 0 -6px 6px -6px inset;
|
||||
}
|
||||
|
||||
.monaco-diff-editor .diff-review-row {
|
||||
.monaco-component.diff-review .diff-review-row {
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.monaco-diff-editor .diff-review-table {
|
||||
.monaco-component.diff-review .diff-review-table {
|
||||
display: table;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.monaco-diff-editor .diff-review-row {
|
||||
.monaco-component.diff-review .diff-review-row {
|
||||
display: table-row;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.monaco-diff-editor .diff-review-spacer {
|
||||
.monaco-component.diff-review .diff-review-spacer {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.monaco-diff-editor .diff-review-spacer > .codicon {
|
||||
.monaco-component.diff-review .diff-review-spacer > .codicon {
|
||||
font-size: 9px !important;
|
||||
}
|
||||
|
||||
.monaco-diff-editor .diff-review-actions {
|
||||
.monaco-component.diff-review .diff-review-actions {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
@@ -57,12 +61,12 @@
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.monaco-diff-editor .diff-review-actions .action-label {
|
||||
.monaco-component.diff-review .diff-review-actions .action-label {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.monaco-diff-editor .revertButton {
|
||||
.monaco-component.diff-review .revertButton {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -354,7 +354,7 @@ class View extends Disposable {
|
||||
super();
|
||||
|
||||
this.domNode = this._element;
|
||||
this.domNode.className = 'diff-review monaco-editor-background';
|
||||
this.domNode.className = 'monaco-component diff-review monaco-editor-background';
|
||||
|
||||
const actionBarContainer = document.createElement('div');
|
||||
actionBarContainer.className = 'diff-review-actions';
|
||||
@@ -381,6 +381,12 @@ class View extends Disposable {
|
||||
this._scrollbar = this._register(new DomScrollableElement(this._content, {}));
|
||||
reset(this.domNode, this._scrollbar.getDomNode(), actionBarContainer);
|
||||
|
||||
this._register(autorun(r => {
|
||||
this._height.read(r);
|
||||
this._width.read(r);
|
||||
this._scrollbar.scanDomNode();
|
||||
}));
|
||||
|
||||
this._register(toDisposable(() => { reset(this.domNode); }));
|
||||
|
||||
this._register(applyStyle(this.domNode, { width: this._width, height: this._height }));
|
||||
|
||||
@@ -307,6 +307,7 @@
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.monaco-editor .inline-chat .diff-review.hidden,
|
||||
.monaco-editor .inline-chat .previewDiff.hidden,
|
||||
.monaco-editor .inline-chat .previewCreate.hidden,
|
||||
.monaco-editor .inline-chat .previewCreateTitle.hidden {
|
||||
|
||||
@@ -27,7 +27,6 @@ import { ProviderResult, TextEdit } from 'vs/editor/common/languages';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
|
||||
import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
@@ -145,7 +144,6 @@ export class InlineChatController implements IEditorContribution {
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@IDialogService private readonly _dialogService: IDialogService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,
|
||||
@IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService,
|
||||
@IChatAgentService private readonly _chatAgentService: IChatAgentService,
|
||||
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
|
||||
@@ -235,13 +233,7 @@ export class InlineChatController implements IEditorContribution {
|
||||
}
|
||||
|
||||
private _getMode(): EditMode {
|
||||
const editMode = this._configurationService.inspect<EditMode>(InlineChatConfigKeys.Mode);
|
||||
let editModeValue = editMode.value;
|
||||
if (this._accessibilityService.isScreenReaderOptimized() && editModeValue === editMode.defaultValue) {
|
||||
// By default, use preview mode for screen reader users
|
||||
editModeValue = EditMode.Preview;
|
||||
}
|
||||
return editModeValue!;
|
||||
return this._configurationService.getValue<EditMode>(InlineChatConfigKeys.Mode);
|
||||
}
|
||||
|
||||
getWidgetPosition(): Position | undefined {
|
||||
|
||||
@@ -36,6 +36,7 @@ import { HunkState } from './inlineChatSession';
|
||||
import { assertType } from 'vs/base/common/types';
|
||||
import { IModelService } from 'vs/editor/common/services/model';
|
||||
import { performAsyncTextEdit, asProgressiveEdit } from './utils';
|
||||
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
|
||||
|
||||
export interface IEditObserver {
|
||||
start(): void;
|
||||
@@ -404,6 +405,8 @@ type HunkDisplayData = {
|
||||
toggleDiff?: () => any;
|
||||
remove(): void;
|
||||
move: (next: boolean) => void;
|
||||
|
||||
hunk: HunkInformation;
|
||||
};
|
||||
|
||||
|
||||
@@ -441,6 +444,7 @@ export class LiveStrategy extends EditModeStrategy {
|
||||
zone: InlineChatZoneWidget,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IEditorWorkerService protected readonly _editorWorkerService: IEditorWorkerService,
|
||||
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,
|
||||
@IInstantiationService protected readonly _instaService: IInstantiationService,
|
||||
) {
|
||||
super(session, editor, zone);
|
||||
@@ -652,6 +656,7 @@ export class LiveStrategy extends EditModeStrategy {
|
||||
: zoneLineNumber - hunkRanges[0].endLineNumber;
|
||||
|
||||
data = {
|
||||
hunk: hunkData,
|
||||
decorationIds,
|
||||
viewZoneId: '',
|
||||
viewZone: viewZoneData,
|
||||
@@ -661,7 +666,7 @@ export class LiveStrategy extends EditModeStrategy {
|
||||
discardHunk,
|
||||
toggleDiff: !hunkData.isInsertion() ? toggleDiff : undefined,
|
||||
remove,
|
||||
move
|
||||
move,
|
||||
};
|
||||
|
||||
this._hunkDisplayData.set(hunkData, data);
|
||||
@@ -700,6 +705,10 @@ export class LiveStrategy extends EditModeStrategy {
|
||||
const remainingHunks = this._session.hunkData.pending;
|
||||
this._updateSummaryMessage(remainingHunks);
|
||||
|
||||
if (this._accessibilityService.isScreenReaderOptimized()) {
|
||||
this._zone.widget.showAccessibleHunk(this._session, widgetData.hunk);
|
||||
}
|
||||
|
||||
this._ctxCurrentChangeHasDiff.set(Boolean(widgetData.toggleDiff));
|
||||
this.toggleDiff = widgetData.toggleDiff;
|
||||
this.acceptHunk = async () => widgetData!.acceptHunk();
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Emitter, Event, MicrotaskEmitter } from 'vs/base/common/event';
|
||||
import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { Lazy } from 'vs/base/common/lazy';
|
||||
import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ISettableObservable, constObservable, derived, observableValue } from 'vs/base/common/observable';
|
||||
import { assertType } from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import 'vs/css!./inlineChat';
|
||||
@@ -19,11 +20,13 @@ import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfi
|
||||
import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
|
||||
import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||
import { AccessibleDiffViewer, IAccessibleDiffViewerModel } from 'vs/editor/browser/widget/diffEditor/components/accessibleDiffViewer';
|
||||
import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
|
||||
import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { EditorLayoutInfo, EditorOption, IComputedEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { LineRange } from 'vs/editor/common/core/lineRange';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { DetailedLineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping';
|
||||
import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { LanguageSelector } from 'vs/editor/common/languageSelector';
|
||||
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
|
||||
import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||
import { ChatModel, ChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||
import { ChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel';
|
||||
import { ExpansionState, HunkData } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
|
||||
import { ExpansionState, HunkData, HunkInformation, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
|
||||
import { asRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils';
|
||||
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';
|
||||
import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel';
|
||||
@@ -182,6 +185,7 @@ export class InlineChatWidget {
|
||||
h('div.label.status.hidden@statusLabel'),
|
||||
h('div.actions.hidden@feedbackToolbar'),
|
||||
]),
|
||||
h('div.accessibleViewer@accessibleViewer')
|
||||
]
|
||||
);
|
||||
|
||||
@@ -204,6 +208,8 @@ export class InlineChatWidget {
|
||||
private readonly _previewDiffEditor: Lazy<EmbeddedDiffEditorWidget>;
|
||||
private readonly _previewDiffModel = this._store.add(new MutableDisposable());
|
||||
|
||||
private readonly _accessibleViewer = this._store.add(new MutableDisposable<HunkAccessibleDiffViewer>());
|
||||
|
||||
private readonly _previewCreateTitle: ResourceLabel;
|
||||
private readonly _previewCreateEditor: Lazy<ICodeEditor>;
|
||||
private readonly _previewCreateDispoable = this._store.add(new MutableDisposable());
|
||||
@@ -467,6 +473,9 @@ export class InlineChatWidget {
|
||||
layout(_dim: Dimension) {
|
||||
this._isLayouting = true;
|
||||
try {
|
||||
if (this._accessibleViewer.value) {
|
||||
this._accessibleViewer.value.width = _dim.width - 12;
|
||||
}
|
||||
const widgetToolbarWidth = getTotalWidth(this._elements.widgetToolbar);
|
||||
const editorToolbarWidth = getTotalWidth(this._elements.editorToolbar) + 8 /* L/R-padding */;
|
||||
const innerEditorWidth = _dim.width - editorToolbarWidth - widgetToolbarWidth;
|
||||
@@ -489,6 +498,7 @@ export class InlineChatWidget {
|
||||
this._elements.previewCreate.style.height = `${previewCreateDim.height}px`;
|
||||
}
|
||||
|
||||
|
||||
const lineHeight = this.parentEditor.getOption(EditorOption.lineHeight);
|
||||
const editorHeight = this.parentEditor.getLayoutInfo().height;
|
||||
const editorHeightInLines = Math.floor(editorHeight / lineHeight);
|
||||
@@ -510,7 +520,8 @@ export class InlineChatWidget {
|
||||
const previewDiffHeight = this._previewDiffEditor.hasValue && this._previewDiffEditor.value.getModel() ? 12 + Math.min(300, Math.max(0, this._previewDiffEditor.value.getContentHeight())) : 0;
|
||||
const previewCreateTitleHeight = getTotalHeight(this._elements.previewCreateTitle);
|
||||
const previewCreateHeight = this._previewCreateEditor.hasValue && this._previewCreateEditor.value.getModel() ? 18 + Math.min(300, Math.max(0, this._previewCreateEditor.value.getContentHeight())) : 0;
|
||||
return base + editorHeight + detectedIntentHeight + followUpsHeight + chatResponseHeight + previewDiffHeight + previewCreateTitleHeight + previewCreateHeight + 18 /* padding */ + 8 /*shadow*/;
|
||||
const accessibleViewHeight = this._accessibleViewer.value?.height ?? 0;
|
||||
return base + editorHeight + detectedIntentHeight + followUpsHeight + chatResponseHeight + previewDiffHeight + previewCreateTitleHeight + previewCreateHeight + accessibleViewHeight + 18 /* padding */ + 8 /*shadow*/;
|
||||
}
|
||||
|
||||
updateProgress(show: boolean) {
|
||||
@@ -735,6 +746,10 @@ export class InlineChatWidget {
|
||||
this.updateInfo('');
|
||||
this.hideCreatePreview();
|
||||
this.hideEditsPreview();
|
||||
|
||||
this._accessibleViewer.clear();
|
||||
this._elements.accessibleViewer.classList.toggle('hidden', true);
|
||||
|
||||
this._onDidChangeHeight.fire();
|
||||
}
|
||||
|
||||
@@ -908,6 +923,25 @@ export class InlineChatWidget {
|
||||
this._slashCommands.add(this._inputEditor.onDidChangeModelContent(updateSlashDecorations));
|
||||
updateSlashDecorations();
|
||||
}
|
||||
|
||||
|
||||
// --- accessible viewer
|
||||
|
||||
showAccessibleHunk(session: Session, hunkData: HunkInformation): void {
|
||||
|
||||
this._elements.accessibleViewer.classList.remove('hidden');
|
||||
this._accessibleViewer.clear();
|
||||
|
||||
this._accessibleViewer.value = this._instantiationService.createInstance(HunkAccessibleDiffViewer,
|
||||
this._elements.accessibleViewer,
|
||||
session,
|
||||
hunkData,
|
||||
new AccessibleHunk(this.parentEditor, session, hunkData)
|
||||
);
|
||||
|
||||
this._onDidChangeHeight.fire();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class InlineChatZoneWidget extends ZoneWidget {
|
||||
@@ -1063,3 +1097,88 @@ export class InlineChatZoneWidget extends ZoneWidget {
|
||||
aria.status(localize('inlineChatClosed', 'Closed inline chat widget'));
|
||||
}
|
||||
}
|
||||
|
||||
class HunkAccessibleDiffViewer extends AccessibleDiffViewer {
|
||||
|
||||
readonly height: number;
|
||||
|
||||
set width(value: number) {
|
||||
this._width2.set(value, undefined);
|
||||
}
|
||||
|
||||
private readonly _width2: ISettableObservable<number>;
|
||||
|
||||
constructor(
|
||||
parentNode: HTMLElement,
|
||||
session: Session,
|
||||
hunk: HunkInformation,
|
||||
models: IAccessibleDiffViewerModel,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
) {
|
||||
const width = observableValue('width', 0);
|
||||
const diff = observableValue('diff', HunkAccessibleDiffViewer._asMapping(hunk));
|
||||
const diffs = derived(r => [diff.read(r)]);
|
||||
const lines = Math.min(10, 8 + diff.get().changedLineCount);
|
||||
const height = models.getModifiedOptions().get(EditorOption.lineHeight) * lines;
|
||||
|
||||
super(parentNode, constObservable(true), () => { }, constObservable(false), width, constObservable(height), diffs, models, instantiationService);
|
||||
|
||||
this.height = height;
|
||||
this._width2 = width;
|
||||
|
||||
this._store.add(session.textModelN.onDidChangeContent(() => {
|
||||
diff.set(HunkAccessibleDiffViewer._asMapping(hunk), undefined);
|
||||
}));
|
||||
}
|
||||
|
||||
private static _asMapping(hunk: HunkInformation): DetailedLineRangeMapping {
|
||||
const ranges0 = hunk.getRanges0();
|
||||
const rangesN = hunk.getRangesN();
|
||||
const originalLineRange = LineRange.fromRangeInclusive(ranges0[0]);
|
||||
const modifiedLineRange = LineRange.fromRangeInclusive(rangesN[0]);
|
||||
const innerChanges: RangeMapping[] = [];
|
||||
for (let i = 1; i < ranges0.length; i++) {
|
||||
innerChanges.push(new RangeMapping(ranges0[i], rangesN[i]));
|
||||
}
|
||||
return new DetailedLineRangeMapping(originalLineRange, modifiedLineRange, innerChanges);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class AccessibleHunk implements IAccessibleDiffViewerModel {
|
||||
|
||||
constructor(
|
||||
private readonly _editor: ICodeEditor,
|
||||
private readonly _session: Session,
|
||||
private readonly _hunk: HunkInformation
|
||||
) { }
|
||||
|
||||
getOriginalModel(): ITextModel {
|
||||
return this._session.textModel0;
|
||||
}
|
||||
getModifiedModel(): ITextModel {
|
||||
return this._session.textModelN;
|
||||
}
|
||||
getOriginalOptions(): IComputedEditorOptions {
|
||||
return this._editor.getOptions();
|
||||
}
|
||||
getModifiedOptions(): IComputedEditorOptions {
|
||||
return this._editor.getOptions();
|
||||
}
|
||||
originalReveal(range: Range): void {
|
||||
// throw new Error('Method not implemented.');
|
||||
}
|
||||
modifiedReveal(range?: Range | undefined): void {
|
||||
this._editor.revealRangeInCenterIfOutsideViewport(range || this._hunk.getRangesN()[0], ScrollType.Smooth);
|
||||
}
|
||||
modifiedSetSelection(range: Range): void {
|
||||
// this._editor.revealRangeInCenterIfOutsideViewport(range, ScrollType.Smooth);
|
||||
// this._editor.setSelection(range);
|
||||
}
|
||||
modifiedFocus(): void {
|
||||
this._editor.focus();
|
||||
}
|
||||
getModifiedPosition(): Position | undefined {
|
||||
return this._hunk.getRangesN()[0].getStartPosition();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user