Skip to content

Commit 7123ddb

Browse files
authored
Merge pull request microsoft#187860 from microsoft/dev/joyceerhl/accurate-vicuna
Refactor: share code for chat slash command blocks
2 parents fe22b24 + 6821322 commit 7123ddb

File tree

7 files changed

+95
-129
lines changed

7 files changed

+95
-129
lines changed

src/vs/workbench/contrib/chat/browser/chat.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ export interface IChatWidget {
6060
acceptInput(query?: string): void;
6161
focusLastMessage(): void;
6262
focusInput(): void;
63-
getSlashCommandsSync(): ISlashCommand[] | undefined;
6463
getSlashCommands(): Promise<ISlashCommand[] | undefined>;
6564
getCodeBlockInfoForEditor(uri: URI): IChatCodeBlockInfo | undefined;
6665
getCodeBlockInfosForResponse(response: IChatResponseViewModel): IChatCodeBlockInfo[];
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
.chat-slash-command-content-widget {
7+
padding: 0 0.4em;
8+
border-radius: 3px;
9+
background-color: var(--vscode-textCodeBlock-background);
10+
color: var(--vscode-textLink-foreground);
11+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import 'vs/css!./chatSlashCommandContentWidget';
7+
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
8+
import { Range } from 'vs/editor/common/core/range';
9+
import { Disposable } from 'vs/base/common/lifecycle';
10+
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget } from 'vs/editor/browser/editorBrowser';
11+
import { KeyCode } from 'vs/base/common/keyCodes';
12+
13+
export class SlashCommandContentWidget extends Disposable implements IContentWidget {
14+
private _domNode = document.createElement('div');
15+
private _lastSlashCommandText: string | undefined;
16+
17+
constructor(private _editor: ICodeEditor) {
18+
super();
19+
20+
this._domNode.toggleAttribute('hidden', true);
21+
this._domNode.classList.add('chat-slash-command-content-widget');
22+
23+
// If backspace at a slash command boundary, remove the slash command
24+
this._register(this._editor.onKeyUp((e) => this._handleKeyUp(e)));
25+
}
26+
27+
override dispose() {
28+
this._editor.removeContentWidget(this);
29+
super.dispose();
30+
}
31+
32+
show() {
33+
this._domNode.toggleAttribute('hidden', false);
34+
this._editor.addContentWidget(this);
35+
}
36+
37+
hide() {
38+
this._domNode.toggleAttribute('hidden', true);
39+
this._editor.removeContentWidget(this);
40+
}
41+
42+
setCommandText(slashCommand: string) {
43+
this._domNode.innerText = `${slashCommand} `;
44+
this._lastSlashCommandText = slashCommand;
45+
}
46+
47+
getId() { return 'chat-slash-command-content-widget'; }
48+
getDomNode() { return this._domNode; }
49+
getPosition() { return { position: { lineNumber: 1, column: 1 }, preference: [ContentWidgetPositionPreference.EXACT] }; }
50+
51+
private _handleKeyUp(e: IKeyboardEvent) {
52+
if (e.keyCode !== KeyCode.Backspace) {
53+
return;
54+
}
55+
56+
const firstLine = this._editor.getModel()?.getLineContent(1);
57+
const selection = this._editor.getSelection();
58+
const withSlash = `/${this._lastSlashCommandText}`;
59+
if (!firstLine?.startsWith(withSlash) || !selection?.isEmpty() || selection?.startLineNumber !== 1 || selection?.startColumn !== withSlash.length + 1) {
60+
return;
61+
}
62+
63+
// Allow to undo the backspace
64+
this._editor.executeEdits('inline-chat-slash-command', [{
65+
range: new Range(1, 1, 1, selection.startColumn),
66+
text: null
67+
}]);
68+
}
69+
}

src/vs/workbench/contrib/chat/browser/chatWidget.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,6 @@ export class ChatWidget extends Disposable implements IChatWidget {
219219
}
220220
}
221221

