Path: blob/main/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { $, Dimension, getActiveElement, getTotalHeight, getWindow, h, reset, trackFocus } from '../../../../base/browser/dom.js';6import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';7import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';8import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js';9import { IAction } from '../../../../base/common/actions.js';10import { Emitter, Event } from '../../../../base/common/event.js';11import { DisposableStore, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';12import { constObservable, derived, IObservable, ISettableObservable, observableValue } from '../../../../base/common/observable.js';13import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js';14import { AccessibleDiffViewer, IAccessibleDiffViewerModel } from '../../../../editor/browser/widget/diffEditor/components/accessibleDiffViewer.js';15import { EditorOption, IComputedEditorOptions } from '../../../../editor/common/config/editorOptions.js';16import { LineRange } from '../../../../editor/common/core/ranges/lineRange.js';17import { Position } from '../../../../editor/common/core/position.js';18import { Range } from '../../../../editor/common/core/range.js';19import { Selection } from '../../../../editor/common/core/selection.js';20import { DetailedLineRangeMapping, RangeMapping } from '../../../../editor/common/diff/rangeMapping.js';21import { ICodeEditorViewState, ScrollType } from '../../../../editor/common/editorCommon.js';22import { ITextModel } from '../../../../editor/common/model.js';23import { ITextModelService } from '../../../../editor/common/services/resolverService.js';24import { localize } from '../../../../nls.js';25import { IAccessibleViewService } from '../../../../platform/accessibility/browser/accessibleView.js';26import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js';27import { IWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from '../../../../platform/actions/browser/buttonbar.js';28import { createActionViewItem, IMenuEntryActionViewItemOptions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';29import { MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js';30import { MenuId, MenuItemAction } from '../../../../platform/actions/common/actions.js';31import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';32import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';33import { IHoverService } from '../../../../platform/hover/browser/hover.js';34import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';35import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js';36import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';37import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js';38import { asCssVariable, asCssVariableName, editorBackground, inputBackground } from '../../../../platform/theme/common/colorRegistry.js';39import { EDITOR_DRAG_AND_DROP_BACKGROUND } from '../../../common/theme.js';40import { AccessibilityVerbositySettingId } from '../../accessibility/browser/accessibilityConfiguration.js';41import { AccessibilityCommandId } from '../../accessibility/common/accessibilityCommands.js';42import { MarkUnhelpfulActionId } from '../../chat/browser/actions/chatTitleActions.js';43import { IChatWidgetViewOptions } from '../../chat/browser/chat.js';44import { ChatVoteDownButton } from '../../chat/browser/chatListRenderer.js';45import { ChatWidget, IChatViewState, IChatWidgetLocationOptions } from '../../chat/browser/chatWidget.js';46import { chatRequestBackground } from '../../chat/common/chatColors.js';47import { ChatContextKeys } from '../../chat/common/chatContextKeys.js';48import { IChatModel } from '../../chat/common/chatModel.js';49import { ChatAgentVoteDirection, IChatService } from '../../chat/common/chatService.js';50import { isResponseVM } from '../../chat/common/chatViewModel.js';51import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_RESPONSE_FOCUSED, inlineChatBackground, inlineChatForeground } from '../common/inlineChat.js';52import { HunkInformation, Session } from './inlineChatSession.js';53import './media/inlineChat.css';545556export interface InlineChatWidgetViewState {57editorViewState: ICodeEditorViewState;58input: string;59placeholder: string;60}6162export interface IInlineChatWidgetConstructionOptions {6364/**65* The menu that rendered as button bar, use for accept, discard etc66*/67statusMenuId: MenuId | { menu: MenuId; options: IWorkbenchButtonBarOptions };6869secondaryMenuId?: MenuId;7071/**72* The options for the chat widget73*/74chatWidgetViewOptions?: IChatWidgetViewOptions;7576inZoneWidget?: boolean;77}7879export class InlineChatWidget {8081protected readonly _elements = h(82'div.inline-chat@root',83[84h('div.chat-widget@chatWidget'),85h('div.accessibleViewer@accessibleViewer'),86h('div.status@status', [87h('div.label.info.hidden@infoLabel'),88h('div.actions.hidden@toolbar1'),89h('div.label.status.hidden@statusLabel'),90h('div.actions.secondary.hidden@toolbar2'),91]),92]93);9495protected readonly _store = new DisposableStore();9697private readonly _ctxInputEditorFocused: IContextKey<boolean>;98private readonly _ctxResponseFocused: IContextKey<boolean>;99100private readonly _chatWidget: ChatWidget;101102protected readonly _onDidChangeHeight = this._store.add(new Emitter<void>());103readonly onDidChangeHeight: Event<void> = Event.filter(this._onDidChangeHeight.event, _ => !this._isLayouting);104105private readonly _requestInProgress = observableValue(this, false);106readonly requestInProgress: IObservable<boolean> = this._requestInProgress;107108private _isLayouting: boolean = false;109110readonly scopedContextKeyService: IContextKeyService;111112constructor(113location: IChatWidgetLocationOptions,114private readonly _options: IInlineChatWidgetConstructionOptions,115@IInstantiationService protected readonly _instantiationService: IInstantiationService,116@IContextKeyService private readonly _contextKeyService: IContextKeyService,117@IKeybindingService private readonly _keybindingService: IKeybindingService,118@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,119@IConfigurationService private readonly _configurationService: IConfigurationService,120@IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService,121@ITextModelService protected readonly _textModelResolverService: ITextModelService,122@IChatService private readonly _chatService: IChatService,123@IHoverService private readonly _hoverService: IHoverService,124) {125this.scopedContextKeyService = this._store.add(_contextKeyService.createScoped(this._elements.chatWidget));126const scopedInstaService = _instantiationService.createChild(127new ServiceCollection([128IContextKeyService,129this.scopedContextKeyService130]),131this._store132);133134this._chatWidget = scopedInstaService.createInstance(135ChatWidget,136location,137{ isInlineChat: true },138{139autoScroll: true,140defaultElementHeight: 32,141renderStyle: 'minimal',142renderInputOnTop: false,143renderFollowups: true,144supportsFileReferences: true,145filter: item => {146if (!isResponseVM(item) || item.errorDetails) {147// show all requests and errors148return true;149}150const emptyResponse = item.response.value.length === 0;151if (emptyResponse) {152return false;153}154if (item.response.value.every(item => item.kind === 'textEditGroup' && _options.chatWidgetViewOptions?.rendererOptions?.renderTextEditsAsSummary?.(item.uri))) {155return false;156}157return true;158},159dndContainer: this._elements.root,160..._options.chatWidgetViewOptions161},162{163listForeground: inlineChatForeground,164listBackground: inlineChatBackground,165overlayBackground: EDITOR_DRAG_AND_DROP_BACKGROUND,166inputEditorBackground: inputBackground,167resultEditorBackground: editorBackground168}169);170this._elements.root.classList.toggle('in-zone-widget', !!_options.inZoneWidget);171this._chatWidget.render(this._elements.chatWidget);172this._elements.chatWidget.style.setProperty(asCssVariableName(chatRequestBackground), asCssVariable(inlineChatBackground));173this._chatWidget.setVisible(true);174this._store.add(this._chatWidget);175176const ctxResponse = ChatContextKeys.isResponse.bindTo(this.scopedContextKeyService);177const ctxResponseVote = ChatContextKeys.responseVote.bindTo(this.scopedContextKeyService);178const ctxResponseSupportIssues = ChatContextKeys.responseSupportsIssueReporting.bindTo(this.scopedContextKeyService);179const ctxResponseError = ChatContextKeys.responseHasError.bindTo(this.scopedContextKeyService);180const ctxResponseErrorFiltered = ChatContextKeys.responseIsFiltered.bindTo(this.scopedContextKeyService);181182const viewModelStore = this._store.add(new DisposableStore());183this._store.add(this._chatWidget.onDidChangeViewModel(() => {184viewModelStore.clear();185186const viewModel = this._chatWidget.viewModel;187if (!viewModel) {188return;189}190191viewModelStore.add(toDisposable(() => {192toolbar2.context = undefined;193ctxResponse.reset();194ctxResponseVote.reset();195ctxResponseError.reset();196ctxResponseErrorFiltered.reset();197ctxResponseSupportIssues.reset();198}));199200viewModelStore.add(viewModel.onDidChange(() => {201202this._requestInProgress.set(viewModel.requestInProgress, undefined);203204const last = viewModel.getItems().at(-1);205toolbar2.context = last;206207ctxResponse.set(isResponseVM(last));208ctxResponseVote.set(isResponseVM(last) ? last.vote === ChatAgentVoteDirection.Down ? 'down' : last.vote === ChatAgentVoteDirection.Up ? 'up' : '' : '');209ctxResponseError.set(isResponseVM(last) && last.errorDetails !== undefined);210ctxResponseErrorFiltered.set((!!(isResponseVM(last) && last.errorDetails?.responseIsFiltered)));211ctxResponseSupportIssues.set(isResponseVM(last) && (last.agent?.metadata.supportIssueReporting ?? false));212213this._onDidChangeHeight.fire();214}));215this._onDidChangeHeight.fire();216}));217218this._store.add(this.chatWidget.onDidChangeContentHeight(() => {219this._onDidChangeHeight.fire();220}));221222// context keys223this._ctxResponseFocused = CTX_INLINE_CHAT_RESPONSE_FOCUSED.bindTo(this._contextKeyService);224const tracker = this._store.add(trackFocus(this.domNode));225this._store.add(tracker.onDidBlur(() => this._ctxResponseFocused.set(false)));226this._store.add(tracker.onDidFocus(() => this._ctxResponseFocused.set(true)));227228this._ctxInputEditorFocused = CTX_INLINE_CHAT_FOCUSED.bindTo(_contextKeyService);229this._store.add(this._chatWidget.inputEditor.onDidFocusEditorWidget(() => this._ctxInputEditorFocused.set(true)));230this._store.add(this._chatWidget.inputEditor.onDidBlurEditorWidget(() => this._ctxInputEditorFocused.set(false)));231232const statusMenuId = _options.statusMenuId instanceof MenuId ? _options.statusMenuId : _options.statusMenuId.menu;233234// BUTTON bar235const statusMenuOptions = _options.statusMenuId instanceof MenuId ? undefined : _options.statusMenuId.options;236const statusButtonBar = scopedInstaService.createInstance(MenuWorkbenchButtonBar, this._elements.toolbar1, statusMenuId, {237toolbarOptions: { primaryGroup: '0_main' },238telemetrySource: _options.chatWidgetViewOptions?.menus?.telemetrySource,239menuOptions: { renderShortTitle: true },240...statusMenuOptions,241});242this._store.add(statusButtonBar.onDidChange(() => this._onDidChangeHeight.fire()));243this._store.add(statusButtonBar);244245// secondary toolbar246const toolbar2 = scopedInstaService.createInstance(MenuWorkbenchToolBar, this._elements.toolbar2, _options.secondaryMenuId ?? MenuId.for(''), {247telemetrySource: _options.chatWidgetViewOptions?.menus?.telemetrySource,248menuOptions: { renderShortTitle: true, shouldForwardArgs: true },249actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => {250if (action instanceof MenuItemAction && action.item.id === MarkUnhelpfulActionId) {251return scopedInstaService.createInstance(ChatVoteDownButton, action, options as IMenuEntryActionViewItemOptions);252}253return createActionViewItem(scopedInstaService, action, options);254}255});256this._store.add(toolbar2.onDidChangeMenuItems(() => this._onDidChangeHeight.fire()));257this._store.add(toolbar2);258259260this._store.add(this._configurationService.onDidChangeConfiguration(e => {261if (e.affectsConfiguration(AccessibilityVerbositySettingId.InlineChat)) {262this._updateAriaLabel();263}264}));265266this._elements.root.tabIndex = 0;267this._elements.statusLabel.tabIndex = 0;268this._updateAriaLabel();269270// this._elements.status271this._store.add(this._hoverService.setupManagedHover(getDefaultHoverDelegate('element'), this._elements.statusLabel, () => {272return this._elements.statusLabel.dataset['title'];273}));274275this._store.add(this._chatService.onDidPerformUserAction(e => {276if (e.sessionId === this._chatWidget.viewModel?.model.sessionId && e.action.kind === 'vote') {277this.updateStatus('Thank you for your feedback!', { resetAfter: 1250 });278}279}));280}281282private _updateAriaLabel(): void {283284this._elements.root.ariaLabel = this._accessibleViewService.getOpenAriaHint(AccessibilityVerbositySettingId.InlineChat);285286if (this._accessibilityService.isScreenReaderOptimized()) {287let label = defaultAriaLabel;288if (this._configurationService.getValue<boolean>(AccessibilityVerbositySettingId.InlineChat)) {289const kbLabel = this._keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp)?.getLabel();290label = kbLabel291? localize('inlineChat.accessibilityHelp', "Inline Chat Input, Use {0} for Inline Chat Accessibility Help.", kbLabel)292: localize('inlineChat.accessibilityHelpNoKb', "Inline Chat Input, Run the Inline Chat Accessibility Help command for more information.");293}294this._chatWidget.inputEditor.updateOptions({ ariaLabel: label });295}296}297298dispose(): void {299this._store.dispose();300}301302get domNode(): HTMLElement {303return this._elements.root;304}305306get chatWidget(): ChatWidget {307return this._chatWidget;308}309310saveState() {311this._chatWidget.saveState();312}313314layout(widgetDim: Dimension) {315const contentHeight = this.contentHeight;316this._isLayouting = true;317try {318this._doLayout(widgetDim);319} finally {320this._isLayouting = false;321322if (this.contentHeight !== contentHeight) {323this._onDidChangeHeight.fire();324}325}326}327328protected _doLayout(dimension: Dimension): void {329const extraHeight = this._getExtraHeight();330const statusHeight = getTotalHeight(this._elements.status);331332// console.log('ZONE#Widget#layout', { height: dimension.height, extraHeight, progressHeight, followUpsHeight, statusHeight, LIST: dimension.height - progressHeight - followUpsHeight - statusHeight - extraHeight });333334this._elements.root.style.height = `${dimension.height - extraHeight}px`;335this._elements.root.style.width = `${dimension.width}px`;336337this._chatWidget.layout(338dimension.height - statusHeight - extraHeight,339dimension.width340);341}342343/**344* The content height of this widget is the size that would require no scrolling345*/346get contentHeight(): number {347const data = {348chatWidgetContentHeight: this._chatWidget.contentHeight,349statusHeight: getTotalHeight(this._elements.status),350extraHeight: this._getExtraHeight()351};352const result = data.chatWidgetContentHeight + data.statusHeight + data.extraHeight;353return result;354}355356get minHeight(): number {357// The chat widget is variable height and supports scrolling. It should be358// at least "maxWidgetHeight" high and at most the content height.359360let maxWidgetOutputHeight = 100;361for (const item of this._chatWidget.viewModel?.getItems() ?? []) {362if (isResponseVM(item) && item.response.value.some(r => r.kind === 'textEditGroup' && !r.state?.applied)) {363maxWidgetOutputHeight = 270;364break;365}366}367368let value = this.contentHeight;369value -= this._chatWidget.contentHeight;370value += Math.min(this._chatWidget.input.contentHeight + maxWidgetOutputHeight, this._chatWidget.contentHeight);371return value;372}373374protected _getExtraHeight(): number {375return this._options.inZoneWidget ? 1 : (2 /*border*/ + 4 /*shadow*/);376}377378get value(): string {379return this._chatWidget.getInput();380}381382set value(value: string) {383this._chatWidget.setInput(value);384}385386selectAll() {387this._chatWidget.inputEditor.setSelection(new Selection(1, 1, Number.MAX_SAFE_INTEGER, 1));388}389390set placeholder(value: string) {391this._chatWidget.setInputPlaceholder(value);392}393394toggleStatus(show: boolean) {395this._elements.toolbar1.classList.toggle('hidden', !show);396this._elements.toolbar2.classList.toggle('hidden', !show);397this._elements.status.classList.toggle('hidden', !show);398this._elements.infoLabel.classList.toggle('hidden', !show);399this._onDidChangeHeight.fire();400}401402updateToolbar(show: boolean) {403this._elements.root.classList.toggle('toolbar', show);404this._elements.toolbar1.classList.toggle('hidden', !show);405this._elements.toolbar2.classList.toggle('hidden', !show);406this._elements.status.classList.toggle('actions', show);407this._elements.infoLabel.classList.toggle('hidden', show);408this._onDidChangeHeight.fire();409}410411async getCodeBlockInfo(codeBlockIndex: number): Promise<ITextModel | undefined> {412const { viewModel } = this._chatWidget;413if (!viewModel) {414return undefined;415}416const items = viewModel.getItems().filter(i => isResponseVM(i));417const item = items.at(-1);418if (!item) {419return;420}421return viewModel.codeBlockModelCollection.get(viewModel.sessionId, item, codeBlockIndex)?.model;422}423424get responseContent(): string | undefined {425const requests = this._chatWidget.viewModel?.model.getRequests();426return requests?.at(-1)?.response?.response.toString();427}428429430getChatModel(): IChatModel | undefined {431return this._chatWidget.viewModel?.model;432}433434setChatModel(chatModel: IChatModel, state?: IChatViewState) {435this._chatWidget.setModel(chatModel, { ...state, inputValue: undefined });436}437438updateInfo(message: string): void {439this._elements.infoLabel.classList.toggle('hidden', !message);440const renderedMessage = renderLabelWithIcons(message);441reset(this._elements.infoLabel, ...renderedMessage);442this._onDidChangeHeight.fire();443}444445updateStatus(message: string, ops: { classes?: string[]; resetAfter?: number; keepMessage?: boolean; title?: string } = {}) {446const isTempMessage = typeof ops.resetAfter === 'number';447if (isTempMessage && !this._elements.statusLabel.dataset['state']) {448const statusLabel = this._elements.statusLabel.innerText;449const title = this._elements.statusLabel.dataset['title'];450const classes = Array.from(this._elements.statusLabel.classList.values());451setTimeout(() => {452this.updateStatus(statusLabel, { classes, keepMessage: true, title });453}, ops.resetAfter);454}455const renderedMessage = renderLabelWithIcons(message);456reset(this._elements.statusLabel, ...renderedMessage);457this._elements.statusLabel.className = `label status ${(ops.classes ?? []).join(' ')}`;458this._elements.statusLabel.classList.toggle('hidden', !message);459if (isTempMessage) {460this._elements.statusLabel.dataset['state'] = 'temp';461} else {462delete this._elements.statusLabel.dataset['state'];463}464465if (ops.title) {466this._elements.statusLabel.dataset['title'] = ops.title;467} else {468delete this._elements.statusLabel.dataset['title'];469}470this._onDidChangeHeight.fire();471}472473reset() {474this._chatWidget.attachmentModel.clear(true);475this._chatWidget.saveState();476477reset(this._elements.statusLabel);478this._elements.statusLabel.classList.toggle('hidden', true);479this._elements.toolbar1.classList.add('hidden');480this._elements.toolbar2.classList.add('hidden');481this.updateInfo('');482483this._elements.accessibleViewer.classList.toggle('hidden', true);484this._onDidChangeHeight.fire();485}486487focus() {488this._chatWidget.focusInput();489}490491hasFocus() {492return this.domNode.contains(getActiveElement());493}494495}496497const defaultAriaLabel = localize('aria-label', "Inline Chat Input");498499export class EditorBasedInlineChatWidget extends InlineChatWidget {500501private readonly _accessibleViewer = this._store.add(new MutableDisposable<HunkAccessibleDiffViewer>());502503504constructor(505location: IChatWidgetLocationOptions,506private readonly _parentEditor: ICodeEditor,507options: IInlineChatWidgetConstructionOptions,508@IContextKeyService contextKeyService: IContextKeyService,509@IKeybindingService keybindingService: IKeybindingService,510@IInstantiationService instantiationService: IInstantiationService,511@IAccessibilityService accessibilityService: IAccessibilityService,512@IConfigurationService configurationService: IConfigurationService,513@IAccessibleViewService accessibleViewService: IAccessibleViewService,514@ITextModelService textModelResolverService: ITextModelService,515@IChatService chatService: IChatService,516@IHoverService hoverService: IHoverService,517@ILayoutService layoutService: ILayoutService518) {519const overflowWidgetsNode = layoutService.getContainer(getWindow(_parentEditor.getContainerDomNode())).appendChild($('.inline-chat-overflow.monaco-editor'));520super(location, {521...options,522chatWidgetViewOptions: {523...options.chatWidgetViewOptions,524editorOverflowWidgetsDomNode: overflowWidgetsNode525}526}, instantiationService, contextKeyService, keybindingService, accessibilityService, configurationService, accessibleViewService, textModelResolverService, chatService, hoverService);527528this._store.add(toDisposable(() => {529overflowWidgetsNode.remove();530}));531}532533// --- layout534535override get contentHeight(): number {536let result = super.contentHeight;537538if (this._accessibleViewer.value) {539result += this._accessibleViewer.value.height + 8 /* padding */;540}541542return result;543}544545protected override _doLayout(dimension: Dimension): void {546547let newHeight = dimension.height;548549if (this._accessibleViewer.value) {550this._accessibleViewer.value.width = dimension.width - 12;551newHeight -= this._accessibleViewer.value.height + 8;552}553554super._doLayout(dimension.with(undefined, newHeight));555556// update/fix the height of the zone which was set to newHeight in super._doLayout557this._elements.root.style.height = `${dimension.height - this._getExtraHeight()}px`;558}559560override reset() {561this._accessibleViewer.clear();562super.reset();563}564565// --- accessible viewer566567showAccessibleHunk(session: Session, hunkData: HunkInformation): void {568569this._elements.accessibleViewer.classList.remove('hidden');570this._accessibleViewer.clear();571572this._accessibleViewer.value = this._instantiationService.createInstance(HunkAccessibleDiffViewer,573this._elements.accessibleViewer,574session,575hunkData,576new AccessibleHunk(this._parentEditor, session, hunkData)577);578579this._onDidChangeHeight.fire();580}581}582583class HunkAccessibleDiffViewer extends AccessibleDiffViewer {584585readonly height: number;586587set width(value: number) {588this._width2.set(value, undefined);589}590591private readonly _width2: ISettableObservable<number>;592593constructor(594parentNode: HTMLElement,595session: Session,596hunk: HunkInformation,597models: IAccessibleDiffViewerModel,598@IInstantiationService instantiationService: IInstantiationService,599) {600const width = observableValue('width', 0);601const diff = observableValue('diff', HunkAccessibleDiffViewer._asMapping(hunk));602const diffs = derived(r => [diff.read(r)]);603const lines = Math.min(10, 8 + diff.get().changedLineCount);604const height = models.getModifiedOptions().get(EditorOption.lineHeight) * lines;605606super(parentNode, constObservable(true), () => { }, constObservable(false), width, constObservable(height), diffs, models, instantiationService);607608this.height = height;609this._width2 = width;610611this._store.add(session.textModelN.onDidChangeContent(() => {612diff.set(HunkAccessibleDiffViewer._asMapping(hunk), undefined);613}));614}615616private static _asMapping(hunk: HunkInformation): DetailedLineRangeMapping {617const ranges0 = hunk.getRanges0();618const rangesN = hunk.getRangesN();619const originalLineRange = LineRange.fromRangeInclusive(ranges0[0]);620const modifiedLineRange = LineRange.fromRangeInclusive(rangesN[0]);621const innerChanges: RangeMapping[] = [];622for (let i = 1; i < ranges0.length; i++) {623innerChanges.push(new RangeMapping(ranges0[i], rangesN[i]));624}625return new DetailedLineRangeMapping(originalLineRange, modifiedLineRange, innerChanges);626}627628}629630class AccessibleHunk implements IAccessibleDiffViewerModel {631632constructor(633private readonly _editor: ICodeEditor,634private readonly _session: Session,635private readonly _hunk: HunkInformation636) { }637638getOriginalModel(): ITextModel {639return this._session.textModel0;640}641getModifiedModel(): ITextModel {642return this._session.textModelN;643}644getOriginalOptions(): IComputedEditorOptions {645return this._editor.getOptions();646}647getModifiedOptions(): IComputedEditorOptions {648return this._editor.getOptions();649}650originalReveal(range: Range): void {651// throw new Error('Method not implemented.');652}653modifiedReveal(range?: Range | undefined): void {654this._editor.revealRangeInCenterIfOutsideViewport(range || this._hunk.getRangesN()[0], ScrollType.Smooth);655}656modifiedSetSelection(range: Range): void {657// this._editor.revealRangeInCenterIfOutsideViewport(range, ScrollType.Smooth);658// this._editor.setSelection(range);659}660modifiedFocus(): void {661this._editor.focus();662}663getModifiedPosition(): Position | undefined {664return this._hunk.getRangesN()[0].getStartPosition();665}666}667668669