joh/icy manatee (#183917)

* Use idle value for widget of interactive editor controller

https://github.com/microsoft/vscode/issues/183820

* also make preview editors idle values

https://github.com/microsoft/vscode/issues/183820
This commit is contained in:
Johannes Rieken 2023-05-31 13:40:32 +02:00 committed by GitHub
parent 2a8dea3225
commit b3f2337542
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 59 additions and 58 deletions

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { renderMarkdown } from 'vs/base/browser/markdownRenderer';
import { Barrier, raceCancellationError } from 'vs/base/common/async';
import { Barrier, IdleValue, raceCancellationError } from 'vs/base/common/async';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { Emitter, Event } from 'vs/base/common/event';
@ -86,7 +86,7 @@ export class InteractiveEditorController implements IEditorContribution {
private _historyOffset: number = -1;
private readonly _store = new DisposableStore();
private readonly _zone: InteractiveEditorZoneWidget;
private readonly _zone: IdleValue<InteractiveEditorZoneWidget>;
private readonly _ctxHasActiveRequest: IContextKey<boolean>;
private readonly _ctxLastResponseType: IContextKey<undefined | InteractiveEditorResponseType>;
private readonly _ctxDidEdit: IContextKey<boolean>;
@ -118,7 +118,7 @@ export class InteractiveEditorController implements IEditorContribution {
this._ctxDidEdit = CTX_INTERACTIVE_EDITOR_DID_EDIT.bindTo(contextKeyService);
this._ctxLastResponseType = CTX_INTERACTIVE_EDITOR_LAST_RESPONSE_TYPE.bindTo(contextKeyService);
this._ctxLastFeedbackKind = CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK.bindTo(contextKeyService);
this._zone = this._store.add(_instaService.createInstance(InteractiveEditorZoneWidget, this._editor));
this._zone = new IdleValue(() => this._store.add(_instaService.createInstance(InteractiveEditorZoneWidget, this._editor)));
this._store.add(this._editor.onDidChangeModel(async e => {
if (this._activeSession || !e.newModelUrl) {
@ -167,7 +167,7 @@ export class InteractiveEditorController implements IEditorContribution {
}
getWidgetPosition(): Position | undefined {
return this._zone.position;
return this._zone.value.position;
}
async run(options: InteractiveEditorRunOptions | undefined): Promise<void> {
@ -234,14 +234,14 @@ export class InteractiveEditorController implements IEditorContribution {
switch (session.editMode) {
case EditMode.Live:
this._strategy = this._instaService.createInstance(LiveStrategy, session, this._editor, this._zone.widget);
this._strategy = this._instaService.createInstance(LiveStrategy, session, this._editor, this._zone.value.widget);
break;
case EditMode.Preview:
this._strategy = this._instaService.createInstance(PreviewStrategy, session, this._zone.widget);
this._strategy = this._instaService.createInstance(PreviewStrategy, session, this._zone.value.widget);
break;
case EditMode.LivePreview:
default:
this._strategy = this._instaService.createInstance(LivePreviewStrategy, session, this._editor, this._zone.widget);
this._strategy = this._instaService.createInstance(LivePreviewStrategy, session, this._editor, this._zone.value.widget);
break;
}
@ -265,11 +265,11 @@ export class InteractiveEditorController implements IEditorContribution {
}]);
this._sessionStore.add(toDisposable(() => wholeRangeDecoration.clear()));
this._zone.widget.updateSlashCommands(this._activeSession.session.slashCommands ?? []);
this._zone.widget.placeholder = this._getPlaceholderText();
this._zone.widget.value = this._activeSession.lastInput ?? '';
this._zone.widget.updateInfo(this._activeSession.session.message ?? localize('welcome.1', "AI-generated code may be incorrect"));
this._zone.show(this._activeSession.wholeRange.getEndPosition());
this._zone.value.widget.updateSlashCommands(this._activeSession.session.slashCommands ?? []);
this._zone.value.widget.placeholder = this._getPlaceholderText();
this._zone.value.widget.value = this._activeSession.lastInput ?? '';
this._zone.value.widget.updateInfo(this._activeSession.session.message ?? localize('welcome.1', "AI-generated code may be incorrect"));
this._zone.value.show(this._activeSession.wholeRange.getEndPosition());
this._sessionStore.add(this._editor.onDidChangeModel((e) => {
const msg = this._activeSession?.lastExchange
@ -357,12 +357,12 @@ export class InteractiveEditorController implements IEditorContribution {
private async [State.WAIT_FOR_INPUT](options: InteractiveEditorRunOptions | undefined): Promise<State.ACCEPT | State.CANCEL | State.PAUSE | State.WAIT_FOR_INPUT | State.MAKE_REQUEST> {
assertType(this._activeSession);
this._zone.widget.placeholder = this._getPlaceholderText();
this._zone.show(this._activeSession.wholeRange.getEndPosition());
this._zone.value.widget.placeholder = this._getPlaceholderText();
this._zone.value.show(this._activeSession.wholeRange.getEndPosition());
if (options?.message) {
this._zone.widget.value = options?.message;
this._zone.widget.selectAll();
this._zone.value.widget.value = options?.message;
this._zone.value.widget.selectAll();
delete options?.message;
}
@ -382,7 +382,7 @@ export class InteractiveEditorController implements IEditorContribution {
msgListener.dispose();
}
this._zone.widget.selectAll();
this._zone.value.widget.selectAll();
if (message & (Message.CANCEL_INPUT | Message.CANCEL_SESSION)) {
return State.CANCEL;
@ -396,11 +396,11 @@ export class InteractiveEditorController implements IEditorContribution {
return State.PAUSE;
}
if (!this._zone.widget.value) {
if (!this._zone.value.widget.value) {
return State.WAIT_FOR_INPUT;
}
const input = this._zone.widget.value;
const input = this._zone.value.widget.value;
if (!InteractiveEditorController._promptHistory.includes(input)) {
InteractiveEditorController._promptHistory.unshift(input);
@ -437,7 +437,7 @@ export class InteractiveEditorController implements IEditorContribution {
requestCts.cancel();
});
const typeListener = this._zone.widget.onDidChangeInput(() => {
const typeListener = this._zone.value.widget.onDidChangeInput(() => {
requestCts.cancel();
});
@ -454,8 +454,8 @@ export class InteractiveEditorController implements IEditorContribution {
let response: EditResponse | MarkdownResponse | ErrorResponse | EmptyResponse;
let reply: IInteractiveEditorResponse | null | undefined;
try {
this._zone.widget.updateProgress(true);
this._zone.widget.updateInfo(!this._activeSession.lastExchange ? localize('thinking', "Thinking\u2026") : '');
this._zone.value.widget.updateProgress(true);
this._zone.value.widget.updateInfo(!this._activeSession.lastExchange ? localize('thinking', "Thinking\u2026") : '');
this._ctxHasActiveRequest.set(true);
reply = await raceCancellationError(Promise.resolve(task), requestCts.token);
@ -472,8 +472,8 @@ export class InteractiveEditorController implements IEditorContribution {
} finally {
this._ctxHasActiveRequest.set(false);
this._zone.widget.updateProgress(false);
this._zone.widget.updateInfo('');
this._zone.value.widget.updateProgress(false);
this._zone.value.widget.updateInfo('');
this._log('request took', sw.elapsed(), this._activeSession.provider.debugName);
}
@ -500,7 +500,7 @@ export class InteractiveEditorController implements IEditorContribution {
const { response } = this._activeSession.lastExchange!;
if (response instanceof EditResponse) {
// edit response -> complex...
this._zone.widget.updateMarkdownMessage(undefined);
this._zone.value.widget.updateMarkdownMessage(undefined);
const canContinue = this._strategy.checkChanges(response);
if (!canContinue) {
@ -540,27 +540,27 @@ export class InteractiveEditorController implements IEditorContribution {
if (response instanceof EmptyResponse) {
// show status message
this._zone.widget.updateStatus(localize('empty', "No results, please refine your input and try again"), { classes: ['warn'] });
this._zone.value.widget.updateStatus(localize('empty', "No results, please refine your input and try again"), { classes: ['warn'] });
return State.WAIT_FOR_INPUT;
} else if (response instanceof ErrorResponse) {
// show error
if (!response.isCancellation) {
this._zone.widget.updateStatus(response.message, { classes: ['error'] });
this._zone.value.widget.updateStatus(response.message, { classes: ['error'] });
}
} else if (response instanceof MarkdownResponse) {
// clear status, show MD message
const renderedMarkdown = renderMarkdown(response.raw.message, { inline: true });
this._zone.widget.updateStatus('');
this._zone.widget.updateMarkdownMessage(renderedMarkdown.element);
this._zone.widget.updateToolbar(true);
this._zone.widget.updateMarkdownMessageExpansionState(this._activeSession.lastExpansionState);
this._zone.value.widget.updateStatus('');
this._zone.value.widget.updateMarkdownMessage(renderedMarkdown.element);
this._zone.value.widget.updateToolbar(true);
this._zone.value.widget.updateMarkdownMessageExpansionState(this._activeSession.lastExpansionState);
} else if (response instanceof EditResponse) {
// edit response -> complex...
this._zone.widget.updateMarkdownMessage(undefined);
this._zone.widget.updateToolbar(true);
this._zone.value.widget.updateMarkdownMessage(undefined);
this._zone.value.widget.updateToolbar(true);
const canContinue = this._strategy.checkChanges(response);
if (!canContinue) {
@ -586,7 +586,7 @@ export class InteractiveEditorController implements IEditorContribution {
this._ctxLastResponseType.reset();
this._ctxLastFeedbackKind.reset();
this._zone.hide();
this._zone.value.hide();
// Return focus to the editor only if the current focus is within the editor widget
if (this._editor.hasWidgetFocus()) {
@ -627,9 +627,9 @@ export class InteractiveEditorController implements IEditorContribution {
}
arrowOut(up: boolean): void {
if (this._zone.position && this._editor.hasModel()) {
if (this._zone.value.position && this._editor.hasModel()) {
const { column } = this._editor.getPosition();
const { lineNumber } = this._zone.position;
const { lineNumber } = this._zone.value.position;
const newLine = up ? lineNumber : lineNumber + 1;
this._editor.setPosition({ lineNumber: newLine, column });
this._editor.focus();
@ -641,7 +641,7 @@ export class InteractiveEditorController implements IEditorContribution {
}
focus(): void {
this._zone.widget.focus();
this._zone.value.widget.focus();
}
populateHistory(up: boolean) {
@ -652,8 +652,8 @@ export class InteractiveEditorController implements IEditorContribution {
const pos = (len + this._historyOffset + (up ? 1 : -1)) % len;
const entry = InteractiveEditorController._promptHistory[pos];
this._zone.widget.value = entry;
this._zone.widget.selectAll();
this._zone.value.widget.value = entry;
this._zone.value.widget.selectAll();
this._historyOffset = pos;
}
@ -665,7 +665,7 @@ export class InteractiveEditorController implements IEditorContribution {
updateExpansionState(expand: boolean) {
if (this._activeSession) {
this._zone.widget.updateMarkdownMessageExpansionState(expand);
this._zone.value.widget.updateMarkdownMessageExpansionState(expand);
this._activeSession.lastExpansionState = expand;
}
}
@ -675,7 +675,7 @@ export class InteractiveEditorController implements IEditorContribution {
const kind = helpful ? InteractiveEditorResponseFeedbackKind.Helpful : InteractiveEditorResponseFeedbackKind.Unhelpful;
this._activeSession.provider.handleInteractiveEditorResponseFeedback?.(this._activeSession.session, this._activeSession.lastExchange.response.raw, kind);
this._ctxLastFeedbackKind.set(helpful ? 'helpful' : 'unhelpful');
this._zone.widget.updateStatus('Thank you for your feedback!', { resetAfter: 1250 });
this._zone.value.widget.updateStatus('Thank you for your feedback!', { resetAfter: 1250 });
}
}

View file

@ -50,6 +50,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution';
import { assertType } from 'vs/base/common/types';
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { IdleValue } from 'vs/base/common/async';
const defaultAriaLabel = localize('aria-label', "Interactive Editor Input");
@ -160,11 +161,11 @@ export class InteractiveEditorWidget {
private readonly _progressBar: ProgressBar;
private readonly _previewDiffEditor: EmbeddedDiffEditorWidget;
private readonly _previewDiffEditor: IdleValue<EmbeddedDiffEditorWidget>;
private readonly _previewDiffModel = this._store.add(new MutableDisposable());
private readonly _previewCreateTitle: ResourceLabel;
private readonly _previewCreateEditor: ICodeEditor;
private readonly _previewCreateEditor: IdleValue<ICodeEditor>;
private readonly _previewCreateModel = this._store.add(new MutableDisposable());
private readonly _onDidChangeHeight = new MicrotaskEmitter<void>();
@ -327,10 +328,10 @@ export class InteractiveEditorWidget {
this._store.add(feedbackToolbar);
// preview editors
this._previewDiffEditor = this._store.add(_instantiationService.createInstance(EmbeddedDiffEditorWidget, this._elements.previewDiff, _previewEditorEditorOptions, { modifiedEditor: codeEditorWidgetOptions, originalEditor: codeEditorWidgetOptions }, parentEditor));
this._previewDiffEditor = new IdleValue(() => this._store.add(_instantiationService.createInstance(EmbeddedDiffEditorWidget, this._elements.previewDiff, _previewEditorEditorOptions, { modifiedEditor: codeEditorWidgetOptions, originalEditor: codeEditorWidgetOptions }, parentEditor)));
this._previewCreateTitle = this._store.add(_instantiationService.createInstance(ResourceLabel, this._elements.previewCreateTitle, { supportIcons: true }));
this._previewCreateEditor = this._store.add(_instantiationService.createInstance(EmbeddedCodeEditorWidget, this._elements.previewCreate, _previewEditorEditorOptions, codeEditorWidgetOptions, parentEditor));
this._previewCreateEditor = new IdleValue(() => this._store.add(_instantiationService.createInstance(EmbeddedCodeEditorWidget, this._elements.previewCreate, _previewEditorEditorOptions, codeEditorWidgetOptions, parentEditor)));
this._elements.message.tabIndex = 0;
this._elements.statusLabel.tabIndex = 0;
@ -372,12 +373,12 @@ export class InteractiveEditorWidget {
this._inputEditor.layout(new Dimension(innerEditorWidth, this._inputEditor.getContentHeight()));
this._elements.placeholder.style.width = `${innerEditorWidth /* input-padding*/}px`;
const previewDiffDim = new Dimension(dim.width, Math.min(300, Math.max(0, this._previewDiffEditor.getContentHeight())));
this._previewDiffEditor.layout(previewDiffDim);
const previewDiffDim = new Dimension(dim.width, Math.min(300, Math.max(0, this._previewDiffEditor.value.getContentHeight())));
this._previewDiffEditor.value.layout(previewDiffDim);
this._elements.previewDiff.style.height = `${previewDiffDim.height}px`;
const previewCreateDim = new Dimension(dim.width, Math.min(300, Math.max(0, this._previewCreateEditor.getContentHeight())));
this._previewCreateEditor.layout(previewCreateDim);
const previewCreateDim = new Dimension(dim.width, Math.min(300, Math.max(0, this._previewCreateEditor.value.getContentHeight())));
this._previewCreateEditor.value.layout(previewCreateDim);
this._elements.previewCreate.style.height = `${previewCreateDim.height}px`;
}
} finally {
@ -389,9 +390,9 @@ export class InteractiveEditorWidget {
const base = getTotalHeight(this._elements.progress) + getTotalHeight(this._elements.status);
const editorHeight = this._inputEditor.getContentHeight() + 12 /* padding and border */;
const markdownMessageHeight = getTotalHeight(this._elements.markdownMessage);
const previewDiffHeight = this._previewDiffEditor.getModel().modified ? 12 + Math.min(300, Math.max(0, this._previewDiffEditor.getContentHeight())) : 0;
const previewDiffHeight = this._previewDiffEditor.value.getModel().modified ? 12 + Math.min(300, Math.max(0, this._previewDiffEditor.value.getContentHeight())) : 0;
const previewCreateTitleHeight = getTotalHeight(this._elements.previewCreateTitle);
const previewCreateHeight = this._previewCreateEditor.getModel() ? 18 + Math.min(300, Math.max(0, this._previewCreateEditor.getContentHeight())) : 0;
const previewCreateHeight = this._previewCreateEditor.value.getModel() ? 18 + Math.min(300, Math.max(0, this._previewCreateEditor.value.getContentHeight())) : 0;
return base + editorHeight + markdownMessageHeight + previewDiffHeight + previewCreateTitleHeight + previewCreateHeight + 18 /* padding */ + 8 /*shadow*/;
}
@ -508,7 +509,7 @@ export class InteractiveEditorWidget {
const languageSelection: ILanguageSelection = { languageId: textModelv0.getLanguageId(), onDidChange: Event.None };
const modified = this._modelService.createModel(createTextBufferFactoryFromSnapshot(textModelv0.createSnapshot()), languageSelection, undefined, true);
modified.applyEdits(edits, false);
this._previewDiffEditor.setModel({ original: textModelv0, modified });
this._previewDiffEditor.value.setModel({ original: textModelv0, modified });
// joined ranges
let originalLineRange = changes[0].originalRange;
@ -531,16 +532,16 @@ export class InteractiveEditorWidget {
const hiddenOriginal = invertLineRange(originalLineRange, textModelv0);
const hiddenModified = invertLineRange(modifiedLineRange, modified);
this._previewDiffEditor.getOriginalEditor().setHiddenAreas(hiddenOriginal.map(lineRangeAsRange), 'diff-hidden');
this._previewDiffEditor.getModifiedEditor().setHiddenAreas(hiddenModified.map(lineRangeAsRange), 'diff-hidden');
this._previewDiffEditor.revealLine(modifiedLineRange.startLineNumber, ScrollType.Immediate);
this._previewDiffEditor.value.getOriginalEditor().setHiddenAreas(hiddenOriginal.map(lineRangeAsRange), 'diff-hidden');
this._previewDiffEditor.value.getModifiedEditor().setHiddenAreas(hiddenModified.map(lineRangeAsRange), 'diff-hidden');
this._previewDiffEditor.value.revealLine(modifiedLineRange.startLineNumber, ScrollType.Immediate);
this._onDidChangeHeight.fire();
}
hideEditsPreview() {
this._elements.previewDiff.classList.add('hidden');
this._previewDiffEditor.setModel(null);
this._previewDiffEditor.value.setModel(null);
this._previewDiffModel.clear();
this._onDidChangeHeight.fire();
}
@ -555,14 +556,14 @@ export class InteractiveEditorWidget {
const model = this._modelService.createModel('', langSelection, undefined, true);
model.applyEdits(edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)));
this._previewCreateModel.value = model;
this._previewCreateEditor.setModel(model);
this._previewCreateEditor.value.setModel(model);
this._onDidChangeHeight.fire();
}
hideCreatePreview() {
this._elements.previewCreateTitle.classList.add('hidden');
this._elements.previewCreate.classList.add('hidden');
this._previewCreateEditor.setModel(null);
this._previewCreateEditor.value.setModel(null);
this._previewCreateTitle.element.clear();
this._onDidChangeHeight.fire();
}