222-
getSlashCommandsSync(): ISlashCommand[] | undefined {
223-
return this.lastSlashCommands;
224-
}
225-
226222
async getSlashCommands(): Promise<ISlashCommand[] | undefined> {
227223
if (!this.viewModel) {
228224
return;

src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts

Lines changed: 12 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,22 @@ import { ITextModel } from 'vs/editor/common/model';
1515
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
1616
import { localize } from 'vs/nls';
1717
import { Registry } from 'vs/platform/registry/common/platform';
18-
import { editorForeground, textCodeBlockBackground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
18+
import { editorForeground } from 'vs/platform/theme/common/colorRegistry';
1919
import { IThemeService } from 'vs/platform/theme/common/themeService';
2020
import { IChatWidget, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
2121
import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget';
2222
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
2323
import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart';
2424
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
25-
import { ContentWidgetPositionPreference, IContentWidget } from 'vs/editor/browser/editorBrowser';
26-
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
27-
import { KeyCode } from 'vs/base/common/keyCodes';
28-
import { Selection } from 'vs/editor/common/core/selection';
25+
import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget';
2926

3027
const decorationDescription = 'chat';
3128
const slashCommandPlaceholderDecorationType = 'chat-session-detail';
3229
const slashCommandTextDecorationType = 'chat-session-text';
33-
const slashCommandContentWidgetId = 'chat-session-content-widget';
3430

3531
class InputEditorDecorations extends Disposable {
3632

37-
private _slashCommandDomNode = document.createElement('div');
38-
private _slashCommandContentWidget: IContentWidget | undefined;
33+
private _slashCommandContentWidget: SlashCommandContentWidget | undefined;
3934
private _previouslyUsedSlashCommands = new Set<string>();
4035

4136
constructor(
@@ -66,7 +61,7 @@ class InputEditorDecorations extends Disposable {
6661

6762
private updateRegisteredDecorationTypes() {
6863
this.codeEditorService.removeDecorationType(slashCommandTextDecorationType);
69-
this.updateInputEditorContentWidgets({ hide: true });
64+
this._slashCommandContentWidget?.hide();
7065
this.codeEditorService.registerDecorationType(decorationDescription, slashCommandTextDecorationType, {
7166
opacity: '0',
7267
after: {
@@ -109,7 +104,7 @@ class InputEditorDecorations extends Disposable {
109104
}
110105
];
111106
this.widget.inputEditor.setDecorationsByType(decorationDescription, slashCommandPlaceholderDecorationType, decoration);
112-
this.updateInputEditorContentWidgets({ hide: true });
107+
this._slashCommandContentWidget?.hide();
113108
return;
114109
}
115110

@@ -142,9 +137,14 @@ class InputEditorDecorations extends Disposable {
142137
}
143138

144139
if (command && inputValue.startsWith(`/${command.command} `)) {
145-
this.updateInputEditorContentWidgets({ command: command.command });
140+
if (!this._slashCommandContentWidget) {
141+
this._slashCommandContentWidget = new SlashCommandContentWidget(this.widget.inputEditor);
142+
this._store.add(this._slashCommandContentWidget);
143+
}
144+
this._slashCommandContentWidget.setCommandText(command.command);
145+
this._slashCommandContentWidget.show();
146146
} else {
147-
this.updateInputEditorContentWidgets({ hide: true });
147+
this._slashCommandContentWidget?.hide();
148148
}
149149

150150
if (command && command.detail) {
@@ -163,40 +163,6 @@ class InputEditorDecorations extends Disposable {
163163
this.widget.inputEditor.setDecorationsByType(decorationDescription, slashCommandTextDecorationType, []);
164164
}
165165
}
166-
167-
private async updateInputEditorContentWidgets(arg: { command: string } | { hide: true }) {
168-
const domNode = this._slashCommandDomNode;
169-
170-
if (this._slashCommandContentWidget && 'hide' in arg) {
171-
domNode.toggleAttribute('hidden', true);
172-
this.widget.inputEditor.removeContentWidget(this._slashCommandContentWidget);
173-
return;
174-
} else if ('command' in arg) {
175-
const theme = this.themeService.getColorTheme();
176-
domNode.style.padding = '0 0.4em';
177-
domNode.style.borderRadius = '3px';
178-
domNode.style.backgroundColor = theme.getColor(textCodeBlockBackground)?.toString() ?? '';
179-
domNode.style.color = theme.getColor(textLinkForeground)?.toString() ?? '';
180-
domNode.innerText = `${arg.command} `;
181-
domNode.toggleAttribute('hidden', false);
182-
183-
this._slashCommandContentWidget = {
184-
getId() { return slashCommandContentWidgetId; },
185-
getDomNode() { return domNode; },
186-
getPosition() {
187-
return {
188-
position: {
189-
lineNumber: 1,
190-
column: 1
191-
},
192-
preference: [ContentWidgetPositionPreference.EXACT]
193-
};
194-
},
195-
};
196-
197-
this.widget.inputEditor.addContentWidget(this._slashCommandContentWidget);
198-
}
199-
}
200166
}
201167

202168
class InputEditorSlashCommandFollowups extends Disposable {
@@ -206,7 +172,6 @@ class InputEditorSlashCommandFollowups extends Disposable {
206172
) {
207173
super();
208174
this._register(this.chatService.onDidSubmitSlashCommand(({ slashCommand, sessionId }) => this.repopulateSlashCommand(slashCommand, sessionId)));
209-
this._register(this.widget.inputEditor.onKeyUp((e) => this.handleKeyUp(e)));
210175
}
211176

212177
private async repopulateSlashCommand(slashCommand: string, sessionId: string) {
@@ -227,22 +192,6 @@ class InputEditorSlashCommandFollowups extends Disposable {
227192

228193
}
229194
}
230-
231-
private handleKeyUp(e: IKeyboardEvent) {
232-
if (e.keyCode !== KeyCode.Backspace) {
233-
return;
234-
}
235-
236-
const value = this.widget.inputEditor.getValue().split(' ')[0];
237-
const currentSelection = this.widget.inputEditor.getSelection();
238-
if (!value.startsWith('/') || !currentSelection?.isEmpty() || currentSelection?.startLineNumber !== 1 || currentSelection?.startColumn !== value.length + 1) {
239-
return;
240-
}
241-
242-
if (this.widget.getSlashCommandsSync()?.find((command) => `/${command.command}` === value)) {
243-
this.widget.inputEditor.executeEdits('chat-input-editor-slash-commands', [{ range: new Range(1, 1, 1, currentSelection.startColumn), text: null }], [new Selection(1, 1, 1, 1)]);
244-
}
245-
}
246195
}
247196

248197
ChatWidget.CONTRIBS.push(InputEditorDecorations, InputEditorSlashCommandFollowups);

src/vs/workbench/contrib/inlineChat/browser/inlineChat.css

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -285,13 +285,6 @@
285285
opacity: 0.5;
286286
}
287287

288-
.monaco-editor .inline-chat-slash-command-content-widget {
289-
padding: 0 0.4em;
290-
border-radius: 3px;
291-
background-color: var(--vscode-textCodeBlock-background);
292-
color: var(--vscode-textLink-foreground);
293-
}
294-
295288
/* diff zone */
296289

297290
.monaco-editor .inline-chat-diff-widget {

src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts

Lines changed: 3 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import 'vs/css!./inlineChat';
7-
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
8-
import { ContentWidgetPositionPreference, IActiveCodeEditor, ICodeEditor, IContentWidget, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser';
7+
import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
8+
import { IActiveCodeEditor, ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser';
99
import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions';
1010
import { IRange, Range } from 'vs/editor/common/core/range';
1111
import { localize } from 'vs/nls';
@@ -49,7 +49,7 @@ import { ExpansionState } from 'vs/workbench/contrib/inlineChat/browser/inlineCh
4949
import { IdleValue } from 'vs/base/common/async';
5050
import * as aria from 'vs/base/browser/ui/aria/aria';
5151
import { IMenuWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar';
52-
import { KeyCode } from 'vs/base/common/keyCodes';
52+
import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget';
5353

5454
const defaultAriaLabel = localize('aria-label', "Inline Chat Input");
5555

@@ -713,57 +713,6 @@ export class InlineChatWidget {
713713
}
714714
}
715715

716-
class SlashCommandContentWidget extends Disposable implements IContentWidget {
717-
private _domNode = document.createElement('div');
718-
private _lastSlashCommandText: string | undefined;
719-
720-
constructor(private _editor: ICodeEditor) {
721-
super();
722-
723-
this._domNode.toggleAttribute('hidden', true);
724-
this._domNode.classList.add('inline-chat-slash-command-content-widget');
725-
726-
// If backspace at a slash command boundary, remove the slash command
727-
this._register(this._editor.onKeyUp((e) => {
728-
if (e.keyCode !== KeyCode.Backspace) {
729-
return;
730-
}
731-
732-
const firstLine = this._editor.getModel()?.getLineContent(1);
733-
const selection = this._editor.getSelection();
734-
const withSlash = `/${this._lastSlashCommandText}`;
735-
if (!firstLine?.startsWith(withSlash) || !selection?.isEmpty() || selection?.startLineNumber !== 1 || selection?.startColumn !== withSlash.length + 1) {
736-
return;
737-
}
738-
739-
// Allow to undo the backspace
740-
this._editor.executeEdits('inline-chat-slash-command', [{
741-
range: new Range(1, 1, 1, selection.startColumn),
742-
text: null
743-
}]);
744-
}));
745-
}
746-
747-
show() {
748-
this._domNode.toggleAttribute('hidden', false);
749-
this._editor.addContentWidget(this);
750-
}
751-
752-
hide() {
753-
this._domNode.toggleAttribute('hidden', true);
754-
this._editor.removeContentWidget(this);
755-
}
756-
757-
setCommandText(slashCommand: string) {
758-
this._domNode.innerText = `${slashCommand} `;
759-
this._lastSlashCommandText = slashCommand;
760-
}
761-
762-
getId() { return 'inline-chat-slash-command-content-widget'; }
763-
getDomNode() { return this._domNode; }
764-
getPosition() { return { position: { lineNumber: 1, column: 1 }, preference: [ContentWidgetPositionPreference.EXACT] }; }
765-
}
766-
767716
export class InlineChatZoneWidget extends ZoneWidget {
768717

769718
readonly widget: InlineChatWidget;

0 commit comments

Comments
 (0)