Skip to content

Commit

Permalink
enable inline chat in IW/REPL (#228998)
Browse files Browse the repository at this point in the history
* REPL and IW are composite code editors

* accept change when running input

* ghost text and styling

* revert ipynb files
  • Loading branch information
amunger committed Sep 19, 2024
1 parent b6a7794 commit 0705fd4
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { extname, isEqual } from '../../../../base/common/resources.js';
import { isFalsyOrWhitespace } from '../../../../base/common/strings.js';
import { URI, UriComponents } from '../../../../base/common/uri.js';
import { IBulkEditService } from '../../../../editor/browser/services/bulkEditService.js';
import { CodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js';
import { EditOperation } from '../../../../editor/common/core/editOperation.js';
import { PLAINTEXT_LANGUAGE_ID } from '../../../../editor/common/languages/modesRegistry.js';
import { ITextModel } from '../../../../editor/common/model.js';
Expand All @@ -36,7 +35,7 @@ import { Registry } from '../../../../platform/registry/common/platform.js';
import { contrastBorder, ifDefinedThenElse, listInactiveSelectionBackground, registerColor } from '../../../../platform/theme/common/colorRegistry.js';
import { EditorPaneDescriptor, IEditorPaneRegistry } from '../../../browser/editor.js';
import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js';
import { EditorExtensions, EditorsOrder, IEditorFactoryRegistry, IEditorSerializer, IUntypedEditorInput } from '../../../common/editor.js';
import { EditorExtensions, EditorsOrder, IEditorControl, IEditorFactoryRegistry, IEditorSerializer, IUntypedEditorInput } from '../../../common/editor.js';
import { EditorInput } from '../../../common/editor/editorInput.js';
import { PANEL_BORDER } from '../../../common/theme.js';
import { ResourceNotebookCellEdit } from '../../bulkEdit/browser/bulkCellEdits.js';
Expand All @@ -47,7 +46,6 @@ import { InteractiveEditorInput } from './interactiveEditorInput.js';
import { IInteractiveHistoryService, InteractiveHistoryService } from './interactiveHistoryService.js';
import { NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from '../../notebook/browser/controller/coreActions.js';
import { INotebookEditorOptions } from '../../notebook/browser/notebookBrowser.js';
import { NotebookEditorWidget } from '../../notebook/browser/notebookEditorWidget.js';
import * as icons from '../../notebook/browser/notebookIcons.js';
import { INotebookEditorService } from '../../notebook/browser/services/notebookEditorService.js';
import { CellEditType, CellKind, CellUri, INTERACTIVE_WINDOW_EDITOR_ID, NotebookSetting, NotebookWorkingCopyTypeIdentifier } from '../../notebook/common/notebookCommon.js';
Expand All @@ -61,6 +59,8 @@ import { IEditorService } from '../../../services/editor/common/editorService.js
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
import { IWorkingCopyIdentifier } from '../../../services/workingCopy/common/workingCopy.js';
import { IWorkingCopyEditorHandler, IWorkingCopyEditorService } from '../../../services/workingCopy/common/workingCopyEditorService.js';
import { isReplEditorControl, ReplEditorControl } from '../../replNotebook/browser/replEditor.js';
import { InlineChatController } from '../../inlineChat/browser/inlineChatController.js';

const interactiveWindowCategory: ILocalizedString = localize2('interactiveWindow', "Interactive Window");

Expand Down Expand Up @@ -396,7 +396,7 @@ registerAction2(class extends Action2 {
const editorInput = editors[0].editor as InteractiveEditorInput;
const currentGroup = editors[0].groupId;
const editor = await editorService.openEditor(editorInput, editorOptions, currentGroup);
const editorControl = editor?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined;
const editorControl = editor?.getControl() as ReplEditorControl;

return {
notebookUri: editorInput.resource,
Expand Down Expand Up @@ -437,7 +437,7 @@ registerAction2(class extends Action2 {
historyService.clearHistory(notebookUri);
const editorInput: IUntypedEditorInput = { resource: notebookUri, options: editorOptions };
const editorPane = await editorService.openEditor(editorInput, group);
const editorControl = editorPane?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined;
const editorControl = editorPane?.getControl() as ReplEditorControl;
// Extensions must retain references to these URIs to manipulate the interactive editor
logService.debug('New interactive window opened. Notebook editor id', editorControl?.notebookEditor?.getId());
return { notebookUri, inputUri, notebookEditorId: editorControl?.notebookEditor?.getId() };
Expand Down Expand Up @@ -495,26 +495,26 @@ registerAction2(class extends Action2 {
const bulkEditService = accessor.get(IBulkEditService);
const historyService = accessor.get(IInteractiveHistoryService);
const notebookEditorService = accessor.get(INotebookEditorService);
let editorControl: { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined;
let editorControl: IEditorControl | undefined;
if (context) {
const resourceUri = URI.revive(context);
const editors = editorService.findEditors(resourceUri);
for (const found of editors) {
if (found.editor.typeId === InteractiveEditorInput.ID) {
const editor = await editorService.openEditor(found.editor, found.groupId);
editorControl = editor?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined;
editorControl = editor?.getControl();
break;
}
}
}
else {
editorControl = editorService.activeEditorPane?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined;
editorControl = editorService.activeEditorPane?.getControl();
}


if (editorControl && editorControl.notebookEditor && editorControl.codeEditor) {
if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {
const notebookDocument = editorControl.notebookEditor.textModel;
const textModel = editorControl.codeEditor.getModel();
const textModel = editorControl.activeCodeEditor.getModel();
const activeKernel = editorControl.notebookEditor.activeKernel;
const language = activeKernel?.supportedLanguages[0] ?? PLAINTEXT_LANGUAGE_ID;

Expand All @@ -526,6 +526,11 @@ registerAction2(class extends Action2 {
return;
}

const ctrl = InlineChatController.get(editorControl.activeCodeEditor);
if (ctrl) {
ctrl.acceptHunk();
}

historyService.replaceLast(notebookDocument.uri, value);
historyService.addToHistory(notebookDocument.uri, '');
textModel.setValue('');
Expand Down Expand Up @@ -584,15 +589,15 @@ registerAction2(class extends Action2 {

async run(accessor: ServicesAccessor): Promise<void> {
const editorService = accessor.get(IEditorService);
const editorControl = editorService.activeEditorPane?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined;
const editorControl = editorService.activeEditorPane?.getControl();

if (editorControl && editorControl.notebookEditor && editorControl.codeEditor) {
if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {
const notebookDocument = editorControl.notebookEditor.textModel;
const textModel = editorControl.codeEditor.getModel();
const range = editorControl.codeEditor.getModel()?.getFullModelRange();
const textModel = editorControl.activeCodeEditor.getModel();
const range = editorControl.activeCodeEditor.getModel()?.getFullModelRange();

if (notebookDocument && textModel && range) {
editorControl.codeEditor.executeEdits('', [EditOperation.replace(range, null)]);
editorControl.activeCodeEditor.executeEdits('', [EditOperation.replace(range, null)]);
}
}
}
Expand Down Expand Up @@ -630,13 +635,13 @@ registerAction2(class extends Action2 {
async run(accessor: ServicesAccessor): Promise<void> {
const editorService = accessor.get(IEditorService);
const historyService = accessor.get(IInteractiveHistoryService);
const editorControl = editorService.activeEditorPane?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined;
const editorControl = editorService.activeEditorPane?.getControl();



if (editorControl && editorControl.notebookEditor && editorControl.codeEditor) {
if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {
const notebookDocument = editorControl.notebookEditor.textModel;
const textModel = editorControl.codeEditor.getModel();
const textModel = editorControl.activeCodeEditor.getModel();

if (notebookDocument && textModel) {
const previousValue = historyService.getPreviousValue(notebookDocument.uri);
Expand Down Expand Up @@ -680,11 +685,11 @@ registerAction2(class extends Action2 {
async run(accessor: ServicesAccessor): Promise<void> {
const editorService = accessor.get(IEditorService);
const historyService = accessor.get(IInteractiveHistoryService);
const editorControl = editorService.activeEditorPane?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined;
const editorControl = editorService.activeEditorPane?.getControl();

if (editorControl && editorControl.notebookEditor && editorControl.codeEditor) {
if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {
const notebookDocument = editorControl.notebookEditor.textModel;
const textModel = editorControl.codeEditor.getModel();
const textModel = editorControl.activeCodeEditor.getModel();

if (notebookDocument && textModel) {
const nextValue = historyService.getNextValue(notebookDocument.uri);
Expand Down Expand Up @@ -714,9 +719,9 @@ registerAction2(class extends Action2 {

async run(accessor: ServicesAccessor): Promise<void> {
const editorService = accessor.get(IEditorService);
const editorControl = editorService.activeEditorPane?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined;
const editorControl = editorService.activeEditorPane?.getControl();

if (editorControl && editorControl.notebookEditor && editorControl.codeEditor) {
if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {
if (editorControl.notebookEditor.getLength() === 0) {
return;
}
Expand All @@ -743,9 +748,9 @@ registerAction2(class extends Action2 {

async run(accessor: ServicesAccessor): Promise<void> {
const editorService = accessor.get(IEditorService);
const editorControl = editorService.activeEditorPane?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined;
const editorControl = editorService.activeEditorPane?.getControl();

if (editorControl && editorControl.notebookEditor && editorControl.codeEditor) {
if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {
if (editorControl.notebookEditor.getLength() === 0) {
return;
}
Expand All @@ -772,9 +777,9 @@ registerAction2(class extends Action2 {

async run(accessor: ServicesAccessor): Promise<void> {
const editorService = accessor.get(IEditorService);
const editorControl = editorService.activeEditorPane?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined;
const editorControl = editorService.activeEditorPane?.getControl();

if (editorControl && editorControl.notebookEditor && editorControl.codeEditor) {
if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {
editorService.activeEditorPane?.focus();
}
else {
Expand All @@ -785,9 +790,9 @@ registerAction2(class extends Action2 {
const editorInput = interactiveWindow.editor as InteractiveEditorInput;
const currentGroup = interactiveWindow.groupId;
const editor = await editorService.openEditor(editorInput, currentGroup);
const editorControl = editor?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined;
const editorControl = editor?.getControl();

if (editorControl && editorControl.notebookEditor && editorControl.codeEditor) {
if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {
editorService.activeEditorPane?.focus();
}
}
Expand All @@ -811,9 +816,9 @@ registerAction2(class extends Action2 {

async run(accessor: ServicesAccessor): Promise<void> {
const editorService = accessor.get(IEditorService);
const editorControl = editorService.activeEditorPane?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget; focusHistory: () => void } | undefined;
const editorControl = editorService.activeEditorPane?.getControl();

if (editorControl && editorControl.notebookEditor && editorControl.codeEditor) {
if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {
editorControl.notebookEditor.focus();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

.interactive-editor .input-cell-container:focus-within .input-editor-container .monaco-editor {
.interactive-editor .input-cell-container:focus-within .input-editor-container>.monaco-editor {
outline: solid 1px var(--vscode-notebook-focusedCellBorder);
}

.interactive-editor .input-cell-container .input-editor-container .monaco-editor {
.interactive-editor .input-cell-container .input-editor-container>.monaco-editor {
outline: solid 1px var(--vscode-notebook-inactiveFocusedCellBorder);
}

Expand Down
30 changes: 25 additions & 5 deletions src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Emitter, Event } from '../../../../base/common/event.js';
import { DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js';
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
import { CodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js';
import { ICodeEditorViewState } from '../../../../editor/common/editorCommon.js';
import { ICodeEditorViewState, ICompositeCodeEditor } from '../../../../editor/common/editorCommon.js';
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { IStorageService } from '../../../../platform/storage/common/storage.js';
Expand Down Expand Up @@ -64,6 +64,8 @@ import { ContentHoverController } from '../../../../editor/contrib/hover/browser
import { GlyphHoverController } from '../../../../editor/contrib/hover/browser/glyphHoverController.js';
import { ReplInputHintContentWidget } from './replInputHintContentWidget.js';
import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js';
import { INLINE_CHAT_ID } from '../../inlineChat/common/inlineChat.js';
import { ReplEditorControl } from '../../replNotebook/browser/replEditor.js';

const DECORATION_KEY = 'interactiveInputDecoration';
const INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'InteractiveEditorViewState';
Expand Down Expand Up @@ -408,7 +410,8 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro
TabCompletionController.ID,
ContentHoverController.ID,
GlyphHoverController.ID,
MarkerController.ID
MarkerController.ID,
INLINE_CHAT_ID
])
}
});
Expand Down Expand Up @@ -503,6 +506,12 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro
}
}));

this._codeEditorWidget.onDidChangeModelDecorations(() => {
if (this.isVisible()) {
this._updateInputHint();
}
});

this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeModel(() => {
this._updateInputHint();
}));
Expand Down Expand Up @@ -653,6 +662,15 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro
return new DOM.Dimension(Math.max(0, width), Math.max(0, height));
}

private _hasConflictingDecoration() {
return Boolean(this._codeEditorWidget.getLineDecorations(1)?.find((d) =>
d.options.beforeContentClassName
|| d.options.afterContentClassName
|| d.options.before?.content
|| d.options.after?.content
));
}

private _updateInputHint(): void {
if (!this._codeEditorWidget) {
return;
Expand All @@ -661,7 +679,8 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro
const shouldHide =
!this._codeEditorWidget.hasModel() ||
this._configurationService.getValue<boolean>(InteractiveWindowSetting.showExecutionHint) === false ||
this._codeEditorWidget.getModel()!.getValueLength() !== 0;
this._codeEditorWidget.getModel()!.getValueLength() !== 0 ||
this._hasConflictingDecoration();

if (!this._hintElement && !shouldHide) {
this._hintElement = this._instantiationService.createInstance(ReplInputHintContentWidget, this._codeEditorWidget);
Expand Down Expand Up @@ -721,10 +740,11 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro
super.clearInput();
}

override getControl(): { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } {
override getControl(): ReplEditorControl & ICompositeCodeEditor {
return {
notebookEditor: this._notebookWidget.value,
codeEditor: this._codeEditorWidget
activeCodeEditor: this._codeEditorWidget,
onDidChangeActiveEditor: Event.None
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

.interactive-editor .input-cell-container:focus-within .input-editor-container .monaco-editor {
.interactive-editor .input-cell-container:focus-within .input-editor-container>.monaco-editor {
outline: solid 1px var(--vscode-notebook-focusedCellBorder);
}

.interactive-editor .input-cell-container .input-editor-container .monaco-editor {
.interactive-editor .input-cell-container .input-editor-container>.monaco-editor {
outline: solid 1px var(--vscode-notebook-inactiveFocusedCellBorder);
}

Expand Down
Loading

0 comments on commit 0705fd4

Please sign in to comment.