Inline chat UI overhaul (#215927)

* don't dimiss inline chat content widget when already having typed something

https://github.com/microsoft/vscode-copilot/issues/6067

* extract `TextOnlyMenuEntryActionViewItem` for reuse

* remove unused variables

* * show chat input below request/response pairs
* setting for text-only buttons
* more dynamic buttons
* always show the first request, don't repopulate input with last message
* keep progress bar hidden, rely on "Generating..."
* no more special background color

* add `minimal` renderer style for chat renderings

* tweak font-size for details when render mode is minimal

* stable scroll position for inline chat, don't push down the lines chat is editing but push the inline chat upwards

* more buttons more compact, tweak labels

* * add missing service dependency
* repopulate input for some unit test

* allow output from `InteractiveChatController` suite
This commit is contained in:
Johannes Rieken 2024-06-17 11:26:21 +02:00 committed by GitHub
parent 0131b07802
commit 88d860624c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 553 additions and 647 deletions

View file

@ -818,8 +818,6 @@
"--vscode-hover-maxWidth",
"--vscode-hover-sourceWhiteSpace",
"--vscode-hover-whiteSpace",
"--vscode-inline-chat-quick-voice-height",
"--vscode-inline-chat-quick-voice-width",
"--vscode-editor-dictation-widget-height",
"--vscode-editor-dictation-widget-width",
"--vscode-interactive-session-foreground",
@ -855,4 +853,4 @@
"--zoom-factor",
"--test-bar-width"
]
}
}

View file

