Move rerun request back to prime spot, return with intent detection back as link (#212775)

* Bring back "Rerun Request" to its prominent spot

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

* move rerun without gesture back into title

fixes https://github.com/microsoft/vscode-copilot/issues/5275
This commit is contained in:
Johannes Rieken 2024-05-15 09:31:04 +02:00 committed by GitHub
parent 5e68ffd760
commit 7680db2a11
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 68 additions and 97 deletions

View file

@ -8,15 +8,14 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { marked } from 'vs/base/common/marked/marked';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { localize, localize2 } from 'vs/nls';
import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions';
import { localize2 } from 'vs/nls';
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { ChatTreeItem, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents';
import { CONTEXT_CHAT_LOCATION, CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_DETECTED_AGENT_COMMAND, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { IChatService, ChatAgentVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
import { isRequestVM, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
@ -254,89 +253,6 @@ export function registerChatTitleActions() {
}
}
});
const rerunMenu = MenuId.for('ChatMessageTitle#Rerun');
MenuRegistry.appendMenuItem(MenuId.ChatMessageTitle, {
submenu: rerunMenu,
title: localize('reunmenu', "Rerun..."),
icon: Codicon.refresh,
group: 'navigation',
order: -10,
when: ContextKeyExpr.and(CONTEXT_RESPONSE, CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Editor)) // TODO@jrieken needs extension adoption
});
registerAction2(class RerunAction extends Action2 {
constructor() {
super({
id: 'workbench.action.chat.rerun',
title: localize2('chat.rerun.label', "Rerun Request"),
f1: false,
category: CHAT_CATEGORY,
icon: Codicon.refresh,
precondition: CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Editor), // TODO@jrieken needs extension adoption
menu: {
id: rerunMenu,
group: 'navigation',
order: -1,
}
});
}
async run(accessor: ServicesAccessor, ...args: [ChatTreeItem | unknown]) {
const chatWidgetService = accessor.get(IChatWidgetService);
const chatService = accessor.get(IChatService);
const widget = chatWidgetService.lastFocusedWidget;
let item = args[0];
if (!isResponseVM(item)) {
item = widget?.getFocus();
}
if (!isResponseVM(item) || !widget) {
return;
}
const request = chatService.getSession(item.sessionId)?.getRequests().find(candidate => candidate.id === item.requestId);
if (request) {
await chatService.resendRequest(request, { noCommandDetection: false, attempt: request.attempt + 1, location: widget.location });
}
}
});
registerAction2(class RerunWithoutCommandDetectionAction extends Action2 {
constructor() {
super({
id: 'workbench.action.chat.rerunWithoutCommandDetection',
title: localize2('chat.rerunWithoutCommandDetection.label', "Rerun without Command Detection"),
f1: false,
category: CHAT_CATEGORY,
icon: Codicon.refresh,
precondition: CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Editor), // TODO@jrieken needs extension adoption
menu: {
when: CONTEXT_RESPONSE_DETECTED_AGENT_COMMAND,
id: rerunMenu,
group: 'navigation',
order: -1,
}
});
}
async run(accessor: ServicesAccessor, ...args: any[]) {
const chatWidgetService = accessor.get(IChatWidgetService);
const chatService = accessor.get(IChatService);
const widget = chatWidgetService.lastFocusedWidget;
let item = args[0];
if (!isResponseVM(item)) {
item = widget?.getFocus();
}
if (!isResponseVM(item) || !widget) {
return;
}
const request = chatService.getSession(item.sessionId)?.getRequests().find(candidate => candidate.id === item.requestId);
if (request) {
await chatService.resendRequest(request, { noCommandDetection: true, attempt: request.attempt, location: widget.location });
}
}
});
}
interface MarkdownContent {

View file

@ -78,6 +78,7 @@ import { ITrustedDomainService } from 'vs/workbench/contrib/url/browser/trustedD
import { IMarkdownVulnerability, annotateSpecialMarkdownContent } from '../common/annotations';
import { CodeBlockModelCollection } from '../common/codeBlockModelCollection';
import { IChatListItemRendererOptions } from './chat';
import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer';
const $ = dom.$;
@ -124,6 +125,9 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
protected readonly _onDidClickFollowup = this._register(new Emitter<IChatFollowup>());
readonly onDidClickFollowup: Event<IChatFollowup> = this._onDidClickFollowup.event;
private readonly _onDidClickRerunWithAgentOrCommandDetection = new Emitter<IChatResponseViewModel>();
readonly onDidClickRerunWithAgentOrCommandDetection: Event<IChatResponseViewModel> = this._onDidClickRerunWithAgentOrCommandDetection.event;
protected readonly _onDidChangeItemHeight = this._register(new Emitter<IItemHeightChangeParams>());
readonly onDidChangeItemHeight: Event<IItemHeightChangeParams> = this._onDidChangeItemHeight.event;
@ -396,19 +400,31 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
}
private _renderDetail(element: IChatResponseViewModel, templateData: IChatListItemTemplate): void {
let progressMsg: string = '';
dom.clearNode(templateData.detail);
if (element.slashCommand && element.agentOrSlashCommandDetected) {
let msg: string = '';
const usingMsg = `${chatSubcommandLeader}${element.slashCommand.name}`;
if (element.isComplete) {
progressMsg = localize('usedAgent', "used {0}", usingMsg);
msg = localize('usedAgent', "used {0} [[(rerun without)]]", usingMsg);
} else {
progressMsg = localize('usingAgent', "using {0}", usingMsg);
msg = localize('usingAgent', "using {0}", usingMsg);
}
} else if (!element.isComplete) {
progressMsg = GeneratingPhrase;
}
dom.reset(templateData.detail, renderFormattedText(msg, {
className: 'agentOrSlashCommandDetected',
inline: true,
actionHandler: {
disposables: templateData.elementDisposables,
callback: (content) => {
this._onDidClickRerunWithAgentOrCommandDetection.fire(element);
},
}
}));
templateData.detail.textContent = progressMsg;
} else if (!element.isComplete) {
templateData.detail.textContent = GeneratingPhrase;
}
}
private renderAvatar(element: ChatTreeItem, templateData: IChatListItemTemplate): void {

View file

@ -436,6 +436,12 @@ export class ChatWidget extends Disposable implements IChatWidget {
// is this used anymore?
this.acceptInput(item.message);
}));
this._register(this.renderer.onDidClickRerunWithAgentOrCommandDetection(item => {
const request = this.chatService.getSession(item.sessionId)?.getRequests().find(candidate => candidate.id === item.requestId);
if (request) {
this.chatService.resendRequest(request, { noCommandDetection: true, attempt: request.attempt, location: this.location }).catch(e => this.logService.error('FAILED to rerun request', e));
}
}));
this.tree = <WorkbenchObjectTree<ChatTreeItem>>scopedInstantiationService.createInstance(
WorkbenchObjectTree,

View file

@ -53,6 +53,11 @@
color: var(--vscode-descriptionForeground);
}
.interactive-item-container .header .detail-container .detail .agentOrSlashCommandDetected A {
cursor: pointer;
color: var(--vscode-textLink-foreground);
}
.interactive-item-container .chat-animated-ellipsis {
display: inline-block;
width: 11px;

View file

@ -40,6 +40,7 @@ registerAction2(InlineChatActions.DiscardHunkAction);
registerAction2(InlineChatActions.DiscardAction);
registerAction2(InlineChatActions.DiscardToClipboardAction);
registerAction2(InlineChatActions.DiscardUndoToNewFileAction);
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_PROVIDER, 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_DISCARD, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_EDIT_MODE, EditMode, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_DID_EDIT, 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 } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER, 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_DISCARD, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_EDIT_MODE, EditMode, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_DID_EDIT, 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 { localize, localize2 } from 'vs/nls';
import { Action2, IAction2Options, MenuRegistry } from 'vs/platform/actions/common/actions';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
@ -29,6 +29,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands';
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';
CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start');
CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES);
@ -356,7 +357,8 @@ export class ToggleDiffForChange extends AbstractInlineChatAction {
{
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)
when: ContextKeyExpr.and(CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live), CTX_INLINE_CHAT_CHANGE_HAS_DIFF),
order: 10,
}
]
});
@ -564,3 +566,28 @@ export class ViewInChatAction extends AbstractInlineChatAction {
}
}
export class RerunAction extends AbstractInlineChatAction {
constructor() {
super({
id: ACTION_REGENERATE_RESPONSE,
title: localize2('chat.rerun.label', "Rerun Request"),
f1: false,
icon: Codicon.refresh,
menu: {
id: MENU_INLINE_CHAT_WIDGET_STATUS,
group: '0_main',
order: 5,
}
});
}
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 });
}
}
}