Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts
5241 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
6
import { $, Dimension, getActiveElement, getTotalHeight, getWindow, h, reset, trackFocus } from '../../../../base/browser/dom.js';
7
import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';
8
import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';
9
import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js';
10
import { IAction } from '../../../../base/common/actions.js';
11
import { Emitter, Event } from '../../../../base/common/event.js';
12
import { MarkdownString } from '../../../../base/common/htmlContent.js';
13
import { DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js';
14
import { autorun, IObservable, observableValue } from '../../../../base/common/observable.js';
15
import { isEqual } from '../../../../base/common/resources.js';
16
import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js';
17
import { Selection } from '../../../../editor/common/core/selection.js';
18
import { ICodeEditorViewState } from '../../../../editor/common/editorCommon.js';
19
import { ITextModel } from '../../../../editor/common/model.js';
20
import { ITextModelService } from '../../../../editor/common/services/resolverService.js';
21
import { localize } from '../../../../nls.js';
22
import { IAccessibleViewService } from '../../../../platform/accessibility/browser/accessibleView.js';
23
import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js';
24
import { IWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from '../../../../platform/actions/browser/buttonbar.js';
25
import { createActionViewItem, IMenuEntryActionViewItemOptions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';
26
import { MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js';
27
import { MenuId, MenuItemAction } from '../../../../platform/actions/common/actions.js';
28
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
29
import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
30
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
31
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
32
import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js';
33
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
34
import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js';
35
import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js';
36
import product from '../../../../platform/product/common/product.js';
37
import { asCssVariable, asCssVariableName, editorBackground, inputBackground } from '../../../../platform/theme/common/colorRegistry.js';
38
import { EDITOR_DRAG_AND_DROP_BACKGROUND } from '../../../common/theme.js';
39
import { IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js';
40
import { AccessibilityVerbositySettingId } from '../../accessibility/browser/accessibilityConfiguration.js';
41
import { AccessibilityCommandId } from '../../accessibility/common/accessibilityCommands.js';
42
import { MarkUnhelpfulActionId } from '../../chat/browser/actions/chatTitleActions.js';
43
import { IChatWidgetViewOptions } from '../../chat/browser/chat.js';
44
import { ChatVoteDownButton } from '../../chat/browser/widget/chatListRenderer.js';
45
import { ChatWidget, IChatWidgetLocationOptions } from '../../chat/browser/widget/chatWidget.js';
46
import { chatRequestBackground } from '../../chat/common/widget/chatColors.js';
47
import { ChatContextKeys } from '../../chat/common/actions/chatContextKeys.js';
48
import { IChatModel } from '../../chat/common/model/chatModel.js';
49
import { ChatMode } from '../../chat/common/chatModes.js';
50
import { ChatAgentVoteDirection, IChatService } from '../../chat/common/chatService/chatService.js';
51
import { isResponseVM } from '../../chat/common/model/chatViewModel.js';
52
import * as marked from '../../../../base/common/marked/marked.js';
53
import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_RESPONSE_FOCUSED, inlineChatBackground, inlineChatForeground } from '../common/inlineChat.js';
54
import './media/inlineChat.css';
55
56
export interface InlineChatWidgetViewState {
57
editorViewState: ICodeEditorViewState;
58
input: string;
59
placeholder: string;
60
}
61
62
export interface IInlineChatWidgetConstructionOptions {
63
64
/**
65
* The menu that rendered as button bar, use for accept, discard etc
66
*/
67
statusMenuId: MenuId | { menu: MenuId; options: IWorkbenchButtonBarOptions };
68
69
secondaryMenuId?: MenuId;
70
71
/**
72
* The options for the chat widget
73
*/
74
chatWidgetViewOptions?: IChatWidgetViewOptions;
75
76
inZoneWidget?: boolean;
77
}
78
79
export class InlineChatWidget {
80
81
protected readonly _elements = h(
82
'div.inline-chat@root',
83
[
84
h('div.chat-widget@chatWidget'),
85
h('div.accessibleViewer@accessibleViewer'),
86
h('div.status@status', [
87
h('div.label.info.hidden@infoLabel'),
88
h('div.actions.hidden@toolbar1'),
89
h('div.label.status.hidden@statusLabel'),
90
h('div.actions.secondary.hidden@toolbar2'),
91
h('div.label.disclaimer.hidden@disclaimerLabel'),
92
]),
93
]
94
);
95
96
protected readonly _store = new DisposableStore();
97
98
private readonly _ctxInputEditorFocused: IContextKey<boolean>;
99
private readonly _ctxResponseFocused: IContextKey<boolean>;
100
101
private readonly _chatWidget: ChatWidget;
102
103
protected readonly _onDidChangeHeight = this._store.add(new Emitter<void>());
104
readonly onDidChangeHeight: Event<void> = Event.filter(this._onDidChangeHeight.event, _ => !this._isLayouting);
105
106
private readonly _requestInProgress = observableValue(this, false);
107
readonly requestInProgress: IObservable<boolean> = this._requestInProgress;
108
109
private _isLayouting: boolean = false;
110
111
readonly scopedContextKeyService: IContextKeyService;
112
113
constructor(
114
location: IChatWidgetLocationOptions,
115
private readonly _options: IInlineChatWidgetConstructionOptions,
116
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
117
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
118
@IKeybindingService private readonly _keybindingService: IKeybindingService,
119
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,
120
@IConfigurationService private readonly _configurationService: IConfigurationService,
121
@IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService,
122
@ITextModelService protected readonly _textModelResolverService: ITextModelService,
123
@IChatService private readonly _chatService: IChatService,
124
@IHoverService private readonly _hoverService: IHoverService,
125
@IChatEntitlementService private readonly _chatEntitlementService: IChatEntitlementService,
126
@IMarkdownRendererService private readonly _markdownRendererService: IMarkdownRendererService,
127
) {
128
this.scopedContextKeyService = this._store.add(_contextKeyService.createScoped(this._elements.chatWidget));
129
const scopedInstaService = _instantiationService.createChild(
130
new ServiceCollection([
131
IContextKeyService,
132
this.scopedContextKeyService
133
]),
134
this._store
135
);
136
137
this._chatWidget = scopedInstaService.createInstance(
138
ChatWidget,
139
location,
140
{ isInlineChat: true },
141
{
142
autoScroll: true,
143
defaultElementHeight: 32,
144
renderStyle: 'minimal',
145
renderInputOnTop: false,
146
renderFollowups: true,
147
supportsFileReferences: true,
148
filter: item => {
149
if (!isResponseVM(item) || item.errorDetails) {
150
// show all requests and errors
151
return true;
152
}
153
const emptyResponse = item.response.value.length === 0;
154
if (emptyResponse) {
155
return false;
156
}
157
if (item.response.value.every(item => item.kind === 'textEditGroup' && _options.chatWidgetViewOptions?.rendererOptions?.renderTextEditsAsSummary?.(item.uri))) {
158
return false;
159
}
160
return true;
161
},
162
dndContainer: this._elements.root,
163
defaultMode: ChatMode.Ask,
164
..._options.chatWidgetViewOptions
165
},
166
{
167
listForeground: inlineChatForeground,
168
listBackground: inlineChatBackground,
169
overlayBackground: EDITOR_DRAG_AND_DROP_BACKGROUND,
170
inputEditorBackground: inputBackground,
171
resultEditorBackground: editorBackground
172
}
173
);
174
this._elements.root.classList.toggle('in-zone-widget', !!_options.inZoneWidget);
175
this._chatWidget.render(this._elements.chatWidget);
176
this._elements.chatWidget.style.setProperty(asCssVariableName(chatRequestBackground), asCssVariable(inlineChatBackground));
177
this._chatWidget.setVisible(true);
178
this._store.add(this._chatWidget);
179
180
const ctxResponse = ChatContextKeys.isResponse.bindTo(this.scopedContextKeyService);
181
const ctxResponseVote = ChatContextKeys.responseVote.bindTo(this.scopedContextKeyService);
182
const ctxResponseSupportIssues = ChatContextKeys.responseSupportsIssueReporting.bindTo(this.scopedContextKeyService);
183
const ctxResponseError = ChatContextKeys.responseHasError.bindTo(this.scopedContextKeyService);
184
const ctxResponseErrorFiltered = ChatContextKeys.responseIsFiltered.bindTo(this.scopedContextKeyService);
185
186
const viewModelStore = this._store.add(new DisposableStore());
187
this._store.add(this._chatWidget.onDidChangeViewModel(() => {
188
viewModelStore.clear();
189
190
const viewModel = this._chatWidget.viewModel;
191
if (!viewModel) {
192
return;
193
}
194
195
viewModelStore.add(toDisposable(() => {
196
toolbar2.context = undefined;
197
ctxResponse.reset();
198
ctxResponseVote.reset();
199
ctxResponseError.reset();
200
ctxResponseErrorFiltered.reset();
201
ctxResponseSupportIssues.reset();
202
}));
203
204
viewModelStore.add(viewModel.onDidChange(() => {
205
206
this._requestInProgress.set(viewModel.model.requestInProgress.get(), undefined);
207
208
const last = viewModel.getItems().at(-1);
209
toolbar2.context = last;
210
211
ctxResponse.set(isResponseVM(last));
212
ctxResponseVote.set(isResponseVM(last) ? last.vote === ChatAgentVoteDirection.Down ? 'down' : last.vote === ChatAgentVoteDirection.Up ? 'up' : '' : '');
213
ctxResponseError.set(isResponseVM(last) && last.errorDetails !== undefined);
214
ctxResponseErrorFiltered.set((!!(isResponseVM(last) && last.errorDetails?.responseIsFiltered)));
215
ctxResponseSupportIssues.set(isResponseVM(last) && (last.agent?.metadata.supportIssueReporting ?? false));
216
217
this._onDidChangeHeight.fire();
218
}));
219
this._onDidChangeHeight.fire();
220
}));
221
222
this._store.add(this.chatWidget.onDidChangeContentHeight(() => {
223
this._onDidChangeHeight.fire();
224
}));
225
226
// context keys
227
this._ctxResponseFocused = CTX_INLINE_CHAT_RESPONSE_FOCUSED.bindTo(this._contextKeyService);
228
const tracker = this._store.add(trackFocus(this.domNode));
229
this._store.add(tracker.onDidBlur(() => this._ctxResponseFocused.set(false)));
230
this._store.add(tracker.onDidFocus(() => this._ctxResponseFocused.set(true)));
231
232
this._ctxInputEditorFocused = CTX_INLINE_CHAT_FOCUSED.bindTo(_contextKeyService);
233
this._store.add(this._chatWidget.inputEditor.onDidFocusEditorWidget(() => this._ctxInputEditorFocused.set(true)));
234
this._store.add(this._chatWidget.inputEditor.onDidBlurEditorWidget(() => this._ctxInputEditorFocused.set(false)));
235
236
const statusMenuId = _options.statusMenuId instanceof MenuId ? _options.statusMenuId : _options.statusMenuId.menu;
237
238
// BUTTON bar
239
const statusMenuOptions = _options.statusMenuId instanceof MenuId ? undefined : _options.statusMenuId.options;
240
const statusButtonBar = scopedInstaService.createInstance(MenuWorkbenchButtonBar, this._elements.toolbar1, statusMenuId, {
241
toolbarOptions: { primaryGroup: '0_main' },
242
telemetrySource: _options.chatWidgetViewOptions?.menus?.telemetrySource,
243
menuOptions: { renderShortTitle: true },
244
...statusMenuOptions,
245
});
246
this._store.add(statusButtonBar.onDidChange(() => this._onDidChangeHeight.fire()));
247
this._store.add(statusButtonBar);
248
249
// secondary toolbar
250
const toolbar2 = scopedInstaService.createInstance(MenuWorkbenchToolBar, this._elements.toolbar2, _options.secondaryMenuId ?? MenuId.for(''), {
251
telemetrySource: _options.chatWidgetViewOptions?.menus?.telemetrySource,
252
menuOptions: { renderShortTitle: true, shouldForwardArgs: true },
253
actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => {
254
if (action instanceof MenuItemAction && action.item.id === MarkUnhelpfulActionId) {
255
return scopedInstaService.createInstance(ChatVoteDownButton, action, options as IMenuEntryActionViewItemOptions);
256
}
257
return createActionViewItem(scopedInstaService, action, options);
258
}
259
});
260
this._store.add(toolbar2.onDidChangeMenuItems(() => this._onDidChangeHeight.fire()));
261
this._store.add(toolbar2);
262
263
264
this._store.add(this._configurationService.onDidChangeConfiguration(e => {
265
if (e.affectsConfiguration(AccessibilityVerbositySettingId.InlineChat)) {
266
this._updateAriaLabel();
267
}
268
}));
269
270
this._elements.root.tabIndex = 0;
271
this._elements.statusLabel.tabIndex = 0;
272
this._updateAriaLabel();
273
this._setupDisclaimer();
274
275
this._store.add(this._hoverService.setupManagedHover(getDefaultHoverDelegate('element'), this._elements.statusLabel, () => {
276
return this._elements.statusLabel.dataset['title'];
277
}));
278
279
this._store.add(this._chatService.onDidPerformUserAction(e => {
280
if (isEqual(e.sessionResource, this._chatWidget.viewModel?.model.sessionResource) && e.action.kind === 'vote') {
281
this.updateStatus(localize('feedbackThanks', "Thank you for your feedback!"), { resetAfter: 1250 });
282
}
283
}));
284
}
285
286
private _updateAriaLabel(): void {
287
288
this._elements.root.ariaLabel = this._accessibleViewService.getOpenAriaHint(AccessibilityVerbositySettingId.InlineChat);
289
290
if (this._accessibilityService.isScreenReaderOptimized()) {
291
let label = defaultAriaLabel;
292
if (this._configurationService.getValue<boolean>(AccessibilityVerbositySettingId.InlineChat)) {
293
const kbLabel = this._keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp)?.getLabel();
294
label = kbLabel
295
? localize('inlineChat.accessibilityHelp', "Inline Chat Input, Use {0} for Inline Chat Accessibility Help.", kbLabel)
296
: localize('inlineChat.accessibilityHelpNoKb', "Inline Chat Input, Run the Inline Chat Accessibility Help command for more information.");
297
}
298
this._chatWidget.inputEditor.updateOptions({ ariaLabel: label });
299
}
300
}
301
302
private _setupDisclaimer(): void {
303
const disposables = this._store.add(new DisposableStore());
304
305
this._store.add(autorun(reader => {
306
disposables.clear();
307
reset(this._elements.disclaimerLabel);
308
309
const sentiment = this._chatEntitlementService.sentimentObs.read(reader);
310
const anonymous = this._chatEntitlementService.anonymousObs.read(reader);
311
const requestInProgress = this._chatService.requestInProgressObs.read(reader);
312
313
const showDisclaimer = !sentiment.installed && anonymous && !requestInProgress;
314
this._elements.disclaimerLabel.classList.toggle('hidden', !showDisclaimer);
315
316
if (showDisclaimer) {
317
const renderedMarkdown = disposables.add(this._markdownRendererService.render(new MarkdownString(localize({ key: 'termsDisclaimer', comment: ['{Locked="]({2})"}', '{Locked="]({3})"}'] }, "By continuing with {0} Copilot, you agree to {1}'s [Terms]({2}) and [Privacy Statement]({3})", product.defaultChatAgent?.provider?.default?.name ?? '', product.defaultChatAgent?.provider?.default?.name ?? '', product.defaultChatAgent?.termsStatementUrl ?? '', product.defaultChatAgent?.privacyStatementUrl ?? ''), { isTrusted: true })));
318
this._elements.disclaimerLabel.appendChild(renderedMarkdown.element);
319
}
320
321
this._onDidChangeHeight.fire();
322
}));
323
}
324
325
dispose(): void {
326
this._store.dispose();
327
}
328
329
get domNode(): HTMLElement {
330
return this._elements.root;
331
}
332
333
get chatWidget(): ChatWidget {
334
return this._chatWidget;
335
}
336
337
saveState() {
338
this._chatWidget.saveState();
339
}
340
341
layout(widgetDim: Dimension) {
342
const contentHeight = this.contentHeight;
343
this._isLayouting = true;
344
try {
345
this._doLayout(widgetDim);
346
} finally {
347
this._isLayouting = false;
348
349
if (this.contentHeight !== contentHeight) {
350
this._onDidChangeHeight.fire();
351
}
352
}
353
}
354
355
protected _doLayout(dimension: Dimension): void {
356
const extraHeight = this._getExtraHeight();
357
const statusHeight = getTotalHeight(this._elements.status);
358
359
// console.log('ZONE#Widget#layout', { height: dimension.height, extraHeight, progressHeight, followUpsHeight, statusHeight, LIST: dimension.height - progressHeight - followUpsHeight - statusHeight - extraHeight });
360
361
this._elements.root.style.height = `${dimension.height - extraHeight}px`;
362
this._elements.root.style.width = `${dimension.width}px`;
363
364
this._chatWidget.layout(
365
dimension.height - statusHeight - extraHeight,
366
dimension.width
367
);
368
}
369
370
/**
371
* The content height of this widget is the size that would require no scrolling
372
*/
373
get contentHeight(): number {
374
const data = {
375
chatWidgetContentHeight: this._chatWidget.contentHeight,
376
statusHeight: getTotalHeight(this._elements.status),
377
extraHeight: this._getExtraHeight()
378
};
379
const result = data.chatWidgetContentHeight + data.statusHeight + data.extraHeight;
380
return result;
381
}
382
383
get minHeight(): number {
384
// The chat widget is variable height and supports scrolling. It should be
385
// at least "maxWidgetHeight" high and at most the content height.
386
387
let maxWidgetOutputHeight = 100;
388
for (const item of this._chatWidget.viewModel?.getItems() ?? []) {
389
if (isResponseVM(item) && item.response.value.some(r => r.kind === 'textEditGroup' && !r.state?.applied)) {
390
maxWidgetOutputHeight = 270;
391
break;
392
}
393
}
394
395
let value = this.contentHeight;
396
value -= this._chatWidget.contentHeight;
397
value += Math.min(this._chatWidget.input.height.get() + maxWidgetOutputHeight, this._chatWidget.contentHeight);
398
return value;
399
}
400
401
protected _getExtraHeight(): number {
402
return this._options.inZoneWidget ? 1 : (2 /*border*/ + 4 /*shadow*/);
403
}
404
405
get value(): string {
406
return this._chatWidget.getInput();
407
}
408
409
set value(value: string) {
410
this._chatWidget.setInput(value);
411
}
412
413
selectAll() {
414
this._chatWidget.inputEditor.setSelection(new Selection(1, 1, Number.MAX_SAFE_INTEGER, 1));
415
}
416
417
set placeholder(value: string) {
418
this._chatWidget.setInputPlaceholder(value);
419
}
420
421
toggleStatus(show: boolean) {
422
this._elements.toolbar1.classList.toggle('hidden', !show);
423
this._elements.toolbar2.classList.toggle('hidden', !show);
424
this._elements.status.classList.toggle('hidden', !show);
425
this._elements.infoLabel.classList.toggle('hidden', !show);
426
this._onDidChangeHeight.fire();
427
}
428
429
updateToolbar(show: boolean) {
430
this._elements.root.classList.toggle('toolbar', show);
431
this._elements.toolbar1.classList.toggle('hidden', !show);
432
this._elements.toolbar2.classList.toggle('hidden', !show);
433
this._elements.status.classList.toggle('actions', show);
434
this._elements.infoLabel.classList.toggle('hidden', show);
435
this._onDidChangeHeight.fire();
436
}
437
438
async getCodeBlockInfo(codeBlockIndex: number): Promise<ITextModel | undefined> {
439
const { viewModel } = this._chatWidget;
440
if (!viewModel) {
441
return undefined;
442
}
443
const items = viewModel.getItems().filter(i => isResponseVM(i));
444
const item = items.at(-1);
445
if (!item) {
446
return;
447
}
448
449
// Try to get the existing code block from the collection
450
const existingEntry = viewModel.codeBlockModelCollection.get(viewModel.sessionResource, item, codeBlockIndex);
451
if (existingEntry) {
452
return existingEntry.model;
453
}
454
455
// If not found, the rendering may not have completed yet.
456
// Parse the markdown and create the code block model synchronously.
457
const markdown = item.response.getMarkdown();
458
let currentCodeBlockIndex = 0;
459
let foundCodeBlock: { text: string; lang: string } | undefined;
460
461
marked.walkTokens(marked.lexer(markdown), token => {
462
if (token.type === 'code') {
463
if (currentCodeBlockIndex === codeBlockIndex) {
464
foundCodeBlock = { text: token.text, lang: token.lang || '' };
465
}
466
currentCodeBlockIndex++;
467
}
468
});
469
470
if (!foundCodeBlock) {
471
return undefined;
472
}
473
474
// Create the code block model synchronously
475
const entry = viewModel.codeBlockModelCollection.updateSync(
476
viewModel.sessionResource,
477
item,
478
codeBlockIndex,
479
{ text: foundCodeBlock.text, languageId: foundCodeBlock.lang, isComplete: true }
480
);
481
482
return entry.model;
483
}
484
485
get responseContent(): string | undefined {
486
const requests = this._chatWidget.viewModel?.model.getRequests();
487
return requests?.at(-1)?.response?.response.toString();
488
}
489
490
491
getChatModel(): IChatModel | undefined {
492
return this._chatWidget.viewModel?.model;
493
}
494
495
setChatModel(chatModel: IChatModel) {
496
chatModel.inputModel.setState({ inputText: '', selections: [] });
497
this._chatWidget.setModel(chatModel);
498
}
499
500
updateInfo(message: string): void {
501
this._elements.infoLabel.classList.toggle('hidden', !message);
502
const renderedMessage = renderLabelWithIcons(message);
503
reset(this._elements.infoLabel, ...renderedMessage);
504
this._onDidChangeHeight.fire();
505
}
506
507
updateStatus(message: string, ops: { classes?: string[]; resetAfter?: number; keepMessage?: boolean; title?: string } = {}) {
508
const isTempMessage = typeof ops.resetAfter === 'number';
509
if (isTempMessage && !this._elements.statusLabel.dataset['state']) {
510
const statusLabel = this._elements.statusLabel.innerText;
511
const title = this._elements.statusLabel.dataset['title'];
512
const classes = Array.from(this._elements.statusLabel.classList.values());
513
setTimeout(() => {
514
this.updateStatus(statusLabel, { classes, keepMessage: true, title });
515
}, ops.resetAfter);
516
}
517
const renderedMessage = renderLabelWithIcons(message);
518
reset(this._elements.statusLabel, ...renderedMessage);
519
this._elements.statusLabel.className = `label status ${(ops.classes ?? []).join(' ')}`;
520
this._elements.statusLabel.classList.toggle('hidden', !message);
521
if (isTempMessage) {
522
this._elements.statusLabel.dataset['state'] = 'temp';
523
} else {
524
delete this._elements.statusLabel.dataset['state'];
525
}
526
527
if (ops.title) {
528
this._elements.statusLabel.dataset['title'] = ops.title;
529
} else {
530
delete this._elements.statusLabel.dataset['title'];
531
}
532
this._onDidChangeHeight.fire();
533
}
534
535
reset() {
536
this._chatWidget.attachmentModel.clear(true);
537
this._chatWidget.saveState();
538
539
reset(this._elements.statusLabel);
540
this._elements.statusLabel.classList.toggle('hidden', true);
541
this._elements.toolbar1.classList.add('hidden');
542
this._elements.toolbar2.classList.add('hidden');
543
this.updateInfo('');
544
545
this._elements.accessibleViewer.classList.toggle('hidden', true);
546
this._onDidChangeHeight.fire();
547
}
548
549
focus() {
550
this._chatWidget.focusInput();
551
}
552
553
hasFocus() {
554
return this.domNode.contains(getActiveElement());
555
}
556
557
}
558
559
const defaultAriaLabel = localize('aria-label', "Inline Chat Input");
560
561
export class EditorBasedInlineChatWidget extends InlineChatWidget {
562
563
constructor(
564
location: IChatWidgetLocationOptions,
565
parentEditor: ICodeEditor,
566
options: IInlineChatWidgetConstructionOptions,
567
@IContextKeyService contextKeyService: IContextKeyService,
568
@IKeybindingService keybindingService: IKeybindingService,
569
@IInstantiationService instantiationService: IInstantiationService,
570
@IAccessibilityService accessibilityService: IAccessibilityService,
571
@IConfigurationService configurationService: IConfigurationService,
572
@IAccessibleViewService accessibleViewService: IAccessibleViewService,
573
@ITextModelService textModelResolverService: ITextModelService,
574
@IChatService chatService: IChatService,
575
@IHoverService hoverService: IHoverService,
576
@ILayoutService layoutService: ILayoutService,
577
@IChatEntitlementService chatEntitlementService: IChatEntitlementService,
578
@IMarkdownRendererService markdownRendererService: IMarkdownRendererService,
579
) {
580
const overflowWidgetsNode = layoutService.getContainer(getWindow(parentEditor.getContainerDomNode())).appendChild($('.inline-chat-overflow.monaco-editor'));
581
super(location, {
582
...options,
583
chatWidgetViewOptions: {
584
...options.chatWidgetViewOptions,
585
editorOverflowWidgetsDomNode: overflowWidgetsNode
586
}
587
}, instantiationService, contextKeyService, keybindingService, accessibilityService, configurationService, accessibleViewService, textModelResolverService, chatService, hoverService, chatEntitlementService, markdownRendererService);
588
589
this._store.add(toDisposable(() => {
590
overflowWidgetsNode.remove();
591
}));
592
}
593
594
// --- layout
595
596
597
protected override _doLayout(dimension: Dimension): void {
598
599
const newHeight = dimension.height;
600
601
super._doLayout(dimension.with(undefined, newHeight));
602
603
// update/fix the height of the zone which was set to newHeight in super._doLayout
604
this._elements.root.style.height = `${dimension.height - this._getExtraHeight()}px`;
605
}
606
607
override reset() {
608
this.chatWidget.setInput();
609
super.reset();
610
}
611
612
}
613
614