@ -38,6 +38,16 @@ export interface IToolBarOptions {
* If true, toggled primary items are highlighted with a background color.
*/
highlightToggledItems?: boolean;
/**
* Render action with icons (default: `true`)
*/
icon?: boolean;
/**
* Render action with label (default: `false`)
*/
label?: boolean;
}
/**
@ -50,7 +60,6 @@ export class ToolBar extends Disposable {
private toggleMenuActionViewItem: DropdownMenuActionViewItem | undefined;
private submenuActionViewItems: DropdownMenuActionViewItem[] = [];
private hasSecondaryActions: boolean = false;
private readonly lookupKeybindings: boolean;
private readonly element: HTMLElement;
private _onDidChangeDropdownVisibility = this._register(new EventMultiplexer<boolean>());
@ -62,7 +71,6 @@ export class ToolBar extends Disposable {
options.hoverDelegate = options.hoverDelegate ?? this._register(createInstantHoverDelegate());
this.options = options;
this.lookupKeybindings = typeof this.options.getKeyBinding === 'function';
this.toggleMenuAction = this._register(new ToggleMenuAction(() => this.toggleMenuActionViewItem?.show(), options.toggleMenuTitle));
@ -198,7 +206,7 @@ export class ToolBar extends Disposable {
}
primaryActionsToSet.forEach(action => {
this.actionBar.push(action, { icon: true, label: false, keybinding: this.getKeybindingLabel(action) });
this.actionBar.push(action, { icon: this.options.icon ?? true, label: this.options.label ?? false, keybinding: this.getKeybindingLabel(action) });
});
}
@ -207,7 +215,7 @@ export class ToolBar extends Disposable {
}
private getKeybindingLabel(action: IAction): string | undefined {
const key = this.lookupKeybindings ? this.options.getKeyBinding?.(action) : undefined;
const key = this.options.getKeyBinding?.(action);
return key?.getLabel() ?? undefined;
}

View file

@ -6,31 +6,12 @@
import * as dom from 'vs/base/browser/dom';
import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
import { IAction } from 'vs/base/common/actions';
import { ResolvedKeybinding } from 'vs/base/common/keybindings';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { TextOnlyMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
class StatusBarViewItem extends MenuEntryActionViewItem {
protected override updateLabel() {
const kb = this._keybindingService.lookupKeybinding(this._action.id, this._contextKeyService);
if (!kb) {
return super.updateLabel();
}
if (this.label) {
this.label.textContent = localize({ key: 'content', comment: ['A label', 'A keybinding'] }, '{0} ({1})', this._action.label, StatusBarViewItem.symbolPrintEnter(kb));
}
}
static symbolPrintEnter(kb: ResolvedKeybinding) {
return kb.getLabel()?.replace(/\benter\b/gi, '\u23CE');
}
}
export class SuggestWidgetStatus {
readonly element: HTMLElement;
@ -49,7 +30,7 @@ export class SuggestWidgetStatus {
this.element = dom.append(container, dom.$('.suggest-status-bar'));
const actionViewItemProvider = <IActionViewItemProvider>(action => {
return action instanceof MenuItemAction ? instantiationService.createInstance(StatusBarViewItem, action, undefined) : undefined;
return action instanceof MenuItemAction ? instantiationService.createInstance(TextOnlyMenuEntryActionViewItem, action, undefined) : undefined;
});
this._leftActions = new ActionBar(this.element, { actionViewItemProvider });
this._rightActions = new ActionBar(this.element, { actionViewItemProvider });

View file

@ -6,11 +6,14 @@
import { ButtonBar, IButton } from 'vs/base/browser/ui/button/button';
import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory';
import { ActionRunner, IAction, IActionRunner, SubmenuAction, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions';
import { Codicon } from 'vs/base/common/codicons';
import { Emitter, Event } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { ThemeIcon } from 'vs/base/common/themables';
import { localize } from 'vs/nls';
import { MenuId, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions';
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IToolBarRenderOptions } from 'vs/platform/actions/browser/toolbar';
import { MenuId, IMenuService, MenuItemAction, IMenuActionOptions } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IHoverService } from 'vs/platform/hover/browser/hover';
@ -66,7 +69,7 @@ export class WorkbenchButtonBar extends ButtonBar {
super.dispose();
}
update(actions: IAction[]): void {
update(actions: IAction[], secondary: IAction[]): void {
const conifgProvider: IButtonConfigProvider = this._options?.buttonConfigProvider ?? (() => ({ showLabel: true }));
@ -127,16 +130,46 @@ export class WorkbenchButtonBar extends ButtonBar {
this._actionRunner.run(action);
}));
}
if (secondary.length > 0) {
const btn = this.addButton({
secondary: true,
ariaLabel: localize('moreActions', "More Actions")
});
btn.icon = Codicon.dropDownButton;
btn.element.classList.add('default-colors', 'monaco-text-button');
btn.enabled = true;
this._updateStore.add(this._hoverService.setupManagedHover(hoverDelegate, btn.element, localize('moreActions', "More Actions")));
this._updateStore.add(btn.onDidClick(async () => {
this._contextMenuService.showContextMenu({
getAnchor: () => btn.element,
getActions: () => secondary,
actionRunner: this._actionRunner,
onHide: () => btn.element.setAttribute('aria-expanded', 'false')
});
btn.element.setAttribute('aria-expanded', 'true');
}));
}
this._onDidChange.fire(this);
}
}
export interface IMenuWorkbenchButtonBarOptions extends IWorkbenchButtonBarOptions {
menuOptions?: IMenuActionOptions;
toolbarOptions?: IToolBarRenderOptions;
}
export class MenuWorkbenchButtonBar extends WorkbenchButtonBar {
constructor(
container: HTMLElement,
menuId: MenuId,
options: IWorkbenchButtonBarOptions | undefined,
options: IMenuWorkbenchButtonBarOptions | undefined,
@IMenuService menuService: IMenuService,
@IContextKeyService contextKeyService: IContextKeyService,
@IContextMenuService contextMenuService: IContextMenuService,
@ -153,12 +186,16 @@ export class MenuWorkbenchButtonBar extends WorkbenchButtonBar {
this.clear();
const actions = menu
.getActions({ renderShortTitle: true })
.flatMap(entry => entry[1]);
super.update(actions);
const primary: IAction[] = [];
const secondary: IAction[] = [];
createAndFillInActionBarActions(
menu,
options?.menuOptions,
{ primary, secondary },
options?.toolbarOptions?.primaryGroup
);
super.update(primary, secondary);
};
this._store.add(menu.onDidChange(update));
update();

View file

@ -11,6 +11,20 @@
background-size: 16px;
}
.monaco-action-bar .action-item.menu-entry.text-only .action-label {
color: var(--vscode-descriptionForeground);
overflow: hidden;
border-radius: 2px;
}
.monaco-action-bar .action-item.menu-entry.text-only:not(:last-of-type) .action-label::after {
content: ', ';
}
.monaco-action-bar .action-item.menu-entry.text-only + .action-item:not(.text-only) > .monaco-dropdown .action-label {
color: var(--vscode-descriptionForeground);
}
.monaco-dropdown-with-default {
display: flex !important;
flex-direction: row;

View file

@ -31,6 +31,7 @@ import { assertType } from 'vs/base/common/types';
import { asCssVariable, selectBorder } from 'vs/platform/theme/common/colorRegistry';
import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { ResolvedKeybinding } from 'vs/base/common/keybindings';
export function createAndFillInContextMenuActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[] }, primaryGroup?: string): void {
const groups = menu.getActions(options);
@ -121,7 +122,7 @@ export interface IMenuEntryActionViewItemOptions {
hoverDelegate?: IHoverDelegate;
}
export class MenuEntryActionViewItem extends ActionViewItem {
export class MenuEntryActionViewItem<T extends IMenuEntryActionViewItemOptions = IMenuEntryActionViewItemOptions> extends ActionViewItem {
private _wantsAltCommand: boolean = false;
private readonly _itemClassDispose = this._register(new MutableDisposable());
@ -129,7 +130,7 @@ export class MenuEntryActionViewItem extends ActionViewItem {
constructor(
action: MenuItemAction,
options: IMenuEntryActionViewItemOptions | undefined,
protected _options: T | undefined,
@IKeybindingService protected readonly _keybindingService: IKeybindingService,
@INotificationService protected _notificationService: INotificationService,
@IContextKeyService protected _contextKeyService: IContextKeyService,
@ -137,7 +138,7 @@ export class MenuEntryActionViewItem extends ActionViewItem {
@IContextMenuService protected _contextMenuService: IContextMenuService,
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService
) {
super(undefined, action, { icon: !!(action.class || action.item.icon), label: !action.class && !action.item.icon, draggable: options?.draggable, keybinding: options?.keybinding, hoverDelegate: options?.hoverDelegate });
super(undefined, action, { icon: !!(action.class || action.item.icon), label: !action.class && !action.item.icon, draggable: _options?.draggable, keybinding: _options?.keybinding, hoverDelegate: _options?.hoverDelegate });
this._altKey = ModifierKeyEmitter.getInstance();
}
@ -285,6 +286,43 @@ export class MenuEntryActionViewItem extends ActionViewItem {
}
}
export interface ITextOnlyMenuEntryActionViewItemOptions extends IMenuEntryActionViewItemOptions {
conversational?: boolean;
}
export class TextOnlyMenuEntryActionViewItem extends MenuEntryActionViewItem<ITextOnlyMenuEntryActionViewItemOptions> {
override render(container: HTMLElement): void {
this.options.label = true;
this.options.icon = false;
super.render(container);
container.classList.add('text-only');
}
protected override updateLabel() {
const kb = this._keybindingService.lookupKeybinding(this._action.id, this._contextKeyService);
if (!kb) {
return super.updateLabel();
}
if (this.label) {
const kb2 = TextOnlyMenuEntryActionViewItem._symbolPrintEnter(kb);
if (this._options?.conversational) {
this.label.textContent = localize({ key: 'content2', comment: ['A label with keybindg like "ESC to dismiss"'] }, '{1} to {0}', this._action.label, kb2);
} else {
this.label.textContent = localize({ key: 'content', comment: ['A label', 'A keybinding'] }, '{0} ({1})', this._action.label, kb2);
}
}
}
private static _symbolPrintEnter(kb: ResolvedKeybinding) {
return kb.getLabel()
?.replace(/\benter\b/gi, '\u23CE')
.replace(/\bEscape\b/gi, 'Esc');
}
}
export class SubmenuEntryActionViewItem extends DropdownMenuActionViewItem {
constructor(

View file

@ -92,7 +92,7 @@ export interface IChatFileTreeInfo {
export type ChatTreeItem = IChatRequestViewModel | IChatResponseViewModel | IChatWelcomeMessageViewModel;
export interface IChatListItemRendererOptions {
readonly renderStyle?: 'default' | 'compact';
readonly renderStyle?: 'default' | 'compact' | 'minimal';
readonly noHeader?: boolean;
readonly noPadding?: boolean;
readonly editableCodeBlock?: boolean;
@ -102,7 +102,7 @@ export interface IChatListItemRendererOptions {
export interface IChatWidgetViewOptions {
renderInputOnTop?: boolean;
renderFollowups?: boolean;
renderStyle?: 'default' | 'compact';
renderStyle?: 'default' | 'compact' | 'minimal';
supportsFileReferences?: boolean;
filter?: (item: ChatTreeItem) => boolean;
rendererOptions?: IChatListItemRendererOptions;

View file

@ -271,17 +271,42 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
if (this.rendererOptions.noPadding) {
rowContainer.classList.add('no-padding');
}
const header = dom.append(rowContainer, $('.header'));
let headerParent = rowContainer;
let referencesListParent = rowContainer;
let valueParent = rowContainer;
let detailContainerParent: HTMLElement | undefined;
let toolbarParent: HTMLElement | undefined;
if (this.rendererOptions.renderStyle === 'minimal') {
rowContainer.classList.add('interactive-item-compact');
rowContainer.classList.add('minimal');
// -----------------------------------------------------
// icon | details
// | references
// | value
// -----------------------------------------------------
const lhsContainer = dom.append(rowContainer, $('.column.left'));
const rhsContainer = dom.append(rowContainer, $('.column'));
headerParent = lhsContainer;
detailContainerParent = rhsContainer;
referencesListParent = rhsContainer;
valueParent = rhsContainer;
toolbarParent = dom.append(rowContainer, $('.header'));
}
const header = dom.append(headerParent, $('.header'));
const user = dom.append(header, $('.user'));
user.tabIndex = 0;
user.role = 'toolbar';
const avatarContainer = dom.append(user, $('.avatar-container'));
const username = dom.append(user, $('h3.username'));
const detailContainer = dom.append(user, $('span.detail-container'));
const detailContainer = dom.append(detailContainerParent ?? user, $('span.detail-container'));
const detail = dom.append(detailContainer, $('span.detail'));
dom.append(detailContainer, $('span.chat-animated-ellipsis'));
const referencesListContainer = dom.append(rowContainer, $('.referencesListContainer'));
const value = dom.append(rowContainer, $('.value'));
const referencesListContainer = dom.append(referencesListParent, $('.referencesListContainer'));
const value = dom.append(valueParent, $('.value'));
const elementDisposables = new DisposableStore();
const contextKeyService = templateDisposables.add(this.contextKeyService.createScoped(rowContainer));
@ -290,7 +315,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
if (this.rendererOptions.noHeader) {
header.classList.add('hidden');
} else {
titleToolbar = templateDisposables.add(scopedInstantiationService.createInstance(MenuWorkbenchToolBar, header, MenuId.ChatMessageTitle, {
titleToolbar = templateDisposables.add(scopedInstantiationService.createInstance(MenuWorkbenchToolBar, toolbarParent ?? header, MenuId.ChatMessageTitle, {
menuOptions: {
shouldForwardArgs: true
},

View file

@ -107,6 +107,9 @@ export class ChatWidget extends Disposable implements IChatWidget {
private _onDidChangeParsedInput = this._register(new Emitter<void>());
readonly onDidChangeParsedInput = this._onDidChangeParsedInput.event;
private readonly _onWillMaybeChangeHeight = new Emitter<void>();
readonly onWillMaybeChangeHeight: Event<void> = this._onWillMaybeChangeHeight.event;
private _onDidChangeHeight = this._register(new Emitter<number>());
readonly onDidChangeHeight = this._onDidChangeHeight.event;
@ -374,6 +377,8 @@ export class ChatWidget extends Disposable implements IChatWidget {
};
});
this._onWillMaybeChangeHeight.fire();
this.tree.setChildren(null, treeItems, {
diffIdentityProvider: {
getId: (element) => {
@ -549,12 +554,12 @@ export class ChatWidget extends Disposable implements IChatWidget {
this._onDidChangeContentHeight.fire();
}
private createInput(container: HTMLElement, options?: { renderFollowups: boolean; renderStyle?: 'default' | 'compact' }): void {
private createInput(container: HTMLElement, options?: { renderFollowups: boolean; renderStyle?: 'default' | 'compact' | 'minimal' }): void {
this.inputPart = this._register(this.instantiationService.createInstance(ChatInputPart,
this.location,
{
renderFollowups: options?.renderFollowups ?? true,
renderStyle: options?.renderStyle,
renderStyle: options?.renderStyle === 'minimal' ? 'compact' : options?.renderStyle,
menus: { executeToolbar: MenuId.ChatExecute, ...this.viewOptions.menus },
editorOverflowWidgetsDomNode: this.viewOptions.editorOverflowWidgetsDomNode,
}

View file

@ -49,12 +49,12 @@
font-weight: 600;
}
.interactive-item-container .header .detail-container {
.interactive-item-container .detail-container {
font-size: 12px;
color: var(--vscode-descriptionForeground);
}
.interactive-item-container .header .detail-container .detail .agentOrSlashCommandDetected A {
.interactive-item-container .detail-container .detail .agentOrSlashCommandDetected A {
cursor: pointer;
color: var(--vscode-textLink-foreground);
}
@ -342,6 +342,28 @@
margin: 8px 0;
}
.interactive-item-container.minimal {
flex-direction: row;
}
.interactive-item-container.minimal .column.left {
width: 20px;
padding-top: 2px;
}
.interactive-item-container.minimal .user > .username {
display: none;
}
.interactive-item-container.minimal .detail-container {
font-size: unset;
}
.interactive-item-container.minimal > .header {
position: absolute;
right: 0;
}
.interactive-session .interactive-input-and-execute-toolbar {
display: flex;
box-sizing: border-box;

View file

@ -4,10 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { registerAction2 } from 'vs/platform/actions/common/actions';
import { IMenuItem, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions';
import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController';
import * as InlineChatActions from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions';
import { INLINE_CHAT_ID } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { CTX_INLINE_CHAT_CONFIG_TXT_BTNS, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, INLINE_CHAT_ID, MENU_INLINE_CHAT_CONTENT_STATUS, MENU_INLINE_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { Registry } from 'vs/platform/registry/common/platform';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
@ -19,6 +19,10 @@ import { IInlineChatSavingService } from 'vs/workbench/contrib/inlineChat/browse
import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionService';
import { InlineChatEnabler, InlineChatSessionServiceImpl } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl';
import { AccessibleViewRegistry } from 'vs/platform/accessibility/browser/accessibleViewRegistry';
import { CancelAction, SubmitAction } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions';
import { localize } from 'vs/nls';
import { CONTEXT_CHAT_INPUT_HAS_TEXT } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
// --- browser
@ -28,6 +32,41 @@ registerSingleton(IInlineChatSavingService, InlineChatSavingServiceImpl, Instant
registerEditorContribution(INLINE_CHAT_ID, InlineChatController, EditorContributionInstantiation.Eager); // EAGER because of notebook dispose/create of editors
// --- MENU special ---
const sendActionMenuItem: IMenuItem = {
group: '0_main',
order: 0,
command: {
id: SubmitAction.ID,
title: localize('edit', "Send"),
},
when: ContextKeyExpr.and(
CONTEXT_CHAT_INPUT_HAS_TEXT,
CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.toNegated(),
CTX_INLINE_CHAT_CONFIG_TXT_BTNS
),
};
MenuRegistry.appendMenuItem(MENU_INLINE_CHAT_CONTENT_STATUS, sendActionMenuItem);
MenuRegistry.appendMenuItem(MENU_INLINE_CHAT_WIDGET_STATUS, sendActionMenuItem);
const cancelActionMenuItem: IMenuItem = {
group: '0_main',
order: 0,
command: {
id: CancelAction.ID,
title: localize('cancel', "Cancel Request"),
shortTitle: localize('cancelShort', "Cancel"),
},
when: ContextKeyExpr.and(
CTX_INLINE_CHAT_REQUEST_IN_PROGRESS,
),
};
MenuRegistry.appendMenuItem(MENU_INLINE_CHAT_WIDGET_STATUS, cancelActionMenuItem);
// --- actions ---
registerAction2(InlineChatActions.StartSessionAction);
registerAction2(InlineChatActions.CloseAction);
@ -36,7 +75,6 @@ registerAction2(InlineChatActions.UnstashSessionAction);
registerAction2(InlineChatActions.DiscardHunkAction);
registerAction2(InlineChatActions.DiscardAction);
registerAction2(InlineChatActions.RerunAction);
registerAction2(InlineChatActions.CancelSessionAction);
registerAction2(InlineChatActions.MoveToNextHunk);
registerAction2(InlineChatActions.MoveToPreviousHunk);

View file

@ -11,7 +11,7 @@ import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/em
import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { InlineChatController, InlineChatRunOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController';
import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_EDIT_MODE, EditMode, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_HAS_STASHED_SESSION, ACTION_ACCEPT_CHANGES, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, MENU_INLINE_CHAT_WIDGET, ACTION_TOGGLE_DIFF, ACTION_REGENERATE_RESPONSE } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { ACTION_ACCEPT_CHANGES, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_EDIT_MODE, EditMode, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatResponseType, ACTION_REGENERATE_RESPONSE, MENU_INLINE_CHAT_CONTENT_STATUS, ACTION_VIEW_IN_CHAT, ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_CONFIG_TXT_BTNS } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { localize, localize2 } from 'vs/nls';
import { Action2, IAction2Options } from 'vs/platform/actions/common/actions';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
@ -29,7 +29,6 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { ILogService } from 'vs/platform/log/common/log';
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { CONTEXT_CHAT_REQUEST_IN_PROGRESS } from 'vs/workbench/contrib/chat/common/chatContextKeys';
CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start');
CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES);
@ -228,27 +227,6 @@ export class FocusInlineChat extends EditorAction2 {
}
}
export class DiscardHunkAction extends AbstractInlineChatAction {
constructor() {
super({
id: 'inlineChat.discardHunkChange',
title: localize('discard', 'Discard'),
icon: Codicon.clearAll,
precondition: CTX_INLINE_CHAT_VISIBLE,
menu: {
id: MENU_INLINE_CHAT_WIDGET_STATUS,
when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.OnlyMessages), CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty), CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live)),
group: '0_main',
order: 3
}
});
}
async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): Promise<void> {
return ctrl.discardHunk();
}
}
export class DiscardAction extends AbstractInlineChatAction {
@ -271,33 +249,6 @@ export class DiscardAction extends AbstractInlineChatAction {
}
}
export class ToggleDiffForChange extends AbstractInlineChatAction {
constructor() {
super({
id: ACTION_TOGGLE_DIFF,
precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live), CTX_INLINE_CHAT_CHANGE_HAS_DIFF),
title: localize2('showChanges', 'Toggle Changes'),
icon: Codicon.diffSingle,
toggled: {
condition: CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF,
},
menu: [
{
id: MENU_INLINE_CHAT_WIDGET_STATUS,
group: '1_main',
when: ContextKeyExpr.and(CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live), CTX_INLINE_CHAT_CHANGE_HAS_DIFF),
order: 10,
}
]
});
}
override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController): void {
ctrl.toggleDiff();
}
}
export class AcceptChanges extends AbstractInlineChatAction {
constructor() {
@ -313,10 +264,13 @@ export class AcceptChanges extends AbstractInlineChatAction {
primary: KeyMod.CtrlCmd | KeyCode.Enter,
}],
menu: {
when: ContextKeyExpr.and(CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.OnlyMessages), CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty)),
id: MENU_INLINE_CHAT_WIDGET_STATUS,
group: '0_main',
order: 0
order: 1,
when: ContextKeyExpr.and(
CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.toNegated(),
CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.MessagesAndEdits)
),
}
});
}
@ -326,32 +280,72 @@ export class AcceptChanges extends AbstractInlineChatAction {
}
}
export class CancelSessionAction extends AbstractInlineChatAction {
export class DiscardHunkAction extends AbstractInlineChatAction {
constructor() {
super({
id: 'inlineChat.cancel',
title: localize('cancel', 'Cancel'),
id: 'inlineChat.discardHunkChange',
title: localize('discard', 'Discard'),
icon: Codicon.clearAll,
precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Preview)),
keybinding: {
weight: KeybindingWeight.EditorContrib - 1,
primary: KeyCode.Escape
},
precondition: CTX_INLINE_CHAT_VISIBLE,
menu: {
id: MENU_INLINE_CHAT_WIDGET_STATUS,
when: ContextKeyExpr.and(CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Preview), CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.Empty)),
group: '0_main',
order: 3
order: 2,
when: ContextKeyExpr.and(
CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate(),
CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.MessagesAndEdits),
CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live)
),
},
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyCode.Escape,
when: CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.MessagesAndEdits)
}
});
}
async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): Promise<void> {
ctrl.cancelSession();
return ctrl.discardHunk();
}
}
export class RerunAction extends AbstractInlineChatAction {
constructor() {
super({
id: ACTION_REGENERATE_RESPONSE,
title: localize2('chat.rerun.label', "Rerun Request"),
shortTitle: localize('rerun', 'Rerun'),
f1: false,
icon: Codicon.refresh,
precondition: CTX_INLINE_CHAT_VISIBLE,
menu: {
id: MENU_INLINE_CHAT_WIDGET_STATUS,
group: '0_main',
order: 5,
when: ContextKeyExpr.and(
CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate(),
CTX_INLINE_CHAT_RESPONSE_TYPE.notEqualsTo(InlineChatResponseType.None)
)
},
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyCode.KeyR
}
});
}
override async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): Promise<void> {
const chatService = accessor.get(IChatService);
const model = ctrl.chatWidget.viewModel?.model;
const lastRequest = model?.getRequests().at(-1);
if (lastRequest) {
await chatService.resendRequest(lastRequest, { noCommandDetection: false, attempt: lastRequest.attempt + 1, location: ctrl.chatWidget.location });
}
}
}
export class CloseAction extends AbstractInlineChatAction {
@ -362,15 +356,25 @@ export class CloseAction extends AbstractInlineChatAction {
icon: Codicon.close,
precondition: CTX_INLINE_CHAT_VISIBLE,
keybinding: {
weight: KeybindingWeight.EditorContrib - 1,
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyCode.Escape,
when: CTX_INLINE_CHAT_USER_DID_EDIT.negate()
},
menu: {
id: MENU_INLINE_CHAT_WIDGET,
group: 'navigation',
menu: [{
id: MENU_INLINE_CHAT_CONTENT_STATUS,
group: '0_main',
order: 10,
}
}, {
id: MENU_INLINE_CHAT_WIDGET_STATUS,
group: '0_main',
order: 1,
when: ContextKeyExpr.and(
CTX_INLINE_CHAT_CONFIG_TXT_BTNS,
ContextKeyExpr.or(
CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.Messages),
CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Preview)
)
),
}]
});
}
@ -387,6 +391,11 @@ export class ConfigureInlineChatAction extends AbstractInlineChatAction {
icon: Codicon.settingsGear,
precondition: CTX_INLINE_CHAT_VISIBLE,
f1: true,
menu: {
id: MENU_INLINE_CHAT_WIDGET_STATUS,
group: 'zzz',
order: 5
}
});
}
@ -478,10 +487,23 @@ export class ViewInChatAction extends AbstractInlineChatAction {
title: localize('viewInChat', 'View in Chat'),
icon: Codicon.commentDiscussion,
precondition: CTX_INLINE_CHAT_VISIBLE,
menu: {
id: MENU_INLINE_CHAT_WIDGET,
group: 'navigation',
order: 5
menu: [{
id: MENU_INLINE_CHAT_WIDGET_STATUS,
group: 'more',
order: 1,
when: CTX_INLINE_CHAT_RESPONSE_TYPE.notEqualsTo(InlineChatResponseType.Messages)
}, {
id: MENU_INLINE_CHAT_WIDGET_STATUS,
group: '0_main',
order: 1,
when: ContextKeyExpr.and(
CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.Messages),
CTX_INLINE_CHAT_CONFIG_TXT_BTNS
)
}],
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyCode.DownArrow,
}
});
}
@ -490,29 +512,27 @@ export class ViewInChatAction extends AbstractInlineChatAction {
}
}
export class RerunAction extends AbstractInlineChatAction {
export class ToggleDiffForChange extends AbstractInlineChatAction {
constructor() {
super({
id: ACTION_REGENERATE_RESPONSE,
title: localize2('chat.rerun.label', "Rerun Request"),
f1: false,
icon: Codicon.refresh,
precondition: CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(),
id: ACTION_TOGGLE_DIFF,
precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live), CTX_INLINE_CHAT_CHANGE_HAS_DIFF),
title: localize2('showChanges', 'Toggle Changes'),
icon: Codicon.diffSingle,
toggled: {
condition: CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF,
},
menu: {
id: MENU_INLINE_CHAT_WIDGET_STATUS,
group: '0_main',
order: 5,
group: 'more',
when: ContextKeyExpr.and(CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live), CTX_INLINE_CHAT_CHANGE_HAS_DIFF),
order: 10,
}
});
}
override async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): Promise<void> {
const chatService = accessor.get(IChatService);
const model = ctrl.chatWidget.viewModel?.model;
const lastRequest = model?.getRequests().at(-1);
if (lastRequest) {
await chatService.resendRequest(lastRequest, { noCommandDetection: false, attempt: lastRequest.attempt + 1, location: ctrl.chatWidget.location });
}
override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController): void {
ctrl.toggleDiff();
}
}

View file

@ -11,7 +11,7 @@ import { Emitter, Event } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IPosition, Position } from 'vs/editor/common/core/position';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { inlineChatBackground } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { inlineChatBackground, MENU_INLINE_CHAT_CONTENT_STATUS } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget';
import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents';
@ -22,6 +22,9 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { ScrollType } from 'vs/editor/common/editorCommon';
import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar';
import { MenuItemAction } from 'vs/platform/actions/common/actions';
import { TextOnlyMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
export class InlineChatContentWidget implements IContentWidget {
@ -31,7 +34,7 @@ export class InlineChatContentWidget implements IContentWidget {
private readonly _store = new DisposableStore();
private readonly _domNode = document.createElement('div');
private readonly _inputContainer = document.createElement('div');
private readonly _messageContainer = document.createElement('div');
private readonly _toolbarContainer = document.createElement('div');
private _position?: IPosition;
@ -68,7 +71,7 @@ export class InlineChatContentWidget implements IContentWidget {
{
defaultElementHeight: 32,
editorOverflowWidgetsDomNode: _editor.getOverflowWidgetsDomNode(),
renderStyle: 'compact',
renderStyle: 'minimal',
renderInputOnTop: true,
renderFollowups: true,
supportsFileReferences: false,
@ -94,13 +97,19 @@ export class InlineChatContentWidget implements IContentWidget {
this._domNode.appendChild(this._inputContainer);
this._messageContainer.classList.add('hidden', 'message');
this._domNode.appendChild(this._messageContainer);
this._toolbarContainer.classList.add('toolbar');
this._domNode.appendChild(this._toolbarContainer);
this._store.add(scopedInstaService.createInstance(MenuWorkbenchToolBar, this._toolbarContainer, MENU_INLINE_CHAT_CONTENT_STATUS, {
actionViewItemProvider: action => action instanceof MenuItemAction ? instaService.createInstance(TextOnlyMenuEntryActionViewItem, action, { conversational: true }) : undefined,
toolbarOptions: { primaryGroup: '0_main' },
icon: false,
label: true,
}));
const tracker = dom.trackFocus(this._domNode);
this._store.add(tracker.onDidBlur(() => {
if (this._visible
if (this._visible && this._widget.inputEditor.getModel()?.getValueLength() === 0
// && !"ON"
) {
this._onDidBlur.fire();

View file

@ -35,7 +35,7 @@ import { EmptyResponse, ErrorResponse, ReplyResponse, Session, SessionPrompt } f
import { IInlineChatSessionService } from './inlineChatSessionService';
import { EditModeStrategy, IEditObserver, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies';
import { InlineChatZoneWidget } from './inlineChatZoneWidget';
import { CTX_INLINE_CHAT_RESPONSE_TYPES, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_VISIBLE, EditMode, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_VISIBLE, EditMode, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { StashedSession } from './inlineChatSession';
import { IModelDeltaDecoration, ITextModel, IValidEditOperation } from 'vs/editor/common/model';
import { InlineChatContentWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget';
@ -110,8 +110,9 @@ export class InlineChatController implements IEditorContribution {
private readonly _ui: Lazy<{ content: InlineChatContentWidget; zone: InlineChatZoneWidget }>;
private readonly _ctxVisible: IContextKey<boolean>;
private readonly _ctxResponseTypes: IContextKey<undefined | InlineChatResponseTypes>;
private readonly _ctxResponseType: IContextKey<undefined | InlineChatResponseType>;
private readonly _ctxUserDidEdit: IContextKey<boolean>;
private readonly _ctxRequestInProgress: IContextKey<boolean>;
private _messages = this._store.add(new Emitter<Message>());
@ -148,7 +149,8 @@ export class InlineChatController implements IEditorContribution {
) {
this._ctxVisible = CTX_INLINE_CHAT_VISIBLE.bindTo(contextKeyService);
this._ctxUserDidEdit = CTX_INLINE_CHAT_USER_DID_EDIT.bindTo(contextKeyService);
this._ctxResponseTypes = CTX_INLINE_CHAT_RESPONSE_TYPES.bindTo(contextKeyService);
this._ctxResponseType = CTX_INLINE_CHAT_RESPONSE_TYPE.bindTo(contextKeyService);
this._ctxRequestInProgress = CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.bindTo(contextKeyService);
this._ui = new Lazy(() => {
let location = ChatAgentLocation.Editor;
@ -302,7 +304,6 @@ export class InlineChatController implements IEditorContribution {
if (m === Message.ACCEPT_INPUT) {
// user accepted the input before having a session
options.autoSend = true;
this._ui.value.zone.widget.updateProgress(true);
this._ui.value.zone.widget.updateInfo(localize('welcome.2', "Getting ready..."));
} else {
createSessionCts.cancel();
@ -384,6 +385,7 @@ export class InlineChatController implements IEditorContribution {
this._updatePlaceholder();
this._showWidget(!this._session.chatModel.hasRequests);
this._ui.value.zone.widget.updateToolbar(true);
this._sessionStore.add(this._editor.onDidChangeModel((e) => {
const msg = this._session?.chatModel.hasRequests
@ -422,17 +424,7 @@ export class InlineChatController implements IEditorContribution {
}));
this._sessionStore.add(this._session.chatModel.onDidChange(async e => {
if (e.kind === 'addRequest' && e.request.response) {
this._ui.value.zone.widget.updateProgress(true);
const listener = e.request.response.onDidChange(() => {
if (e.request.response?.isCanceled || e.request.response?.isComplete) {
this._ui.value.zone.widget.updateProgress(false);
listener.dispose();
}
});
} else if (e.kind === 'removeRequest') {
if (e.kind === 'removeRequest') {
// TODO@jrieken there is still some work left for when a request "in the middle"
// is removed. We will undo all changes till that point but not remove those
// later request
@ -607,9 +599,6 @@ export class InlineChatController implements IEditorContribution {
return State.WAIT_FOR_INPUT;
}
const input = request.message.text;
this._ui.value.zone.widget.value = input;
this._session.addInput(new SessionPrompt(request, this._editor.getModel()!.getAlternativeVersionId()));
return State.SHOW_REQUEST;
@ -620,6 +609,8 @@ export class InlineChatController implements IEditorContribution {
assertType(this._session);
assertType(this._session.chatModel.requestInProgress);
this._ctxRequestInProgress.set(true);
const { chatModel } = this._session;
const request: IChatRequestModel | undefined = chatModel.getRequests().at(-1);
@ -627,7 +618,7 @@ export class InlineChatController implements IEditorContribution {
assertType(request.response);
this._showWidget(false);
this._ui.value.zone.widget.value = request.message.text;
// this._ui.value.zone.widget.value = request.message.text;
this._ui.value.zone.widget.selectAll(false);
this._ui.value.zone.widget.updateInfo('');
@ -684,6 +675,8 @@ export class InlineChatController implements IEditorContribution {
// apply edits
const handleResponse = () => {
this._updateCtxResponseType();
if (!localEditGroup) {
localEditGroup = <IChatTextEditGroup | undefined>response.response.value.find(part => part.kind === 'textEditGroup' && isEqual(part.uri, this._session?.textModelN.uri));
}
@ -747,8 +740,7 @@ export class InlineChatController implements IEditorContribution {
this._session.wholeRange.fixup(diff?.changes ?? []);
await this._session.hunkData.recompute(editState, diff);
this._ui.value.zone.widget.updateToolbar(true);
this._ui.value.zone.widget.updateProgress(false);
this._ctxRequestInProgress.set(false);
return next;
}
@ -759,20 +751,6 @@ export class InlineChatController implements IEditorContribution {
const { response } = this._session.lastExchange!;
let responseTypes: InlineChatResponseTypes | undefined;
for (const request of this._session.chatModel.getRequests()) {
if (!request.response) {
continue;
}
const thisType = asInlineChatResponseType(request.response.response);
if (responseTypes === undefined) {
responseTypes = thisType;
} else if (responseTypes !== thisType) {
responseTypes = InlineChatResponseTypes.Mixed;
break;
}
}
this._ctxResponseTypes.set(responseTypes);
let newPosition: Position | undefined;
@ -792,7 +770,6 @@ export class InlineChatController implements IEditorContribution {
} else if (response instanceof ReplyResponse) {
// real response -> complex...
this._ui.value.zone.widget.updateStatus('');
this._ui.value.zone.widget.updateToolbar(true);
newPosition = await this._strategy.renderChanges(response);
}
@ -867,6 +844,7 @@ export class InlineChatController implements IEditorContribution {
private _showWidget(initialRender: boolean = false, position?: Position) {
assertType(this._editor.hasModel());
this._ctxVisible.set(true);
let widgetPosition: Position;
if (position) {
@ -904,11 +882,6 @@ export class InlineChatController implements IEditorContribution {
}
}
if (this._session && this._ui.rawValue?.zone) {
this._ui.rawValue?.zone.updateBackgroundColor(widgetPosition, this._session.wholeRange.value);
}
this._ctxVisible.set(true);
return widgetPosition;
}
@ -926,6 +899,31 @@ export class InlineChatController implements IEditorContribution {
}
}
private _updateCtxResponseType(): void {
if (!this._session) {
this._ctxResponseType.set(InlineChatResponseType.None);
return;
}
const hasLocalEdit = (response: IResponse): boolean => {
return response.value.some(part => part.kind === 'textEditGroup' && isEqual(part.uri, this._session?.textModelN.uri));
};
let responseType = InlineChatResponseType.None;
for (const request of this._session.chatModel.getRequests()) {
if (!request.response) {
continue;
}
responseType = InlineChatResponseType.Messages;
if (hasLocalEdit(request.response.response)) {
responseType = InlineChatResponseType.MessagesAndEdits;
break; // no need to check further
}
}
this._ctxResponseType.set(responseType);
}
private async _makeChanges(edits: TextEdit[], opts: ProgressingEditsOptions | undefined, undoStopBefore: boolean) {
assertType(this._session);
assertType(this._strategy);
@ -1135,25 +1133,3 @@ async function moveToPanelChat(accessor: ServicesAccessor, model: ChatModel | un
widget.focusLastMessage();
}
}
function asInlineChatResponseType(response: IResponse): InlineChatResponseTypes {
let result: InlineChatResponseTypes | undefined;
for (const item of response.value) {
let thisType: InlineChatResponseTypes;
switch (item.kind) {
case 'textEditGroup':
thisType = InlineChatResponseTypes.OnlyEdits;
break;
case 'markdownContent':
default:
thisType = InlineChatResponseTypes.OnlyMessages;
break;
}
if (result === undefined) {
result = thisType;
} else if (result !== thisType) {
return InlineChatResponseTypes.Mixed;
}
}
return result ?? InlineChatResponseTypes.Empty;
}

View file

@ -1,256 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Dimension, h } from 'vs/base/browser/dom';
import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { Range } from 'vs/editor/common/core/range';
import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import * as colorRegistry from 'vs/platform/theme/common/colorRegistry';
import * as editorColorRegistry from 'vs/editor/common/core/editorColorRegistry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { INLINE_CHAT_ID, inlineChatRegionHighlight } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { Position } from 'vs/editor/common/core/position';
import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
import { ResourceLabel } from 'vs/workbench/browser/labels';
import { FileKind } from 'vs/platform/files/common/files';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { ButtonBar, IButton } from 'vs/base/browser/ui/button/button';
import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles';
import { SaveReason, SideBySideEditor } from 'vs/workbench/common/editor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IAction, toAction } from 'vs/base/common/actions';
import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel';
import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { Codicon } from 'vs/base/common/codicons';
import { TAB_ACTIVE_MODIFIED_BORDER } from 'vs/workbench/common/theme';
import { localize } from 'vs/nls';
import { Event } from 'vs/base/common/event';
export class InlineChatFileCreatePreviewWidget extends ZoneWidget {
private static TitleHeight = 35;
private readonly _elements = h('div.inline-chat-newfile-widget@domNode', [
h('div.title@title', [
h('span.name.show-file-icons@name'),
h('span.detail@detail'),
]),
h('div.editor@editor'),
]);
private readonly _name: ResourceLabel;
private readonly _previewEditor: ICodeEditor;
private readonly _previewStore = new MutableDisposable();
private readonly _buttonBar: ButtonBarWidget;
private _dim: Dimension | undefined;
constructor(
parentEditor: ICodeEditor,
@IInstantiationService instaService: IInstantiationService,
@IThemeService themeService: IThemeService,
@ITextModelService private readonly _textModelResolverService: ITextModelService,
@IEditorService private readonly _editorService: IEditorService,
) {
super(parentEditor, {
showArrow: false,
showFrame: true,
frameColor: colorRegistry.asCssVariable(TAB_ACTIVE_MODIFIED_BORDER),
frameWidth: 1,
isResizeable: true,
isAccessible: true,
showInHiddenAreas: true,
ordinal: 10000 + 2
});
super.create();
this._name = instaService.createInstance(ResourceLabel, this._elements.name, { supportIcons: true });
this._elements.detail.appendChild(renderIcon(Codicon.circleFilled));
const contributions = EditorExtensionsRegistry
.getEditorContributions()
.filter(c => c.id !== INLINE_CHAT_ID);
this._previewEditor = instaService.createInstance(EmbeddedCodeEditorWidget, this._elements.editor, {
scrollBeyondLastLine: false,
stickyScroll: { enabled: false },
minimap: { enabled: false },
scrollbar: { alwaysConsumeMouseWheel: false, useShadows: true, ignoreHorizontalScrollbarInContentHeight: true, },
}, { isSimpleWidget: true, contributions }, parentEditor);
const doStyle = () => {
const theme = themeService.getColorTheme();
const overrides: [target: string, source: string][] = [
[colorRegistry.editorBackground, inlineChatRegionHighlight],
[editorColorRegistry.editorGutter, inlineChatRegionHighlight],
];
for (const [target, source] of overrides) {
const value = theme.getColor(source);
if (value) {
this._elements.domNode.style.setProperty(colorRegistry.asCssVariableName(target), String(value));
}
}
};
doStyle();
this._disposables.add(themeService.onDidColorThemeChange(doStyle));
this._buttonBar = instaService.createInstance(ButtonBarWidget);
this._elements.title.appendChild(this._buttonBar.domNode);
}
override dispose(): void {
this._name.dispose();
this._buttonBar.dispose();
this._previewEditor.dispose();
this._previewStore.dispose();
super.dispose();
}
protected override _fillContainer(container: HTMLElement): void {
container.appendChild(this._elements.domNode);
}
override show(): void {
throw new Error('Use showFileCreation');
}
async showCreation(where: Position, untitledTextModel: IUntitledTextEditorModel): Promise<void> {
const store = new DisposableStore();
this._previewStore.value = store;
this._name.element.setFile(untitledTextModel.resource, {
fileKind: FileKind.FILE,
fileDecorations: { badges: true, colors: true }
});
const actionSave = toAction({
id: '1',
label: localize('save', "Create"),
run: () => untitledTextModel.save({ reason: SaveReason.EXPLICIT })
});
const actionSaveAs = toAction({
id: '2',
label: localize('saveAs', "Create As"),
run: async () => {
const ids = this._editorService.findEditors(untitledTextModel.resource, { supportSideBySide: SideBySideEditor.ANY });
await this._editorService.save(ids.slice(), { saveAs: true, reason: SaveReason.EXPLICIT });
}
});
this._buttonBar.update([
[actionSave, actionSaveAs],
[(toAction({ id: '3', label: localize('discard', "Discard"), run: () => untitledTextModel.revert() }))]
]);
store.add(Event.any(
untitledTextModel.onDidRevert,
untitledTextModel.onDidSave,
untitledTextModel.onDidChangeDirty,
untitledTextModel.onWillDispose
)(() => this.hide()));
await untitledTextModel.resolve();
const ref = await this._textModelResolverService.createModelReference(untitledTextModel.resource);
store.add(ref);
const model = ref.object.textEditorModel;
this._previewEditor.setModel(model);
const lineHeight = this.editor.getOption(EditorOption.lineHeight);
this._elements.title.style.height = `${InlineChatFileCreatePreviewWidget.TitleHeight}px`;
const titleHightInLines = InlineChatFileCreatePreviewWidget.TitleHeight / lineHeight;
const maxLines = Math.max(4, Math.floor((this.editor.getLayoutInfo().height / lineHeight) * .33));
const lines = Math.min(maxLines, model.getLineCount());
super.show(where, titleHightInLines + lines);
}
override hide(): void {
this._previewStore.clear();
super.hide();
}
// --- layout
protected override revealRange(range: Range, isLastLine: boolean): void {
// ignore
}
protected override _onWidth(widthInPixel: number): void {
if (this._dim) {
this._doLayout(this._dim.height, widthInPixel);
}
}
protected override _doLayout(heightInPixel: number, widthInPixel: number): void {
const { lineNumbersLeft } = this.editor.getLayoutInfo();
this._elements.title.style.marginLeft = `${lineNumbersLeft}px`;
const newDim = new Dimension(widthInPixel, heightInPixel);
if (!Dimension.equals(this._dim, newDim)) {
this._dim = newDim;
this._previewEditor.layout(this._dim.with(undefined, this._dim.height - InlineChatFileCreatePreviewWidget.TitleHeight));
}
}
}
class ButtonBarWidget {
private readonly _domNode = h('div.buttonbar-widget');
private readonly _buttonBar: ButtonBar;
private readonly _store = new DisposableStore();
constructor(
@IContextMenuService private _contextMenuService: IContextMenuService,
) {
this._buttonBar = new ButtonBar(this.domNode);
}
update(allActions: IAction[][]): void {
this._buttonBar.clear();
let secondary = false;
for (const actions of allActions) {
let btn: IButton;
const [first, ...rest] = actions;
if (!first) {
continue;
} else if (rest.length === 0) {
// single action
btn = this._buttonBar.addButton({ ...defaultButtonStyles, secondary });
} else {
btn = this._buttonBar.addButtonWithDropdown({
...defaultButtonStyles,
addPrimaryActionToDropdown: false,
actions: rest,
contextMenuProvider: this._contextMenuService
});
}
btn.label = first.label;
this._store.add(btn.onDidClick(() => first.run()));
secondary = true;
}
}
dispose(): void {
this._buttonBar.dispose();
this._store.dispose();
}
get domNode() {
return this._domNode.root;
}
}

View file

@ -25,7 +25,7 @@ import { localize } from 'vs/nls';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar';
import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar';
import { MenuId } from 'vs/platform/actions/common/actions';
import { MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@ -35,9 +35,9 @@ import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibil
import { IAccessibleViewService } from 'vs/platform/accessibility/browser/accessibleView';
import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands';
import { ChatModel, IChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { HunkInformation, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_RESPONSE_FOCUSED, inlineChatBackground } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_RESPONSE_FOCUSED, inlineChatBackground, InlineChatConfigKeys } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget';
import { chatRequestBackground } from 'vs/workbench/contrib/chat/common/chatColors';
import { Selection } from 'vs/editor/common/core/selection';
@ -48,6 +48,7 @@ import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateF
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IHoverService } from 'vs/platform/hover/browser/hover';
import { IChatWidgetViewOptions } from 'vs/workbench/contrib/chat/browser/chat';
import { TextOnlyMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
export interface InlineChatWidgetViewState {
@ -62,6 +63,7 @@ export interface IInlineChatWidgetConstructionOptions {
* The menu that rendered as button bar, use for accept, discard etc
*/
statusMenuId: MenuId | { menu: MenuId; options: IWorkbenchButtonBarOptions };
/**
* The men that rendered in the lower right corner, use for feedback
*/
@ -91,7 +93,6 @@ export class InlineChatWidget {
[
h('div.chat-widget@chatWidget'),
h('div.progress@progress'),
h('div.followUps.hidden@followUps'),
h('div.previewDiff.hidden@previewDiff'),
h('div.accessibleViewer@accessibleViewer'),
h('div.status@status', [
@ -139,8 +140,6 @@ export class InlineChatWidget {
this._progressBar = new ProgressBar(this._elements.progress);
this._store.add(this._progressBar);
let allowRequests = false;
this.scopedContextKeyService = this._store.add(_contextKeyService.createScoped(this._elements.chatWidget));
const scopedInstaService = _instantiationService.createChild(
new ServiceCollection([
@ -156,20 +155,11 @@ export class InlineChatWidget {
{ resource: true },
{
defaultElementHeight: 32,
renderStyle: 'compact',
renderInputOnTop: true,
renderStyle: 'minimal',
renderInputOnTop: false,
renderFollowups: true,
supportsFileReferences: true,
// editorOverflowWidgetsDomNode: options.editorOverflowWidgetsDomNode,
filter: item => {
if (isWelcomeVM(item)) {
return false;
}
if (isRequestVM(item)) {
return allowRequests;
}
return true;
},
filter: item => !isWelcomeVM(item),
...options.chatWidgetViewOptions
},
{
@ -184,34 +174,6 @@ export class InlineChatWidget {
this._chatWidget.setVisible(true);
this._store.add(this._chatWidget);
const viewModelListener = this._store.add(new MutableDisposable());
this._store.add(this._chatWidget.onDidChangeViewModel(() => {
const model = this._chatWidget.viewModel;
if (!model) {
allowRequests = false;
viewModelListener.clear();
return;
}
const updateAllowRequestsFilter = () => {
let requestCount = 0;
for (const item of model.getItems()) {
if (isRequestVM(item)) {
if (++requestCount >= 2) {
break;
}
}
}
const newAllowRequest = requestCount >= 2;
if (newAllowRequest !== allowRequests) {
allowRequests = newAllowRequest;
this._chatWidget.refilter();
}
};
viewModelListener.value = model.onDidChange(updateAllowRequestsFilter);
}));
const viewModelStore = this._store.add(new DisposableStore());
this._store.add(this._chatWidget.onDidChangeViewModel(() => {
viewModelStore.clear();
@ -237,12 +199,31 @@ export class InlineChatWidget {
this._store.add(this._chatWidget.inputEditor.onDidBlurEditorWidget(() => this._ctxInputEditorFocused.set(false)));
const statusMenuId = options.statusMenuId instanceof MenuId ? options.statusMenuId : options.statusMenuId.menu;
const statusMenuOptions = options.statusMenuId instanceof MenuId ? undefined : options.statusMenuId.options;
const statusButtonBar = this._instantiationService.createInstance(MenuWorkbenchButtonBar, this._elements.statusToolbar, statusMenuId, statusMenuOptions);
this._store.add(statusButtonBar.onDidChange(() => this._onDidChangeHeight.fire()));
this._store.add(statusButtonBar);
if (this._configurationService.getValue(InlineChatConfigKeys.ExpTextButtons)) {
// TEXT-ONLY bar
const statusToolbarMenu = scopedInstaService.createInstance(MenuWorkbenchToolBar, this._elements.statusToolbar, statusMenuId, {
hiddenItemStrategy: HiddenItemStrategy.NoHide,
actionViewItemProvider: action => action instanceof MenuItemAction ? this._instantiationService.createInstance(TextOnlyMenuEntryActionViewItem, action, { conversational: true }) : undefined,
toolbarOptions: { primaryGroup: '0_main' },
menuOptions: { renderShortTitle: true },
label: true,
icon: false
});
this._store.add(statusToolbarMenu.onDidChangeMenuItems(() => this._onDidChangeHeight.fire()));
this._store.add(statusToolbarMenu);
} else {
// BUTTON bar
const statusMenuOptions = options.statusMenuId instanceof MenuId ? undefined : options.statusMenuId.options;
const statusButtonBar = scopedInstaService.createInstance(MenuWorkbenchButtonBar, this._elements.statusToolbar, statusMenuId, {
toolbarOptions: { primaryGroup: '0_main' },
menuOptions: { renderShortTitle: true },
...statusMenuOptions,
});
this._store.add(statusButtonBar.onDidChange(() => this._onDidChangeHeight.fire()));
this._store.add(statusButtonBar);
}
const workbenchToolbarOptions = {
hiddenItemStrategy: HiddenItemStrategy.NoHide,
@ -265,7 +246,6 @@ export class InlineChatWidget {
}));
this._elements.root.tabIndex = 0;
this._elements.followUps.tabIndex = 0;
this._elements.statusLabel.tabIndex = 0;
this._updateAriaLabel();
@ -332,7 +312,6 @@ export class InlineChatWidget {
protected _doLayout(dimension: Dimension): void {
const extraHeight = this._getExtraHeight();
const progressHeight = getTotalHeight(this._elements.progress);
const followUpsHeight = getTotalHeight(this._elements.followUps);
const statusHeight = getTotalHeight(this._elements.status);
// console.log('ZONE#Widget#layout', { height: dimension.height, extraHeight, progressHeight, followUpsHeight, statusHeight, LIST: dimension.height - progressHeight - followUpsHeight - statusHeight - extraHeight });
@ -342,7 +321,7 @@ export class InlineChatWidget {
this._elements.progress.style.width = `${dimension.width}px`;
this._chatWidget.layout(
dimension.height - progressHeight - followUpsHeight - statusHeight - extraHeight,
dimension.height - progressHeight - statusHeight - extraHeight,
dimension.width
);
}
@ -352,13 +331,12 @@ export class InlineChatWidget {
*/
get contentHeight(): number {
const data = {
followUpsHeight: getTotalHeight(this._elements.followUps),
chatWidgetContentHeight: this._chatWidget.contentHeight,
progressHeight: getTotalHeight(this._elements.progress),
statusHeight: getTotalHeight(this._elements.status),
extraHeight: this._getExtraHeight()
};
const result = data.progressHeight + data.chatWidgetContentHeight + data.followUpsHeight + data.statusHeight + data.extraHeight;
const result = data.progressHeight + data.chatWidgetContentHeight + data.statusHeight + data.extraHeight;
return result;
}
@ -381,7 +359,7 @@ export class InlineChatWidget {
}
protected _getExtraHeight(): number {
return 12 /* padding */ + 2 /*border*/ + 12 /*shadow*/;
return 4 /* padding */ + 2 /*border*/ + 12 /*shadow*/;
}
updateProgress(show: boolean) {
@ -421,6 +399,7 @@ export class InlineChatWidget {
}
updateToolbar(show: boolean) {
this._elements.root.classList.toggle('toolbar', show);
this._elements.statusToolbar.classList.toggle('hidden', !show);
this._elements.feedbackToolbar.classList.toggle('hidden', !show);
this._elements.status.classList.toggle('actions', show);

View file

@ -9,7 +9,7 @@ import { assertType } from 'vs/base/common/types';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions';
import { Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { Range } from 'vs/editor/common/core/range';
import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget';
import { localize } from 'vs/nls';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
@ -75,13 +75,23 @@ export class InlineChatZoneWidget extends ZoneWidget {
}
}
});
this._disposables.add(this.widget);
let scrollState: StableEditorBottomScrollState | undefined;
this._disposables.add(this.widget.chatWidget.onWillMaybeChangeHeight(() => {
if (this.position) {
scrollState = StableEditorBottomScrollState.capture(this.editor);
}
}));
this._disposables.add(this.widget.onDidChangeHeight(() => {
if (this.position) {
// only relayout when visible
scrollState ??= StableEditorBottomScrollState.capture(this.editor);
this._relayout(this._computeHeight().linesValue);
scrollState.restore(this.editor);
}
}));
this._disposables.add(this.widget);
this.create();
this._disposables.add(addDisposableListener(this.domNode, 'click', e => {
@ -172,20 +182,16 @@ export class InlineChatZoneWidget extends ZoneWidget {
}
override updatePositionAndHeight(position: Position): void {
const scrollState = StableEditorBottomScrollState.capture(this.editor);
super.updatePositionAndHeight(position, this._computeHeight().linesValue);
this._setWidgetMargins(position);
scrollState.restore(this.editor);
}
protected override _getWidth(info: EditorLayoutInfo): number {
return info.width - info.minimap.minimapWidth;
}
updateBackgroundColor(newPosition: Position, wholeRange: IRange) {
assertType(this.container);
const widgetLineNumber = newPosition.lineNumber;
this.container.classList.toggle('inside-selection', widgetLineNumber > wholeRange.startLineNumber && widgetLineNumber < wholeRange.endLineNumber);
}
private _calculateIndentationWidth(position: Position): number {
const viewModel = this.editor._getViewModel();
if (!viewModel) {
@ -224,11 +230,12 @@ export class InlineChatZoneWidget extends ZoneWidget {
}
override hide(): void {
this.container!.classList.remove('inside-selection');
const scrollState = StableEditorBottomScrollState.capture(this.editor);
this._ctxCursorPosition.reset();
this.widget.reset();
this.widget.chatWidget.setVisible(false);
super.hide();
aria.status(localize('inlineChatClosed', 'Closed inline chat widget'));
scrollState.restore(this.editor);
}
}

View file

@ -11,10 +11,6 @@
max-width: unset;
}
.monaco-workbench .zone-widget-container.inside-selection {
background-color: var(--vscode-inlineChat-regionHighlight);
}
.monaco-workbench .inline-chat {
color: inherit;
padding: 0 8px 8px 8px;
@ -25,6 +21,10 @@
background: var(--vscode-inlineChat-background);
}
.monaco-workbench .inline-chat.toolbar {
padding-bottom: 4px;
}
.monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-input-part .interactive-execute-toolbar {
margin-bottom: 1px;
}
@ -39,7 +39,7 @@
}
.monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-list .interactive-item-container.interactive-item-compact {
padding: 6px 4px;
padding: 6px 0;
gap: 6px;
}
@ -78,7 +78,7 @@
.monaco-workbench .inline-chat .status .label {
overflow: hidden;
color: var(--vscode-descriptionForeground);
font-size: 12px;
font-size: 11px;
display: inline-flex;
}
@ -146,9 +146,25 @@
padding-top: 3px;
}
.monaco-workbench .inline-chat .status .actions .action-item.text-only .action-label,
.monaco-workbench .inline-chat-content-widget .status .actions .action-item.text-only .action-label {
font-size: 12px;
line-height: 16px;
padding: 2px;
margin: 2px 0;
border-radius: 2px;
}
.monaco-action-bar .action-item.menu-entry.text-only + .action-item:not(.text-only) > .monaco-dropdown .action-label {
font-size: 12px;
line-height: 16px;
width: unset;
height: unset;
}
.monaco-workbench .inline-chat .status .actions > .monaco-button,
.monaco-workbench .inline-chat .status .actions > .monaco-button-dropdown {
margin-right: 6px;
margin-right: 4px;
}
.monaco-workbench .inline-chat .status .actions > .monaco-button-dropdown > .monaco-dropdown-button {
@ -166,12 +182,8 @@
}
.monaco-workbench .inline-chat .status .actions .monaco-text-button {
padding: 2px 4px;
white-space: nowrap;
}
.monaco-workbench .inline-chat .status .monaco-toolbar .action-item {
padding: 0 2px;
white-space: nowrap;
}
/* TODO@jrieken not needed? */

View file

@ -5,7 +5,7 @@
.monaco-workbench .inline-chat-content-widget {
z-index: 50;
padding: 6px 6px 6px 6px;
padding: 6px 6px 0px 6px;
border-radius: 4px;
background-color: var(--vscode-inlineChat-background);
box-shadow: 0 4px 8px var(--vscode-inlineChat-shadow);
@ -23,20 +23,3 @@
.monaco-workbench .inline-chat-content-widget.interactive-session .interactive-input-part.compact {
padding: 0;
}
.monaco-workbench .inline-chat-content-widget .message {
overflow: hidden;
color: var(--vscode-descriptionForeground);
font-size: 11px;
display: inline-flex;
}
.monaco-workbench .inline-chat-content-widget .message > .codicon {
padding-right: 5px;
font-size: 12px;
line-height: 18px;
}
.monaco-workbench .inline-chat-content-widget .hidden {
display: none;
}

View file

@ -6,95 +6,24 @@
import { localize } from 'vs/nls';
import { MenuId } from 'vs/platform/actions/common/actions';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { Registry } from 'vs/platform/registry/common/platform';
import { diffInserted, diffRemoved, editorHoverHighlight, editorWidgetBackground, editorWidgetBorder, focusBorder, inputBackground, inputPlaceholderForeground, registerColor, transparent, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
import { Extensions as ExtensionsMigration, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration';
export const enum InlineChatResponseTypes {
Empty = 'empty',
OnlyEdits = 'onlyEdits',
OnlyMessages = 'onlyMessages',
Mixed = 'mixed'
}
export const INLINE_CHAT_ID = 'interactiveEditor';
export const INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID = 'interactiveEditorAccessiblityHelp';
export const enum EditMode {
Live = 'live',
Preview = 'preview'
}
export const CTX_INLINE_CHAT_HAS_AGENT = new RawContextKey<boolean>('inlineChatHasProvider', false, localize('inlineChatHasProvider', "Whether a provider for interactive editors exists"));
export const CTX_INLINE_CHAT_VISIBLE = new RawContextKey<boolean>('inlineChatVisible', false, localize('inlineChatVisible', "Whether the interactive editor input is visible"));
export const CTX_INLINE_CHAT_FOCUSED = new RawContextKey<boolean>('inlineChatFocused', false, localize('inlineChatFocused', "Whether the interactive editor input is focused"));
export const CTX_INLINE_CHAT_RESPONSE_FOCUSED = new RawContextKey<boolean>('inlineChatResponseFocused', false, localize('inlineChatResponseFocused', "Whether the interactive widget's response is focused"));
export const CTX_INLINE_CHAT_EMPTY = new RawContextKey<boolean>('inlineChatEmpty', false, localize('inlineChatEmpty', "Whether the interactive editor input is empty"));
export const CTX_INLINE_CHAT_INNER_CURSOR_FIRST = new RawContextKey<boolean>('inlineChatInnerCursorFirst', false, localize('inlineChatInnerCursorFirst', "Whether the cursor of the iteractive editor input is on the first line"));
export const CTX_INLINE_CHAT_INNER_CURSOR_LAST = new RawContextKey<boolean>('inlineChatInnerCursorLast', false, localize('inlineChatInnerCursorLast', "Whether the cursor of the iteractive editor input is on the last line"));
export const CTX_INLINE_CHAT_INNER_CURSOR_START = new RawContextKey<boolean>('inlineChatInnerCursorStart', false, localize('inlineChatInnerCursorStart', "Whether the cursor of the iteractive editor input is on the start of the input"));
export const CTX_INLINE_CHAT_INNER_CURSOR_END = new RawContextKey<boolean>('inlineChatInnerCursorEnd', false, localize('inlineChatInnerCursorEnd', "Whether the cursor of the iteractive editor input is on the end of the input"));
export const CTX_INLINE_CHAT_OUTER_CURSOR_POSITION = new RawContextKey<'above' | 'below' | ''>('inlineChatOuterCursorPosition', '', localize('inlineChatOuterCursorPosition', "Whether the cursor of the outer editor is above or below the interactive editor input"));
export const CTX_INLINE_CHAT_HAS_STASHED_SESSION = new RawContextKey<boolean>('inlineChatHasStashedSession', false, localize('inlineChatHasStashedSession', "Whether interactive editor has kept a session for quick restore"));
export const CTX_INLINE_CHAT_RESPONSE_TYPES = new RawContextKey<InlineChatResponseTypes | undefined>('inlineChatResponseTypes', InlineChatResponseTypes.Empty, localize('inlineChatResponseTypes', "What type was the responses have been receieved"));
export const CTX_INLINE_CHAT_USER_DID_EDIT = new RawContextKey<boolean>('inlineChatUserDidEdit', undefined, localize('inlineChatUserDidEdit', "Whether the user did changes ontop of the inline chat"));
export const CTX_INLINE_CHAT_DOCUMENT_CHANGED = new RawContextKey<boolean>('inlineChatDocumentChanged', false, localize('inlineChatDocumentChanged', "Whether the document has changed concurrently"));
export const CTX_INLINE_CHAT_CHANGE_HAS_DIFF = new RawContextKey<boolean>('inlineChatChangeHasDiff', false, localize('inlineChatChangeHasDiff', "Whether the current change supports showing a diff"));
export const CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF = new RawContextKey<boolean>('inlineChatChangeShowsDiff', false, localize('inlineChatChangeShowsDiff', "Whether the current change showing a diff"));
export const CTX_INLINE_CHAT_EDIT_MODE = new RawContextKey<EditMode>('config.inlineChat.mode', EditMode.Live);
// --- (select) action identifier
export const ACTION_ACCEPT_CHANGES = 'inlineChat.acceptChanges';
export const ACTION_REGENERATE_RESPONSE = 'inlineChat.regenerate';
export const ACTION_VIEW_IN_CHAT = 'inlineChat.viewInChat';
export const ACTION_TOGGLE_DIFF = 'inlineChat.toggleDiff';
// --- menus
export const MENU_INLINE_CHAT_WIDGET = MenuId.for('inlineChatWidget');
export const MENU_INLINE_CHAT_WIDGET_STATUS = MenuId.for('inlineChatWidget.status');
// --- colors
export const inlineChatBackground = registerColor('inlineChat.background', { dark: editorWidgetBackground, light: editorWidgetBackground, hcDark: editorWidgetBackground, hcLight: editorWidgetBackground }, localize('inlineChat.background', "Background color of the interactive editor widget"));
export const inlineChatBorder = registerColor('inlineChat.border', { dark: editorWidgetBorder, light: editorWidgetBorder, hcDark: editorWidgetBorder, hcLight: editorWidgetBorder }, localize('inlineChat.border', "Border color of the interactive editor widget"));
export const inlineChatShadow = registerColor('inlineChat.shadow', { dark: widgetShadow, light: widgetShadow, hcDark: widgetShadow, hcLight: widgetShadow }, localize('inlineChat.shadow', "Shadow color of the interactive editor widget"));
export const inlineChatRegionHighlight = registerColor('inlineChat.regionHighlight', { dark: editorHoverHighlight, light: editorHoverHighlight, hcDark: editorHoverHighlight, hcLight: editorHoverHighlight }, localize('inlineChat.regionHighlight', "Background highlighting of the current interactive region. Must be transparent."), true);
export const inlineChatInputBorder = registerColor('inlineChatInput.border', { dark: editorWidgetBorder, light: editorWidgetBorder, hcDark: editorWidgetBorder, hcLight: editorWidgetBorder }, localize('inlineChatInput.border', "Border color of the interactive editor input"));
export const inlineChatInputFocusBorder = registerColor('inlineChatInput.focusBorder', { dark: focusBorder, light: focusBorder, hcDark: focusBorder, hcLight: focusBorder }, localize('inlineChatInput.focusBorder', "Border color of the interactive editor input when focused"));
export const inlineChatInputPlaceholderForeground = registerColor('inlineChatInput.placeholderForeground', { dark: inputPlaceholderForeground, light: inputPlaceholderForeground, hcDark: inputPlaceholderForeground, hcLight: inputPlaceholderForeground }, localize('inlineChatInput.placeholderForeground', "Foreground color of the interactive editor input placeholder"));
export const inlineChatInputBackground = registerColor('inlineChatInput.background', { dark: inputBackground, light: inputBackground, hcDark: inputBackground, hcLight: inputBackground }, localize('inlineChatInput.background', "Background color of the interactive editor input"));
export const inlineChatDiffInserted = registerColor('inlineChatDiff.inserted', { dark: transparent(diffInserted, .5), light: transparent(diffInserted, .5), hcDark: transparent(diffInserted, .5), hcLight: transparent(diffInserted, .5) }, localize('inlineChatDiff.inserted', "Background color of inserted text in the interactive editor input"));
export const overviewRulerInlineChatDiffInserted = registerColor('editorOverviewRuler.inlineChatInserted', { dark: transparent(diffInserted, 0.6), light: transparent(diffInserted, 0.8), hcDark: transparent(diffInserted, 0.6), hcLight: transparent(diffInserted, 0.8) }, localize('editorOverviewRuler.inlineChatInserted', 'Overview ruler marker color for inline chat inserted content.'));
export const minimapInlineChatDiffInserted = registerColor('editorOverviewRuler.inlineChatInserted', { dark: transparent(diffInserted, 0.6), light: transparent(diffInserted, 0.8), hcDark: transparent(diffInserted, 0.6), hcLight: transparent(diffInserted, 0.8) }, localize('editorOverviewRuler.inlineChatInserted', 'Overview ruler marker color for inline chat inserted content.'));
export const inlineChatDiffRemoved = registerColor('inlineChatDiff.removed', { dark: transparent(diffRemoved, .5), light: transparent(diffRemoved, .5), hcDark: transparent(diffRemoved, .5), hcLight: transparent(diffRemoved, .5) }, localize('inlineChatDiff.removed', "Background color of removed text in the interactive editor input"));
export const overviewRulerInlineChatDiffRemoved = registerColor('editorOverviewRuler.inlineChatRemoved', { dark: transparent(diffRemoved, 0.6), light: transparent(diffRemoved, 0.8), hcDark: transparent(diffRemoved, 0.6), hcLight: transparent(diffRemoved, 0.8) }, localize('editorOverviewRuler.inlineChatRemoved', 'Overview ruler marker color for inline chat removed content.'));
import { diffInserted, diffRemoved, editorWidgetBackground, editorWidgetBorder, focusBorder, inputBackground, inputPlaceholderForeground, registerColor, transparent, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
// settings
Registry.as<IConfigurationMigrationRegistry>(ExtensionsMigration.ConfigurationMigration).registerConfigurationMigrations(
[{
key: 'interactiveEditor.editMode', migrateFn: (value: any) => {
return [['inlineChat.mode', { value: value }]];
}
}]
);
export const enum InlineChatConfigKeys {
Mode = 'inlineChat.mode',
FinishOnType = 'inlineChat.finishOnType',
AcceptedOrDiscardBeforeSave = 'inlineChat.acceptedOrDiscardBeforeSave',
HoldToSpeech = 'inlineChat.holdToSpeech',
AccessibleDiffView = 'inlineChat.accessibleDiffView'
AccessibleDiffView = 'inlineChat.accessibleDiffView',
ExpTextButtons = 'inlineChat.experimental.textButtons'
}
export const enum EditMode {
Live = 'live',
Preview = 'preview'
}
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
@ -136,6 +65,75 @@ Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfigurat
localize('accessibleDiffView.on', "The accessible diff viewer is always enabled."),
localize('accessibleDiffView.off', "The accessible diff viewer is never enabled."),
],
}
},
[InlineChatConfigKeys.ExpTextButtons]: {
description: localize('txtButtons', "Whether to use textual buttons (Requires restart)."),
default: false,
type: 'boolean'
},
}
});
export const INLINE_CHAT_ID = 'interactiveEditor';
export const INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID = 'interactiveEditorAccessiblityHelp';
// --- CONTEXT
export const enum InlineChatResponseType {
None = 'none',
Messages = 'messages',
MessagesAndEdits = 'messagesAndEdits'
}
export const CTX_INLINE_CHAT_HAS_AGENT = new RawContextKey<boolean>('inlineChatHasProvider', false, localize('inlineChatHasProvider', "Whether a provider for interactive editors exists"));
export const CTX_INLINE_CHAT_VISIBLE = new RawContextKey<boolean>('inlineChatVisible', false, localize('inlineChatVisible', "Whether the interactive editor input is visible"));
export const CTX_INLINE_CHAT_FOCUSED = new RawContextKey<boolean>('inlineChatFocused', false, localize('inlineChatFocused', "Whether the interactive editor input is focused"));
export const CTX_INLINE_CHAT_RESPONSE_FOCUSED = new RawContextKey<boolean>('inlineChatResponseFocused', false, localize('inlineChatResponseFocused', "Whether the interactive widget's response is focused"));
export const CTX_INLINE_CHAT_EMPTY = new RawContextKey<boolean>('inlineChatEmpty', false, localize('inlineChatEmpty', "Whether the interactive editor input is empty"));
export const CTX_INLINE_CHAT_INNER_CURSOR_FIRST = new RawContextKey<boolean>('inlineChatInnerCursorFirst', false, localize('inlineChatInnerCursorFirst', "Whether the cursor of the iteractive editor input is on the first line"));
export const CTX_INLINE_CHAT_INNER_CURSOR_LAST = new RawContextKey<boolean>('inlineChatInnerCursorLast', false, localize('inlineChatInnerCursorLast', "Whether the cursor of the iteractive editor input is on the last line"));
export const CTX_INLINE_CHAT_INNER_CURSOR_START = new RawContextKey<boolean>('inlineChatInnerCursorStart', false, localize('inlineChatInnerCursorStart', "Whether the cursor of the iteractive editor input is on the start of the input"));
export const CTX_INLINE_CHAT_INNER_CURSOR_END = new RawContextKey<boolean>('inlineChatInnerCursorEnd', false, localize('inlineChatInnerCursorEnd', "Whether the cursor of the iteractive editor input is on the end of the input"));
export const CTX_INLINE_CHAT_OUTER_CURSOR_POSITION = new RawContextKey<'above' | 'below' | ''>('inlineChatOuterCursorPosition', '', localize('inlineChatOuterCursorPosition', "Whether the cursor of the outer editor is above or below the interactive editor input"));
export const CTX_INLINE_CHAT_HAS_STASHED_SESSION = new RawContextKey<boolean>('inlineChatHasStashedSession', false, localize('inlineChatHasStashedSession', "Whether interactive editor has kept a session for quick restore"));
export const CTX_INLINE_CHAT_USER_DID_EDIT = new RawContextKey<boolean>('inlineChatUserDidEdit', undefined, localize('inlineChatUserDidEdit', "Whether the user did changes ontop of the inline chat"));
export const CTX_INLINE_CHAT_DOCUMENT_CHANGED = new RawContextKey<boolean>('inlineChatDocumentChanged', false, localize('inlineChatDocumentChanged', "Whether the document has changed concurrently"));
export const CTX_INLINE_CHAT_CHANGE_HAS_DIFF = new RawContextKey<boolean>('inlineChatChangeHasDiff', false, localize('inlineChatChangeHasDiff', "Whether the current change supports showing a diff"));
export const CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF = new RawContextKey<boolean>('inlineChatChangeShowsDiff', false, localize('inlineChatChangeShowsDiff', "Whether the current change showing a diff"));
export const CTX_INLINE_CHAT_EDIT_MODE = new RawContextKey<EditMode>('config.inlineChat.mode', EditMode.Live);
export const CTX_INLINE_CHAT_REQUEST_IN_PROGRESS = new RawContextKey<boolean>('inlineChatRequestInProgress', false, localize('inlineChatRequestInProgress', "Whether an inline chat request is currently in progress"));
export const CTX_INLINE_CHAT_RESPONSE_TYPE = new RawContextKey<InlineChatResponseType>('inlineChatResponseType', InlineChatResponseType.None, localize('inlineChatResponseTypes', "What type was the responses have been receieved, nothing yet, just messages, or messaged and local edits"));
export const CTX_INLINE_CHAT_CONFIG_TXT_BTNS = ContextKeyExpr.equals(`config.${[InlineChatConfigKeys.ExpTextButtons]}`, true);
// --- (selected) action identifier
export const ACTION_ACCEPT_CHANGES = 'inlineChat.acceptChanges';
export const ACTION_REGENERATE_RESPONSE = 'inlineChat.regenerate';
export const ACTION_VIEW_IN_CHAT = 'inlineChat.viewInChat';
export const ACTION_TOGGLE_DIFF = 'inlineChat.toggleDiff';
// --- menus
export const MENU_INLINE_CHAT_WIDGET = MenuId.for('inlineChatWidget');
export const MENU_INLINE_CHAT_CONTENT_STATUS = MenuId.for('inlineChat.content.status');
export const MENU_INLINE_CHAT_WIDGET_STATUS = MenuId.for('inlineChatWidget.status');
// --- colors
export const inlineChatBackground = registerColor('inlineChat.background', { dark: editorWidgetBackground, light: editorWidgetBackground, hcDark: editorWidgetBackground, hcLight: editorWidgetBackground }, localize('inlineChat.background', "Background color of the interactive editor widget"));
export const inlineChatBorder = registerColor('inlineChat.border', { dark: editorWidgetBorder, light: editorWidgetBorder, hcDark: editorWidgetBorder, hcLight: editorWidgetBorder }, localize('inlineChat.border', "Border color of the interactive editor widget"));
export const inlineChatShadow = registerColor('inlineChat.shadow', { dark: widgetShadow, light: widgetShadow, hcDark: widgetShadow, hcLight: widgetShadow }, localize('inlineChat.shadow', "Shadow color of the interactive editor widget"));
export const inlineChatInputBorder = registerColor('inlineChatInput.border', { dark: editorWidgetBorder, light: editorWidgetBorder, hcDark: editorWidgetBorder, hcLight: editorWidgetBorder }, localize('inlineChatInput.border', "Border color of the interactive editor input"));
export const inlineChatInputFocusBorder = registerColor('inlineChatInput.focusBorder', { dark: focusBorder, light: focusBorder, hcDark: focusBorder, hcLight: focusBorder }, localize('inlineChatInput.focusBorder', "Border color of the interactive editor input when focused"));
export const inlineChatInputPlaceholderForeground = registerColor('inlineChatInput.placeholderForeground', { dark: inputPlaceholderForeground, light: inputPlaceholderForeground, hcDark: inputPlaceholderForeground, hcLight: inputPlaceholderForeground }, localize('inlineChatInput.placeholderForeground', "Foreground color of the interactive editor input placeholder"));
export const inlineChatInputBackground = registerColor('inlineChatInput.background', { dark: inputBackground, light: inputBackground, hcDark: inputBackground, hcLight: inputBackground }, localize('inlineChatInput.background', "Background color of the interactive editor input"));
export const inlineChatDiffInserted = registerColor('inlineChatDiff.inserted', { dark: transparent(diffInserted, .5), light: transparent(diffInserted, .5), hcDark: transparent(diffInserted, .5), hcLight: transparent(diffInserted, .5) }, localize('inlineChatDiff.inserted', "Background color of inserted text in the interactive editor input"));
export const overviewRulerInlineChatDiffInserted = registerColor('editorOverviewRuler.inlineChatInserted', { dark: transparent(diffInserted, 0.6), light: transparent(diffInserted, 0.8), hcDark: transparent(diffInserted, 0.6), hcLight: transparent(diffInserted, 0.8) }, localize('editorOverviewRuler.inlineChatInserted', 'Overview ruler marker color for inline chat inserted content.'));
export const minimapInlineChatDiffInserted = registerColor('editorOverviewRuler.inlineChatInserted', { dark: transparent(diffInserted, 0.6), light: transparent(diffInserted, 0.8), hcDark: transparent(diffInserted, 0.6), hcLight: transparent(diffInserted, 0.8) }, localize('editorOverviewRuler.inlineChatInserted', 'Overview ruler marker color for inline chat inserted content.'));
export const inlineChatDiffRemoved = registerColor('inlineChatDiff.removed', { dark: transparent(diffRemoved, .5), light: transparent(diffRemoved, .5), hcDark: transparent(diffRemoved, .5), hcLight: transparent(diffRemoved, .5) }, localize('inlineChatDiff.removed', "Background color of removed text in the interactive editor input"));
export const overviewRulerInlineChatDiffRemoved = registerColor('editorOverviewRuler.inlineChatRemoved', { dark: transparent(diffRemoved, 0.6), light: transparent(diffRemoved, 0.8), hcDark: transparent(diffRemoved, 0.6), hcLight: transparent(diffRemoved, 0.8) }, localize('editorOverviewRuler.inlineChatRemoved', 'Overview ruler marker color for inline chat removed content.'));

View file

@ -31,7 +31,7 @@ import { IView, IViewDescriptorService } from 'vs/workbench/common/views';
import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
import { IAccessibleViewService } from 'vs/platform/accessibility/browser/accessibleView';
import { IChatAccessibilityService, IChatWidget, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { ChatAgentLocation, ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { ChatAgentLocation, ChatAgentService, IChatAgentData, IChatAgentNameService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { InlineChatController, InlineChatRunOptions, State } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController';
import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
@ -156,6 +156,11 @@ suite('InteractiveChatController', function () {
[IChatWidgetService, new SyncDescriptor(ChatWidgetService)],
[IChatSlashCommandService, new SyncDescriptor(ChatSlashCommandService)],
[IChatService, new SyncDescriptor(ChatService)],
[IChatAgentNameService, new class extends mock<IChatAgentNameService>() {
override getAgentNameRestriction(chatAgentData: IChatAgentData): boolean {
return false;
}
}],
[IEditorWorkerService, new SyncDescriptor(TestWorkerService)],
[IContextKeyService, contextKeyService],
[IChatAgentService, new SyncDescriptor(ChatAgentService)],
@ -323,6 +328,7 @@ suite('InteractiveChatController', function () {
assert.ok(session);
assert.deepStrictEqual(session.wholeRange.value, new Range(3, 1, 3, 3)); // initial
ctrl.chatWidget.setInput('GENGEN');
ctrl.acceptInput();
assert.strictEqual(await ctrl.awaitStates([State.SHOW_REQUEST, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]), undefined);
@ -540,6 +546,7 @@ suite('InteractiveChatController', function () {
// REQUEST 2
const p2 = ctrl.awaitStates([State.SHOW_REQUEST, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]);
ctrl.chatWidget.setInput('1');
await ctrl.acceptInput();
assert.strictEqual(await p2, undefined);
@ -623,6 +630,7 @@ suite('InteractiveChatController', function () {
// REQUEST 2
const p2 = ctrl.awaitStates([State.SHOW_REQUEST, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]);
ctrl.chatWidget.setInput('1');
await ctrl.acceptInput();
assert.strictEqual(await p2, undefined);

View file

@ -15,7 +15,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { CTX_NOTEBOOK_CELL_CHAT_FOCUSED, CTX_NOTEBOOK_CHAT_HAS_ACTIVE_REQUEST, CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION, CTX_NOTEBOOK_CHAT_USER_DID_EDIT, MENU_CELL_CHAT_INPUT, MENU_CELL_CHAT_WIDGET, MENU_CELL_CHAT_WIDGET_STATUS } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatContext';
import { NotebookChatController } from 'vs/workbench/contrib/notebook/browser/controller/chat/notebookChatController';
import { CELL_TITLE_CELL_GROUP_ID, INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, getEditorFromArgsOrActivePane } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';
@ -261,7 +261,7 @@ registerAction2(class extends NotebookAction {
id: MENU_CELL_CHAT_WIDGET_STATUS,
group: 'inline',
order: 0,
when: CTX_INLINE_CHAT_RESPONSE_TYPES.notEqualsTo(InlineChatResponseTypes.OnlyMessages),
when: CTX_INLINE_CHAT_RESPONSE_TYPE.notEqualsTo(InlineChatResponseType.Messages),
}
],
f1: false

View file

@ -203,13 +203,17 @@ async function loadTests(opts) {
'throw ListenerLeakError'
]);
const _allowedSuitesWithOutput = new Set([
'InteractiveChatController'
]);
let _testsWithUnexpectedOutput = false;
for (const consoleFn of [console.log, console.error, console.info, console.warn, console.trace, console.debug]) {
console[consoleFn.name] = function (msg) {
if (!currentTest) {
consoleFn.apply(console, arguments);
} else if (!_allowedTestOutput.some(a => a.test(msg)) && !_allowedTestsWithOutput.has(currentTest.title)) {
} else if (!_allowedTestOutput.some(a => a.test(msg)) && !_allowedTestsWithOutput.has(currentTest.title) && !_allowedSuitesWithOutput.has(currentTest.parent?.title)) {
_testsWithUnexpectedOutput = true;
consoleFn.apply(console, arguments);
}