Skip to content

Commit

Permalink
Split input actions, move attachments inside chat input (microsoft#22…
Browse files Browse the repository at this point in the history
…8684)

* Rearrange buttons

* Fix compact chat

* Fix
  • Loading branch information
roblourens authored Sep 15, 2024
1 parent b1d9889 commit 3be54cc
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 71 deletions.
1 change: 1 addition & 0 deletions src/vs/platform/actions/common/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ export class MenuId {
static readonly ChatMessageTitle = new MenuId('ChatMessageTitle');
static readonly ChatExecute = new MenuId('ChatExecute');
static readonly ChatExecuteSecondary = new MenuId('ChatExecuteSecondary');
static readonly ChatInput = new MenuId('ChatInput');
static readonly ChatInputSide = new MenuId('ChatInputSide');
static readonly ChatInlineResourceAnchorContext = new MenuId('ChatInlineResourceAnchorContext');
static readonly ChatInlineSymbolAnchorContext = new MenuId('ChatInlineSymbolAnchorContext');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ class AttachContextAction extends Action2 {
menu: [
{
when: AttachContextAction._cdt,
id: MenuId.ChatExecute,
id: MenuId.ChatInput,
group: 'navigation',
},
]
Expand Down
98 changes: 64 additions & 34 deletions src/vs/workbench/contrib/chat/browser/chatInputPart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
private _inputEditor!: CodeEditorWidget;
private _inputEditorElement!: HTMLElement;

private toolbar!: MenuWorkbenchToolBar;
private executeToolbar!: MenuWorkbenchToolBar;
private inputActionsToolbar!: MenuWorkbenchToolBar;

get inputEditor() {
return this._inputEditor;
Expand All @@ -142,7 +143,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
private inputEditorHasFocus: IContextKey<boolean>;

private cachedDimensions: dom.Dimension | undefined;
private cachedToolbarWidth: number | undefined;
private cachedExecuteToolbarWidth: number | undefined;
private cachedInputToolbarWidth: number | undefined;

readonly inputUri = URI.parse(`${ChatInputPart.INPUT_SCHEME}:input-${ChatInputPart._counter++}`);

Expand Down Expand Up @@ -368,22 +370,39 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
}

render(container: HTMLElement, initialValue: string, widget: IChatWidget) {
this.container = dom.append(container, $('.interactive-input-part'));
this.container.classList.toggle('compact', this.options.renderStyle === 'compact');

let inputContainer: HTMLElement;
let inputAndSideToolbar: HTMLElement;
let elements;
if (this.options.renderStyle === 'compact') {
inputAndSideToolbar = dom.append(this.container, $('.interactive-input-and-side-toolbar'));
this.followupsContainer = dom.append(this.container, $('.interactive-input-followups'));
inputContainer = dom.append(inputAndSideToolbar, $('.interactive-input-and-execute-toolbar'));
this.attachedContextContainer = dom.append(this.container, $('.chat-attached-context'));
elements = dom.h('.interactive-input-part', [
dom.h('.interactive-input-and-side-toolbar@inputAndSideToolbar', [
dom.h('.chat-input-container@inputContainer', [
dom.h('.chat-editor-container@editorContainer'),
dom.h('.chat-input-toolbars@inputToolbars'),
]),
]),
dom.h('.chat-attached-context@attachedContextContainer'),
dom.h('.interactive-input-followups@followupsContainer'),
]);
} else {
this.followupsContainer = dom.append(this.container, $('.interactive-input-followups'));
this.attachedContextContainer = dom.append(this.container, $('.chat-attached-context'));
inputAndSideToolbar = dom.append(this.container, $('.interactive-input-and-side-toolbar'));
inputContainer = dom.append(inputAndSideToolbar, $('.interactive-input-and-execute-toolbar'));
elements = dom.h('.interactive-input-part', [
dom.h('.interactive-input-followups@followupsContainer'),
dom.h('.interactive-input-and-side-toolbar@inputAndSideToolbar', [
dom.h('.chat-input-container@inputContainer', [
dom.h('.chat-editor-container@editorContainer'),
dom.h('.chat-attached-context@attachedContextContainer'),
dom.h('.chat-input-toolbars@inputToolbars'),
]),
]),
]);
}
this.container = elements.root;
container.append(this.container);
this.container.classList.toggle('compact', this.options.renderStyle === 'compact');
this.followupsContainer = elements.followupsContainer;
const inputAndSideToolbar = elements.inputAndSideToolbar; // The chat input and toolbar to the right
const inputContainer = elements.inputContainer; // The chat editor, attachments, and toolbars
const editorContainer = elements.editorContainer;
this.attachedContextContainer = elements.attachedContextContainer;
const toolbarsContainer = elements.inputToolbars;
this.initAttachedContext(this.attachedContextContainer);

const inputScopedContextKeyService = this._register(this.contextKeyService.createScoped(inputContainer));
Expand Down Expand Up @@ -415,7 +434,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
options.scrollbar = { ...(options.scrollbar ?? {}), vertical: 'hidden' };
options.stickyScroll = { enabled: false };

this._inputEditorElement = dom.append(inputContainer, $(chatInputEditorContainerSelector));
this._inputEditorElement = dom.append(editorContainer!, $(chatInputEditorContainerSelector));
const editorOptions = getSimpleCodeEditorWidgetOptions();
editorOptions.contributions?.push(...EditorExtensionsRegistry.getSomeEditorContributions([ContentHoverController.ID, GlyphHoverController.ID]));
this._inputEditor = this._register(scopedInstantiationService.createInstance(CodeEditorWidget, this._inputEditorElement, options, editorOptions));
Expand Down Expand Up @@ -443,7 +462,19 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
this._onDidBlur.fire();
}));

