diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index b6e1d902ea0..c4fca1250df 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -193,7 +193,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostUriOpeners = rpcProtocol.set(ExtHostContext.ExtHostUriOpeners, new ExtHostUriOpeners(rpcProtocol)); const extHostProfileContentHandlers = rpcProtocol.set(ExtHostContext.ExtHostProfileContentHandlers, new ExtHostProfileContentHandlers(rpcProtocol)); rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService)); - const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInteractiveEditor, new ExtHostInteractiveEditor(rpcProtocol, extHostDocuments, extHostLogService)); + const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInteractiveEditor, new ExtHostInteractiveEditor(rpcProtocol, extHostDocuments, extHostLogService, extHostCommands)); const extHostInteractiveSession = rpcProtocol.set(ExtHostContext.ExtHostInteractiveSession, new ExtHostInteractiveSession(rpcProtocol, extHostLogService)); // Check that no named customers are missing diff --git a/src/vs/workbench/api/common/extHostInteractiveEditor.ts b/src/vs/workbench/api/common/extHostInteractiveEditor.ts index c6cadd2737e..e9c703854ba 100644 --- a/src/vs/workbench/api/common/extHostInteractiveEditor.ts +++ b/src/vs/workbench/api/common/extHostInteractiveEditor.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; -import { toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ISelection } from 'vs/editor/common/core/selection'; import { IInteractiveEditorSession, IInteractiveEditorRequest } from 'vs/workbench/contrib/interactiveEditor/common/interactiveEditor'; @@ -15,6 +15,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import { WorkspaceEdit } from 'vs/workbench/api/common/extHostTypes'; import type * as vscode from 'vscode'; +import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; class ProviderWrapper { @@ -28,18 +29,28 @@ class ProviderWrapper { ) { } } +class SessionWrapper { + + readonly store = new DisposableStore(); + + constructor( + readonly session: vscode.InteractiveEditorSession + ) { } +} + export class ExtHostInteractiveEditor implements ExtHostInteractiveEditorShape { private static _nextId = 0; private readonly _inputProvider = new Map(); - private readonly _inputSessions = new Map(); + private readonly _inputSessions = new Map(); private readonly _proxy: MainThreadInteractiveEditorShape; constructor( mainContext: IMainContext, private readonly _documents: ExtHostDocuments, - private readonly _logService: ILogService + private readonly _logService: ILogService, + private readonly _commands: ExtHostCommands, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadInteractiveEditor); } @@ -68,7 +79,7 @@ export class ExtHostInteractiveEditor implements ExtHostInteractiveEditorShape { } const id = ExtHostInteractiveEditor._nextId++; - this._inputSessions.set(id, session); + this._inputSessions.set(id, new SessionWrapper(session)); return { id, placeholder: session.placeholder, slashCommands: session.slashCommands }; } @@ -78,42 +89,47 @@ export class ExtHostInteractiveEditor implements ExtHostInteractiveEditorShape { if (!entry) { return undefined; } - const session = this._inputSessions.get(item.id); - if (!session) { + const sessionData = this._inputSessions.get(item.id); + if (!sessionData) { return; } const res = await entry.provider.provideInteractiveEditorResponse({ - session, + session: sessionData.session, prompt: request.prompt, selection: typeConvert.Selection.to(request.selection), - wholeRange: typeConvert.Range.to(request.wholeRange) + wholeRange: typeConvert.Range.to(request.wholeRange), }, token); - if (ExtHostInteractiveEditor._isMessageResponse(res)) { - return { - type: 'message', - message: typeConvert.MarkdownString.from(res.contents), - wholeRange: typeConvert.Range.from(res.wholeRange) - }; - } - if (res) { - const { edits, placeholder } = res; + + const stub: Partial = { + wholeRange: typeConvert.Range.from(res.wholeRange), + placeholder: res.placeholder, + commands: res.commands ? res.commands.map(c => this._commands.converter.toInternal(c, sessionData.store)) : undefined, + }; + + if (ExtHostInteractiveEditor._isMessageResponse(res)) { + return { + ...stub, + type: 'message', + message: typeConvert.MarkdownString.from(res.contents), + }; + } + + const { edits } = res; if (edits instanceof WorkspaceEdit) { return { + ...stub, type: 'bulkEdit', - placeholder, edits: typeConvert.WorkspaceEdit.from(edits), - wholeRange: typeConvert.Range.from(res.wholeRange) }; } else if (Array.isArray(edits)) { return { + ...stub, type: 'editorEdit', - placeholder, edits: edits.map(typeConvert.TextEdit.from), - wholeRange: typeConvert.Range.from(res.wholeRange), }; } } @@ -122,10 +138,11 @@ export class ExtHostInteractiveEditor implements ExtHostInteractiveEditorShape { } $releaseSession(handle: number, sessionId: number) { - const session = this._inputSessions.get(sessionId); + const sessionData = this._inputSessions.get(sessionId); const entry = this._inputProvider.get(handle); - if (session && entry) { - entry.provider.releaseInteractiveEditorSession?.(session); + if (sessionData && entry) { + entry.provider.releaseInteractiveEditorSession?.(sessionData.session); + sessionData.store.dispose(); } this._inputSessions.delete(sessionId); } diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditor.contribution.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditor.contribution.ts index abbc67dbef6..68710ccb323 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditor.contribution.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditor.contribution.ts @@ -16,16 +16,16 @@ registerSingleton(IInteractiveEditorService, InteractiveEditorServiceImpl, Insta registerEditorContribution(InteractiveEditorController.ID, InteractiveEditorController, EditorContributionInstantiation.Lazy); registerAction2(interactiveEditorActions.StartSessionAction); -registerAction2(interactiveEditorActions.ToggleHistory); registerAction2(interactiveEditorActions.MakeRequestAction); registerAction2(interactiveEditorActions.StopRequestAction); registerAction2(interactiveEditorActions.AcceptWithPreviewInteractiveEditorAction); -registerAction2(interactiveEditorActions.TogglePreviewMode); registerAction2(interactiveEditorActions.CancelSessionAction); registerAction2(interactiveEditorActions.ArrowOutUpAction); registerAction2(interactiveEditorActions.ArrowOutDownAction); registerAction2(interactiveEditorActions.FocusInteractiveEditor); registerAction2(interactiveEditorActions.PreviousFromHistory); registerAction2(interactiveEditorActions.NextFromHistory); -registerAction2(interactiveEditorActions.UndoCommand); + +// registerAction2(interactiveEditorActions.ToggleHistory); +// registerAction2(interactiveEditorActions.UndoCommand); registerAction2(interactiveEditorActions.CopyRecordings); diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditor.css b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditor.css index 567911ac5c1..2a956a0f63a 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditor.css +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditor.css @@ -15,47 +15,22 @@ display: flex; } -.monaco-editor .interactive-editor .body .expando { - flex-grow: 0; - padding-top: 6px; - padding-right: 2px; - cursor: pointer; -} -.monaco-editor .interactive-editor .body .history { - display: block; - color: var(--vscode-descriptionForeground); -} -.monaco-editor .interactive-editor .body .history.hidden { - display: none; -} - -.monaco-editor .interactive-editor .body .history .history-entry { +.monaco-editor .interactive-editor .body .content { display: flex; - justify-content: space-between; - padding: 2px + box-sizing: border-box; + border-radius: 2px; + border: 1px solid var(--vscode-settings-textInputBorder); } -.monaco-editor .interactive-editor .body .history .history-entry.hidden { - display: none; -} - -.monaco-editor .interactive-editor .body .history .history-entry:hover { - background-color: var(--vscode-list-hoverBackground); - color: var(--vscode-list-hoverForeground); -} - -.monaco-editor .interactive-editor .body.preview { - padding: 4px 4px 0 4px; +.monaco-editor .interactive-editor .body .content.synthetic-focus { + outline: 1px solid var(--vscode-focusBorder); } .monaco-editor .interactive-editor .body .content .input { - border-radius: 2px; - box-sizing: border-box; padding: 2px; background-color: var(--vscode-input-background); - border: 1px solid var(--vscode-settings-textInputBorder); cursor: text; } @@ -63,10 +38,6 @@ background-color: var(--vscode-input-background); } -.monaco-editor .interactive-editor .body .content .input.synthetic-focus { - outline: 1px solid var(--vscode-focusBorder); -} - .monaco-editor .interactive-editor .body .content .input .editor-placeholder { position: absolute; z-index: 1; @@ -110,19 +81,65 @@ top: 0; } -/* message */ +/* status */ -.monaco-editor .interactive-editor .message { - padding: 6px 0; - color: var(--vscode-errorForeground); +.monaco-editor .interactive-editor .status { + padding: 6px 10px 2px 2px; } -.monaco-editor .interactive-editor .message.hidden { +.monaco-editor .interactive-editor .status.hidden { display: none; } -/* decoration styles */ +.monaco-editor .interactive-editor .status .status-item { + display: flex; + justify-content: space-between; + align-items: center; +} +.monaco-editor .interactive-editor .status .status-item.error .label { + color: var(--vscode-errorForeground); +} + +.monaco-editor .interactive-editor .status .status-item.warn .label { + color: var(--vscode-editorWarning-foreground); +} + +.monaco-editor .interactive-editor .status .monaco-toolbar .action-label.checked { + color: var(--vscode-checkbox-foreground); + background-color: var(--vscode-checkbox-background); + outline: 1px solid var(--vscode-checkbox-border); +} + +/* history */ + +.monaco-editor .interactive-editor .history { + border-top: 1px solid var(--vscode-widget-border); + padding: 0 10px 2px 2px; + display: block; + color: var(--vscode-descriptionForeground); +} + +.monaco-editor .interactive-editor .history.hidden { + display: none; +} + +.monaco-editor .interactive-editor .history .history-entry { + display: flex; + justify-content: space-between; + padding: 2px +} + +.monaco-editor .interactive-editor .history .history-entry.hidden { + display: none; +} + +.monaco-editor .interactive-editor .history .history-entry:hover { + background-color: var(--vscode-list-hoverBackground); + color: var(--vscode-list-hoverForeground); +} + +/* decoration styles */ .monaco-editor .interactive-editor-lines-deleted-range-inline { text-decoration: line-through; diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts index fc4d4c79a39..28908496e93 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts @@ -10,7 +10,7 @@ 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_PREVIEW, CTX_INTERACTIVE_EDITOR_VISIBLE, MENU_INTERACTIVE_EDITOR_WIDGET, CTX_INTERACTIVE_EDITOR_HISTORY_VISIBLE, CTX_INTERACTIVE_EDITOR_HISTORY_POSSIBLE } 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_HISTORY_VISIBLE, CTX_INTERACTIVE_EDITOR_HISTORY_POSSIBLE } 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'; @@ -90,7 +90,7 @@ export class MakeRequestAction extends AbstractInteractiveEditorAction { } runInteractiveEditorCommand(_accessor: ServicesAccessor, ctrl: InteractiveEditorController, _editor: ICodeEditor, ..._args: any[]): void { - ctrl.accept(); + ctrl.accept(false); } } @@ -140,27 +140,6 @@ export class AcceptWithPreviewInteractiveEditorAction extends AbstractInteractiv } } -export class TogglePreviewMode extends AbstractInteractiveEditorAction { - - constructor() { - super({ - id: 'interactiveEditor.togglePreview', - title: localize('togglePreview', 'Inline Preview'), - precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_VISIBLE), - toggled: CTX_INTERACTIVE_EDITOR_PREVIEW, - menu: { - id: MENU_INTERACTIVE_EDITOR_WIDGET, - group: 'C', - order: 1 - } - }); - } - - runInteractiveEditorCommand(_accessor: ServicesAccessor, ctrl: InteractiveEditorController, _editor: ICodeEditor, ..._args: any[]): void { - ctrl.togglePreview(); - } -} - export class CancelSessionAction extends AbstractInteractiveEditorAction { constructor() { diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts index f7df11396c6..be8c2c21abc 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorWidget.ts @@ -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_PREVIEW, CTX_INTERACTIVE_EDITOR_VISIBLE, MENU_INTERACTIVE_EDITOR_WIDGET, CTX_INTERACTIVE_EDITOR_HISTORY_VISIBLE, IInteractiveEditorRequest, IInteractiveEditorSession, CTX_INTERACTIVE_EDITOR_HISTORY_POSSIBLE, IInteractiveEditorSlashCommand } 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, CTX_INTERACTIVE_EDITOR_HISTORY_VISIBLE, IInteractiveEditorRequest, IInteractiveEditorSession, CTX_INTERACTIVE_EDITOR_HISTORY_POSSIBLE, IInteractiveEditorSlashCommand } 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'; @@ -40,7 +40,7 @@ import { isCancellationError } from 'vs/base/common/errors'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { ILogService } from 'vs/platform/log/common/log'; import { StopWatch } from 'vs/base/common/stopwatch'; -import { Action, IAction } from 'vs/base/common/actions'; +import { Action, IAction, Separator } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { LRUCache } from 'vs/base/common/map'; @@ -52,9 +52,10 @@ import { IViewsService } from 'vs/workbench/common/views'; import { IInteractiveSessionContributionService } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionContributionService'; import { InteractiveSessionViewPane } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionSidebar'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { CompletionContext, CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList, ProviderResult } from 'vs/editor/common/languages'; +import { Command, CompletionContext, CompletionItem, CompletionItemInsertTextRule, CompletionItemKind, CompletionItemProvider, CompletionList, ProviderResult } from 'vs/editor/common/languages'; import { LanguageSelector } from 'vs/editor/common/languageSelector'; import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style'; +import { ICommandService } from 'vs/platform/commands/common/commands'; interface IHistoryEntry { updateVisibility(visible: boolean): void; @@ -72,17 +73,17 @@ class InteractiveEditorWidget { 'div.interactive-editor@root', [ h('div.body', [ - h('div.content', [ + h('div.content@content', [ h('div.input@input', [ h('div.editor-placeholder@placeholder'), h('div.editor-container@editor'), ]), - h('div.history.hidden@history'), + h('div.toolbar@rhsToolbar'), ]), - h('div.toolbar@rhsToolbar'), ]), h('div.progress@progress'), - h('div.message.hidden@message'), + h('div.status.hidden@status'), + h('div.history.hidden@history'), ] ); @@ -238,7 +239,7 @@ class InteractiveEditorWidget { } getHeight(): number { - const base = getTotalHeight(this._elements.progress) + getTotalHeight(this._elements.message) + getTotalHeight(this._elements.history); + const base = getTotalHeight(this._elements.progress) + getTotalHeight(this._elements.status) + getTotalHeight(this._elements.history); const editorHeight = this.inputEditor.getContentHeight() + 6 /* padding and border */; return base + editorHeight + 12 /* padding */; } @@ -310,7 +311,7 @@ class InteractiveEditorWidget { const updateFocused = () => { const hasFocus = this.inputEditor.hasWidgetFocus(); ctxInputEditorFocused.set(hasFocus); - this._elements.input.classList.toggle('synthetic-focus', hasFocus); + this._elements.content.classList.toggle('synthetic-focus', hasFocus); }; disposeOnDone.add(this.inputEditor.onDidFocusEditorWidget(updateFocused)); disposeOnDone.add(this.inputEditor.onDidBlurEditorWidget(updateFocused)); @@ -339,6 +340,52 @@ class InteractiveEditorWidget { this._onDidChangeHeight.fire(); } + createStatusEntry() { + const { root, label, actions } = h('div.status-item@item', [ + h('div.label@label'), + h('div.actions@actions'), + ]); + + const toolbar = this._instantiationService.createInstance(WorkbenchToolBar, actions, {}); + this._historyStore.add(toolbar); + + reset(this._elements.status, root); + this._onDidChangeHeight.fire(); + + let oldClasses: string[] = []; + + return { + update: (update: { message?: string; actions?: IAction[]; classes?: string[] }) => { + if (update.message) { + label.innerText = update.message; + this._elements.status.classList.remove('hidden'); + } + if (update.actions) { + toolbar.setActions(update.actions); + } + if (update.classes) { + oldClasses.forEach(value => root.classList.remove(value)); + oldClasses = update.classes.slice(); + root.classList.add(...update.classes); + } + }, + updateMessage(message: string) { + label.innerText = message; + }, + updateActions(actions: IAction[]) { + toolbar.setActions(actions); + }, + updateClasses: (classes: string[]) => { + root.classList.add(...classes); + }, + remove: () => { + root.remove(); + this._elements.status.classList.add('hidden'); + this._onDidChangeHeight.fire(); + } + }; + } + createHistoryEntry(value: string): IHistoryEntry { const { root, label, actions } = h('div.history-entry@item', [ @@ -386,22 +433,11 @@ class InteractiveEditorWidget { reset(this._elements.history); } - showMessage(value: string) { - this._elements.message.classList.remove('hidden'); - this._elements.message.innerText = value; - this._onDidChangeHeight.fire(); - } - - clearMessage() { - this._elements.message.classList.add('hidden'); - reset(this._elements.message); - this._onDidChangeHeight.fire(); - } - reset() { this._ctxInputEmpty.reset(); this.clearHistory(); - this.clearMessage(); + // this.clearMessage(); + reset(this._elements.status); } focus() { @@ -517,6 +553,14 @@ export class InteractiveEditorZoneWidget extends ZoneWidget { } } +class CommandAction extends Action { + + constructor(command: Command, @ICommandService commandService: ICommandService) { + const icon = ThemeIcon.fromString(command.title); + super(command.id, icon ? command.tooltip : command.title, icon ? ThemeIcon.asClassName(icon) : undefined, true, () => commandService.executeCommand(command.id)); + } +} + class UndoStepAction extends Action { static all: UndoStepAction[] = []; @@ -592,8 +636,18 @@ class InlineDiffDecorations { private _data: { tracking: IModelDeltaDecoration; decorating: IModelDecorationOptions }[] = []; private _visible: boolean = false; - constructor(editor: ICodeEditor) { + constructor(editor: ICodeEditor, visible: boolean = false) { this._collection = editor.createDecorationsCollection(); + this._visible = visible; + } + + get visible() { + return this._visible; + } + + set visible(value: boolean) { + this._visible = value; + this.update(); } clear() { @@ -605,7 +659,7 @@ class InlineDiffDecorations { this._data.push(InlineDiffDecorations._asDecorationData(op)); } - private _update() { + update() { this._collection.set(this._data.map(d => { const res = { ...d.tracking }; if (this._visible) { @@ -615,16 +669,6 @@ class InlineDiffDecorations { })); } - updateVisible(value: boolean) { - this._visible = value; - this._update(); - } - - // toggleVisible() { - // this._visible = !this._visible; - // this._update(); - // } - private static _asDecorationData(edit: IValidEditOperation): { tracking: IModelDeltaDecoration; decorating: IModelDecorationOptions } { let content = edit.text; if (content.length > 12) { @@ -676,8 +720,8 @@ export class InteractiveEditorController implements IEditorContribution { private readonly _store = new DisposableStore(); private readonly _recorder = new SessionRecorder(); private readonly _zone: InteractiveEditorZoneWidget; - private readonly _ctxShowPreview: IContextKey; private readonly _ctxHasActiveRequest: IContextKey; + private _inlineDiffEnabled: boolean = false; private _ctsSession: CancellationTokenSource = new CancellationTokenSource(); private _ctsRequest?: CancellationTokenSource; @@ -693,7 +737,6 @@ export class InteractiveEditorController implements IEditorContribution { @ITelemetryService private readonly _telemetryService: ITelemetryService ) { this._zone = this._store.add(_instaService.createInstance(InteractiveEditorZoneWidget, this._editor)); - this._ctxShowPreview = CTX_INTERACTIVE_EDITOR_PREVIEW.bindTo(contextKeyService); this._ctxHasActiveRequest = CTX_INTERACTIVE_EDITOR_HAS_ACTIVE_REQUEST.bindTo(contextKeyService); } @@ -741,7 +784,8 @@ export class InteractiveEditorController implements IEditorContribution { undos: '' }; - const inlineDiffDecorations = new InlineDiffDecorations(this._editor); + const statusWidget = this._zone.widget.createStatusEntry(); + const inlineDiffDecorations = new InlineDiffDecorations(this._editor, this._inlineDiffEnabled); const blockDecoration = this._editor.createDecorationsCollection(); const wholeRangeDecoration = this._editor.createDecorationsCollection(); @@ -813,6 +857,7 @@ export class InteractiveEditorController implements IEditorContribution { let round = 0; + do { round += 1; @@ -836,8 +881,6 @@ export class InteractiveEditorController implements IEditorContribution { this._historyOffset = -1; const input = await this._zone.getInput(wholeRange.getEndPosition(), placeholder, value, this._ctsRequest.token); - this._zone.widget.clearMessage(); - if (!input || !input.value) { continue; } @@ -863,7 +906,9 @@ export class InteractiveEditorController implements IEditorContribution { if (!isCancellationError(e)) { this._logService.error('[IE] ERROR during request', provider.debugName); this._logService.error(e); - this._zone.widget.showMessage(toErrorMessage(e)); + // this._zone.widget.showMessage(toErrorMessage(e)); + statusWidget.update({ message: toErrorMessage(e), classes: ['error'], actions: [] }); + // statusWidget continue; } } finally { @@ -872,7 +917,6 @@ export class InteractiveEditorController implements IEditorContribution { this._logService.trace('[IE] request took', sw.elapsed(), provider.debugName); } - if (this._ctsRequest.token.isCancellationRequested) { this._logService.trace('[IE] request CANCELED', provider.debugName); value = input.value; @@ -883,7 +927,7 @@ export class InteractiveEditorController implements IEditorContribution { if (!reply) { this._logService.trace('[IE] NO reply or edits', provider.debugName); value = input.value; - this._zone.widget.showMessage(localize('empty', "No results, tweak your input and try again.")); + statusWidget.update({ message: localize('empty', "No results, tweak your input and try again."), classes: ['warn'], actions: [] }); historyEntry.remove(); continue; } @@ -942,18 +986,10 @@ export class InteractiveEditorController implements IEditorContribution { ignoreModelChanges = false; } - inlineDiffDecorations.updateVisible(input.preview); + inlineDiffDecorations.update(); const that = this; historyEntry.updateActions([ - // new class extends Action { - // constructor() { - // super(Math.random().toString(), localize('ie.inlineDiff', "Toggle Inline Diff"), ThemeIcon.asClassName(Codicon.diff), true); - // } - // override async run() { - // inlineDiffDecorations.toggleVisible(); - // } - // }, new class extends UndoStepAction { constructor() { super(textModel); @@ -967,6 +1003,41 @@ export class InteractiveEditorController implements IEditorContribution { } }]); + + const replyActions = reply.commands?.map(command => this._instaService.createInstance(CommandAction, command)) ?? []; + const fixedActions = [new class extends Action { + + constructor() { + super(Math.random().toString(), localize('ie.inlineDiff', "Toggle Inline Diff"), ThemeIcon.asClassName(Codicon.diff), true); + this._setChecked(inlineDiffDecorations.visible); + } + override async run() { + + inlineDiffDecorations.visible = !inlineDiffDecorations.visible; + this._setChecked(inlineDiffDecorations.visible); + } + // }, new class extends UndoStepAction { + // constructor() { + // super(textModel); + // } + // override async run() { + // super.run(); + // historyEntry.updateVisibility(false); + // value = input.value; + // that._ctsRequest?.cancel(); + // data.undos += round + '|'; + // } + }]; + + const actions = Separator.join(replyActions, fixedActions); + const editsCount = (moreMinimalEdits ?? reply.edits).length; + + statusWidget.update({ + message: editsCount === 1 ? localize('edit.1', "Done, made 1 change") : localize('edit.N', "Done, made {0} changes", editsCount), + classes: [], + actions, + }); + if (!InteractiveEditorController._promptHistory.includes(input.value)) { InteractiveEditorController._promptHistory.unshift(input.value); } @@ -976,6 +1047,8 @@ export class InteractiveEditorController implements IEditorContribution { } while (!thisSession.token.isCancellationRequested); + this._inlineDiffEnabled = inlineDiffDecorations.visible; + // done, cleanup wholeRangeDecoration.clear(); blockDecoration.clear(); @@ -995,17 +1068,10 @@ export class InteractiveEditorController implements IEditorContribution { this._telemetryService.publicLog2('interactiveEditor/session', data); } - accept(preview: boolean = this._preview): void { + accept(preview: boolean): void { this._zone.widget.acceptInput(preview); } - private _preview: boolean = false; // TODO@jrieken persist this - - togglePreview(): void { - this._preview = !this._preview; - this._ctxShowPreview.set(this._preview); - } - cancelCurrentRequest(): void { this._ctsRequest?.cancel(); } diff --git a/src/vs/workbench/contrib/interactiveEditor/common/interactiveEditor.ts b/src/vs/workbench/contrib/interactiveEditor/common/interactiveEditor.ts index 853f725c675..78e3f8ceb1a 100644 --- a/src/vs/workbench/contrib/interactiveEditor/common/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactiveEditor/common/interactiveEditor.ts @@ -8,7 +8,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IRange } from 'vs/editor/common/core/range'; import { ISelection } from 'vs/editor/common/core/selection'; -import { ProviderResult, TextEdit, WorkspaceEdit } from 'vs/editor/common/languages'; +import { Command, ProviderResult, TextEdit, WorkspaceEdit } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; @@ -40,6 +40,7 @@ export interface IInteractiveEditorEditResponse { edits: TextEdit[]; placeholder?: string; wholeRange?: IRange; + commands?: Command[]; } export interface IInteractiveEditorBulkEditResponse { @@ -47,6 +48,7 @@ export interface IInteractiveEditorBulkEditResponse { edits: WorkspaceEdit; placeholder?: string; wholeRange?: IRange; + commands?: Command[]; } export interface IInteractiveEditorMessageResponse { @@ -54,6 +56,7 @@ export interface IInteractiveEditorMessageResponse { message: IMarkdownString; placeholder?: string; wholeRange?: IRange; + commands?: Command[]; } export interface IInteractiveEditorSessionProvider { @@ -80,7 +83,6 @@ export const CTX_INTERACTIVE_EDITOR_HAS_PROVIDER = new RawContextKey('i export const CTX_INTERACTIVE_EDITOR_VISIBLE = new RawContextKey('interactiveEditorVisible', false, localize('interactiveEditorVisible', "Whether the interactive editor input is visible")); export const CTX_INTERACTIVE_EDITOR_FOCUSED = new RawContextKey('interactiveEditorFocused', false, localize('interactiveEditorFocused', "Whether the interactive editor input is focused")); export const CTX_INTERACTIVE_EDITOR_EMPTY = new RawContextKey('interactiveEditorEmpty', false, localize('interactiveEditorEmpty', "Whether the interactive editor input is empty")); -export const CTX_INTERACTIVE_EDITOR_PREVIEW = new RawContextKey('interactiveEditorPreview', false, localize('interactiveEditorPreview', "Whether the interactive editor input shows inline previews")); export const CTX_INTERACTIVE_EDITOR_HISTORY_POSSIBLE = new RawContextKey('interactiveEditorHistoryPossible', false, localize('interactiveEditorHistoryPossible', "Whether the interactive editor has history entries")); export const CTX_INTERACTIVE_EDITOR_HISTORY_VISIBLE = new RawContextKey('interactiveEditorHistoryVisible', false, localize('interactiveEditorHistoryVisible', "Whether the interactive editor history is visible")); export const CTX_INTERACTIVE_EDITOR_INNER_CURSOR_FIRST = new RawContextKey('interactiveEditorInnerCursorFirst', false, localize('interactiveEditorInnerCursorFirst', "Whether the cursor of the iteractive editor input is on the first line")); diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index 16184737a66..efd484c7aef 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -31,12 +31,15 @@ declare module 'vscode' { edits: TextEdit[] | WorkspaceEdit; placeholder?: string; wholeRange?: Range; + commands?: Command[]; } // todo@API make classes export interface InteractiveEditorMessageResponse { contents: MarkdownString; + placeholder?: string; wholeRange?: Range; + commands?: Command[]; } export interface TextDocumentContext {