diff --git a/src/vs/workbench/contrib/positronNotebook/browser/positronNotebook.contribution.ts b/src/vs/workbench/contrib/positronNotebook/browser/positronNotebook.contribution.ts index 362f73811e6..747b1b0d598 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/positronNotebook.contribution.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/positronNotebook.contribution.ts @@ -56,7 +56,7 @@ import { POSITRON_EXECUTE_CELL_COMMAND_ID, POSITRON_NOTEBOOK_EDITOR_ID, POSITRON import { getActiveCell, getSelectedCells, SelectionState } from './selectionMachine.js'; import { POSITRON_NOTEBOOK_CELL_CONTEXT_KEYS as CELL_CONTEXT_KEYS, POSITRON_NOTEBOOK_CELL_EDITOR_FOCUSED, POSITRON_NOTEBOOK_EDITOR_FOCUSED, POSITRON_NOTEBOOK_CELL_HAS_OUTPUTS, POSITRON_NOTEBOOK_CELL_JSON_OUTPUT_COUNT, POSITRON_NOTEBOOK_CELL_IMAGE_OUTPUT_COUNT, POSITRON_NOTEBOOK_CELL_OUTPUT_COLLAPSED, POSITRON_NOTEBOOK_CELL_OUTPUT_OVERFLOWS, POSITRON_NOTEBOOK_CELL_OUTPUT_SCROLLING, POSITRON_NOTEBOOK_EXPERIMENTAL, POSITRON_NOTEBOOK_OUTPUT_IMAGE_TARGETED, POSITRON_NOTEBOOK_OUTPUT_JSON_TARGETED } from './ContextKeysManager.js'; import './contrib/undoRedo/positronNotebookUndoRedo.js'; -import { registerAction2, MenuId, MenuRegistry } from '../../../../platform/actions/common/actions.js'; +import { Action2, registerAction2, MenuId, MenuRegistry } from '../../../../platform/actions/common/actions.js'; import { ExecuteSelectionInConsoleAction } from './ExecuteSelectionInConsoleAction.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { KernelStatusBadge } from './KernelStatusBadge.js'; @@ -76,6 +76,8 @@ import './AskAssistantAction.js'; // Register AskAssistantAction import { CONTEXT_FIND_INPUT_FOCUSED, CONTEXT_REPLACE_INPUT_FOCUSED } from '../../../../editor/contrib/find/browser/findModel.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; +import { IViewsService } from '../../../services/views/common/viewsService.js'; +import { IOutlinePane } from '../../outline/browser/outline.js'; export const POSITRON_NOTEBOOK_COMMAND_MODE = ContextKeyExpr.and( POSITRON_NOTEBOOK_EDITOR_FOCUSED, @@ -2103,6 +2105,39 @@ registerAction2(class extends NotebookAction2 { } }); +// Toggle Outline - Reveals or hides the Outline view in the Explorer pane +export class ToggleOutlineAction extends Action2 { + constructor() { + super({ + id: 'positronNotebook.toggleOutline', + title: localize2('toggleOutline', 'Toggle Outline'), + icon: ThemeIcon.fromId('list-unordered'), + f1: true, + category: POSITRON_NOTEBOOK_CATEGORY, + positronActionBarOptions: { + controlType: 'button', + displayTitle: false + }, + menu: { + id: MenuId.EditorActionsLeft, + group: 'navigation', + order: 45, + when: ContextKeyExpr.equals('activeEditor', POSITRON_NOTEBOOK_EDITOR_ID) + } + }); + } + + override async run(accessor: ServicesAccessor): Promise { + const viewsService = accessor.get(IViewsService); + if (viewsService.isViewVisible(IOutlinePane.Id)) { + viewsService.closeView(IOutlinePane.Id); + } else { + await viewsService.openView(IOutlinePane.Id, true); + } + } +} +registerAction2(ToggleOutlineAction); + // Ask Assistant - Opens assistant chat with prompt options for the notebook // Action is defined in AskAssistantAction.ts diff --git a/src/vs/workbench/contrib/positronNotebook/test/browser/toggleOutlineAction.vitest.ts b/src/vs/workbench/contrib/positronNotebook/test/browser/toggleOutlineAction.vitest.ts new file mode 100644 index 00000000000..75308f3c9c3 --- /dev/null +++ b/src/vs/workbench/contrib/positronNotebook/test/browser/toggleOutlineAction.vitest.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2026 Posit Software, PBC. All rights reserved. + * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. + *--------------------------------------------------------------------------------------------*/ + +/// + +import { createTestContainer } from '../../../../../test/vitest/positronTestContainer.js'; +import { stubInterface } from '../../../../../test/vitest/stubInterface.js'; +import { ToggleOutlineAction } from '../../browser/positronNotebook.contribution.js'; +import { IOutlinePane } from '../../../outline/browser/outline.js'; +import { IViewsService } from '../../../../services/views/common/viewsService.js'; + +describe('ToggleOutlineAction', () => { + const ctx = createTestContainer() + .withWorkbenchServices() + .build(); + + let isViewVisible: ReturnType>; + // openView is generic (); store it with a wider mock signature and + // re-narrow inside the stub via a closure so stubInterface's Partial + // stays type-correct. + let openView: ReturnType Promise>>; + let closeView: ReturnType>; + + beforeEach(() => { + isViewVisible = vi.fn(); + openView = vi.fn<(id: string, focus?: boolean) => Promise>().mockResolvedValue(null); + closeView = vi.fn(); + ctx.instantiationService.stub(IViewsService, stubInterface({ + isViewVisible, + openView: (id: string, focus?: boolean) => openView(id, focus) as Promise, + closeView, + })); + }); + + async function run(): Promise { + const action = new ToggleOutlineAction(); + await ctx.instantiationService.invokeFunction(accessor => action.run(accessor)); + } + + it('opens and focuses the outline view when it is not visible', async () => { + isViewVisible.mockReturnValue(false); + + await run(); + + expect(openView).toHaveBeenCalledWith(IOutlinePane.Id, true); + expect(closeView).not.toHaveBeenCalled(); + }); + + it('closes the outline view when it is visible', async () => { + isViewVisible.mockReturnValue(true); + + await run(); + + expect(closeView).toHaveBeenCalledWith(IOutlinePane.Id); + expect(openView).not.toHaveBeenCalled(); + }); +});