Path: blob/main/src/vs/workbench/contrib/interactive/browser/interactive.contribution.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 { Iterable } from '../../../../base/common/iterator.js';6import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';7import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js';8import { parse } from '../../../../base/common/marshalling.js';9import { Schemas } from '../../../../base/common/network.js';10import { extname, isEqual } from '../../../../base/common/resources.js';11import { isFalsyOrWhitespace } from '../../../../base/common/strings.js';12import { URI, UriComponents } from '../../../../base/common/uri.js';13import { IBulkEditService } from '../../../../editor/browser/services/bulkEditService.js';14import { EditOperation } from '../../../../editor/common/core/editOperation.js';15import { PLAINTEXT_LANGUAGE_ID } from '../../../../editor/common/languages/modesRegistry.js';16import { ITextModel } from '../../../../editor/common/model.js';17import { IModelService } from '../../../../editor/common/services/model.js';18import { ITextModelContentProvider, ITextModelService } from '../../../../editor/common/services/resolverService.js';19import { peekViewBorder } from '../../../../editor/contrib/peekView/browser/peekView.js';20import { Context as SuggestContext } from '../../../../editor/contrib/suggest/browser/suggest.js';21import { localize, localize2 } from '../../../../nls.js';22import { ILocalizedString } from '../../../../platform/action/common/action.js';23import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';24import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';25import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from '../../../../platform/configuration/common/configurationRegistry.js';26import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';27import { EditorActivation, ITextResourceEditorInput } from '../../../../platform/editor/common/editor.js';28import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';29import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';30import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';31import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';32import { ILogService } from '../../../../platform/log/common/log.js';33import { Registry } from '../../../../platform/registry/common/platform.js';34import { contrastBorder, ifDefinedThenElse, listInactiveSelectionBackground, registerColor } from '../../../../platform/theme/common/colorRegistry.js';35import { EditorPaneDescriptor, IEditorPaneRegistry } from '../../../browser/editor.js';36import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js';37import { EditorExtensions, EditorsOrder, IEditorControl, IEditorFactoryRegistry, IEditorSerializer, IUntypedEditorInput } from '../../../common/editor.js';38import { EditorInput } from '../../../common/editor/editorInput.js';39import { PANEL_BORDER } from '../../../common/theme.js';40import { ResourceNotebookCellEdit } from '../../bulkEdit/browser/bulkCellEdits.js';41import { ReplEditorSettings, INTERACTIVE_INPUT_CURSOR_BOUNDARY } from './interactiveCommon.js';42import { IInteractiveDocumentService, InteractiveDocumentService } from './interactiveDocumentService.js';43import { InteractiveEditor } from './interactiveEditor.js';44import { InteractiveEditorInput } from './interactiveEditorInput.js';45import { IInteractiveHistoryService, InteractiveHistoryService } from './interactiveHistoryService.js';46import { NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from '../../notebook/browser/controller/coreActions.js';47import { INotebookEditorOptions } from '../../notebook/browser/notebookBrowser.js';48import * as icons from '../../notebook/browser/notebookIcons.js';49import { INotebookEditorService } from '../../notebook/browser/services/notebookEditorService.js';50import { CellEditType, CellKind, CellUri, INTERACTIVE_WINDOW_EDITOR_ID, NotebookSetting, NotebookWorkingCopyTypeIdentifier } from '../../notebook/common/notebookCommon.js';51import { InteractiveWindowOpen, IS_COMPOSITE_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED } from '../../notebook/common/notebookContextKeys.js';52import { INotebookKernelService } from '../../notebook/common/notebookKernelService.js';53import { INotebookService } from '../../notebook/common/notebookService.js';54import { columnToEditorGroup } from '../../../services/editor/common/editorGroupColumn.js';55import { IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js';56import { IEditorResolverService, RegisteredEditorPriority } from '../../../services/editor/common/editorResolverService.js';57import { IEditorService } from '../../../services/editor/common/editorService.js';58import { IExtensionService } from '../../../services/extensions/common/extensions.js';59import { IWorkingCopyIdentifier } from '../../../services/workingCopy/common/workingCopy.js';60import { IWorkingCopyEditorHandler, IWorkingCopyEditorService } from '../../../services/workingCopy/common/workingCopyEditorService.js';61import { isReplEditorControl, ReplEditorControl } from '../../replNotebook/browser/replEditor.js';62import { InlineChatController } from '../../inlineChat/browser/inlineChatController.js';63import { IsLinuxContext, IsWindowsContext } from '../../../../platform/contextkey/common/contextkeys.js';6465const interactiveWindowCategory: ILocalizedString = localize2('interactiveWindow', "Interactive Window");6667Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane).registerEditorPane(68EditorPaneDescriptor.create(69InteractiveEditor,70INTERACTIVE_WINDOW_EDITOR_ID,71'Interactive Window'72),73[74new SyncDescriptor(InteractiveEditorInput)75]76);7778export class InteractiveDocumentContribution extends Disposable implements IWorkbenchContribution {7980static readonly ID = 'workbench.contrib.interactiveDocument';8182constructor(83@INotebookService notebookService: INotebookService,84@IEditorResolverService editorResolverService: IEditorResolverService,85@IEditorService editorService: IEditorService,86@IInstantiationService private readonly instantiationService: IInstantiationService87) {88super();8990const info = notebookService.getContributedNotebookType('interactive');9192// We need to contribute a notebook type for the Interactive Window to provide notebook models.93if (!info) {94this._register(notebookService.registerContributedNotebookType('interactive', {95providerDisplayName: 'Interactive Notebook',96displayName: 'Interactive',97filenamePattern: ['*.interactive'],98priority: RegisteredEditorPriority.builtin99}));100}101102editorResolverService.registerEditor(103`${Schemas.vscodeInteractiveInput}:/**`,104{105id: 'vscode-interactive-input',106label: 'Interactive Editor',107priority: RegisteredEditorPriority.exclusive108},109{110canSupportResource: uri => uri.scheme === Schemas.vscodeInteractiveInput,111singlePerResource: true112},113{114createEditorInput: ({ resource }) => {115const editorInput = editorService.findEditors({116resource,117editorId: 'interactive',118typeId: InteractiveEditorInput.ID119}, { order: EditorsOrder.SEQUENTIAL }).at(0);120return editorInput!;121}122}123);124125editorResolverService.registerEditor(126`*.interactive`,127{128id: 'interactive',129label: 'Interactive Editor',130priority: RegisteredEditorPriority.exclusive131},132{133canSupportResource: uri =>134(uri.scheme === Schemas.untitled && extname(uri) === '.interactive') ||135(uri.scheme === Schemas.vscodeNotebookCell && extname(uri) === '.interactive'),136singlePerResource: true137},138{139createEditorInput: ({ resource, options }) => {140const data = CellUri.parse(resource);141let cellOptions: ITextResourceEditorInput | undefined;142let iwResource = resource;143144if (data) {145cellOptions = { resource, options };146iwResource = data.notebook;147}148149const notebookOptions: INotebookEditorOptions | undefined = {150...options,151cellOptions,152cellRevealType: undefined,153cellSelections: undefined,154isReadOnly: undefined,155viewState: undefined,156indexedCellOptions: undefined157};158159const editorInput = createEditor(iwResource, this.instantiationService);160return {161editor: editorInput,162options: notebookOptions163};164},165createUntitledEditorInput: ({ resource, options }) => {166if (!resource) {167throw new Error('Interactive window editors must have a resource name');168}169const data = CellUri.parse(resource);170let cellOptions: ITextResourceEditorInput | undefined;171172if (data) {173cellOptions = { resource, options };174}175176const notebookOptions: INotebookEditorOptions = {177...options,178cellOptions,179cellRevealType: undefined,180cellSelections: undefined,181isReadOnly: undefined,182viewState: undefined,183indexedCellOptions: undefined184};185186const editorInput = createEditor(resource, this.instantiationService);187return {188editor: editorInput,189options: notebookOptions190};191}192}193);194}195}196197class InteractiveInputContentProvider implements ITextModelContentProvider {198199static readonly ID = 'workbench.contrib.interactiveInputContentProvider';200201private readonly _registration: IDisposable;202203constructor(204@ITextModelService textModelService: ITextModelService,205@IModelService private readonly _modelService: IModelService,206) {207this._registration = textModelService.registerTextModelContentProvider(Schemas.vscodeInteractiveInput, this);208}209210dispose(): void {211this._registration.dispose();212}213214async provideTextContent(resource: URI): Promise<ITextModel | null> {215const existing = this._modelService.getModel(resource);216if (existing) {217return existing;218}219const result: ITextModel | null = this._modelService.createModel('', null, resource, false);220return result;221}222}223224function createEditor(resource: URI, instantiationService: IInstantiationService): EditorInput {225const counter = /\/Interactive-(\d+)/.exec(resource.path);226const inputBoxPath = counter && counter[1] ? `/InteractiveInput-${counter[1]}` : 'InteractiveInput';227const inputUri = URI.from({ scheme: Schemas.vscodeInteractiveInput, path: inputBoxPath });228const editorInput = InteractiveEditorInput.create(instantiationService, resource, inputUri);229230return editorInput;231}232233class InteractiveWindowWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution, IWorkingCopyEditorHandler {234235static readonly ID = 'workbench.contrib.interactiveWindowWorkingCopyEditorHandler';236237constructor(238@IInstantiationService private readonly _instantiationService: IInstantiationService,239@IWorkingCopyEditorService private readonly _workingCopyEditorService: IWorkingCopyEditorService,240@IExtensionService private readonly _extensionService: IExtensionService,241) {242super();243244this._installHandler();245}246247handles(workingCopy: IWorkingCopyIdentifier): boolean {248const viewType = this._getViewType(workingCopy);249return !!viewType && viewType === 'interactive';250251}252253isOpen(workingCopy: IWorkingCopyIdentifier, editor: EditorInput): boolean {254if (!this.handles(workingCopy)) {255return false;256}257258return editor instanceof InteractiveEditorInput && isEqual(workingCopy.resource, editor.resource);259}260261createEditor(workingCopy: IWorkingCopyIdentifier): EditorInput {262return createEditor(workingCopy.resource, this._instantiationService);263}264265private async _installHandler(): Promise<void> {266await this._extensionService.whenInstalledExtensionsRegistered();267268this._register(this._workingCopyEditorService.registerHandler(this));269}270271private _getViewType(workingCopy: IWorkingCopyIdentifier): string | undefined {272return NotebookWorkingCopyTypeIdentifier.parse(workingCopy.typeId)?.viewType;273}274}275276registerWorkbenchContribution2(InteractiveDocumentContribution.ID, InteractiveDocumentContribution, WorkbenchPhase.BlockRestore);277registerWorkbenchContribution2(InteractiveInputContentProvider.ID, InteractiveInputContentProvider, {278editorTypeId: INTERACTIVE_WINDOW_EDITOR_ID279});280registerWorkbenchContribution2(InteractiveWindowWorkingCopyEditorHandler.ID, InteractiveWindowWorkingCopyEditorHandler, {281editorTypeId: INTERACTIVE_WINDOW_EDITOR_ID282});283284type interactiveEditorInputData = { resource: URI; inputResource: URI; name: string; language: string };285286export class InteractiveEditorSerializer implements IEditorSerializer {287public static readonly ID = InteractiveEditorInput.ID;288289canSerialize(editor: EditorInput): editor is InteractiveEditorInput {290if (!(editor instanceof InteractiveEditorInput)) {291return false;292}293294return URI.isUri(editor.primary.resource) && URI.isUri(editor.inputResource);295}296297serialize(input: EditorInput): string | undefined {298if (!this.canSerialize(input)) {299return undefined;300}301302return JSON.stringify({303resource: input.primary.resource,304inputResource: input.inputResource,305name: input.getName(),306language: input.language307});308}309310deserialize(instantiationService: IInstantiationService, raw: string) {311const data = <interactiveEditorInputData>parse(raw);312if (!data) {313return undefined;314}315const { resource, inputResource, name, language } = data;316if (!URI.isUri(resource) || !URI.isUri(inputResource)) {317return undefined;318}319320const input = InteractiveEditorInput.create(instantiationService, resource, inputResource, name, language);321return input;322}323}324325Registry.as<IEditorFactoryRegistry>(EditorExtensions.EditorFactory)326.registerEditorSerializer(327InteractiveEditorSerializer.ID,328InteractiveEditorSerializer);329330registerSingleton(IInteractiveHistoryService, InteractiveHistoryService, InstantiationType.Delayed);331registerSingleton(IInteractiveDocumentService, InteractiveDocumentService, InstantiationType.Delayed);332333registerAction2(class extends Action2 {334constructor() {335super({336id: '_interactive.open',337title: localize2('interactive.open', 'Open Interactive Window'),338f1: false,339category: interactiveWindowCategory,340metadata: {341description: localize('interactive.open', 'Open Interactive Window'),342args: [343{344name: 'showOptions',345description: 'Show Options',346schema: {347type: 'object',348properties: {349'viewColumn': {350type: 'number',351default: -1352},353'preserveFocus': {354type: 'boolean',355default: true356}357},358}359},360{361name: 'resource',362description: 'Interactive resource Uri',363isOptional: true364},365{366name: 'controllerId',367description: 'Notebook controller Id',368isOptional: true369},370{371name: 'title',372description: 'Notebook editor title',373isOptional: true374}375]376}377378});379}380381async run(accessor: ServicesAccessor, showOptions?: number | { viewColumn?: number; preserveFocus?: boolean }, resource?: URI, id?: string, title?: string): Promise<{ notebookUri: URI; inputUri: URI; notebookEditorId?: string }> {382const editorService = accessor.get(IEditorService);383const editorGroupService = accessor.get(IEditorGroupsService);384const historyService = accessor.get(IInteractiveHistoryService);385const kernelService = accessor.get(INotebookKernelService);386const logService = accessor.get(ILogService);387const configurationService = accessor.get(IConfigurationService);388const group = columnToEditorGroup(editorGroupService, configurationService, typeof showOptions === 'number' ? showOptions : showOptions?.viewColumn);389const editorOptions = {390activation: EditorActivation.PRESERVE,391preserveFocus: typeof showOptions !== 'number' ? (showOptions?.preserveFocus ?? false) : false392};393394if (resource && extname(resource) === '.interactive') {395logService.debug('Open interactive window from resource:', resource.toString());396const resourceUri = URI.revive(resource);397const editors = editorService.findEditors(resourceUri).filter(id => id.editor instanceof InteractiveEditorInput && id.editor.resource?.toString() === resourceUri.toString());398if (editors.length) {399logService.debug('Find existing interactive window:', resource.toString());400const editorInput = editors[0].editor as InteractiveEditorInput;401const currentGroup = editors[0].groupId;402const editor = await editorService.openEditor(editorInput, editorOptions, currentGroup);403const editorControl = editor?.getControl() as ReplEditorControl;404405return {406notebookUri: editorInput.resource,407inputUri: editorInput.inputResource,408notebookEditorId: editorControl?.notebookEditor?.getId()409};410}411}412413const existingNotebookDocument = new Set<string>();414editorService.getEditors(EditorsOrder.SEQUENTIAL).forEach(editor => {415if (editor.editor.resource) {416existingNotebookDocument.add(editor.editor.resource.toString());417}418});419420let notebookUri: URI | undefined = undefined;421let inputUri: URI | undefined = undefined;422let counter = 1;423do {424notebookUri = URI.from({ scheme: Schemas.untitled, path: `/Interactive-${counter}.interactive` });425inputUri = URI.from({ scheme: Schemas.vscodeInteractiveInput, path: `/InteractiveInput-${counter}` });426427counter++;428} while (existingNotebookDocument.has(notebookUri.toString()));429InteractiveEditorInput.setName(notebookUri, title);430431logService.debug('Open new interactive window:', notebookUri.toString(), inputUri.toString());432433if (id) {434const allKernels = kernelService.getMatchingKernel({ uri: notebookUri, notebookType: 'interactive' }).all;435const preferredKernel = allKernels.find(kernel => kernel.id === id);436if (preferredKernel) {437kernelService.preselectKernelForNotebook(preferredKernel, { uri: notebookUri, notebookType: 'interactive' });438}439}440441historyService.clearHistory(notebookUri);442const editorInput: IUntypedEditorInput = { resource: notebookUri, options: editorOptions };443const editorPane = await editorService.openEditor(editorInput, group);444const editorControl = editorPane?.getControl() as ReplEditorControl;445// Extensions must retain references to these URIs to manipulate the interactive editor446logService.debug('New interactive window opened. Notebook editor id', editorControl?.notebookEditor?.getId());447return { notebookUri, inputUri, notebookEditorId: editorControl?.notebookEditor?.getId() };448}449});450451registerAction2(class extends Action2 {452constructor() {453super({454id: 'interactive.execute',455title: localize2('interactive.execute', 'Execute Code'),456category: interactiveWindowCategory,457keybinding: [{458// when: NOTEBOOK_CELL_LIST_FOCUSED,459when: ContextKeyExpr.and(460IS_COMPOSITE_NOTEBOOK,461ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive')462),463primary: KeyMod.CtrlCmd | KeyCode.Enter,464weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT465}, {466when: ContextKeyExpr.and(467IS_COMPOSITE_NOTEBOOK,468ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'),469ContextKeyExpr.equals('config.interactiveWindow.executeWithShiftEnter', true)470),471primary: KeyMod.Shift | KeyCode.Enter,472weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT473}, {474when: ContextKeyExpr.and(475IS_COMPOSITE_NOTEBOOK,476ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'),477ContextKeyExpr.equals('config.interactiveWindow.executeWithShiftEnter', false)478),479primary: KeyCode.Enter,480weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT481}],482menu: [483{484id: MenuId.InteractiveInputExecute485},486],487icon: icons.executeIcon,488f1: false,489metadata: {490description: 'Execute the Contents of the Input Box',491args: [492{493name: 'resource',494description: 'Interactive resource Uri',495isOptional: true496}497]498}499});500}501502async run(accessor: ServicesAccessor, context?: UriComponents): Promise<void> {503const editorService = accessor.get(IEditorService);504const bulkEditService = accessor.get(IBulkEditService);505const historyService = accessor.get(IInteractiveHistoryService);506const notebookEditorService = accessor.get(INotebookEditorService);507let editorControl: IEditorControl | undefined;508if (context) {509const resourceUri = URI.revive(context);510const editors = editorService.findEditors(resourceUri);511for (const found of editors) {512if (found.editor.typeId === InteractiveEditorInput.ID) {513const editor = await editorService.openEditor(found.editor, found.groupId);514editorControl = editor?.getControl();515break;516}517}518}519else {520editorControl = editorService.activeEditorPane?.getControl();521}522523if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {524const notebookDocument = editorControl.notebookEditor.textModel;525const textModel = editorControl.activeCodeEditor?.getModel();526const activeKernel = editorControl.notebookEditor.activeKernel;527const language = activeKernel?.supportedLanguages[0] ?? PLAINTEXT_LANGUAGE_ID;528529if (notebookDocument && textModel && editorControl.activeCodeEditor) {530const index = notebookDocument.length;531const value = textModel.getValue();532533if (isFalsyOrWhitespace(value)) {534return;535}536537const ctrl = InlineChatController.get(editorControl.activeCodeEditor);538if (ctrl) {539ctrl.acceptSession();540}541542historyService.replaceLast(notebookDocument.uri, value);543historyService.addToHistory(notebookDocument.uri, '');544textModel.setValue('');545546const collapseState = editorControl.notebookEditor.notebookOptions.getDisplayOptions().interactiveWindowCollapseCodeCells === 'fromEditor' ?547{548inputCollapsed: false,549outputCollapsed: false550} :551undefined;552553await bulkEditService.apply([554new ResourceNotebookCellEdit(notebookDocument.uri,555{556editType: CellEditType.Replace,557index: index,558count: 0,559cells: [{560cellKind: CellKind.Code,561mime: undefined,562language,563source: value,564outputs: [],565metadata: {},566collapseState567}]568}569)570]);571572// reveal the cell into view first573const range = { start: index, end: index + 1 };574editorControl.notebookEditor.revealCellRangeInView(range);575await editorControl.notebookEditor.executeNotebookCells(editorControl.notebookEditor.getCellsInRange({ start: index, end: index + 1 }));576577// update the selection and focus in the extension host model578const editor = notebookEditorService.getNotebookEditor(editorControl.notebookEditor.getId());579if (editor) {580editor.setSelections([range]);581editor.setFocus(range);582}583}584}585}586});587588registerAction2(class extends Action2 {589constructor() {590super({591id: 'interactive.input.clear',592title: localize2('interactive.input.clear', 'Clear the interactive window input editor contents'),593category: interactiveWindowCategory,594f1: false595});596}597598async run(accessor: ServicesAccessor): Promise<void> {599const editorService = accessor.get(IEditorService);600const editorControl = editorService.activeEditorPane?.getControl();601602if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {603const notebookDocument = editorControl.notebookEditor.textModel;604const editor = editorControl.activeCodeEditor;605const range = editor?.getModel()?.getFullModelRange();606607608if (notebookDocument && editor && range) {609editor.executeEdits('', [EditOperation.replace(range, null)]);610}611}612}613});614615registerAction2(class extends Action2 {616constructor() {617super({618id: 'interactive.history.previous',619title: localize2('interactive.history.previous', 'Previous value in history'),620category: interactiveWindowCategory,621f1: false,622keybinding: {623when: ContextKeyExpr.and(624INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('bottom'),625INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('none'),626SuggestContext.Visible.toNegated()627),628primary: KeyCode.UpArrow,629weight: KeybindingWeight.WorkbenchContrib630},631precondition: ContextKeyExpr.and(IS_COMPOSITE_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED.negate())632});633}634635async run(accessor: ServicesAccessor): Promise<void> {636const editorService = accessor.get(IEditorService);637const historyService = accessor.get(IInteractiveHistoryService);638const editorControl = editorService.activeEditorPane?.getControl();639640641642if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {643const notebookDocument = editorControl.notebookEditor.textModel;644const textModel = editorControl.activeCodeEditor?.getModel();645646if (notebookDocument && textModel) {647const previousValue = historyService.getPreviousValue(notebookDocument.uri);648if (previousValue) {649textModel.setValue(previousValue);650}651}652}653}654});655656registerAction2(class extends Action2 {657constructor() {658super({659id: 'interactive.history.next',660title: localize2('interactive.history.next', 'Next value in history'),661category: interactiveWindowCategory,662f1: false,663keybinding: {664when: ContextKeyExpr.and(665INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('top'),666INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('none'),667SuggestContext.Visible.toNegated()668),669primary: KeyCode.DownArrow,670weight: KeybindingWeight.WorkbenchContrib671},672precondition: ContextKeyExpr.and(IS_COMPOSITE_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED.negate())673});674}675676async run(accessor: ServicesAccessor): Promise<void> {677const editorService = accessor.get(IEditorService);678const historyService = accessor.get(IInteractiveHistoryService);679const editorControl = editorService.activeEditorPane?.getControl();680681if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {682const notebookDocument = editorControl.notebookEditor.textModel;683const textModel = editorControl.activeCodeEditor?.getModel();684685if (notebookDocument && textModel) {686const nextValue = historyService.getNextValue(notebookDocument.uri);687if (nextValue !== null) {688textModel.setValue(nextValue);689}690}691}692}693});694695696registerAction2(class extends Action2 {697constructor() {698super({699id: 'interactive.scrollToTop',700title: localize('interactiveScrollToTop', 'Scroll to Top'),701keybinding: {702when: ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'),703primary: KeyMod.CtrlCmd | KeyCode.Home,704mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow },705weight: KeybindingWeight.WorkbenchContrib706},707category: interactiveWindowCategory,708});709}710711async run(accessor: ServicesAccessor): Promise<void> {712const editorService = accessor.get(IEditorService);713const editorControl = editorService.activeEditorPane?.getControl();714715if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {716if (editorControl.notebookEditor.getLength() === 0) {717return;718}719720editorControl.notebookEditor.revealCellRangeInView({ start: 0, end: 1 });721}722}723});724725registerAction2(class extends Action2 {726constructor() {727super({728id: 'interactive.scrollToBottom',729title: localize('interactiveScrollToBottom', 'Scroll to Bottom'),730keybinding: {731when: ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'),732primary: KeyMod.CtrlCmd | KeyCode.End,733mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow },734weight: KeybindingWeight.WorkbenchContrib735},736category: interactiveWindowCategory,737});738}739740async run(accessor: ServicesAccessor): Promise<void> {741const editorService = accessor.get(IEditorService);742const editorControl = editorService.activeEditorPane?.getControl();743744if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {745if (editorControl.notebookEditor.getLength() === 0) {746return;747}748749const len = editorControl.notebookEditor.getLength();750editorControl.notebookEditor.revealCellRangeInView({ start: len - 1, end: len });751}752}753});754755registerAction2(class extends Action2 {756constructor() {757super({758id: 'interactive.input.focus',759title: localize2('interactive.input.focus', 'Focus Input Editor'),760category: interactiveWindowCategory,761menu: {762id: MenuId.CommandPalette,763when: InteractiveWindowOpen764},765});766}767768async run(accessor: ServicesAccessor): Promise<void> {769const editorService = accessor.get(IEditorService);770const editorControl = editorService.activeEditorPane?.getControl();771772if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {773editorService.activeEditorPane?.focus();774}775else {776// find and open the most recent interactive window777const openEditors = editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE);778const interactiveWindow = Iterable.find(openEditors, identifier => { return identifier.editor.typeId === InteractiveEditorInput.ID; });779if (interactiveWindow) {780const editorInput = interactiveWindow.editor as InteractiveEditorInput;781const currentGroup = interactiveWindow.groupId;782const editor = await editorService.openEditor(editorInput, currentGroup);783const editorControl = editor?.getControl();784785if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {786editorService.activeEditorPane?.focus();787}788}789}790}791});792793registerAction2(class extends Action2 {794constructor() {795super({796id: 'interactive.history.focus',797title: localize2('interactive.history.focus', 'Focus History'),798category: interactiveWindowCategory,799menu: {800id: MenuId.CommandPalette,801when: ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'),802},803keybinding: [{804// On mac, require that the cursor is at the top of the input, to avoid stealing cmd+up to move the cursor to the top805when: ContextKeyExpr.and(806INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('bottom'),807INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('none')),808weight: KeybindingWeight.WorkbenchContrib + 5,809primary: KeyMod.CtrlCmd | KeyCode.UpArrow810},811{812when: ContextKeyExpr.or(IsWindowsContext, IsLinuxContext),813weight: KeybindingWeight.WorkbenchContrib,814primary: KeyMod.CtrlCmd | KeyCode.UpArrow,815}],816precondition: ContextKeyExpr.and(IS_COMPOSITE_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED.negate())817});818}819820async run(accessor: ServicesAccessor): Promise<void> {821const editorService = accessor.get(IEditorService);822const editorControl = editorService.activeEditorPane?.getControl();823824if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {825editorControl.notebookEditor.focus();826}827}828});829830registerColor('interactive.activeCodeBorder', {831dark: ifDefinedThenElse(peekViewBorder, peekViewBorder, '#007acc'),832light: ifDefinedThenElse(peekViewBorder, peekViewBorder, '#007acc'),833hcDark: contrastBorder,834hcLight: contrastBorder835}, localize('interactive.activeCodeBorder', 'The border color for the current interactive code cell when the editor has focus.'));836837registerColor('interactive.inactiveCodeBorder', {838//dark: theme.getColor(listInactiveSelectionBackground) ?? transparent(listInactiveSelectionBackground, 1),839dark: ifDefinedThenElse(listInactiveSelectionBackground, listInactiveSelectionBackground, '#37373D'),840light: ifDefinedThenElse(listInactiveSelectionBackground, listInactiveSelectionBackground, '#E4E6F1'),841hcDark: PANEL_BORDER,842hcLight: PANEL_BORDER843}, localize('interactive.inactiveCodeBorder', 'The border color for the current interactive code cell when the editor does not have focus.'));844845Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({846id: 'interactiveWindow',847order: 100,848type: 'object',849'properties': {850[ReplEditorSettings.interactiveWindowAlwaysScrollOnNewCell]: {851type: 'boolean',852default: true,853markdownDescription: localize('interactiveWindow.alwaysScrollOnNewCell', "Automatically scroll the interactive window to show the output of the last statement executed. If this value is false, the window will only scroll if the last cell was already the one scrolled to.")854},855[NotebookSetting.InteractiveWindowPromptToSave]: {856type: 'boolean',857default: false,858markdownDescription: localize('interactiveWindow.promptToSaveOnClose', "Prompt to save the interactive window when it is closed. Only new interactive windows will be affected by this setting change.")859},860[ReplEditorSettings.executeWithShiftEnter]: {861type: 'boolean',862default: false,863markdownDescription: localize('interactiveWindow.executeWithShiftEnter', "Execute the Interactive Window (REPL) input box with shift+enter, so that enter can be used to create a newline."),864tags: ['replExecute']865},866[ReplEditorSettings.showExecutionHint]: {867type: 'boolean',868default: true,869markdownDescription: localize('interactiveWindow.showExecutionHint', "Display a hint in the Interactive Window (REPL) input box to indicate how to execute code."),870tags: ['replExecute']871}872}873});874875876