Path: blob/main/src/vs/workbench/contrib/notebook/browser/controller/cellOutputActions.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';6import { localize } from '../../../../../nls.js';7import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js';8import { IClipboardService } from '../../../../../platform/clipboard/common/clipboardService.js';9import { IOpenerService } from '../../../../../platform/opener/common/opener.js';10import { INotebookOutputActionContext, NOTEBOOK_ACTIONS_CATEGORY } from './coreActions.js';11import { NOTEBOOK_CELL_HAS_HIDDEN_OUTPUTS, NOTEBOOK_CELL_HAS_OUTPUTS } from '../../common/notebookContextKeys.js';12import * as icons from '../notebookIcons.js';13import { ILogService } from '../../../../../platform/log/common/log.js';14import { copyCellOutput } from '../viewModel/cellOutputTextHelper.js';15import { IEditorService } from '../../../../services/editor/common/editorService.js';16import { ICellOutputViewModel, ICellViewModel, INotebookEditor, getNotebookEditorFromEditorPane } from '../notebookBrowser.js';17import { CellKind, CellUri } from '../../common/notebookCommon.js';18import { CodeCellViewModel } from '../viewModel/codeCellViewModel.js';19import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';20import { INotebookEditorModelResolverService } from '../../common/notebookEditorModelResolverService.js';2122export const COPY_OUTPUT_COMMAND_ID = 'notebook.cellOutput.copy';2324registerAction2(class ShowAllOutputsAction extends Action2 {25constructor() {26super({27id: 'notebook.cellOuput.showEmptyOutputs',28title: localize('notebookActions.showAllOutput', "Show Empty Outputs"),29menu: {30id: MenuId.NotebookOutputToolbar,31when: ContextKeyExpr.and(NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_HAS_HIDDEN_OUTPUTS)32},33f1: false,34category: NOTEBOOK_ACTIONS_CATEGORY35});36}3738run(accessor: ServicesAccessor, context: INotebookOutputActionContext): void {39const cell = context.cell;40if (cell && cell.cellKind === CellKind.Code) {4142for (let i = 1; i < cell.outputsViewModels.length; i++) {43if (!cell.outputsViewModels[i].visible.get()) {44cell.outputsViewModels[i].setVisible(true, true);45(cell as CodeCellViewModel).updateOutputHeight(i, 1, 'command');46}47}48}49}50});5152registerAction2(class CopyCellOutputAction extends Action2 {53constructor() {54super({55id: COPY_OUTPUT_COMMAND_ID,56title: localize('notebookActions.copyOutput', "Copy Cell Output"),57menu: {58id: MenuId.NotebookOutputToolbar,59when: NOTEBOOK_CELL_HAS_OUTPUTS60},61category: NOTEBOOK_ACTIONS_CATEGORY,62icon: icons.copyIcon,63});64}6566private getNoteboookEditor(editorService: IEditorService, outputContext: INotebookOutputActionContext | { outputViewModel: ICellOutputViewModel } | undefined): INotebookEditor | undefined {67if (outputContext && 'notebookEditor' in outputContext) {68return outputContext.notebookEditor;69}70return getNotebookEditorFromEditorPane(editorService.activeEditorPane);71}7273async run(accessor: ServicesAccessor, outputContext: INotebookOutputActionContext | { outputViewModel: ICellOutputViewModel } | undefined): Promise<void> {74const notebookEditor = this.getNoteboookEditor(accessor.get(IEditorService), outputContext);7576if (!notebookEditor) {77return;78}7980let outputViewModel: ICellOutputViewModel | undefined;81if (outputContext && 'outputId' in outputContext && typeof outputContext.outputId === 'string') {82outputViewModel = getOutputViewModelFromId(outputContext.outputId, notebookEditor);83} else if (outputContext && 'outputViewModel' in outputContext) {84outputViewModel = outputContext.outputViewModel;85}8687if (!outputViewModel) {88// not able to find the output from the provided context, use the active cell89const activeCell = notebookEditor.getActiveCell();90if (!activeCell) {91return;92}9394if (activeCell.focusedOutputId !== undefined) {95outputViewModel = activeCell.outputsViewModels.find(output => {96return output.model.outputId === activeCell.focusedOutputId;97});98} else {99outputViewModel = activeCell.outputsViewModels.find(output => output.pickedMimeType?.isTrusted);100}101}102103if (!outputViewModel) {104return;105}106107const mimeType = outputViewModel.pickedMimeType?.mimeType;108109if (mimeType?.startsWith('image/')) {110const focusOptions = { skipReveal: true, outputId: outputViewModel.model.outputId, altOutputId: outputViewModel.model.alternativeOutputId };111await notebookEditor.focusNotebookCell(outputViewModel.cellViewModel as ICellViewModel, 'output', focusOptions);112notebookEditor.copyOutputImage(outputViewModel);113} else {114const clipboardService = accessor.get(IClipboardService);115const logService = accessor.get(ILogService);116117copyCellOutput(mimeType, outputViewModel, clipboardService, logService);118}119}120121});122123export function getOutputViewModelFromId(outputId: string, notebookEditor: INotebookEditor): ICellOutputViewModel | undefined {124const notebookViewModel = notebookEditor.getViewModel();125if (notebookViewModel) {126const codeCells = notebookViewModel.viewCells.filter(cell => cell.cellKind === CellKind.Code) as CodeCellViewModel[];127for (const cell of codeCells) {128const output = cell.outputsViewModels.find(output => output.model.outputId === outputId || output.model.alternativeOutputId === outputId);129if (output) {130return output;131}132}133}134135return undefined;136}137138export const OPEN_OUTPUT_COMMAND_ID = 'notebook.cellOutput.openInTextEditor';139140registerAction2(class OpenCellOutputInEditorAction extends Action2 {141constructor() {142super({143id: OPEN_OUTPUT_COMMAND_ID,144title: localize('notebookActions.openOutputInEditor', "Open Cell Output in Text Editor"),145f1: false,146category: NOTEBOOK_ACTIONS_CATEGORY,147icon: icons.copyIcon,148});149}150151private getNoteboookEditor(editorService: IEditorService, outputContext: INotebookOutputActionContext | { outputViewModel: ICellOutputViewModel } | undefined): INotebookEditor | undefined {152if (outputContext && 'notebookEditor' in outputContext) {153return outputContext.notebookEditor;154}155return getNotebookEditorFromEditorPane(editorService.activeEditorPane);156}157158async run(accessor: ServicesAccessor, outputContext: INotebookOutputActionContext | { outputViewModel: ICellOutputViewModel } | undefined): Promise<void> {159const notebookEditor = this.getNoteboookEditor(accessor.get(IEditorService), outputContext);160const notebookModelService = accessor.get(INotebookEditorModelResolverService);161162if (!notebookEditor) {163return;164}165166let outputViewModel: ICellOutputViewModel | undefined;167if (outputContext && 'outputId' in outputContext && typeof outputContext.outputId === 'string') {168outputViewModel = getOutputViewModelFromId(outputContext.outputId, notebookEditor);169} else if (outputContext && 'outputViewModel' in outputContext) {170outputViewModel = outputContext.outputViewModel;171}172173const openerService = accessor.get(IOpenerService);174175if (outputViewModel?.model.outputId && notebookEditor.textModel?.uri) {176// reserve notebook document reference since the active notebook editor might not be pinned so it can be replaced by the output editor177const ref = await notebookModelService.resolve(notebookEditor.textModel.uri);178await openerService.open(CellUri.generateCellOutputUriWithId(notebookEditor.textModel.uri, outputViewModel.model.outputId));179ref.dispose();180}181}182});183184export const OPEN_OUTPUT_IN_OUTPUT_PREVIEW_COMMAND_ID = 'notebook.cellOutput.openInOutputPreview';185186registerAction2(class OpenCellOutputInNotebookOutputEditorAction extends Action2 {187constructor() {188super({189id: OPEN_OUTPUT_IN_OUTPUT_PREVIEW_COMMAND_ID,190title: localize('notebookActions.openOutputInNotebookOutputEditor', "Open in Output Preview"),191menu: {192id: MenuId.NotebookOutputToolbar,193when: ContextKeyExpr.and(NOTEBOOK_CELL_HAS_OUTPUTS, ContextKeyExpr.equals('config.notebook.output.openInPreviewEditor.enabled', true))194},195f1: false,196category: NOTEBOOK_ACTIONS_CATEGORY,197});198}199200private getNotebookEditor(editorService: IEditorService, outputContext: INotebookOutputActionContext | { outputViewModel: ICellOutputViewModel } | undefined): INotebookEditor | undefined {201if (outputContext && 'notebookEditor' in outputContext) {202return outputContext.notebookEditor;203}204return getNotebookEditorFromEditorPane(editorService.activeEditorPane);205}206207async run(accessor: ServicesAccessor, outputContext: INotebookOutputActionContext | { outputViewModel: ICellOutputViewModel } | undefined): Promise<void> {208const notebookEditor = this.getNotebookEditor(accessor.get(IEditorService), outputContext);209if (!notebookEditor) {210return;211}212213let outputViewModel: ICellOutputViewModel | undefined;214if (outputContext && 'outputId' in outputContext && typeof outputContext.outputId === 'string') {215outputViewModel = getOutputViewModelFromId(outputContext.outputId, notebookEditor);216} else if (outputContext && 'outputViewModel' in outputContext) {217outputViewModel = outputContext.outputViewModel;218}219220if (!outputViewModel) {221return;222}223224const genericCellViewModel = outputViewModel.cellViewModel;225if (!genericCellViewModel) {226return;227}228229// get cell index230const cellViewModel = notebookEditor.getCellByHandle(genericCellViewModel.handle);231if (!cellViewModel) {232return;233}234const cellIndex = notebookEditor.getCellIndex(cellViewModel);235if (cellIndex === undefined) {236return;237}238239// get output index240const outputIndex = genericCellViewModel.outputsViewModels.indexOf(outputViewModel);241if (outputIndex === -1) {242return;243}244245if (!notebookEditor.textModel) {246return;247}248249// craft rich output URI to pass data to the notebook output editor/viewer250const outputURI = CellUri.generateOutputEditorUri(251notebookEditor.textModel.uri,252cellViewModel.id,253cellIndex,254outputViewModel.model.outputId,255outputIndex,256);257258const openerService = accessor.get(IOpenerService);259openerService.open(outputURI, { openToSide: true });260}261});262263264