Skip to content

Commit

Permalink
chat editing: new button (#229988)
Browse files Browse the repository at this point in the history
chat editing: add new button
  • Loading branch information
aeschli authored Sep 27, 2024
1 parent ee05ba8 commit da96c73
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 98 deletions.
54 changes: 52 additions & 2 deletions src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ import { KeybindingWeight } from '../../../../../platform/keybinding/common/keyb
import { ActiveEditorContext } from '../../../../common/contextkeys.js';
import { CHAT_CATEGORY, isChatViewTitleActionContext } from './chatActions.js';
import { clearChatEditor } from './chatClear.js';
import { CHAT_VIEW_ID, IChatWidgetService } from '../chat.js';
import { CHAT_VIEW_ID, EDITS_VIEW_ID, IChatWidgetService } from '../chat.js';
import { ChatEditorInput } from '../chatEditorInput.js';
import { ChatViewPane } from '../chatViewPane.js';
import { CONTEXT_IN_CHAT_SESSION, CONTEXT_CHAT_ENABLED } from '../../common/chatContextKeys.js';
import { CONTEXT_IN_CHAT_SESSION, CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED } from '../../common/chatContextKeys.js';
import { IViewsService } from '../../../../services/views/common/viewsService.js';

export const ACTION_ID_NEW_CHAT = `workbench.action.chat.newChat`;

export const ACTION_ID_NEW_EDIT_SESSION = `workbench.action.chat.newEditSession`;

export function registerNewChatActions() {
registerAction2(class NewChatEditorAction extends Action2 {
constructor() {
Expand Down Expand Up @@ -100,6 +102,54 @@ export function registerNewChatActions() {
}
}
});

registerAction2(class GlobalClearEditsAction extends Action2 {
constructor() {
super({
id: ACTION_ID_NEW_EDIT_SESSION,
title: localize2('chat.newEdits.label', "New Edit Session"),
category: CHAT_CATEGORY,
icon: Codicon.plus,
precondition: ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED),
f1: true,
menu: [{
id: MenuId.ChatContext,
group: 'z_clear'
},
{
id: MenuId.ViewTitle,
when: ContextKeyExpr.equals('view', EDITS_VIEW_ID),
group: 'navigation',
order: -1
}]
});
}

async run(accessor: ServicesAccessor, ...args: any[]) {
const context = args[0];
const accessibilitySignalService = accessor.get(IAccessibilitySignalService);
if (isChatViewTitleActionContext(context)) {
// Is running in the Chat view title
announceChatCleared(accessibilitySignalService);
context.chatView.widget.clear();
context.chatView.widget.focusInput();
} else {
// Is running from f1 or keybinding
const widgetService = accessor.get(IChatWidgetService);
const viewsService = accessor.get(IViewsService);

let widget = widgetService.lastFocusedWidget;
if (!widget) {
const chatView = await viewsService.openView(EDITS_VIEW_ID) as ChatViewPane;
widget = chatView.widget;
}

announceChatCleared(accessibilitySignalService);
widget.clear();
widget.focusInput();
}
}
});
}

function announceChatCleared(accessibilitySignalService: IAccessibilitySignalService): void {
Expand Down
6 changes: 6 additions & 0 deletions src/vs/workbench/contrib/chat/browser/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ export async function showChatView(viewsService: IViewsService): Promise<IChatWi
return (await viewsService.openView<ChatViewPane>(CHAT_VIEW_ID))?.widget;
}

export async function showEditsView(viewsService: IViewsService): Promise<IChatWidget | undefined> {
return (await viewsService.openView<ChatViewPane>(EDITS_VIEW_ID))?.widget;
}

