Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support for welcome message, show commands only after first response, show changed lines after edit, fix persisting of inline diff #178592

Merged
merged 1 commit into from Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion src/vs/workbench/api/common/extHostInteractiveEditor.ts
Expand Up @@ -116,7 +116,6 @@ export class ExtHostInteractiveEditor implements ExtHostInteractiveEditorShape {
const stub: Partial<IInteractiveEditorResponseDto> = {
wholeRange: typeConvert.Range.from(res.wholeRange),
placeholder: res.placeholder,
detail: res.detail
};

if (ExtHostInteractiveEditor._isMessageResponse(res)) {
Expand Down
Expand Up @@ -96,11 +96,12 @@
align-items: center;
}

.monaco-editor .interactive-editor .status.hidden {
.monaco-editor .interactive-editor .status .actions.hidden {
display: none;
}

.monaco-editor .interactive-editor .status .label {
margin-left: auto;
padding-left: 12px;
font-size: 12px;
}
Expand Down
Expand Up @@ -10,15 +10,15 @@ import { EditorAction2 } from 'vs/editor/browser/editorExtensions';
import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { InteractiveEditorController, Recording } from 'vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget';
import { CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_HAS_ACTIVE_REQUEST, CTX_INTERACTIVE_EDITOR_HAS_PROVIDER, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_FIRST, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_LAST, CTX_INTERACTIVE_EDITOR_EMPTY, CTX_INTERACTIVE_EDITOR_OUTER_CURSOR_POSITION, CTX_INTERACTIVE_EDITOR_VISIBLE, MENU_INTERACTIVE_EDITOR_WIDGET, CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE, MENU_INTERACTIVE_EDITOR_WIDGET_UNDO, MENU_INTERACTIVE_EDITOR_WIDGET_STATUS, CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK, CTX_INTERACTIVE_EDITOR_INLNE_DIFF } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditor';
import { CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_HAS_ACTIVE_REQUEST, CTX_INTERACTIVE_EDITOR_HAS_PROVIDER, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_FIRST, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_LAST, CTX_INTERACTIVE_EDITOR_EMPTY, CTX_INTERACTIVE_EDITOR_OUTER_CURSOR_POSITION, CTX_INTERACTIVE_EDITOR_VISIBLE, MENU_INTERACTIVE_EDITOR_WIDGET, CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE, MENU_INTERACTIVE_EDITOR_WIDGET_UNDO, MENU_INTERACTIVE_EDITOR_WIDGET_STATUS, CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK, CTX_INTERACTIVE_EDITOR_INLNE_DIFF, CTX_INTERACTIVE_EDITOR_HAS_RESPONSE } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditor';
import { localize } from 'vs/nls';
import { IAction2Options } from 'vs/platform/actions/common/actions';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor';

export class StartSessionAction extends EditorAction2 {
Expand Down Expand Up @@ -248,14 +248,14 @@ export class UndoToClipboard extends AbstractInteractiveEditorAction {
super({
id: 'interactiveEditor.undoToClipboard',
title: localize('undo.clipboard', 'Undo to Clipboard'),
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_VISIBLE, CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE),
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_VISIBLE, CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE.isEqualTo('simple')),
keybinding: {
weight: KeybindingWeight.EditorContrib + 10,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyZ,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyZ },
},
menu: {
when: CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE,
when: CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE.isEqualTo('simple'),
id: MENU_INTERACTIVE_EDITOR_WIDGET_UNDO,
group: '1_undo',
order: 1
Expand All @@ -278,9 +278,9 @@ export class UndoToNewFile extends AbstractInteractiveEditorAction {
super({
id: 'interactiveEditor.undoToFile',
title: localize('undo.newfile', 'Undo to New File'),
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_VISIBLE, CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE),
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_VISIBLE, CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE.isEqualTo('simple')),
menu: {
when: CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE,
when: CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE.isEqualTo('simple'),
id: MENU_INTERACTIVE_EDITOR_WIDGET_UNDO,
group: '1_undo',
order: 2
Expand All @@ -293,7 +293,7 @@ export class UndoToNewFile extends AbstractInteractiveEditorAction {
const lastText = ctrl.undoLast();
if (lastText !== undefined) {
const input: IUntitledTextResourceEditorInput = { forceUntitled: true, resource: undefined, contents: lastText, languageId: editor.getModel()?.getLanguageId() };
editorService.openEditor(input);
editorService.openEditor(input, SIDE_GROUP);
}
}
}
Expand All @@ -305,9 +305,9 @@ export class UndoCommand extends AbstractInteractiveEditorAction {
id: 'interactiveEditor.undo',
title: localize('undo', 'Undo'),
icon: Codicon.commentDiscussion,
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_VISIBLE, CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE),
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_VISIBLE, CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE.isEqualTo('simple')),
menu: {
when: CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE,
when: CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE.isEqualTo('simple'),
id: MENU_INTERACTIVE_EDITOR_WIDGET_UNDO,
group: '1_undo',
order: 3
Expand All @@ -326,7 +326,7 @@ export class FeebackHelpfulCommand extends AbstractInteractiveEditorAction {
id: 'interactiveEditor.feedbackHelpful',
title: localize('feedback.helpful', 'Helpful'),
icon: Codicon.thumbsup,
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_VISIBLE),
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_VISIBLE, CTX_INTERACTIVE_EDITOR_HAS_RESPONSE),
toggled: CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK.isEqualTo('helpful'),
menu: {
id: MENU_INTERACTIVE_EDITOR_WIDGET_STATUS,
Expand All @@ -347,7 +347,7 @@ export class FeebackUnhelpfulCommand extends AbstractInteractiveEditorAction {
id: 'interactiveEditor.feedbackunhelpful',
title: localize('feedback.unhelpful', 'Unhelpful'),
icon: Codicon.thumbsdown,
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_VISIBLE),
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_VISIBLE, CTX_INTERACTIVE_EDITOR_HAS_RESPONSE),
toggled: CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK.isEqualTo('unhelpful'),
menu: {
id: MENU_INTERACTIVE_EDITOR_WIDGET_STATUS,
Expand Down
Expand Up @@ -15,7 +15,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget';
import { assertType } from 'vs/base/common/types';
import { IInteractiveEditorResponse, IInteractiveEditorService, CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_HAS_ACTIVE_REQUEST, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_FIRST, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_LAST, CTX_INTERACTIVE_EDITOR_EMPTY, CTX_INTERACTIVE_EDITOR_OUTER_CURSOR_POSITION, CTX_INTERACTIVE_EDITOR_VISIBLE, MENU_INTERACTIVE_EDITOR_WIDGET, IInteractiveEditorRequest, IInteractiveEditorSession, IInteractiveEditorSlashCommand, IInteractiveEditorSessionProvider, InteractiveEditorResponseFeedbackKind, IInteractiveEditorEditResponse, CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE as CTX_INTERACTIVE_EDITOR_LAST_EDIT_KIND, MENU_INTERACTIVE_EDITOR_WIDGET_STATUS, CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK as CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK_KIND, CTX_INTERACTIVE_EDITOR_INLNE_DIFF } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditor';
import { IInteractiveEditorResponse, IInteractiveEditorService, CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_HAS_ACTIVE_REQUEST, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_FIRST, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_LAST, CTX_INTERACTIVE_EDITOR_EMPTY, CTX_INTERACTIVE_EDITOR_OUTER_CURSOR_POSITION, CTX_INTERACTIVE_EDITOR_VISIBLE, MENU_INTERACTIVE_EDITOR_WIDGET, IInteractiveEditorRequest, IInteractiveEditorSession, IInteractiveEditorSlashCommand, IInteractiveEditorSessionProvider, InteractiveEditorResponseFeedbackKind, IInteractiveEditorEditResponse, CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE as CTX_INTERACTIVE_EDITOR_LAST_EDIT_KIND, MENU_INTERACTIVE_EDITOR_WIDGET_STATUS, CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK as CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK_KIND, CTX_INTERACTIVE_EDITOR_INLNE_DIFF, CTX_INTERACTIVE_EDITOR_HAS_RESPONSE } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditor';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Iterable } from 'vs/base/common/iterator';
import { ICursorStateComputer, IModelDecorationOptions, IModelDeltaDecoration, ITextModel, IValidEditOperation } from 'vs/editor/common/model';
Expand Down Expand Up @@ -55,6 +55,7 @@ import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style';
import { IInteractiveSessionService } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { splitLines } from 'vs/base/common/strings';

class InteractiveEditorWidget {

Expand All @@ -75,8 +76,8 @@ class InteractiveEditorWidget {
]),
]),
h('div.progress@progress'),
h('div.status.hidden@status', [
h('div.actions@statusToolbar'),
h('div.status@status', [
h('div.actions.hidden@statusToolbar'),
h('div.label@statusLabel'),
]),
]
Expand Down Expand Up @@ -337,6 +338,11 @@ class InteractiveEditorWidget {
this.inputEditor.setSelection(this._inputModel.getFullModelRange());
}

updateToolbar(show: boolean) {
this._elements.statusToolbar.classList.toggle('hidden', !show);
this._onDidChangeHeight.fire();
}

updateMessage(message: string, classes?: string[], resetAfter?: number) {
const isTempMessage = typeof resetAfter === 'number';
if (isTempMessage && !this._elements.statusLabel.dataset['state']) {
Expand All @@ -360,12 +366,13 @@ class InteractiveEditorWidget {
} else {
delete this._elements.statusLabel.dataset['state'];
}
this._onDidChangeHeight.fire();
}

reset() {
this._ctxInputEmpty.reset();
reset(this._elements.statusLabel);
this._elements.status.classList.add('hidden');
this._elements.statusToolbar.classList.add('hidden');
}

focus() {
Expand Down Expand Up @@ -634,6 +641,7 @@ export class InteractiveEditorController implements IEditorContribution {
private readonly _recorder = new SessionRecorder();
private readonly _zone: InteractiveEditorZoneWidget;
private readonly _ctxHasActiveRequest: IContextKey<boolean>;
private readonly _ctxHasResponse: IContextKey<boolean>;
private readonly _ctxInlineDiff: IContextKey<boolean>;
private readonly _ctxLastEditKind: IContextKey<'' | 'simple'>;
private readonly _ctxLastFeedbackKind: IContextKey<'helpful' | 'unhelpful' | ''>;
Expand All @@ -659,6 +667,7 @@ export class InteractiveEditorController implements IEditorContribution {
) {
this._zone = this._store.add(_instaService.createInstance(InteractiveEditorZoneWidget, this._editor));
this._ctxHasActiveRequest = CTX_INTERACTIVE_EDITOR_HAS_ACTIVE_REQUEST.bindTo(contextKeyService);
this._ctxHasResponse = CTX_INTERACTIVE_EDITOR_HAS_RESPONSE.bindTo(contextKeyService);
this._ctxInlineDiff = CTX_INTERACTIVE_EDITOR_INLNE_DIFF.bindTo(contextKeyService);
this._ctxLastEditKind = CTX_INTERACTIVE_EDITOR_LAST_EDIT_KIND.bindTo(contextKeyService);
this._ctxLastFeedbackKind = CTX_INTERACTIVE_EDITOR_LAST_FEEDBACK_KIND.bindTo(contextKeyService);
Expand Down Expand Up @@ -741,6 +750,8 @@ export class InteractiveEditorController implements IEditorContribution {
store.add(this._instaService.invokeFunction(installSlashCommandSupport, this._zone.widget.inputEditor as IActiveCodeEditor, session.slashCommands));
}

this._zone.widget.updateMessage(session.message ?? localize('welcome.1', "AI-generated code may be incorrect."));

// CANCEL when input changes
this._editor.onDidChangeModel(this._ctsSession.cancel, this._ctsSession, store);

Expand Down Expand Up @@ -856,6 +867,7 @@ export class InteractiveEditorController implements IEditorContribution {
}
} finally {
this._ctxHasActiveRequest.set(false);
this._ctxHasResponse.set(!!reply);
this._zone.widget.updateProgress(false);
this._logService.trace('[IE] request took', sw.elapsed(), provider.debugName);
}
Expand Down Expand Up @@ -934,7 +946,33 @@ export class InteractiveEditorController implements IEditorContribution {

inlineDiffDecorations.update();

this._zone.widget.updateMessage(reply.detail ?? localize('fyi', "AI-generated code may be incorrect."));
// line count
const lineSet = new Set<number>();
let addRemoveCount = 0;
for (const edit of moreMinimalEdits ?? reply.edits) {

const len2 = splitLines(edit.text).length - 1;

if (Range.isEmpty(edit.range) && len2 > 0) {
// insert lines
addRemoveCount += len2;
} else if (Range.isEmpty(edit.range) && edit.text.length === 0) {
// delete
addRemoveCount += edit.range.endLineNumber - edit.range.startLineNumber + 1;
} else {
// edit
for (let line = edit.range.startLineNumber; line <= edit.range.endLineNumber; line++) {
lineSet.add(line);
}
}
}
const linesChanged = addRemoveCount + lineSet.size;

this._zone.widget.updateToolbar(true);
this._zone.widget.updateMessage(linesChanged === 1
? localize('lines.1', "Generated reply and changed 1 line.")
: localize('lines.N', "Generated reply and changed {0} lines.", linesChanged)
);

if (!InteractiveEditorController._promptHistory.includes(input)) {
InteractiveEditorController._promptHistory.unshift(input);
Expand All @@ -957,6 +995,7 @@ export class InteractiveEditorController implements IEditorContribution {
session.dispose?.();

this._ctxLastEditKind.reset();
this._ctxHasResponse.reset();
this._ctxLastFeedbackKind.reset();
this._lastEditState = undefined;
this._lastInlineDecorations = undefined;
Expand Down Expand Up @@ -995,6 +1034,7 @@ export class InteractiveEditorController implements IEditorContribution {
toggleInlineDiff(): void {
this._inlineDiffEnabled = !this._inlineDiffEnabled;
this._ctxInlineDiff.set(this._inlineDiffEnabled);
this._storageService.store(InteractiveEditorController._inlineDiffStorageKey, this._inlineDiffEnabled, StorageScope.PROFILE, StorageTarget.USER);
if (this._lastInlineDecorations) {
this._lastInlineDecorations.visible = this._inlineDiffEnabled;
}
Expand Down
Expand Up @@ -26,6 +26,7 @@ export interface IInteractiveEditorSlashCommand {
export interface IInteractiveEditorSession {
id: number;
placeholder?: string;
message?: string;
slashCommands?: IInteractiveEditorSlashCommand[];
wholeRange?: IRange;
dispose?(): void;
Expand All @@ -43,7 +44,6 @@ export interface IInteractiveEditorEditResponse {
id: number;
type: 'editorEdit';
edits: TextEdit[];
detail?: string;
placeholder?: string;
wholeRange?: IRange;
}
Expand All @@ -52,7 +52,6 @@ export interface IInteractiveEditorBulkEditResponse {
id: number;
type: 'bulkEdit';
edits: WorkspaceEdit;
detail?: string;
placeholder?: string;
wholeRange?: IRange;
}
Expand All @@ -61,7 +60,6 @@ export interface IInteractiveEditorMessageResponse {
id: number;
type: 'message';
message: IMarkdownString;
detail?: string;
placeholder?: string;
wholeRange?: IRange;
}
Expand Down Expand Up @@ -110,6 +108,7 @@ export const CTX_INTERACTIVE_EDITOR_INNER_CURSOR_FIRST = new RawContextKey<boole
export const CTX_INTERACTIVE_EDITOR_INNER_CURSOR_LAST = new RawContextKey<boolean>('interactiveEditorInnerCursorLast', false, localize('interactiveEditorInnerCursorLast', "Whether the cursor of the iteractive editor input is on the last line"));
export const CTX_INTERACTIVE_EDITOR_OUTER_CURSOR_POSITION = new RawContextKey<'above' | 'below' | ''>('interactiveEditorOuterCursorPosition', '', localize('interactiveEditorOuterCursorPosition', "Whether the cursor of the outer editor is above or below the interactive editor input"));
export const CTX_INTERACTIVE_EDITOR_HAS_ACTIVE_REQUEST = new RawContextKey<boolean>('interactiveEditorHasActiveRequest', false, localize('interactiveEditorHasActiveRequest', "Whether interactive editor has an active request"));
export const CTX_INTERACTIVE_EDITOR_HAS_RESPONSE = new RawContextKey<boolean>('interactiveEditorHasResponse', false, localize('interactiveEditorHasResponse', "Whether interactive editor has a response"));
export const CTX_INTERACTIVE_EDITOR_INLNE_DIFF = new RawContextKey<boolean>('interactiveEditorInlineDiff', false, localize('interactiveEditorInlineDiff', "Whether interactive editor show inline diffs for changes"));

export const CTX_INTERACTIVE_EDITOR_LAST_EDIT_TYPE = new RawContextKey<'simple' | ''>('interactiveEditorLastEditKind', '', localize('interactiveEditorLastEditKind', "The last kind of edit that was performed"));
Expand Down
2 changes: 0 additions & 2 deletions src/vscode-dts/vscode.proposed.interactive.d.ts
Expand Up @@ -33,15 +33,13 @@ declare module 'vscode' {
edits: TextEdit[] | WorkspaceEdit;
placeholder?: string;
wholeRange?: Range;
detail?: string;
}

// todo@API make classes
export interface InteractiveEditorMessageResponse {
contents: MarkdownString;
placeholder?: string;
wholeRange?: Range;
detail?: string;
}

export enum InteractiveEditorResponseFeedbackKind {
Expand Down