this.toolbar = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, inputContainer, this.options.menus.executeToolbar, {
this._register(dom.addStandardDisposableListener(toolbarsContainer, dom.EventType.CLICK, e => this.inputEditor.focus()));
this.inputActionsToolbar = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, toolbarsContainer, MenuId.ChatInput, {
telemetrySource: this.options.menus.telemetrySource,
menuOptions: { shouldForwardArgs: true },
hiddenItemStrategy: HiddenItemStrategy.Ignore,
}));
this.inputActionsToolbar.context = { widget } satisfies IChatExecuteActionContext;
this._register(this.inputActionsToolbar.onDidChangeMenuItems(() => {
if (this.cachedDimensions && typeof this.cachedInputToolbarWidth === 'number' && this.cachedInputToolbarWidth !== this.inputActionsToolbar.getItemsWidth()) {
this.layout(this.cachedDimensions.height, this.cachedDimensions.width);
}
}));
this.executeToolbar = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, toolbarsContainer, this.options.menus.executeToolbar, {
telemetrySource: this.options.menus.telemetrySource,
menuOptions: {
shouldForwardArgs: true
Expand All @@ -460,14 +491,12 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
return undefined;
}
}));
this.toolbar.getElement().classList.add('interactive-execute-toolbar');
this.toolbar.context = { widget } satisfies IChatExecuteActionContext;
this._register(this.toolbar.onDidChangeMenuItems(() => {
if (this.cachedDimensions && typeof this.cachedToolbarWidth === 'number' && this.cachedToolbarWidth !== this.toolbar.getItemsWidth()) {
this.executeToolbar.context = { widget } satisfies IChatExecuteActionContext;
this._register(this.executeToolbar.onDidChangeMenuItems(() => {
if (this.cachedDimensions && typeof this.cachedExecuteToolbarWidth === 'number' && this.cachedExecuteToolbarWidth !== this.executeToolbar.getItemsWidth()) {
this.layout(this.cachedDimensions.height, this.cachedDimensions.width);
}
}));

if (this.options.menus.inputSideToolbar) {
const toolbarSide = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, inputAndSideToolbar, this.options.menus.inputSideToolbar, {
telemetrySource: this.options.menus.telemetrySource,
Expand Down Expand Up @@ -598,7 +627,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge

get contentHeight(): number {
const data = this.getLayoutData();
return data.followupsHeight + data.inputPartEditorHeight + data.inputPartVerticalPadding + data.inputEditorBorder + data.implicitContextHeight + data.executeToolbarHeight;
return data.followupsHeight + data.inputPartEditorHeight + data.inputPartVerticalPadding + data.inputEditorBorder + data.attachmentsHeight + data.toolbarsHeight;
}

layout(height: number, width: number) {
Expand All @@ -612,16 +641,15 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
this.initAttachedContext(this.attachedContextContainer, true);

const data = this.getLayoutData();

const inputEditorHeight = Math.min(data.inputPartEditorHeight, height - data.followupsHeight - data.inputPartVerticalPadding);
const inputEditorHeight = Math.min(data.inputPartEditorHeight, height - data.followupsHeight - data.attachmentsHeight - data.inputPartVerticalPadding - data.toolbarsHeight);

const followupsWidth = width - data.inputPartHorizontalPadding;
this.followupsContainer.style.width = `${followupsWidth}px`;

this._inputPartHeight = data.followupsHeight + inputEditorHeight + data.inputPartVerticalPadding + data.inputEditorBorder + data.implicitContextHeight + data.executeToolbarHeight;
this._inputPartHeight = data.inputPartVerticalPadding + data.followupsHeight + inputEditorHeight + data.inputEditorBorder + data.attachmentsHeight + data.toolbarsHeight;

const initialEditorScrollWidth = this._inputEditor.getScrollWidth();
const newEditorWidth = width - data.inputPartHorizontalPadding - data.editorBorder - data.editorPadding - data.executeToolbarWidth - data.sideToolbarWidth;
const newEditorWidth = width - data.inputPartHorizontalPadding - data.editorBorder - data.inputPartHorizontalPaddingInside - data.toolbarsWidth - data.sideToolbarWidth;
const newDimension = { width: newEditorWidth, height: inputEditorHeight };
if (!this.previousInputEditorDimension || (this.previousInputEditorDimension.width !== newDimension.width || this.previousInputEditorDimension.height !== newDimension.height)) {
// This layout call has side-effects that are hard to understand. eg if we are calling this inside a onDidChangeContent handler, this can trigger the next onDidChangeContent handler
Expand All @@ -637,19 +665,21 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
}

private getLayoutData() {
const executeToolbarWidth = this.cachedToolbarWidth = this.toolbar.getItemsWidth();
const executeToolbarPadding = (this.toolbar.getItemsLength() - 1) * 4;
const executeToolbarWidth = this.cachedExecuteToolbarWidth = this.executeToolbar.getItemsWidth();
const inputToolbarWidth = this.cachedInputToolbarWidth = this.inputActionsToolbar.getItemsWidth();
const executeToolbarPadding = (this.executeToolbar.getItemsLength() - 1) * 4;
const inputToolbarPadding = (this.inputActionsToolbar.getItemsLength() - 1) * 4;
return {
inputEditorBorder: 2,
followupsHeight: this.followupsContainer.offsetHeight,
inputPartEditorHeight: Math.min(this._inputEditor.getContentHeight(), this.inputEditorMaxHeight),
inputPartHorizontalPadding: this.options.renderStyle === 'compact' ? 12 : 32,
inputPartVerticalPadding: this.options.renderStyle === 'compact' ? 12 : 24,
implicitContextHeight: this.attachedContextContainer.offsetHeight,
inputPartVerticalPadding: this.options.renderStyle === 'compact' ? 12 : 30,
attachmentsHeight: this.attachedContextContainer.offsetHeight,
editorBorder: 2,
editorPadding: 12,
executeToolbarWidth: this.options.renderStyle === 'compact' ? executeToolbarWidth + executeToolbarPadding : 0,
executeToolbarHeight: this.options.renderStyle === 'compact' ? 0 : 22,
inputPartHorizontalPaddingInside: 12,
toolbarsWidth: this.options.renderStyle === 'compact' ? executeToolbarWidth + executeToolbarPadding + inputToolbarWidth + inputToolbarPadding : 0,
toolbarsHeight: this.options.renderStyle === 'compact' ? 0 : 22,
sideToolbarWidth: this.inputSideToolbarContainer ? dom.getTotalWidth(this.inputSideToolbarContainer) + 4 /*gap*/ : 0,
};
}
Expand Down
42 changes: 18 additions & 24 deletions src/vs/workbench/contrib/chat/browser/media/chat.css
Original file line number Diff line number Diff line change
Expand Up @@ -458,22 +458,19 @@ have to be updated for changes to the rules above, or to support more deeply nes
margin-right: 3px;
}

.interactive-session .interactive-input-and-execute-toolbar {
.interactive-session .chat-input-container {
box-sizing: border-box;
cursor: text;
background-color: var(--vscode-input-background);
border: 1px solid var(--vscode-input-border, transparent);
border-radius: 4px;
position: relative;
padding: 0 6px;
margin-bottom: 4px;
padding: 0 6px 6px 6px; /* top padding is inside the editor widget */
}

.interactive-session .interactive-input-part.compact .interactive-input-and-execute-toolbar {
.interactive-session .interactive-input-part.compact .chat-input-container {
display: flex;
align-items: flex-end;
justify-content: space-between;
margin-bottom: 0;
padding-bottom: 0;
border-radius: 2px;
}

Expand All @@ -483,40 +480,37 @@ have to be updated for changes to the rules above, or to support more deeply nes
align-items: center;
}

.interactive-session .interactive-input-and-execute-toolbar.focused {
.interactive-session .chat-input-container.focused {
border-color: var(--vscode-focusBorder);
}

.interactive-input-and-execute-toolbar .monaco-editor .mtk1 {
.chat-editor-container .monaco-editor .mtk1 {
color: var(--vscode-input-foreground);
}

.interactive-session .interactive-input-and-execute-toolbar .monaco-editor,
.interactive-session .interactive-input-and-execute-toolbar .monaco-editor .monaco-editor-background {
.interactive-session .chat-editor-container .monaco-editor,
.interactive-session .chat-editor-container .monaco-editor .monaco-editor-background {
background-color: var(--vscode-input-background) !important;
}

.interactive-session .interactive-input-and-execute-toolbar .monaco-editor .cursors-layer {
.interactive-session .chat-editor-container .monaco-editor .cursors-layer {
padding-left: 4px;
}

.interactive-session .interactive-input-part .interactive-execute-toolbar {
height: 22px;

/* It's bottom-aligned, make it appear centered within the container */
margin-bottom: 7px;
.interactive-session .chat-input-toolbars {
display: flex;
}

.interactive-session .interactive-input-part .interactive-execute-toolbar .monaco-action-bar {
float: right;
.interactive-session .chat-input-toolbars :first-child {
margin-right: auto;
}

.interactive-session .interactive-input-part .interactive-execute-toolbar .monaco-action-bar .actions-container {
.interactive-session .chat-input-toolbars .monaco-action-bar .actions-container {
display: flex;
gap: 4px;
}

.interactive-session .interactive-input-part .interactive-execute-toolbar .codicon-debug-stop {
.interactive-session .chat-input-toolbars .codicon-debug-stop {
color: var(--vscode-icon-foreground) !important;
}

Expand Down Expand Up @@ -601,7 +595,7 @@ have to be updated for changes to the rules above, or to support more deeply nes

.interactive-session .interactive-input-part {
margin: 0px 16px;
padding: 0 0 12px 0;
padding: 12px 0px;
display: flex;
flex-direction: column;
}
Expand Down Expand Up @@ -741,11 +735,11 @@ have to be updated for changes to the rules above, or to support more deeply nes
margin: 0 3px;
}

.quick-input-widget .interactive-session .interactive-input-part .interactive-execute-toolbar {
.quick-input-widget .interactive-session .interactive-input-part .chat-input-toolbars {
margin-bottom: 1px;
}

.quick-input-widget .interactive-session .interactive-input-and-execute-toolbar {
.quick-input-widget .interactive-session .chat-input-container {
margin: 0;
border-radius: 2px;
padding: 0 4px 0 6px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ export class StartVoiceChatAction extends Action2 {
SpeechToTextInProgress.negate() // disable when speech to text is in progress
),
menu: [{
id: MenuId.ChatExecute,
id: MenuId.ChatInput,
when: ContextKeyExpr.and(
HasSpeechProvider,
ScopedChatSynthesisInProgress.negate(), // hide when text to speech is in progress
Expand Down Expand Up @@ -632,7 +632,7 @@ export class StopListeningAction extends Action2 {
icon: spinningLoading,
precondition: GlobalVoiceChatInProgress, // need global context here because of `f1: true`
menu: [{
id: MenuId.ChatExecute,
id: MenuId.ChatInput,
when: AnyScopedVoiceChatInProgress,
group: 'navigation',
order: -1
Expand Down Expand Up @@ -964,7 +964,7 @@ export class StopReadAloud extends Action2 {
},
menu: [
{
id: MenuId.ChatExecute,
id: MenuId.ChatInput,
when: ScopedChatSynthesisInProgress,
group: 'navigation',
order: -1
Expand Down Expand Up @@ -1308,7 +1308,7 @@ export class InstallSpeechProviderForVoiceChatAction extends BaseInstallSpeechPr
icon: Codicon.mic,
precondition: InstallingSpeechProvider.negate(),
menu: [{
id: MenuId.ChatExecute,
id: MenuId.ChatInput,
when: HasSpeechProvider.negate(),
group: 'navigation',
order: -1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,24 @@
import { addDisposableListener, Dimension } from '../../../../base/browser/dom.js';
import * as aria from '../../../../base/browser/ui/aria/aria.js';
import { MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
import { isEqual } from '../../../../base/common/resources.js';
import { assertType } from '../../../../base/common/types.js';
import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js';
import { StableEditorBottomScrollState } from '../../../../editor/browser/stableEditorScroll.js';
import { EditorLayoutInfo, EditorOption } from '../../../../editor/common/config/editorOptions.js';
import { Position } from '../../../../editor/common/core/position.js';
import { Range } from '../../../../editor/common/core/range.js';
import { ScrollType } from '../../../../editor/common/editorCommon.js';
import { ZoneWidget } from '../../../../editor/contrib/zoneWidget/browser/zoneWidget.js';
import { localize } from '../../../../nls.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { ACTION_REGENERATE_RESPONSE, ACTION_REPORT_ISSUE, ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, EditMode, InlineChatConfigKeys, MENU_INLINE_CHAT_WIDGET_SECONDARY, MENU_INLINE_CHAT_WIDGET_STATUS } from '../common/inlineChat.js';
import { EditorBasedInlineChatWidget } from './inlineChatWidget.js';
import { isEqual } from '../../../../base/common/resources.js';
import { StableEditorBottomScrollState } from '../../../../editor/browser/stableEditorScroll.js';
import { ScrollType } from '../../../../editor/common/editorCommon.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { ILogService } from '../../../../platform/log/common/log.js';
import { IChatWidgetLocationOptions } from '../../chat/browser/chatWidget.js';
import { MenuId } from '../../../../platform/actions/common/actions.js';
import { isResponseVM } from '../../chat/common/chatViewModel.js';
import { ACTION_REGENERATE_RESPONSE, ACTION_REPORT_ISSUE, ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, EditMode, InlineChatConfigKeys, MENU_INLINE_CHAT_WIDGET_SECONDARY, MENU_INLINE_CHAT_WIDGET_STATUS } from '../common/inlineChat.js';
import { EditorBasedInlineChatWidget } from './inlineChatWidget.js';

export class InlineChatZoneWidget extends ZoneWidget {

Expand Down Expand Up @@ -66,7 +65,6 @@ export class InlineChatZoneWidget extends ZoneWidget {
secondaryMenuId: MENU_INLINE_CHAT_WIDGET_SECONDARY,
chatWidgetViewOptions: {
menus: {
executeToolbar: MenuId.ChatExecute,
telemetrySource: 'interactiveEditorWidget-toolbar',
},
rendererOptions: {
Expand Down

0 comments on commit 3be54cc

Please sign in to comment.