export const IQuickChatService = createDecorator<IQuickChatService>('quickChatService');
export interface IQuickChatService {
readonly _serviceBrand: undefined;
Expand Down Expand Up @@ -197,3 +201,5 @@ export interface IChatCodeBlockContextProviderService {
export const GeneratingPhrase = localize('generating', "Generating");

export const CHAT_VIEW_ID = `workbench.panel.chat.view.${CHAT_PROVIDER_ID}`;

export const EDITS_VIEW_ID = 'workbench.panel.chat.view.edits';
98 changes: 12 additions & 86 deletions src/vs/workbench/contrib/chat/browser/chatEditingService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Sequencer } from '../../../../base/common/async.js';
import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';
import { Codicon } from '../../../../base/common/codicons.js';
import { BugIndicatingError } from '../../../../base/common/errors.js';
import { Emitter } from '../../../../base/common/event.js';
import { Disposable, DisposableStore, IDisposable, IReference, MutableDisposable } from '../../../../base/common/lifecycle.js';
import { Schemas } from '../../../../base/common/network.js';
import { derived, IObservable, ITransaction, observableValue, ValueWithChangeEventFromObservable } from '../../../../base/common/observable.js';
import { URI } from '../../../../base/common/uri.js';
import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js';
import { IBulkEditService } from '../../../../editor/browser/services/bulkEditService.js';
import { TextEdit } from '../../../../editor/common/languages.js';
import { ILanguageService } from '../../../../editor/common/languages/language.js';
Expand All @@ -29,20 +29,14 @@ import { DiffEditorInput } from '../../../common/editor/diffEditorInput.js';
import { EditorInput } from '../../../common/editor/editorInput.js';
import { IEditorGroup, IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js';
import { IEditorService } from '../../../services/editor/common/editorService.js';
import { IViewsService } from '../../../services/views/common/viewsService.js';
import { MultiDiffEditor } from '../../multiDiffEditor/browser/multiDiffEditor.js';
import { MultiDiffEditorInput } from '../../multiDiffEditor/browser/multiDiffEditorInput.js';
import { IMultiDiffSourceResolver, IMultiDiffSourceResolverService, IResolvedMultiDiffSource, MultiDiffEditorItem } from '../../multiDiffEditor/browser/multiDiffSourceResolverService.js';
import { ChatAgentLocation } from '../common/chatAgents.js';
import { CONTEXT_CHAT_EDITING_ENABLED, CONTEXT_CHAT_ENABLED } from '../common/chatContextKeys.js';
import { ICodeMapperResponse, ICodeMapperService } from '../common/chatCodeMapperService.js';
import { ChatEditingSessionState, IChatEditingService, IChatEditingSession, IChatEditingSessionStream, IModifiedFileEntry, ModifiedFileEntryState } from '../common/chatEditingService.js';
import { IChatResponseModel } from '../common/chatModel.js';
import { IChatService } from '../common/chatService.js';
import { IChatVariablesService } from '../common/chatVariables.js';
import { CHAT_CATEGORY } from './actions/chatActions.js';
import { CHAT_VIEW_ID, IChatWidgetService, showChatView } from './chat.js';
import { ICodeMapperResponse, ICodeMapperService } from '../common/chatCodeMapperService.js';
import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';
import { IChatWidgetService } from './chat.js';

const decidedChatEditingResourceContextKey = new RawContextKey<string[]>('decidedChatEditingResource', []);
const chatEditingResourceContextKey = new RawContextKey<string | undefined>('chatEditingResource', undefined);
Expand Down Expand Up @@ -98,7 +92,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
super.dispose();
}

async startOrContinueEditingSession(chatSessionId: string, options?: { silent: boolean }): Promise<void> {
async startOrContinueEditingSession(chatSessionId: string, options?: { silent: boolean }): Promise<IChatEditingSession> {
const session = this._currentSessionObs.get();
if (session) {
if (session.chatSessionId !== chatSessionId) {
Expand All @@ -108,7 +102,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
return this._createEditingSession(chatSessionId, options);
}

private async _createEditingSession(chatSessionId: string, options?: { silent: boolean }): Promise<void> {
private async _createEditingSession(chatSessionId: string, options?: { silent: boolean }): Promise<IChatEditingSession> {
if (this._currentSessionObs.get()) {
throw new BugIndicatingError('Cannot have more than one active editing session');
}
Expand All @@ -131,6 +125,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic

this._currentSessionObs.set(session, undefined);
this._onDidCreateEditingSession.fire(session);
return session;
}

public triggerEditComputation(responseModel: IChatResponseModel): Promise<void> {
Expand Down Expand Up @@ -471,79 +466,6 @@ export class ChatEditingDiscardAllAction extends Action2 {
}
registerAction2(ChatEditingDiscardAllAction);

export class ChatEditingStartSessionAction extends Action2 {
static readonly ID = 'chatEditing.startSession';
static readonly LABEL = localize2('chatEditing.startSession', 'Start Editing Session');

constructor() {
super({
id: ChatEditingStartSessionAction.ID,
title: ChatEditingStartSessionAction.LABEL,
category: CHAT_CATEGORY,
icon: Codicon.edit,
precondition: CONTEXT_CHAT_ENABLED,
f1: true,
menu: [{
id: MenuId.ViewTitle,
when: ContextKeyExpr.and(ContextKeyExpr.equals('view', CHAT_VIEW_ID), CONTEXT_CHAT_EDITING_ENABLED),
group: 'navigation',
order: 1
}]
});
}

async run(accessor: ServicesAccessor, ...args: any[]): Promise<void> {
const textEditorService = accessor.get(IEditorService);
const variablesService = accessor.get(IChatVariablesService);
const chatEditingService = accessor.get(IChatEditingService);
const chatWidgetService = accessor.get(IChatWidgetService);
const viewsService = accessor.get(IViewsService);
const currentEditingSession = chatEditingService.currentEditingSession;
if (currentEditingSession) {
return;
}

const panelWidgets = chatWidgetService.getWidgetByLocation(ChatAgentLocation.Panel);

const widget = panelWidgets[0] ?? await showChatView(viewsService);
if (!widget?.viewModel) {
return;
}

const visibleTextEditorControls = textEditorService.visibleTextEditorControls.filter((e) => isCodeEditor(e));
visibleTextEditorControls.forEach((e) => {
const activeUri = e.getModel()?.uri;
if (activeUri && [Schemas.file, Schemas.vscodeRemote, Schemas.untitled].includes(activeUri.scheme)) {
variablesService.attachContext('file', { uri: activeUri, }, ChatAgentLocation.Panel);
}
});
await chatEditingService.startOrContinueEditingSession(widget.viewModel.sessionId, { silent: true });
}
}



registerAction2(ChatEditingStartSessionAction);
export class ChatEditingStopSessionAction extends Action2 {
static readonly ID = 'chatEditing.stopSession';
static readonly LABEL = localize2('chatEditing.stopSession', 'Stop Editing Session');

constructor() {
super({
id: ChatEditingStopSessionAction.ID,
title: ChatEditingStopSessionAction.LABEL,
category: CHAT_CATEGORY,
f1: true
});
}

async run(accessor: ServicesAccessor, ...args: any[]): Promise<void> {
const chatEditingService = accessor.get(IChatEditingService);
await chatEditingService.currentEditingSession?.stop();
}
}
registerAction2(ChatEditingStopSessionAction);

export class ChatEditingShowChangesAction extends Action2 {
static readonly ID = 'chatEditing.openDiffs';
static readonly LABEL = localize('chatEditing.openDiffs', 'Open Diffs');
Expand Down Expand Up @@ -727,7 +649,11 @@ class ChatEditingSession extends Disposable implements IChatEditingSession {
}));
}));

this.dispose();
if (this._state.get() !== ChatEditingSessionState.Disposed) {
// session got disposed while we were closing editors
this.dispose();
}

}

override dispose() {
Expand Down
29 changes: 20 additions & 9 deletions src/vs/workbench/contrib/chat/browser/chatWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { IThemeService } from '../../../../platform/theme/common/themeService.js
import { TerminalChatController } from '../../terminal/terminalContribExports.js';
import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentService, IChatWelcomeMessageContent, isChatWelcomeMessageContent } from '../common/chatAgents.js';
import { CONTEXT_CHAT_INPUT_HAS_AGENT, CONTEXT_CHAT_LOCATION, CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_QUICK_CHAT, CONTEXT_LAST_ITEM_ID, CONTEXT_PARTICIPANT_SUPPORTS_MODEL_PICKER, CONTEXT_RESPONSE_FILTERED } from '../common/chatContextKeys.js';
import { IChatEditingService, IChatEditingSession } from '../common/chatEditingService.js';
import { ChatEditingSessionState, IChatEditingService, IChatEditingSession } from '../common/chatEditingService.js';
import { IChatModel, IChatRequestVariableEntry, IChatResponseModel } from '../common/chatModel.js';
import { ChatRequestAgentPart, IParsedChatRequest, chatAgentLeader, chatSubcommandLeader, formatChatQuestion } from '../common/chatParserTypes.js';
import { ChatRequestParser } from '../common/chatRequestParser.js';
Expand Down Expand Up @@ -271,6 +271,25 @@ export class ChatWidget extends Disposable implements IChatWidget {
this.renderChatEditingSessionState(session);
}));

