mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 21:09:43 +00:00
Inline chat fixes (#208383)
* - Allow to set overflowWidgetDomNode for chat widget's input editor - tweak z-index for inline chat content widget fixes https://github.com/microsoft/vscode/issues/208375 * set default/dummy model while chat is getting ready fixes https://github.com/microsoft/vscode/issues/208377 * restore decoration highlights for commands, tweak command suggestions, leave some debt for agents fixes https://github.com/microsoft/vscode/issues/208379
This commit is contained in:
parent
85eea4a9b2
commit
fcb8417ba3
|
@ -98,6 +98,7 @@ export interface IChatWidgetViewOptions {
|
|||
inputSideToolbar?: MenuId;
|
||||
telemetrySource?: string;
|
||||
};
|
||||
editorOverflowWidgetsDomNode?: HTMLElement;
|
||||
}
|
||||
|
||||
export interface IChatViewViewContext {
|
||||
|
|
|
@ -47,11 +47,23 @@ import { IChatFollowup } from 'vs/workbench/contrib/chat/common/chatService';
|
|||
import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel';
|
||||
import { IChatHistoryEntry, IChatWidgetHistoryService } from 'vs/workbench/contrib/chat/common/chatWidgetHistoryService';
|
||||
import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
|
||||
import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
const INPUT_EDITOR_MAX_HEIGHT = 250;
|
||||
|
||||
interface IChatInputPartOptions {
|
||||
renderFollowups: boolean;
|
||||
renderStyle?: 'default' | 'compact';
|
||||
menus: {
|
||||
executeToolbar: MenuId;
|
||||
inputSideToolbar?: MenuId;
|
||||
telemetrySource?: string;
|
||||
};
|
||||
editorOverflowWidgetsDomNode?: HTMLElement;
|
||||
}
|
||||
|
||||
export class ChatInputPart extends Disposable implements IHistoryNavigationWidget {
|
||||
static readonly INPUT_SCHEME = 'chatSessionInput';
|
||||
private static _counter = 0;
|
||||
|
@ -120,11 +132,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
|||
constructor(
|
||||
// private readonly editorOptions: ChatEditorOptions, // TODO this should be used
|
||||
private readonly location: ChatAgentLocation,
|
||||
private readonly options: {
|
||||
renderFollowups: boolean;
|
||||
renderStyle?: 'default' | 'compact';
|
||||
menus: { executeToolbar: MenuId; inputSideToolbar?: MenuId; telemetrySource?: string };
|
||||
},
|
||||
private readonly options: IChatInputPartOptions,
|
||||
@IChatWidgetHistoryService private readonly historyService: IChatWidgetHistoryService,
|
||||
@IModelService private readonly modelService: IModelService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
|
@ -273,7 +281,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
|||
this.historyNavigationBackwardsEnablement = historyNavigationBackwardsEnablement;
|
||||
this.historyNavigationForewardsEnablement = historyNavigationForwardsEnablement;
|
||||
|
||||
const options = getSimpleEditorOptions(this.configurationService);
|
||||
const options: IEditorConstructionOptions = getSimpleEditorOptions(this.configurationService);
|
||||
options.overflowWidgetsDomNode = this.options.editorOverflowWidgetsDomNode;
|
||||
options.readOnly = false;
|
||||
options.ariaLabel = this._getAriaLabel();
|
||||
options.fontFamily = DEFAULT_FONT_FAMILY;
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.chat-slash-command-content-widget {
|
||||
background-color: var(--vscode-chat-slashCommandBackground);
|
||||
color: var(--vscode-chat-slashCommandForeground);
|
||||
border-radius: 3px;
|
||||
padding: 1px;
|
||||
}
|
|
@ -1,96 +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 'vs/css!./chatSlashCommandContentWidget';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget } from 'vs/editor/browser/editorBrowser';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { localize } from 'vs/nls';
|
||||
import * as aria from 'vs/base/browser/ui/aria/aria';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
|
||||
export class SlashCommandContentWidget extends Disposable implements IContentWidget {
|
||||
private _domNode = document.createElement('div');
|
||||
private _lastSlashCommandText: string | undefined;
|
||||
private _isVisible = false;
|
||||
|
||||
constructor(private _editor: ICodeEditor) {
|
||||
super();
|
||||
|
||||
this._domNode.toggleAttribute('hidden', true);
|
||||
this._domNode.classList.add('chat-slash-command-content-widget');
|
||||
|
||||
// If backspace at a slash command boundary, remove the slash command
|
||||
this._register(this._editor.onKeyDown((e) => this._handleKeyDown(e)));
|
||||
}
|
||||
|
||||
override dispose() {
|
||||
this.hide();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
show() {
|
||||
if (!this._isVisible) {
|
||||
this._isVisible = true;
|
||||
this._domNode.toggleAttribute('hidden', false);
|
||||
this._editor.addContentWidget(this);
|
||||
}
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (this._isVisible) {
|
||||
this._isVisible = false;
|
||||
this._domNode.toggleAttribute('hidden', true);
|
||||
this._editor.removeContentWidget(this);
|
||||
}
|
||||
}
|
||||
|
||||
setCommandText(slashCommand: string) {
|
||||
this._domNode.innerText = `/${slashCommand} `;
|
||||
this._lastSlashCommandText = slashCommand;
|
||||
}
|
||||
|
||||
getId() {
|
||||
return 'chat-slash-command-content-widget';
|
||||
}
|
||||
|
||||
getDomNode() {
|
||||
return this._domNode;
|
||||
}
|
||||
|
||||
getPosition() {
|
||||
return { position: { lineNumber: 1, column: 1 }, preference: [ContentWidgetPositionPreference.EXACT] };
|
||||
}
|
||||
|
||||
beforeRender(): null {
|
||||
const lineHeight = this._editor.getOption(EditorOption.lineHeight);
|
||||
this._domNode.style.lineHeight = `${lineHeight - 2 /*padding*/}px`;
|
||||
return null;
|
||||
}
|
||||
|
||||
private _handleKeyDown(e: IKeyboardEvent) {
|
||||
if (e.keyCode !== KeyCode.Backspace) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstLine = this._editor.getModel()?.getLineContent(1);
|
||||
const selection = this._editor.getSelection();
|
||||
const withSlash = `/${this._lastSlashCommandText} `;
|
||||
if (!firstLine?.startsWith(withSlash) || !selection?.isEmpty() || selection?.startLineNumber !== 1 || selection?.startColumn !== withSlash.length + 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow to undo the backspace
|
||||
this._editor.executeEdits('chat-slash-command', [{
|
||||
range: new Range(1, 1, 1, selection.startColumn),
|
||||
text: null
|
||||
}]);
|
||||
|
||||
// Announce the deletion
|
||||
aria.alert(localize('exited slash command mode', 'Exited {0} mode', this._lastSlashCommandText));
|
||||
}
|
||||
}
|
|
@ -501,7 +501,8 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
|||
{
|
||||
renderFollowups: options?.renderFollowups ?? true,
|
||||
renderStyle: options?.renderStyle,
|
||||
menus: { executeToolbar: MenuId.ChatExecute, ...this.viewOptions.menus }
|
||||
menus: { executeToolbar: MenuId.ChatExecute, ...this.viewOptions.menus },
|
||||
editorOverflowWidgetsDomNode: this.viewOptions.editorOverflowWidgetsDomNode,
|
||||
}
|
||||
));
|
||||
this.inputPart.render(container, '', this);
|
||||
|
|
|
@ -18,6 +18,7 @@ import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessi
|
|||
import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget';
|
||||
import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||
import { editorBackground, editorForeground, inputBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||
|
||||
export class InlineChatContentWidget implements IContentWidget {
|
||||
|
||||
|
@ -36,6 +37,8 @@ export class InlineChatContentWidget implements IContentWidget {
|
|||
|
||||
private _visible: boolean = false;
|
||||
private _focusNext: boolean = false;
|
||||
|
||||
private readonly _defaultChatModel: ChatModel;
|
||||
private readonly _widget: ChatWidget;
|
||||
|
||||
constructor(
|
||||
|
@ -43,11 +46,14 @@ export class InlineChatContentWidget implements IContentWidget {
|
|||
@IInstantiationService instaService: IInstantiationService,
|
||||
) {
|
||||
|
||||
this._defaultChatModel = this._store.add(instaService.createInstance(ChatModel, `inlineChatDefaultModel/editorContentWidgetPlaceholder`, undefined));
|
||||
|
||||
this._widget = instaService.createInstance(
|
||||
ChatWidget,
|
||||
ChatAgentLocation.Editor,
|
||||
{ resource: true },
|
||||
{
|
||||
editorOverflowWidgetsDomNode: _editor.getOverflowWidgetsDomNode(),
|
||||
renderStyle: 'compact',
|
||||
renderInputOnTop: true,
|
||||
supportsFileReferences: false,
|
||||
|
@ -66,6 +72,7 @@ export class InlineChatContentWidget implements IContentWidget {
|
|||
this._store.add(this._widget);
|
||||
this._store.add(this._widget.onDidChangeHeight(() => _editor.layoutContentWidget(this)));
|
||||
this._widget.render(this._inputContainer);
|
||||
this._widget.setModel(this._defaultChatModel, {});
|
||||
|
||||
this._domNode.tabIndex = -1;
|
||||
this._domNode.className = 'inline-chat-content-widget interactive-session';
|
||||
|
@ -78,7 +85,9 @@ export class InlineChatContentWidget implements IContentWidget {
|
|||
|
||||
const tracker = dom.trackFocus(this._domNode);
|
||||
this._store.add(tracker.onDidBlur(() => {
|
||||
if (this._visible) {
|
||||
if (this._visible
|
||||
// && !"ON"
|
||||
) {
|
||||
this._onDidBlur.fire();
|
||||
}
|
||||
}));
|
||||
|
|
|
@ -19,7 +19,7 @@ import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
|
|||
import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { ISelection, Selection } from 'vs/editor/common/core/selection';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { IEditorContribution, IEditorDecorationsCollection } from 'vs/editor/common/editorCommon';
|
||||
import { CompletionItemKind, CompletionList, TextEdit } from 'vs/editor/common/languages';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
|
||||
import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController';
|
||||
|
@ -41,7 +41,7 @@ import { InlineChatZoneWidget } from './inlineChatZoneWidget';
|
|||
import { CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_RESPONSE_TYPES, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_VISIBLE, EditMode, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { StashedSession } from './inlineChatSession';
|
||||
import { IValidEditOperation } from 'vs/editor/common/model';
|
||||
import { IModelDeltaDecoration, ITextModel, IValidEditOperation } from 'vs/editor/common/model';
|
||||
import { InlineChatContentWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget';
|
||||
import { MessageController } from 'vs/editor/contrib/message/browser/messageController';
|
||||
import { tail } from 'vs/base/common/arrays';
|
||||
|
@ -432,7 +432,11 @@ export class InlineChatController implements IEditorContribution {
|
|||
}
|
||||
}));
|
||||
|
||||
// TODO@jrieken
|
||||
// Update context key
|
||||
this._ctxSupportIssueReporting.set(this._session.provider.supportIssueReporting ?? false);
|
||||
|
||||
// #region DEBT
|
||||
// DEBT@jrieken
|
||||
// REMOVE when agents are adopted
|
||||
this._sessionStore.add(this._languageFeatureService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, {
|
||||
_debugDisplayName: 'inline chat commands',
|
||||
|
@ -455,14 +459,63 @@ export class InlineChatController implements IEditorContribution {
|
|||
kind: CompletionItemKind.Text,
|
||||
insertText: withSlash,
|
||||
range: Range.fromPositions(new Position(1, 1), position),
|
||||
command: command.executeImmediately ? { id: 'workbench.action.chat.acceptInput', title: withSlash } : undefined
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}));
|
||||
// Update context key
|
||||
this._ctxSupportIssueReporting.set(this._session.provider.supportIssueReporting ?? false);
|
||||
|
||||
const updateSlashDecorations = (collection: IEditorDecorationsCollection, model: ITextModel) => {
|
||||
|
||||
const newDecorations: IModelDeltaDecoration[] = [];
|
||||
for (const command of (this._session?.session.slashCommands ?? []).sort((a, b) => b.command.length - a.command.length)) {
|
||||
const withSlash = `/${command.command}`;
|
||||
const firstLine = model.getLineContent(1);
|
||||
if (firstLine.startsWith(withSlash)) {
|
||||
newDecorations.push({
|
||||
range: new Range(1, 1, 1, withSlash.length + 1),
|
||||
options: {
|
||||
description: 'inline-chat-slash-command',
|
||||
inlineClassName: 'inline-chat-slash-command',
|
||||
after: {
|
||||
// Force some space between slash command and placeholder
|
||||
content: ' '
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// inject detail when otherwise empty
|
||||
if (firstLine.trim() === `/${command.command}`) {
|
||||
newDecorations.push({
|
||||
range: new Range(1, withSlash.length, 1, withSlash.length),
|
||||
options: {
|
||||
description: 'inline-chat-slash-command-detail',
|
||||
after: {
|
||||
content: `${command.detail}`,
|
||||
inlineClassName: 'inline-chat-slash-command-detail'
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
collection.set(newDecorations);
|
||||
};
|
||||
const inputInputEditor = this._input.value.chatWidget.inputEditor;
|
||||
const zoneInputEditor = this._zone.value.widget.chatWidget.inputEditor;
|
||||
const inputDecorations = inputInputEditor.createDecorationsCollection();
|
||||
const zoneDecorations = zoneInputEditor.createDecorationsCollection();
|
||||
this._sessionStore.add(inputInputEditor.onDidChangeModelContent(() => updateSlashDecorations(inputDecorations, inputInputEditor.getModel()!)));
|
||||
this._sessionStore.add(zoneInputEditor.onDidChangeModelContent(() => updateSlashDecorations(zoneDecorations, zoneInputEditor.getModel()!)));
|
||||
this._sessionStore.add(toDisposable(() => {
|
||||
inputDecorations.clear();
|
||||
zoneDecorations.clear();
|
||||
}));
|
||||
|
||||
//#endregion ------- DEBT
|
||||
|
||||
if (!this._session.lastExchange) {
|
||||
return State.WAIT_FOR_INPUT;
|
||||
|
|
|
@ -93,6 +93,8 @@ export interface IInlineChatWidgetConstructionOptions {
|
|||
* globally.
|
||||
*/
|
||||
editableCodeBlocks?: boolean;
|
||||
|
||||
editorOverflowWidgetsDomNode?: HTMLElement;
|
||||
}
|
||||
|
||||
export interface IInlineChatMessage {
|
||||
|
@ -395,9 +397,18 @@ export class InlineChatWidget {
|
|||
this._chatWidget.setInput(value);
|
||||
}
|
||||
|
||||
|
||||
selectAll(includeSlashCommand: boolean = true) {
|
||||
// TODO@jrieken includeSlashCommand
|
||||
this._chatWidget.inputEditor.setSelection(new Selection(1, 1, Number.MAX_SAFE_INTEGER, 1));
|
||||
// DEBT@jrieken
|
||||
// REMOVE when agents are adopted
|
||||
let startColumn = 1;
|
||||
if (!includeSlashCommand) {
|
||||
const match = /^(\/\w+)\s*/.exec(this._chatWidget.inputEditor.getModel()!.getLineContent(1));
|
||||
if (match) {
|
||||
startColumn = match[1].length + 1;
|
||||
}
|
||||
}
|
||||
this._chatWidget.inputEditor.setSelection(new Selection(1, startColumn, Number.MAX_SAFE_INTEGER, 1));
|
||||
}
|
||||
|
||||
set placeholder(value: string) {
|
||||
|
@ -612,7 +623,7 @@ export class EditorBasedInlineChatWidget extends InlineChatWidget {
|
|||
@ITextModelService textModelResolverService: ITextModelService,
|
||||
@IChatService chatService: IChatService,
|
||||
) {
|
||||
super(ChatAgentLocation.Editor, options, instantiationService, contextKeyService, keybindingService, accessibilityService, configurationService, accessibleViewService, textModelResolverService, chatService);
|
||||
super(ChatAgentLocation.Editor, { ...options, editorOverflowWidgetsDomNode: _parentEditor.getOverflowWidgetsDomNode() }, instantiationService, contextKeyService, keybindingService, accessibilityService, configurationService, accessibleViewService, textModelResolverService, chatService);
|
||||
|
||||
// preview editors
|
||||
this._previewDiffEditor = new Lazy(() => this._store.add(instantiationService.createInstance(EmbeddedDiffEditorWidget, this._elements.previewDiff, {
|
||||
|
|
|
@ -266,7 +266,10 @@
|
|||
}
|
||||
|
||||
.monaco-workbench .inline-chat-slash-command {
|
||||
opacity: 0;
|
||||
background-color: var(--vscode-chat-slashCommandBackground);
|
||||
color: var(--vscode-chat-slashCommandForeground);
|
||||
border-radius: 4px;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.monaco-workbench .inline-chat-slash-command-detail {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench .inline-chat-content-widget {
|
||||
z-index: 50;
|
||||
padding: 6px 6px 2px 6px;
|
||||
border-radius: 4px;
|
||||
background-color: var(--vscode-inlineChat-background);
|
||||
|
|
Loading…
Reference in a new issue