Improve slash command rendering in inline chat (#187769)

* Render inline chat slash commands as blocks

* Allow backspace to delete slash command block

* Remove unnecessary template string literal
This commit is contained in:
Joyce Er
2023-07-13 15:18:12 +02:00
committed by GitHub
parent 8e7d6f87e7
commit 930a745d60
2 changed files with 78 additions and 3 deletions
@@ -278,13 +278,20 @@
}
.monaco-editor .inline-chat-slash-command {
color: var(--vscode-textLink-foreground)
opacity: 0;
}
.monaco-editor .inline-chat-slash-command-detail {
opacity: 0.5;
}
.monaco-editor .inline-chat-slash-command-content-widget {
padding: 0 0.4em;
border-radius: 3px;
background-color: var(--vscode-textCodeBlock-background);
color: var(--vscode-textLink-foreground);
}
/* diff zone */
.monaco-editor .inline-chat-diff-widget {
@@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./inlineChat';
import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser';
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { ContentWidgetPositionPreference, IActiveCodeEditor, ICodeEditor, IContentWidget, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser';
import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions';
import { IRange, Range } from 'vs/editor/common/core/range';
import { localize } from 'vs/nls';
@@ -49,6 +49,7 @@ import { ExpansionState } from 'vs/workbench/contrib/inlineChat/browser/inlineCh
import { IdleValue } from 'vs/base/common/async';
import * as aria from 'vs/base/browser/ui/aria/aria';
import { IMenuWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar';
import { KeyCode } from 'vs/base/common/keyCodes';
const defaultAriaLabel = localize('aria-label', "Inline Chat Input");
@@ -180,6 +181,8 @@ export class InlineChatWidget {
private _preferredExpansionState: ExpansionState | undefined;
private _expansionState: ExpansionState = ExpansionState.NOT_CROPPED;
private _slashCommandContentWidget: SlashCommandContentWidget;
constructor(
private readonly parentEditor: ICodeEditor,
@IModelService private readonly _modelService: IModelService,
@@ -278,6 +281,11 @@ export class InlineChatWidget {
this._store.add(this._inputModel.onDidChangeContent(togglePlaceholder));
togglePlaceholder();
// slash command content widget
this._slashCommandContentWidget = new SlashCommandContentWidget(this._inputEditor);
this._store.add(this._slashCommandContentWidget);
// toolbars
const toolbar = this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.editorToolbar, MENU_INLINE_CHAT_WIDGET, {
@@ -654,6 +662,8 @@ export class InlineChatWidget {
const decorations = this._inputEditor.createDecorationsCollection();
const updateSlashDecorations = () => {
this._slashCommandContentWidget.hide();
const newDecorations: IModelDeltaDecoration[] = [];
for (const command of commands) {
const withSlash = `/${command.command}`;
@@ -664,9 +674,16 @@ export class InlineChatWidget {
options: {
description: 'inline-chat-slash-command',
inlineClassName: 'inline-chat-slash-command',
after: {
// Force some space between slash command and placeholder
content: ' '
}
}
});
this._slashCommandContentWidget.setCommandText(command.command);
this._slashCommandContentWidget.show();
// inject detail when otherwise empty
if (firstLine === `/${command.command} `) {
newDecorations.push({
@@ -691,6 +708,57 @@ export class InlineChatWidget {
}
}
class SlashCommandContentWidget extends Disposable implements IContentWidget {
private _domNode = document.createElement('div');
private _lastSlashCommandText: string | undefined;
constructor(private _editor: ICodeEditor) {
super();
this._domNode.toggleAttribute('hidden', true);
this._domNode.classList.add('inline-chat-slash-command-content-widget');
// If backspace at a slash command boundary, remove the slash command
this._register(this._editor.onKeyUp((e) => {
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('inline-chat-slash-command', [{
range: new Range(1, 1, 1, selection.startColumn),
text: null
}]);
}));
}
show() {
this._domNode.toggleAttribute('hidden', false);
this._editor.addContentWidget(this);
}
hide() {
this._domNode.toggleAttribute('hidden', true);
this._editor.removeContentWidget(this);
}
setCommandText(slashCommand: string) {
this._domNode.innerText = `${slashCommand} `;
this._lastSlashCommandText = slashCommand;
}
getId() { return 'inline-chat-slash-command-content-widget'; }
getDomNode() { return this._domNode; }
getPosition() { return { position: { lineNumber: 1, column: 1 }, preference: [ContentWidgetPositionPreference.EXACT] }; }
}
export class InlineChatZoneWidget extends ZoneWidget {
readonly widget: InlineChatWidget;