Path: blob/main/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import './media/interactive.css';6import * as DOM from '../../../../base/browser/dom.js';7import * as domStylesheets from '../../../../base/browser/domStylesheets.js';8import { CancellationToken } from '../../../../base/common/cancellation.js';9import { Emitter, Event } from '../../../../base/common/event.js';10import { DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js';11import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';12import { CodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js';13import { ICodeEditorViewState, ICompositeCodeEditor } from '../../../../editor/common/editorCommon.js';14import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';15import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';16import { IStorageService } from '../../../../platform/storage/common/storage.js';17import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';18import { IThemeService } from '../../../../platform/theme/common/themeService.js';19import { EditorPane } from '../../../browser/parts/editor/editorPane.js';20import { EditorPaneSelectionChangeReason, IEditorMemento, IEditorOpenContext, IEditorPaneScrollPosition, IEditorPaneSelectionChangeEvent, IEditorPaneWithScrolling } from '../../../common/editor.js';21import { getSimpleEditorOptions } from '../../codeEditor/browser/simpleEditorOptions.js';22import { InteractiveEditorInput } from './interactiveEditorInput.js';23import { ICellViewModel, INotebookEditorOptions, INotebookEditorViewState } from '../../notebook/browser/notebookBrowser.js';24import { NotebookEditorExtensionsRegistry } from '../../notebook/browser/notebookEditorExtensions.js';25import { IBorrowValue, INotebookEditorService } from '../../notebook/browser/services/notebookEditorService.js';26import { NotebookEditorWidget } from '../../notebook/browser/notebookEditorWidget.js';27import { GroupsOrder, IEditorGroup, IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js';28import { ExecutionStateCellStatusBarContrib, TimerCellStatusBarContrib } from '../../notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.js';29import { INotebookKernelService } from '../../notebook/common/notebookKernelService.js';30import { PLAINTEXT_LANGUAGE_ID } from '../../../../editor/common/languages/modesRegistry.js';31import { ILanguageService } from '../../../../editor/common/languages/language.js';32import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js';33import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';34import { ReplEditorSettings, INTERACTIVE_INPUT_CURSOR_BOUNDARY } from './interactiveCommon.js';35import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';36import { NotebookOptions } from '../../notebook/browser/notebookOptions.js';37import { ToolBar } from '../../../../base/browser/ui/toolbar/toolbar.js';38import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';39import { createActionViewItem, getActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';40import { EditorExtensionsRegistry } from '../../../../editor/browser/editorExtensions.js';41import { ParameterHintsController } from '../../../../editor/contrib/parameterHints/browser/parameterHints.js';42import { MenuPreventer } from '../../codeEditor/browser/menuPreventer.js';43import { SelectionClipboardContributionID } from '../../codeEditor/browser/selectionClipboard.js';44import { ContextMenuController } from '../../../../editor/contrib/contextmenu/browser/contextmenu.js';45import { SuggestController } from '../../../../editor/contrib/suggest/browser/suggestController.js';46import { SnippetController2 } from '../../../../editor/contrib/snippet/browser/snippetController2.js';47import { TabCompletionController } from '../../snippets/browser/tabCompletion.js';48import { MarkerController } from '../../../../editor/contrib/gotoError/browser/gotoError.js';49import { EditorInput } from '../../../common/editor/editorInput.js';50import { ITextResourceConfigurationService } from '../../../../editor/common/services/textResourceConfiguration.js';51import { ITextEditorOptions, TextEditorSelectionSource } from '../../../../platform/editor/common/editor.js';52import { INotebookExecutionStateService, NotebookExecutionType } from '../../notebook/common/notebookExecutionStateService.js';53import { NOTEBOOK_KERNEL } from '../../notebook/common/notebookContextKeys.js';54import { ICursorPositionChangedEvent } from '../../../../editor/common/cursorEvents.js';55import { IExtensionService } from '../../../services/extensions/common/extensions.js';56import { isEqual } from '../../../../base/common/resources.js';57import { NotebookFindContrib } from '../../notebook/browser/contrib/find/notebookFindWidget.js';58import { INTERACTIVE_WINDOW_EDITOR_ID } from '../../notebook/common/notebookCommon.js';59import './interactiveEditor.css';60import { IEditorOptions } from '../../../../editor/common/config/editorOptions.js';61import { deepClone } from '../../../../base/common/objects.js';62import { ContentHoverController } from '../../../../editor/contrib/hover/browser/contentHoverController.js';63import { GlyphHoverController } from '../../../../editor/contrib/hover/browser/glyphHoverController.js';64import { ReplInputHintContentWidget } from './replInputHintContentWidget.js';65import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js';66import { INLINE_CHAT_ID } from '../../inlineChat/common/inlineChat.js';67import { ReplEditorControl } from '../../replNotebook/browser/replEditor.js';6869const DECORATION_KEY = 'interactiveInputDecoration';70const INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'InteractiveEditorViewState';7172const INPUT_CELL_VERTICAL_PADDING = 8;73const INPUT_CELL_HORIZONTAL_PADDING_RIGHT = 10;74const INPUT_EDITOR_PADDING = 8;757677export interface InteractiveEditorViewState {78readonly notebook?: INotebookEditorViewState;79readonly input?: ICodeEditorViewState | null;80}8182export interface InteractiveEditorOptions extends ITextEditorOptions {83readonly viewState?: InteractiveEditorViewState;84}8586export class InteractiveEditor extends EditorPane implements IEditorPaneWithScrolling {87private _rootElement!: HTMLElement;88private _styleElement!: HTMLStyleElement;89private _notebookEditorContainer!: HTMLElement;90private _notebookWidget: IBorrowValue<NotebookEditorWidget> = { value: undefined };91private _inputCellContainer!: HTMLElement;92private _inputFocusIndicator!: HTMLElement;93private _inputRunButtonContainer!: HTMLElement;94private _inputEditorContainer!: HTMLElement;95private _codeEditorWidget!: CodeEditorWidget;96private _notebookWidgetService: INotebookEditorService;97private _instantiationService: IInstantiationService;98private _languageService: ILanguageService;99private _contextKeyService: IContextKeyService;100private _configurationService: IConfigurationService;101private _notebookKernelService: INotebookKernelService;102private _keybindingService: IKeybindingService;103private _menuService: IMenuService;104private _contextMenuService: IContextMenuService;105private _editorGroupService: IEditorGroupsService;106private _notebookExecutionStateService: INotebookExecutionStateService;107private _extensionService: IExtensionService;108private readonly _widgetDisposableStore: DisposableStore = this._register(new DisposableStore());109private _lastLayoutDimensions?: { readonly dimension: DOM.Dimension; readonly position: DOM.IDomPosition };110private _editorOptions: IEditorOptions;111private _notebookOptions: NotebookOptions;112private _editorMemento: IEditorMemento<InteractiveEditorViewState>;113private readonly _groupListener = this._register(new MutableDisposable());114private _runbuttonToolbar: ToolBar | undefined;115private _hintElement: ReplInputHintContentWidget | undefined;116117private _onDidFocusWidget = this._register(new Emitter<void>());118override get onDidFocus(): Event<void> { return this._onDidFocusWidget.event; }119private _onDidChangeSelection = this._register(new Emitter<IEditorPaneSelectionChangeEvent>());120readonly onDidChangeSelection = this._onDidChangeSelection.event;121private _onDidChangeScroll = this._register(new Emitter<void>());122readonly onDidChangeScroll = this._onDidChangeScroll.event;123124constructor(125group: IEditorGroup,126@ITelemetryService telemetryService: ITelemetryService,127@IThemeService themeService: IThemeService,128@IStorageService storageService: IStorageService,129@IInstantiationService instantiationService: IInstantiationService,130@INotebookEditorService notebookWidgetService: INotebookEditorService,131@IContextKeyService contextKeyService: IContextKeyService,132@ICodeEditorService codeEditorService: ICodeEditorService,133@INotebookKernelService notebookKernelService: INotebookKernelService,134@ILanguageService languageService: ILanguageService,135@IKeybindingService keybindingService: IKeybindingService,136@IConfigurationService configurationService: IConfigurationService,137@IMenuService menuService: IMenuService,138@IContextMenuService contextMenuService: IContextMenuService,139@IEditorGroupsService editorGroupService: IEditorGroupsService,140@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService,141@INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService,142@IExtensionService extensionService: IExtensionService,143) {144super(145INTERACTIVE_WINDOW_EDITOR_ID,146group,147telemetryService,148themeService,149storageService150);151this._notebookWidgetService = notebookWidgetService;152this._configurationService = configurationService;153this._notebookKernelService = notebookKernelService;154this._languageService = languageService;155this._keybindingService = keybindingService;156this._menuService = menuService;157this._contextMenuService = contextMenuService;158this._editorGroupService = editorGroupService;159this._notebookExecutionStateService = notebookExecutionStateService;160this._extensionService = extensionService;161162this._rootElement = DOM.$('.interactive-editor');163this._contextKeyService = this._register(contextKeyService.createScoped(this._rootElement));164this._contextKeyService.createKey('isCompositeNotebook', true);165this._instantiationService = this._register(instantiationService.createChild(new ServiceCollection([IContextKeyService, this._contextKeyService])));166167this._editorOptions = this._computeEditorOptions();168this._register(this._configurationService.onDidChangeConfiguration(e => {169if (e.affectsConfiguration('editor') || e.affectsConfiguration('notebook')) {170this._editorOptions = this._computeEditorOptions();171}172}));173this._notebookOptions = instantiationService.createInstance(NotebookOptions, this.window, true, { cellToolbarInteraction: 'hover', globalToolbar: true, stickyScrollEnabled: false, dragAndDropEnabled: false, disableRulers: true });174this._editorMemento = this.getEditorMemento<InteractiveEditorViewState>(editorGroupService, textResourceConfigurationService, INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY);175176codeEditorService.registerDecorationType('interactive-decoration', DECORATION_KEY, {});177this._register(this._keybindingService.onDidUpdateKeybindings(this._updateInputHint, this));178this._register(this._notebookExecutionStateService.onDidChangeExecution((e) => {179if (e.type === NotebookExecutionType.cell && isEqual(e.notebook, this._notebookWidget.value?.viewModel?.notebookDocument.uri)) {180const cell = this._notebookWidget.value?.getCellByHandle(e.cellHandle);181if (cell && e.changed?.state) {182this._scrollIfNecessary(cell);183}184}185}));186}187188private get inputCellContainerHeight() {189return 19 + 2 + INPUT_CELL_VERTICAL_PADDING * 2 + INPUT_EDITOR_PADDING * 2;190}191192private get inputCellEditorHeight() {193return 19 + INPUT_EDITOR_PADDING * 2;194}195196protected createEditor(parent: HTMLElement): void {197DOM.append(parent, this._rootElement);198this._rootElement.style.position = 'relative';199this._notebookEditorContainer = DOM.append(this._rootElement, DOM.$('.notebook-editor-container'));200this._inputCellContainer = DOM.append(this._rootElement, DOM.$('.input-cell-container'));201this._inputCellContainer.style.position = 'absolute';202this._inputCellContainer.style.height = `${this.inputCellContainerHeight}px`;203this._inputFocusIndicator = DOM.append(this._inputCellContainer, DOM.$('.input-focus-indicator'));204this._inputRunButtonContainer = DOM.append(this._inputCellContainer, DOM.$('.run-button-container'));205this._setupRunButtonToolbar(this._inputRunButtonContainer);206this._inputEditorContainer = DOM.append(this._inputCellContainer, DOM.$('.input-editor-container'));207this._createLayoutStyles();208}209210private _setupRunButtonToolbar(runButtonContainer: HTMLElement) {211const menu = this._register(this._menuService.createMenu(MenuId.InteractiveInputExecute, this._contextKeyService));212this._runbuttonToolbar = this._register(new ToolBar(runButtonContainer, this._contextMenuService, {213getKeyBinding: action => this._keybindingService.lookupKeybinding(action.id),214actionViewItemProvider: (action, options) => {215return createActionViewItem(this._instantiationService, action, options);216},217renderDropdownAsChildElement: true218}));219220const { primary, secondary } = getActionBarActions(menu.getActions({ shouldForwardArgs: true }));221this._runbuttonToolbar.setActions([...primary, ...secondary]);222}223224private _createLayoutStyles(): void {225this._styleElement = domStylesheets.createStyleSheet(this._rootElement);226const styleSheets: string[] = [];227228const {229codeCellLeftMargin,230cellRunGutter231} = this._notebookOptions.getLayoutConfiguration();232const {233focusIndicator234} = this._notebookOptions.getDisplayOptions();235const leftMargin = this._notebookOptions.getCellEditorContainerLeftMargin();236237styleSheets.push(`238.interactive-editor .input-cell-container {239padding: ${INPUT_CELL_VERTICAL_PADDING}px ${INPUT_CELL_HORIZONTAL_PADDING_RIGHT}px ${INPUT_CELL_VERTICAL_PADDING}px ${leftMargin}px;240}241`);242if (focusIndicator === 'gutter') {243styleSheets.push(`244.interactive-editor .input-cell-container:focus-within .input-focus-indicator::before {245border-color: var(--vscode-notebook-focusedCellBorder) !important;246}247.interactive-editor .input-focus-indicator::before {248border-color: var(--vscode-notebook-inactiveFocusedCellBorder) !important;249}250.interactive-editor .input-cell-container .input-focus-indicator {251display: block;252top: ${INPUT_CELL_VERTICAL_PADDING}px;253}254.interactive-editor .input-cell-container {255border-top: 1px solid var(--vscode-notebook-inactiveFocusedCellBorder);256}257`);258} else {259// border260styleSheets.push(`261.interactive-editor .input-cell-container {262border-top: 1px solid var(--vscode-notebook-inactiveFocusedCellBorder);263}264.interactive-editor .input-cell-container .input-focus-indicator {265display: none;266}267`);268}269270styleSheets.push(`271.interactive-editor .input-cell-container .run-button-container {272width: ${cellRunGutter}px;273left: ${codeCellLeftMargin}px;274margin-top: ${INPUT_EDITOR_PADDING - 2}px;275}276`);277278this._styleElement.textContent = styleSheets.join('\n');279}280281private _computeEditorOptions(): IEditorOptions {282let overrideIdentifier: string | undefined = undefined;283if (this._codeEditorWidget) {284overrideIdentifier = this._codeEditorWidget.getModel()?.getLanguageId();285}286const editorOptions = deepClone(this._configurationService.getValue<IEditorOptions>('editor', { overrideIdentifier }));287const editorOptionsOverride = getSimpleEditorOptions(this._configurationService);288const computed = Object.freeze({289...editorOptions,290...editorOptionsOverride,291...{292glyphMargin: true,293padding: {294top: INPUT_EDITOR_PADDING,295bottom: INPUT_EDITOR_PADDING296},297hover: {298enabled: true299},300rulers: []301}302});303304return computed;305}306307protected override saveState(): void {308this._saveEditorViewState(this.input);309super.saveState();310}311312override getViewState(): InteractiveEditorViewState | undefined {313const input = this.input;314if (!(input instanceof InteractiveEditorInput)) {315return undefined;316}317318this._saveEditorViewState(input);319return this._loadNotebookEditorViewState(input);320}321322private _saveEditorViewState(input: EditorInput | undefined): void {323if (this._notebookWidget.value && input instanceof InteractiveEditorInput) {324if (this._notebookWidget.value.isDisposed) {325return;326}327328const state = this._notebookWidget.value.getEditorViewState();329const editorState = this._codeEditorWidget.saveViewState();330this._editorMemento.saveEditorState(this.group, input.notebookEditorInput.resource, {331notebook: state,332input: editorState333});334}335}336337private _loadNotebookEditorViewState(input: InteractiveEditorInput): InteractiveEditorViewState | undefined {338const result = this._editorMemento.loadEditorState(this.group, input.notebookEditorInput.resource);339if (result) {340return result;341}342// when we don't have a view state for the group/input-tuple then we try to use an existing343// editor for the same resource.344for (const group of this._editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) {345if (group.activeEditorPane !== this && group.activeEditorPane === this && group.activeEditor?.matches(input)) {346const notebook = this._notebookWidget.value?.getEditorViewState();347const input = this._codeEditorWidget.saveViewState();348return {349notebook,350input351};352}353}354return;355}356357override async setInput(input: InteractiveEditorInput, options: InteractiveEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {358const notebookInput = input.notebookEditorInput;359360// there currently is a widget which we still own so361// we need to hide it before getting a new widget362this._notebookWidget.value?.onWillHide();363364this._codeEditorWidget?.dispose();365366this._widgetDisposableStore.clear();367368this._notebookWidget = <IBorrowValue<NotebookEditorWidget>>this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, this.group.id, notebookInput, {369isReplHistory: true,370isReadOnly: true,371contributions: NotebookEditorExtensionsRegistry.getSomeEditorContributions([372ExecutionStateCellStatusBarContrib.id,373TimerCellStatusBarContrib.id,374NotebookFindContrib.id375]),376menuIds: {377notebookToolbar: MenuId.InteractiveToolbar,378cellTitleToolbar: MenuId.InteractiveCellTitle,379cellDeleteToolbar: MenuId.InteractiveCellDelete,380cellInsertToolbar: MenuId.NotebookCellBetween,381cellTopInsertToolbar: MenuId.NotebookCellListTop,382cellExecuteToolbar: MenuId.InteractiveCellExecute,383cellExecutePrimary: undefined384},385cellEditorContributions: EditorExtensionsRegistry.getSomeEditorContributions([386SelectionClipboardContributionID,387ContextMenuController.ID,388ContentHoverController.ID,389GlyphHoverController.ID,390MarkerController.ID391]),392options: this._notebookOptions,393codeWindow: this.window394}, undefined, this.window);395396this._codeEditorWidget = this._instantiationService.createInstance(CodeEditorWidget, this._inputEditorContainer, this._editorOptions, {397...{398isSimpleWidget: false,399contributions: EditorExtensionsRegistry.getSomeEditorContributions([400MenuPreventer.ID,401SelectionClipboardContributionID,402ContextMenuController.ID,403SuggestController.ID,404ParameterHintsController.ID,405SnippetController2.ID,406TabCompletionController.ID,407ContentHoverController.ID,408GlyphHoverController.ID,409MarkerController.ID,410INLINE_CHAT_ID,411])412}413});414415if (this._lastLayoutDimensions) {416this._notebookEditorContainer.style.height = `${this._lastLayoutDimensions.dimension.height - this.inputCellContainerHeight}px`;417this._notebookWidget.value!.layout(new DOM.Dimension(this._lastLayoutDimensions.dimension.width, this._lastLayoutDimensions.dimension.height - this.inputCellContainerHeight), this._notebookEditorContainer);418const leftMargin = this._notebookOptions.getCellEditorContainerLeftMargin();419const maxHeight = Math.min(this._lastLayoutDimensions.dimension.height / 2, this.inputCellEditorHeight);420this._codeEditorWidget.layout(this._validateDimension(this._lastLayoutDimensions.dimension.width - leftMargin - INPUT_CELL_HORIZONTAL_PADDING_RIGHT, maxHeight));421this._inputFocusIndicator.style.height = `${this.inputCellEditorHeight}px`;422this._inputCellContainer.style.top = `${this._lastLayoutDimensions.dimension.height - this.inputCellContainerHeight}px`;423this._inputCellContainer.style.width = `${this._lastLayoutDimensions.dimension.width}px`;424}425426await super.setInput(input, options, context, token);427const model = await input.resolve();428if (this._runbuttonToolbar) {429this._runbuttonToolbar.context = input.resource;430}431432if (model === null) {433throw new Error('The Interactive Window model could not be resolved');434}435436this._notebookWidget.value?.setParentContextKeyService(this._contextKeyService);437438const viewState = options?.viewState ?? this._loadNotebookEditorViewState(input);439await this._extensionService.whenInstalledExtensionsRegistered();440await this._notebookWidget.value!.setModel(model.notebook, viewState?.notebook);441model.notebook.setCellCollapseDefault(this._notebookOptions.getCellCollapseDefault());442this._notebookWidget.value!.setOptions({443isReadOnly: true444});445this._widgetDisposableStore.add(this._notebookWidget.value!.onDidResizeOutput((cvm) => {446this._scrollIfNecessary(cvm);447}));448this._widgetDisposableStore.add(this._notebookWidget.value!.onDidFocusWidget(() => this._onDidFocusWidget.fire()));449this._widgetDisposableStore.add(this._notebookOptions.onDidChangeOptions(e => {450if (e.compactView || e.focusIndicator) {451// update the styling452this._styleElement?.remove();453this._createLayoutStyles();454}455456if (this._lastLayoutDimensions && this.isVisible()) {457this.layout(this._lastLayoutDimensions.dimension, this._lastLayoutDimensions.position);458}459460if (e.interactiveWindowCollapseCodeCells) {461model.notebook.setCellCollapseDefault(this._notebookOptions.getCellCollapseDefault());462}463}));464465const languageId = this._notebookWidget.value?.activeKernel?.supportedLanguages[0] ?? input.language ?? PLAINTEXT_LANGUAGE_ID;466const editorModel = await input.resolveInput(languageId);467editorModel.setLanguage(languageId);468this._codeEditorWidget.setModel(editorModel);469if (viewState?.input) {470this._codeEditorWidget.restoreViewState(viewState.input);471}472this._editorOptions = this._computeEditorOptions();473this._codeEditorWidget.updateOptions(this._editorOptions);474475this._widgetDisposableStore.add(this._codeEditorWidget.onDidFocusEditorWidget(() => this._onDidFocusWidget.fire()));476this._widgetDisposableStore.add(this._codeEditorWidget.onDidContentSizeChange(e => {477if (!e.contentHeightChanged) {478return;479}480481if (this._lastLayoutDimensions) {482this._layoutWidgets(this._lastLayoutDimensions.dimension, this._lastLayoutDimensions.position);483}484}));485486this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeCursorPosition(e => this._onDidChangeSelection.fire({ reason: this._toEditorPaneSelectionChangeReason(e) })));487this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeModelContent(() => this._onDidChangeSelection.fire({ reason: EditorPaneSelectionChangeReason.EDIT })));488489490this._widgetDisposableStore.add(this._notebookKernelService.onDidChangeNotebookAffinity(this._syncWithKernel, this));491this._widgetDisposableStore.add(this._notebookKernelService.onDidChangeSelectedNotebooks(this._syncWithKernel, this));492493this._widgetDisposableStore.add(this.themeService.onDidColorThemeChange(() => {494if (this.isVisible()) {495this._updateInputHint();496}497}));498499this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeModelContent(() => {500if (this.isVisible()) {501this._updateInputHint();502}503}));504505this._codeEditorWidget.onDidChangeModelDecorations(() => {506if (this.isVisible()) {507this._updateInputHint();508}509});510511this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeModel(() => {512this._updateInputHint();513}));514515this._configurationService.onDidChangeConfiguration(e => {516if (e.affectsConfiguration(ReplEditorSettings.showExecutionHint)) {517this._updateInputHint();518}519});520521const cursorAtBoundaryContext = INTERACTIVE_INPUT_CURSOR_BOUNDARY.bindTo(this._contextKeyService);522if (input.resource && input.historyService.has(input.resource)) {523cursorAtBoundaryContext.set('top');524} else {525cursorAtBoundaryContext.set('none');526}527528this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeCursorPosition(({ position }) => {529const viewModel = this._codeEditorWidget._getViewModel()!;530const lastLineNumber = viewModel.getLineCount();531const lastLineCol = viewModel.getLineLength(lastLineNumber) + 1;532const viewPosition = viewModel.coordinatesConverter.convertModelPositionToViewPosition(position);533const firstLine = viewPosition.lineNumber === 1 && viewPosition.column === 1;534const lastLine = viewPosition.lineNumber === lastLineNumber && viewPosition.column === lastLineCol;535536if (firstLine) {537if (lastLine) {538cursorAtBoundaryContext.set('both');539} else {540cursorAtBoundaryContext.set('top');541}542} else {543if (lastLine) {544cursorAtBoundaryContext.set('bottom');545} else {546cursorAtBoundaryContext.set('none');547}548}549}));550551this._widgetDisposableStore.add(editorModel.onDidChangeContent(() => {552const value = editorModel.getValue();553if (this.input?.resource) {554const historyService = (this.input as InteractiveEditorInput).historyService;555if (!historyService.matchesCurrent(this.input.resource, value)) {556historyService.replaceLast(this.input.resource, value);557}558}559}));560561this._widgetDisposableStore.add(this._notebookWidget.value!.onDidScroll(() => this._onDidChangeScroll.fire()));562563this._syncWithKernel();564565this._updateInputHint();566}567568override setOptions(options: INotebookEditorOptions | undefined): void {569this._notebookWidget.value?.setOptions(options);570super.setOptions(options);571}572573private _toEditorPaneSelectionChangeReason(e: ICursorPositionChangedEvent): EditorPaneSelectionChangeReason {574switch (e.source) {575case TextEditorSelectionSource.PROGRAMMATIC: return EditorPaneSelectionChangeReason.PROGRAMMATIC;576case TextEditorSelectionSource.NAVIGATION: return EditorPaneSelectionChangeReason.NAVIGATION;577case TextEditorSelectionSource.JUMP: return EditorPaneSelectionChangeReason.JUMP;578default: return EditorPaneSelectionChangeReason.USER;579}580}581582private _cellAtBottom(cell: ICellViewModel): boolean {583const visibleRanges = this._notebookWidget.value?.visibleRanges || [];584const cellIndex = this._notebookWidget.value?.getCellIndex(cell);585if (cellIndex === Math.max(...visibleRanges.map(range => range.end - 1))) {586return true;587}588return false;589}590591private _scrollIfNecessary(cvm: ICellViewModel) {592const index = this._notebookWidget.value!.getCellIndex(cvm);593if (index === this._notebookWidget.value!.getLength() - 1) {594// If we're already at the bottom or auto scroll is enabled, scroll to the bottom595if (this._configurationService.getValue<boolean>(ReplEditorSettings.interactiveWindowAlwaysScrollOnNewCell) || this._cellAtBottom(cvm)) {596this._notebookWidget.value!.scrollToBottom();597}598}599}600601private _syncWithKernel() {602const notebook = this._notebookWidget.value?.textModel;603const textModel = this._codeEditorWidget.getModel();604605if (notebook && textModel) {606const info = this._notebookKernelService.getMatchingKernel(notebook);607const selectedOrSuggested = info.selected608?? (info.suggestions.length === 1 ? info.suggestions[0] : undefined)609?? (info.all.length === 1 ? info.all[0] : undefined);610611if (selectedOrSuggested) {612const language = selectedOrSuggested.supportedLanguages[0];613// All kernels will initially list plaintext as the supported language before they properly initialized.614if (language && language !== 'plaintext') {615const newMode = this._languageService.createById(language).languageId;616textModel.setLanguage(newMode);617}618619NOTEBOOK_KERNEL.bindTo(this._contextKeyService).set(selectedOrSuggested.id);620}621}622}623624layout(dimension: DOM.Dimension, position: DOM.IDomPosition): void {625this._rootElement.classList.toggle('mid-width', dimension.width < 1000 && dimension.width >= 600);626this._rootElement.classList.toggle('narrow-width', dimension.width < 600);627const editorHeightChanged = dimension.height !== this._lastLayoutDimensions?.dimension.height;628this._lastLayoutDimensions = { dimension, position };629630if (!this._notebookWidget.value) {631return;632}633634if (editorHeightChanged && this._codeEditorWidget) {635SuggestController.get(this._codeEditorWidget)?.cancelSuggestWidget();636}637638this._notebookEditorContainer.style.height = `${this._lastLayoutDimensions.dimension.height - this.inputCellContainerHeight}px`;639this._layoutWidgets(dimension, position);640}641642private _layoutWidgets(dimension: DOM.Dimension, position: DOM.IDomPosition) {643const contentHeight = this._codeEditorWidget.hasModel() ? this._codeEditorWidget.getContentHeight() : this.inputCellEditorHeight;644const maxHeight = Math.min(dimension.height / 2, contentHeight);645const leftMargin = this._notebookOptions.getCellEditorContainerLeftMargin();646647const inputCellContainerHeight = maxHeight + INPUT_CELL_VERTICAL_PADDING * 2;648this._notebookEditorContainer.style.height = `${dimension.height - inputCellContainerHeight}px`;649650this._notebookWidget.value!.layout(dimension.with(dimension.width, dimension.height - inputCellContainerHeight), this._notebookEditorContainer, position);651this._codeEditorWidget.layout(this._validateDimension(dimension.width - leftMargin - INPUT_CELL_HORIZONTAL_PADDING_RIGHT, maxHeight));652this._inputFocusIndicator.style.height = `${contentHeight}px`;653this._inputCellContainer.style.top = `${dimension.height - inputCellContainerHeight}px`;654this._inputCellContainer.style.width = `${dimension.width}px`;655}656657private _validateDimension(width: number, height: number) {658return new DOM.Dimension(Math.max(0, width), Math.max(0, height));659}660661private _hasConflictingDecoration() {662return Boolean(this._codeEditorWidget.getLineDecorations(1)?.find((d) =>663d.options.beforeContentClassName664|| d.options.afterContentClassName665|| d.options.before?.content666|| d.options.after?.content667));668}669670private _updateInputHint(): void {671if (!this._codeEditorWidget) {672return;673}674675const shouldHide =676!this._codeEditorWidget.hasModel() ||677this._configurationService.getValue<boolean>(ReplEditorSettings.showExecutionHint) === false ||678this._codeEditorWidget.getModel()!.getValueLength() !== 0 ||679this._hasConflictingDecoration();680681if (!this._hintElement && !shouldHide) {682this._hintElement = this._instantiationService.createInstance(ReplInputHintContentWidget, this._codeEditorWidget);683} else if (this._hintElement && shouldHide) {684this._hintElement.dispose();685this._hintElement = undefined;686}687}688689getScrollPosition(): IEditorPaneScrollPosition {690return {691scrollTop: this._notebookWidget.value?.scrollTop ?? 0,692scrollLeft: 0693};694}695696setScrollPosition(position: IEditorPaneScrollPosition): void {697this._notebookWidget.value?.setScrollTop(position.scrollTop);698}699700override focus() {701super.focus();702703this._notebookWidget.value?.onShow();704this._codeEditorWidget.focus();705}706707focusHistory() {708this._notebookWidget.value!.focus();709}710711protected override setEditorVisible(visible: boolean): void {712super.setEditorVisible(visible);713this._groupListener.value = this.group.onWillCloseEditor(e => this._saveEditorViewState(e.editor));714715if (!visible) {716this._saveEditorViewState(this.input);717if (this.input && this._notebookWidget.value) {718this._notebookWidget.value.onWillHide();719}720}721722this._updateInputHint();723}724725override clearInput() {726if (this._notebookWidget.value) {727this._saveEditorViewState(this.input);728this._notebookWidget.value.onWillHide();729}730731this._codeEditorWidget?.dispose();732733this._notebookWidget = { value: undefined };734this._widgetDisposableStore.clear();735736super.clearInput();737}738739override getControl(): ReplEditorControl & ICompositeCodeEditor {740return {741notebookEditor: this._notebookWidget.value,742activeCodeEditor: this._codeEditorWidget,743onDidChangeActiveEditor: Event.None744};745}746}747748749