mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-23 01:58:53 +01:00
more inline chat controller alignment (#239785)
* remove `InlineChatController#onWillStartSession` * more inline chat controller alignment * more inline chat controller alignment * fix compile errors
This commit is contained in:
@@ -30,7 +30,7 @@ export class InlineChatAccessibleView implements IAccessibleViewImplementation {
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
const responseContent = controller?.getMessage();
|
||||
const responseContent = controller.widget.responseContent;
|
||||
if (!responseContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ import { HunkInformation, Session, StashedSession } from './inlineChatSession.js
|
||||
import { IInlineChatSessionService } from './inlineChatSessionService.js';
|
||||
import { InlineChatError } from './inlineChatSessionServiceImpl.js';
|
||||
import { HunkAction, IEditObserver, LiveStrategy, ProgressingEditsOptions } from './inlineChatStrategies.js';
|
||||
import { EditorBasedInlineChatWidget } from './inlineChatWidget.js';
|
||||
import { InlineChatZoneWidget } from './inlineChatZoneWidget.js';
|
||||
|
||||
export const enum State {
|
||||
@@ -116,10 +117,6 @@ export class InlineChatController implements IEditorContribution {
|
||||
|
||||
private readonly _messages = this._store.add(new Emitter<Message>());
|
||||
protected readonly _onDidEnterState = this._store.add(new Emitter<State>());
|
||||
readonly onDidEnterState = this._onDidEnterState.event;
|
||||
|
||||
private readonly _onWillStartSession = this._store.add(new Emitter<void>());
|
||||
readonly onWillStartSession = this._onWillStartSession.event;
|
||||
|
||||
get chatWidget() {
|
||||
return this._ui.value.widget.chatWidget;
|
||||
@@ -209,7 +206,7 @@ export class InlineChatController implements IEditorContribution {
|
||||
this._store.add(this._inlineChatSessionService.onDidEndSession(e => {
|
||||
if (e.session === this._session && e.endedByExternalCause) {
|
||||
this._log('session ENDED by external cause');
|
||||
this.finishExistingSession();
|
||||
this.acceptSession();
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -242,8 +239,8 @@ export class InlineChatController implements IEditorContribution {
|
||||
}
|
||||
}
|
||||
|
||||
getMessage(): string | undefined {
|
||||
return this._ui.value.widget.responseContent;
|
||||
get widget(): EditorBasedInlineChatWidget {
|
||||
return this._ui.value.widget;
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
@@ -256,9 +253,13 @@ export class InlineChatController implements IEditorContribution {
|
||||
|
||||
private _currentRun?: Promise<void>;
|
||||
|
||||
async run(options: InlineChatRunOptions | undefined = {}): Promise<void> {
|
||||
async run(options: InlineChatRunOptions | undefined = {}): Promise<boolean> {
|
||||
|
||||
let lastState: State | undefined;
|
||||
const d = this._onDidEnterState.event(e => lastState = e);
|
||||
|
||||
try {
|
||||
this.finishExistingSession();
|
||||
this.acceptSession();
|
||||
if (this._currentRun) {
|
||||
await this._currentRun;
|
||||
}
|
||||
@@ -266,7 +267,6 @@ export class InlineChatController implements IEditorContribution {
|
||||
this._editor.setSelection(options.initialSelection);
|
||||
}
|
||||
this._stashedSession.clear();
|
||||
this._onWillStartSession.fire();
|
||||
this._currentRun = this._nextState(State.CREATE_SESSION, options);
|
||||
await this._currentRun;
|
||||
|
||||
@@ -281,7 +281,10 @@ export class InlineChatController implements IEditorContribution {
|
||||
|
||||
} finally {
|
||||
this._currentRun = undefined;
|
||||
d.dispose();
|
||||
}
|
||||
|
||||
return lastState !== State.CANCEL;
|
||||
}
|
||||
|
||||
// ---- state machine
|
||||
@@ -427,7 +430,7 @@ export class InlineChatController implements IEditorContribution {
|
||||
|
||||
if (shouldFinishSession) {
|
||||
this._log('text changed outside of whole range, FINISH session');
|
||||
this.finishExistingSession();
|
||||
this.acceptSession();
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -1077,13 +1080,6 @@ export class InlineChatController implements IEditorContribution {
|
||||
this._messages.fire(Message.CANCEL_SESSION);
|
||||
}
|
||||
|
||||
finishExistingSession(): void {
|
||||
if (this._session) {
|
||||
this._log('finishing existing session, using APPLY');
|
||||
this.acceptSession();
|
||||
}
|
||||
}
|
||||
|
||||
reportIssue() {
|
||||
const response = this._session?.chatModel.lastRequest?.response;
|
||||
if (response) {
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
|
||||
import { CancellationToken } from '../../../../base/common/cancellation.js';
|
||||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { Event } from '../../../../base/common/event.js';
|
||||
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
|
||||
import { Lazy } from '../../../../base/common/lazy.js';
|
||||
import { DisposableStore } from '../../../../base/common/lifecycle.js';
|
||||
import { autorun, autorunWithStore, constObservable, derived, observableFromEvent, observableSignalFromEvent, observableValue, transaction } from '../../../../base/common/observable.js';
|
||||
import { autorun, autorunWithStore, constObservable, derived, IObservable, observableFromEvent, observableSignalFromEvent, observableValue, transaction } from '../../../../base/common/observable.js';
|
||||
import { isEqual } from '../../../../base/common/resources.js';
|
||||
import { assertType } from '../../../../base/common/types.js';
|
||||
import { ICodeEditor, isCodeEditor, isDiffEditor } from '../../../../editor/browser/editorBrowser.js';
|
||||
@@ -17,6 +18,7 @@ import { observableCodeEditor } from '../../../../editor/browser/observableCodeE
|
||||
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
|
||||
import { EmbeddedCodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/embeddedCodeEditorWidget.js';
|
||||
import { EmbeddedDiffEditorWidget } from '../../../../editor/browser/widget/diffEditor/embeddedDiffEditorWidget.js';
|
||||
import { Position } from '../../../../editor/common/core/position.js';
|
||||
import { IEditorContribution } from '../../../../editor/common/editorCommon.js';
|
||||
import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js';
|
||||
import { localize, localize2 } from '../../../../nls.js';
|
||||
@@ -55,6 +57,8 @@ export class InlineChatController2 implements IEditorContribution {
|
||||
private readonly _isActiveController = observableValue(this, false);
|
||||
private readonly _zone: Lazy<InlineChatZoneWidget>;
|
||||
|
||||
private readonly _currentSession: IObservable<IInlineChatSession2 | undefined>;
|
||||
|
||||
get widget(): EditorBasedInlineChatWidget {
|
||||
return this._zone.value.widget;
|
||||
}
|
||||
@@ -63,7 +67,7 @@ export class InlineChatController2 implements IEditorContribution {
|
||||
private readonly _editor: ICodeEditor,
|
||||
@IInstantiationService private readonly _instaService: IInstantiationService,
|
||||
@INotebookEditorService private readonly _notebookEditorService: INotebookEditorService,
|
||||
@IInlineChatSessionService inlineChatSessions: IInlineChatSessionService,
|
||||
@IInlineChatSessionService private readonly _inlineChatSessions: IInlineChatSessionService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
) {
|
||||
|
||||
@@ -120,18 +124,18 @@ export class InlineChatController2 implements IEditorContribution {
|
||||
|
||||
const editorObs = observableCodeEditor(_editor);
|
||||
|
||||
const sessionsSignal = observableSignalFromEvent(this, inlineChatSessions.onDidChangeSessions);
|
||||
const sessionsSignal = observableSignalFromEvent(this, _inlineChatSessions.onDidChangeSessions);
|
||||
|
||||
const sessionObs = derived(r => {
|
||||
this._currentSession = derived(r => {
|
||||
sessionsSignal.read(r);
|
||||
const model = editorObs.model.read(r);
|
||||
const value = model && inlineChatSessions.getSession2(model.uri);
|
||||
const value = model && _inlineChatSessions.getSession2(model.uri);
|
||||
return value ?? undefined;
|
||||
});
|
||||
|
||||
|
||||
this._store.add(autorunWithStore((r, store) => {
|
||||
const session = sessionObs.read(r);
|
||||
const session = this._currentSession.read(r);
|
||||
|
||||
if (!session) {
|
||||
ctxHasSession.set(undefined);
|
||||
@@ -147,7 +151,7 @@ export class InlineChatController2 implements IEditorContribution {
|
||||
|
||||
this._store.add(autorunWithStore((r, store) => {
|
||||
|
||||
const session = sessionObs.read(r);
|
||||
const session = this._currentSession.read(r);
|
||||
const isActive = this._isActiveController.read(r);
|
||||
|
||||
if (!session || !isActive) {
|
||||
@@ -198,7 +202,7 @@ export class InlineChatController2 implements IEditorContribution {
|
||||
this._store.add(autorun(r => {
|
||||
|
||||
const overlay = ChatEditorOverlayController.get(_editor)!;
|
||||
const session = sessionObs.read(r);
|
||||
const session = this._currentSession.read(r);
|
||||
const model = editorObs.model.read(r);
|
||||
if (!session || !model) {
|
||||
overlay.hide();
|
||||
@@ -229,13 +233,52 @@ export class InlineChatController2 implements IEditorContribution {
|
||||
this._showWidgetOverrideObs.set(!value, undefined);
|
||||
}
|
||||
|
||||
markActiveController() {
|
||||
this._isActiveController.set(true, undefined);
|
||||
|
||||
getWidgetPosition(): Position | undefined {
|
||||
return this._zone.rawValue?.position;
|
||||
}
|
||||
|
||||
focus() {
|
||||
this._zone.rawValue?.widget.focus();
|
||||
}
|
||||
|
||||
markActiveController() {
|
||||
this._isActiveController.set(true, undefined);
|
||||
}
|
||||
|
||||
async run(arg?: InlineChatRunOptions): Promise<boolean> {
|
||||
assertType(this._editor.hasModel());
|
||||
|
||||
const uri = this._editor.getModel().uri;
|
||||
const session = await this._inlineChatSessions.createSession2(this._editor, uri, CancellationToken.None);
|
||||
|
||||
this.markActiveController();
|
||||
|
||||
if (arg && InlineChatRunOptions.isInlineChatRunOptions(arg)) {
|
||||
if (arg.initialRange) {
|
||||
this._editor.revealRange(arg.initialRange);
|
||||
}
|
||||
if (arg.initialSelection) {
|
||||
this._editor.setSelection(arg.initialSelection);
|
||||
}
|
||||
if (arg.message) {
|
||||
this._zone.value.widget.chatWidget.setInput(arg.message);
|
||||
if (arg.autoSend) {
|
||||
await this._zone.value.widget.chatWidget.acceptInput();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await Event.toPromise(session.editingSession.onDidDispose);
|
||||
|
||||
const rejected = session.editingSession.getEntry(uri)?.state.get() === WorkingSetEntryState.Rejected;
|
||||
return !rejected;
|
||||
}
|
||||
|
||||
acceptSession() {
|
||||
const value = this._currentSession.get();
|
||||
value?.editingSession.accept();
|
||||
}
|
||||
}
|
||||
|
||||
export class StartSessionAction2 extends EditorAction2 {
|
||||
@@ -268,35 +311,8 @@ export class StartSessionAction2 extends EditorAction2 {
|
||||
}
|
||||
|
||||
override async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) {
|
||||
const inlineChatSessions = accessor.get(IInlineChatSessionService);
|
||||
if (!editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
const ctrl = InlineChatController2.get(editor);
|
||||
if (!ctrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const textModel = editor.getModel();
|
||||
await inlineChatSessions.createSession2(editor, textModel.uri, CancellationToken.None);
|
||||
|
||||
ctrl.markActiveController();
|
||||
|
||||
const arg = args[0];
|
||||
if (arg && InlineChatRunOptions.isInlineChatRunOptions(arg)) {
|
||||
if (arg.initialRange) {
|
||||
editor.revealRange(arg.initialRange);
|
||||
}
|
||||
if (arg.initialSelection) {
|
||||
editor.setSelection(arg.initialSelection);
|
||||
}
|
||||
if (arg.message) {
|
||||
ctrl.widget.chatWidget.setInput(arg.message);
|
||||
if (arg.autoSend) {
|
||||
await ctrl.widget.chatWidget.acceptInput();
|
||||
}
|
||||
}
|
||||
}
|
||||
ctrl?.run();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { ICodeEditor, MouseTargetType } from '../../../../editor/browser/editorB
|
||||
import { IEditorContribution } from '../../../../editor/common/editorCommon.js';
|
||||
import { localize, localize2 } from '../../../../nls.js';
|
||||
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { InlineChatController, State } from './inlineChatController.js';
|
||||
import { InlineChatController } from './inlineChatController.js';
|
||||
import { ACTION_START, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_VISIBLE, InlineChatConfigKeys } from '../common/inlineChat.js';
|
||||
import { EditorAction2, ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
|
||||
import { EditOperation } from '../../../../editor/common/core/editOperation.js';
|
||||
@@ -83,22 +83,14 @@ export class InlineChatExpandLineAction extends EditorAction2 {
|
||||
return null;
|
||||
});
|
||||
|
||||
let lastState: State | undefined;
|
||||
const d = ctrl.onDidEnterState(e => lastState = e);
|
||||
// trigger chat
|
||||
const accepted = await ctrl.run({
|
||||
autoSend: true,
|
||||
message: lineContent.trim(),
|
||||
position: new Position(lineNumber, startColumn)
|
||||
});
|
||||
|
||||
try {
|
||||
// trigger chat
|
||||
await ctrl.run({
|
||||
autoSend: true,
|
||||
message: lineContent.trim(),
|
||||
position: new Position(lineNumber, startColumn)
|
||||
});
|
||||
|
||||
} finally {
|
||||
d.dispose();
|
||||
}
|
||||
|
||||
if (lastState === State.CANCEL) {
|
||||
if (!accepted) {
|
||||
model.pushEditOperations(null, undoEdits, () => null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ export class InlineChatNotebookContribution {
|
||||
// cancel all sibling sessions
|
||||
for (const editor of editors) {
|
||||
if (editor !== newSessionEditor) {
|
||||
InlineChatController.get(editor)?.finishExistingSession();
|
||||
InlineChatController.get(editor)?.acceptSession();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -350,9 +350,18 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
|
||||
store.add(chatModel);
|
||||
|
||||
store.add(autorun(r => {
|
||||
const entry = editingSession.readEntry(uri, r);
|
||||
const state = entry?.state.read(r);
|
||||
if (state === WorkingSetEntryState.Accepted || state === WorkingSetEntryState.Rejected) {
|
||||
|
||||
const entries = editingSession.entries.read(r);
|
||||
if (entries.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const allSettled = entries.every(entry => {
|
||||
const state = entry.state.read(r);
|
||||
return state === WorkingSetEntryState.Accepted || state === WorkingSetEntryState.Rejected;
|
||||
});
|
||||
|
||||
if (allSettled) {
|
||||
// self terminate
|
||||
store.dispose();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/ac
|
||||
import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';
|
||||
import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js';
|
||||
import { IAction } from '../../../../base/common/actions.js';
|
||||
import { isNonEmptyArray } from '../../../../base/common/arrays.js';
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { DisposableStore, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { constObservable, derived, IObservable, ISettableObservable, observableValue } from '../../../../base/common/observable.js';
|
||||
@@ -424,10 +423,7 @@ export class InlineChatWidget {
|
||||
|
||||
get responseContent(): string | undefined {
|
||||
const requests = this._chatWidget.viewModel?.model.getRequests();
|
||||
if (!isNonEmptyArray(requests)) {
|
||||
return undefined;
|
||||
}
|
||||
return requests.at(-1)?.response?.response.toString();
|
||||
return requests?.at(-1)?.response?.response.toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user