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:
Johannes Rieken
2025-02-06 12:22:23 +01:00
committed by GitHub
parent 18191a16c8
commit 330e6bfeb9
12 changed files with 114 additions and 106 deletions

View File

@@ -30,7 +30,7 @@ export class InlineChatAccessibleView implements IAccessibleViewImplementation {
if (!controller) {
return;
}
const responseContent = controller?.getMessage();
const responseContent = controller.widget.responseContent;
if (!responseContent) {
return;
}

View File

@@ -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) {

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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();
}