From 1b3b40265b453b7a40f1569b2d783b4fdfb3eb21 Mon Sep 17 00:00:00 2001 From: rebornix Date: Thu, 19 Nov 2020 18:05:53 -0800 Subject: [PATCH] trusted notebook metadata --- .../notebook/browser/contrib/coreActions.ts | 34 ++++++++++-- .../notebook/browser/notebookBrowser.ts | 1 + .../notebook/browser/notebookEditorWidget.ts | 4 +- .../notebook/browser/notebookServiceImpl.ts | 19 ++++--- .../browser/view/renderers/cellOutput.ts | 54 ++++++++++++++++--- .../browser/view/renderers/cellRenderer.ts | 3 ++ .../browser/view/renderers/cellWidgets.ts | 16 ++++++ .../browser/view/renderers/codeCell.ts | 26 +++++---- .../browser/view/renderers/markdownCell.ts | 32 ++++++++++- .../browser/viewModel/baseCellViewModel.ts | 4 +- .../browser/viewModel/notebookViewModel.ts | 4 ++ .../contrib/notebook/common/notebookCommon.ts | 16 +++++- .../notebook/test/notebookViewModel.test.ts | 6 +-- 13 files changed, 179 insertions(+), 40 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index e742bffc887..145598c4474 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -21,9 +21,9 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { CATEGORIES } from 'vs/workbench/common/actions'; -import { BaseCellRenderTemplate, CellEditState, CellFocusMode, EXPAND_CELL_CONTENT_COMMAND_ID, ICellViewModel, INotebookEditor, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { BaseCellRenderTemplate, CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, EXPAND_CELL_CONTENT_COMMAND_ID, ICellViewModel, INotebookEditor, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { CellEditType, CellKind, ICellEditOperation, ICellRange, isDocumentExcludePattern, NotebookCellMetadata, NotebookCellRunState, NOTEBOOK_EDITOR_CURSOR_BEGIN_END, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellKind, ICellEditOperation, ICellRange, isDocumentExcludePattern, NotebookCellMetadata, NotebookCellRunState, NotebookDocumentMetadata, NOTEBOOK_EDITOR_CURSOR_BEGIN_END, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -37,6 +37,7 @@ const NOTEBOOK_FOCUS_PREVIOUS_EDITOR = 'notebook.focusPreviousEditor'; const NOTEBOOK_FOCUS_NEXT_EDITOR = 'notebook.focusNextEditor'; const CLEAR_ALL_CELLS_OUTPUTS_COMMAND_ID = 'notebook.clearAllCellsOutputs'; const RENDER_ALL_MARKDOWN_CELLS = 'notebook.renderAllMarkdownCells'; +const TRUST_NOTEBOOK = 'notebook.trust'; // Cell Commands const INSERT_CODE_CELL_ABOVE_COMMAND_ID = 'notebook.cell.insertCodeCellAbove'; @@ -64,7 +65,6 @@ const SPLIT_CELL_COMMAND_ID = 'notebook.cell.split'; const JOIN_CELL_ABOVE_COMMAND_ID = 'notebook.cell.joinAbove'; const JOIN_CELL_BELOW_COMMAND_ID = 'notebook.cell.joinBelow'; -const EXECUTE_CELL_COMMAND_ID = 'notebook.cell.execute'; const CANCEL_CELL_COMMAND_ID = 'notebook.cell.cancelExecution'; const EXECUTE_CELL_SELECT_BELOW = 'notebook.cell.executeAndSelectBelow'; const EXECUTE_CELL_INSERT_BELOW = 'notebook.cell.executeAndInsertBelow'; @@ -1824,6 +1824,34 @@ registerAction2(class extends ChangeNotebookCellMetadataAction { } }); +abstract class ChangeNotebookMetadataAction extends NotebookCellAction { + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + const textModel = context.notebookEditor.viewModel?.notebookDocument; + if (!textModel) { + return; + } + + textModel.applyEdits(textModel.versionId, [{ editType: CellEditType.DocumentMetadata, metadata: { ...textModel.metadata, ...this.getMetadataDelta() } }], true, undefined, () => undefined, undefined); + } + + abstract getMetadataDelta(): Partial; +} + +registerAction2(class extends ChangeNotebookMetadataAction { + constructor() { + super({ + id: TRUST_NOTEBOOK, + title: localize('notebook.trust', "Trust Notebook"), + }); + } + + getMetadataDelta(): Partial { + return { trusted: true }; + } +}); + + + registerAction2(class extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index d5bac93dd4c..5db3402f081 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -60,6 +60,7 @@ export const NOTEBOOK_CELL_OUTPUT_COLLAPSED = new RawContextKey('notebo // Shared commands export const EXPAND_CELL_CONTENT_COMMAND_ID = 'notebook.cell.expandCellContent'; +export const EXECUTE_CELL_COMMAND_ID = 'notebook.cell.execute'; // Kernels diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 57e49098cda..0e2883e8295 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -873,7 +873,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private _updateForMetadata(): void { const notebookMetadata = this.viewModel!.metadata; this._editorEditable?.set(!!notebookMetadata?.editable); - this._editorRunnable?.set(!!notebookMetadata?.runnable); + this._editorRunnable?.set(this.viewModel!.runnable); this._overflowContainer.classList.toggle('notebook-editor-editable', !!notebookMetadata?.editable); this.getDomNode().classList.toggle('notebook-editor-editable', !!notebookMetadata?.editable); @@ -1621,7 +1621,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } async executeNotebook(): Promise { - if (!this._notebookViewModel!.metadata.runnable) { + if (!this._notebookViewModel!.runnable) { return; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index dc6bf626860..8179b4ed84d 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -28,7 +28,7 @@ import { NotebookKernelProviderAssociationRegistry, NotebookViewTypesExtensionRe import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellKind, CellOutputKind, DisplayOrderKey, IDisplayOutput, INotebookDecorationRenderOptions, INotebookKernelInfo2, INotebookKernelProvider, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, notebookDocumentFilterMatch, NotebookEditorPriority, NOTEBOOK_DISPLAY_ORDER, RENDERER_NOT_AVAILABLE, sortMimeTypes } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellKind, CellOutputKind, DisplayOrderKey, IDisplayOutput, INotebookDecorationRenderOptions, INotebookKernelInfo2, INotebookKernelProvider, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, ITransformedDisplayOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, notebookDocumentFilterMatch, NotebookEditorPriority, NOTEBOOK_DISPLAY_ORDER, RENDERER_NOT_AVAILABLE, sortMimeTypes } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; import { NotebookEditorDescriptor, NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; @@ -732,10 +732,10 @@ export class NotebookService extends Disposable implements INotebookService, ICu getMimeTypeInfo(textModel: NotebookTextModel, output: ITransformedDisplayOutputDto): readonly IOrderedMimeType[] { // TODO@rebornix no string[] casting - return this._getOrderedMimeTypes(output, textModel.metadata.displayOrder as string[] ?? []); + return this._getOrderedMimeTypes(textModel, output, textModel.metadata.displayOrder as string[] ?? []); } - private _getOrderedMimeTypes(output: IDisplayOutput, documentDisplayOrder: string[]): IOrderedMimeType[] { + private _getOrderedMimeTypes(textModel: NotebookTextModel, output: IDisplayOutput, documentDisplayOrder: string[]): IOrderedMimeType[] { const mimeTypes = Object.keys(output.data); const coreDisplayOrder = this._displayOrder; const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], documentDisplayOrder, coreDisplayOrder?.defaultOrder || []); @@ -751,31 +751,36 @@ export class NotebookService extends Disposable implements INotebookService, ICu orderMimeTypes.push({ mimeType: mimeType, rendererId: handler.id, + isTrusted: textModel.metadata.trusted }); for (let i = 1; i < handlers.length; i++) { orderMimeTypes.push({ mimeType: mimeType, - rendererId: handlers[i].id + rendererId: handlers[i].id, + isTrusted: textModel.metadata.trusted }); } if (mimeTypeSupportedByCore(mimeType)) { orderMimeTypes.push({ mimeType: mimeType, - rendererId: BUILTIN_RENDERER_ID + rendererId: BUILTIN_RENDERER_ID, + isTrusted: mimeTypeIsAlwaysSecure(mimeType) || textModel.metadata.trusted }); } } else { if (mimeTypeSupportedByCore(mimeType)) { orderMimeTypes.push({ mimeType: mimeType, - rendererId: BUILTIN_RENDERER_ID + rendererId: BUILTIN_RENDERER_ID, + isTrusted: mimeTypeIsAlwaysSecure(mimeType) || textModel.metadata.trusted }); } else { orderMimeTypes.push({ mimeType: mimeType, - rendererId: RENDERER_NOT_AVAILABLE + rendererId: RENDERER_NOT_AVAILABLE, + isTrusted: textModel.metadata.trusted }); } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts index 4e2cc304e32..fc6f6a9aeec 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts @@ -63,7 +63,8 @@ export class OutputElement extends Disposable { const mimeTypes = this.notebookService.getMimeTypeInfo(this.notebookEditor.textModel!, this.output); - const pick = this.pickedMimeTypes.get(this.output) ?? Math.max(mimeTypes.findIndex(mimeType => mimeType.rendererId !== RENDERER_NOT_AVAILABLE), 0); + // there is at least one mimetype which is safe and can be rendered by the core + const pick = this.pickedMimeTypes.get(this.output) ?? Math.max(mimeTypes.findIndex(mimeType => mimeType.rendererId !== RENDERER_NOT_AVAILABLE && mimeType.isTrusted), 0); if (mimeTypes.length > 1) { outputItemDiv.style.position = 'relative'; @@ -104,6 +105,8 @@ export class OutputElement extends Disposable { } else { result = this.notebookEditor.getOutputRenderer().render(this.output, innerContainer, pickedMimeTypeRenderer.mimeType, this.getNotebookUri(),); } + + this.pickedMimeTypes.set(this.output, pick); } else { // for text and error, there is no mimetype const innerContainer = DOM.$('.output-inner-container'); @@ -174,10 +177,10 @@ export class OutputElement extends Disposable { async pickActiveMimeTypeRenderer(output: ITransformedDisplayOutputDto) { const mimeTypes = this.notebookService.getMimeTypeInfo(this.notebookEditor.textModel!, output); - const currIndex = this.pickedMimeTypes.get(output) ?? 0; + const currIndex = this.pickedMimeTypes.get(output); // const currIndex = output.pickedMimeTypeIndex; - const items = mimeTypes.map((mimeType, index): IMimeTypeRenderer => ({ + const items = mimeTypes.filter(mimeType => mimeType.isTrusted).map((mimeType, index): IMimeTypeRenderer => ({ label: mimeType.mimeType, id: mimeType.mimeType, index: index, @@ -189,7 +192,9 @@ export class OutputElement extends Disposable { const picker = this.quickInputService.createQuickPick(); picker.items = items; picker.activeItems = items.filter(item => !!item.picked); - picker.placeholder = nls.localize('promptChooseMimeType.placeHolder', "Select output mimetype to render for current output"); + picker.placeholder = items.length !== mimeTypes.length + ? nls.localize('promptChooseMimeTypeInSecure.placeHolder', "Select mimetype to render for current output. Rich mimetypes are available only when the notebook is trusted") + : nls.localize('promptChooseMimeType.placeHolder', "Select mimetype to render for current output"); const pick = await new Promise(resolve => { picker.onDidAccept(() => { @@ -284,7 +289,7 @@ export class OutputContainer extends Disposable { this.templateData.outputContainer!.style.display = 'block'; // there are outputs, we need to calcualte their sizes and trigger relayout // @TODO@rebornix, if there is no resizable output, we should not check their height individually, which hurts the performance - const outputsToRender = this.viewCell.outputs.slice(0, Math.min(OUTPUT_COUNT_LIMIT, this.viewCell.outputs.length)); + const outputsToRender = this._calcuateOutputsToRender(); for (let index = 0; index < outputsToRender.length; index++) { const currOutput = this.viewCell.outputs[index]; @@ -356,6 +361,43 @@ export class OutputContainer extends Disposable { }); } + private _calcuateOutputsToRender() { + const outputs = this.viewCell.outputs.slice(0, Math.min(OUTPUT_COUNT_LIMIT, this.viewCell.outputs.length)); + if (!this.notebookEditor.viewModel!.metadata.trusted) { + // not trusted + const secureOutput = outputs.filter(output => { + switch (output.outputKind) { + case CellOutputKind.Text: + return true; + case CellOutputKind.Error: + return true; + case CellOutputKind.Rich: + { + const mimeTypes = []; + for (const property in output.data) { + mimeTypes.push(property); + } + + if (mimeTypes.indexOf('text/plain') >= 0 + || mimeTypes.indexOf('text/markdown') >= 0 + || mimeTypes.indexOf('application/json') >= 0 + || mimeTypes.includes('image/png')) { + return true; + } + + return false; + } + default: + return false; + } + }); + + return secureOutput; + } + + return outputs; + } + private _updateOutputs(splices: NotebookCellOutputsSplice[]) { if (!splices.length) { return; @@ -393,7 +435,7 @@ export class OutputContainer extends Disposable { }); let prevElement: HTMLElement | undefined = undefined; - const outputsToRender = this.viewCell.outputs.slice(0, Math.min(OUTPUT_COUNT_LIMIT, this.viewCell.outputs.length)); + const outputsToRender = this._calcuateOutputsToRender(); outputsToRender.reverse().forEach(output => { if (this.outputEntries.has(output)) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 3164a4d1d09..f7e1fd60fa9 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -414,6 +414,9 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR const betweenCellToolbar = disposables.add(this.createBetweenCellToolbar(bottomCellContainer, disposables, contextKeyService)); const statusBar = disposables.add(this.instantiationService.createInstance(CellEditorStatusBar, editorPart)); + DOM.hide(statusBar.durationContainer); + DOM.hide(statusBar.cellRunStatusContainer); + const titleMenu = disposables.add(this.cellMenus.getCellTitleMenu(contextKeyService)); const templateData: MarkdownCellRenderTemplate = { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets.ts index 174561c01fb..b8c2a1172b9 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets.ts @@ -23,6 +23,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ChangeCellLanguageAction, INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; import { ICellViewModel, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { BaseCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel'; import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; import { CellKind, CellStatusbarAlignment, INotebookCellStatusBarEntry } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -339,3 +340,18 @@ export function getResizesObserver(referenceDomElement: HTMLElement | null, dime return new ElementSizeObserver(referenceDomElement, dimension, changeCallback); } } + +export function getExecuteCellPlaceholder(viewCell: BaseCellViewModel) { + return { + alignment: CellStatusbarAlignment.LEFT, + priority: -1, + cellResource: viewCell.uri, + command: undefined, + // text: `${keybinding?.getLabel() || 'Ctrl + Enter'} to run`, + // tooltip: `${keybinding?.getLabel() || 'Ctrl + Enter'} to run`, + text: 'Ctrl + Enter to run', + tooltip: 'Ctrl + Enter to run', + visible: true, + opacity: '0.7' + }; +} diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts index 1c2016566ba..c1350eeabb0 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts @@ -16,10 +16,9 @@ import { CellFocusMode, CodeCellRenderTemplate, INotebookEditor } from 'vs/workb import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { ClickTargetType } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; +import { ClickTargetType, getExecuteCellPlaceholder } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; import { OutputContainer } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellOutput'; import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; -import { CellStatusbarAlignment } from 'vs/workbench/contrib/notebook/common/notebookCommon'; export class CodeCell extends Disposable { @@ -35,7 +34,9 @@ export class CodeCell extends Disposable { @INotebookCellStatusBarService readonly notebookCellStatusBarService: INotebookCellStatusBarService, @IOpenerService readonly openerService: IOpenerService, @ITextFileService readonly textFileService: ITextFileService, - @IModeService private readonly _modeService: IModeService + @IModeService private readonly _modeService: IModeService, + // @IKeybindingService private readonly _keybindingService: IKeybindingService, + ) { super(); @@ -218,19 +219,14 @@ export class CodeCell extends Disposable { updateForCollapseState(); const updatePlaceholder = () => { - if (this.notebookEditor.getActiveCell() === this.viewCell && viewCell.metadata.runState === undefined && viewCell.metadata.lastRunDuration === undefined) { + if (this.notebookEditor.getActiveCell() === this.viewCell + && viewCell.metadata.runState === undefined + && viewCell.metadata.lastRunDuration === undefined + ) { // active cell and no run status if (this._activeCellRunPlaceholder === null) { - this._activeCellRunPlaceholder = this.notebookCellStatusBarService.addEntry({ - alignment: CellStatusbarAlignment.LEFT, - priority: -1, - cellResource: viewCell.uri, - command: undefined, - text: 'Ctrl + Enter to run', - tooltip: 'Ctrl + Enter to run', - visible: true, - opacity: '0.7' - }); + // const keybinding = this._keybindingService.lookupKeybinding(EXECUTE_CELL_COMMAND_ID); + this._activeCellRunPlaceholder = this.notebookCellStatusBarService.addEntry(getExecuteCellPlaceholder(this.viewCell)); } return; @@ -247,6 +243,8 @@ export class CodeCell extends Disposable { this._register(this.viewCell.model.onDidChangeMetadata(() => { updatePlaceholder(); })); + + updatePlaceholder(); } private viewUpdate(): void { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts index 7c55a34abc2..94d2589aa83 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts @@ -7,7 +7,7 @@ import * as DOM from 'vs/base/browser/dom'; import { raceCancellation } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { renderCodicons } from 'vs/base/browser/codicons'; -import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -19,7 +19,8 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; +import { getExecuteCellPlaceholder, getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; +import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; export class StatefulMarkdownCell extends Disposable { @@ -29,6 +30,7 @@ export class StatefulMarkdownCell extends Disposable { private localDisposables = new DisposableStore(); private foldingState: CellFoldingState; + private _activeCellRunPlaceholder: IDisposable | null = null; constructor( private readonly notebookEditor: INotebookEditor, @@ -37,6 +39,7 @@ export class StatefulMarkdownCell extends Disposable { private editorOptions: IEditorOptions, private readonly renderedEditors: Map, @IContextKeyService private readonly contextKeyService: IContextKeyService, + @INotebookCellStatusBarService readonly notebookCellStatusBarService: INotebookCellStatusBarService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(); @@ -129,6 +132,31 @@ export class StatefulMarkdownCell extends Disposable { }); this.viewUpdate(); + + const updatePlaceholder = () => { + if (this.notebookEditor.getActiveCell() === this.viewCell) { + // active cell and no run status + if (this._activeCellRunPlaceholder === null) { + // const keybinding = this._keybindingService.lookupKeybinding(EXECUTE_CELL_COMMAND_ID); + this._activeCellRunPlaceholder = this.notebookCellStatusBarService.addEntry(getExecuteCellPlaceholder(this.viewCell)); + } + + return; + } + + this._activeCellRunPlaceholder?.dispose(); + this._activeCellRunPlaceholder = null; + }; + + this._register(this.notebookEditor.onDidChangeActiveCell(() => { + updatePlaceholder(); + })); + + this._register(this.viewCell.model.onDidChangeMetadata(() => { + updatePlaceholder(); + })); + + updatePlaceholder(); } private viewUpdate(): void { diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index 8a8b33bf03c..d49dc929cb0 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -453,8 +453,8 @@ export abstract class BaseCellViewModel extends Disposable { const editable = this.metadata?.editable ?? documentMetadata.cellEditable; - const runnable = this.metadata?.runnable ?? - documentMetadata.cellRunnable; + const runnable = (this.metadata?.runnable ?? + documentMetadata.cellRunnable) && !!documentMetadata.trusted; const hasExecutionOrder = this.metadata?.hasExecutionOrder ?? documentMetadata.cellHasExecutionOrder; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index 4ea7ba52c4f..86a16531394 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -180,6 +180,10 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD return this._notebook.metadata; } + get runnable() { + return !!this._notebook.metadata?.runnable && !!this._notebook.metadata?.trusted; + } + private readonly _onDidChangeViewCells = this._register(new Emitter()); get onDidChangeViewCells(): Event { return this._onDidChangeViewCells.event; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 58e0f94a031..dcab1f736e6 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -86,7 +86,7 @@ export interface NotebookDocumentMetadata { displayOrder?: (string | glob.IRelativePattern)[]; custom?: { [key: string]: unknown }; runState?: NotebookRunState; - trusted?: boolean; + trusted: boolean; } export enum NotebookCellRunState { @@ -185,6 +185,7 @@ export enum MimeTypeRendererResolver { export interface IOrderedMimeType { mimeType: string; rendererId: string; + isTrusted: boolean; } export interface ITransformedDisplayOutputDto extends IDisplayOutput { @@ -538,6 +539,19 @@ export namespace CellUri { } } +export function mimeTypeIsAlwaysSecure(mimeType: string) { + if ([ + 'application/json', + 'text/markdown', + 'image/png', + 'text/plain' + ].indexOf(mimeType) > -1) { + return true; + } + + return false; +} + export function mimeTypeSupportedByCore(mimeType: string) { if ([ 'application/json', diff --git a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts index b8d970e6e7f..1ec883fe144 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts @@ -156,7 +156,7 @@ suite('NotebookViewModel', () => { ['var e = 5;', 'javascript', CellKind.Code, [], { editable: false, runnable: false }], ], (editor, viewModel) => { - viewModel.notebookDocument.metadata = { editable: true, runnable: true, cellRunnable: true, cellEditable: true, cellHasExecutionOrder: true }; + viewModel.notebookDocument.metadata = { editable: true, runnable: true, cellRunnable: true, cellEditable: true, cellHasExecutionOrder: true, trusted: true }; const defaults = { hasExecutionOrder: true }; @@ -190,7 +190,7 @@ suite('NotebookViewModel', () => { ...defaults }); - viewModel.notebookDocument.metadata = { editable: true, runnable: true, cellRunnable: false, cellEditable: true, cellHasExecutionOrder: true }; + viewModel.notebookDocument.metadata = { editable: true, runnable: true, cellRunnable: false, cellEditable: true, cellHasExecutionOrder: true, trusted: true }; assert.deepEqual(viewModel.viewCells[0].getEvaluatedMetadata(viewModel.metadata), { editable: true, @@ -222,7 +222,7 @@ suite('NotebookViewModel', () => { ...defaults }); - viewModel.notebookDocument.metadata = { editable: true, runnable: true, cellRunnable: false, cellEditable: false, cellHasExecutionOrder: true }; + viewModel.notebookDocument.metadata = { editable: true, runnable: true, cellRunnable: false, cellEditable: false, cellHasExecutionOrder: true, trusted: true }; assert.deepEqual(viewModel.viewCells[0].getEvaluatedMetadata(viewModel.metadata), { editable: false, -- GitLab