if (this._location.location === ChatAgentLocation.EditingSession) {
let currentEditSession: IChatEditingSession | undefined = undefined;
this._register(this.onDidChangeViewModel(async () => {

const sessionId = this._viewModel?.sessionId;
if (sessionId !== currentEditSession?.chatSessionId) {
if (currentEditSession && (currentEditSession.state.get() !== ChatEditingSessionState.Disposed)) {
await currentEditSession.stop();
}
if (sessionId) {
currentEditSession = await this.chatEditingService.startOrContinueEditingSession(sessionId, { silent: true });
} else {
currentEditSession = undefined;
}
}

}));
}

this._register(codeEditorService.registerCodeEditorOpenHandler(async (input: ITextResourceEditorInput, _source: ICodeEditor | null, _sideBySide?: boolean): Promise<ICodeEditor | null> => {
const resource = input.resource;
if (resource.scheme !== Schemas.vscodeChatCodeBlock) {
Expand Down Expand Up @@ -808,14 +827,6 @@ export class ChatWidget extends Disposable implements IChatWidget {
}
}));

if (this._location.location === ChatAgentLocation.EditingSession) {
const currentSession = this.chatEditingService.currentEditingSession;
if (currentSession && (currentSession.chatSessionId !== model.sessionId)) {
currentSession?.stop();
}
this.chatEditingService.startOrContinueEditingSession(model.sessionId);
}

if (this.tree) {
this.onDidChangeItems();
revealLastElement(this.tree);
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/contrib/chat/common/chatEditingService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface IChatEditingService {

readonly currentEditingSession: IChatEditingSession | null;

startOrContinueEditingSession(chatSessionId: string, options?: { silent: boolean }): Promise<void>;
startOrContinueEditingSession(chatSessionId: string, options?: { silent: boolean }): Promise<IChatEditingSession>;
triggerEditComputation(responseModel: IChatResponseModel): Promise<void>;
}

Expand Down

0 comments on commit da96c73

Please sign in to comment.