From 0e63448cdc2687782561a16c9c67ae4a47c1eac1 Mon Sep 17 00:00:00 2001 From: seem Date: Tue, 30 Sep 2025 18:52:53 +0200 Subject: [PATCH 1/7] move remaining files to contrib to fix import layering errors --- .../api/browser/mainThreadNotebookEditors.ts | 3 +-- .../inlineChat/browser/inlineChatNotebook.ts | 2 +- .../notebook/browser/notebook.contribution.ts | 2 +- .../browser/positronConsoleActions.ts | 2 +- .../browser/ContextKeysManager.ts | 0 .../browser/PositronNotebookInstance.ts | 4 ++-- .../SelectPositronNotebookKernelAction.ts | 2 +- .../notebookCells/CellEditorMonacoWidget.tsx | 2 +- .../actionBar/registerCellCommand.ts | 4 ++-- .../actionBar/registerNotebookCommand.ts | 4 ++-- .../actionBar/useActionsForCell.tsx | 2 +- .../browser/notebookCells/useCellContextKeys.ts | 2 +- .../browser/positronNotebook.contribution.ts | 2 +- .../browser/positronNotebookService.ts | 4 ++-- .../common/positronNotebookCommon.ts | 13 +++++++++++++ ...positronNotebookConfigurationHandling.test.ts | 3 +-- .../positronNotebookEditorResolution.test.ts | 3 +-- .../positronNotebook/test/browser/testUtils.ts | 2 +- .../common/positronNotebookUtils.ts | 16 ---------------- 19 files changed, 33 insertions(+), 39 deletions(-) rename src/vs/workbench/{services => contrib}/positronNotebook/browser/ContextKeysManager.ts (100%) rename src/vs/workbench/{services => contrib}/positronNotebook/browser/positronNotebookService.ts (96%) delete mode 100644 src/vs/workbench/services/positronNotebook/common/positronNotebookUtils.ts diff --git a/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts b/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts index 10990998dcb6..0b655329d5d1 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ // --- Start Positron --- -import { POSITRON_NOTEBOOK_EDITOR_ID } from '../../contrib/positronNotebook/common/positronNotebookCommon.js'; +import { POSITRON_NOTEBOOK_EDITOR_ID, usingPositronNotebooks } from '../../contrib/positronNotebook/common/positronNotebookCommon.js'; import { checkPositronNotebookEnabled } from '../../contrib/positronNotebook/browser/positronNotebookExperimentalConfig.js'; -import { usingPositronNotebooks } from '../../services/positronNotebook/common/positronNotebookUtils.js'; // --- End Positron --- import { DisposableStore, dispose } from '../../../base/common/lifecycle.js'; import { equals } from '../../../base/common/objects.js'; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatNotebook.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatNotebook.ts index 5cd807d9c649..8cb90be5b379 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatNotebook.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatNotebook.ts @@ -17,7 +17,7 @@ import { NotebookTextDiffEditor } from '../../notebook/browser/diff/notebookDiff import { NotebookMultiTextDiffEditor } from '../../notebook/browser/diff/notebookMultiDiffEditor.js'; // --- Start Positron --- // Imports to support inline chat in Positron notebooks. -import { IPositronNotebookService } from '../../../services/positronNotebook/browser/positronNotebookService.js'; +import { IPositronNotebookService } from '../../positronNotebook/browser/positronNotebookService.js'; // --- End Positron --- export class InlineChatNotebookContribution { diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 054cea057427..36950ea8838e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -142,7 +142,7 @@ import { getFormattedNotebookMetadataJSON } from '../common/model/notebookMetada import { NotebookOutputEditor } from './outputEditor/notebookOutputEditor.js'; import { NotebookOutputEditorInput } from './outputEditor/notebookOutputEditorInput.js'; // --- Start Positron --- -import { IPositronNotebookService } from '../../../services/positronNotebook/browser/positronNotebookService.js'; +import { IPositronNotebookService } from '../../positronNotebook/browser/positronNotebookService.js'; // --- End Positron --- /*--------------------------------------------------------------------------------------------- */ diff --git a/src/vs/workbench/contrib/positronConsole/browser/positronConsoleActions.ts b/src/vs/workbench/contrib/positronConsole/browser/positronConsoleActions.ts index 5a5d9262b88d..6057d7fd9cd6 100644 --- a/src/vs/workbench/contrib/positronConsole/browser/positronConsoleActions.ts +++ b/src/vs/workbench/contrib/positronConsole/browser/positronConsoleActions.ts @@ -32,7 +32,7 @@ import { IPositronConsoleService, POSITRON_CONSOLE_VIEW_ID } from '../../../serv import { IExecutionHistoryService } from '../../../services/positronHistory/common/executionHistoryService.js'; import { CodeAttributionSource, IConsoleCodeAttribution } from '../../../services/positronConsole/common/positronConsoleCodeExecution.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; -import { POSITRON_NOTEBOOK_CELL_EDITOR_FOCUSED } from '../../../services/positronNotebook/browser/ContextKeysManager.js'; +import { POSITRON_NOTEBOOK_CELL_EDITOR_FOCUSED } from '../../positronNotebook/browser/ContextKeysManager.js'; import { getContextFromActiveEditor } from '../../notebook/browser/controller/coreActions.js'; /** diff --git a/src/vs/workbench/services/positronNotebook/browser/ContextKeysManager.ts b/src/vs/workbench/contrib/positronNotebook/browser/ContextKeysManager.ts similarity index 100% rename from src/vs/workbench/services/positronNotebook/browser/ContextKeysManager.ts rename to src/vs/workbench/contrib/positronNotebook/browser/ContextKeysManager.ts diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts index 7058d5203465..cd84233a28ba 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts @@ -23,8 +23,8 @@ import { BaseCellEditorOptions } from './BaseCellEditorOptions.js'; import * as DOM from '../../../../base/browser/dom.js'; import { IPositronNotebookCell } from './PositronNotebookCells/IPositronNotebookCell.js'; import { CellSelectionType, getSelectedCell, getSelectedCells, SelectionStateMachine } from '../../../contrib/positronNotebook/browser/selectionMachine.js'; -import { PositronNotebookContextKeyManager } from '../../../services/positronNotebook/browser/ContextKeysManager.js'; -import { IPositronNotebookService } from '../../../services/positronNotebook/browser/positronNotebookService.js'; +import { PositronNotebookContextKeyManager } from './ContextKeysManager.js'; +import { IPositronNotebookService } from './positronNotebookService.js'; import { IPositronNotebookInstance, KernelStatus } from './IPositronNotebookInstance.js'; import { NotebookCellTextModel } from '../../notebook/common/model/notebookCellTextModel.js'; import { disposableTimeout } from '../../../../base/common/async.js'; diff --git a/src/vs/workbench/contrib/positronNotebook/browser/SelectPositronNotebookKernelAction.ts b/src/vs/workbench/contrib/positronNotebook/browser/SelectPositronNotebookKernelAction.ts index 164fcc7c49a6..d14ee3b35cfa 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/SelectPositronNotebookKernelAction.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/SelectPositronNotebookKernelAction.ts @@ -11,7 +11,7 @@ import { IQuickInputService, IQuickPickItem } from '../../../../platform/quickin import { selectKernelIcon } from '../../notebook/browser/notebookIcons.js'; import { INotebookKernelService, INotebookKernel } from '../../notebook/common/notebookKernelService.js'; import { PositronNotebookInstance } from './PositronNotebookInstance.js'; -import { IPositronNotebookService } from '../../../services/positronNotebook/browser/positronNotebookService.js'; +import { IPositronNotebookService } from './positronNotebookService.js'; import { POSITRON_RUNTIME_NOTEBOOK_KERNELS_EXTENSION_ID } from '../../runtimeNotebookKernel/common/runtimeNotebookKernelConfig.js'; export const SELECT_KERNEL_ID_POSITRON = 'positronNotebook.selectKernel'; diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx index b836d04051f3..05ca45243268 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx @@ -22,7 +22,7 @@ import { DisposableStore } from '../../../../../base/common/lifecycle.js'; import { PositronNotebookCellGeneral } from '../PositronNotebookCells/PositronNotebookCell.js'; import { usePositronReactServicesContext } from '../../../../../base/browser/positronReactRendererContext.js'; import { autorun } from '../../../../../base/common/observable.js'; -import { POSITRON_NOTEBOOK_CELL_EDITOR_FOCUSED } from '../../../../services/positronNotebook/browser/ContextKeysManager.js'; +import { POSITRON_NOTEBOOK_CELL_EDITOR_FOCUSED } from '../ContextKeysManager.js'; /** * diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/actionBar/registerCellCommand.ts b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/actionBar/registerCellCommand.ts index 425757a409b9..f040902e428c 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/actionBar/registerCellCommand.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/actionBar/registerCellCommand.ts @@ -5,12 +5,12 @@ import { CommandsRegistry, ICommandMetadata } from '../../../../../../platform/commands/common/commands.js'; import { ServicesAccessor } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { IPositronNotebookService } from '../../../../../services/positronNotebook/browser/positronNotebookService.js'; +import { IPositronNotebookService } from '../../positronNotebookService.js'; import { IPositronNotebookCell } from '../../PositronNotebookCells/IPositronNotebookCell.js'; import { NotebookCellActionBarRegistry, INotebookCellActionBarItem } from './actionBarRegistry.js'; import { IDisposable, DisposableStore } from '../../../../../../base/common/lifecycle.js'; import { KeybindingsRegistry, KeybindingWeight } from '../../../../../../platform/keybinding/common/keybindingsRegistry.js'; -import { POSITRON_NOTEBOOK_CELL_EDITOR_FOCUSED, POSITRON_NOTEBOOK_EDITOR_CONTAINER_FOCUSED } from '../../../../../services/positronNotebook/browser/ContextKeysManager.js'; +import { POSITRON_NOTEBOOK_CELL_EDITOR_FOCUSED, POSITRON_NOTEBOOK_EDITOR_CONTAINER_FOCUSED } from '../../ContextKeysManager.js'; import { IPositronNotebookCommandKeybinding } from './commandUtils.js'; import { IPositronNotebookInstance } from '../../IPositronNotebookInstance.js'; import { getSelectedCell, getSelectedCells, getEditingCell } from '../../selectionMachine.js'; diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/actionBar/registerNotebookCommand.ts b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/actionBar/registerNotebookCommand.ts index 2f6d1ef6eeac..785d0cdc393f 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/actionBar/registerNotebookCommand.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/actionBar/registerNotebookCommand.ts @@ -7,9 +7,9 @@ import { IDisposable, DisposableStore } from '../../../../../../base/common/life import { ICommandMetadata, CommandsRegistry } from '../../../../../../platform/commands/common/commands.js'; import { ServicesAccessor } from '../../../../../../platform/instantiation/common/instantiation.js'; import { KeybindingsRegistry, KeybindingWeight } from '../../../../../../platform/keybinding/common/keybindingsRegistry.js'; -import { POSITRON_NOTEBOOK_EDITOR_CONTAINER_FOCUSED } from '../../../../../services/positronNotebook/browser/ContextKeysManager.js'; +import { POSITRON_NOTEBOOK_EDITOR_CONTAINER_FOCUSED } from '../../ContextKeysManager.js'; import { IPositronNotebookInstance } from '../../IPositronNotebookInstance.js'; -import { IPositronNotebookService } from '../../../../../services/positronNotebook/browser/positronNotebookService.js'; +import { IPositronNotebookService } from '../../positronNotebookService.js'; import { IPositronNotebookCommandKeybinding } from './commandUtils.js'; /** diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/actionBar/useActionsForCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/actionBar/useActionsForCell.tsx index a141c6a7d021..2f01cabfabf5 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/actionBar/useActionsForCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/actionBar/useActionsForCell.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { useObservedValue } from '../../useObservedValue.js'; import { CellActionPosition, INotebookCellActionBarItem, NotebookCellActionBarRegistry } from './actionBarRegistry.js'; import { useCellScopedContextKeyService } from '../CellContextKeyServiceProvider.js'; -import { POSITRON_NOTEBOOK_CELL_CONTEXT_KEYS } from '../../../../../services/positronNotebook/browser/ContextKeysManager.js'; +import { POSITRON_NOTEBOOK_CELL_CONTEXT_KEYS } from '../../ContextKeysManager.js'; // Create a set of all the context key names const notebookCellContextKeysSet = new Set(Object.values(POSITRON_NOTEBOOK_CELL_CONTEXT_KEYS).map(key => key.key)); diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/useCellContextKeys.ts b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/useCellContextKeys.ts index 0f40a0c5fcfc..8795fc6728a3 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/useCellContextKeys.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/useCellContextKeys.ts @@ -11,7 +11,7 @@ import { autorun } from '../../../../../base/common/observable.js'; import { DisposableStore } from '../../../../../base/common/lifecycle.js'; import { CellKind, CellSelectionStatus, IPositronNotebookCell } from '../PositronNotebookCells/IPositronNotebookCell.js'; import { IPositronNotebookInstance } from '../IPositronNotebookInstance.js'; -import { bindCellContextKeys, resetCellContextKeys } from '../../../../services/positronNotebook/browser/ContextKeysManager.js'; +import { bindCellContextKeys, resetCellContextKeys } from '../ContextKeysManager.js'; import { useEnvironment } from '../EnvironmentProvider.js'; import { IScopedContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; diff --git a/src/vs/workbench/contrib/positronNotebook/browser/positronNotebook.contribution.ts b/src/vs/workbench/contrib/positronNotebook/browser/positronNotebook.contribution.ts index 8456b902b550..bf3e330f0806 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/positronNotebook.contribution.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/positronNotebook.contribution.ts @@ -37,7 +37,7 @@ import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextke import { INotebookEditorOptions } from '../../notebook/browser/notebookBrowser.js'; import { POSITRON_NOTEBOOK_EDITOR_ID, POSITRON_NOTEBOOK_EDITOR_INPUT_ID } from '../common/positronNotebookCommon.js'; import { SelectionState } from './selectionMachine.js'; -import { POSITRON_NOTEBOOK_CELL_CONTEXT_KEYS as CELL_CONTEXT_KEYS } from '../../../services/positronNotebook/browser/ContextKeysManager.js'; +import { POSITRON_NOTEBOOK_CELL_CONTEXT_KEYS as CELL_CONTEXT_KEYS } from './ContextKeysManager.js'; import { registerAction2 } from '../../../../platform/actions/common/actions.js'; import { ExecuteSelectionInConsoleAction } from './ExecuteSelectionInConsoleAction.js'; diff --git a/src/vs/workbench/services/positronNotebook/browser/positronNotebookService.ts b/src/vs/workbench/contrib/positronNotebook/browser/positronNotebookService.ts similarity index 96% rename from src/vs/workbench/services/positronNotebook/browser/positronNotebookService.ts rename to src/vs/workbench/contrib/positronNotebook/browser/positronNotebookService.ts index 0f03262e474b..8dc4150beb15 100644 --- a/src/vs/workbench/services/positronNotebook/browser/positronNotebookService.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/positronNotebookService.ts @@ -8,8 +8,8 @@ import { URI } from '../../../../base/common/uri.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; -import { IPositronNotebookInstance } from '../../../contrib/positronNotebook/browser/IPositronNotebookInstance.js'; -import { usingPositronNotebooks as utilUsingPositronNotebooks } from '../common/positronNotebookUtils.js'; +import { IPositronNotebookInstance } from './IPositronNotebookInstance.js'; +import { usingPositronNotebooks as utilUsingPositronNotebooks } from '../common/positronNotebookCommon.js'; import { isEqual } from '../../../../base/common/resources.js'; export const IPositronNotebookService = createDecorator('positronNotebookService'); diff --git a/src/vs/workbench/contrib/positronNotebook/common/positronNotebookCommon.ts b/src/vs/workbench/contrib/positronNotebook/common/positronNotebookCommon.ts index 4ab5fbf5da7f..ad8c6c42f959 100644 --- a/src/vs/workbench/contrib/positronNotebook/common/positronNotebookCommon.ts +++ b/src/vs/workbench/contrib/positronNotebook/common/positronNotebookCommon.ts @@ -3,5 +3,18 @@ * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. *--------------------------------------------------------------------------------------------*/ +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; + export const POSITRON_NOTEBOOK_EDITOR_ID = 'workbench.editor.positronNotebook'; export const POSITRON_NOTEBOOK_EDITOR_INPUT_ID = 'workbench.input.positronNotebook'; + +/** + * Check if Positron notebooks are configured as the default editor for .ipynb files + * @param configurationService Configuration service + * @returns true if Positron notebooks are the default editor, false otherwise + */ + +export function usingPositronNotebooks(configurationService: IConfigurationService): boolean { + const editorAssociations = configurationService.getValue>('workbench.editorAssociations') || {}; + return editorAssociations['*.ipynb'] === 'workbench.editor.positronNotebook'; +} diff --git a/src/vs/workbench/contrib/positronNotebook/test/browser/positronNotebookConfigurationHandling.test.ts b/src/vs/workbench/contrib/positronNotebook/test/browser/positronNotebookConfigurationHandling.test.ts index ea53d2fe2674..98823b127280 100644 --- a/src/vs/workbench/contrib/positronNotebook/test/browser/positronNotebookConfigurationHandling.test.ts +++ b/src/vs/workbench/contrib/positronNotebook/test/browser/positronNotebookConfigurationHandling.test.ts @@ -12,9 +12,8 @@ import { EditorResolverService } from '../../../../services/editor/browser/edito import { IEditorResolverService, RegisteredEditorPriority } from '../../../../services/editor/common/editorResolverService.js'; import { ITestInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; import { PositronNotebookEditorInput } from '../../browser/PositronNotebookEditorInput.js'; -import { usingPositronNotebooks } from '../../../../services/positronNotebook/common/positronNotebookUtils.js'; +import { POSITRON_NOTEBOOK_EDITOR_ID, usingPositronNotebooks } from '../../common/positronNotebookCommon.js'; import { createPositronNotebookTestServices } from './testUtils.js'; -import { POSITRON_NOTEBOOK_EDITOR_ID } from '../../common/positronNotebookCommon.js'; // Mock implementation for testing static editor registration class MockPositronNotebookContribution extends DisposableStore { diff --git a/src/vs/workbench/contrib/positronNotebook/test/browser/positronNotebookEditorResolution.test.ts b/src/vs/workbench/contrib/positronNotebook/test/browser/positronNotebookEditorResolution.test.ts index 74bb2c7c4cad..727ec0f15556 100644 --- a/src/vs/workbench/contrib/positronNotebook/test/browser/positronNotebookEditorResolution.test.ts +++ b/src/vs/workbench/contrib/positronNotebook/test/browser/positronNotebookEditorResolution.test.ts @@ -40,9 +40,8 @@ import { RegisteredEditorPriority, ResolvedStatus } from '../../../../services/e import { ITestInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; import { EditorPart } from '../../../../browser/parts/editor/editorPart.js'; import { PositronNotebookEditorInput } from '../../browser/PositronNotebookEditorInput.js'; -import { usingPositronNotebooks } from '../../../../services/positronNotebook/common/positronNotebookUtils.js'; +import { POSITRON_NOTEBOOK_EDITOR_ID, usingPositronNotebooks } from '../../common/positronNotebookCommon.js'; import { createPositronNotebookTestServices } from './testUtils.js'; -import { POSITRON_NOTEBOOK_EDITOR_ID } from '../../common/positronNotebookCommon.js'; suite.skip('Positron Notebook Editor Resolution', () => { // Suite skipped: Positron notebook editor is behind feature flag (positron.notebook.enabled=false by default) diff --git a/src/vs/workbench/contrib/positronNotebook/test/browser/testUtils.ts b/src/vs/workbench/contrib/positronNotebook/test/browser/testUtils.ts index 7c7af29de368..7749ec17800a 100644 --- a/src/vs/workbench/contrib/positronNotebook/test/browser/testUtils.ts +++ b/src/vs/workbench/contrib/positronNotebook/test/browser/testUtils.ts @@ -19,7 +19,7 @@ import { INotebookExecutionService } from '../../../notebook/common/notebookExec import { INotebookExecutionStateService } from '../../../notebook/common/notebookExecutionStateService.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { IRuntimeSessionService } from '../../../../services/runtimeSession/common/runtimeSessionService.js'; -import { IPositronNotebookService } from '../../../../services/positronNotebook/browser/positronNotebookService.js'; +import { IPositronNotebookService } from '../../browser/positronNotebookService.js'; import { IPositronWebviewPreloadService } from '../../../../services/positronWebviewPreloads/browser/positronWebviewPreloadService.js'; import { Event } from '../../../../../base/common/event.js'; diff --git a/src/vs/workbench/services/positronNotebook/common/positronNotebookUtils.ts b/src/vs/workbench/services/positronNotebook/common/positronNotebookUtils.ts deleted file mode 100644 index 3e6fffdaa4eb..000000000000 --- a/src/vs/workbench/services/positronNotebook/common/positronNotebookUtils.ts +++ /dev/null @@ -1,16 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (C) 2025 Posit Software, PBC. All rights reserved. - * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; - -/** - * Check if Positron notebooks are configured as the default editor for .ipynb files - * @param configurationService Configuration service - * @returns true if Positron notebooks are the default editor, false otherwise - */ -export function usingPositronNotebooks(configurationService: IConfigurationService): boolean { - const editorAssociations = configurationService.getValue>('workbench.editorAssociations') || {}; - return editorAssociations['*.ipynb'] === 'workbench.editor.positronNotebook'; -} From a93622ff8009c5828a4d09fc4d940354f8e40ab0 Mon Sep 17 00:00:00 2001 From: seem Date: Fri, 26 Sep 2025 15:55:42 +0200 Subject: [PATCH 2/7] lift notebook instance --- .../browser/PositronNotebookEditor.tsx | 30 +++++++++++-------- .../browser/PositronNotebookEditorInput.ts | 15 ---------- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditor.tsx b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditor.tsx index 629b1c8c0e7e..0fb79a46bbd4 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditor.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditor.tsx @@ -43,6 +43,7 @@ import { URI } from '../../../../base/common/uri.js'; import { isEqual } from '../../../../base/common/resources.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { PositronNotebookInstance } from './PositronNotebookInstance.js'; /** @@ -75,6 +76,8 @@ export class PositronNotebookEditor extends AbstractEditorWithViewState('isVisible', false); - - // Getter for notebook instance to avoid having to cast the input every time. - get notebookInstance() { - return this._input?.notebookInstance; - } - protected override setEditorVisible(visible: boolean): void { this._isVisible.set(visible, undefined); super.setEditorVisible(visible); @@ -239,12 +238,14 @@ export class PositronNotebookEditor extends AbstractEditorWithViewState Date: Fri, 26 Sep 2025 15:56:09 +0200 Subject: [PATCH 3/7] remove unused code path --- .../browser/ContextKeysManager.ts | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/ContextKeysManager.ts b/src/vs/workbench/contrib/positronNotebook/browser/ContextKeysManager.ts index 8dd0180c70df..ec45ae7d1d70 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/ContextKeysManager.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/ContextKeysManager.ts @@ -5,7 +5,7 @@ import * as DOM from '../../../../base/browser/dom.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; -import { IContextKey, IContextKeyService, IScopedContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextKey, IScopedContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; /** * Context key that is set when the Positron notebook editor container is focused. This will _not_ be true when the user is editing a cell. @@ -97,7 +97,6 @@ export function resetCellContextKeys(keys: IPositronNotebookCellContextKeys | un */ export class PositronNotebookContextKeyManager extends Disposable { //#region Private Properties - private _container?: HTMLElement; private _scopedContextKeyService?: IScopedContextKeyService; //#endregion Private Properties @@ -105,20 +104,10 @@ export class PositronNotebookContextKeyManager extends Disposable { positronEditorFocus?: IContextKey; //#endregion Public Properties - //#region Constructor & Dispose - constructor( - @IContextKeyService private readonly _contextKeyService: IContextKeyService, - ) { - super(); - } - - //#endregion Constructor & Dispose - //#region Public Methods - setContainer(container: HTMLElement, scopedContextKeyService?: IScopedContextKeyService) { - this._container = container; + setContainer(container: HTMLElement, scopedContextKeyService: IScopedContextKeyService) { this.positronEditorFocus?.reset(); - this._scopedContextKeyService = scopedContextKeyService ?? this._contextKeyService.createScoped(this._container); + this._scopedContextKeyService = scopedContextKeyService; this.positronEditorFocus = POSITRON_NOTEBOOK_EDITOR_CONTAINER_FOCUSED.bindTo(this._scopedContextKeyService); From a1ab0d09f04af2ad4b165a61bd03be297322108a Mon Sep 17 00:00:00 2001 From: seem Date: Tue, 30 Sep 2025 18:47:40 +0200 Subject: [PATCH 4/7] remove patches that are not needed if we impl `INotebookEditor` --- .../api/browser/mainThreadNotebookEditors.ts | 9 ----- .../workbench/api/common/extHostNotebook.ts | 8 ----- .../inlineChat/browser/inlineChatNotebook.ts | 35 ------------------- 3 files changed, 52 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts b/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts index 0b655329d5d1..036336c6a2b7 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts @@ -122,15 +122,6 @@ export class MainThreadNotebookEditors implements MainThreadNotebookEditorsShape // --- End Positron --- const editorPane = await this._editorService.openEditor({ resource: URI.revive(resource), options: editorOptions }, columnToEditorGroup(this._editorGroupService, this._configurationService, options.position)); - // --- Start Positron --- - if (editorPane?.getId() === POSITRON_NOTEBOOK_EDITOR_ID) { - // Positron notebook is already open, just return a synthetic ID - // We can't return the actual notebook editor ID because Positron notebooks - // don't implement INotebookEditor interface yet (https://github.com/posit-dev/positron/issues/9440) - const uri = URI.revive(resource); - return `positron-notebook-${uri.toString()}`; - } - // --- End Positron --- const notebookEditor = getNotebookEditorFromEditorPane(editorPane); if (notebookEditor) { diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 0256adcf2660..1dea8c800d7f 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -231,14 +231,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { if (editor) { return editor; } - // --- Start Positron --- - // Positron notebooks don't implement INotebookEditor yet, so mock the result. - // This will lead to unexpected errors in extensions that use this API - // until we implement INotebookEditor (https://github.com/posit-dev/positron/issues/9440). - if (editorId.startsWith('positron-notebook-')) { - return {} as any; - } - // --- End Positron --- if (editorId) { throw new Error(`Could NOT open editor for "${notebook.uri.toString()}" because another editor opened in the meantime.`); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatNotebook.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatNotebook.ts index 8cb90be5b379..95c38b1b78ef 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatNotebook.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatNotebook.ts @@ -15,10 +15,6 @@ import { CellUri } from '../../notebook/common/notebookCommon.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { NotebookTextDiffEditor } from '../../notebook/browser/diff/notebookDiffEditor.js'; import { NotebookMultiTextDiffEditor } from '../../notebook/browser/diff/notebookMultiDiffEditor.js'; -// --- Start Positron --- -// Imports to support inline chat in Positron notebooks. -import { IPositronNotebookService } from '../../positronNotebook/browser/positronNotebookService.js'; -// --- End Positron --- export class InlineChatNotebookContribution { @@ -28,10 +24,6 @@ export class InlineChatNotebookContribution { @IInlineChatSessionService sessionService: IInlineChatSessionService, @IEditorService editorService: IEditorService, @INotebookEditorService notebookEditorService: INotebookEditorService, - // --- Start Positron --- - // Imports to support inline chat in Positron notebooks. - @IPositronNotebookService positronNotebookService: IPositronNotebookService, - // --- End Positron --- ) { this._store.add(sessionService.registerSessionKeyComputer(Schemas.vscodeNotebookCell, { @@ -65,19 +57,6 @@ export class InlineChatNotebookContribution { // } } } - // --- Start Positron --- - // To support inline chat in Positron notebooks: - // construct a session comparison key from the corresponding notebook - for (const positronInstance of positronNotebookService.listInstances(data.notebook)) { - const candidate = `${positronInstance.id}#${uri}`; - if (!fallback) { - fallback = candidate; - } - if (positronInstance.hasCodeEditor(editor)) { - return candidate; - } - } - // --- End Positron --- if (fallback) { return fallback; @@ -117,20 +96,6 @@ export class InlineChatNotebookContribution { } } } - // --- Start Positron --- - // To support inline chat in Positron notebooks: - // cancel existing chat sessions when a new one is started. - for (const positronInstance of positronNotebookService.listInstances(candidate.notebook)) { - if (positronInstance.hasCodeEditor(newSessionEditor)) { - for (const { editor } of positronInstance.cells.get()) { - if (editor && editor !== newSessionEditor) { - InlineChatController.get(editor)?.acceptSession(); - } - } - break; - } - } - // --- End Positron --- })); } From ad87c2e0b2533a6eeddc54f6368ecba396f6491c Mon Sep 17 00:00:00 2001 From: seem Date: Tue, 30 Sep 2025 19:08:17 +0200 Subject: [PATCH 5/7] wip: implement `INotebookEditor` --- .../browser/IPositronNotebookInstance.ts | 29 + .../browser/PositronNotebookEditor.tsx | 4 +- .../browser/PositronNotebookEditorControl.ts | 52 -- .../adapters/PositronCellOutputViewModel.ts | 41 ++ .../adapters/PositronNotebookCellViewModel.ts | 354 ++++++++++++ .../adapters/PositronNotebookEditorControl.ts | 523 ++++++++++++++++++ .../adapters/PositronNotebookViewModel.ts | 115 ++++ 7 files changed, 1064 insertions(+), 54 deletions(-) delete mode 100644 src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditorControl.ts create mode 100644 src/vs/workbench/contrib/positronNotebook/browser/adapters/PositronCellOutputViewModel.ts create mode 100644 src/vs/workbench/contrib/positronNotebook/browser/adapters/PositronNotebookCellViewModel.ts create mode 100644 src/vs/workbench/contrib/positronNotebook/browser/adapters/PositronNotebookEditorControl.ts create mode 100644 src/vs/workbench/contrib/positronNotebook/browser/adapters/PositronNotebookViewModel.ts diff --git a/src/vs/workbench/contrib/positronNotebook/browser/IPositronNotebookInstance.ts b/src/vs/workbench/contrib/positronNotebook/browser/IPositronNotebookInstance.ts index 1893f106d6c2..52709eaadd49 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/IPositronNotebookInstance.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/IPositronNotebookInstance.ts @@ -10,6 +10,10 @@ import { SelectionStateMachine } from './selectionMachine.js'; import { ILanguageRuntimeSession } from '../../../services/runtimeSession/common/runtimeSessionService.js'; import { Event } from '../../../../base/common/event.js'; import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; +import { NotebookOptions } from '../../notebook/browser/notebookOptions.js'; +import { NotebookTextModel } from '../../notebook/common/model/notebookTextModel.js'; +import { IBaseCellEditorOptions, INotebookEditorOptions, INotebookEditorViewState } from '../../notebook/browser/notebookBrowser.js'; +import { INotebookKernel } from '../../notebook/common/notebookKernelService.js'; /** * Represents the possible states of a notebook's kernel connection */ @@ -99,17 +103,42 @@ export interface IPositronNotebookInstance { */ readonly isDisposed: boolean; + /** + * The current text model for the notebook, representing its content and structure. + */ + readonly textModel: NotebookTextModel | undefined; + + /** + * Event that fires when the notebook's text model changes. + */ + readonly onDidChangeModel: Event; + /** * Indicates whether this notebook is read-only and cannot be edited. */ readonly isReadOnly: boolean; + /** + * Options for how the notebook should be displayed. Currently not really used but will be as + * notebook gets fleshed out. + */ + readonly notebookOptions: NotebookOptions; + + /** + * The current selected kernel for the notebook, if any. + */ + readonly kernel: IObservable; + /** * Event that fires when the cells container is scrolled */ readonly onDidScrollCellsContainer: Event; // ===== Methods ===== + getEditorViewState(): INotebookEditorViewState; + getBaseCellEditorOptions(language: string): IBaseCellEditorOptions; + setOptions(options: INotebookEditorOptions | undefined): Promise; + /** * Executes the specified cells in order. * diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditor.tsx b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditor.tsx index 0fb79a46bbd4..70987fcfd66c 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditor.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditor.tsx @@ -35,7 +35,7 @@ import { PositronNotebookEditorInput } from './PositronNotebookEditorInput.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { NotebookVisibilityProvider } from './NotebookVisibilityContext.js'; import { observableValue } from '../../../../base/common/observable.js'; -import { PositronNotebookEditorControl } from './PositronNotebookEditorControl.js'; +import { PositronNotebookEditorControl } from './adapters/PositronNotebookEditorControl.js'; import { POSITRON_NOTEBOOK_EDITOR_ID } from '../common/positronNotebookCommon.js'; import { AbstractEditorWithViewState } from '../../../browser/parts/editor/editorWithViewState.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; @@ -245,7 +245,7 @@ export class PositronNotebookEditor extends AbstractEditorWithViewState { - const selectionStateMachine = this._notebookInstance.selectionStateMachine; - selectionStateMachine.state.read(reader); - // We currently assume that the first selected cell is the "active" one, - // but we should probably explicitly track the active cell in the selection state. - this._activeCodeEditor = getSelectedCells(selectionStateMachine.state.get())[0]?.editor; - })); - } - - /** - * Gets the active cell's code editor. - */ - public get activeCodeEditor(): IEditor | undefined { - return this._activeCodeEditor; - } -} diff --git a/src/vs/workbench/contrib/positronNotebook/browser/adapters/PositronCellOutputViewModel.ts b/src/vs/workbench/contrib/positronNotebook/browser/adapters/PositronCellOutputViewModel.ts new file mode 100644 index 000000000000..fc622807bee5 --- /dev/null +++ b/src/vs/workbench/contrib/positronNotebook/browser/adapters/PositronCellOutputViewModel.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2025 Posit Software, PBC. All rights reserved. + * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. + *--------------------------------------------------------------------------------------------*/ +import { Emitter } from '../../../../../base/common/event.js'; +import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { observableValue } from '../../../../../base/common/observable.js'; +import { ICellOutputViewModel, ICellViewModel } from '../../../notebook/browser/notebookBrowser.js'; +import { NotebookTextModel } from '../../../notebook/common/model/notebookTextModel.js'; +import { ICellOutput, IOrderedMimeType } from '../../../notebook/common/notebookCommon.js'; + +export class PositronCellOutputViewModel extends Disposable implements ICellOutputViewModel { + private readonly _onDidResetRenderer = this._register(new Emitter()); + public readonly onDidResetRenderer = this._onDidResetRenderer.event; + + visible = observableValue('outputVisible', false); + + constructor( + public readonly cellViewModel: ICellViewModel, + public readonly model: ICellOutput + ) { + super(); + } + + resolveMimeTypes(textModel: NotebookTextModel, kernelProvides: readonly string[] | undefined): [readonly IOrderedMimeType[], number] { + throw new Error('Method not implemented.'); + } + pickedMimeType: IOrderedMimeType | undefined; + hasMultiMimeType(): boolean { + throw new Error('Method not implemented.'); + } + setVisible(visible: boolean, force?: boolean): void { + throw new Error('Method not implemented.'); + } + resetRenderer(): void { + throw new Error('Method not implemented.'); + } + toRawJSON() { + throw new Error('Method not implemented.'); + } +} diff --git a/src/vs/workbench/contrib/positronNotebook/browser/adapters/PositronNotebookCellViewModel.ts b/src/vs/workbench/contrib/positronNotebook/browser/adapters/PositronNotebookCellViewModel.ts new file mode 100644 index 000000000000..ed9ca715d8e4 --- /dev/null +++ b/src/vs/workbench/contrib/positronNotebook/browser/adapters/PositronNotebookCellViewModel.ts @@ -0,0 +1,354 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2025 Posit Software, PBC. All rights reserved. + * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. + *--------------------------------------------------------------------------------------------*/ +import { Emitter } from '../../../../../base/common/event.js'; +import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { Mimes } from '../../../../../base/common/mime.js'; +import { ISettableObservable, observableValue } from '../../../../../base/common/observable.js'; +import { generateUuid } from '../../../../../base/common/uuid.js'; +import { IEditorCommentsOptions } from '../../../../../editor/common/config/editorOptions.js'; +import { IPosition } from '../../../../../editor/common/core/position.js'; +import { Range } from '../../../../../editor/common/core/range.js'; +import { Selection } from '../../../../../editor/common/core/selection.js'; +import { ITextModel, IModelDeltaDecoration } from '../../../../../editor/common/model.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { ICellViewModel, ICommonCellViewModelLayoutChangeInfo, INotebookCellDecorationOptions, CellLayoutState, IEditableCellViewModel, CellFocusMode, CodeCellLayoutInfo, ICellOutputViewModel, CellEditState } from '../../../notebook/browser/notebookBrowser.js'; +import { CellViewModelStateChangeEvent, NotebookLayoutInfo } from '../../../notebook/browser/notebookViewEvents.js'; +import { NotebookCellTextModel } from '../../../notebook/common/model/notebookCellTextModel.js'; +import { INotebookCellStatusBarItem } from '../../../notebook/common/notebookCommon.js'; +import { IPositronNotebookInstance } from '../IPositronNotebookInstance.js'; +import { IPositronNotebookCell } from '../PositronNotebookCells/IPositronNotebookCell.js'; +import { PositronCellOutputViewModel } from './PositronCellOutputViewModel.js'; + +export class PositronNotebookCellViewModel extends Disposable implements ICellViewModel { + //#region Events + private readonly _onDidChangeLayout = this._register(new Emitter()); + private readonly _onDidChangeCellStatusBarItems = this._register(new Emitter()); + private readonly _onCellDecorationsChanged = this._register(new Emitter<{ added: INotebookCellDecorationOptions[]; removed: INotebookCellDecorationOptions[] }>()); + private readonly _onDidChangeState = this._register(new Emitter()); + private readonly _onDidChangeEditorAttachState = this._register(new Emitter()); + + public readonly onDidChangeLayout = this._onDidChangeLayout.event; + public readonly onDidChangeCellStatusBarItems = this._onDidChangeCellStatusBarItems.event; + public readonly onCellDecorationsChanged = this._onCellDecorationsChanged.event; + public readonly onDidChangeState = this._onDidChangeState.event; + public readonly onDidChangeEditorAttachState = this._onDidChangeEditorAttachState.event; + //#endregion + public readonly id = generateUuid(); + + /** + * Should be set by INotebookEditor.focusNotebookCell + */ + public focusedOutputId?: string | undefined; + + constructor( + private readonly viewType: string, + private readonly _cell: IPositronNotebookCell, + private readonly _notebookInstance: IPositronNotebookInstance, + private readonly _notebookLayoutInfo: ISettableObservable, + @IConfigurationService private readonly _configurationService: IConfigurationService + ) { + super(); + + this._outputViewModels = this.model.outputs.map(output => new PositronCellOutputViewModel(this, output)); + + const initialNotebookLayoutInfo = this._notebookLayoutInfo.get(); + this._layoutInfo = observableValue('cellLayoutInfo', { + fontInfo: initialNotebookLayoutInfo?.fontInfo || null, + editorHeight: 0, + editorWidth: initialNotebookLayoutInfo + ? this._notebookInstance.notebookOptions.computeCodeCellEditorWidth(initialNotebookLayoutInfo.width) + : 0, + chatHeight: 0, + statusBarHeight: 0, + commentOffset: 0, + commentHeight: 0, + outputContainerOffset: 0, + outputTotalHeight: 0, + outputShowMoreContainerHeight: 0, + outputShowMoreContainerOffset: 0, + totalHeight: this.computeTotalHeight(17, 0, 0, 0), + codeIndicatorHeight: 0, + outputIndicatorHeight: 0, + bottomToolbarOffset: 0, + layoutState: CellLayoutState.Uninitialized, + estimatedHasHorizontalScrolling: false + }); + + this._commentOptions = this._configurationService.getValue('editor.comments', { overrideIdentifier: this.language }); + this._register(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('editor.comments')) { + this._commentOptions = this._configurationService.getValue('editor.comments', { overrideIdentifier: this.language }); + } + })); + } + get cellKind() { + return this._cell.kind; + } + + get model() { + if (!(this._cell.cellModel instanceof NotebookCellTextModel)) { + throw new Error(`Unexpected cell model type: ${typeof this._cell.cellModel}`); + } + return this._cell.cellModel; + } + + //#region BaseCellViewModel + get handle() { + return this.model.handle; + } + + get uri() { + return this.model.uri; + } + + // get lineCount() { + // return this.model.textBuffer.getLineCount(); + // } + get metadata() { + return this.model.metadata; + } + + get internalMetadata() { + return this.model.internalMetadata; + } + + get language() { + return this.model.language; + } + + get mime() { + if (typeof this.model.mime === 'string') { + return this.model.mime; + } + + switch (this.language) { + case 'markdown': + return Mimes.markdown; + + default: + return Mimes.text; + } + } + + get textBuffer() { + return this.model.textBuffer; + } + + get editorAttached(): boolean { + return Boolean(this._cell.editor); + } + + get textModel(): ITextModel | undefined { + return this.model.textModel; + } + + private _editStateSource: string = ''; + + get editStateSource(): string { + return this._editStateSource; + } + + getText(): string { + return this.model.getValue(); + } + getAlternativeId(): number { + return this.model.alternativeId; + } + getTextLength(): number { + return this.model.getTextLength(); + } + hasModel(): this is IEditableCellViewModel { + return !!this.textModel; + } + getSelections(): Selection[] { + // TODO: Check editor view state if no editor? + return this._cell.editor?.getSelections() ?? []; + } + setSelections(selections: Selection[]): void { + if (selections.length) { + if (this._cell.editor) { + this._cell.editor.setSelections(selections); + } + // TODO: Set in editor view state if no editor? + } + } + getSelectionsStartPosition(): IPosition[] | undefined { + if (this._cell.editor) { + const selections = this._cell.editor.getSelections(); + return selections?.map(s => s.getStartPosition()); + } + // TODO: Check editor view state if no editor? + return undefined; + } + private _inputCollapsed: boolean = false; + get isInputCollapsed(): boolean { + return this._inputCollapsed; + } + set isInputCollapsed(v: boolean) { + this._inputCollapsed = v; + this._onDidChangeState.fire({ inputCollapsedChanged: true }); + } + private _outputCollapsed: boolean = false; + get isOutputCollapsed(): boolean { + return this._outputCollapsed; + } + set isOutputCollapsed(v: boolean) { + this._outputCollapsed = v; + this._onDidChangeState.fire({ outputCollapsedChanged: true }); + } + private _dragging: boolean = false; + get dragging(): boolean { + return this._dragging; + } + + set dragging(v: boolean) { + this._dragging = v; + this._onDidChangeState.fire({ dragStateChanged: true }); + } + private _lineNumbers: 'on' | 'off' | 'inherit' = 'inherit'; + get lineNumbers(): 'on' | 'off' | 'inherit' { + return this._lineNumbers; + } + + set lineNumbers(lineNumbers: 'on' | 'off' | 'inherit') { + if (lineNumbers === this._lineNumbers) { + return; + } + + this._lineNumbers = lineNumbers; + this._onDidChangeState.fire({ cellLineNumberChanged: true }); + } + private _commentOptions: IEditorCommentsOptions; + public get commentOptions(): IEditorCommentsOptions { + return this._commentOptions; + } + + public set commentOptions(newOptions: IEditorCommentsOptions) { + this._commentOptions = newOptions; + } + private _focusMode: CellFocusMode = CellFocusMode.Container; + get focusMode() { + return this._focusMode; + } + set focusMode(newMode: CellFocusMode) { + if (this._focusMode !== newMode) { + this._focusMode = newMode; + this._onDidChangeState.fire({ focusModeChanged: true }); + } + } + + protected _commentHeight = 0; + + set commentHeight(height: number) { + if (this._commentHeight === height) { + return; + } + this._commentHeight = height; + // this.layoutChange({ commentHeight: true }, 'BaseCellViewModel#commentHeight'); + } + //#endregion + //#region CodeCellViewModel + private computeTotalHeight(editorHeight: number, outputsTotalHeight: number, outputShowMoreContainerHeight: number, chatHeight: number): number { + const layoutConfiguration = this._notebookInstance.notebookOptions.getLayoutConfiguration(); + const { bottomToolbarGap } = this._notebookInstance.notebookOptions.computeBottomToolbarDimensions(this.viewType); + return layoutConfiguration.editorToolbarHeight + + layoutConfiguration.cellTopMargin + + chatHeight + + editorHeight + + this._notebookInstance.notebookOptions.computeEditorStatusbarHeight(this.internalMetadata, this.uri) + + this._commentHeight + + outputsTotalHeight + + outputShowMoreContainerHeight + + bottomToolbarGap + + layoutConfiguration.cellBottomMargin; + } + private _chatHeight = 0; + set chatHeight(height: number) { + if (this._chatHeight === height) { + return; + } + + this._chatHeight = height; + // this.layoutChange({ chatHeight: true }, 'CodeCellViewModel#chatHeight'); + } + get chatHeight() { + return this._chatHeight; + } + + private _hoveringOutput: boolean = false; + public get outputIsHovered(): boolean { + return this._hoveringOutput; + } + + public set outputIsHovered(v: boolean) { + this._hoveringOutput = v; + this._onDidChangeState.fire({ outputIsHoveredChanged: true }); + } + + private _focusOnOutput: boolean = false; + public get outputIsFocused(): boolean { + return this._focusOnOutput; + } + + public set outputIsFocused(v: boolean) { + this._focusOnOutput = v; + this._onDidChangeState.fire({ outputIsFocusedChanged: true }); + } + + private _focusInputInOutput: boolean = false; + public get inputInOutputIsFocused(): boolean { + return this._focusInputInOutput; + } + + public set inputInOutputIsFocused(v: boolean) { + this._focusInputInOutput = v; + } + + private _layoutInfo: ISettableObservable; + + get layoutInfo() { + return this._layoutInfo.get(); + } + + private _outputViewModels: ICellOutputViewModel[]; + + get outputsViewModels() { + return this._outputViewModels; + } + //#endregion + getHeight(lineHeight: number): number { + throw new Error('Method not implemented.'); + } + resolveTextModel(): Promise { + throw new Error('Method not implemented.'); + } + getCellDecorations(): INotebookCellDecorationOptions[] { + throw new Error('Method not implemented.'); + } + getCellStatusBarItems(): INotebookCellStatusBarItem[] { + throw new Error('Method not implemented.'); + } + getEditState(): CellEditState { + throw new Error('Method not implemented.'); + } + updateEditState(state: CellEditState, source: string): void { + throw new Error('Method not implemented.'); + } + deltaModelDecorations(oldDecorations: readonly string[], newDecorations: readonly IModelDeltaDecoration[]): string[] { + throw new Error('Method not implemented.'); + } + getCellDecorationRange(id: string): Range | null { + throw new Error('Method not implemented.'); + } + enableAutoLanguageDetection(): void { + throw new Error('Method not implemented.'); + } + getOutputOffset(index: number): number { + throw new Error('Method not implemented.'); + } + updateOutputHeight(index: number, height: number, source?: string): void { + throw new Error('Method not implemented.'); + } +} diff --git a/src/vs/workbench/contrib/positronNotebook/browser/adapters/PositronNotebookEditorControl.ts b/src/vs/workbench/contrib/positronNotebook/browser/adapters/PositronNotebookEditorControl.ts new file mode 100644 index 000000000000..34a66589f1ab --- /dev/null +++ b/src/vs/workbench/contrib/positronNotebook/browser/adapters/PositronNotebookEditorControl.ts @@ -0,0 +1,523 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2025 Posit Software, PBC. All rights reserved. + * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. + *--------------------------------------------------------------------------------------------*/ +import { CancellationToken } from '../../../../../base/common/cancellation.js'; +import { Emitter, Event } from '../../../../../base/common/event.js'; +import { Disposable, DisposableStore, MutableDisposable } from '../../../../../base/common/lifecycle.js'; +import { observableValue } from '../../../../../base/common/observable.js'; +import { generateUuid } from '../../../../../base/common/uuid.js'; +import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; +import { FontInfo } from '../../../../../editor/common/config/fontInfo.js'; +import { Range } from '../../../../../editor/common/core/range.js'; +import { Selection } from '../../../../../editor/common/core/selection.js'; +import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { CellFindMatchWithIndex, IActiveNotebookEditor, IBaseCellEditorOptions, ICellOutputViewModel, ICellViewModel, IFocusNotebookCellOptions, IInsetRenderOutput, IModelDecorationsChangeAccessor, INotebookCellOverlayChangeAccessor, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorMouseEvent, INotebookEditorOptions, INotebookEditorViewState, INotebookViewCellsUpdateEvent, INotebookViewModel, INotebookViewZoneChangeAccessor, INotebookWebviewMessage } from '../../../notebook/browser/notebookBrowser.js'; +import { NotebookOptions } from '../../../notebook/browser/notebookOptions.js'; +import { NotebookCellStateChangedEvent, NotebookLayoutInfo } from '../../../notebook/browser/notebookViewEvents.js'; +import { INotebookEditorService } from '../../../notebook/browser/services/notebookEditorService.js'; +import { NotebookTextModel } from '../../../notebook/common/model/notebookTextModel.js'; +import { INotebookFindOptions } from '../../../notebook/common/notebookCommon.js'; +import { INotebookKernel } from '../../../notebook/common/notebookKernelService.js'; +import { ICellRange } from '../../../notebook/common/notebookRange.js'; +import { IWebviewElement } from '../../../webview/browser/webview.js'; +import { IPositronNotebookInstance } from '../IPositronNotebookInstance.js'; +import { IPositronNotebookCell } from '../PositronNotebookCells/IPositronNotebookCell.js'; +import { getSelectedCells } from '../selectionMachine.js'; +import { PositronNotebookViewModel } from './PositronNotebookViewModel.js'; + +/** + * The PositronNotebookEditorControl is used by features like inline chat, debugging, and outlines + * to access the code editor widget of the selected cell in a Positron notebook. + */ +export class PositronNotebookEditorControl extends Disposable implements INotebookEditor { + //#region Private properties + private _layoutInfo; + private _activeCodeEditor; + + private readonly _viewModel = this._register(new MutableDisposable()); + private readonly _viewModelDisposables = this._register(new DisposableStore()); + + /** + * A unique identifier for this notebook editor control. + */ + private readonly _uuid = generateUuid(); + //#endregion + + //#region Events + private readonly _onDidChangeCellState = this._register(new Emitter()); + private readonly _onDidChangeViewCells = this._register(new Emitter()); + private readonly _onDidChangeVisibleRanges = this._register(new Emitter()); + private readonly _onDidChangeSelection = this._register(new Emitter()); + private readonly _onDidChangeFocus = this._register(new Emitter()); + private readonly _onDidChangeModel = this._register(new Emitter()); + private readonly _onDidAttachViewModel = this._register(new Emitter()); + private readonly _onDidFocusWidget = this._register(new Emitter()); + private readonly _onDidBlurWidget = this._register(new Emitter()); + private readonly _onDidScroll = this._register(new Emitter()); + private readonly _onDidChangeLayout = this._register(new Emitter()); + private readonly _onDidChangeActiveCell = this._register(new Emitter()); + private readonly _onDidChangeActiveKernel = this._register(new Emitter()); + private readonly _onMouseUp = this._register(new Emitter()); + private readonly _onMouseDown = this._register(new Emitter()); + private readonly _onDidReceiveMessage = this._register(new Emitter()); + + /** + * Event that fires when the active cell, and therefore the active code editor, changes. + */ + public readonly onDidChangeActiveEditor; + public readonly onDidChangeCellState = this._onDidChangeCellState.event; + public readonly onDidChangeViewCells = this._onDidChangeViewCells.event; + public readonly onDidChangeVisibleRanges = this._onDidChangeVisibleRanges.event; + public readonly onDidChangeSelection = this._onDidChangeSelection.event; + public readonly onDidChangeFocus = this._onDidChangeFocus.event; + public readonly onDidChangeModel = this._onDidChangeModel.event; + public readonly onDidAttachViewModel = this._onDidAttachViewModel.event; + public readonly onDidFocusWidget = this._onDidFocusWidget.event; + public readonly onDidBlurWidget = this._onDidBlurWidget.event; + public readonly onDidScroll = this._onDidScroll.event; + public readonly onDidChangeLayout = this._onDidChangeLayout.event; + public readonly onDidChangeActiveCell = this._onDidChangeActiveCell.event; + public readonly onDidChangeActiveKernel = this._onDidChangeActiveKernel.event; + public readonly onMouseUp = this._onMouseUp.event; + public readonly onMouseDown = this._onMouseDown.event; + public readonly onDidReceiveMessage = this._onDidReceiveMessage.event; + //#endregion + + constructor( + private readonly _notebookInstance: IPositronNotebookInstance, + @INotebookEditorService private readonly _notebookEditorService: INotebookEditorService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + ) { + super(); + + const selectionMachine = this._notebookInstance.selectionStateMachine; + + // Mock layout info for now + this._layoutInfo = observableValue('layoutInfo', { + width: 0, + height: 0, + scrollHeight: 0, + // eslint-disable-next-line local/code-no-dangerous-type-assertions + fontInfo: {} as FontInfo, + stickyHeight: 0, + listViewOffsetTop: 0 + }); + + // Register this as a notebook editor widget + this._notebookEditorService.addNotebookEditor(this); + + // Update the active code editor when the notebook selection state changes. + this._activeCodeEditor = selectionMachine.state.map( + this, + (state) => /** @description activeCodeEditor */ getSelectedCells(state)[0]?.editor + ); + this.onDidChangeActiveEditor = Event.fromObservable(this._activeCodeEditor.map(this, () => this)); + + this._register(this._notebookInstance.onDidChangeModel(model => { + this._viewModelDisposables.clear(); + + if (model) { + const viewModel = this._instantiationService.createInstance(PositronNotebookViewModel, this._notebookInstance, model, this._layoutInfo); + this._viewModel.value = viewModel; + + // Forward view model events. + this._viewModelDisposables.add(viewModel.onDidChangeSelection(() => { + this._onDidChangeSelection.fire(); + })); + } else { + this._viewModel.value = undefined; + } + + this._onDidChangeModel.fire(model); + })); + } + + //#region readonly properties + /** + * The visible range of cells. + */ + public get visibleRanges(): ICellRange[] { + // TODO: Implement visible ranges + return []; + } + + /** + * The notebook text model. + */ + public get textModel(): NotebookTextModel | undefined { + return this._notebookInstance.textModel; + } + + /** + * Whether the notebook is visible. + */ + public get isVisible(): boolean { + return this._notebookInstance.connectedToEditor; + } + + /** + * Whether the notebook is read-only. + */ + public get isReadOnly(): boolean { + return this._notebookInstance.isReadOnly; + } + + /** + * The notebook options. + */ + public get notebookOptions(): NotebookOptions { + return this._notebookInstance.notebookOptions; + } + + /** + * Whether the notebook is disposed. + */ + public get isDisposed(): boolean { + return this._notebookInstance.isDisposed; + } + + /** + * The active kernel. + */ + public get activeKernel(): INotebookKernel | undefined { + return this._notebookInstance.kernel.get(); + } + + /** + * The scoped context key service. + */ + public get scopedContextKeyService(): IContextKeyService { + throw new Error('Method not implemented.'); + } + + /** + * The scroll top position. + */ + public get scrollTop(): number { + throw new Error('Method not implemented.'); + } + + /** + * The scroll bottom position. + */ + public get scrollBottom(): number { + throw new Error('Method not implemented.'); + } + + /** + * The cell/code editor pairs. + */ + public get codeEditors(): [ICellViewModel, ICodeEditor][] { + const codeEditors: [ICellViewModel, ICodeEditor][] = []; + for (const [index, cell] of this._notebookInstance.cells.get().entries()) { + if (cell.editor) { + const viewCell = this._viewModel.value?.viewCells[index]; + if (!viewCell) { + throw new Error(`View cell for cell at index ${index} not found`); + } + codeEditors.push([viewCell, cell.editor]); + } + } + return codeEditors; + } + + /** + * The active cell and code editor pair. + */ + public get activeCellAndCodeEditor(): [ICellViewModel, ICodeEditor] | undefined { + throw new Error('Method not implemented.'); + } + + /** + * The active cell's code editor. + */ + public get activeCodeEditor(): ICodeEditor | undefined { + return this._activeCodeEditor.get(); + } + + //#region Private methods + private toPositronCells(cells?: Iterable): IPositronNotebookCell[] { + const allPositronCells = this._notebookInstance.cells.get(); + if (!cells) { + return allPositronCells; + } + + const positronCells: IPositronNotebookCell[] = []; + for (const cell of cells) { + const positronCell = allPositronCells.find(c => c.handleId === cell.handle); + if (!positronCell) { + throw new Error(`Cell with handleId ${cell.handle} not found in Positron notebook instance`); + } + positronCells.push(positronCell); + } + return positronCells; + } + //#endregion + + //#region Public methods + getLength(): number { + return this._notebookInstance.cells.get().length; + } + getSelections(): ICellRange[] { + return this._viewModel.value?.getSelections() ?? []; + } + setSelections(selections: ICellRange[]): void { + throw new Error('Method not implemented.'); + } + getFocus(): ICellRange { + // TODO: Is this necessarily the first selected cell? + const activeCell = getSelectedCells(this._notebookInstance.selectionStateMachine.state.get())[0]; + if (activeCell) { + return { start: activeCell.index, end: activeCell.index + 1 }; + } + return { start: 0, end: 0 }; + } + setFocus(focus: ICellRange): void { + throw new Error('Method not implemented.'); + } + getId(): string { + return this._uuid; + } + getViewModel(): INotebookViewModel | undefined { + return this._viewModel.value; + } + hasModel(): this is IActiveNotebookEditor { + return this._notebookInstance.textModel !== undefined; + } + getDomNode(): HTMLElement { + if (!this._notebookInstance.cellsContainer) { + throw new Error('Notebook instance does not have a cells container'); + } + return this._notebookInstance.cellsContainer; + } + getInnerWebview(): IWebviewElement | undefined { + throw new Error('Method not implemented.'); + } + getSelectionViewModels(): ICellViewModel[] { + throw new Error('Method not implemented.'); + } + getEditorViewState(): INotebookEditorViewState { + return this._notebookInstance.getEditorViewState(); + } + restoreListViewState(viewState: INotebookEditorViewState | undefined): void { + throw new Error('Method not implemented.'); + } + getBaseCellEditorOptions(language: string): IBaseCellEditorOptions { + return this._notebookInstance.getBaseCellEditorOptions(language); + } + focus(): void { + throw new Error('Method not implemented.'); + } + focusContainer(clearSelection?: boolean): void { + throw new Error('Method not implemented.'); + } + hasEditorFocus(): boolean { + throw new Error('Method not implemented.'); + } + hasWebviewFocus(): boolean { + throw new Error('Method not implemented.'); + } + hasOutputTextSelection(): boolean { + throw new Error('Method not implemented.'); + } + async setOptions(options: INotebookEditorOptions | undefined): Promise { + return this._notebookInstance.setOptions(options); + } + focusElement(cell: ICellViewModel): void { + throw new Error('Method not implemented.'); + } + /** + * Gather info about editor layout such as width, height, and scroll behavior. + * @returns The current layout info for the editor. + */ + getLayoutInfo(): NotebookLayoutInfo { + return this._layoutInfo.get(); + } + getVisibleRangesPlusViewportAboveAndBelow(): ICellRange[] { + throw new Error('Method not implemented.'); + } + focusNotebookCell(cell: ICellViewModel, focus: 'editor' | 'container' | 'output', options?: IFocusNotebookCellOptions): Promise { + throw new Error('Method not implemented.'); + } + async executeNotebookCells(cells?: Iterable): Promise { + const positronCells = this.toPositronCells(cells); + await this._notebookInstance.runCells(positronCells); + } + cancelNotebookCells(cells?: Iterable): Promise { + throw new Error('Method not implemented.'); + } + getActiveCell(): ICellViewModel | undefined { + if (this._viewModel.value) { + const activeCell = getSelectedCells(this._notebookInstance.selectionStateMachine.state.get())[0]; + if (activeCell) { + return this._viewModel.value.viewCells[activeCell.index]; + } + } + return undefined; + } + layoutNotebookCell(cell: ICellViewModel, height: number): Promise { + throw new Error('Method not implemented.'); + } + createOutput(cell: ICellViewModel, output: IInsetRenderOutput, offset: number, createWhenIdle: boolean): Promise { + throw new Error('Method not implemented.'); + } + updateOutput(cell: ICellViewModel, output: IInsetRenderOutput, offset: number): Promise { + throw new Error('Method not implemented.'); + } + copyOutputImage(cellOutput: ICellOutputViewModel): Promise { + throw new Error('Method not implemented.'); + } + selectOutputContent(cell: ICellViewModel): void { + throw new Error('Method not implemented.'); + } + selectInputContents(cell: ICellViewModel): void { + throw new Error('Method not implemented.'); + } + postMessage(message: any): void { + throw new Error('Method not implemented.'); + } + addClassName(className: string): void { + throw new Error('Method not implemented.'); + } + removeClassName(className: string): void { + throw new Error('Method not implemented.'); + } + setScrollTop(scrollTop: number): void { + throw new Error('Method not implemented.'); + } + revealCellRangeInView(range: ICellRange): void { + throw new Error('Method not implemented.'); + } + revealInView(cell: ICellViewModel): Promise { + throw new Error('Method not implemented.'); + } + revealInViewAtTop(cell: ICellViewModel): void { + throw new Error('Method not implemented.'); + } + revealInCenter(cell: ICellViewModel): void { + throw new Error('Method not implemented.'); + } + revealInCenterIfOutsideViewport(cell: ICellViewModel): Promise { + throw new Error('Method not implemented.'); + } + revealFirstLineIfOutsideViewport(cell: ICellViewModel): Promise { + throw new Error('Method not implemented.'); + } + revealLineInViewAsync(cell: ICellViewModel, line: number): Promise { + throw new Error('Method not implemented.'); + } + revealLineInCenterAsync(cell: ICellViewModel, line: number): Promise { + throw new Error('Method not implemented.'); + } + revealLineInCenterIfOutsideViewportAsync(cell: ICellViewModel, line: number): Promise { + throw new Error('Method not implemented.'); + } + revealRangeInViewAsync(cell: ICellViewModel, range: Selection | Range): Promise { + throw new Error('Method not implemented.'); + } + revealRangeInCenterAsync(cell: ICellViewModel, range: Selection | Range): Promise { + throw new Error('Method not implemented.'); + } + revealRangeInCenterIfOutsideViewportAsync(cell: ICellViewModel, range: Selection | Range): Promise { + throw new Error('Method not implemented.'); + } + revealCellOffsetInCenter(cell: ICellViewModel, offset: number): void { + throw new Error('Method not implemented.'); + } + revealOffsetInCenterIfOutsideViewport(offset: number): void { + throw new Error('Method not implemented.'); + } + getCellRangeFromViewRange(startIndex: number, endIndex: number): ICellRange | undefined { + throw new Error('Method not implemented.'); + } + setHiddenAreas(_ranges: ICellRange[]): boolean { + throw new Error('Method not implemented.'); + } + setCellEditorSelection(cell: ICellViewModel, selection: Range): void { + throw new Error('Method not implemented.'); + } + deltaCellDecorations(oldDecorations: string[], newDecorations: INotebookDeltaDecoration[]): string[] { + throw new Error('Method not implemented.'); + } + changeModelDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null { + throw new Error('Method not implemented.'); + } + changeViewZones(callback: (accessor: INotebookViewZoneChangeAccessor) => void): void { + throw new Error('Method not implemented.'); + } + changeCellOverlays(callback: (accessor: INotebookCellOverlayChangeAccessor) => void): void { + throw new Error('Method not implemented.'); + } + getViewZoneLayoutInfo(id: string): { top: number; height: number } | null { + throw new Error('Method not implemented.'); + } + getContribution(id: string): T { + // TODO: Implement notebook editor contributions + return null as unknown as T; + } + getViewIndexByModelIndex(index: number): number { + throw new Error('Method not implemented.'); + } + getCellsInRange(range?: ICellRange): ReadonlyArray { + const viewCells = this._viewModel.value?.viewCells ?? []; + if (!range) { + // Return all cells if no range is specified + return viewCells; + } + // Return cells within the specified range [start, end) + // Note: end is exclusive based on typical VS Code patterns + return viewCells.slice(range.start, range.end); + } + + cellAt(index: number): ICellViewModel | undefined { + throw new Error('Method not implemented.'); + } + getCellByHandle(handle: number): ICellViewModel | undefined { + throw new Error('Method not implemented.'); + } + getCellIndex(cell: ICellViewModel): number | undefined { + return this._viewModel.value?.getCellIndex(cell); + } + getNextVisibleCellIndex(index: number): number | undefined { + throw new Error('Method not implemented.'); + } + getPreviousVisibleCellIndex(index: number): number | undefined { + throw new Error('Method not implemented.'); + } + find(query: string, options: INotebookFindOptions, token: CancellationToken, skipWarmup?: boolean, shouldGetSearchPreviewInfo?: boolean, ownerID?: string): Promise { + throw new Error('Method not implemented.'); + } + findHighlightCurrent(matchIndex: number, ownerID?: string): Promise { + throw new Error('Method not implemented.'); + } + findUnHighlightCurrent(matchIndex: number, ownerID?: string): Promise { + throw new Error('Method not implemented.'); + } + findStop(ownerID?: string): void { + throw new Error('Method not implemented.'); + } + showProgress(): void { + throw new Error('Method not implemented.'); + } + hideProgress(): void { + throw new Error('Method not implemented.'); + } + getAbsoluteTopOfElement(cell: ICellViewModel): number { + throw new Error('Method not implemented.'); + } + getAbsoluteBottomOfElement(cell: ICellViewModel): number { + throw new Error('Method not implemented.'); + } + getHeightOfElement(cell: ICellViewModel): number { + throw new Error('Method not implemented.'); + } + //#endregion + + public override dispose(): void { + this._notebookEditorService.removeNotebookEditor(this); + + super.dispose(); + } +} diff --git a/src/vs/workbench/contrib/positronNotebook/browser/adapters/PositronNotebookViewModel.ts b/src/vs/workbench/contrib/positronNotebook/browser/adapters/PositronNotebookViewModel.ts new file mode 100644 index 000000000000..b8d37bd436ac --- /dev/null +++ b/src/vs/workbench/contrib/positronNotebook/browser/adapters/PositronNotebookViewModel.ts @@ -0,0 +1,115 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2025 Posit Software, PBC. All rights reserved. + * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. + *--------------------------------------------------------------------------------------------*/ +import { Emitter } from '../../../../../base/common/event.js'; +import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { autorun, ISettableObservable } from '../../../../../base/common/observable.js'; +import { Range } from '../../../../../editor/common/core/range.js'; +import { TrackedRangeStickiness } from '../../../../../editor/common/model.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { INotebookViewModel, INotebookViewCellsUpdateEvent, ICellViewModel, INotebookDeltaViewZoneDecoration, INotebookDeltaCellStatusBarItems, CellFindMatchWithIndex } from '../../../notebook/browser/notebookBrowser.js'; +import { NotebookLayoutInfo } from '../../../notebook/browser/notebookViewEvents.js'; +import { NotebookTextModel } from '../../../notebook/common/model/notebookTextModel.js'; +import { ICellRange } from '../../../notebook/common/notebookRange.js'; +import { IPositronNotebookInstance } from '../IPositronNotebookInstance.js'; +import { getSelectedCells } from '../selectionMachine.js'; +import { PositronNotebookCellViewModel } from './PositronNotebookCellViewModel.js'; + +export class PositronNotebookViewModel extends Disposable implements INotebookViewModel { + //#region Events + private readonly _onDidChangeViewCells = this._register(new Emitter()); + private readonly _onDidChangeSelection = this._register(new Emitter()); + private readonly _onDidFoldingStateChanged = this._register(new Emitter()); + + public readonly onDidChangeViewCells = this._onDidChangeViewCells.event; + public readonly onDidChangeSelection = this._onDidChangeSelection.event; + public readonly onDidFoldingStateChanged = this._onDidFoldingStateChanged.event; + //#endregion Events + + //#region Private Properties + private _viewCells; + //#endregion Private Properties + + constructor( + private readonly _notebookInstance: IPositronNotebookInstance, + private readonly _notebook: NotebookTextModel, + private readonly _layoutInfo: ISettableObservable, + @IInstantiationService private readonly _instantiationService: IInstantiationService + ) { + super(); + + // TODO: Do we need more finegrained updates? Do we need an equals fn? + this._viewCells = this._notebookInstance.cells.map(cells => + cells.map(cell => this._instantiationService.createInstance( + PositronNotebookCellViewModel, + this._notebook.viewType, + cell, + this._notebookInstance, + this._layoutInfo + )) + ); + + this._register(autorun(reader => { + const selectionStateMachine = this._notebookInstance.selectionStateMachine; + selectionStateMachine.state.read(reader); + // TODO: How should we determine the event source (currently hardcoding 'view')? + this._onDidChangeSelection.fire('view'); + })); + } + + get notebookDocument() { + return this._notebook; + } + get viewCells() { + return this._viewCells.get(); + } + get layoutInfo(): NotebookLayoutInfo { + return this._layoutInfo.get(); + } + get viewType(): string { + return this._notebook.viewType; + } + getNearestVisibleCellIndexUpwards(index: number): number { + throw new Error('Method not implemented.'); + } + getTrackedRange(id: string): ICellRange | null { + throw new Error('Method not implemented.'); + } + setTrackedRange(id: string | null, newRange: ICellRange | null, newStickiness: TrackedRangeStickiness): string | null { + throw new Error('Method not implemented.'); + } + getOverviewRulerDecorations(): INotebookDeltaViewZoneDecoration[] { + throw new Error('Method not implemented.'); + } + getSelections(): ICellRange[] { + // TODO: Think this isn't handling single vs multi selections correctly. + // Can we have the right logic here and fix it in selection state machine later? + const cells = getSelectedCells(this._notebookInstance.selectionStateMachine.state.get()); + return cells.map(cell => ({ start: cell.index, end: cell.index + 1 })); + } + getCellIndex(cell: ICellViewModel): number { + if (cell instanceof PositronNotebookCellViewModel) { + return this.viewCells.indexOf(cell); + } + return -1; + } + getMostRecentlyExecutedCell(): ICellViewModel | undefined { + throw new Error('Method not implemented.'); + } + deltaCellStatusBarItems(oldItems: string[], newItems: INotebookDeltaCellStatusBarItems[]): string[] { + throw new Error('Method not implemented.'); + } + getFoldedLength(index: number): number { + throw new Error('Method not implemented.'); + } + getFoldingStartIndex(index: number): number { + throw new Error('Method not implemented.'); + } + replaceOne(cell: ICellViewModel, range: Range, text: string): Promise { + throw new Error('Method not implemented.'); + } + replaceAll(matches: CellFindMatchWithIndex[], texts: string[]): Promise { + throw new Error('Method not implemented.'); + } +} From cab34b2d992694c38d1b010a06cd5b765dee76ac Mon Sep 17 00:00:00 2001 From: seem Date: Tue, 30 Sep 2025 19:08:52 +0200 Subject: [PATCH 6/7] include positron notebook editor ids in known notebooks --- .../workbench/contrib/notebook/browser/notebookBrowser.ts | 7 ++++++- .../contrib/notebook/common/notebookEditorInput.ts | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 4c79dad20478..e51e3e2eb9a3 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -33,6 +33,9 @@ import { IContextKeyService } from '../../../../platform/contextkey/common/conte import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { IObservable } from '../../../../base/common/observable.js'; import { INotebookTextDiffEditor } from './diff/notebookDiffEditorBrowser.js'; +// --- Start Positron --- +import { POSITRON_NOTEBOOK_EDITOR_ID } from '../../positronNotebook/common/positronNotebookCommon.js'; +// --- End Positron --- //#region Shared commands export const EXPAND_CELL_INPUT_COMMAND_ID = 'notebook.cell.expandCellInput'; @@ -926,7 +929,9 @@ export function getNotebookEditorFromEditorPane(editorPane?: IEditorPane): INote return; } - if (editorPane.getId() === NOTEBOOK_EDITOR_ID) { + // --- Start Positron --- + if (editorPane.getId() === NOTEBOOK_EDITOR_ID || editorPane.getId() === POSITRON_NOTEBOOK_EDITOR_ID) { + // --- End Positron --- return editorPane.getControl() as INotebookEditor | undefined; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts index 46a2e7a7abe0..d10dda6d32eb 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts @@ -34,6 +34,7 @@ import { ICustomEditorLabelService } from '../../../services/editor/common/custo // --- Start Positron --- import { IRuntimeSessionService } from '../../../services/runtimeSession/common/runtimeSessionService.js'; import { ILogService } from '../../../../platform/log/common/log.js'; +import { POSITRON_NOTEBOOK_EDITOR_INPUT_ID } from '../../positronNotebook/common/positronNotebookCommon.js'; // --- End Positron --- export interface NotebookEditorInputOptions { @@ -419,5 +420,8 @@ export function isCompositeNotebookEditorInput(thing: unknown): thing is ICompos export function isNotebookEditorInput(thing: EditorInput | undefined): thing is NotebookEditorInput { return !!thing && typeof thing === 'object' - && thing.typeId === NotebookEditorInput.ID; + // --- Start Positron --- + // && thing.typeId === NotebookEditorInput.ID; + && (thing.typeId === NotebookEditorInput.ID || thing.typeId === POSITRON_NOTEBOOK_EDITOR_INPUT_ID); + // --- End Positron --- } From c246232ec5e0f25b5d8d93b47d8c41465ddfb68f Mon Sep 17 00:00:00 2001 From: seem Date: Tue, 30 Sep 2025 19:27:26 +0200 Subject: [PATCH 7/7] refine --- .../adapters/PositronNotebookEditorControl.ts | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/adapters/PositronNotebookEditorControl.ts b/src/vs/workbench/contrib/positronNotebook/browser/adapters/PositronNotebookEditorControl.ts index 34a66589f1ab..56d2c3d1604c 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/adapters/PositronNotebookEditorControl.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/adapters/PositronNotebookEditorControl.ts @@ -34,6 +34,7 @@ import { PositronNotebookViewModel } from './PositronNotebookViewModel.js'; export class PositronNotebookEditorControl extends Disposable implements INotebookEditor { //#region Private properties private _layoutInfo; + private _activeCell; private _activeCodeEditor; private readonly _viewModel = this._register(new MutableDisposable()); @@ -57,8 +58,6 @@ export class PositronNotebookEditorControl extends Disposable implements INotebo private readonly _onDidBlurWidget = this._register(new Emitter()); private readonly _onDidScroll = this._register(new Emitter()); private readonly _onDidChangeLayout = this._register(new Emitter()); - private readonly _onDidChangeActiveCell = this._register(new Emitter()); - private readonly _onDidChangeActiveKernel = this._register(new Emitter()); private readonly _onMouseUp = this._register(new Emitter()); private readonly _onMouseDown = this._register(new Emitter()); private readonly _onDidReceiveMessage = this._register(new Emitter()); @@ -78,8 +77,8 @@ export class PositronNotebookEditorControl extends Disposable implements INotebo public readonly onDidBlurWidget = this._onDidBlurWidget.event; public readonly onDidScroll = this._onDidScroll.event; public readonly onDidChangeLayout = this._onDidChangeLayout.event; - public readonly onDidChangeActiveCell = this._onDidChangeActiveCell.event; - public readonly onDidChangeActiveKernel = this._onDidChangeActiveKernel.event; + public readonly onDidChangeActiveCell; + public readonly onDidChangeActiveKernel; public readonly onMouseUp = this._onMouseUp.event; public readonly onMouseDown = this._onMouseDown.event; public readonly onDidReceiveMessage = this._onDidReceiveMessage.event; @@ -103,18 +102,20 @@ export class PositronNotebookEditorControl extends Disposable implements INotebo fontInfo: {} as FontInfo, stickyHeight: 0, listViewOffsetTop: 0 - }); + } satisfies NotebookLayoutInfo); // Register this as a notebook editor widget this._notebookEditorService.addNotebookEditor(this); // Update the active code editor when the notebook selection state changes. - this._activeCodeEditor = selectionMachine.state.map( - this, - (state) => /** @description activeCodeEditor */ getSelectedCells(state)[0]?.editor - ); + this._activeCell = selectionMachine.state.map(this, (state) => /** @description activeCell */ getSelectedCells(state)[0]); + this._activeCodeEditor = this._activeCell.map(this, (cell) => /** @description activeCodeEditor */ cell?.editor); + + this.onDidChangeActiveCell = Event.fromObservable(this._activeCell.map(this, () => { })); this.onDidChangeActiveEditor = Event.fromObservable(this._activeCodeEditor.map(this, () => this)); + this.onDidChangeActiveKernel = Event.fromObservable(this._notebookInstance.kernel.map(this, () => { })); + // Update the view model when the notebook text model changes. this._register(this._notebookInstance.onDidChangeModel(model => { this._viewModelDisposables.clear(); @@ -139,7 +140,6 @@ export class PositronNotebookEditorControl extends Disposable implements INotebo * The visible range of cells. */ public get visibleRanges(): ICellRange[] { - // TODO: Implement visible ranges return []; } @@ -284,7 +284,7 @@ export class PositronNotebookEditorControl extends Disposable implements INotebo return this._viewModel.value; } hasModel(): this is IActiveNotebookEditor { - return this._notebookInstance.textModel !== undefined; + return Boolean(this._notebookInstance.textModel); } getDomNode(): HTMLElement { if (!this._notebookInstance.cellsContainer) { @@ -330,7 +330,6 @@ export class PositronNotebookEditorControl extends Disposable implements INotebo } /** * Gather info about editor layout such as width, height, and scroll behavior. - * @returns The current layout info for the editor. */ getLayoutInfo(): NotebookLayoutInfo { return this._layoutInfo.get(); @@ -350,7 +349,7 @@ export class PositronNotebookEditorControl extends Disposable implements INotebo } getActiveCell(): ICellViewModel | undefined { if (this._viewModel.value) { - const activeCell = getSelectedCells(this._notebookInstance.selectionStateMachine.state.get())[0]; + const activeCell = this._activeCell.get(); if (activeCell) { return this._viewModel.value.viewCells[activeCell.index]; } @@ -454,7 +453,6 @@ export class PositronNotebookEditorControl extends Disposable implements INotebo throw new Error('Method not implemented.'); } getContribution(id: string): T { - // TODO: Implement notebook editor contributions return null as unknown as T; } getViewIndexByModelIndex(index: number): number { @@ -470,12 +468,11 @@ export class PositronNotebookEditorControl extends Disposable implements INotebo // Note: end is exclusive based on typical VS Code patterns return viewCells.slice(range.start, range.end); } - cellAt(index: number): ICellViewModel | undefined { - throw new Error('Method not implemented.'); + return this._viewModel.value?.viewCells[index]; } getCellByHandle(handle: number): ICellViewModel | undefined { - throw new Error('Method not implemented.'); + return this._viewModel.value?.viewCells.find(cell => cell.handle === handle); } getCellIndex(cell: ICellViewModel): number | undefined { return this._viewModel.value?.getCellIndex(cell);