mirror of
https://github.com/Microsoft/vscode
synced 2024-11-05 18:29:38 +00:00
Merge pull request #175849 from microsoft/joh/statistical-gazelle
interactive editor work
This commit is contained in:
commit
635055c07e
30 changed files with 1559 additions and 24 deletions
|
@ -131,13 +131,13 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ
|
|||
return this._workerManager.withWorker().then(client => client.computeDirtyDiff(original, modified, ignoreTrimWhitespace));
|
||||
}
|
||||
|
||||
public computeMoreMinimalEdits(resource: URI, edits: languages.TextEdit[] | null | undefined): Promise<languages.TextEdit[] | undefined> {
|
||||
public computeMoreMinimalEdits(resource: URI, edits: languages.TextEdit[] | null | undefined, pretty: boolean = false): Promise<languages.TextEdit[] | undefined> {
|
||||
if (isNonEmptyArray(edits)) {
|
||||
if (!canSyncModel(this._modelService, resource)) {
|
||||
return Promise.resolve(edits); // File too large
|
||||
}
|
||||
const sw = StopWatch.create(true);
|
||||
const result = this._workerManager.withWorker().then(client => client.computeMoreMinimalEdits(resource, edits));
|
||||
const result = this._workerManager.withWorker().then(client => client.computeMoreMinimalEdits(resource, edits, pretty));
|
||||
result.finally(() => this._logService.trace('FORMAT#computeMoreMinimalEdits', resource.toString(true), sw.elapsed()));
|
||||
return Promise.race([result, timeout(1000).then(() => edits)]);
|
||||
|
||||
|
@ -528,9 +528,9 @@ export class EditorWorkerClient extends Disposable implements IEditorWorkerClien
|
|||
});
|
||||
}
|
||||
|
||||
public computeMoreMinimalEdits(resource: URI, edits: languages.TextEdit[]): Promise<languages.TextEdit[]> {
|
||||
public computeMoreMinimalEdits(resource: URI, edits: languages.TextEdit[], pretty: boolean): Promise<languages.TextEdit[]> {
|
||||
return this._withSyncedResources([resource]).then(proxy => {
|
||||
return proxy.computeMoreMinimalEdits(resource.toString(), edits);
|
||||
return proxy.computeMoreMinimalEdits(resource.toString(), edits, pretty);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -99,7 +99,7 @@ export class BlockDecorations extends ViewPart {
|
|||
bottom = ctx.getVerticalOffsetAfterLineNumber(decoration.range.endLineNumber, true);
|
||||
} else {
|
||||
top = ctx.getVerticalOffsetForLineNumber(decoration.range.startLineNumber, true);
|
||||
bottom = decoration.range.isEmpty()
|
||||
bottom = decoration.range.isEmpty() && !decoration.options.blockDoesNotCollapse
|
||||
? ctx.getVerticalOffsetForLineNumber(decoration.range.startLineNumber, false)
|
||||
: ctx.getVerticalOffsetAfterLineNumber(decoration.range.endLineNumber, true);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import * as objects from 'vs/base/common/objects';
|
||||
import { ICodeEditor, IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||
import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||
import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget';
|
||||
import { ConfigurationChangedEvent, IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
|
@ -29,6 +29,7 @@ export class EmbeddedCodeEditorWidget extends CodeEditorWidget {
|
|||
constructor(
|
||||
domElement: HTMLElement,
|
||||
options: IEditorOptions,
|
||||
codeEditorWidgetOptions: ICodeEditorWidgetOptions,
|
||||
parentEditor: ICodeEditor,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@ICodeEditorService codeEditorService: ICodeEditorService,
|
||||
|
@ -40,7 +41,7 @@ export class EmbeddedCodeEditorWidget extends CodeEditorWidget {
|
|||
@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService,
|
||||
@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,
|
||||
) {
|
||||
super(domElement, { ...parentEditor.getRawOptions(), overflowWidgetsDomNode: parentEditor.getOverflowWidgetsDomNode() }, {}, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService, languageConfigurationService, languageFeaturesService);
|
||||
super(domElement, { ...parentEditor.getRawOptions(), overflowWidgetsDomNode: parentEditor.getOverflowWidgetsDomNode() }, codeEditorWidgetOptions, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService, languageConfigurationService, languageFeaturesService);
|
||||
|
||||
this._parentEditor = parentEditor;
|
||||
this._overwriteOptions = options;
|
||||
|
|
|
@ -99,6 +99,7 @@ export interface IModelDecorationOptions {
|
|||
* In this case, the range must be empty and set to the last line.
|
||||
*/
|
||||
blockIsAfterEnd?: boolean | null;
|
||||
blockDoesNotCollapse?: boolean | null;
|
||||
blockPadding?: [top: number, right: number, bottom: number, left: number] | null;
|
||||
|
||||
/**
|
||||
|
|
|
@ -2248,6 +2248,7 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions {
|
|||
readonly description: string;
|
||||
readonly blockClassName: string | null;
|
||||
readonly blockIsAfterEnd: boolean | null;
|
||||
readonly blockDoesNotCollapse?: boolean | null;
|
||||
readonly blockPadding: [top: number, right: number, bottom: number, left: number] | null;
|
||||
readonly stickiness: model.TrackedRangeStickiness;
|
||||
readonly zIndex: number;
|
||||
|
@ -2275,6 +2276,7 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions {
|
|||
private constructor(options: model.IModelDecorationOptions) {
|
||||
this.description = options.description;
|
||||
this.blockClassName = options.blockClassName ? cleanClassName(options.blockClassName) : null;
|
||||
this.blockDoesNotCollapse = options.blockDoesNotCollapse ?? null;
|
||||
this.blockIsAfterEnd = options.blockIsAfterEnd ?? null;
|
||||
this.blockPadding = options.blockPadding ?? null;
|
||||
this.stickiness = options.stickiness || model.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges;
|
||||
|
|
|
@ -461,7 +461,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
|
|||
|
||||
private static readonly _diffLimit = 100000;
|
||||
|
||||
public async computeMoreMinimalEdits(modelUrl: string, edits: TextEdit[]): Promise<TextEdit[]> {
|
||||
public async computeMoreMinimalEdits(modelUrl: string, edits: TextEdit[], pretty: boolean): Promise<TextEdit[]> {
|
||||
const model = this._getModel(modelUrl);
|
||||
if (!model) {
|
||||
return edits;
|
||||
|
@ -506,7 +506,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
|
|||
}
|
||||
|
||||
// compute diff between original and edit.text
|
||||
const changes = stringDiff(original, text, false);
|
||||
const changes = stringDiff(original, text, pretty);
|
||||
const editOffset = model.offsetAt(Range.lift(range).getStartPosition());
|
||||
|
||||
for (const change of changes) {
|
||||
|
|
|
@ -28,7 +28,7 @@ export interface IEditorWorkerService {
|
|||
canComputeDirtyDiff(original: URI, modified: URI): boolean;
|
||||
computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise<IChange[] | null>;
|
||||
|
||||
computeMoreMinimalEdits(resource: URI, edits: TextEdit[] | null | undefined): Promise<TextEdit[] | undefined>;
|
||||
computeMoreMinimalEdits(resource: URI, edits: TextEdit[] | null | undefined, pretty?: boolean): Promise<TextEdit[] | undefined>;
|
||||
|
||||
canComputeWordRanges(resource: URI): boolean;
|
||||
computeWordRanges(resource: URI, range: IRange): Promise<{ [word: string]: IRange[] } | null>;
|
||||
|
|
|
@ -92,7 +92,7 @@ export abstract class SymbolNavigationAction extends EditorAction2 {
|
|||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return <typeof opts>result;
|
||||
}
|
||||
|
||||
readonly configuration: SymbolNavigationActionConfig;
|
||||
|
|
|
@ -313,7 +313,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget {
|
|||
enabled: false
|
||||
}
|
||||
};
|
||||
this._preview = this._instantiationService.createInstance(EmbeddedCodeEditorWidget, this._previewContainer, options, this.editor);
|
||||
this._preview = this._instantiationService.createInstance(EmbeddedCodeEditorWidget, this._previewContainer, options, {}, this.editor);
|
||||
dom.hide(this._previewContainer);
|
||||
this._previewNotAvailableMessage = new TextModel(nls.localize('missingPreviewMessage', "no preview available"), PLAINTEXT_LANGUAGE_ID, TextModel.DEFAULT_CREATION_OPTIONS, null, this._undoRedoService, this._languageService, this._languageConfigurationService);
|
||||
|
||||
|
|
|
@ -710,7 +710,7 @@ export class SynchronizedInlineCompletionsCache extends Disposable {
|
|||
for (const c of this.completions) {
|
||||
const newRange = model.getDecorationRange(c.decorationId);
|
||||
if (!newRange) {
|
||||
onUnexpectedError(new Error('Decoration has no range'));
|
||||
// onUnexpectedError(new Error('Decoration has no range'));
|
||||
continue;
|
||||
}
|
||||
if (!c.synchronizedRange.equalsRange(newRange)) {
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { InteractiveEditorController } from 'vs/editor/contrib/interactive/browser/interactiveEditorWidget';
|
||||
import * as interactiveEditorActions from 'vs/editor/contrib/interactive/browser/interactiveEditorActions';
|
||||
import { IInteractiveEditorService } from 'vs/editor/contrib/interactive/common/interactiveEditor';
|
||||
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { InteractiveEditorServiceImpl } from 'vs/editor/contrib/interactive/common/interactiveEditorServiceImpl';
|
||||
|
||||
registerSingleton(IInteractiveEditorService, InteractiveEditorServiceImpl, InstantiationType.Delayed);
|
||||
|
||||
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);
|
124
src/vs/editor/contrib/interactive/browser/interactiveEditor.css
Normal file
124
src/vs/editor/contrib/interactive/browser/interactiveEditor.css
Normal file
|
@ -0,0 +1,124 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-editor .interactive-editor {
|
||||
z-index: 100;
|
||||
color: inherit;
|
||||
padding: 6px 0 6px 6px;
|
||||
}
|
||||
|
||||
/* body */
|
||||
|
||||
.monaco-editor .interactive-editor .body {
|
||||
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;
|
||||
margin: 0;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
list-style-type: disc;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.monaco-editor .interactive-editor .body .history:not(:empty) {
|
||||
margin: 0;
|
||||
padding: 4px 0 0 12px;
|
||||
}
|
||||
|
||||
.monaco-editor .interactive-editor .body .history.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-editor .interactive-editor .body.preview {
|
||||
padding: 4px 4px 0 4px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.monaco-editor .interactive-editor .monaco-editor-background {
|
||||
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;
|
||||
padding: 3px 0 0 2px;
|
||||
color: var(--vscode-input-placeholderForeground);
|
||||
font-family: var(--monaco-monospace-font);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.monaco-editor .interactive-editor .body .content .input .editor-placeholder.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-editor .interactive-editor .body .content .input .editor-container {
|
||||
/* height: 24px; */
|
||||
vertical-align: middle;
|
||||
}
|
||||
.monaco-editor .interactive-editor .body .toolbar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-self: start;
|
||||
padding: 4px 8px 0 2px;
|
||||
}
|
||||
|
||||
.monaco-editor .interactive-editor .body .toolbar .actions-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* progress bit */
|
||||
|
||||
.monaco-editor .interactive-editor .progress {
|
||||
position: relative;
|
||||
width: calc(100% - 18px);
|
||||
left: 19px;
|
||||
}
|
||||
|
||||
/* UGLY - fighting against workbench styles */
|
||||
.monaco-workbench .part.editor > .content .monaco-editor .interactive-editor .progress .monaco-progress-container {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
/* decoration styles */
|
||||
|
||||
|
||||
.monaco-editor .interactive-editor-lines-deleted-range-inline {
|
||||
text-decoration: line-through;
|
||||
background-color: var(--vscode-diffEditor-removedTextBackground);
|
||||
opacity: 0.6;
|
||||
}
|
||||
.monaco-editor .interactive-editor-lines-inserted-range {
|
||||
background-color: var(--vscode-diffEditor-insertedTextBackground);
|
||||
}
|
||||
|
||||
.monaco-editor .interactive-editor-block {
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--vscode-widget-border);
|
||||
box-shadow: 0 0 8px 1px var(--vscode-widget-shadow);
|
||||
}
|
|
@ -0,0 +1,328 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorAction2 } from 'vs/editor/browser/editorExtensions';
|
||||
import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { InteractiveEditorController } from 'vs/editor/contrib/interactive/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, MENU_INTERACTIVE_EDITOR_WIDGET_LHS, CTX_INTERACTIVE_EDITOR_HISTORY_VISIBLE } from 'vs/editor/contrib/interactive/common/interactiveEditor';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IAction2Options } from 'vs/platform/actions/common/actions';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
|
||||
export class StartSessionAction extends EditorAction2 {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'interactiveEditor.start',
|
||||
title: { value: localize('run', 'Start Session'), original: 'Start Session' },
|
||||
category: AbstractInteractiveEditorAction.category,
|
||||
f1: true,
|
||||
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_HAS_PROVIDER, EditorContextKeys.writable, CTX_INTERACTIVE_EDITOR_VISIBLE.negate()),
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyI)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) {
|
||||
InteractiveEditorController.get(editor)?.run();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractInteractiveEditorAction extends EditorAction2 {
|
||||
|
||||
static readonly category = { value: localize('cat', 'Interactive Editor'), original: 'Interactive Editor' };
|
||||
|
||||
constructor(desc: IAction2Options) {
|
||||
super({
|
||||
...desc,
|
||||
category: AbstractInteractiveEditorAction.category,
|
||||
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_HAS_PROVIDER, desc.precondition)
|
||||
});
|
||||
}
|
||||
|
||||
override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) {
|
||||
if (editor instanceof EmbeddedCodeEditorWidget) {
|
||||
editor = editor.getParentEditor();
|
||||
}
|
||||
const ctrl = InteractiveEditorController.get(editor);
|
||||
if (!ctrl) {
|
||||
return;
|
||||
}
|
||||
this.runInteractiveEditorCommand(accessor, ctrl, editor, ..._args);
|
||||
}
|
||||
|
||||
abstract runInteractiveEditorCommand(accessor: ServicesAccessor, ctrl: InteractiveEditorController, editor: ICodeEditor, ...args: any[]): void;
|
||||
}
|
||||
|
||||
|
||||
export class MakeRequestAction extends AbstractInteractiveEditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'interactiveEditor.accept',
|
||||
title: localize('accept', 'Make Request'),
|
||||
icon: Codicon.arrowUp,
|
||||
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_VISIBLE, CTX_INTERACTIVE_EDITOR_EMPTY.negate()),
|
||||
keybinding: {
|
||||
when: CTX_INTERACTIVE_EDITOR_FOCUSED,
|
||||
weight: KeybindingWeight.EditorCore + 7,
|
||||
primary: KeyCode.Enter
|
||||
},
|
||||
menu: {
|
||||
id: MENU_INTERACTIVE_EDITOR_WIDGET,
|
||||
group: 'main',
|
||||
order: 1,
|
||||
when: CTX_INTERACTIVE_EDITOR_HAS_ACTIVE_REQUEST.isEqualTo(false)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
runInteractiveEditorCommand(_accessor: ServicesAccessor, ctrl: InteractiveEditorController, _editor: ICodeEditor, ..._args: any[]): void {
|
||||
ctrl.accept();
|
||||
}
|
||||
}
|
||||
|
||||
export class StopRequestAction extends AbstractInteractiveEditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'interactiveEditor.stop',
|
||||
title: localize('stop', 'Stop Request'),
|
||||
icon: Codicon.debugStop,
|
||||
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_VISIBLE, CTX_INTERACTIVE_EDITOR_EMPTY.negate(), CTX_INTERACTIVE_EDITOR_HAS_ACTIVE_REQUEST),
|
||||
menu: {
|
||||
id: MENU_INTERACTIVE_EDITOR_WIDGET,
|
||||
group: 'main',
|
||||
order: 1,
|
||||
when: CTX_INTERACTIVE_EDITOR_HAS_ACTIVE_REQUEST
|
||||
},
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.EditorContrib,
|
||||
primary: KeyCode.Escape
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
runInteractiveEditorCommand(_accessor: ServicesAccessor, ctrl: InteractiveEditorController, _editor: ICodeEditor, ..._args: any[]): void {
|
||||
ctrl.cancelCurrentRequest();
|
||||
}
|
||||
}
|
||||
|
||||
export class AcceptWithPreviewInteractiveEditorAction extends AbstractInteractiveEditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'interactiveEditor.acceptWithPreview',
|
||||
title: localize('acceptPreview', 'Ask Question & Preview Reply'),
|
||||
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_VISIBLE, CTX_INTERACTIVE_EDITOR_EMPTY.negate()),
|
||||
keybinding: {
|
||||
when: CTX_INTERACTIVE_EDITOR_FOCUSED,
|
||||
weight: KeybindingWeight.EditorCore + 7,
|
||||
primary: KeyMod.Shift + KeyCode.Enter
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
runInteractiveEditorCommand(_accessor: ServicesAccessor, ctrl: InteractiveEditorController, _editor: ICodeEditor, ..._args: any[]): void {
|
||||
ctrl.accept(true);
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
super({
|
||||
id: 'interactiveEditor.cancel',
|
||||
title: localize('cancel', 'Cancel'),
|
||||
precondition: CTX_INTERACTIVE_EDITOR_VISIBLE,
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.EditorContrib - 1,
|
||||
primary: KeyCode.Escape
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
runInteractiveEditorCommand(_accessor: ServicesAccessor, ctrl: InteractiveEditorController, _editor: ICodeEditor, ..._args: any[]): void {
|
||||
ctrl.cancelSession();
|
||||
}
|
||||
}
|
||||
|
||||
export class ArrowOutUpAction extends AbstractInteractiveEditorAction {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'interactiveEditor.arrowOutUp',
|
||||
title: localize('arrowUp', 'Cursor Up'),
|
||||
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_FIRST),
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.EditorCore,
|
||||
primary: KeyCode.UpArrow
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
runInteractiveEditorCommand(_accessor: ServicesAccessor, ctrl: InteractiveEditorController, _editor: ICodeEditor, ..._args: any[]): void {
|
||||
ctrl.arrowOut(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class ArrowOutDownAction extends AbstractInteractiveEditorAction {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'interactiveEditor.arrowOutDown',
|
||||
title: localize('arrowDown', 'Cursor Down'),
|
||||
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_LAST),
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.EditorCore,
|
||||
primary: KeyCode.DownArrow
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
runInteractiveEditorCommand(_accessor: ServicesAccessor, ctrl: InteractiveEditorController, _editor: ICodeEditor, ..._args: any[]): void {
|
||||
ctrl.arrowOut(false);
|
||||
}
|
||||
}
|
||||
|
||||
export class FocusInteractiveEditor extends EditorAction2 {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'interactiveEditor.focus',
|
||||
title: localize('focus', 'Focus'),
|
||||
category: AbstractInteractiveEditorAction.category,
|
||||
precondition: ContextKeyExpr.and(EditorContextKeys.editorTextFocus, CTX_INTERACTIVE_EDITOR_VISIBLE, CTX_INTERACTIVE_EDITOR_FOCUSED.negate()),
|
||||
keybinding: [{
|
||||
weight: KeybindingWeight.EditorCore + 10, // win against core_command
|
||||
when: CTX_INTERACTIVE_EDITOR_OUTER_CURSOR_POSITION.isEqualTo('above'),
|
||||
primary: KeyCode.DownArrow,
|
||||
}, {
|
||||
weight: KeybindingWeight.EditorCore + 10, // win against core_command
|
||||
when: CTX_INTERACTIVE_EDITOR_OUTER_CURSOR_POSITION.isEqualTo('below'),
|
||||
primary: KeyCode.UpArrow,
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) {
|
||||
InteractiveEditorController.get(editor)?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
export class PreviousFromHistory extends AbstractInteractiveEditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'interactiveEditor.previousFromHistory',
|
||||
title: localize('previousFromHistory', 'Previous From History'),
|
||||
precondition: CTX_INTERACTIVE_EDITOR_FOCUSED,
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.EditorCore + 10, // win against core_command
|
||||
primary: KeyMod.CtrlCmd | KeyCode.UpArrow,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
override runInteractiveEditorCommand(_accessor: ServicesAccessor, ctrl: InteractiveEditorController, _editor: ICodeEditor, ..._args: any[]): void {
|
||||
ctrl.populateHistory(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class NextFromHistory extends AbstractInteractiveEditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'interactiveEditor.nextFromHistory',
|
||||
title: localize('nextFromHistory', 'Next From History'),
|
||||
precondition: CTX_INTERACTIVE_EDITOR_FOCUSED,
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.EditorCore + 10, // win against core_command
|
||||
primary: KeyMod.CtrlCmd | KeyCode.DownArrow,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
override runInteractiveEditorCommand(_accessor: ServicesAccessor, ctrl: InteractiveEditorController, _editor: ICodeEditor, ..._args: any[]): void {
|
||||
ctrl.populateHistory(false);
|
||||
}
|
||||
}
|
||||
|
||||
export class UndoCommand extends AbstractInteractiveEditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'interactiveEditor.undo',
|
||||
title: localize('undo', 'Undo'),
|
||||
icon: Codicon.commentDiscussion,
|
||||
precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_VISIBLE),
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.EditorContrib + 10,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KeyZ,
|
||||
},
|
||||
menu: {
|
||||
id: MENU_INTERACTIVE_EDITOR_WIDGET,
|
||||
group: 'B',
|
||||
order: 1
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
override runInteractiveEditorCommand(_accessor: ServicesAccessor, _ctrl: InteractiveEditorController, editor: ICodeEditor, ..._args: any[]): void {
|
||||
editor.getModel()?.undo();
|
||||
}
|
||||
}
|
||||
|
||||
export class ToggleHistory extends AbstractInteractiveEditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'interactiveEditor.toggleHistory',
|
||||
title: localize('toggleHistory', 'Toggle History'),
|
||||
icon: Codicon.chevronRight,
|
||||
precondition: CTX_INTERACTIVE_EDITOR_VISIBLE,
|
||||
toggled: {
|
||||
condition: CTX_INTERACTIVE_EDITOR_HISTORY_VISIBLE,
|
||||
icon: Codicon.chevronDown,
|
||||
},
|
||||
menu: {
|
||||
id: MENU_INTERACTIVE_EDITOR_WIDGET_LHS,
|
||||
group: 'main',
|
||||
order: 1
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
override runInteractiveEditorCommand(_accessor: ServicesAccessor, ctrl: InteractiveEditorController, _editor: ICodeEditor, ..._args: any[]): void {
|
||||
ctrl.toggleHistory();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,744 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./interactiveEditor';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IInstantiationService } 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, MENU_INTERACTIVE_EDITOR_WIDGET_LHS } from 'vs/editor/contrib/interactive/common/interactiveEditor';
|
||||
import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { IModelDeltaDecoration, ITextModel, IValidEditOperation } from 'vs/editor/common/model';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { Dimension, addDisposableListener, getTotalHeight, getTotalWidth, h, reset } from 'vs/base/browser/dom';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration';
|
||||
import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||
import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
|
||||
import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
|
||||
import { IModelService } from 'vs/editor/common/services/model';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
|
||||
import { GhostTextController } from 'vs/editor/contrib/inlineCompletions/browser/ghostTextController';
|
||||
import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar';
|
||||
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
|
||||
import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { raceCancellationError } from 'vs/base/common/async';
|
||||
import { isCancellationError } from 'vs/base/common/errors';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
|
||||
|
||||
class InteractiveEditorWidget {
|
||||
|
||||
private static _noop = () => { };
|
||||
|
||||
private readonly _elements = h(
|
||||
'div.interactive-editor@root',
|
||||
[
|
||||
h('div.body', [
|
||||
h('div.toolbar@lhsToolbar'),
|
||||
h('div.content', [
|
||||
h('div.input@input', [
|
||||
h('div.editor-placeholder@placeholder'),
|
||||
h('div.editor-container@editor'),
|
||||
]),
|
||||
h('ol.history.hidden@history'),
|
||||
]),
|
||||
h('div.toolbar@rhsToolbar'),
|
||||
]),
|
||||
h('div.progress@progress')
|
||||
]
|
||||
);
|
||||
|
||||
private readonly _store = new DisposableStore();
|
||||
|
||||
private readonly _inputEditor: ICodeEditor;
|
||||
private readonly _inputModel: ITextModel;
|
||||
private readonly _ctxInputEmpty: IContextKey<boolean>;
|
||||
private readonly _ctxHistoryVisible: IContextKey<boolean>;
|
||||
|
||||
private readonly _progressBar: ProgressBar;
|
||||
|
||||
private readonly _onDidChangeHeight = new Emitter<void>();
|
||||
readonly onDidChangeHeight: Event<void> = this._onDidChangeHeight.event;
|
||||
|
||||
private _isExpanded = false;
|
||||
private _editorDim: Dimension | undefined;
|
||||
|
||||
public acceptInput: (preview: boolean) => void = InteractiveEditorWidget._noop;
|
||||
private _cancelInput: () => void = InteractiveEditorWidget._noop;
|
||||
|
||||
constructor(
|
||||
parentEditor: ICodeEditor | undefined,
|
||||
@IModelService private readonly _modelService: IModelService,
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
) {
|
||||
|
||||
this._ctxHistoryVisible = CTX_INTERACTIVE_EDITOR_HISTORY_VISIBLE.bindTo(this._contextKeyService);
|
||||
|
||||
// editor logic
|
||||
const editorOptions: IEditorConstructionOptions = {
|
||||
ariaLabel: localize('aria-label', "Interactive Editor Input"),
|
||||
wordWrap: 'on',
|
||||
overviewRulerLanes: 0,
|
||||
glyphMargin: false,
|
||||
lineNumbers: 'off',
|
||||
folding: false,
|
||||
selectOnLineNumbers: false,
|
||||
hideCursorInOverviewRuler: true,
|
||||
selectionHighlight: false,
|
||||
scrollbar: {
|
||||
useShadows: false,
|
||||
vertical: 'hidden',
|
||||
horizontal: 'auto',
|
||||
// alwaysConsumeMouseWheel: false
|
||||
},
|
||||
lineDecorationsWidth: 0,
|
||||
overviewRulerBorder: false,
|
||||
scrollBeyondLastLine: false,
|
||||
renderLineHighlight: 'none',
|
||||
fixedOverflowWidgets: true,
|
||||
dragAndDrop: false,
|
||||
revealHorizontalRightPadding: 5,
|
||||
minimap: { enabled: false },
|
||||
guides: { indentation: false },
|
||||
cursorWidth: 2,
|
||||
wrappingStrategy: 'advanced',
|
||||
wrappingIndent: 'none',
|
||||
padding: { top: 3, bottom: 2 },
|
||||
renderWhitespace: 'none',
|
||||
dropIntoEditor: { enabled: true },
|
||||
|
||||
quickSuggestions: false,
|
||||
suggest: {
|
||||
showIcons: false,
|
||||
showSnippets: false,
|
||||
}
|
||||
};
|
||||
|
||||
const codeEditorWidgetOptions: ICodeEditorWidgetOptions = {
|
||||
isSimpleWidget: true,
|
||||
contributions: EditorExtensionsRegistry.getSomeEditorContributions([
|
||||
SnippetController2.ID,
|
||||
GhostTextController.ID,
|
||||
SuggestController.ID
|
||||
])
|
||||
};
|
||||
|
||||
this._inputEditor = parentEditor
|
||||
? this._instantiationService.createInstance(EmbeddedCodeEditorWidget, this._elements.editor, editorOptions, codeEditorWidgetOptions, parentEditor)
|
||||
: this._instantiationService.createInstance(CodeEditorWidget, this._elements.editor, editorOptions, codeEditorWidgetOptions);
|
||||
this._store.add(this._inputEditor);
|
||||
|
||||
const uri = URI.from({ scheme: 'vscode', authority: 'interactive-editor', path: `/interactive-editor/model.txt` });
|
||||
this._inputModel = this._modelService.getModel(uri) ?? this._modelService.createModel('', null, uri);
|
||||
this._inputEditor.setModel(this._inputModel);
|
||||
|
||||
|
||||
|
||||
// show/hide placeholder depending on text model being empty
|
||||
// content height
|
||||
|
||||
const currentContentHeight = 0;
|
||||
|
||||
this._ctxInputEmpty = CTX_INTERACTIVE_EDITOR_EMPTY.bindTo(this._contextKeyService);
|
||||
const togglePlaceholder = () => {
|
||||
const hasText = this._inputModel.getValueLength() > 0;
|
||||
this._elements.placeholder.classList.toggle('hidden', hasText);
|
||||
this._ctxInputEmpty.set(!hasText);
|
||||
|
||||
const contentHeight = this._inputEditor.getContentHeight();
|
||||
if (contentHeight !== currentContentHeight && this._editorDim) {
|
||||
this._editorDim = this._editorDim.with(undefined, contentHeight);
|
||||
this._inputEditor.layout(this._editorDim);
|
||||
this._onDidChangeHeight.fire();
|
||||
}
|
||||
};
|
||||
this._store.add(this._inputModel.onDidChangeContent(togglePlaceholder));
|
||||
togglePlaceholder();
|
||||
|
||||
this._store.add(addDisposableListener(this._elements.placeholder, 'click', () => this._inputEditor.focus()));
|
||||
|
||||
const lhsToolbar = this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.lhsToolbar, MENU_INTERACTIVE_EDITOR_WIDGET_LHS, {
|
||||
telemetrySource: 'interactiveEditorWidget-toolbar-lhs',
|
||||
toolbarOptions: { primaryGroup: 'main' }
|
||||
});
|
||||
this._store.add(lhsToolbar);
|
||||
|
||||
const rhsToolbar = this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.rhsToolbar, MENU_INTERACTIVE_EDITOR_WIDGET, {
|
||||
telemetrySource: 'interactiveEditorWidget-toolbar-rhs',
|
||||
toolbarOptions: { primaryGroup: 'main' }
|
||||
});
|
||||
this._store.add(rhsToolbar);
|
||||
|
||||
this._progressBar = new ProgressBar(this._elements.progress);
|
||||
this._store.add(this._progressBar);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._store.dispose();
|
||||
this._ctxInputEmpty.reset();
|
||||
this._ctxHistoryVisible.reset();
|
||||
}
|
||||
|
||||
getDomNode(): HTMLElement {
|
||||
return this._elements.root;
|
||||
}
|
||||
|
||||
layout(dim: Dimension) {
|
||||
|
||||
const innerEditorWidth = Math.min(
|
||||
Number.MAX_SAFE_INTEGER, // TODO@jrieken define max width?
|
||||
dim.width - (getTotalWidth(this._elements.lhsToolbar) + getTotalWidth(this._elements.rhsToolbar) + 12 /* L/R-padding */)
|
||||
);
|
||||
const newDim = new Dimension(innerEditorWidth, this._inputEditor.getContentHeight());
|
||||
if (!this._editorDim || !Dimension.equals(this._editorDim, newDim)) {
|
||||
this._editorDim = newDim;
|
||||
this._inputEditor.layout(this._editorDim);
|
||||
|
||||
this._elements.placeholder.style.width = `${innerEditorWidth - 4 /* input-padding*/}px`;
|
||||
}
|
||||
}
|
||||
|
||||
getHeight(): number {
|
||||
return this._inputEditor.getContentHeight() + getTotalHeight(this._elements.history);
|
||||
}
|
||||
|
||||
updateProgress(show: boolean) {
|
||||
if (show) {
|
||||
this._progressBar.infinite();
|
||||
} else {
|
||||
this._progressBar.stop();
|
||||
}
|
||||
}
|
||||
|
||||
getInput(placeholder: string, value: string, token: CancellationToken): Promise<{ value: string; preview: boolean } | undefined> {
|
||||
|
||||
this._elements.placeholder.innerText = placeholder;
|
||||
this._elements.placeholder.style.fontSize = `${this._inputEditor.getOption(EditorOption.fontSize)}px`;
|
||||
this._elements.placeholder.style.lineHeight = `${this._inputEditor.getOption(EditorOption.lineHeight)}px`;
|
||||
this._inputModel.setValue(value);
|
||||
|
||||
const disposeOnDone = new DisposableStore();
|
||||
|
||||
disposeOnDone.add(this._inputEditor.onDidLayoutChange(() => this._onDidChangeHeight.fire()));
|
||||
|
||||
const ctxInnerCursorFirst = CTX_INTERACTIVE_EDITOR_INNER_CURSOR_FIRST.bindTo(this._contextKeyService);
|
||||
const ctxInnerCursorLast = CTX_INTERACTIVE_EDITOR_INNER_CURSOR_LAST.bindTo(this._contextKeyService);
|
||||
const ctxInputEditorFocused = CTX_INTERACTIVE_EDITOR_FOCUSED.bindTo(this._contextKeyService);
|
||||
|
||||
return new Promise<{ value: string; preview: boolean } | undefined>(resolve => {
|
||||
|
||||
this._cancelInput = () => {
|
||||
this.acceptInput = InteractiveEditorWidget._noop;
|
||||
this._cancelInput = InteractiveEditorWidget._noop;
|
||||
resolve(undefined);
|
||||
return true;
|
||||
};
|
||||
|
||||
this.acceptInput = (preview) => {
|
||||
const newValue = this._inputEditor.getModel()!.getValue();
|
||||
if (newValue.trim().length === 0) {
|
||||
// empty or whitespace only
|
||||
this._cancelInput();
|
||||
return;
|
||||
}
|
||||
|
||||
this.acceptInput = InteractiveEditorWidget._noop;
|
||||
this._cancelInput = InteractiveEditorWidget._noop;
|
||||
resolve({ value: newValue, preview });
|
||||
|
||||
const entry = document.createElement('li');
|
||||
entry.classList.add('history-entry');
|
||||
entry.innerText = newValue;
|
||||
|
||||
this._elements.history.insertBefore(entry, this._elements.history.firstChild);
|
||||
this._onDidChangeHeight.fire();
|
||||
};
|
||||
|
||||
disposeOnDone.add(token.onCancellationRequested(() => this._cancelInput()));
|
||||
|
||||
// CONTEXT KEYS
|
||||
|
||||
// (1) inner cursor position (last/first line selected)
|
||||
const updateInnerCursorFirstLast = () => {
|
||||
if (!this._inputEditor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
const { lineNumber } = this._inputEditor.getPosition();
|
||||
ctxInnerCursorFirst.set(lineNumber === 1);
|
||||
ctxInnerCursorLast.set(lineNumber === this._inputEditor.getModel().getLineCount());
|
||||
};
|
||||
disposeOnDone.add(this._inputEditor.onDidChangeCursorPosition(updateInnerCursorFirstLast));
|
||||
updateInnerCursorFirstLast();
|
||||
|
||||
// (2) input editor focused or not
|
||||
const updateFocused = () => {
|
||||
const hasFocus = this._inputEditor.hasWidgetFocus();
|
||||
ctxInputEditorFocused.set(hasFocus);
|
||||
this._elements.input.classList.toggle('synthetic-focus', hasFocus);
|
||||
};
|
||||
disposeOnDone.add(this._inputEditor.onDidFocusEditorWidget(updateFocused));
|
||||
disposeOnDone.add(this._inputEditor.onDidBlurEditorWidget(updateFocused));
|
||||
updateFocused();
|
||||
|
||||
this.focus();
|
||||
|
||||
}).finally(() => {
|
||||
disposeOnDone.dispose();
|
||||
|
||||
ctxInnerCursorFirst.reset();
|
||||
ctxInnerCursorLast.reset();
|
||||
ctxInputEditorFocused.reset();
|
||||
});
|
||||
}
|
||||
|
||||
populateInputField(value: string) {
|
||||
this._inputModel.setValue(value.trim());
|
||||
this._inputEditor.setSelection(this._inputModel.getFullModelRange());
|
||||
}
|
||||
|
||||
toggleHistory(): void {
|
||||
this._isExpanded = !this._isExpanded;
|
||||
this._elements.history.classList.toggle('hidden', !this._isExpanded);
|
||||
this._ctxHistoryVisible.set(this._isExpanded);
|
||||
this._onDidChangeHeight.fire();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._ctxInputEmpty.reset();
|
||||
|
||||
// empty history
|
||||
this._isExpanded = false;
|
||||
this._elements.history.classList.toggle('hidden', true);
|
||||
this._ctxHistoryVisible.reset();
|
||||
reset(this._elements.history);
|
||||
}
|
||||
|
||||
focus() {
|
||||
this._inputEditor.focus();
|
||||
}
|
||||
}
|
||||
|
||||
export class InteractiveEditorZoneWidget extends ZoneWidget {
|
||||
|
||||
readonly widget: InteractiveEditorWidget;
|
||||
|
||||
private readonly _ctxVisible: IContextKey<boolean>;
|
||||
private readonly _ctxCursorPosition: IContextKey<'above' | 'below' | ''>;
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
@IInstantiationService private readonly _instaService: IInstantiationService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
) {
|
||||
super(editor, { showFrame: false, showArrow: false, isAccessible: true, className: 'interactive-editor-widget', keepEditorSelection: true });
|
||||
|
||||
this._ctxVisible = CTX_INTERACTIVE_EDITOR_VISIBLE.bindTo(contextKeyService);
|
||||
this._ctxCursorPosition = CTX_INTERACTIVE_EDITOR_OUTER_CURSOR_POSITION.bindTo(contextKeyService);
|
||||
|
||||
this._disposables.add(toDisposable(() => {
|
||||
this._ctxVisible.reset();
|
||||
this._ctxCursorPosition.reset();
|
||||
}));
|
||||
|
||||
this.widget = this._instaService.createInstance(InteractiveEditorWidget, this.editor);
|
||||
this._disposables.add(this.widget.onDidChangeHeight(() => this._relayout()));
|
||||
this._disposables.add(this.widget);
|
||||
this.create();
|
||||
|
||||
|
||||
// todo@jrieken listen ONLY when showing
|
||||
const updateCursorIsAboveContextKey = () => {
|
||||
if (!this.position || !this.editor.hasModel()) {
|
||||
this._ctxCursorPosition.reset();
|
||||
} else if (this.position.lineNumber === this.editor.getPosition().lineNumber) {
|
||||
this._ctxCursorPosition.set('above');
|
||||
} else if (this.position.lineNumber + 1 === this.editor.getPosition().lineNumber) {
|
||||
this._ctxCursorPosition.set('below');
|
||||
} else {
|
||||
this._ctxCursorPosition.reset();
|
||||
}
|
||||
};
|
||||
this._disposables.add(this.editor.onDidChangeCursorPosition(e => updateCursorIsAboveContextKey()));
|
||||
this._disposables.add(this.editor.onDidFocusEditorText(e => updateCursorIsAboveContextKey()));
|
||||
updateCursorIsAboveContextKey();
|
||||
}
|
||||
|
||||
protected override _fillContainer(container: HTMLElement): void {
|
||||
container.appendChild(this.widget.getDomNode());
|
||||
}
|
||||
|
||||
protected override _getWidth(info: EditorLayoutInfo): number {
|
||||
// TODO@jrieken
|
||||
// makes the zone widget wider than wanted but this aligns
|
||||
// it with wholeLine decorations that are added above
|
||||
return info.width;
|
||||
}
|
||||
|
||||
private _dimension?: Dimension;
|
||||
|
||||
protected override _onWidth(widthInPixel: number): void {
|
||||
if (this._dimension) {
|
||||
this._doLayout(this._dimension.height, widthInPixel);
|
||||
}
|
||||
}
|
||||
|
||||
protected override _doLayout(heightInPixel: number, widthInPixel: number): void {
|
||||
|
||||
const info = this.editor.getLayoutInfo();
|
||||
const spaceLeft = info.lineNumbersWidth + info.glyphMarginWidth + info.decorationsWidth;
|
||||
const spaceRight = info.minimap.minimapWidth + info.verticalScrollbarWidth;
|
||||
|
||||
const width = widthInPixel - (spaceLeft + spaceRight);
|
||||
this._dimension = new Dimension(width, heightInPixel);
|
||||
this.widget.getDomNode().style.marginLeft = `${spaceLeft}px`;
|
||||
this.widget.getDomNode().style.marginRight = `${spaceRight}px`;
|
||||
this.widget.layout(this._dimension);
|
||||
}
|
||||
|
||||
private _computeHeightInLines(): number {
|
||||
const lineHeight = this.editor.getOption(EditorOption.lineHeight);
|
||||
const contentHeightInLines = (this.widget.getHeight() / lineHeight);
|
||||
return 2 + contentHeightInLines;
|
||||
}
|
||||
|
||||
protected override _relayout() {
|
||||
super._relayout(this._computeHeightInLines());
|
||||
}
|
||||
|
||||
async getInput(where: IRange, placeholder: string, value: string, token: CancellationToken): Promise<{ value: string; preview: boolean } | undefined> {
|
||||
assertType(this.editor.hasModel());
|
||||
super.show(where, this._computeHeightInLines());
|
||||
this._ctxVisible.set(true);
|
||||
|
||||
const task = this.widget.getInput(placeholder, value, token);
|
||||
const result = await task;
|
||||
return result;
|
||||
}
|
||||
|
||||
override hide(): void {
|
||||
this._ctxVisible.reset();
|
||||
this._ctxCursorPosition.reset();
|
||||
this.widget.reset();
|
||||
super.hide();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class InteractiveEditorController implements IEditorContribution {
|
||||
|
||||
static ID = 'interactiveEditor';
|
||||
|
||||
static get(editor: ICodeEditor) {
|
||||
return editor.getContribution<InteractiveEditorController>(InteractiveEditorController.ID);
|
||||
}
|
||||
|
||||
private static _decoBlock = ModelDecorationOptions.register({
|
||||
description: 'interactive-editor',
|
||||
blockClassName: 'interactive-editor-block',
|
||||
blockDoesNotCollapse: true,
|
||||
blockPadding: [1, 0, 1, 4]
|
||||
});
|
||||
|
||||
|
||||
private static _promptHistory: string[] = [];
|
||||
private _historyOffset: number = -1;
|
||||
|
||||
private readonly _store = new DisposableStore();
|
||||
private readonly _zone: InteractiveEditorZoneWidget;
|
||||
private readonly _ctxShowPreview: IContextKey<boolean>;
|
||||
private readonly _ctxHasActiveRequest: IContextKey<boolean>;
|
||||
|
||||
private _ctsSession: CancellationTokenSource = new CancellationTokenSource();
|
||||
private _ctsRequest?: CancellationTokenSource;
|
||||
|
||||
constructor(
|
||||
private readonly _editor: ICodeEditor,
|
||||
@IInstantiationService instaService: IInstantiationService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IInteractiveEditorService private readonly _interactiveEditorService: IInteractiveEditorService,
|
||||
@IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
) {
|
||||
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);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._store.dispose();
|
||||
this._ctsSession.dispose(true);
|
||||
this._ctsSession.dispose();
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return InteractiveEditorController.ID;
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
|
||||
this._ctsSession.dispose(true);
|
||||
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const provider = Iterable.first(this._interactiveEditorService.getAll());
|
||||
if (!provider) {
|
||||
this._logService.trace('[IE] NO provider found');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
this._ctsSession = new CancellationTokenSource();
|
||||
|
||||
const session = await provider.prepareInteractiveEditorSession(this._editor.getModel(), this._editor.getSelection(), this._ctsSession.token);
|
||||
if (!session) {
|
||||
this._logService.trace('[IE] NO session', provider.debugName);
|
||||
return;
|
||||
}
|
||||
|
||||
this._logService.trace('[IE] NEW session', provider.debugName);
|
||||
|
||||
const decoBackground = this._editor.createDecorationsCollection();
|
||||
const decoPreview = this._editor.createDecorationsCollection();
|
||||
|
||||
const decoWholeRange = this._editor.createDecorationsCollection();
|
||||
decoWholeRange.set([{
|
||||
range: this._editor.getSelection(),
|
||||
options: { description: 'interactive-editor-marker' }
|
||||
}]);
|
||||
|
||||
let placeholder = session.placeholder ?? '';
|
||||
let value = '';
|
||||
|
||||
const listener = new DisposableStore();
|
||||
this._editor.onDidChangeModel(this._ctsSession.cancel, this._ctsSession, listener);
|
||||
|
||||
// CANCEL if the document has changed outside the current range
|
||||
this._editor.onDidChangeModelContent(e => {
|
||||
|
||||
let cancel = false;
|
||||
const wholeRange = decoWholeRange.getRange(0);
|
||||
if (!wholeRange) {
|
||||
cancel = true;
|
||||
} else {
|
||||
for (const change of e.changes) {
|
||||
if (!Range.areIntersectingOrTouching(wholeRange, change.range)) {
|
||||
cancel = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cancel) {
|
||||
this._ctsSession.cancel();
|
||||
this._logService.trace('[IE] CANCEL because of model change OUTSIDE range');
|
||||
}
|
||||
|
||||
}, undefined, listener);
|
||||
|
||||
|
||||
|
||||
do {
|
||||
|
||||
const wholeRange = decoWholeRange.getRange(0);
|
||||
if (!wholeRange) {
|
||||
// nuked whole file contents?
|
||||
break;
|
||||
}
|
||||
|
||||
const newDecorations: IModelDeltaDecoration[] = [{
|
||||
range: wholeRange,
|
||||
options: InteractiveEditorController._decoBlock
|
||||
}];
|
||||
|
||||
decoBackground.set(newDecorations);
|
||||
|
||||
this._historyOffset = -1;
|
||||
const input = await this._zone.getInput(wholeRange.collapseToEnd(), placeholder, value, this._ctsSession.token);
|
||||
|
||||
if (!input || !input.value) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this._ctsRequest?.dispose(true);
|
||||
this._ctsRequest = new CancellationTokenSource(this._ctsSession.token);
|
||||
|
||||
const sw = StopWatch.create();
|
||||
const task = provider.provideResponse(
|
||||
session,
|
||||
{
|
||||
session,
|
||||
prompt: input.value,
|
||||
selection: this._editor.getSelection(),
|
||||
wholeRange: wholeRange
|
||||
},
|
||||
this._ctsRequest.token
|
||||
);
|
||||
|
||||
let reply: IInteractiveEditorResponse | null | undefined;
|
||||
try {
|
||||
this._zone.widget.updateProgress(true);
|
||||
this._ctxHasActiveRequest.set(true);
|
||||
reply = await raceCancellationError(Promise.resolve(task), this._ctsRequest.token);
|
||||
|
||||
} catch (e) {
|
||||
if (!isCancellationError(e)) {
|
||||
this._logService.error('[IE] ERROR during request', provider.debugName);
|
||||
this._logService.error(e);
|
||||
}
|
||||
} finally {
|
||||
this._ctxHasActiveRequest.set(false);
|
||||
this._zone.widget.updateProgress(false);
|
||||
}
|
||||
|
||||
this._logService.trace('[IE] request took', sw.elapsed(), provider.debugName);
|
||||
|
||||
if (this._ctsRequest.token.isCancellationRequested) {
|
||||
value = input.value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!reply || isFalsyOrEmpty(reply.edits)) {
|
||||
this._logService.trace('[IE] NO reply or edits', provider.debugName);
|
||||
reply = { edits: [] };
|
||||
placeholder = '';
|
||||
continue;
|
||||
}
|
||||
|
||||
// make edits more minimal
|
||||
const moreMinimalEdits = (await this._editorWorkerService.computeMoreMinimalEdits(this._editor.getModel().uri, reply.edits, true)) ?? reply.edits;
|
||||
|
||||
// clear old preview
|
||||
decoPreview.clear();
|
||||
|
||||
const undoEdits: IValidEditOperation[] = [];
|
||||
this._editor.pushUndoStop();
|
||||
this._editor.executeEdits(
|
||||
'interactive-editor',
|
||||
moreMinimalEdits.map(edit => {
|
||||
// return EditOperation.replaceMove(Range.lift(edit.range), edit.text); ???
|
||||
return EditOperation.replace(Range.lift(edit.range), edit.text);
|
||||
}),
|
||||
_undoEdits => {
|
||||
let last: Position | null = null;
|
||||
for (const undoEdit of _undoEdits) {
|
||||
undoEdits.push(undoEdit);
|
||||
last = !last || last.isBefore(undoEdit.range.getEndPosition()) ? undoEdit.range.getEndPosition() : last;
|
||||
}
|
||||
return last && [Selection.fromPositions(last)];
|
||||
}
|
||||
);
|
||||
this._editor.pushUndoStop();
|
||||
|
||||
if (input.preview) {
|
||||
const decorations: IModelDeltaDecoration[] = [];
|
||||
for (const edit of undoEdits) {
|
||||
|
||||
let content = edit.text;
|
||||
if (content.length > 12) {
|
||||
content = content.substring(0, 12) + '…';
|
||||
}
|
||||
decorations.push({
|
||||
range: edit.range,
|
||||
options: {
|
||||
description: 'interactive-editor-inline-diff',
|
||||
className: 'interactive-editor-lines-inserted-range',
|
||||
before: {
|
||||
content,
|
||||
inlineClassName: 'interactive-editor-lines-deleted-range-inline',
|
||||
attachedData: edit
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
decoPreview.set(decorations);
|
||||
}
|
||||
|
||||
if (!InteractiveEditorController._promptHistory.includes(input.value)) {
|
||||
InteractiveEditorController._promptHistory.unshift(input.value);
|
||||
}
|
||||
placeholder = reply.placeholder ?? session.placeholder ?? '';
|
||||
|
||||
} while (!this._ctsSession.token.isCancellationRequested);
|
||||
|
||||
// done, cleanup
|
||||
decoWholeRange.clear();
|
||||
decoBackground.clear();
|
||||
decoPreview.clear();
|
||||
|
||||
listener.dispose();
|
||||
session.dispose?.();
|
||||
|
||||
this._zone.hide();
|
||||
this._editor.focus();
|
||||
|
||||
this._logService.trace('[IE] session DONE', provider.debugName);
|
||||
}
|
||||
|
||||
accept(preview: boolean = this._preview): 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();
|
||||
}
|
||||
|
||||
cancelSession() {
|
||||
this._ctsSession.cancel();
|
||||
}
|
||||
|
||||
arrowOut(up: boolean): void {
|
||||
if (this._zone.position && this._editor.hasModel()) {
|
||||
const { column } = this._editor.getPosition();
|
||||
const { lineNumber } = this._zone.position;
|
||||
const newLine = up ? lineNumber : lineNumber + 1;
|
||||
this._editor.setPosition({ lineNumber: newLine, column });
|
||||
this._editor.focus();
|
||||
}
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this._zone.widget.focus();
|
||||
}
|
||||
|
||||
populateHistory(up: boolean) {
|
||||
const len = InteractiveEditorController._promptHistory.length;
|
||||
if (len === 0) {
|
||||
return;
|
||||
}
|
||||
const pos = (len + this._historyOffset + (up ? 1 : -1)) % len;
|
||||
const entry = InteractiveEditorController._promptHistory[pos];
|
||||
this._zone.widget.populateInputField(entry);
|
||||
this._historyOffset = pos;
|
||||
}
|
||||
|
||||
toggleHistory(): void {
|
||||
this._zone.widget.toggleHistory();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
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 } 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';
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export interface IInteractiveEditorSession {
|
||||
id: number;
|
||||
placeholder?: string;
|
||||
dispose?(): void;
|
||||
}
|
||||
|
||||
export interface IInteractiveEditorRequest {
|
||||
session: IInteractiveEditorSession;
|
||||
prompt: string;
|
||||
// model: ITextModel;
|
||||
selection: ISelection;
|
||||
wholeRange: IRange;
|
||||
}
|
||||
|
||||
export interface IInteractiveEditorResponse {
|
||||
// item: IInputModeSession;
|
||||
edits: TextEdit[]; // WorkspaceEdit?
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export interface IInteractiveEditorSessionProvider {
|
||||
|
||||
debugName: string;
|
||||
|
||||
prepareInteractiveEditorSession(model: ITextModel, range: ISelection, token: CancellationToken): ProviderResult<IInteractiveEditorSession>;
|
||||
|
||||
provideResponse(item: IInteractiveEditorSession, request: IInteractiveEditorRequest, token: CancellationToken): ProviderResult<IInteractiveEditorResponse>;
|
||||
}
|
||||
|
||||
export const IInteractiveEditorService = createDecorator<IInteractiveEditorService>('IInteractiveEditorService');
|
||||
|
||||
export interface IInteractiveEditorService {
|
||||
_serviceBrand: undefined;
|
||||
add(provider: IInteractiveEditorSessionProvider): IDisposable;
|
||||
getAll(): Iterable<IInteractiveEditorSessionProvider>;
|
||||
}
|
||||
|
||||
export const MENU_INTERACTIVE_EDITOR_WIDGET_LHS = MenuId.for('interactiveEditorWidgetLhs');
|
||||
export const MENU_INTERACTIVE_EDITOR_WIDGET = MenuId.for('interactiveEditorWidgetRhs');
|
||||
|
||||
export const CTX_INTERACTIVE_EDITOR_HAS_PROVIDER = new RawContextKey<boolean>('interactiveEditorHasProvider', false, localize('interactiveEditorHasProvider', "Whether a provider for interactive editors exists"));
|
||||
export const CTX_INTERACTIVE_EDITOR_VISIBLE = new RawContextKey<boolean>('interactiveEditorVisible', false, localize('interactiveEditorVisible', "Whether the interactive editor input is visible"));
|
||||
export const CTX_INTERACTIVE_EDITOR_FOCUSED = new RawContextKey<boolean>('interactiveEditorFocused', false, localize('interactiveEditorFocused', "Whether the interactive editor input is focused"));
|
||||
export const CTX_INTERACTIVE_EDITOR_EMPTY = new RawContextKey<boolean>('interactiveEditorEmpty', false, localize('interactiveEditorEmpty', "Whether the interactive editor input is empty"));
|
||||
export const CTX_INTERACTIVE_EDITOR_PREVIEW = new RawContextKey<boolean>('interactiveEditorPreview', false, localize('interactiveEditorPreview', "Whether the interactive editor input shows inline previews"));
|
||||
export const CTX_INTERACTIVE_EDITOR_HISTORY_VISIBLE = new RawContextKey<boolean>('interactiveEditorHistoryVisible', false, localize('interactiveEditorHistoryVisible', "Whether the interactive editor history is visible"));
|
||||
export const CTX_INTERACTIVE_EDITOR_INNER_CURSOR_FIRST = new RawContextKey<boolean>('interactiveEditorInnerCursorFirst', false, localize('interactiveEditorInnerCursorFirst', "Whether the cursor of the iteractive editor input is on the first line"));
|
||||
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"));
|
|
@ -0,0 +1,37 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { LinkedList } from 'vs/base/common/linkedList';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IInteractiveEditorService, IInteractiveEditorSessionProvider, CTX_INTERACTIVE_EDITOR_HAS_PROVIDER } from './interactiveEditor';
|
||||
|
||||
export class InteractiveEditorServiceImpl implements IInteractiveEditorService {
|
||||
|
||||
declare _serviceBrand: undefined;
|
||||
|
||||
private readonly _entries = new LinkedList<IInteractiveEditorSessionProvider>();
|
||||
|
||||
private readonly _ctxHasProvider: IContextKey<boolean>;
|
||||
|
||||
constructor(@IContextKeyService contextKeyService: IContextKeyService) {
|
||||
this._ctxHasProvider = CTX_INTERACTIVE_EDITOR_HAS_PROVIDER.bindTo(contextKeyService);
|
||||
}
|
||||
|
||||
add(provider: IInteractiveEditorSessionProvider): IDisposable {
|
||||
|
||||
const rm = this._entries.push(provider);
|
||||
this._ctxHasProvider.set(true);
|
||||
|
||||
return toDisposable(() => {
|
||||
rm();
|
||||
this._ctxHasProvider.set(this._entries.size > 0);
|
||||
});
|
||||
}
|
||||
|
||||
getAll() {
|
||||
return [...this._entries];
|
||||
}
|
||||
}
|
|
@ -261,7 +261,7 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider {
|
|||
}
|
||||
}
|
||||
|
||||
private _getWidth(info: EditorLayoutInfo): number {
|
||||
protected _getWidth(info: EditorLayoutInfo): number {
|
||||
return info.width - info.minimap.minimapWidth - info.verticalScrollbarWidth;
|
||||
}
|
||||
|
||||
|
|
|
@ -57,6 +57,7 @@ import 'vs/editor/contrib/wordHighlighter/browser/wordHighlighter';
|
|||
import 'vs/editor/contrib/wordOperations/browser/wordOperations';
|
||||
import 'vs/editor/contrib/wordPartOperations/browser/wordPartOperations';
|
||||
import 'vs/editor/contrib/readOnlyMessage/browser/contribution';
|
||||
import 'vs/editor/contrib/interactive/browser/interactiveEditor.contribution';
|
||||
|
||||
// Load up these strings even in VSCode, even if they are not used
|
||||
// in order to get them translated
|
||||
|
|
|
@ -88,7 +88,7 @@ suite('EditorSimpleWorker', () => {
|
|||
|
||||
test('MoreMinimal', () => {
|
||||
|
||||
return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: 'This is line One', range: new Range(1, 1, 1, 17) }]).then(edits => {
|
||||
return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: 'This is line One', range: new Range(1, 1, 1, 17) }], false).then(edits => {
|
||||
assert.strictEqual(edits.length, 1);
|
||||
const [first] = edits;
|
||||
assert.strictEqual(first.text, 'O');
|
||||
|
@ -104,7 +104,7 @@ suite('EditorSimpleWorker', () => {
|
|||
'}'
|
||||
], '\n');
|
||||
|
||||
return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: '{\r\n\t"a":1\r\n}', range: new Range(1, 1, 3, 2) }]).then(edits => {
|
||||
return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: '{\r\n\t"a":1\r\n}', range: new Range(1, 1, 3, 2) }], false).then(edits => {
|
||||
assert.strictEqual(edits.length, 0);
|
||||
});
|
||||
});
|
||||
|
@ -117,7 +117,7 @@ suite('EditorSimpleWorker', () => {
|
|||
'}'
|
||||
], '\n');
|
||||
|
||||
return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: '{\r\n\t"b":1\r\n}', range: new Range(1, 1, 3, 2) }]).then(edits => {
|
||||
return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: '{\r\n\t"b":1\r\n}', range: new Range(1, 1, 3, 2) }], false).then(edits => {
|
||||
assert.strictEqual(edits.length, 1);
|
||||
const [first] = edits;
|
||||
assert.strictEqual(first.text, 'b');
|
||||
|
@ -125,7 +125,7 @@ suite('EditorSimpleWorker', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('MoreMinimal, issue #15385 newline changes and other', function () {
|
||||
test('MoreMinimal, issue #15385 newline changes and other 2/2', function () {
|
||||
|
||||
const model = worker.addModel([
|
||||
'package main', // 1
|
||||
|
@ -133,7 +133,7 @@ suite('EditorSimpleWorker', () => {
|
|||
'}' // 3
|
||||
]);
|
||||
|
||||
return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: '\n', range: new Range(3, 2, 4, 1000) }]).then(edits => {
|
||||
return worker.computeMoreMinimalEdits(model.uri.toString(), [{ text: '\n', range: new Range(3, 2, 4, 1000) }], false).then(edits => {
|
||||
assert.strictEqual(edits.length, 1);
|
||||
const [first] = edits;
|
||||
assert.strictEqual(first.text, '\n');
|
||||
|
|
1
src/vs/monaco.d.ts
vendored
1
src/vs/monaco.d.ts
vendored
|
@ -1552,6 +1552,7 @@ declare namespace monaco.editor {
|
|||
* In this case, the range must be empty and set to the last line.
|
||||
*/
|
||||
blockIsAfterEnd?: boolean | null;
|
||||
blockDoesNotCollapse?: boolean | null;
|
||||
blockPadding?: [top: number, right: number, bottom: number, left: number] | null;
|
||||
/**
|
||||
* Message to be rendered when hovering over the glyph margin decoration.
|
||||
|
|
|
@ -440,6 +440,8 @@ export class MenuItemAction implements IAction {
|
|||
this.enabled = !item.precondition || contextKeyService.contextMatchesRules(item.precondition);
|
||||
this.checked = undefined;
|
||||
|
||||
let icon: ThemeIcon | undefined;
|
||||
|
||||
if (item.toggled) {
|
||||
const toggled = ((item.toggled as { condition: ContextKeyExpression }).condition ? item.toggled : { condition: item.toggled }) as {
|
||||
condition: ContextKeyExpression; icon?: Icon; tooltip?: string | ILocalizedString; title?: string | ILocalizedString;
|
||||
|
@ -449,17 +451,24 @@ export class MenuItemAction implements IAction {
|
|||
this.tooltip = typeof toggled.tooltip === 'string' ? toggled.tooltip : toggled.tooltip.value;
|
||||
}
|
||||
|
||||
if (this.checked && ThemeIcon.isThemeIcon(toggled.icon)) {
|
||||
icon = toggled.icon;
|
||||
}
|
||||
|
||||
if (toggled.title) {
|
||||
this.label = typeof toggled.title === 'string' ? toggled.title : toggled.title.value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!icon) {
|
||||
icon = ThemeIcon.isThemeIcon(item.icon) ? item.icon : undefined;
|
||||
}
|
||||
|
||||
this.item = item;
|
||||
this.alt = alt ? new MenuItemAction(alt, undefined, options, hideActions, contextKeyService, _commandService) : undefined;
|
||||
this._options = options;
|
||||
if (ThemeIcon.isThemeIcon(item.icon)) {
|
||||
this.class = ThemeIcon.asClassName(item.icon);
|
||||
}
|
||||
this.class = icon && ThemeIcon.asClassName(icon);
|
||||
|
||||
}
|
||||
|
||||
run(...args: any[]): Promise<void> {
|
||||
|
|
|
@ -70,6 +70,7 @@ import './mainThreadNotebookKernels';
|
|||
import './mainThreadNotebookDocumentsAndEditors';
|
||||
import './mainThreadNotebookRenderers';
|
||||
import './mainThreadInteractive';
|
||||
import './mainThreadInteractiveEditor';
|
||||
import './mainThreadInteractiveSession';
|
||||
import './mainThreadTask';
|
||||
import './mainThreadLabelService';
|
||||
|
|
54
src/vs/workbench/api/browser/mainThreadInteractiveEditor.ts
Normal file
54
src/vs/workbench/api/browser/mainThreadInteractiveEditor.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DisposableMap } from 'vs/base/common/lifecycle';
|
||||
import { IInteractiveEditorService } from 'vs/editor/contrib/interactive/common/interactiveEditor';
|
||||
import { ExtHostContext, ExtHostInteractiveEditorShape, MainContext, MainThreadInteractiveEditorShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadInteractiveEditor)
|
||||
export class MainThreadInteractiveEditor implements MainThreadInteractiveEditorShape {
|
||||
|
||||
private readonly _registrations = new DisposableMap<number>();
|
||||
private readonly _proxy: ExtHostInteractiveEditorShape;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IInteractiveEditorService private readonly _interactiveEditorService: IInteractiveEditorService,
|
||||
) {
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostInteractiveEditor);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._registrations.dispose();
|
||||
}
|
||||
|
||||
async $registerInteractiveEditorProvider(handle: number, debugName: string): Promise<void> {
|
||||
const unreg = this._interactiveEditorService.add({
|
||||
debugName,
|
||||
prepareInteractiveEditorSession: async (model, range, token) => {
|
||||
const session = await this._proxy.$prepareInteractiveSession(handle, model.uri, range, token);
|
||||
if (!session) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
...session,
|
||||
dispose: () => {
|
||||
this._proxy.$releaseSession(handle, session.id);
|
||||
}
|
||||
};
|
||||
},
|
||||
provideResponse: (item, request, token) => {
|
||||
return this._proxy.$provideResponse(handle, item, request, token);
|
||||
}
|
||||
});
|
||||
|
||||
this._registrations.set(handle, unreg);
|
||||
}
|
||||
|
||||
async $unregisterInteractiveEditorProvider(handle: number): Promise<void> {
|
||||
this._registrations.deleteAndDispose(handle);
|
||||
}
|
||||
}
|
|
@ -98,6 +98,7 @@ import { EditSessionIdentityMatch } from 'vs/platform/workspace/common/editSessi
|
|||
import { ExtHostProfileContentHandlers } from 'vs/workbench/api/common/extHostProfileContentHandler';
|
||||
import { ExtHostQuickDiff } from 'vs/workbench/api/common/extHostQuickDiff';
|
||||
import { ExtHostInteractiveSession } from 'vs/workbench/api/common/extHostInteractiveSession';
|
||||
import { ExtHostInteractiveEditor } from 'vs/workbench/api/common/extHostInteractiveEditor';
|
||||
|
||||
export interface IExtensionRegistries {
|
||||
mine: ExtensionDescriptionRegistry;
|
||||
|
@ -192,6 +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 extHostInteractiveSession = rpcProtocol.set(ExtHostContext.ExtHostInteractiveSession, new ExtHostInteractiveSession(rpcProtocol, extHostLogService));
|
||||
|
||||
// Check that no named customers are missing
|
||||
|
@ -1216,6 +1218,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
// this needs to be updated whenever the API proposal changes
|
||||
_version: 1,
|
||||
|
||||
registerInteractiveEditorSessionProvider(provider: vscode.InteractiveEditorSessionProvider) {
|
||||
checkProposedApiEnabled(extension, 'interactive');
|
||||
return extHostInteractiveEditor.registerProvider(extension, provider);
|
||||
},
|
||||
registerInteractiveSessionProvider(id: string, provider: vscode.InteractiveSessionProvider) {
|
||||
checkProposedApiEnabled(extension, 'interactive');
|
||||
return extHostInteractiveSession.registerInteractiveSessionProvider(extension, id, provider);
|
||||
|
|
|
@ -26,6 +26,7 @@ import * as languages from 'vs/editor/common/languages';
|
|||
import { CharacterPair, CommentRule, EnterAction } from 'vs/editor/common/languages/languageConfiguration';
|
||||
import { EndOfLineSequence } from 'vs/editor/common/model';
|
||||
import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel';
|
||||
import { IInteractiveEditorResponse, IInteractiveEditorSession, IInteractiveEditorRequest } from 'vs/editor/contrib/interactive/common/interactiveEditor';
|
||||
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { ConfigurationTarget, IConfigurationChange, IConfigurationData, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration';
|
||||
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
|
@ -1068,6 +1069,17 @@ export interface MainThreadNotebookRenderersShape extends IDisposable {
|
|||
export interface MainThreadInteractiveShape extends IDisposable {
|
||||
}
|
||||
|
||||
export interface MainThreadInteractiveEditorShape extends IDisposable {
|
||||
$registerInteractiveEditorProvider(handle: number, debugName: string): Promise<void>;
|
||||
$unregisterInteractiveEditorProvider(handle: number): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ExtHostInteractiveEditorShape {
|
||||
$prepareInteractiveSession(handle: number, uri: UriComponents, range: ISelection, token: CancellationToken): Promise<IInteractiveEditorSession | undefined>;
|
||||
$provideResponse(handle: number, session: IInteractiveEditorSession, request: IInteractiveEditorRequest, token: CancellationToken): Promise<IInteractiveEditorResponse | undefined>;
|
||||
$releaseSession(handle: number, sessionId: number): void;
|
||||
}
|
||||
|
||||
export interface MainThreadUrlsShape extends IDisposable {
|
||||
$registerUriHandler(handle: number, extensionId: ExtensionIdentifier): Promise<void>;
|
||||
$unregisterUriHandler(handle: number): Promise<void>;
|
||||
|
@ -2411,6 +2423,7 @@ export const MainContext = {
|
|||
MainThreadNotebookRenderers: createProxyIdentifier<MainThreadNotebookRenderersShape>('MainThreadNotebookRenderers'),
|
||||
MainThreadInteractive: createProxyIdentifier<MainThreadInteractiveShape>('MainThreadInteractive'),
|
||||
MainThreadInteractiveSession: createProxyIdentifier<MainThreadInteractiveSessionShape>('MainThreadInteractiveSession'),
|
||||
MainThreadInteractiveEditor: createProxyIdentifier<MainThreadInteractiveEditorShape>('MainThreadInteractiveEditor'),
|
||||
MainThreadTheming: createProxyIdentifier<MainThreadThemingShape>('MainThreadTheming'),
|
||||
MainThreadTunnelService: createProxyIdentifier<MainThreadTunnelServiceShape>('MainThreadTunnelService'),
|
||||
MainThreadTimeline: createProxyIdentifier<MainThreadTimelineShape>('MainThreadTimeline'),
|
||||
|
@ -2466,6 +2479,7 @@ export const ExtHostContext = {
|
|||
ExtHostNotebookKernels: createProxyIdentifier<ExtHostNotebookKernelsShape>('ExtHostNotebookKernels'),
|
||||
ExtHostNotebookRenderers: createProxyIdentifier<ExtHostNotebookRenderersShape>('ExtHostNotebookRenderers'),
|
||||
ExtHostInteractive: createProxyIdentifier<ExtHostInteractiveShape>('ExtHostInteractive'),
|
||||
ExtHostInteractiveEditor: createProxyIdentifier<ExtHostInteractiveEditorShape>('ExtHostInteractiveEditor'),
|
||||
ExtHostInteractiveSession: createProxyIdentifier<ExtHostInteractiveSessionShape>('ExtHostInteractiveSession'),
|
||||
ExtHostTheming: createProxyIdentifier<ExtHostThemingShape>('ExtHostTheming'),
|
||||
ExtHostTunnelService: createProxyIdentifier<ExtHostTunnelServiceShape>('ExtHostTunnelService'),
|
||||
|
|
111
src/vs/workbench/api/common/extHostInteractiveEditor.ts
Normal file
111
src/vs/workbench/api/common/extHostInteractiveEditor.ts
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { ISelection } from 'vs/editor/common/core/selection';
|
||||
import { IInteractiveEditorResponse, IInteractiveEditorSession, IInteractiveEditorRequest } from 'vs/editor/contrib/interactive/common/interactiveEditor';
|
||||
import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ExtHostInteractiveEditorShape, IMainContext, MainContext, MainThreadInteractiveEditorShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
|
||||
import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import type * as vscode from 'vscode';
|
||||
|
||||
class ProviderWrapper {
|
||||
|
||||
private static _pool = 0;
|
||||
|
||||
readonly handle: number = ProviderWrapper._pool++;
|
||||
|
||||
constructor(
|
||||
readonly extension: Readonly<IRelaxedExtensionDescription>,
|
||||
readonly provider: vscode.InteractiveEditorSessionProvider,
|
||||
) { }
|
||||
}
|
||||
|
||||
export class ExtHostInteractiveEditor implements ExtHostInteractiveEditorShape {
|
||||
|
||||
private static _nextId = 0;
|
||||
|
||||
private readonly _inputProvider = new Map<number, ProviderWrapper>();
|
||||
private readonly _inputSessions = new Map<number, vscode.InteractiveEditorSession>();
|
||||
private readonly _proxy: MainThreadInteractiveEditorShape;
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _logService: ILogService
|
||||
) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadInteractiveEditor);
|
||||
}
|
||||
|
||||
registerProvider(extension: Readonly<IRelaxedExtensionDescription>, provider: vscode.InteractiveEditorSessionProvider): vscode.Disposable {
|
||||
const wrapper = new ProviderWrapper(extension, provider);
|
||||
this._inputProvider.set(wrapper.handle, wrapper);
|
||||
this._proxy.$registerInteractiveEditorProvider(wrapper.handle, extension.identifier.value);
|
||||
return toDisposable(() => {
|
||||
this._proxy.$unregisterInteractiveEditorProvider(wrapper.handle);
|
||||
this._inputProvider.delete(wrapper.handle);
|
||||
});
|
||||
}
|
||||
|
||||
async $prepareInteractiveSession(handle: number, uri: UriComponents, range: ISelection, token: CancellationToken): Promise<IInteractiveEditorSession | undefined> {
|
||||
const entry = this._inputProvider.get(handle);
|
||||
if (!entry) {
|
||||
this._logService.warn('CANNOT prepare session because the PROVIDER IS GONE');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const document = this._documents.getDocument(URI.revive(uri));
|
||||
const session = await entry.provider.prepareInteractiveEditorSession({ document, selection: typeConvert.Selection.to(range) }, token);
|
||||
if (!session) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const id = ExtHostInteractiveEditor._nextId++;
|
||||
this._inputSessions.set(id, session);
|
||||
|
||||
return { id, placeholder: session.placeholder };
|
||||
}
|
||||
|
||||
async $provideResponse(handle: number, item: IInteractiveEditorSession, request: IInteractiveEditorRequest, token: CancellationToken): Promise<IInteractiveEditorResponse | undefined> {
|
||||
const entry = this._inputProvider.get(handle);
|
||||
if (!entry) {
|
||||
return undefined;
|
||||
}
|
||||
const session = this._inputSessions.get(item.id);
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await entry.provider.provideInteractiveEditorResponse({
|
||||
session,
|
||||
prompt: request.prompt,
|
||||
selection: typeConvert.Selection.to(request.selection),
|
||||
wholeRange: typeConvert.Range.to(request.wholeRange)
|
||||
}, token);
|
||||
|
||||
if (!res) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
edits: res.edits.map(typeConvert.TextEdit.from),
|
||||
placeholder: res.placeholder
|
||||
};
|
||||
}
|
||||
|
||||
$releaseSession(handle: number, sessionId: number) {
|
||||
const session = this._inputSessions.get(sessionId);
|
||||
const entry = this._inputProvider.get(handle);
|
||||
if (session && entry) {
|
||||
entry.provider.releaseInteractiveEditorSession?.(session);
|
||||
}
|
||||
this._inputSessions.delete(sessionId);
|
||||
}
|
||||
|
||||
}
|
|
@ -183,6 +183,7 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget {
|
|||
EmbeddedCodeEditorWidget,
|
||||
editorContainer,
|
||||
editorOptions,
|
||||
{},
|
||||
this.editor
|
||||
);
|
||||
|
||||
|
|
|
@ -1182,6 +1182,7 @@ class PlainTextMessagePeek extends Disposable implements IPeekOutputRenderer {
|
|||
EmbeddedCodeEditorWidget,
|
||||
this.container,
|
||||
commonEditorOptions,
|
||||
{},
|
||||
this.editor,
|
||||
) : this.instantiationService.createInstance(
|
||||
CodeEditorWidget,
|
||||
|
|
|
@ -184,6 +184,7 @@ export class TypeHierarchyTreePeekWidget extends peekView.PeekViewWidget {
|
|||
EmbeddedCodeEditorWidget,
|
||||
editorContainer,
|
||||
editorOptions,
|
||||
{},
|
||||
this.editor
|
||||
);
|
||||
|
||||
|
|
|
@ -31,11 +31,11 @@ declare module 'vscode' {
|
|||
action?: string;
|
||||
}
|
||||
|
||||
export interface InteractivEditorSessionProvider {
|
||||
export interface InteractiveEditorSessionProvider {
|
||||
// Create a session. The lifetime of this session is the duration of the editing session with the input mode widget.
|
||||
prepareInteractiveEditorSession(context: TextDocumentContext, token: CancellationToken): ProviderResult<InteractiveEditorSession>;
|
||||
|
||||
provideInteractivEditorResponse(request: InteractiveEditorRequest, token: CancellationToken): ProviderResult<InteractiveEditorResponse>;
|
||||
provideInteractiveEditorResponse(request: InteractiveEditorRequest, token: CancellationToken): ProviderResult<InteractiveEditorResponse>;
|
||||
|
||||
// eslint-disable-next-line local/vscode-dts-provider-naming
|
||||
releaseInteractiveEditorSession?(session: InteractiveEditorSession): any;
|
||||
|
@ -85,5 +85,7 @@ declare module 'vscode' {
|
|||
|
||||
export function registerInteractiveSessionProvider(id: string, provider: InteractiveSessionProvider): Disposable;
|
||||
export function addInteractiveRequest(context: InteractiveSessionRequestArgs): void;
|
||||
|
||||
export function registerInteractiveEditorSessionProvider(provider: InteractiveEditorSessionProvider): Disposable;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue