From 370a180f408d851b7ae5530d958b50e421eb6d57 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Thu, 21 Mar 2024 11:38:55 -0400 Subject: [PATCH 01/58] Add info about cell type to the PositronNotebookCell class --- .../positronNotebook/browser/PositronNotebookCell.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts index d1737ad3c81..398c4c71946 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { ITextModel } from 'vs/editor/common/model'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { ICellOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, ICellOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IPositronNotebookInstance } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance'; @@ -18,6 +18,7 @@ type ExecutionStatus = 'running' | 'pending' | 'unconfirmed' | 'idle'; export class PositronNotebookCell extends Disposable implements IPositronNotebookCell { executionStatus: ISettableObservable; outputs: ISettableObservable; + kind: CellKind; constructor( public viewModel: NotebookCellTextModel, @@ -28,6 +29,7 @@ export class PositronNotebookCell extends Disposable implements IPositronNoteboo this.executionStatus = observableValue('cellExecutionStatus', 'idle'); this.outputs = observableValue('cellOutputs', this.viewModel.outputs); + this.kind = viewModel.cellKind; // Listen for changes to the cell outputs and update the observable this._register( this.viewModel.onDidChangeOutputs(() => { @@ -67,6 +69,11 @@ export class PositronNotebookCell extends Disposable implements IPositronNoteboo */ interface IPositronNotebookCell { + /** + * Is the cell a code or markdown cell? + */ + kind: CellKind; + /** * Cell specific uri for the cell within the notebook */ From 85ccfe57c3c2a0912602e8fb58633720d570bc3f Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Thu, 21 Mar 2024 12:58:31 -0400 Subject: [PATCH 02/58] Add support for plain "error" notebook messages --- .../contrib/positronNotebook/browser/getOutputContents.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/getOutputContents.ts b/src/vs/workbench/contrib/positronNotebook/browser/getOutputContents.ts index 70eee3ae203..7c0b0fe22e7 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/getOutputContents.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/getOutputContents.ts @@ -124,6 +124,11 @@ export function parseOutputData(output: ICellOutput['outputs'][number]): ParsedO if (parsedMessage?.name === 'Runtime Error') { return { type: 'error', content: parsedMessage.message }; } + + if (mime === 'application/vnd.code.notebook.error') { + return { type: 'error', content: parsedMessage.message }; + } + } catch (e) { } From 360e71a6f88b296e6490e2ac3d619b18fc1ebfc5 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Thu, 21 Mar 2024 15:09:09 -0400 Subject: [PATCH 03/58] Restructure notebook cells to differentiate between markup and code cells. --- .../browser/PositronNotebookCell.ts | 31 +++++++-- .../browser/PositronNotebookComponent.tsx | 2 +- .../browser/PositronNotebookInstance.ts | 13 +--- .../CellEditorMonacoWidget.tsx} | 17 ++++- .../NodebookCodeCell.tsx} | 66 ++++--------------- .../{ => notebookCells}/NotebookCell.css | 0 .../browser/notebookCells/NotebookCell.tsx | 55 ++++++++++++++++ .../notebookCells/NotebookMarkupCell.tsx | 10 +++ 8 files changed, 120 insertions(+), 74 deletions(-) rename src/vs/workbench/contrib/positronNotebook/browser/{useCellEditorWidget.tsx => notebookCells/CellEditorMonacoWidget.tsx} (89%) rename src/vs/workbench/contrib/positronNotebook/browser/{NotebookCell.tsx => notebookCells/NodebookCodeCell.tsx} (52%) rename src/vs/workbench/contrib/positronNotebook/browser/{ => notebookCells}/NotebookCell.css (100%) create mode 100644 src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx create mode 100644 src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts index 398c4c71946..c14a2873e31 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts @@ -18,7 +18,6 @@ type ExecutionStatus = 'running' | 'pending' | 'unconfirmed' | 'idle'; export class PositronNotebookCell extends Disposable implements IPositronNotebookCell { executionStatus: ISettableObservable; outputs: ISettableObservable; - kind: CellKind; constructor( public viewModel: NotebookCellTextModel, @@ -29,7 +28,6 @@ export class PositronNotebookCell extends Disposable implements IPositronNoteboo this.executionStatus = observableValue('cellExecutionStatus', 'idle'); this.outputs = observableValue('cellOutputs', this.viewModel.outputs); - this.kind = viewModel.cellKind; // Listen for changes to the cell outputs and update the observable this._register( this.viewModel.onDidChangeOutputs(() => { @@ -41,6 +39,10 @@ export class PositronNotebookCell extends Disposable implements IPositronNoteboo ); } + get kind(): CellKind { + return this.viewModel.cellKind; + } + get uri(): URI { return this.viewModel.uri; } @@ -61,18 +63,17 @@ export class PositronNotebookCell extends Disposable implements IPositronNoteboo const modelRef = await this.textModelResolverService.createModelReference(this.uri); return modelRef.object.textEditorModel; } - } /** * Wrapper class for notebook cell that exposes the properties that the UI needs to render the cell. */ -interface IPositronNotebookCell { +export interface IPositronNotebookCell { /** - * Is the cell a code or markdown cell? + * Is the cell a code or markup cell? */ - kind: CellKind; + get kind(): CellKind; /** * Cell specific uri for the cell within the notebook @@ -114,3 +115,21 @@ interface IPositronNotebookCell { */ delete(): void; } + +export interface IPositronNotebookCodeCell extends IPositronNotebookCell { + kind: CellKind.Code; +} + +export function isCodeCell(cell: IPositronNotebookCell): cell is IPositronNotebookCodeCell { + return cell.kind === CellKind.Code; +} + +export interface IPositronNotebookMarkupCell extends IPositronNotebookCell { + kind: CellKind.Markup; +} + +export function isMarkupCell(cell: IPositronNotebookCell): cell is IPositronNotebookMarkupCell { + return cell.kind === CellKind.Markup; +} + + diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.tsx b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.tsx index 5158ac61d78..d5ddce1371a 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.tsx @@ -6,7 +6,7 @@ import 'vs/css!./PositronNotebookComponent'; import * as React from 'react'; import { useNotebookInstance } from 'vs/workbench/contrib/positronNotebook/browser/NotebookInstanceProvider'; -import { NotebookCell } from './NotebookCell'; +import { NotebookCell } from './notebookCells/NotebookCell'; import { AddCellButton } from './AddCellButton'; import { useObservedValue } from './useObservedValue'; import { localize } from 'vs/nls'; diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts index 7d8cd058d8e..e5725c6786f 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts @@ -89,7 +89,7 @@ export interface IPositronNotebookInstance { /** * Add a new cell of a given type to the notebook at the requested index */ - addCell(type: keyof typeof PositronNotebookInstance.cellTypeToKind, index: number): void; + addCell(type: CellKind, index: number): void; /** * Delete a cell from the notebook @@ -113,13 +113,6 @@ export interface IPositronNotebookInstance { export class PositronNotebookInstance extends Disposable implements IPositronNotebookInstance { - /** - * Map from string of cell kind to the integer enum used internally. - */ - static cellTypeToKind = { - 'code': CellKind.Code, - 'markdown': CellKind.Markup, - }; /** * Value to keep track of what instance number. @@ -385,7 +378,7 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot } } - addCell(type: 'code' | 'markdown', index: number): void { + addCell(type: CellKind, index: number): void { if (!this._viewModel) { throw new Error(localize('noViewModel', "No view model for notebook")); } @@ -400,7 +393,7 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot index, '', this.language, - PositronNotebookInstance.cellTypeToKind[type], + type, undefined, [], synchronous, diff --git a/src/vs/workbench/contrib/positronNotebook/browser/useCellEditorWidget.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx similarity index 89% rename from src/vs/workbench/contrib/positronNotebook/browser/useCellEditorWidget.tsx rename to src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx index 0f54ce37462..435d34dc0fe 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/useCellEditorWidget.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx @@ -9,16 +9,29 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { CellEditorOptions } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions'; import { useNotebookInstance } from 'vs/workbench/contrib/positronNotebook/browser/NotebookInstanceProvider'; -import { PositronNotebookCell } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell'; +import { IPositronNotebookCell } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell'; import { useServices } from 'vs/workbench/contrib/positronNotebook/browser/ServicesProvider'; import { observeValue } from 'vs/workbench/contrib/positronNotebook/common/utils/observeValue'; + +/** + * + * @param opts.cell Cell to be shown and edited in the editor widget + * @returns An editor widget for the cell + */ +export function CellEditorMonacoWidget({ cell }: { cell: IPositronNotebookCell }) { + const { editorPartRef } = useCellEditorWidget(cell); + + return
; +} + + /** * Create a cell editor widget for a cell. * @param cell Cell whose editor is to be created * @returns Refs to place the editor and the wrapping div */ -export function useCellEditorWidget({ cell }: { cell: PositronNotebookCell }) { +export function useCellEditorWidget(cell: IPositronNotebookCell) { const services = useServices(); const instance = useNotebookInstance(); diff --git a/src/vs/workbench/contrib/positronNotebook/browser/NotebookCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NodebookCodeCell.tsx similarity index 52% rename from src/vs/workbench/contrib/positronNotebook/browser/NotebookCell.tsx rename to src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NodebookCodeCell.tsx index 5a22acc30f8..d2857615843 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/NotebookCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NodebookCodeCell.tsx @@ -1,68 +1,29 @@ /*--------------------------------------------------------------------------------------------- * Copyright (C) 2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./NotebookCell'; import * as React from 'react'; import { VSBuffer } from 'vs/base/common/buffer'; import { NotebookCellOutputTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel'; import { ICellOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { PositronNotebookCell } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell'; +import { IPositronNotebookCodeCell } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell'; import { parseOutputData } from 'vs/workbench/contrib/positronNotebook/browser/getOutputContents'; import { useObservedValue } from 'vs/workbench/contrib/positronNotebook/browser/useObservedValue'; -import { useCellEditorWidget } from './useCellEditorWidget'; +import { CellEditorMonacoWidget } from './CellEditorMonacoWidget'; import { localize } from 'vs/nls'; -import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; -/** - * Logic for running a cell and handling its output. - * @param opts.cell The `PositronNotebookCell` to render - */ -export function NotebookCell(opts: { - cell: PositronNotebookCell; -}) { - const { editorPartRef } = useCellEditorWidget(opts); +export function NodebookCodeCell({ cell }: { cell: IPositronNotebookCodeCell }) { + const outputContents = useObservedValue(cell.outputs); - const executionStatus = useObservedValue(opts.cell.executionStatus); - const outputContents = useObservedValue(opts.cell.outputs); - - const isRunning = executionStatus === 'running'; - return ( -
-
- - -
-
-
-
-
- { - outputContents?.map((output) => - ) - } -
-
-
- ); + return <> + +
+ {outputContents?.map((output) => )} +
+ ; } - - function NotebookCellOutput({ cellOutput }: { cellOutput: ICellOutput }) { const { outputs } = cellOutput; @@ -71,9 +32,7 @@ function NotebookCellOutput({ cellOutput }: { cellOutput: ICellOutput }) { if (cellOutput instanceof NotebookCellOutputTextModel) { return <> - { - outputs.map(({ data, mime }, i) => ) - } + {outputs.map(({ data, mime }, i) => )} ; } @@ -83,8 +42,6 @@ function NotebookCellOutput({ cellOutput }: { cellOutput: ICellOutput }) { } - - function CellOutputContents(output: { data: VSBuffer; mime: string }) { const parsed = parseOutputData(output); @@ -110,4 +67,3 @@ function CellOutputContents(output: { data: VSBuffer; mime: string }) { } } - diff --git a/src/vs/workbench/contrib/positronNotebook/browser/NotebookCell.css b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.css similarity index 100% rename from src/vs/workbench/contrib/positronNotebook/browser/NotebookCell.css rename to src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.css diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx new file mode 100644 index 00000000000..610f96392f0 --- /dev/null +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./NotebookCell'; + +import * as React from 'react'; +import { IPositronNotebookCodeCell, IPositronNotebookMarkupCell, isCodeCell } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell'; +import { useObservedValue } from 'vs/workbench/contrib/positronNotebook/browser/useObservedValue'; +import { localize } from 'vs/nls'; +import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; +import { NodebookCodeCell } from './NodebookCodeCell'; +import { NotebookMarkupCell } from './NotebookMarkupCell'; + +/** + * Logic for running a cell and handling its output. + * @param opts.cell The `PositronNotebookCell` to render + */ +export function NotebookCell({ cell }: { + cell: IPositronNotebookCodeCell | IPositronNotebookMarkupCell; +}) { + const executionStatus = useObservedValue(cell.executionStatus); + const isRunning = executionStatus === 'running'; + + return ( +
+
+ + +
+
+ { + isCodeCell(cell) ? + : + + } +
+
+ ); +} + + + diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx new file mode 100644 index 00000000000..c699f63b114 --- /dev/null +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import * as React from 'react'; +import { IPositronNotebookMarkupCell } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell'; + +export function NotebookMarkupCell({ cell }: { cell: IPositronNotebookMarkupCell }) { + return
{cell.getContent()}
; +} From 0a94da3c3759e5ca4f6f066a0527fc89fdc61395 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Thu, 21 Mar 2024 15:30:50 -0400 Subject: [PATCH 04/58] Distinguish between view and text model for cells better. --- .../browser/AddCellButton.tsx | 3 +- .../browser/PositronNotebookCell.ts | 42 +++++++++++++++---- .../browser/PositronNotebookComponent.tsx | 2 +- .../browser/PositronNotebookInstance.ts | 12 ++++-- .../notebookCells/CellEditorMonacoWidget.tsx | 2 +- 5 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/AddCellButton.tsx b/src/vs/workbench/contrib/positronNotebook/browser/AddCellButton.tsx index d1d79182a03..266b0895591 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/AddCellButton.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/AddCellButton.tsx @@ -7,6 +7,7 @@ import * as React from 'react'; import { useNotebookInstance } from 'vs/workbench/contrib/positronNotebook/browser/NotebookInstanceProvider'; import { localize } from 'vs/nls'; import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; export function AddCellButton({ index }: { index: number }) { const notebookInstance = useNotebookInstance(); @@ -16,7 +17,7 @@ export function AddCellButton({ index }: { index: number }) { className='action action-button' ariaLabel={localize('addCell', 'Add cell')} onPressed={() => { - notebookInstance.addCell('code', index); + notebookInstance.addCell(CellKind.Code, index); }} > Add Cell diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts index c14a2873e31..79c5d74f4a9 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts @@ -7,6 +7,7 @@ import { ISettableObservable, observableValue } from 'vs/base/common/observableI import { URI } from 'vs/base/common/uri'; import { ITextModel } from 'vs/editor/common/model'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { CellKind, ICellOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IPositronNotebookInstance } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance'; @@ -20,35 +21,53 @@ export class PositronNotebookCell extends Disposable implements IPositronNoteboo outputs: ISettableObservable; constructor( - public viewModel: NotebookCellTextModel, + public cellModel: NotebookCellTextModel, private _instance: IPositronNotebookInstance, @ITextModelService private readonly textModelResolverService: ITextModelService, ) { super(); this.executionStatus = observableValue('cellExecutionStatus', 'idle'); - this.outputs = observableValue('cellOutputs', this.viewModel.outputs); + this.outputs = observableValue('cellOutputs', this.cellModel.outputs); // Listen for changes to the cell outputs and update the observable this._register( - this.viewModel.onDidChangeOutputs(() => { + this.cellModel.onDidChangeOutputs(() => { // By unpacking the array and repacking we make sure that // the React component will rerender when the outputs change. Probably not // great to have this leak here. - this.outputs.set([...this.viewModel.outputs], undefined); + this.outputs.set([...this.cellModel.outputs], undefined); }) ); } get kind(): CellKind { - return this.viewModel.cellKind; + return this.cellModel.cellKind; } get uri(): URI { - return this.viewModel.uri; + return this.cellModel.uri; + } + + get viewModel(): ICellViewModel { + + const notebookViewModel = this._instance.viewModel; + if (!notebookViewModel) { + throw new Error('Notebook view model not found'); + } + + const viewCells = notebookViewModel.viewCells; + + const cell = viewCells.find(cell => cell.uri.toString() === this.cellModel.uri.toString()); + + if (cell) { + return cell; + } + + throw new Error('Cell view model not found'); } getContent(): string { - return this.viewModel.getValue(); + return this.cellModel.getValue(); } run(): void { @@ -86,9 +105,14 @@ export interface IPositronNotebookCell { getContent(): string; /** - * The view model for the cell. + * The notebook text model for the cell. + */ + cellModel: NotebookCellTextModel; + + /** + * Get the view model for the cell */ - viewModel: NotebookCellTextModel; + get viewModel(): ICellViewModel; /** * Get the text editor model for use in the monaco editor widgets diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.tsx b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.tsx index d5ddce1371a..124e59c5cd9 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.tsx @@ -38,7 +38,7 @@ export function PositronNotebookComponent() {
{notebookCells?.length ? notebookCells?.map((cell, index) => <> - + ) :
{localize('noCells', 'No cells')}
} diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts index e5725c6786f..27ce56c796f 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts @@ -103,6 +103,8 @@ export interface IPositronNotebookInstance { */ attachView(viewModel: NotebookViewModel, viewState?: INotebookEditorViewState): void; + readonly viewModel: NotebookViewModel | undefined; + /** * Method called when the instance is detached from a view. This is used to cleanup * all the logic and variables related to the view/DOM. @@ -174,6 +176,10 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot return this._input.resource; } + get viewModel(): NotebookViewModel | undefined { + return this._viewModel; + } + /** * Internal event emitter for when the editor's options change. @@ -368,11 +374,11 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot const hasExecutions = [...cells].some(cell => Boolean(this.notebookExecutionStateService.getCellExecution(cell.uri))); if (hasExecutions) { - this.notebookExecutionService.cancelNotebookCells(this._textModel, Array.from(cells).map(c => c.viewModel)); + this.notebookExecutionService.cancelNotebookCells(this._textModel, Array.from(cells).map(c => c.cellModel)); return; } - await this.notebookExecutionService.executeNotebookCells(this._textModel, Array.from(cells).map(c => c.viewModel), this._contextKeyService); + await this.notebookExecutionService.executeNotebookCells(this._textModel, Array.from(cells).map(c => c.cellModel), this._contextKeyService); for (const cell of cells) { cell.executionStatus.set('idle', undefined); } @@ -410,7 +416,7 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot // TODO: Hook up readOnly to the notebook actual value const readOnly = false; const computeUndoRedo = !readOnly || textModel.viewType === 'interactive'; - const cellIndex = textModel.cells.indexOf(cell.viewModel); + const cellIndex = textModel.cells.indexOf(cell.cellModel); const edits: ICellReplaceEdit = { editType: CellEditType.Replace, index: cellIndex, count: 1, cells: [] diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx index 435d34dc0fe..c249a22cd4e 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx @@ -57,7 +57,7 @@ export function useCellEditorWidget(cell: IPositronNotebookCell) { const nativeContainer = DOM.$('.positron-monaco-editor-container'); editorPartRef.current.appendChild(nativeContainer); - const language = cell.viewModel.language; + const language = cell.cellModel.language; const editorContextKeyService = services.scopedContextKeyProviderCallback(editorPartRef.current); const editorInstaService = services.instantiationService.createChild(new ServiceCollection([IContextKeyService, editorContextKeyService])); const editorOptions = new CellEditorOptions(instance.getBaseCellEditorOptions(language), instance.notebookOptions, services.configurationService); From 76e7241b802f8fded9d1b6e0dc5d73dc5cff96b0 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Thu, 21 Mar 2024 16:23:31 -0400 Subject: [PATCH 05/58] Add support for rendering (to html string) the content of a markdown cell. --- package.json | 1 + src/bootstrap-window.js | 1 + .../browser/notebookCells/NotebookMarkupCell.tsx | 9 ++++++++- yarn.lock | 5 +++++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 59f2ba57e08..42cdcc83d0c 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,7 @@ "https-proxy-agent": "^7.0.2", "jschardet": "3.0.0", "kerberos": "^2.0.1", + "marked": "^12.0.1", "minimist": "^1.2.6", "native-is-elevated": "0.7.0", "native-keymap": "^3.3.4", diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index 5e45e58bb7d..f5fa40cd920 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -137,6 +137,7 @@ 'tas-client-umd': `${baseNodeModulesPath}/tas-client-umd/lib/tas-client-umd.js`, // --- Start Positron --- 'he': `${baseNodeModulesPath}/he/he.js`, + 'marked': `${baseNodeModulesPath}/marked/marked.min.js`, // ------------------------------------------------------------------------------------- // If you need to debug React, you can switch to the development version by temporarily // commenting out production React and uncommenting development React. (The fixup for diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx index c699f63b114..08fe4410f09 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx @@ -5,6 +5,13 @@ import * as React from 'react'; import { IPositronNotebookMarkupCell } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell'; +import { marked } from 'marked'; + + export function NotebookMarkupCell({ cell }: { cell: IPositronNotebookMarkupCell }) { - return
{cell.getContent()}
; + const htmlOfContent = marked(cell.getContent()) as string; + + return
+ {htmlOfContent} +
; } diff --git a/yarn.lock b/yarn.lock index 8c24ab96072..4ca5714fe7d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7025,6 +7025,11 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +marked@^12.0.1: + version "12.0.1" + resolved "https://registry.yarnpkg.com/marked/-/marked-12.0.1.tgz#8ab1eb15560c7cbe3b011074845d7ca6c4d392b0" + integrity sha512-Y1/V2yafOcOdWQCX0XpAKXzDakPOpn6U0YLxTJs3cww6VxOzZV1BTOOYWLvH3gX38cq+iLwljHHTnMtlDfg01Q== + matchdep@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/matchdep/-/matchdep-2.0.0.tgz#c6f34834a0d8dbc3b37c27ee8bbcb27c7775582e" From 304a73d051a472c85a289badbdfdea70a9896d03 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Thu, 21 Mar 2024 16:45:54 -0400 Subject: [PATCH 06/58] Add render support for markdown. --- .../browser/notebookCells/NotebookMarkupCell.css | 8 ++++++++ .../browser/notebookCells/NotebookMarkupCell.tsx | 16 +++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.css diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.css b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.css new file mode 100644 index 00000000000..0774c8192c9 --- /dev/null +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.css @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +.positron-notebook-markup-rendered { + padding-inline: 1rem; + padding-block: 0.5rem; +} diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx index 08fe4410f09..19c423e2330 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx @@ -1,17 +1,27 @@ /*--------------------------------------------------------------------------------------------- * Copyright (C) 2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./NotebookMarkupCell'; import * as React from 'react'; import { IPositronNotebookMarkupCell } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell'; import { marked } from 'marked'; +import { renderHtml } from 'vs/base/browser/renderHtml'; +import { CellEditorMonacoWidget } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget'; -export function NotebookMarkupCell({ cell }: { cell: IPositronNotebookMarkupCell }) { - const htmlOfContent = marked(cell.getContent()) as string; +export function NotebookMarkupCell({ cell }: { cell: IPositronNotebookMarkupCell }) { return
- {htmlOfContent} + +
+ +
; } + +function RenderMarkdown({ content }: { content: string }) { + const htmlOfContent = marked(content) as string; + return
{renderHtml(htmlOfContent)}
; +} From 8d4e8f233401eceaefc4fe87b79046e61786544d Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Fri, 22 Mar 2024 13:25:38 -0400 Subject: [PATCH 07/58] Create two distinct classes for code and markup cells and add an observable renderedHtml field for the markup cells. --- .../browser/PositronNotebookCell.ts | 89 +++++++++++++++---- .../browser/PositronNotebookInstance.ts | 26 +++--- .../notebookCells/CellEditorMonacoWidget.tsx | 6 +- .../browser/notebookCells/NotebookCell.tsx | 5 +- .../notebookCells/NotebookMarkupCell.tsx | 15 ++-- 5 files changed, 98 insertions(+), 43 deletions(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts index 79c5d74f4a9..a89609b39da 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts @@ -2,11 +2,15 @@ * Copyright (C) 2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; +// eslint-disable-next-line local/code-import-patterns +import { marked } from 'marked'; + +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ISettableObservable, observableValue } from 'vs/base/common/observableInternal/base'; import { URI } from 'vs/base/common/uri'; import { ITextModel } from 'vs/editor/common/model'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { CellKind, ICellOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -16,10 +20,13 @@ import { IPositronNotebookInstance } from 'vs/workbench/contrib/positronNotebook type ExecutionStatus = 'running' | 'pending' | 'unconfirmed' | 'idle'; -export class PositronNotebookCell extends Disposable implements IPositronNotebookCell { +class PositronNotebookCellGeneral extends Disposable implements IPositronNotebookGeneralCell { executionStatus: ISettableObservable; outputs: ISettableObservable; + // Not marked as private so we can access it in subclasses + _disposableStore = new DisposableStore(); + constructor( public cellModel: NotebookCellTextModel, private _instance: IPositronNotebookInstance, @@ -40,10 +47,6 @@ export class PositronNotebookCell extends Disposable implements IPositronNoteboo ); } - get kind(): CellKind { - return this.cellModel.cellKind; - } - get uri(): URI { return this.cellModel.uri; } @@ -82,17 +85,18 @@ export class PositronNotebookCell extends Disposable implements IPositronNoteboo const modelRef = await this.textModelResolverService.createModelReference(this.uri); return modelRef.object.textEditorModel; } + + override dispose(): void { + this._disposableStore.dispose(); + super.dispose(); + } } /** * Wrapper class for notebook cell that exposes the properties that the UI needs to render the cell. + * This interface is extended to provide the specific properties for code and markup cells. */ -export interface IPositronNotebookCell { - - /** - * Is the cell a code or markup cell? - */ - get kind(): CellKind; +export interface IPositronNotebookGeneralCell extends Disposable { /** * Cell specific uri for the cell within the notebook @@ -140,20 +144,69 @@ export interface IPositronNotebookCell { delete(): void; } -export interface IPositronNotebookCodeCell extends IPositronNotebookCell { +/** + * Cell that contains code that can be executed + */ +export interface IPositronNotebookCodeCell extends IPositronNotebookGeneralCell { kind: CellKind.Code; } -export function isCodeCell(cell: IPositronNotebookCell): cell is IPositronNotebookCodeCell { - return cell.kind === CellKind.Code; +class PositronNotebookCodeCell extends PositronNotebookCellGeneral implements IPositronNotebookCodeCell { + kind: CellKind.Code = CellKind.Code; } -export interface IPositronNotebookMarkupCell extends IPositronNotebookCell { + +/** + * Cell that contains markup content + */ +export interface IPositronNotebookMarkupCell extends IPositronNotebookGeneralCell { kind: CellKind.Markup; + renderedHtml: ISettableObservable; +} + +class PositronNotebookMarkupCell extends PositronNotebookCellGeneral implements IPositronNotebookMarkupCell { + + renderedHtml: ISettableObservable = observableValue('renderedHTML', undefined); + kind: CellKind.Markup = CellKind.Markup; + + constructor( + cellModel: NotebookCellTextModel, + instance: IPositronNotebookInstance, + textModelResolverService: ITextModelService, + ) { + super(cellModel, instance, textModelResolverService); + + // Render the markdown content and update the observable when the cell content changes + this._disposableStore.add(this.cellModel.onDidChangeContent(() => { + this._renderContent(); + })); + + this._renderContent(); + } + + private _renderContent(): void { + const renderedHtml = marked(this.getContent()); + if (typeof renderedHtml !== 'string') { + throw new Error('Notebooks do not support async markdown rendering yet.'); + } + this.renderedHtml.set(renderedHtml, undefined); + } } -export function isMarkupCell(cell: IPositronNotebookCell): cell is IPositronNotebookMarkupCell { - return cell.kind === CellKind.Markup; +/** + * Instantiate a notebook cell based on the cell's kind + * @param cell Text model for the cell + * @param instance The containing Positron notebook instance that this cell resides in. + * @param instantiationService The instantiation service to use to create the cell + * @returns The instantiated notebook cell of the correct type. + */ +export function createNotebookCell(cell: NotebookCellTextModel, instance: IPositronNotebookInstance, instantiationService: IInstantiationService): IPositronNotebookGeneralCell { + if (cell.cellKind === CellKind.Code) { + return instantiationService.createInstance(PositronNotebookCodeCell, cell, instance); + } else { + return instantiationService.createInstance(PositronNotebookMarkupCell, cell, instance); + } } + diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts index 27ce56c796f..409aff72330 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts @@ -20,7 +20,7 @@ import { CellEditType, CellKind, ICellReplaceEdit, SelectionStateType } from 'vs import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; -import { PositronNotebookCell } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell'; +import { IPositronNotebookGeneralCell, createNotebookCell } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell'; import { PositronNotebookEditorInput } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditorInput'; import { BaseCellEditorOptions } from './BaseCellEditorOptions'; import * as DOM from 'vs/base/browser/dom'; @@ -51,7 +51,7 @@ export interface IPositronNotebookInstance { /** * The cells that make up the notebook */ - cells: ISettableObservable; + cells: ISettableObservable; /** * Status of kernel for the notebook. @@ -61,7 +61,7 @@ export interface IPositronNotebookInstance { /** * The currently selected cells. Typically a single cell but can be multiple cells. */ - selectedCells: PositronNotebookCell[]; + selectedCells: IPositronNotebookGeneralCell[]; /** * Has the notebook instance been disposed? @@ -74,7 +74,7 @@ export interface IPositronNotebookInstance { * Run the given cells * @param cells The cells to run */ - runCells(cells: PositronNotebookCell[]): Promise; + runCells(cells: IPositronNotebookGeneralCell[]): Promise; /** * Run the selected cells @@ -94,7 +94,7 @@ export interface IPositronNotebookInstance { /** * Delete a cell from the notebook */ - deleteCell(cell: PositronNotebookCell): void; + deleteCell(cell: IPositronNotebookGeneralCell): void; /** * Attach a view model to this instance @@ -125,17 +125,17 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot private _identifier: string = `Positron Notebook | NotebookInstance(${PositronNotebookInstance.count++}) |`; - selectedCells: PositronNotebookCell[] = []; + selectedCells: IPositronNotebookGeneralCell[] = []; /** * Internal cells that we use to manage the state of the notebook */ - private _cells: PositronNotebookCell[] = []; + private _cells: IPositronNotebookGeneralCell[] = []; /** * User facing cells wrapped in an observerable for the UI to react to changes */ - cells: ISettableObservable; + cells: ISettableObservable; /** * Status of kernel for the notebook. @@ -245,7 +245,7 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot ) { super(); - this.cells = observableValue('positronNotebookCells', this._cells); + this.cells = observableValue('positronNotebookCells', this._cells); this.kernelStatus = observableValue('positronNotebookKernelStatus', KernelStatus.Uninitialized); this.isReadOnly = this.creationOptions?.isReadOnly ?? false; @@ -305,7 +305,7 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot this._cells.forEach(cell => cell.dispose()); // Update cells with new cells - this._cells = notebookModel.cells.map(cell => this._instantiationService.createInstance(PositronNotebookCell, cell, this)); + this._cells = notebookModel.cells.map(cell => createNotebookCell(cell, this, this._instantiationService)); this.language = notebookModel.cells[0].language; @@ -336,7 +336,7 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot } - async runCells(cells: PositronNotebookCell[]): Promise { + async runCells(cells: IPositronNotebookGeneralCell[]): Promise { if (!cells) { throw new Error(localize('noCells', "No cells to run")); @@ -357,7 +357,7 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot * @param cells Cells to run * @returns */ - private async _runCells(cells: PositronNotebookCell[]): Promise { + private async _runCells(cells: IPositronNotebookGeneralCell[]): Promise { this._logService.info(this._identifier, '_runCells'); @@ -407,7 +407,7 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot ); } - deleteCell(cell: PositronNotebookCell): void { + deleteCell(cell: IPositronNotebookGeneralCell): void { if (!this._textModel) { throw new Error(localize('noModelForDelete', "No model for notebook to delete cell from")); } diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx index c249a22cd4e..b760e3d4467 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx @@ -9,7 +9,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { CellEditorOptions } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions'; import { useNotebookInstance } from 'vs/workbench/contrib/positronNotebook/browser/NotebookInstanceProvider'; -import { IPositronNotebookCell } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell'; +import { IPositronNotebookGeneralCell } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell'; import { useServices } from 'vs/workbench/contrib/positronNotebook/browser/ServicesProvider'; import { observeValue } from 'vs/workbench/contrib/positronNotebook/common/utils/observeValue'; @@ -19,7 +19,7 @@ import { observeValue } from 'vs/workbench/contrib/positronNotebook/common/utils * @param opts.cell Cell to be shown and edited in the editor widget * @returns An editor widget for the cell */ -export function CellEditorMonacoWidget({ cell }: { cell: IPositronNotebookCell }) { +export function CellEditorMonacoWidget({ cell }: { cell: IPositronNotebookGeneralCell }) { const { editorPartRef } = useCellEditorWidget(cell); return
; @@ -31,7 +31,7 @@ export function CellEditorMonacoWidget({ cell }: { cell: IPositronNotebookCell } * @param cell Cell whose editor is to be created * @returns Refs to place the editor and the wrapping div */ -export function useCellEditorWidget(cell: IPositronNotebookCell) { +export function useCellEditorWidget(cell: IPositronNotebookGeneralCell) { const services = useServices(); const instance = useNotebookInstance(); diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx index 610f96392f0..f3e7c4796f8 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx @@ -4,12 +4,13 @@ import 'vs/css!./NotebookCell'; import * as React from 'react'; -import { IPositronNotebookCodeCell, IPositronNotebookMarkupCell, isCodeCell } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell'; +import { IPositronNotebookCodeCell, IPositronNotebookMarkupCell } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell'; import { useObservedValue } from 'vs/workbench/contrib/positronNotebook/browser/useObservedValue'; import { localize } from 'vs/nls'; import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; import { NodebookCodeCell } from './NodebookCodeCell'; import { NotebookMarkupCell } from './NotebookMarkupCell'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; /** * Logic for running a cell and handling its output. @@ -42,7 +43,7 @@ export function NotebookCell({ cell }: {
{ - isCodeCell(cell) ? + cell.kind === CellKind.Code ? : } diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx index 19c423e2330..4ae412aa5d9 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx @@ -6,22 +6,23 @@ import 'vs/css!./NotebookMarkupCell'; import * as React from 'react'; import { IPositronNotebookMarkupCell } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell'; -import { marked } from 'marked'; import { renderHtml } from 'vs/base/browser/renderHtml'; import { CellEditorMonacoWidget } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget'; +import { useObservedValue } from 'vs/workbench/contrib/positronNotebook/browser/useObservedValue'; export function NotebookMarkupCell({ cell }: { cell: IPositronNotebookMarkupCell }) { + + const renderedHtml = useObservedValue(cell.renderedHtml); + return
- + { + renderedHtml ?
{renderHtml(renderedHtml)}
: null + + }
; } - -function RenderMarkdown({ content }: { content: string }) { - const htmlOfContent = marked(content) as string; - return
{renderHtml(htmlOfContent)}
; -} From f8984012c0e61345f689a994de425e25a4b83ad9 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Fri, 22 Mar 2024 14:48:52 -0400 Subject: [PATCH 08/58] Further separate code and markup cell models and rendering --- .../browser/PositronNotebookCell.ts | 125 +++++------------- .../browser/PositronNotebookComponent.tsx | 4 +- .../browser/PositronNotebookInstance.ts | 13 +- .../notebookCells/CellEditorMonacoWidget.tsx | 3 +- .../notebookCells/NodebookCodeCell.tsx | 21 ++- .../browser/notebookCells/NotebookCell.tsx | 52 +++----- .../notebookCells/NotebookCellSkeleton.tsx | 31 +++++ .../notebookCells/NotebookMarkupCell.tsx | 11 +- .../browser/notebookCells/interfaces.ts | 96 ++++++++++++++ 9 files changed, 211 insertions(+), 145 deletions(-) create mode 100644 src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSkeleton.tsx create mode 100644 src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts index a89609b39da..37072d5d96e 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts @@ -15,36 +15,21 @@ import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBr import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { CellKind, ICellOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IPositronNotebookInstance } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance'; +import { ExecutionStatus, IPositronNotebookCodeCell, IPositronNotebookGeneralCell, IPositronNotebookMarkupCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; -type ExecutionStatus = 'running' | 'pending' | 'unconfirmed' | 'idle'; - - -class PositronNotebookCellGeneral extends Disposable implements IPositronNotebookGeneralCell { - executionStatus: ISettableObservable; - outputs: ISettableObservable; +abstract class PositronNotebookCellGeneral extends Disposable implements IPositronNotebookGeneralCell { + kind!: CellKind; // Not marked as private so we can access it in subclasses _disposableStore = new DisposableStore(); constructor( public cellModel: NotebookCellTextModel, - private _instance: IPositronNotebookInstance, + public _instance: IPositronNotebookInstance, @ITextModelService private readonly textModelResolverService: ITextModelService, ) { super(); - this.executionStatus = observableValue('cellExecutionStatus', 'idle'); - this.outputs = observableValue('cellOutputs', this.cellModel.outputs); - - // Listen for changes to the cell outputs and update the observable - this._register( - this.cellModel.onDidChangeOutputs(() => { - // By unpacking the array and repacking we make sure that - // the React component will rerender when the outputs change. Probably not - // great to have this leak here. - this.outputs.set([...this.cellModel.outputs], undefined); - }) - ); } get uri(): URI { @@ -73,101 +58,61 @@ class PositronNotebookCellGeneral extends Disposable implements IPositronNoteboo return this.cellModel.getValue(); } - run(): void { - this._instance.runCells([this]); + async getTextEditorModel(): Promise { + const modelRef = await this.textModelResolverService.createModelReference(this.uri); + return modelRef.object.textEditorModel; } delete(): void { this._instance.deleteCell(this); } - async getTextEditorModel(): Promise { - const modelRef = await this.textModelResolverService.createModelReference(this.uri); - return modelRef.object.textEditorModel; - } - override dispose(): void { this._disposableStore.dispose(); super.dispose(); } } -/** - * Wrapper class for notebook cell that exposes the properties that the UI needs to render the cell. - * This interface is extended to provide the specific properties for code and markup cells. - */ -export interface IPositronNotebookGeneralCell extends Disposable { - - /** - * Cell specific uri for the cell within the notebook - */ - get uri(): URI; - - /** - * The content of the cell. This is the raw text of the cell. - */ - getContent(): string; - - /** - * The notebook text model for the cell. - */ - cellModel: NotebookCellTextModel; - - /** - * Get the view model for the cell - */ - get viewModel(): ICellViewModel; - - /** - * Get the text editor model for use in the monaco editor widgets - */ - getTextEditorModel(): Promise; - - /** - * Current execution status for this cell - */ - executionStatus: ISettableObservable; - /** - * Current cell outputs as an observable - */ +class PositronNotebookCodeCell extends PositronNotebookCellGeneral implements IPositronNotebookCodeCell { + override kind: CellKind.Code = CellKind.Code; + executionStatus: ISettableObservable; outputs: ISettableObservable; - /** - * Run this cell - */ - run(): void; - /** - * Delete this cell - */ - delete(): void; -} + constructor( + cellModel: NotebookCellTextModel, + instance: IPositronNotebookInstance, + textModelResolverService: ITextModelService, + ) { + super(cellModel, instance, textModelResolverService); -/** - * Cell that contains code that can be executed - */ -export interface IPositronNotebookCodeCell extends IPositronNotebookGeneralCell { - kind: CellKind.Code; -} + this.executionStatus = observableValue('cellExecutionStatus', 'idle'); + this.outputs = observableValue('cellOutputs', this.cellModel.outputs); -class PositronNotebookCodeCell extends PositronNotebookCellGeneral implements IPositronNotebookCodeCell { - kind: CellKind.Code = CellKind.Code; + // Listen for changes to the cell outputs and update the observable + this._register( + this.cellModel.onDidChangeOutputs(() => { + // By unpacking the array and repacking we make sure that + // the React component will rerender when the outputs change. Probably not + // great to have this leak here. + this.outputs.set([...this.cellModel.outputs], undefined); + }) + ); + } + + run(): void { + this._instance.runCells([this]); + } } -/** - * Cell that contains markup content - */ -export interface IPositronNotebookMarkupCell extends IPositronNotebookGeneralCell { - kind: CellKind.Markup; - renderedHtml: ISettableObservable; -} + class PositronNotebookMarkupCell extends PositronNotebookCellGeneral implements IPositronNotebookMarkupCell { renderedHtml: ISettableObservable = observableValue('renderedHTML', undefined); - kind: CellKind.Markup = CellKind.Markup; + override kind: CellKind.Markup = CellKind.Markup; constructor( cellModel: NotebookCellTextModel, @@ -200,7 +145,7 @@ class PositronNotebookMarkupCell extends PositronNotebookCellGeneral implements * @param instantiationService The instantiation service to use to create the cell * @returns The instantiated notebook cell of the correct type. */ -export function createNotebookCell(cell: NotebookCellTextModel, instance: IPositronNotebookInstance, instantiationService: IInstantiationService): IPositronNotebookGeneralCell { +export function createNotebookCell(cell: NotebookCellTextModel, instance: IPositronNotebookInstance, instantiationService: IInstantiationService) { if (cell.cellKind === CellKind.Code) { return instantiationService.createInstance(PositronNotebookCodeCell, cell, instance); } else { diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.tsx b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.tsx index 124e59c5cd9..ad7aa1d79b4 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.tsx @@ -6,12 +6,12 @@ import 'vs/css!./PositronNotebookComponent'; import * as React from 'react'; import { useNotebookInstance } from 'vs/workbench/contrib/positronNotebook/browser/NotebookInstanceProvider'; -import { NotebookCell } from './notebookCells/NotebookCell'; import { AddCellButton } from './AddCellButton'; import { useObservedValue } from './useObservedValue'; import { localize } from 'vs/nls'; import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; import { KernelStatusBadge } from './KernelStatusBadge'; +import { NotebookCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell'; export function PositronNotebookComponent() { @@ -38,7 +38,7 @@ export function PositronNotebookComponent() {
{notebookCells?.length ? notebookCells?.map((cell, index) => <> - + ) :
{localize('noCells', 'No cells')}
} diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts index 409aff72330..dece39b1c48 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts @@ -20,10 +20,11 @@ import { CellEditType, CellKind, ICellReplaceEdit, SelectionStateType } from 'vs import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; -import { IPositronNotebookGeneralCell, createNotebookCell } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell'; +import { createNotebookCell } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell'; import { PositronNotebookEditorInput } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditorInput'; import { BaseCellEditorOptions } from './BaseCellEditorOptions'; import * as DOM from 'vs/base/browser/dom'; +import { IPositronNotebookCodeCell, IPositronNotebookGeneralCell, isCodeCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; enum KernelStatus { @@ -110,12 +111,9 @@ export interface IPositronNotebookInstance { * all the logic and variables related to the view/DOM. */ detachView(): void; - } export class PositronNotebookInstance extends Disposable implements IPositronNotebookInstance { - - /** * Value to keep track of what instance number. * Used for keeping track in the logs. @@ -358,7 +356,8 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot * @returns */ private async _runCells(cells: IPositronNotebookGeneralCell[]): Promise { - + // Filter so we're only working with code cells. + const codeCells = cells.filter(cell => isCodeCell(cell)) as IPositronNotebookCodeCell[]; this._logService.info(this._identifier, '_runCells'); if (!this._textModel) { @@ -367,7 +366,7 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot this._trySetupKernel(); - for (const cell of cells) { + for (const cell of codeCells) { cell.executionStatus.set('running', undefined); } @@ -379,7 +378,7 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot } await this.notebookExecutionService.executeNotebookCells(this._textModel, Array.from(cells).map(c => c.cellModel), this._contextKeyService); - for (const cell of cells) { + for (const cell of codeCells) { cell.executionStatus.set('idle', undefined); } } diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx index b760e3d4467..cef5098d02c 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx @@ -9,8 +9,8 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { CellEditorOptions } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions'; import { useNotebookInstance } from 'vs/workbench/contrib/positronNotebook/browser/NotebookInstanceProvider'; -import { IPositronNotebookGeneralCell } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell'; import { useServices } from 'vs/workbench/contrib/positronNotebook/browser/ServicesProvider'; +import { IPositronNotebookGeneralCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; import { observeValue } from 'vs/workbench/contrib/positronNotebook/common/utils/observeValue'; @@ -21,7 +21,6 @@ import { observeValue } from 'vs/workbench/contrib/positronNotebook/common/utils */ export function CellEditorMonacoWidget({ cell }: { cell: IPositronNotebookGeneralCell }) { const { editorPartRef } = useCellEditorWidget(cell); - return
; } diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NodebookCodeCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NodebookCodeCell.tsx index d2857615843..ee62e18a7c9 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NodebookCodeCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NodebookCodeCell.tsx @@ -6,22 +6,37 @@ import * as React from 'react'; import { VSBuffer } from 'vs/base/common/buffer'; import { NotebookCellOutputTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel'; import { ICellOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { IPositronNotebookCodeCell } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell'; +import { IPositronNotebookCodeCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; import { parseOutputData } from 'vs/workbench/contrib/positronNotebook/browser/getOutputContents'; import { useObservedValue } from 'vs/workbench/contrib/positronNotebook/browser/useObservedValue'; import { CellEditorMonacoWidget } from './CellEditorMonacoWidget'; import { localize } from 'vs/nls'; +import { NotebookCellSkeleton } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSkeleton'; +import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; export function NodebookCodeCell({ cell }: { cell: IPositronNotebookCodeCell }) { const outputContents = useObservedValue(cell.outputs); + const executionStatus = useObservedValue(cell.executionStatus); + const isRunning = executionStatus === 'running'; + + return cell.delete()} + actionBarItems={ + + } + > - return <>
{outputContents?.map((output) => )}
- ; +
; } function NotebookCellOutput({ cellOutput }: { cellOutput: ICellOutput }) { diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx index f3e7c4796f8..4d067f2bbba 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx @@ -4,52 +4,30 @@ import 'vs/css!./NotebookCell'; import * as React from 'react'; -import { IPositronNotebookCodeCell, IPositronNotebookMarkupCell } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell'; -import { useObservedValue } from 'vs/workbench/contrib/positronNotebook/browser/useObservedValue'; -import { localize } from 'vs/nls'; -import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; +import { IPositronNotebookGeneralCell, isCodeCell, isMarkupCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; import { NodebookCodeCell } from './NodebookCodeCell'; import { NotebookMarkupCell } from './NotebookMarkupCell'; -import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + /** * Logic for running a cell and handling its output. * @param opts.cell The `PositronNotebookCell` to render */ export function NotebookCell({ cell }: { - cell: IPositronNotebookCodeCell | IPositronNotebookMarkupCell; + cell: IPositronNotebookGeneralCell; }) { - const executionStatus = useObservedValue(cell.executionStatus); - const isRunning = executionStatus === 'running'; - - return ( -
-
- - -
-
- { - cell.kind === CellKind.Code ? - : - - } -
-
- ); + + if (isCodeCell(cell)) { + return ; + } + + if (isMarkupCell(cell)) { + return ; + } + + throw new Error('Unknown cell type'); + + } diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSkeleton.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSkeleton.tsx new file mode 100644 index 00000000000..a2018110f1c --- /dev/null +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSkeleton.tsx @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import * as React from 'react'; +import { localize } from 'vs/nls'; +import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; + +export function NotebookCellSkeleton({ actionBarItems, onDelete, children }: { + actionBarItems: React.ReactNode; + onDelete: () => void; + children: React.ReactNode; +}) { + return ( +
+
+ {actionBarItems} + +
+
+ {children} +
+
+ ); +} diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx index 4ae412aa5d9..1ea3e3f539f 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx @@ -4,11 +4,12 @@ import 'vs/css!./NotebookMarkupCell'; import * as React from 'react'; -import { IPositronNotebookMarkupCell } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell'; +import { IPositronNotebookMarkupCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; import { renderHtml } from 'vs/base/browser/renderHtml'; import { CellEditorMonacoWidget } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget'; import { useObservedValue } from 'vs/workbench/contrib/positronNotebook/browser/useObservedValue'; +import { NotebookCellSkeleton } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSkeleton'; @@ -16,13 +17,15 @@ export function NotebookMarkupCell({ cell }: { cell: IPositronNotebookMarkupCell const renderedHtml = useObservedValue(cell.renderedHtml); - return
+ return cell.delete()} + actionBarItems={null} + >
{ renderedHtml ?
{renderHtml(renderedHtml)}
: null - }
-
; + ; } diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts new file mode 100644 index 00000000000..cb46ef2b4ec --- /dev/null +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts @@ -0,0 +1,96 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { ISettableObservable } from 'vs/base/common/observableInternal/base'; +import { URI } from 'vs/base/common/uri'; +import { ITextModel } from 'vs/editor/common/model'; +import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { CellKind, ICellOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + +export type ExecutionStatus = 'running' | 'pending' | 'unconfirmed' | 'idle'; + +/** + * Wrapper class for notebook cell that exposes the properties that the UI needs to render the cell. + * This interface is extended to provide the specific properties for code and markup cells. + */ +export interface IPositronNotebookGeneralCell extends Disposable { + + /** + * The kind of cell + */ + kind: CellKind; + + /** + * Cell specific uri for the cell within the notebook + */ + get uri(): URI; + + /** + * The content of the cell. This is the raw text of the cell. + */ + getContent(): string; + + /** + * The notebook text model for the cell. + */ + cellModel: NotebookCellTextModel; + + /** + * Get the view model for the cell + */ + get viewModel(): ICellViewModel; + + /** + * Get the text editor model for use in the monaco editor widgets + */ + getTextEditorModel(): Promise; + + /** + * Delete this cell + */ + delete(): void; +} + + +/** + * Cell that contains code that can be executed + */ +export interface IPositronNotebookCodeCell extends IPositronNotebookGeneralCell { + kind: CellKind.Code; + + /** + * Current execution status for this cell + */ + executionStatus: ISettableObservable; + + /** + * Current cell outputs as an observable + */ + outputs: ISettableObservable; + + /** + * Run this cell + */ + run(): void; +} + +export function isCodeCell(cell: IPositronNotebookGeneralCell): cell is IPositronNotebookCodeCell { + return cell.kind === CellKind.Code; +} + + +/** + * Cell that contains markup content + */ +export interface IPositronNotebookMarkupCell extends IPositronNotebookGeneralCell { + kind: CellKind.Markup; + renderedHtml: ISettableObservable; +} + +export function isMarkupCell(cell: IPositronNotebookGeneralCell): cell is IPositronNotebookMarkupCell { + return cell.kind === CellKind.Markup; +} + From 4d6bfcb57c679194e286cea964d799c840b4dbb8 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Fri, 22 Mar 2024 15:08:29 -0400 Subject: [PATCH 09/58] Add new button for creating markup cell next to code cell creation button --- .../{AddCellButton.css => AddCellButtons.css} | 9 +++++++-- .../{AddCellButton.tsx => AddCellButtons.tsx} | 20 ++++++++++++++----- .../browser/PositronNotebookComponent.tsx | 6 +++--- 3 files changed, 25 insertions(+), 10 deletions(-) rename src/vs/workbench/contrib/positronNotebook/browser/{AddCellButton.css => AddCellButtons.css} (75%) rename src/vs/workbench/contrib/positronNotebook/browser/{AddCellButton.tsx => AddCellButtons.tsx} (60%) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/AddCellButton.css b/src/vs/workbench/contrib/positronNotebook/browser/AddCellButtons.css similarity index 75% rename from src/vs/workbench/contrib/positronNotebook/browser/AddCellButton.css rename to src/vs/workbench/contrib/positronNotebook/browser/AddCellButtons.css index 5f9fa2483a0..ee9dbf6803f 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/AddCellButton.css +++ b/src/vs/workbench/contrib/positronNotebook/browser/AddCellButtons.css @@ -2,13 +2,18 @@ * Copyright (C) 2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ -.positron-add-cell-button { +.positron-add-cell-buttons { display: flex; justify-content: center; opacity: 0; transition: opacity 0.2s; + gap: 8px; + + .action-label { + cursor: pointer; + } } -.positron-add-cell-button:hover { +.positron-add-cell-buttons:hover { opacity: 1; } diff --git a/src/vs/workbench/contrib/positronNotebook/browser/AddCellButton.tsx b/src/vs/workbench/contrib/positronNotebook/browser/AddCellButtons.tsx similarity index 60% rename from src/vs/workbench/contrib/positronNotebook/browser/AddCellButton.tsx rename to src/vs/workbench/contrib/positronNotebook/browser/AddCellButtons.tsx index 266b0895591..6f7570c19e9 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/AddCellButton.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/AddCellButtons.tsx @@ -1,7 +1,7 @@ /*--------------------------------------------------------------------------------------------- * Copyright (C) 2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./AddCellButton'; +import 'vs/css!./AddCellButtons'; import * as React from 'react'; import { useNotebookInstance } from 'vs/workbench/contrib/positronNotebook/browser/NotebookInstanceProvider'; @@ -9,18 +9,28 @@ import { localize } from 'vs/nls'; import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -export function AddCellButton({ index }: { index: number }) { +export function AddCellButtons({ index }: { index: number }) { const notebookInstance = useNotebookInstance(); - return
+ return
+
; diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.tsx b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.tsx index ad7aa1d79b4..5a1eae12d00 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.tsx @@ -6,7 +6,7 @@ import 'vs/css!./PositronNotebookComponent'; import * as React from 'react'; import { useNotebookInstance } from 'vs/workbench/contrib/positronNotebook/browser/NotebookInstanceProvider'; -import { AddCellButton } from './AddCellButton'; +import { AddCellButtons } from './AddCellButtons'; import { useObservedValue } from './useObservedValue'; import { localize } from 'vs/nls'; import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; @@ -36,10 +36,10 @@ export function PositronNotebookComponent() {
- + {notebookCells?.length ? notebookCells?.map((cell, index) => <> - + ) :
{localize('noCells', 'No cells')}
}
From aa47cd3e6b11b09f007a53af9fd02a48df082465 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Fri, 22 Mar 2024 15:30:08 -0400 Subject: [PATCH 10/58] Add option for showing and hiding the editor for a markup cell --- .../browser/PositronNotebookCell.ts | 11 +++++++++ .../notebookCells/NotebookMarkupCell.tsx | 23 ++++++++++++++++--- .../browser/notebookCells/interfaces.ts | 4 ++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts index 37072d5d96e..e81fe77e241 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts @@ -112,6 +112,7 @@ class PositronNotebookCodeCell extends PositronNotebookCellGeneral implements IP class PositronNotebookMarkupCell extends PositronNotebookCellGeneral implements IPositronNotebookMarkupCell { renderedHtml: ISettableObservable = observableValue('renderedHTML', undefined); + editorShown: ISettableObservable = observableValue('editorShown', false); override kind: CellKind.Markup = CellKind.Markup; constructor( @@ -136,6 +137,16 @@ class PositronNotebookMarkupCell extends PositronNotebookCellGeneral implements } this.renderedHtml.set(renderedHtml, undefined); } + + showEditor(): void { + this.editorShown.set(true, undefined); + } + hideEditor(): void { + this.editorShown.set(false, undefined); + } + toggleEditor(): void { + this.editorShown.set(!this.editorShown.get(), undefined); + } } /** diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx index 1ea3e3f539f..1601c566413 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx @@ -10,19 +10,36 @@ import { renderHtml } from 'vs/base/browser/renderHtml'; import { CellEditorMonacoWidget } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget'; import { useObservedValue } from 'vs/workbench/contrib/positronNotebook/browser/useObservedValue'; import { NotebookCellSkeleton } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSkeleton'; +import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; export function NotebookMarkupCell({ cell }: { cell: IPositronNotebookMarkupCell }) { const renderedHtml = useObservedValue(cell.renderedHtml); + const editorShown = useObservedValue(cell.editorShown); + + const showHideButton = ; return cell.delete()} - actionBarItems={null} + actionBarItems={showHideButton} > - -
+ {editorShown ? : null + } +
{ + cell.toggleEditor(); + }}> { renderedHtml ?
{renderHtml(renderedHtml)}
: null } diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts index cb46ef2b4ec..e153a61e93c 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts @@ -88,6 +88,10 @@ export function isCodeCell(cell: IPositronNotebookGeneralCell): cell is IPositro export interface IPositronNotebookMarkupCell extends IPositronNotebookGeneralCell { kind: CellKind.Markup; renderedHtml: ISettableObservable; + editorShown: ISettableObservable; + showEditor(): void; + hideEditor(): void; + toggleEditor(): void; } export function isMarkupCell(cell: IPositronNotebookGeneralCell): cell is IPositronNotebookMarkupCell { From 0d0c189a1d51b5e418955ace2241bcd84c03fc8a Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Fri, 22 Mar 2024 15:37:14 -0400 Subject: [PATCH 11/58] Fix overflowing action-bar border when in collapsed markup cell mode --- .../positronNotebook/browser/notebookCells/NotebookCell.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.css b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.css index a1a556908b6..56c48696804 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.css +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.css @@ -23,6 +23,7 @@ border: var(--positron-notebooks-border); border-top-left-radius: 0.25rem; border-top-right-radius: 0.25rem; + border-bottom: transparent; display: flex; gap: 0.25rem; From 138a2a316d7951ae3731f9721bb0e20997872e2c Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Fri, 22 Mar 2024 15:51:06 -0400 Subject: [PATCH 12/58] Add message about empty markup cell so user's not super confused --- .../browser/notebookCells/NotebookMarkupCell.css | 7 +++++++ .../browser/notebookCells/NotebookMarkupCell.tsx | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.css b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.css index 0774c8192c9..8367fc21a54 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.css +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.css @@ -5,4 +5,11 @@ .positron-notebook-markup-rendered { padding-inline: 1rem; padding-block: 0.5rem; + + .empty-output-msg { + opacity: 0.75; + font-style: italic; + text-align: center; + cursor: pointer; + } } diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx index 1601c566413..685cfd839eb 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx @@ -41,7 +41,9 @@ export function NotebookMarkupCell({ cell }: { cell: IPositronNotebookMarkupCell cell.toggleEditor(); }}> { - renderedHtml ?
{renderHtml(renderedHtml)}
: null + renderedHtml ?
{renderHtml(renderedHtml)}
:
+ Empty markup cell. {editorShown ? '' : 'Double click to edit'} +
}
; From d7c4ec309aa75f3679b0a2afc75c9449e00ce24a Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Fri, 22 Mar 2024 16:06:54 -0400 Subject: [PATCH 13/58] Add logic to hide the cell outlines in markdown cells if the editor is hidden. --- .../notebookCells/NotebookCellSkeleton.tsx | 25 ++++++++----- .../notebookCells/NotebookMarkupCell.css | 13 +++++++ .../notebookCells/NotebookMarkupCell.tsx | 36 +++++++++++-------- 3 files changed, 50 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSkeleton.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSkeleton.tsx index a2018110f1c..eeefb440ba3 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSkeleton.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSkeleton.tsx @@ -13,19 +13,26 @@ export function NotebookCellSkeleton({ actionBarItems, onDelete, children }: { }) { return (
-
+ {actionBarItems} - -
+
{children}
); } + +export function NotebookCellActionBar({ children, onDelete }: { children: React.ReactNode; onDelete: () => void }) { + + return
+ {children} + +
; +} diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.css b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.css index 8367fc21a54..2393507ce10 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.css +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.css @@ -13,3 +13,16 @@ cursor: pointer; } } + +.positron-notebook-cell.editor-hidden { + --positron-notebooks-border-color: transparent; +} + +.positron-notebook-cell.editor-hidden:not(:hover) .action-bar { + display: none; +} +/* Put the actions bar halfway down the cell so it's more clearly associated with the content that + doesn't have borders to deliniate where it sits */ +.positron-notebook-cell.editor-hidden .action-bar { + top: calc(50% - (var(--bar-h) / 2)); +} diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx index 685cfd839eb..5d933d198dd 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx @@ -9,7 +9,7 @@ import { IPositronNotebookMarkupCell } from 'vs/workbench/contrib/positronNotebo import { renderHtml } from 'vs/base/browser/renderHtml'; import { CellEditorMonacoWidget } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget'; import { useObservedValue } from 'vs/workbench/contrib/positronNotebook/browser/useObservedValue'; -import { NotebookCellSkeleton } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSkeleton'; +import { NotebookCellActionBar } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSkeleton'; import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; @@ -31,20 +31,26 @@ export function NotebookMarkupCell({ cell }: { cell: IPositronNotebookMarkupCell {editorShown ? 'Hide Editor' : 'Show Editor'} ; - return cell.delete()} - actionBarItems={showHideButton} - > - {editorShown ? : null - } -
{ - cell.toggleEditor(); - }}> - { - renderedHtml ?
{renderHtml(renderedHtml)}
:
- Empty markup cell. {editorShown ? '' : 'Double click to edit'} + + return ( +
+ cell.delete()}> + {showHideButton} + +
+ {editorShown ? : null + } +
{ + cell.toggleEditor(); + }}> + { + renderedHtml ?
{renderHtml(renderedHtml)}
:
+ Empty markup cell. {editorShown ? '' : 'Double click to edit'} +
+ }
- } +
- ; + ); + } From 4b041a2870357a968375329ce9fe48118a8f580c Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Mon, 25 Mar 2024 11:50:46 -0400 Subject: [PATCH 14/58] Switch off of notebook skeleton for the markup cell, too --- .../notebookCells/NodebookCodeCell.tsx | 22 +++++++++---------- ...Skeleton.tsx => NotebookCellActionBar.tsx} | 21 +++--------------- .../notebookCells/NotebookMarkupCell.tsx | 5 ++--- 3 files changed, 16 insertions(+), 32 deletions(-) rename src/vs/workbench/contrib/positronNotebook/browser/notebookCells/{NotebookCellSkeleton.tsx => NotebookCellActionBar.tsx} (53%) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NodebookCodeCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NodebookCodeCell.tsx index ee62e18a7c9..3143b8a80ed 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NodebookCodeCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NodebookCodeCell.tsx @@ -11,8 +11,8 @@ import { parseOutputData } from 'vs/workbench/contrib/positronNotebook/browser/g import { useObservedValue } from 'vs/workbench/contrib/positronNotebook/browser/useObservedValue'; import { CellEditorMonacoWidget } from './CellEditorMonacoWidget'; import { localize } from 'vs/nls'; -import { NotebookCellSkeleton } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSkeleton'; import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; +import { NotebookCellActionBar } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellActionBar'; export function NodebookCodeCell({ cell }: { cell: IPositronNotebookCodeCell }) { @@ -20,23 +20,23 @@ export function NodebookCodeCell({ cell }: { cell: IPositronNotebookCodeCell }) const executionStatus = useObservedValue(cell.executionStatus); const isRunning = executionStatus === 'running'; - return cell.delete()} - actionBarItems={ + return
+ - } - > - - -
- {outputContents?.map((output) => )} + +
+ +
+ {outputContents?.map((output) => )} +
- ; +
; + } function NotebookCellOutput({ cellOutput }: { cellOutput: ICellOutput }) { diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSkeleton.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellActionBar.tsx similarity index 53% rename from src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSkeleton.tsx rename to src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellActionBar.tsx index eeefb440ba3..89e8ec64475 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSkeleton.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellActionBar.tsx @@ -5,32 +5,17 @@ import * as React from 'react'; import { localize } from 'vs/nls'; import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; +import { IPositronNotebookGeneralCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; -export function NotebookCellSkeleton({ actionBarItems, onDelete, children }: { - actionBarItems: React.ReactNode; - onDelete: () => void; - children: React.ReactNode; -}) { - return ( -
- - {actionBarItems} - -
- {children} -
-
- ); -} -export function NotebookCellActionBar({ children, onDelete }: { children: React.ReactNode; onDelete: () => void }) { +export function NotebookCellActionBar({ cell, children }: { cell: IPositronNotebookGeneralCell; children: React.ReactNode }) { return
{children} diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx index 5d933d198dd..feec05d9556 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx @@ -9,7 +9,7 @@ import { IPositronNotebookMarkupCell } from 'vs/workbench/contrib/positronNotebo import { renderHtml } from 'vs/base/browser/renderHtml'; import { CellEditorMonacoWidget } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget'; import { useObservedValue } from 'vs/workbench/contrib/positronNotebook/browser/useObservedValue'; -import { NotebookCellActionBar } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSkeleton'; +import { NotebookCellActionBar } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellActionBar'; import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; @@ -31,10 +31,9 @@ export function NotebookMarkupCell({ cell }: { cell: IPositronNotebookMarkupCell {editorShown ? 'Hide Editor' : 'Show Editor'} ; - return (
- cell.delete()}> + {showHideButton}
From 84c934945bb3728898d854453d2823c1753e68cc Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Tue, 26 Mar 2024 10:15:04 -0400 Subject: [PATCH 15/58] markdown now renders in webview so we can successfully open images. --- .../browser/PositronNotebookCell.ts | 4 + .../browser/PositronNotebookEditor.tsx | 3 + .../browser/ServicesProvider.tsx | 6 ++ .../notebookCells/NotebookMarkupCell.tsx | 20 ++--- .../browser/notebookCells/interfaces.ts | 5 ++ .../useMarkupWebviewRenderer.tsx | 89 +++++++++++++++++++ 6 files changed, 116 insertions(+), 11 deletions(-) create mode 100644 src/vs/workbench/contrib/positronNotebook/browser/notebookCells/useMarkupWebviewRenderer.tsx diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts index e81fe77e241..9e4f50ed8a8 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts @@ -36,6 +36,10 @@ abstract class PositronNotebookCellGeneral extends Disposable implements IPositr return this.cellModel.uri; } + get notebookUri(): URI { + return this._instance.uri; + } + get viewModel(): ICellViewModel { const notebookViewModel = this._instance.viewModel; diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditor.tsx b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditor.tsx index 57426c9ee92..790deffcac2 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditor.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditor.tsx @@ -50,6 +50,7 @@ import { } from 'vs/workbench/services/editor/common/editorGroupsService'; import { PositronNotebookEditorInput } from './PositronNotebookEditorInput'; import { ILogService } from 'vs/platform/log/common/log'; +import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; interface NotebookLayoutInfo { @@ -109,6 +110,7 @@ export class PositronNotebookEditor extends EditorPane { @IConfigurationService private readonly _configurationService: IConfigurationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ITextModelService private readonly _textModelResolverService: ITextModelService, + @IWebviewService private readonly _webviewService: IWebviewService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @ILogService private readonly _logService: ILogService, @@ -443,6 +445,7 @@ export class PositronNotebookEditor extends EditorPane { configurationService: this._configurationService, instantiationService: this._instantiationService, textModelResolverService: this._textModelResolverService, + webviewService: this._webviewService, logService: this._logService, sizeObservable: this._size, scopedContextKeyProviderCallback: container => scopedContextKeyService.createScoped(container) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/ServicesProvider.tsx b/src/vs/workbench/contrib/positronNotebook/browser/ServicesProvider.tsx index ee5b265de4d..6d982e15cf5 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/ServicesProvider.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/ServicesProvider.tsx @@ -10,6 +10,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IScopedContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; +import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; @@ -38,6 +39,11 @@ interface ServiceBundle { */ logService: ILogService; + /** + * Service for creating webviews + */ + webviewService: IWebviewService; + /** * An observable for the size of the notebook. */ diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx index feec05d9556..bf0b7e8f70f 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx @@ -2,22 +2,20 @@ * Copyright (C) 2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ import 'vs/css!./NotebookMarkupCell'; - import * as React from 'react'; + import { IPositronNotebookMarkupCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; -import { renderHtml } from 'vs/base/browser/renderHtml'; +import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; import { CellEditorMonacoWidget } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget'; -import { useObservedValue } from 'vs/workbench/contrib/positronNotebook/browser/useObservedValue'; import { NotebookCellActionBar } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellActionBar'; -import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; - - - +import { useObservedValue } from 'vs/workbench/contrib/positronNotebook/browser/useObservedValue'; +import { useMarkupWebviewRenderer } from './useMarkupWebviewRenderer'; export function NotebookMarkupCell({ cell }: { cell: IPositronNotebookMarkupCell }) { const renderedHtml = useObservedValue(cell.renderedHtml); const editorShown = useObservedValue(cell.editorShown); + const webviewContainerRef = useMarkupWebviewRenderer(cell); const showHideButton =
diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts index e153a61e93c..4b4f5792f74 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts @@ -28,6 +28,11 @@ export interface IPositronNotebookGeneralCell extends Disposable { */ get uri(): URI; + /** + * URI for the notebook that contains this cell + */ + get notebookUri(): URI; + /** * The content of the cell. This is the raw text of the cell. */ diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/useMarkupWebviewRenderer.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/useMarkupWebviewRenderer.tsx new file mode 100644 index 00000000000..67af74c1326 --- /dev/null +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/useMarkupWebviewRenderer.tsx @@ -0,0 +1,89 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ +import * as React from 'react'; +import { Schemas } from 'vs/base/common/network'; +import { dirname, } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; +import { useServices } from 'vs/workbench/contrib/positronNotebook/browser/ServicesProvider'; +import { IPositronNotebookMarkupCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; +import { useObservedValue } from 'vs/workbench/contrib/positronNotebook/browser/useObservedValue'; +import { IWebviewElement, WebviewInitInfo } from 'vs/workbench/contrib/webview/browser/webview'; +import { asWebviewUri } from 'vs/workbench/contrib/webview/common/webview'; + +export function useMarkupWebviewRenderer(cell: IPositronNotebookMarkupCell) { + const services = useServices(); + + const webviewService = services.webviewService; + const webviewContainerRef = React.useRef(null); + const renderedHtml = useObservedValue(cell.renderedHtml); + const webviewRef = React.useRef(undefined); + + // Creates webview and mounts it to the container + React.useEffect(() => { + if (!webviewContainerRef.current) { + return; + } + + + const notebookRoot = getNotebookBaseUri(cell.notebookUri); + + const webviewInitInfo: WebviewInitInfo = { + title: localize('markup rendering webview', "Markup Rendering Webview"), + contentOptions: { + allowMultipleAPIAcquire: true, + localResourceRoots: [ + notebookRoot + ] + }, + options: {}, + extension: undefined, + }; + + webviewRef.current = webviewService.createWebviewElement(webviewInitInfo); + webviewRef.current.mountTo(webviewContainerRef.current); + + return () => { + webviewRef.current?.dispose(); + }; + }, [cell.notebookUri, cell.uri, webviewService]); + + // Sync the rendered HTML to the webview HTML + React.useEffect(() => { + if (!webviewRef.current) { + return; + } + const notebookRoot = asWebviewUri(getNotebookBaseUri(cell.notebookUri)); + + webviewRef.current.setHtml(` + + + + + + + + ${renderedHtml || '

No content

'} + + + `); + + }, [cell.notebookUri, renderedHtml]); + + return webviewContainerRef; +} + + +function getNotebookBaseUri(notebookUri: URI) { + if (notebookUri.scheme === Schemas.untitled) { + // TODO: Use workspace context service to set the base URI to workspace root + throw new Error('Have not yet implemented untitled notebook URIs'); + } + + return dirname(notebookUri); +} From cbb754693b586d0eca75ac6da367d5194ff97848 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Tue, 26 Mar 2024 11:58:07 -0400 Subject: [PATCH 16/58] Add communication from webview to outside with info about height of content and double-click status --- .../useMarkupWebviewRenderer.tsx | 109 +++++++++++++++--- 1 file changed, 96 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/useMarkupWebviewRenderer.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/useMarkupWebviewRenderer.tsx index 67af74c1326..28670aff763 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/useMarkupWebviewRenderer.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/useMarkupWebviewRenderer.tsx @@ -26,12 +26,12 @@ export function useMarkupWebviewRenderer(cell: IPositronNotebookMarkupCell) { return; } - const notebookRoot = getNotebookBaseUri(cell.notebookUri); const webviewInitInfo: WebviewInitInfo = { title: localize('markup rendering webview', "Markup Rendering Webview"), contentOptions: { + allowScripts: true, allowMultipleAPIAcquire: true, localResourceRoots: [ notebookRoot @@ -41,38 +41,96 @@ export function useMarkupWebviewRenderer(cell: IPositronNotebookMarkupCell) { extension: undefined, }; - webviewRef.current = webviewService.createWebviewElement(webviewInitInfo); - webviewRef.current.mountTo(webviewContainerRef.current); + const webview = webviewService.createWebviewElement(webviewInitInfo); + webview.mountTo(webviewContainerRef.current); + webview.onMessage(({ message }) => { + + if (isWebviewMessage(message) && webviewContainerRef.current) { + switch (message.type) { + case 'dblclick': + cell.toggleEditor(); + break; + case 'markup-content-height': + webviewContainerRef.current.style.height = `${message.value}px`; + break; + } + } + }); + webviewRef.current = webview; + + webviewContainerRef.current.addEventListener('dblclick', () => { + cell.toggleEditor(); + }); + return () => { - webviewRef.current?.dispose(); + webview?.dispose(); }; - }, [cell.notebookUri, cell.uri, webviewService]); + }, [cell.notebookUri, webviewService, cell]); // Sync the rendered HTML to the webview HTML React.useEffect(() => { - if (!webviewRef.current) { + const webview = webviewRef.current; + if (!webview) { return; } - const notebookRoot = asWebviewUri(getNotebookBaseUri(cell.notebookUri)); - - webviewRef.current.setHtml(` + webview.setHtml(` - + + ${renderedHtml || '

No content

'} `); - }, [cell.notebookUri, renderedHtml]); return webviewContainerRef; @@ -87,3 +145,28 @@ function getNotebookBaseUri(notebookUri: URI) { return dirname(notebookUri); } + + +type WebviewMessage = { + type: 'markup-content-height'; + value: number; +} | { + type: 'dblclick'; + value: null; +}; + + + +function isWebviewMessage(message: unknown): message is WebviewMessage { + if (typeof message !== 'object' || message === null) { + return false; + } + if (!('type' in message)) { + return false; + } + + const knownTypes: WebviewMessage['type'][] = ['markup-content-height', 'dblclick']; + + return knownTypes.includes((message as WebviewMessage).type); +} + From 461dff90f59a0b65dbd441afdb8a502abb0ba89a Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Tue, 26 Mar 2024 12:02:02 -0400 Subject: [PATCH 17/58] mend --- .../notebookCells/useMarkupWebviewRenderer.tsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/useMarkupWebviewRenderer.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/useMarkupWebviewRenderer.tsx index 28670aff763..beb27f90854 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/useMarkupWebviewRenderer.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/useMarkupWebviewRenderer.tsx @@ -104,20 +104,16 @@ export function useMarkupWebviewRenderer(cell: IPositronNotebookMarkupCell) { vscode.postMessage({ type: 'dblclick'}); } - // Listen for resizes so we can update height of webview as dynamic height - // content like images resize - document.onresize = () => { - reportHeight(); - }; - // Wait for page load to report height. This is needed // so things like images etc can load for height to be correct window.onload = () => { reportHeight(); }; - window.onDblClick = () => { - reportDoubleClick() + // Listen for resizes so we can update height of webview as dynamic height + // content like images resize + document.onresize = () => { + reportHeight(); }; window.addEventListener("dblclick", (event) => { From 80a56a4cfca5c2b7565753902469303054c1ff79 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Tue, 26 Mar 2024 12:12:08 -0400 Subject: [PATCH 18/58] Make action bar look a bit better when overlapping content in collapsed markup view. Also fix distracting action button outlines. --- .../browser/PositronNotebookComponent.css | 1 - .../browser/notebookCells/NotebookCell.css | 44 ------------------ .../notebookCells/NotebookCellActionBar.css | 45 +++++++++++++++++++ .../notebookCells/NotebookCellActionBar.tsx | 3 +- .../notebookCells/NotebookMarkupCell.css | 6 ++- 5 files changed, 51 insertions(+), 48 deletions(-) create mode 100644 src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellActionBar.css diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.css b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.css index 9a27edb63f0..aa45edefa85 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.css +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.css @@ -47,7 +47,6 @@ align-items: center; padding-block: 0.25rem; padding-inline: 0.5rem; - outline: 1px solid var(--vscode-positronDataExplorer-border, grey); border-radius: 0.25rem; width: fit-content; cursor: pointer; diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.css b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.css index 56c48696804..42ff3b1b407 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.css +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.css @@ -15,50 +15,6 @@ position: relative; - .action-bar { - --bar-h: 1rem; - position: absolute; - top: calc((var(--bar-h) + 0.25rem) * -1); - right: 0; - border: var(--positron-notebooks-border); - border-top-left-radius: 0.25rem; - border-top-right-radius: 0.25rem; - border-bottom: transparent; - display: flex; - gap: 0.25rem; - - .action-button { - aspect-ratio: 1; - display: grid; - place-content: center; - padding: 2px; - - &:hover { - scale: 1.2; - - .button-icon::before { - color: blue; - } - } - - .button-icon { - align-items: center; - justify-content: center; - display: flex; - width: 16px; - aspect-ratio: 1; - } - - .button-icon::before { - color: inherit; - } - - &.disabled { - color: grey; - } - } - } - .cell-contents { border: var(--positron-notebooks-border); diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellActionBar.css b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellActionBar.css new file mode 100644 index 00000000000..b852720683a --- /dev/null +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellActionBar.css @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ +.positron-notebooks-cell-action-bar { + --bar-h: 1rem; + position: absolute; + top: calc((var(--bar-h) + 0.25rem) * -1); + right: 0; + border: var(--positron-notebooks-border); + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; + border-bottom: transparent; + background-color: var(--vscode-editor-background, white); + display: flex; + gap: 0.25rem; + + .action-button { + aspect-ratio: 1; + display: grid; + place-content: center; + padding: 2px; + + &:hover { + .button-icon::before { + color: blue; + } + } + + .button-icon { + align-items: center; + justify-content: center; + display: flex; + width: 16px; + aspect-ratio: 1; + } + + .button-icon::before { + color: inherit; + } + + &.disabled { + color: grey; + } + } +} diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellActionBar.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellActionBar.tsx index 89e8ec64475..e8c8a7f921b 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellActionBar.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellActionBar.tsx @@ -1,6 +1,7 @@ /*--------------------------------------------------------------------------------------------- * Copyright (C) 2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./NotebookCellActionBar'; import * as React from 'react'; import { localize } from 'vs/nls'; @@ -10,7 +11,7 @@ import { IPositronNotebookGeneralCell } from 'vs/workbench/contrib/positronNoteb export function NotebookCellActionBar({ cell, children }: { cell: IPositronNotebookGeneralCell; children: React.ReactNode }) { - return
+ return
{children}
); - } From 936032ac5353a87c16d20ad5af739803c791203f Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Wed, 27 Mar 2024 17:09:04 -0400 Subject: [PATCH 23/58] Add placeholder image for when images are not yet loaded --- .../positron-notebooks/src/extension.ts | 2 +- .../browser/notebookCells/DeferredImage.css | 7 ++++ .../browser/notebookCells/DeferredImage.tsx | 41 +++++++++++++++---- 3 files changed, 42 insertions(+), 8 deletions(-) create mode 100644 src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.css diff --git a/extensions/positron-notebooks/src/extension.ts b/extensions/positron-notebooks/src/extension.ts index 386b8470983..a0f2bb9e808 100644 --- a/extensions/positron-notebooks/src/extension.ts +++ b/extensions/positron-notebooks/src/extension.ts @@ -27,7 +27,7 @@ export function activate(context: vscode.ExtensionContext) { return `data:image/${imageType};base64,${data.toString('base64')}`; } catch (e) { console.error(e); - return 'failed to convert image to base64'; + return null; } } ) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.css b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.css new file mode 100644 index 00000000000..6d45bdc15f5 --- /dev/null +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.css @@ -0,0 +1,7 @@ +.positron-notebooks-deferred-img-placeholder { + width: min(400px, 100%); + height: max(20vh, 200px); + background-color: var(--vscode-positronVariables-activeSelectionBackground); + /* Code from https://heropatterns.com/ */ + background-image: url("data:image/svg+xml,%3Csvg width='100' height='20' viewBox='0 0 100 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M21.184 20c.357-.13.72-.264 1.088-.402l1.768-.661C33.64 15.347 39.647 14 50 14c10.271 0 15.362 1.222 24.629 4.928.955.383 1.869.74 2.75 1.072h6.225c-2.51-.73-5.139-1.691-8.233-2.928C65.888 13.278 60.562 12 50 12c-10.626 0-16.855 1.397-26.66 5.063l-1.767.662c-2.475.923-4.66 1.674-6.724 2.275h6.335zm0-20C13.258 2.892 8.077 4 0 4V2c5.744 0 9.951-.574 14.85-2h6.334zM77.38 0C85.239 2.966 90.502 4 100 4V2c-6.842 0-11.386-.542-16.396-2h-6.225zM0 14c8.44 0 13.718-1.21 22.272-4.402l1.768-.661C33.64 5.347 39.647 4 50 4c10.271 0 15.362 1.222 24.629 4.928C84.112 12.722 89.438 14 100 14v-2c-10.271 0-15.362-1.222-24.629-4.928C65.888 3.278 60.562 2 50 2 39.374 2 33.145 3.397 23.34 7.063l-1.767.662C13.223 10.84 8.163 12 0 12v2z' fill='white' fill-opacity='0.4' fill-rule='evenodd'/%3E%3C/svg%3E"); +} diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx index ba4b2fa9da6..eb9223bdd20 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx @@ -1,6 +1,7 @@ /*--------------------------------------------------------------------------------------------- * Copyright (C) 2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./DeferredImage'; import * as React from 'react'; import { useServices } from 'vs/workbench/contrib/positronNotebook/browser/ServicesProvider'; @@ -9,6 +10,16 @@ import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { dirname } from 'vs/base/common/resources'; + +type ImageDataResults = { + status: 'pending'; +} | { + status: 'success'; + data: string; +} | { + status: 'error'; + error: string; +}; /** * Special image component that defers loading of the image while it converts it to a data-url using * the `positronNotebookHelpers.convertImageToBase64` command. @@ -21,24 +32,40 @@ export function DeferredImage({ src = 'no-source', ...props }: React.ComponentPr const notebookInstance = useNotebookInstance(); const baseLocation = getNotebookBaseUri(notebookInstance.uri).path; - const [dataUrl, setDataUrl] = React.useState(null); + const [results, setResults] = React.useState({ status: 'pending' }); React.useEffect(() => { services.commandService.executeCommand( 'positronNotebookHelpers.convertImageToBase64', src, baseLocation - ).then((base64: string) => { - setDataUrl(base64); + ).then((base64: string | null) => { + if (!base64) { + services.logService.error('Failed to convert image to base64', src); + setResults({ status: 'error', error: 'Failed to convert image to base64' }); + return; + } + setResults({ status: 'success', data: base64 }); }); }, [src, baseLocation, services.commandService, services.logService]); - if (!dataUrl) { - return Loading image: {dataUrl}; + + switch (results.status) { + case 'pending': + return
; + case 'error': + return ; + case 'success': + return ; } - return ; -} +} + function getNotebookBaseUri(notebookUri: URI) { if (notebookUri.scheme === Schemas.untitled) { // TODO: Use workspace context service to set the base URI to workspace root From 80abc51ea653480be0bc6eb1978ab5673fbd3b73 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Thu, 28 Mar 2024 12:15:29 -0400 Subject: [PATCH 24/58] Switch to using markdown-to-html rendering from the `markdown.api.render` command. --- extensions/positron-notebooks/yarn.lock | 461 +----------------- package.json | 1 - src/bootstrap-window.js | 1 - .../browser/PositronNotebookCell.ts | 21 +- yarn.lock | 5 - 5 files changed, 19 insertions(+), 470 deletions(-) diff --git a/extensions/positron-notebooks/yarn.lock b/extensions/positron-notebooks/yarn.lock index d3502cb42a3..636843039d2 100644 --- a/extensions/positron-notebooks/yarn.lock +++ b/extensions/positron-notebooks/yarn.lock @@ -107,41 +107,6 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== -"@types/body-parser@*": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" - integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.35" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" - integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== - dependencies: - "@types/node" "*" - -"@types/express-serve-static-core@^4.17.33": - version "4.17.35" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz#c95dd4424f0d32e525d23812aa8ab8e4d3906c4f" - integrity sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - -"@types/express@^4.17.17": - version "4.17.17" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4" - integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.33" - "@types/qs" "*" - "@types/serve-static" "*" - "@types/glob@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" @@ -150,33 +115,11 @@ "@types/minimatch" "*" "@types/node" "*" -"@types/http-errors@*": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.1.tgz#20172f9578b225f6c7da63446f56d4ce108d5a65" - integrity sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ== - -"@types/http-proxy@^1.17.8": - version "1.17.11" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.11.tgz#0ca21949a5588d55ac2b659b69035c84bd5da293" - integrity sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA== - dependencies: - "@types/node" "*" - "@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== -"@types/mime@*": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" - integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== - -"@types/mime@^1": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" - integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== - "@types/minimatch@*": version "5.1.2" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" @@ -197,38 +140,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.35.tgz#879c4659cb7b3fe515844f029c75079c941bb65c" integrity sha512-2ATO8pfhG1kDvw4Lc4C0GXIMSQFFJBCo/R1fSgTwmUlq5oy95LXyjDQinsRVgQY6gp6ghh3H91wk9ES5/5C+Tw== -"@types/qs@*": - version "6.9.7" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== - -"@types/range-parser@*": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" - integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== - "@types/semver@^7.3.12": version "7.3.13" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== -"@types/send@*": - version "0.17.1" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.1.tgz#ed4932b8a2a805f1fe362a70f4e62d0ac994e301" - integrity sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/serve-static@*": - version "1.15.2" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.2.tgz#3e5419ecd1e40e7405d34093f10befb43f63381a" - integrity sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw== - dependencies: - "@types/http-errors" "*" - "@types/mime" "*" - "@types/node" "*" - "@typescript-eslint/eslint-plugin@^5.12.1": version "5.46.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.46.1.tgz#098abb4c9354e19f460d57ab18bff1f676a6cff0" @@ -327,14 +243,6 @@ rimraf "^3.0.2" unzipper "^0.10.11" -accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -409,11 +317,6 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" @@ -469,24 +372,6 @@ bluebird@~3.4.1: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" integrity sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA== -body-parser@1.20.1: - version "1.20.1" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" - integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.11.0" - raw-body "2.5.1" - type-is "~1.6.18" - unpipe "1.0.0" - boolbase@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" @@ -535,11 +420,6 @@ buffers@~0.1.1: resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" integrity sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ== -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - call-bind@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -670,28 +550,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - -cookie@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== - core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" @@ -727,13 +585,6 @@ css-what@^6.1.0: resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - debug@4, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -770,16 +621,6 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -depd@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - detect-libc@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" @@ -846,21 +687,11 @@ duplexer2@~0.1.4: dependencies: readable-stream "^2.0.2" -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -883,11 +714,6 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" @@ -1014,58 +840,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -eventemitter3@^4.0.0: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - expand-template@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== -express@^4.18.2: - version "4.18.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" - integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.1" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.5.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.2.0" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.11.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -1120,19 +899,6 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - find-up@5.0.0, find-up@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" @@ -1159,21 +925,6 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== -follow-redirects@^1.0.0: - version "1.15.6" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" - integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -1339,17 +1090,6 @@ htmlparser2@^8.0.1: domutils "^3.0.1" entities "^4.3.0" -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - http-proxy-agent@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" @@ -1359,26 +1099,6 @@ http-proxy-agent@^4.0.1: agent-base "6" debug "4" -http-proxy-middleware@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" - integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== - dependencies: - "@types/http-proxy" "^1.17.8" - http-proxy "^1.18.1" - is-glob "^4.0.1" - is-plain-obj "^3.0.0" - micromatch "^4.0.2" - -http-proxy@^1.18.1: - version "1.18.1" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" - integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - https-proxy-agent@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" @@ -1387,13 +1107,6 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - ieee754@^1.1.13: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -1425,7 +1138,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3: +inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -1435,11 +1148,6 @@ ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -1479,11 +1187,6 @@ is-plain-obj@^2.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== -is-plain-obj@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" - integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== - is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" @@ -1602,27 +1305,12 @@ mdurl@^1.0.1: resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== - merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -micromatch@^4.0.2, micromatch@^4.0.4: +micromatch@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== @@ -1630,19 +1318,7 @@ micromatch@^4.0.2, micromatch@^4.0.4: braces "^3.0.2" picomatch "^2.3.1" -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@1.6.0, mime@^1.3.4: +mime@^1.3.4: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== @@ -1713,11 +1389,6 @@ mocha@^9.2.1: yargs-parser "20.2.4" yargs-unparser "2.0.0" -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -1753,11 +1424,6 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - node-abi@^3.3.0: version "3.30.0" resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.30.0.tgz#d84687ad5d24ca81cdfa912a36f2c5c19b137359" @@ -1787,13 +1453,6 @@ object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -1856,11 +1515,6 @@ parse5@^7.0.0: dependencies: entities "^4.4.0" -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -1876,11 +1530,6 @@ path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== - path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -1924,14 +1573,6 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -1945,7 +1586,7 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -qs@6.11.0, qs@^6.9.1: +qs@^6.9.1: version "6.11.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== @@ -1964,21 +1605,6 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" - integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" @@ -2035,11 +1661,6 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -2071,7 +1692,7 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -2081,11 +1702,6 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - sax@>=0.6.0: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -2103,25 +1719,6 @@ semver@^7.3.5, semver@^7.3.7: dependencies: lru-cache "^6.0.0" -send@0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - serialize-javascript@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" @@ -2129,26 +1726,11 @@ serialize-javascript@6.0.0: dependencies: randombytes "^2.1.0" -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.18.0" - setimmediate@~1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -2189,11 +1771,6 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - string-width@^4.1.0, string-width@^4.2.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -2295,11 +1872,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - "traverse@>=0.3.0 <0.4": version "0.3.9" resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" @@ -2360,14 +1932,6 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - typed-rest-client@^1.8.4: version "1.8.9" resolved "https://registry.yarnpkg.com/typed-rest-client/-/typed-rest-client-1.8.9.tgz#e560226bcadfe71b0fb5c416b587f8da3b8f92d8" @@ -2392,11 +1956,6 @@ underscore@^1.12.1: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441" integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - unzipper@^0.10.11: version "0.10.11" resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.10.11.tgz#0b4991446472cbdb92ee7403909f26c2419c782e" @@ -2430,21 +1989,11 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - vsce@^2.11.0: version "2.15.0" resolved "https://registry.yarnpkg.com/vsce/-/vsce-2.15.0.tgz#4a992e78532092a34a755143c6b6c2cabcb7d729" diff --git a/package.json b/package.json index 42cdcc83d0c..59f2ba57e08 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,6 @@ "https-proxy-agent": "^7.0.2", "jschardet": "3.0.0", "kerberos": "^2.0.1", - "marked": "^12.0.1", "minimist": "^1.2.6", "native-is-elevated": "0.7.0", "native-keymap": "^3.3.4", diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index f5fa40cd920..5e45e58bb7d 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -137,7 +137,6 @@ 'tas-client-umd': `${baseNodeModulesPath}/tas-client-umd/lib/tas-client-umd.js`, // --- Start Positron --- 'he': `${baseNodeModulesPath}/he/he.js`, - 'marked': `${baseNodeModulesPath}/marked/marked.min.js`, // ------------------------------------------------------------------------------------- // If you need to debug React, you can switch to the development version by temporarily // commenting out production React and uncommenting development React. (The fixup for diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts index 9e4f50ed8a8..2c2f381057f 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts @@ -2,14 +2,12 @@ * Copyright (C) 2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ -// eslint-disable-next-line local/code-import-patterns -import { marked } from 'marked'; - import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ISettableObservable, observableValue } from 'vs/base/common/observableInternal/base'; import { URI } from 'vs/base/common/uri'; import { ITextModel } from 'vs/editor/common/model'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; @@ -28,6 +26,7 @@ abstract class PositronNotebookCellGeneral extends Disposable implements IPositr public cellModel: NotebookCellTextModel, public _instance: IPositronNotebookInstance, @ITextModelService private readonly textModelResolverService: ITextModelService, + @ICommandService readonly _commandService: ICommandService, ) { super(); } @@ -88,8 +87,9 @@ class PositronNotebookCodeCell extends PositronNotebookCellGeneral implements IP cellModel: NotebookCellTextModel, instance: IPositronNotebookInstance, textModelResolverService: ITextModelService, + commandService: ICommandService ) { - super(cellModel, instance, textModelResolverService); + super(cellModel, instance, textModelResolverService, commandService); this.executionStatus = observableValue('cellExecutionStatus', 'idle'); this.outputs = observableValue('cellOutputs', this.cellModel.outputs); @@ -119,12 +119,14 @@ class PositronNotebookMarkupCell extends PositronNotebookCellGeneral implements editorShown: ISettableObservable = observableValue('editorShown', false); override kind: CellKind.Markup = CellKind.Markup; + constructor( cellModel: NotebookCellTextModel, instance: IPositronNotebookInstance, textModelResolverService: ITextModelService, + commandService: ICommandService ) { - super(cellModel, instance, textModelResolverService); + super(cellModel, instance, textModelResolverService, commandService); // Render the markdown content and update the observable when the cell content changes this._disposableStore.add(this.cellModel.onDidChangeContent(() => { @@ -134,8 +136,13 @@ class PositronNotebookMarkupCell extends PositronNotebookCellGeneral implements this._renderContent(); } - private _renderContent(): void { - const renderedHtml = marked(this.getContent()); + private async _renderContent(): Promise { + + const renderedHtml = await this._commandService.executeCommand( + 'markdown.api.render', + this.getContent() + ); + if (typeof renderedHtml !== 'string') { throw new Error('Notebooks do not support async markdown rendering yet.'); } diff --git a/yarn.lock b/yarn.lock index 4ca5714fe7d..8c24ab96072 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7025,11 +7025,6 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -marked@^12.0.1: - version "12.0.1" - resolved "https://registry.yarnpkg.com/marked/-/marked-12.0.1.tgz#8ab1eb15560c7cbe3b011074845d7ca6c4d392b0" - integrity sha512-Y1/V2yafOcOdWQCX0XpAKXzDakPOpn6U0YLxTJs3cww6VxOzZV1BTOOYWLvH3gX38cq+iLwljHHTnMtlDfg01Q== - matchdep@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/matchdep/-/matchdep-2.0.0.tgz#c6f34834a0d8dbc3b37c27ee8bbcb27c7775582e" From a228322835cb44e032776f27d67e1bf483a31cf8 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Thu, 28 Mar 2024 12:42:47 -0400 Subject: [PATCH 25/58] Centralize the markdown rendering logic into a single component and add a few safety checks. --- .../browser/PositronNotebookCell.ts | 28 +-- .../browser/notebookCells/Markdown.tsx | 71 ++++++++ .../notebookCells/NotebookMarkupCell.tsx | 18 +- .../browser/notebookCells/interfaces.ts | 22 ++- .../useMarkupWebviewRenderer.tsx | 168 ------------------ 5 files changed, 106 insertions(+), 201 deletions(-) create mode 100644 src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx delete mode 100644 src/vs/workbench/contrib/positronNotebook/browser/notebookCells/useMarkupWebviewRenderer.tsx diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts index 2c2f381057f..95226309209 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts @@ -7,7 +7,6 @@ import { ISettableObservable, observableValue } from 'vs/base/common/observableI import { URI } from 'vs/base/common/uri'; import { ITextModel } from 'vs/editor/common/model'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { ICommandService } from 'vs/platform/commands/common/commands'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; @@ -26,7 +25,6 @@ abstract class PositronNotebookCellGeneral extends Disposable implements IPositr public cellModel: NotebookCellTextModel, public _instance: IPositronNotebookInstance, @ITextModelService private readonly textModelResolverService: ITextModelService, - @ICommandService readonly _commandService: ICommandService, ) { super(); } @@ -82,14 +80,12 @@ class PositronNotebookCodeCell extends PositronNotebookCellGeneral implements IP executionStatus: ISettableObservable; outputs: ISettableObservable; - constructor( cellModel: NotebookCellTextModel, instance: IPositronNotebookInstance, textModelResolverService: ITextModelService, - commandService: ICommandService ) { - super(cellModel, instance, textModelResolverService, commandService); + super(cellModel, instance, textModelResolverService); this.executionStatus = observableValue('cellExecutionStatus', 'idle'); this.outputs = observableValue('cellOutputs', this.cellModel.outputs); @@ -115,7 +111,7 @@ class PositronNotebookCodeCell extends PositronNotebookCellGeneral implements IP class PositronNotebookMarkupCell extends PositronNotebookCellGeneral implements IPositronNotebookMarkupCell { - renderedHtml: ISettableObservable = observableValue('renderedHTML', undefined); + markdownString: ISettableObservable = observableValue('markdownString', undefined); editorShown: ISettableObservable = observableValue('editorShown', false); override kind: CellKind.Markup = CellKind.Markup; @@ -124,29 +120,19 @@ class PositronNotebookMarkupCell extends PositronNotebookCellGeneral implements cellModel: NotebookCellTextModel, instance: IPositronNotebookInstance, textModelResolverService: ITextModelService, - commandService: ICommandService ) { - super(cellModel, instance, textModelResolverService, commandService); + super(cellModel, instance, textModelResolverService); // Render the markdown content and update the observable when the cell content changes this._disposableStore.add(this.cellModel.onDidChangeContent(() => { - this._renderContent(); + this.markdownString.set(this.getContent(), undefined); })); - this._renderContent(); + this._updateContent(); } - private async _renderContent(): Promise { - - const renderedHtml = await this._commandService.executeCommand( - 'markdown.api.render', - this.getContent() - ); - - if (typeof renderedHtml !== 'string') { - throw new Error('Notebooks do not support async markdown rendering yet.'); - } - this.renderedHtml.set(renderedHtml, undefined); + private _updateContent(): void { + this.markdownString.set(this.getContent(), undefined); } showEditor(): void { diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx new file mode 100644 index 00000000000..83e4e36bbd9 --- /dev/null +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ +import * as React from 'react'; +import { renderHtml } from 'vs/base/browser/renderHtml'; +import { DeferredImage } from './DeferredImage'; +import { useServices } from 'vs/workbench/contrib/positronNotebook/browser/ServicesProvider'; + +/** + * Component that render markdown content from a string. + * @param content: Markdown content to render in string form + * @returns React element containing the rendered markdown. + */ +export function Markdown({ content }: { content: string }) { + + const renderedHtml = useMarkdown(content); + + switch (renderedHtml.status) { + case 'error': + return
Error rendering markdown: {renderedHtml.errorMsg}
; + case 'rendering': + return
Rendering markdown...
; + case 'success': + return
{renderedHtml.nodes}
; + } +} + +type MarkdownRenderResults = { + status: 'rendering'; +} | { + status: 'success'; + nodes: React.ReactElement; +} | { + status: 'error'; + errorMsg: string; +}; + +function useMarkdown(content: string): MarkdownRenderResults { + + const services = useServices(); + const [renderedHtml, setRenderedHtml] = React.useState({ + status: 'rendering' + }); + + React.useEffect(() => { + services.commandService.executeCommand( + 'markdown.api.render', + content + ).then((html: string) => { + + setRenderedHtml( + { + status: 'success', + nodes: renderHtml(html, { + componentOverrides: { + img: DeferredImage + } + }) + }); + }) + .catch((error: Error) => { + setRenderedHtml({ + status: 'error', + errorMsg: error.message + }); + } + ); + }, [content, services.commandService]); + + return renderedHtml; +} diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx index 2c55cba0d7b..0272d511e50 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx @@ -10,12 +10,11 @@ import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; import { CellEditorMonacoWidget } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget'; import { NotebookCellActionBar } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellActionBar'; import { useObservedValue } from 'vs/workbench/contrib/positronNotebook/browser/useObservedValue'; -import { renderHtml } from 'vs/base/browser/renderHtml'; -import { DeferredImage } from './DeferredImage'; +import { Markdown } from './Markdown'; export function NotebookMarkupCell({ cell }: { cell: IPositronNotebookMarkupCell }) { - const renderedHtml = useObservedValue(cell.renderedHtml); + const markdownString = useObservedValue(cell.markdownString); const editorShown = useObservedValue(cell.editorShown); const showHideButton =
); } + + + diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts index 4b4f5792f74..d9ea1e50694 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts @@ -92,10 +92,30 @@ export function isCodeCell(cell: IPositronNotebookGeneralCell): cell is IPositro */ export interface IPositronNotebookMarkupCell extends IPositronNotebookGeneralCell { kind: CellKind.Markup; - renderedHtml: ISettableObservable; + + /** + * Observable content of cell. Equivalent to the cell's content, but as an observable + */ + markdownString: ISettableObservable; + + /** + * Observable that indicates whether the editor is currently shown + */ editorShown: ISettableObservable; + + /** + * Show the editor for this cell + */ showEditor(): void; + + /** + * Hide the editor for this cell + */ hideEditor(): void; + + /** + * Toggle the editor for this cell + */ toggleEditor(): void; } diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/useMarkupWebviewRenderer.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/useMarkupWebviewRenderer.tsx deleted file mode 100644 index beb27f90854..00000000000 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/useMarkupWebviewRenderer.tsx +++ /dev/null @@ -1,168 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (C) 2024 Posit Software, PBC. All rights reserved. - *--------------------------------------------------------------------------------------------*/ -import * as React from 'react'; -import { Schemas } from 'vs/base/common/network'; -import { dirname, } from 'vs/base/common/resources'; -import { URI } from 'vs/base/common/uri'; -import { localize } from 'vs/nls'; -import { useServices } from 'vs/workbench/contrib/positronNotebook/browser/ServicesProvider'; -import { IPositronNotebookMarkupCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; -import { useObservedValue } from 'vs/workbench/contrib/positronNotebook/browser/useObservedValue'; -import { IWebviewElement, WebviewInitInfo } from 'vs/workbench/contrib/webview/browser/webview'; -import { asWebviewUri } from 'vs/workbench/contrib/webview/common/webview'; - -export function useMarkupWebviewRenderer(cell: IPositronNotebookMarkupCell) { - const services = useServices(); - - const webviewService = services.webviewService; - const webviewContainerRef = React.useRef(null); - const renderedHtml = useObservedValue(cell.renderedHtml); - const webviewRef = React.useRef(undefined); - - // Creates webview and mounts it to the container - React.useEffect(() => { - if (!webviewContainerRef.current) { - return; - } - - const notebookRoot = getNotebookBaseUri(cell.notebookUri); - - const webviewInitInfo: WebviewInitInfo = { - title: localize('markup rendering webview', "Markup Rendering Webview"), - contentOptions: { - allowScripts: true, - allowMultipleAPIAcquire: true, - localResourceRoots: [ - notebookRoot - ] - }, - options: {}, - extension: undefined, - }; - - const webview = webviewService.createWebviewElement(webviewInitInfo); - webview.mountTo(webviewContainerRef.current); - webview.onMessage(({ message }) => { - - if (isWebviewMessage(message) && webviewContainerRef.current) { - switch (message.type) { - case 'dblclick': - cell.toggleEditor(); - break; - case 'markup-content-height': - webviewContainerRef.current.style.height = `${message.value}px`; - break; - } - } - }); - webviewRef.current = webview; - - webviewContainerRef.current.addEventListener('dblclick', () => { - cell.toggleEditor(); - }); - - - return () => { - webview?.dispose(); - }; - }, [cell.notebookUri, webviewService, cell]); - - // Sync the rendered HTML to the webview HTML - React.useEffect(() => { - const webview = webviewRef.current; - if (!webview) { - return; - } - webview.setHtml(` - - - - - - - - - ${renderedHtml || '

No content

'} - - - `); - }, [cell.notebookUri, renderedHtml]); - - return webviewContainerRef; -} - - -function getNotebookBaseUri(notebookUri: URI) { - if (notebookUri.scheme === Schemas.untitled) { - // TODO: Use workspace context service to set the base URI to workspace root - throw new Error('Have not yet implemented untitled notebook URIs'); - } - - return dirname(notebookUri); -} - - -type WebviewMessage = { - type: 'markup-content-height'; - value: number; -} | { - type: 'dblclick'; - value: null; -}; - - - -function isWebviewMessage(message: unknown): message is WebviewMessage { - if (typeof message !== 'object' || message === null) { - return false; - } - if (!('type' in message)) { - return false; - } - - const knownTypes: WebviewMessage['type'][] = ['markup-content-height', 'dblclick']; - - return knownTypes.includes((message as WebviewMessage).type); -} - From 0b722304e91e57082ed42f742be3d253136b618e Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Thu, 28 Mar 2024 12:52:44 -0400 Subject: [PATCH 26/58] Add basic styles to markdown rendered content taken from existing notebook markup style rendering --- .../browser/notebookCells/Markdown.css | 157 ++++++++++++++++++ .../browser/notebookCells/Markdown.tsx | 2 + 2 files changed, 159 insertions(+) create mode 100644 src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.css diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.css b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.css new file mode 100644 index 00000000000..c8f2c04a49e --- /dev/null +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.css @@ -0,0 +1,157 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +/* These styles are taken from the vscode notebook markup rendering styles found in + extensions/markdown-language-features/notebook/index.ts */ +.positron-markdown-rendered { + .emptyMarkdownCell::before { + content: var(--notebook-empty-markdown-cell-content); + font-style: italic; + opacity: 0.6; + } + + img { + max-width: 100%; + max-height: 100%; + } + + a { + text-decoration: none; + } + + a:hover { + text-decoration: underline; + } + + a:focus, + input:focus, + select:focus, + textarea:focus { + outline: 1px solid -webkit-focus-ring-color; + outline-offset: -1px; + } + + hr { + border: 0; + height: 2px; + border-bottom: 2px solid; + } + + h2, + h3, + h4, + h5, + h6 { + font-weight: normal; + } + + h1 { + font-size: 2.3em; + } + + h2 { + font-size: 2em; + } + + h3 { + font-size: 1.7em; + } + + h3 { + font-size: 1.5em; + } + + h4 { + font-size: 1.3em; + } + + h5 { + font-size: 1.2em; + } + + h1, + h2, + h3 { + font-weight: normal; + } + + div { + width: 100%; + } + + /* Adjust margin of first item in markdown cell */ + *:first-child { + margin-top: 0px; + } + + /* h1 tags don't need top margin */ + h1:first-child { + margin-top: 0; + } + + /* Removes bottom margin when only one item exists in markdown cell */ + #preview > *:only-child, + #preview > *:last-child { + margin-bottom: 0; + padding-bottom: 0; + } + + /* makes all markdown cells consistent */ + div { + min-height: var(--notebook-markdown-min-height); + } + + table { + border-collapse: collapse; + border-spacing: 0; + } + + table th, + table td { + border: 1px solid; + } + + table > thead > tr > th { + text-align: left; + border-bottom: 1px solid; + } + + table > thead > tr > th, + table > thead > tr > td, + table > tbody > tr > th, + table > tbody > tr > td { + padding: 5px 10px; + } + + table > tbody > tr + tr > td { + border-top: 1px solid; + } + + blockquote { + margin: 0 7px 0 5px; + padding: 0 16px 0 10px; + border-left-width: 5px; + border-left-style: solid; + } + + code { + font-size: 1em; + font-family: var(--vscode-editor-font-family); + } + + pre code { + line-height: 1.357em; + white-space: pre-wrap; + padding: 0; + } + + li p { + margin-bottom: 0.7em; + } + + ul, + ol { + margin-bottom: 0.7em; + } +} diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx index 83e4e36bbd9..37fbd78283e 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx @@ -1,6 +1,8 @@ /*--------------------------------------------------------------------------------------------- * Copyright (C) 2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./Markdown'; + import * as React from 'react'; import { renderHtml } from 'vs/base/browser/renderHtml'; import { DeferredImage } from './DeferredImage'; From d67f1716d4db3ce7223f261c6a4a8f516a3138fb Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Thu, 28 Mar 2024 13:04:47 -0400 Subject: [PATCH 27/58] Remove unused export from extension left-over from copy-pasting --- extensions/positron-notebooks/src/extension.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/extensions/positron-notebooks/src/extension.ts b/extensions/positron-notebooks/src/extension.ts index a0f2bb9e808..b855b0f8429 100644 --- a/extensions/positron-notebooks/src/extension.ts +++ b/extensions/positron-notebooks/src/extension.ts @@ -6,11 +6,6 @@ import path from 'path'; import * as vscode from 'vscode'; import { readFileSync } from 'fs'; -/** - * ProxyServerStyles type. - */ -export type ProxyServerStyles = { readonly [key: string]: string | number }; - /** * Activates the extension. * @param context An ExtensionContext that contains the extention context. From 65245f2822b8e5d7593a90b6266de89506a140e2 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Thu, 28 Mar 2024 13:09:47 -0400 Subject: [PATCH 28/58] Remove unneccesary space in notebook cell component --- .../positronNotebook/browser/notebookCells/NotebookCell.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx index 4d067f2bbba..c2c87754317 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx @@ -26,8 +26,6 @@ export function NotebookCell({ cell }: { } throw new Error('Unknown cell type'); - - } From bc69b2395a32b188bc82d6355e5b29dcfca1e362 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Thu, 28 Mar 2024 16:48:39 -0400 Subject: [PATCH 29/58] Switch to using markdown instead of markup for... ahem, markdown cells. --- .../contrib/positronNotebook/browser/AddCellButtons.tsx | 4 ++-- .../positronNotebook/browser/PositronNotebookCell.ts | 6 +++--- .../positronNotebook/browser/notebookCells/Markdown.css | 2 +- .../browser/notebookCells/NotebookCell.tsx | 8 ++++---- .../{NotebookMarkupCell.css => NotebookMarkdownCell.css} | 0 .../{NotebookMarkupCell.tsx => NotebookMarkdownCell.tsx} | 6 +++--- .../positronNotebook/browser/notebookCells/interfaces.ts | 8 ++++---- 7 files changed, 17 insertions(+), 17 deletions(-) rename src/vs/workbench/contrib/positronNotebook/browser/notebookCells/{NotebookMarkupCell.css => NotebookMarkdownCell.css} (100%) rename src/vs/workbench/contrib/positronNotebook/browser/notebookCells/{NotebookMarkupCell.tsx => NotebookMarkdownCell.tsx} (87%) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/AddCellButtons.tsx b/src/vs/workbench/contrib/positronNotebook/browser/AddCellButtons.tsx index 6f7570c19e9..62229fae4d1 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/AddCellButtons.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/AddCellButtons.tsx @@ -25,12 +25,12 @@ export function AddCellButtons({ index }: { index: number }) {
; diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts index 95226309209..7d1aae439a1 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts @@ -12,7 +12,7 @@ import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBr import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { CellKind, ICellOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IPositronNotebookInstance } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance'; -import { ExecutionStatus, IPositronNotebookCodeCell, IPositronNotebookGeneralCell, IPositronNotebookMarkupCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; +import { ExecutionStatus, IPositronNotebookCodeCell, IPositronNotebookGeneralCell, IPositronNotebookMarkdownCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; abstract class PositronNotebookCellGeneral extends Disposable implements IPositronNotebookGeneralCell { @@ -109,7 +109,7 @@ class PositronNotebookCodeCell extends PositronNotebookCellGeneral implements IP -class PositronNotebookMarkupCell extends PositronNotebookCellGeneral implements IPositronNotebookMarkupCell { +class PositronNotebookMarkdownCell extends PositronNotebookCellGeneral implements IPositronNotebookMarkdownCell { markdownString: ISettableObservable = observableValue('markdownString', undefined); editorShown: ISettableObservable = observableValue('editorShown', false); @@ -157,7 +157,7 @@ export function createNotebookCell(cell: NotebookCellTextModel, instance: IPosit if (cell.cellKind === CellKind.Code) { return instantiationService.createInstance(PositronNotebookCodeCell, cell, instance); } else { - return instantiationService.createInstance(PositronNotebookMarkupCell, cell, instance); + return instantiationService.createInstance(PositronNotebookMarkdownCell, cell, instance); } } diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.css b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.css index c8f2c04a49e..ca3f05ee7f2 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.css +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.css @@ -2,7 +2,7 @@ * Copyright (C) 2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ -/* These styles are taken from the vscode notebook markup rendering styles found in +/* These styles are taken from the vscode notebook markdown rendering styles found in extensions/markdown-language-features/notebook/index.ts */ .positron-markdown-rendered { .emptyMarkdownCell::before { diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx index c2c87754317..015d583898e 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx @@ -4,9 +4,9 @@ import 'vs/css!./NotebookCell'; import * as React from 'react'; -import { IPositronNotebookGeneralCell, isCodeCell, isMarkupCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; +import { IPositronNotebookGeneralCell, isCodeCell, isMarkdownCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; import { NodebookCodeCell } from './NodebookCodeCell'; -import { NotebookMarkupCell } from './NotebookMarkupCell'; +import { NotebookMarkdownCell } from './NotebookMarkdownCell'; /** @@ -21,8 +21,8 @@ export function NotebookCell({ cell }: { return ; } - if (isMarkupCell(cell)) { - return ; + if (isMarkdownCell(cell)) { + return ; } throw new Error('Unknown cell type'); diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.css b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkdownCell.css similarity index 100% rename from src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.css rename to src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkdownCell.css diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkdownCell.tsx similarity index 87% rename from src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx rename to src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkdownCell.tsx index 0272d511e50..569835ded99 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkupCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkdownCell.tsx @@ -1,10 +1,10 @@ /*--------------------------------------------------------------------------------------------- * Copyright (C) 2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./NotebookMarkupCell'; +import 'vs/css!./NotebookMarkdownCell'; import * as React from 'react'; -import { IPositronNotebookMarkupCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; +import { IPositronNotebookMarkdownCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; import { CellEditorMonacoWidget } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget'; @@ -12,7 +12,7 @@ import { NotebookCellActionBar } from 'vs/workbench/contrib/positronNotebook/bro import { useObservedValue } from 'vs/workbench/contrib/positronNotebook/browser/useObservedValue'; import { Markdown } from './Markdown'; -export function NotebookMarkupCell({ cell }: { cell: IPositronNotebookMarkupCell }) { +export function NotebookMarkdownCell({ cell }: { cell: IPositronNotebookMarkdownCell }) { const markdownString = useObservedValue(cell.markdownString); const editorShown = useObservedValue(cell.editorShown); diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts index d9ea1e50694..ffc70fd049a 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts @@ -14,7 +14,7 @@ export type ExecutionStatus = 'running' | 'pending' | 'unconfirmed' | 'idle'; /** * Wrapper class for notebook cell that exposes the properties that the UI needs to render the cell. - * This interface is extended to provide the specific properties for code and markup cells. + * This interface is extended to provide the specific properties for code and markdown cells. */ export interface IPositronNotebookGeneralCell extends Disposable { @@ -88,9 +88,9 @@ export function isCodeCell(cell: IPositronNotebookGeneralCell): cell is IPositro /** - * Cell that contains markup content + * Cell that contains markdown content */ -export interface IPositronNotebookMarkupCell extends IPositronNotebookGeneralCell { +export interface IPositronNotebookMarkdownCell extends IPositronNotebookGeneralCell { kind: CellKind.Markup; /** @@ -119,7 +119,7 @@ export interface IPositronNotebookMarkupCell extends IPositronNotebookGeneralCel toggleEditor(): void; } -export function isMarkupCell(cell: IPositronNotebookGeneralCell): cell is IPositronNotebookMarkupCell { +export function isMarkdownCell(cell: IPositronNotebookGeneralCell): cell is IPositronNotebookMarkdownCell { return cell.kind === CellKind.Markup; } From 7f405886c9c189a468da94d013984af78eb6042f Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Thu, 28 Mar 2024 16:50:08 -0400 Subject: [PATCH 30/58] Switch to simpler name for general purpose notebook cell. --- .../browser/PositronNotebookCell.ts | 4 ++-- .../browser/PositronNotebookInstance.ts | 24 +++++++++---------- .../notebookCells/CellEditorMonacoWidget.tsx | 6 ++--- .../browser/notebookCells/NotebookCell.tsx | 4 ++-- .../notebookCells/NotebookCellActionBar.tsx | 4 ++-- .../browser/notebookCells/interfaces.ts | 10 ++++---- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts index 7d1aae439a1..aae02f2627e 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts @@ -12,10 +12,10 @@ import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBr import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { CellKind, ICellOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IPositronNotebookInstance } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance'; -import { ExecutionStatus, IPositronNotebookCodeCell, IPositronNotebookGeneralCell, IPositronNotebookMarkdownCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; +import { ExecutionStatus, IPositronNotebookCodeCell, IPositronNotebookCell, IPositronNotebookMarkdownCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; -abstract class PositronNotebookCellGeneral extends Disposable implements IPositronNotebookGeneralCell { +abstract class PositronNotebookCellGeneral extends Disposable implements IPositronNotebookCell { kind!: CellKind; // Not marked as private so we can access it in subclasses diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts index dece39b1c48..23cf18c2ab0 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts @@ -24,7 +24,7 @@ import { createNotebookCell } from 'vs/workbench/contrib/positronNotebook/browse import { PositronNotebookEditorInput } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditorInput'; import { BaseCellEditorOptions } from './BaseCellEditorOptions'; import * as DOM from 'vs/base/browser/dom'; -import { IPositronNotebookCodeCell, IPositronNotebookGeneralCell, isCodeCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; +import { IPositronNotebookCodeCell, IPositronNotebookCell, isCodeCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; enum KernelStatus { @@ -52,7 +52,7 @@ export interface IPositronNotebookInstance { /** * The cells that make up the notebook */ - cells: ISettableObservable; + cells: ISettableObservable; /** * Status of kernel for the notebook. @@ -62,7 +62,7 @@ export interface IPositronNotebookInstance { /** * The currently selected cells. Typically a single cell but can be multiple cells. */ - selectedCells: IPositronNotebookGeneralCell[]; + selectedCells: IPositronNotebookCell[]; /** * Has the notebook instance been disposed? @@ -75,7 +75,7 @@ export interface IPositronNotebookInstance { * Run the given cells * @param cells The cells to run */ - runCells(cells: IPositronNotebookGeneralCell[]): Promise; + runCells(cells: IPositronNotebookCell[]): Promise; /** * Run the selected cells @@ -95,7 +95,7 @@ export interface IPositronNotebookInstance { /** * Delete a cell from the notebook */ - deleteCell(cell: IPositronNotebookGeneralCell): void; + deleteCell(cell: IPositronNotebookCell): void; /** * Attach a view model to this instance @@ -123,17 +123,17 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot private _identifier: string = `Positron Notebook | NotebookInstance(${PositronNotebookInstance.count++}) |`; - selectedCells: IPositronNotebookGeneralCell[] = []; + selectedCells: IPositronNotebookCell[] = []; /** * Internal cells that we use to manage the state of the notebook */ - private _cells: IPositronNotebookGeneralCell[] = []; + private _cells: IPositronNotebookCell[] = []; /** * User facing cells wrapped in an observerable for the UI to react to changes */ - cells: ISettableObservable; + cells: ISettableObservable; /** * Status of kernel for the notebook. @@ -243,7 +243,7 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot ) { super(); - this.cells = observableValue('positronNotebookCells', this._cells); + this.cells = observableValue('positronNotebookCells', this._cells); this.kernelStatus = observableValue('positronNotebookKernelStatus', KernelStatus.Uninitialized); this.isReadOnly = this.creationOptions?.isReadOnly ?? false; @@ -334,7 +334,7 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot } - async runCells(cells: IPositronNotebookGeneralCell[]): Promise { + async runCells(cells: IPositronNotebookCell[]): Promise { if (!cells) { throw new Error(localize('noCells', "No cells to run")); @@ -355,7 +355,7 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot * @param cells Cells to run * @returns */ - private async _runCells(cells: IPositronNotebookGeneralCell[]): Promise { + private async _runCells(cells: IPositronNotebookCell[]): Promise { // Filter so we're only working with code cells. const codeCells = cells.filter(cell => isCodeCell(cell)) as IPositronNotebookCodeCell[]; this._logService.info(this._identifier, '_runCells'); @@ -406,7 +406,7 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot ); } - deleteCell(cell: IPositronNotebookGeneralCell): void { + deleteCell(cell: IPositronNotebookCell): void { if (!this._textModel) { throw new Error(localize('noModelForDelete', "No model for notebook to delete cell from")); } diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx index cef5098d02c..efab6037235 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.tsx @@ -10,7 +10,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { CellEditorOptions } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellEditorOptions'; import { useNotebookInstance } from 'vs/workbench/contrib/positronNotebook/browser/NotebookInstanceProvider'; import { useServices } from 'vs/workbench/contrib/positronNotebook/browser/ServicesProvider'; -import { IPositronNotebookGeneralCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; +import { IPositronNotebookCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; import { observeValue } from 'vs/workbench/contrib/positronNotebook/common/utils/observeValue'; @@ -19,7 +19,7 @@ import { observeValue } from 'vs/workbench/contrib/positronNotebook/common/utils * @param opts.cell Cell to be shown and edited in the editor widget * @returns An editor widget for the cell */ -export function CellEditorMonacoWidget({ cell }: { cell: IPositronNotebookGeneralCell }) { +export function CellEditorMonacoWidget({ cell }: { cell: IPositronNotebookCell }) { const { editorPartRef } = useCellEditorWidget(cell); return
; } @@ -30,7 +30,7 @@ export function CellEditorMonacoWidget({ cell }: { cell: IPositronNotebookGenera * @param cell Cell whose editor is to be created * @returns Refs to place the editor and the wrapping div */ -export function useCellEditorWidget(cell: IPositronNotebookGeneralCell) { +export function useCellEditorWidget(cell: IPositronNotebookCell) { const services = useServices(); const instance = useNotebookInstance(); diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx index 015d583898e..b2ffb93cb89 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx @@ -4,7 +4,7 @@ import 'vs/css!./NotebookCell'; import * as React from 'react'; -import { IPositronNotebookGeneralCell, isCodeCell, isMarkdownCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; +import { IPositronNotebookCell, isCodeCell, isMarkdownCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; import { NodebookCodeCell } from './NodebookCodeCell'; import { NotebookMarkdownCell } from './NotebookMarkdownCell'; @@ -14,7 +14,7 @@ import { NotebookMarkdownCell } from './NotebookMarkdownCell'; * @param opts.cell The `PositronNotebookCell` to render */ export function NotebookCell({ cell }: { - cell: IPositronNotebookGeneralCell; + cell: IPositronNotebookCell; }) { if (isCodeCell(cell)) { diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellActionBar.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellActionBar.tsx index e8c8a7f921b..d61b061e5e4 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellActionBar.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellActionBar.tsx @@ -6,10 +6,10 @@ import 'vs/css!./NotebookCellActionBar'; import * as React from 'react'; import { localize } from 'vs/nls'; import { Button } from 'vs/base/browser/ui/positronComponents/button/button'; -import { IPositronNotebookGeneralCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; +import { IPositronNotebookCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; -export function NotebookCellActionBar({ cell, children }: { cell: IPositronNotebookGeneralCell; children: React.ReactNode }) { +export function NotebookCellActionBar({ cell, children }: { cell: IPositronNotebookCell; children: React.ReactNode }) { return
{children} diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts index ffc70fd049a..0fd3f93b2b6 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts @@ -16,7 +16,7 @@ export type ExecutionStatus = 'running' | 'pending' | 'unconfirmed' | 'idle'; * Wrapper class for notebook cell that exposes the properties that the UI needs to render the cell. * This interface is extended to provide the specific properties for code and markdown cells. */ -export interface IPositronNotebookGeneralCell extends Disposable { +export interface IPositronNotebookCell extends Disposable { /** * The kind of cell @@ -63,7 +63,7 @@ export interface IPositronNotebookGeneralCell extends Disposable { /** * Cell that contains code that can be executed */ -export interface IPositronNotebookCodeCell extends IPositronNotebookGeneralCell { +export interface IPositronNotebookCodeCell extends IPositronNotebookCell { kind: CellKind.Code; /** @@ -82,7 +82,7 @@ export interface IPositronNotebookCodeCell extends IPositronNotebookGeneralCell run(): void; } -export function isCodeCell(cell: IPositronNotebookGeneralCell): cell is IPositronNotebookCodeCell { +export function isCodeCell(cell: IPositronNotebookCell): cell is IPositronNotebookCodeCell { return cell.kind === CellKind.Code; } @@ -90,7 +90,7 @@ export function isCodeCell(cell: IPositronNotebookGeneralCell): cell is IPositro /** * Cell that contains markdown content */ -export interface IPositronNotebookMarkdownCell extends IPositronNotebookGeneralCell { +export interface IPositronNotebookMarkdownCell extends IPositronNotebookCell { kind: CellKind.Markup; /** @@ -119,7 +119,7 @@ export interface IPositronNotebookMarkdownCell extends IPositronNotebookGeneralC toggleEditor(): void; } -export function isMarkdownCell(cell: IPositronNotebookGeneralCell): cell is IPositronNotebookMarkdownCell { +export function isMarkdownCell(cell: IPositronNotebookCell): cell is IPositronNotebookMarkdownCell { return cell.kind === CellKind.Markup; } From bc2640e0cb072ba361d8de7bfe05299975897f7a Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Thu, 28 Mar 2024 16:55:13 -0400 Subject: [PATCH 31/58] Move type-guard functions into main positron notebook cell class --- .../browser/PositronNotebookCell.ts | 9 +++++++++ .../browser/PositronNotebookInstance.ts | 4 ++-- .../browser/notebookCells/NotebookCell.tsx | 6 +++--- .../browser/notebookCells/interfaces.ts | 17 ++++++++++------- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts index aae02f2627e..14108986c71 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts @@ -72,6 +72,15 @@ abstract class PositronNotebookCellGeneral extends Disposable implements IPositr this._disposableStore.dispose(); super.dispose(); } + + isMarkdownCell(): this is IPositronNotebookMarkdownCell { + return this.kind === CellKind.Markup; + } + + isCodeCell(): this is IPositronNotebookCodeCell { + return this.kind === CellKind.Code; + } + } diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts index 23cf18c2ab0..b98b8800ba5 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts @@ -24,7 +24,7 @@ import { createNotebookCell } from 'vs/workbench/contrib/positronNotebook/browse import { PositronNotebookEditorInput } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditorInput'; import { BaseCellEditorOptions } from './BaseCellEditorOptions'; import * as DOM from 'vs/base/browser/dom'; -import { IPositronNotebookCodeCell, IPositronNotebookCell, isCodeCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; +import { IPositronNotebookCodeCell, IPositronNotebookCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; enum KernelStatus { @@ -357,7 +357,7 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot */ private async _runCells(cells: IPositronNotebookCell[]): Promise { // Filter so we're only working with code cells. - const codeCells = cells.filter(cell => isCodeCell(cell)) as IPositronNotebookCodeCell[]; + const codeCells = cells.filter(cell => cell.isCodeCell()) as IPositronNotebookCodeCell[]; this._logService.info(this._identifier, '_runCells'); if (!this._textModel) { diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx index b2ffb93cb89..f7efe91b156 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCell.tsx @@ -4,7 +4,7 @@ import 'vs/css!./NotebookCell'; import * as React from 'react'; -import { IPositronNotebookCell, isCodeCell, isMarkdownCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; +import { IPositronNotebookCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; import { NodebookCodeCell } from './NodebookCodeCell'; import { NotebookMarkdownCell } from './NotebookMarkdownCell'; @@ -17,11 +17,11 @@ export function NotebookCell({ cell }: { cell: IPositronNotebookCell; }) { - if (isCodeCell(cell)) { + if (cell.isCodeCell()) { return ; } - if (isMarkdownCell(cell)) { + if (cell.isMarkdownCell()) { return ; } diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts index 0fd3f93b2b6..4f36c24f324 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts @@ -57,6 +57,16 @@ export interface IPositronNotebookCell extends Disposable { * Delete this cell */ delete(): void; + + /** + * Type guard for checking if cell is a markdown cell + */ + isMarkdownCell(): this is IPositronNotebookMarkdownCell; + + /** + * Type guard for checking if cell is a code cell + */ + isCodeCell(): this is IPositronNotebookCodeCell; } @@ -82,9 +92,6 @@ export interface IPositronNotebookCodeCell extends IPositronNotebookCell { run(): void; } -export function isCodeCell(cell: IPositronNotebookCell): cell is IPositronNotebookCodeCell { - return cell.kind === CellKind.Code; -} /** @@ -119,7 +126,3 @@ export interface IPositronNotebookMarkdownCell extends IPositronNotebookCell { toggleEditor(): void; } -export function isMarkdownCell(cell: IPositronNotebookCell): cell is IPositronNotebookMarkdownCell { - return cell.kind === CellKind.Markup; -} - From 43b99e1316cab558b8da46dbdd29d06b509c9128 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Thu, 28 Mar 2024 17:07:11 -0400 Subject: [PATCH 32/58] Make "running" a cell for markdown cells the equivalent of toggling the editor. Remove unneccesary open and close methods as well. --- .../browser/PositronNotebookCell.ts | 16 +++++++-------- .../notebookCells/NotebookMarkdownCell.tsx | 20 +++++++------------ .../browser/notebookCells/interfaces.ts | 20 ++++++------------- 3 files changed, 21 insertions(+), 35 deletions(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts index 14108986c71..49f2a0fef92 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCell.ts @@ -68,6 +68,9 @@ abstract class PositronNotebookCellGeneral extends Disposable implements IPositr this._instance.deleteCell(this); } + // Add placeholder run method to be overridden by subclasses + abstract run(): void; + override dispose(): void { this._disposableStore.dispose(); super.dispose(); @@ -80,7 +83,6 @@ abstract class PositronNotebookCellGeneral extends Disposable implements IPositr isCodeCell(): this is IPositronNotebookCodeCell { return this.kind === CellKind.Code; } - } @@ -110,7 +112,7 @@ class PositronNotebookCodeCell extends PositronNotebookCellGeneral implements IP ); } - run(): void { + override run(): void { this._instance.runCells([this]); } } @@ -144,15 +146,13 @@ class PositronNotebookMarkdownCell extends PositronNotebookCellGeneral implement this.markdownString.set(this.getContent(), undefined); } - showEditor(): void { - this.editorShown.set(true, undefined); - } - hideEditor(): void { - this.editorShown.set(false, undefined); - } toggleEditor(): void { this.editorShown.set(!this.editorShown.get(), undefined); } + + override run(): void { + this.toggleEditor(); + } } /** diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkdownCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkdownCell.tsx index 569835ded99..4c0313ef3f9 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkdownCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkdownCell.tsx @@ -11,28 +11,22 @@ import { CellEditorMonacoWidget } from 'vs/workbench/contrib/positronNotebook/br import { NotebookCellActionBar } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellActionBar'; import { useObservedValue } from 'vs/workbench/contrib/positronNotebook/browser/useObservedValue'; import { Markdown } from './Markdown'; +import { localize } from 'vs/nls'; export function NotebookMarkdownCell({ cell }: { cell: IPositronNotebookMarkdownCell }) { const markdownString = useObservedValue(cell.markdownString); const editorShown = useObservedValue(cell.editorShown); - const showHideButton = ; - return (
- {showHideButton} +
{editorShown ? : null} diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts index 4f36c24f324..48a46c86d61 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces.ts @@ -58,6 +58,11 @@ export interface IPositronNotebookCell extends Disposable { */ delete(): void; + /** + * Run this cell + */ + run(): void; + /** * Type guard for checking if cell is a markdown cell */ @@ -86,10 +91,7 @@ export interface IPositronNotebookCodeCell extends IPositronNotebookCell { */ outputs: ISettableObservable; - /** - * Run this cell - */ - run(): void; + } @@ -110,16 +112,6 @@ export interface IPositronNotebookMarkdownCell extends IPositronNotebookCell { */ editorShown: ISettableObservable; - /** - * Show the editor for this cell - */ - showEditor(): void; - - /** - * Hide the editor for this cell - */ - hideEditor(): void; - /** * Toggle the editor for this cell */ From 322c9326a0a91bcb724b7f21ccf0cfc1009a9578 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Thu, 28 Mar 2024 17:09:41 -0400 Subject: [PATCH 33/58] Update cell running method to "run" markdown cells as well as code cells --- .../browser/PositronNotebookInstance.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts index b98b8800ba5..2ad1795cc7d 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookInstance.ts @@ -24,7 +24,7 @@ import { createNotebookCell } from 'vs/workbench/contrib/positronNotebook/browse import { PositronNotebookEditorInput } from 'vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditorInput'; import { BaseCellEditorOptions } from './BaseCellEditorOptions'; import * as DOM from 'vs/base/browser/dom'; -import { IPositronNotebookCodeCell, IPositronNotebookCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; +import { IPositronNotebookCell } from 'vs/workbench/contrib/positronNotebook/browser/notebookCells/interfaces'; enum KernelStatus { @@ -357,7 +357,7 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot */ private async _runCells(cells: IPositronNotebookCell[]): Promise { // Filter so we're only working with code cells. - const codeCells = cells.filter(cell => cell.isCodeCell()) as IPositronNotebookCodeCell[]; + const codeCells = cells; this._logService.info(this._identifier, '_runCells'); if (!this._textModel) { @@ -367,7 +367,9 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot this._trySetupKernel(); for (const cell of codeCells) { - cell.executionStatus.set('running', undefined); + if (cell.isCodeCell()) { + cell.executionStatus.set('running', undefined); + } } const hasExecutions = [...cells].some(cell => Boolean(this.notebookExecutionStateService.getCellExecution(cell.uri))); @@ -379,7 +381,9 @@ export class PositronNotebookInstance extends Disposable implements IPositronNot await this.notebookExecutionService.executeNotebookCells(this._textModel, Array.from(cells).map(c => c.cellModel), this._contextKeyService); for (const cell of codeCells) { - cell.executionStatus.set('idle', undefined); + if (cell.isCodeCell()) { + cell.executionStatus.set('idle', undefined); + } } } From 5a5ea7fdd210f1e0e6a2c1e290aa17a238d61634 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Thu, 28 Mar 2024 17:16:23 -0400 Subject: [PATCH 34/58] Switch to async version of readfile. --- extensions/positron-notebooks/src/extension.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/extensions/positron-notebooks/src/extension.ts b/extensions/positron-notebooks/src/extension.ts index b855b0f8429..52f615b3a37 100644 --- a/extensions/positron-notebooks/src/extension.ts +++ b/extensions/positron-notebooks/src/extension.ts @@ -4,7 +4,7 @@ import path from 'path'; import * as vscode from 'vscode'; -import { readFileSync } from 'fs'; +import { readFile } from 'fs'; /** * Activates the extension. @@ -15,16 +15,22 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push( vscode.commands.registerCommand( 'positronNotebookHelpers.convertImageToBase64', - async (imageSrc: string, baseLoc: string) => { + async (imageSrc: string, baseLoc: string) => new Promise((resolve) => { const imageType = path.extname(imageSrc).slice(1); try { - const data = readFileSync(path.join(baseLoc, imageSrc)); - return `data:image/${imageType};base64,${data.toString('base64')}`; + readFile(path.join(baseLoc, imageSrc), (err, data) => { + if (err) { + console.error(err); + resolve(null); + } + resolve(`data:image/${imageType};base64,${data.toString('base64')}`); + }); } catch (e) { console.error(e); return null; } - } + }) ) ); } + From d4af14eb5ae67fa9183c9703f2c38420393b5504 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Thu, 28 Mar 2024 17:48:11 -0400 Subject: [PATCH 35/58] Debounce error messages and also fix bug where errored readFile calls still tried to convert to base64 --- .../positron-notebooks/src/extension.ts | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/extensions/positron-notebooks/src/extension.ts b/extensions/positron-notebooks/src/extension.ts index 52f615b3a37..2a46ac6fd9f 100644 --- a/extensions/positron-notebooks/src/extension.ts +++ b/extensions/positron-notebooks/src/extension.ts @@ -6,6 +6,9 @@ import path from 'path'; import * as vscode from 'vscode'; import { readFile } from 'fs'; +// Make a debounced error logger function so we don't spam the console with errors as a user is +// typing in a file name. +const errorLogger = debouncedError(300); /** * Activates the extension. * @param context An ExtensionContext that contains the extention context. @@ -20,13 +23,17 @@ export function activate(context: vscode.ExtensionContext) { try { readFile(path.join(baseLoc, imageSrc), (err, data) => { if (err) { - console.error(err); + errorLogger(err); resolve(null); + } else if (!data) { + errorLogger('No data found.'); + resolve(null); + } else { + resolve(`data:image/${imageType};base64,${data.toString('base64')}`); } - resolve(`data:image/${imageType};base64,${data.toString('base64')}`); }); } catch (e) { - console.error(e); + errorLogger(e); return null; } }) @@ -34,3 +41,35 @@ export function activate(context: vscode.ExtensionContext) { ); } +/** + * Simple debounced error logger. + * @param wait The time to wait before logging the error. + * @returns A debounced error logger function. + */ +function debouncedError(wait: number) { + // In case we want to swap for a different log function + const errorFn = console.error; + let timeout: ReturnType | null = null; + + function debounceWrapper(...args: Parameters): void { + if (!wait) { + errorFn(...args); + return; + } + + // Reset the timeout + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + + timeout = setTimeout(function () { + timeout = null; + errorFn(...args); + }, wait); + + return; + } + + return debounceWrapper; +} From 6227d04774a70344d2d9a8cef04382cdf89dae3a Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Thu, 28 Mar 2024 18:08:26 -0400 Subject: [PATCH 36/58] Add ability to open links using opener service --- .../browser/PositronNotebookEditor.tsx | 3 +++ .../browser/ServicesProvider.tsx | 6 ++++++ .../browser/notebookCells/Markdown.tsx | 18 ++++++++++++++++-- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditor.tsx b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditor.tsx index 65b083f0f8b..6b9a82dd6d2 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditor.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookEditor.tsx @@ -52,6 +52,7 @@ import { PositronNotebookEditorInput } from './PositronNotebookEditorInput'; import { ILogService } from 'vs/platform/log/common/log'; import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; interface NotebookLayoutInfo { @@ -115,6 +116,7 @@ export class PositronNotebookEditor extends EditorPane { @IContextKeyService private readonly contextKeyService: IContextKeyService, @ILogService private readonly _logService: ILogService, @ICommandService private readonly _commandService: ICommandService, + @IOpenerService private readonly _openerService: IOpenerService ) { // Call the base class's constructor. super( @@ -448,6 +450,7 @@ export class PositronNotebookEditor extends EditorPane { webviewService: this._webviewService, commandService: this._commandService, logService: this._logService, + openerService: this._openerService, sizeObservable: this._size, scopedContextKeyProviderCallback: container => scopedContextKeyService.createScoped(container) }}> diff --git a/src/vs/workbench/contrib/positronNotebook/browser/ServicesProvider.tsx b/src/vs/workbench/contrib/positronNotebook/browser/ServicesProvider.tsx index 619fae95620..b69009bfcd2 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/ServicesProvider.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/ServicesProvider.tsx @@ -11,6 +11,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IScopedContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; @@ -45,6 +46,11 @@ interface ServiceBundle { */ webviewService: IWebviewService; + /** + * Servicer for opening external links + */ + openerService: IOpenerService; + /** * Service for envoking commands from extensions */ diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx index 37fbd78283e..6ff4cad66eb 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx @@ -49,13 +49,13 @@ function useMarkdown(content: string): MarkdownRenderResults { 'markdown.api.render', content ).then((html: string) => { - setRenderedHtml( { status: 'success', nodes: renderHtml(html, { componentOverrides: { - img: DeferredImage + img: DeferredImage, + a: ExternalLink, } }) }); @@ -71,3 +71,17 @@ function useMarkdown(content: string): MarkdownRenderResults { return renderedHtml; } + +// eslint-disable-next-line react/prop-types +function ExternalLink({ href = 'no-source', ...props }: React.ComponentPropsWithoutRef<'a'>) { + const services = useServices(); + + return { + e.preventDefault(); + services.openerService.open(href); + }} + />; +} From dd3f53fe990022e6aac02ec4d4840772b76dd283 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Thu, 28 Mar 2024 18:26:07 -0400 Subject: [PATCH 37/58] Consolidate html link clicking logic into same componentOverrides field that is used for image loading. Also update modalDialogs to use this new approach. --- src/vs/base/browser/renderHtml.tsx | 26 -------------- .../browser/ui/ExternalLink/ExternalLink.tsx | 35 +++++++++++++++++++ .../browser/positronModalDialogs.tsx | 5 ++- .../browser/notebookCells/Markdown.tsx | 18 ++-------- 4 files changed, 42 insertions(+), 42 deletions(-) create mode 100644 src/vs/base/browser/ui/ExternalLink/ExternalLink.tsx diff --git a/src/vs/base/browser/renderHtml.tsx b/src/vs/base/browser/renderHtml.tsx index 40cec3771d8..9790492923a 100644 --- a/src/vs/base/browser/renderHtml.tsx +++ b/src/vs/base/browser/renderHtml.tsx @@ -10,13 +10,6 @@ import * as React from 'react'; * Options for rendering HTML to React elements. */ interface HTMLRendererOptions { - /** - * Callback for when a link is clicked. Typically used to open the link with - * the opener service. - * @param url The URL of the link that was clicked. Pulled from the href attribute. - */ - onLinkClick?: (url: string) => void; - /** * Component overrides for the HTML renderer. * Keyed by the node name (e.g. `'img'`) and the value is a component that can replace the @@ -63,25 +56,6 @@ export const renderHtml = (html: string, opts: HTMLRendererOptions = {}): React. // currently ignored. return undefined; } else if (node.type === 'tag' && node.children) { - // If we are looking at a link tag, then we want to replace the href with an onClick - // event that will call the onLinkClick callback. This typically will be used to open - // the link with the opener service. - if (node.name === 'a' && node.attrs && typeof node.attrs['href'] === 'string') { - // Note the use of `note.attrs` here. This is because not all tags have the href - // attribute and typescript doesn't like it if we look for it on the stricter - // `React.DOMAttributes` type. - const href = node.attrs['href']; - - if (opts.onLinkClick) { - // We know we wont be overwriting the onClick event here because the parser - // doesn't allow for `on*` attributes to be parsed. - nodeAttrs['onClick'] = ((e: React.MouseEvent) => { - opts.onLinkClick!(href); - e.preventDefault(); - }); - } - } - if (node.children.length === 1 && node.children[0].type === 'text') { // If this is a tag with a single text child, create a React element // for the tag and its text content. diff --git a/src/vs/base/browser/ui/ExternalLink/ExternalLink.tsx b/src/vs/base/browser/ui/ExternalLink/ExternalLink.tsx new file mode 100644 index 00000000000..fc19aab7822 --- /dev/null +++ b/src/vs/base/browser/ui/ExternalLink/ExternalLink.tsx @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ +import * as React from 'react'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; + + +interface ExternalLinkProps extends React.ComponentPropsWithoutRef<'a'> { + /** + * The opener service to use to open the link + */ + openerService: IOpenerService; +} +/** + * Special link that opens in the opener service. Used to make links that behave like normal links + * while in the UI/React layer. + * @param props The props for the link with the opener service added. + * @returns The rendered link component that opens in the opener service. + */ +export function ExternalLink(props: ExternalLinkProps) { + // eslint-disable-next-line react/prop-types + const { href, openerService, ...otherProps } = props; + + return { + if (!href) { + return; + } + e.preventDefault(); + openerService.open(href); + }} + />; +} diff --git a/src/vs/workbench/contrib/positronModalDialogs/browser/positronModalDialogs.tsx b/src/vs/workbench/contrib/positronModalDialogs/browser/positronModalDialogs.tsx index 5cd6f9e917d..0ad4de06207 100644 --- a/src/vs/workbench/contrib/positronModalDialogs/browser/positronModalDialogs.tsx +++ b/src/vs/workbench/contrib/positronModalDialogs/browser/positronModalDialogs.tsx @@ -22,6 +22,7 @@ import { PositronModalReactRenderer } from 'vs/workbench/browser/positronModalRe import { OKCancelActionBar } from 'vs/workbench/browser/positronComponents/positronModalDialog/components/okCancelActionBar'; import { OKCancelModalDialog } from 'vs/workbench/browser/positronComponents/positronModalDialog/positronOKCancelModalDialog'; import { IModalDialogPromptInstance, IPositronModalDialogsService, ShowConfirmationModalDialogOptions } from 'vs/workbench/services/positronModalDialogs/common/positronModalDialogs'; +import { ExternalLink } from 'vs/base/browser/ui/ExternalLink/ExternalLink'; /** * PositronModalDialogs class. @@ -186,7 +187,9 @@ export class PositronModalDialogs implements IPositronModalDialogsService { {renderHtml( message, { - onLinkClick: (href: string) => this._openerService.open(href) + componentOverrides: { + a: (props) => + } } )} diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx index 6ff4cad66eb..5276e6cfa38 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx @@ -7,6 +7,7 @@ import * as React from 'react'; import { renderHtml } from 'vs/base/browser/renderHtml'; import { DeferredImage } from './DeferredImage'; import { useServices } from 'vs/workbench/contrib/positronNotebook/browser/ServicesProvider'; +import { ExternalLink } from 'vs/base/browser/ui/ExternalLink/ExternalLink'; /** * Component that render markdown content from a string. @@ -55,7 +56,7 @@ function useMarkdown(content: string): MarkdownRenderResults { nodes: renderHtml(html, { componentOverrides: { img: DeferredImage, - a: ExternalLink, + a: (props) => } }) }); @@ -67,21 +68,8 @@ function useMarkdown(content: string): MarkdownRenderResults { }); } ); - }, [content, services.commandService]); + }, [content, services]); return renderedHtml; } -// eslint-disable-next-line react/prop-types -function ExternalLink({ href = 'no-source', ...props }: React.ComponentPropsWithoutRef<'a'>) { - const services = useServices(); - - return { - e.preventDefault(); - services.openerService.open(href); - }} - />; -} From 35e8e809abc5684981c3ac73f2e6ed3b3c6f8c52 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Fri, 29 Mar 2024 11:13:38 -0400 Subject: [PATCH 38/58] Update extensions/positron-notebooks/extension.webpack.config.js Co-authored-by: Jonathan Signed-off-by: Nick Strayer --- extensions/positron-notebooks/extension.webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/positron-notebooks/extension.webpack.config.js b/extensions/positron-notebooks/extension.webpack.config.js index e93bc435683..3f2a2b3d7f0 100644 --- a/extensions/positron-notebooks/extension.webpack.config.js +++ b/extensions/positron-notebooks/extension.webpack.config.js @@ -1,5 +1,5 @@ /*--------------------------------------------------------------------------------------------- - * Copyright (C) 2023 Posit Software, PBC. All rights reserved. + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ //@ts-check From 38654e240f1bfebcd93a29d12489bfdc76fdd518 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Fri, 29 Mar 2024 11:13:46 -0400 Subject: [PATCH 39/58] Update extensions/positron-notebooks/src/extension.ts Co-authored-by: Jonathan Signed-off-by: Nick Strayer --- extensions/positron-notebooks/src/extension.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/positron-notebooks/src/extension.ts b/extensions/positron-notebooks/src/extension.ts index 2a46ac6fd9f..798a3488390 100644 --- a/extensions/positron-notebooks/src/extension.ts +++ b/extensions/positron-notebooks/src/extension.ts @@ -1,5 +1,5 @@ /*--------------------------------------------------------------------------------------------- - * Copyright (C) 2022 Posit Software, PBC. All rights reserved. + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ import path from 'path'; From b7cc4eb590890f569fc1bf924bbbaecdcb77c1a7 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Fri, 29 Mar 2024 11:19:04 -0400 Subject: [PATCH 40/58] Declare command in contributes section. --- extensions/positron-notebooks/package.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/extensions/positron-notebooks/package.json b/extensions/positron-notebooks/package.json index b82fd79a070..04543bdd862 100644 --- a/extensions/positron-notebooks/package.json +++ b/extensions/positron-notebooks/package.json @@ -11,11 +11,17 @@ "Other" ], "activationEvents": [ - "onCommand:positronNotebookHelpers.convertImageToBase64", "onStartupFinished" ], + "contributes": { + "commands": [ + { + "command": "positronNotebookHelpers.convertImageToBase64", + "title": "Convert Image to Base64" + } + ] + }, "main": "./out/extension.js", - "contributes": {}, "scripts": { "vscode:prepublish": "yarn run compile", "compile": "tsc -p ./", From d5211f69ab911d382ead7ee74143bb0fda4f9803 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Fri, 29 Mar 2024 11:37:39 -0400 Subject: [PATCH 41/58] Be better about mime-type mapping --- .../positron-notebooks/src/extension.ts | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/extensions/positron-notebooks/src/extension.ts b/extensions/positron-notebooks/src/extension.ts index 798a3488390..4197a6b6b40 100644 --- a/extensions/positron-notebooks/src/extension.ts +++ b/extensions/positron-notebooks/src/extension.ts @@ -19,7 +19,13 @@ export function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand( 'positronNotebookHelpers.convertImageToBase64', async (imageSrc: string, baseLoc: string) => new Promise((resolve) => { - const imageType = path.extname(imageSrc).slice(1); + const fileExtension = path.extname(imageSrc).slice(1); + const mimeType = mimeTypeMap[fileExtension]; + if (!mimeType) { + errorLogger(`Unsupported file type: "${fileExtension}."`); + resolve(null); + return; + } try { readFile(path.join(baseLoc, imageSrc), (err, data) => { if (err) { @@ -29,7 +35,7 @@ export function activate(context: vscode.ExtensionContext) { errorLogger('No data found.'); resolve(null); } else { - resolve(`data:image/${imageType};base64,${data.toString('base64')}`); + resolve(`data:${mimeType};base64,${data.toString('base64')}`); } }); } catch (e) { @@ -73,3 +79,24 @@ function debouncedError(wait: number) { return debounceWrapper; } + + +/** + * Map image file extension to MIME type. + * + * Supports all the 'image' types from [this list](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types) + */ +const mimeTypeMap: Record = { + png: 'image/png', + apng: 'image/apng', + avif: 'image/avif', + ico: 'image/vnd.microsoft.icon', + jpeg: 'image/jpeg', + jpg: 'image/jpeg', + gif: 'image/gif', + bmp: 'image/bmp', + webp: 'image/webp', + svg: 'image/svg+xml', + tiff: 'image/tiff', + tif: 'image/tiff', +}; From 8f82d6ed0ae8f000fdb007eefb851b8addc1f20f Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Fri, 29 Mar 2024 12:01:02 -0400 Subject: [PATCH 42/58] Pass responsibilities for handling bad conversions to the front-end by passing info about failure as object --- .../positron-notebooks/src/extension.ts | 72 +++++++------------ .../browser/notebookCells/DeferredImage.tsx | 42 +++++++---- 2 files changed, 56 insertions(+), 58 deletions(-) diff --git a/extensions/positron-notebooks/src/extension.ts b/extensions/positron-notebooks/src/extension.ts index 4197a6b6b40..c9112d6dbf8 100644 --- a/extensions/positron-notebooks/src/extension.ts +++ b/extensions/positron-notebooks/src/extension.ts @@ -6,9 +6,13 @@ import path from 'path'; import * as vscode from 'vscode'; import { readFile } from 'fs'; -// Make a debounced error logger function so we don't spam the console with errors as a user is -// typing in a file name. -const errorLogger = debouncedError(300); +// Make sure this matches the error message type defined where used +// (src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx) +type CoversionErrorMsg = { + status: 'error'; + message: string; +}; + /** * Activates the extension. * @param context An ExtensionContext that contains the extention context. @@ -18,68 +22,44 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push( vscode.commands.registerCommand( 'positronNotebookHelpers.convertImageToBase64', - async (imageSrc: string, baseLoc: string) => new Promise((resolve) => { + async (imageSrc: string, baseLoc: string) => new Promise((resolve) => { + const fullImagePath = path.join(baseLoc, imageSrc); const fileExtension = path.extname(imageSrc).slice(1); const mimeType = mimeTypeMap[fileExtension]; if (!mimeType) { - errorLogger(`Unsupported file type: "${fileExtension}."`); - resolve(null); + resolve({ + status: 'error', + message: `Unsupported file type: "${fileExtension}."`, + }); return; } try { - readFile(path.join(baseLoc, imageSrc), (err, data) => { + readFile(fullImagePath, (err, data) => { if (err) { - errorLogger(err); - resolve(null); + resolve({ + status: 'error', + message: err.message, + }); } else if (!data) { - errorLogger('No data found.'); - resolve(null); + resolve({ + status: 'error', + message: `No data found in file "${fullImagePath}."`, + }); } else { resolve(`data:${mimeType};base64,${data.toString('base64')}`); } }); } catch (e) { - errorLogger(e); - return null; + return { + type: 'error', + message: e instanceof Error ? e.message : `Error occured while converting image ${fullImagePath} to base64.`, + }; } }) ) ); } -/** - * Simple debounced error logger. - * @param wait The time to wait before logging the error. - * @returns A debounced error logger function. - */ -function debouncedError(wait: number) { - // In case we want to swap for a different log function - const errorFn = console.error; - let timeout: ReturnType | null = null; - - function debounceWrapper(...args: Parameters): void { - if (!wait) { - errorFn(...args); - return; - } - - // Reset the timeout - if (timeout) { - clearTimeout(timeout); - timeout = null; - } - - timeout = setTimeout(function () { - timeout = null; - errorFn(...args); - }, wait); - - return; - } - - return debounceWrapper; -} - /** * Map image file extension to MIME type. diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx index eb9223bdd20..703905beda4 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx @@ -10,6 +10,23 @@ import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { dirname } from 'vs/base/common/resources'; +/** + * This should match the error message defined in the command definition + * (extensions/positron-notebooks/src/extension.ts) + */ +type CoversionErrorMsg = { + status: 'error'; + message: string; +}; + +/** + * Predicate function to allow us to be safe with our response processing from command. + * @param x: Variable of unknown type to check if it is a `CoversionErrorMsg`. + * @returns Whether the object is a `CoversionErrorMsg`. + */ +function isConversionErrorMsg(x: unknown): x is CoversionErrorMsg { + return x !== null && typeof x === 'object' && 'status' in x && x.status === 'error' && 'message' in x; +} type ImageDataResults = { status: 'pending'; @@ -18,8 +35,9 @@ type ImageDataResults = { data: string; } | { status: 'error'; - error: string; + message: string; }; + /** * Special image component that defers loading of the image while it converts it to a data-url using * the `positronNotebookHelpers.convertImageToBase64` command. @@ -38,16 +56,18 @@ export function DeferredImage({ src = 'no-source', ...props }: React.ComponentPr services.commandService.executeCommand( 'positronNotebookHelpers.convertImageToBase64', src, baseLocation - ).then((base64: string | null) => { - if (!base64) { - services.logService.error('Failed to convert image to base64', src); - setResults({ status: 'error', error: 'Failed to convert image to base64' }); - return; + ).then((res: unknown) => { + if (typeof res === 'string') { + setResults({ status: 'success', data: res }); + } else if (isConversionErrorMsg(res)) { + services.logService.error('Failed to convert image to base64', src, res.message); + setResults(res); + } else { + services.logService.error('Unexpected response from convertImageToBase64', res); + setResults({ status: 'error', message: 'Unexpected response from convertImageToBase64' }); } - setResults({ status: 'success', data: base64 }); }); - }, [src, baseLocation, services.commandService, services.logService]); - + }, [src, baseLocation, services]); switch (results.status) { case 'pending': @@ -58,12 +78,10 @@ export function DeferredImage({ src = 'no-source', ...props }: React.ComponentPr {...props} >
; case 'error': - return ; + return ; case 'success': return ; } - - } function getNotebookBaseUri(notebookUri: URI) { From cb92717bac912a0757c07123890f7f3d4e6f1756 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Fri, 29 Mar 2024 12:32:55 -0400 Subject: [PATCH 43/58] Localize everything! --- .../browser/notebookCells/DeferredImage.tsx | 14 +++++++++----- .../browser/notebookCells/Markdown.tsx | 5 +++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx index 703905beda4..f83c5ae8753 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx @@ -9,6 +9,7 @@ import { useNotebookInstance } from 'vs/workbench/contrib/positronNotebook/brows import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { dirname } from 'vs/base/common/resources'; +import { localize } from 'vs/nls'; /** * This should match the error message defined in the command definition @@ -60,11 +61,12 @@ export function DeferredImage({ src = 'no-source', ...props }: React.ComponentPr if (typeof res === 'string') { setResults({ status: 'success', data: res }); } else if (isConversionErrorMsg(res)) { - services.logService.error('Failed to convert image to base64', src, res.message); + services.logService.error(localize('failedToConvert', 'Failed to convert image to base64'), src, res.message); setResults(res); } else { - services.logService.error('Unexpected response from convertImageToBase64', res); - setResults({ status: 'error', message: 'Unexpected response from convertImageToBase64' }); + const unexpectedResponseString = localize('unexpectedResponse', 'Unexpected response from convertImageToBase64'); + services.logService.error(unexpectedResponseString, res); + setResults({ status: 'error', message: unexpectedResponseString }); } }); }, [src, baseLocation, services]); @@ -73,12 +75,14 @@ export function DeferredImage({ src = 'no-source', ...props }: React.ComponentPr case 'pending': return
; case 'error': - return ; + // Show image tag without attempt to convert. Probably will be broken but will provide + // clue as to what's going on. + return ; case 'success': return ; } diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx index 5276e6cfa38..65e54c4c7bb 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx @@ -8,6 +8,7 @@ import { renderHtml } from 'vs/base/browser/renderHtml'; import { DeferredImage } from './DeferredImage'; import { useServices } from 'vs/workbench/contrib/positronNotebook/browser/ServicesProvider'; import { ExternalLink } from 'vs/base/browser/ui/ExternalLink/ExternalLink'; +import { localize } from 'vs/nls'; /** * Component that render markdown content from a string. @@ -20,9 +21,9 @@ export function Markdown({ content }: { content: string }) { switch (renderedHtml.status) { case 'error': - return
Error rendering markdown: {renderedHtml.errorMsg}
; + return
{localize('errorRenderingMd', 'Error rendering markdown:')} {renderedHtml.errorMsg}
; case 'rendering': - return
Rendering markdown...
; + return
{localize('renderingMd', "Rendering markdown...")}
; case 'success': return
{renderedHtml.nodes}
; } From d9d176d62083e07b6ebd6ca362d3bae9e9719435 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Fri, 29 Mar 2024 12:43:32 -0400 Subject: [PATCH 44/58] Make error handing handle entire markdown rendering command pipeline and also added a timeout. --- .../browser/notebookCells/Markdown.tsx | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx index 65e54c4c7bb..c2c6ffa05c3 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx @@ -46,13 +46,17 @@ function useMarkdown(content: string): MarkdownRenderResults { status: 'rendering' }); + React.useEffect(() => { - services.commandService.executeCommand( - 'markdown.api.render', - content - ).then((html: string) => { - setRenderedHtml( - { + // Use an async function so we get the easier-to-read syntax of `await` instead of `.then()` + async function renderMarkdown() { + try { + const html = await services.commandService.executeCommand( + 'markdown.api.render', + content + ); + + setRenderedHtml({ status: 'success', nodes: renderHtml(html, { componentOverrides: { @@ -61,14 +65,25 @@ function useMarkdown(content: string): MarkdownRenderResults { } }) }); - }) - .catch((error: Error) => { + } catch (error) { setRenderedHtml({ status: 'error', errorMsg: error.message }); } - ); + } + // Run a timeout to catch if rendering takes too long + const timeoutMs = 3000; + const timeout = setTimeout(() => { + setRenderedHtml({ + status: 'error', + errorMsg: localize('renderingMdTimeout', "Rendering markdown timed out after {0} ms", timeoutMs) + }); + }, timeoutMs); + + renderMarkdown().finally(() => clearTimeout(timeout)); + + return () => clearTimeout(timeout); }, [content, services]); return renderedHtml; From f4b4bc9c0262c7c7604d5e470ca3f99d1c40a214 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Fri, 29 Mar 2024 12:47:00 -0400 Subject: [PATCH 45/58] Remove left-over comments in tsconfig from original copy location --- extensions/positron-notebooks/tsconfig.json | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/extensions/positron-notebooks/tsconfig.json b/extensions/positron-notebooks/tsconfig.json index aaaaaabb860..0418934fa38 100644 --- a/extensions/positron-notebooks/tsconfig.json +++ b/extensions/positron-notebooks/tsconfig.json @@ -4,26 +4,14 @@ "target": "ES2020", "outDir": "out", "esModuleInterop": true, - "lib": [ - "ES2020" - ], + "lib": ["ES2020"], "sourceMap": true, "rootDir": "src", "strict": true, - "typeRoots": [ - "./node_modules/@types" - ], + "typeRoots": ["./node_modules/@types"], "paths": { "*": ["./node_modules/*"] - }, - - /* Additional Checks */ - // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ + } }, - "include": [ - "src/**/*", - "../../src/vscode-dts/vscode.d.ts", - ] + "include": ["src/**/*", "../../src/vscode-dts/vscode.d.ts"] } From 9b34e68d322599a6c0b9c39bdc7d5776a5061f5a Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Fri, 29 Mar 2024 16:29:33 -0400 Subject: [PATCH 46/58] Add helper function for the task of running a command with a timeout and utilize in the markdown and image rendering. --- .../browser/notebookCells/DeferredImage.tsx | 45 ++++++++---- .../browser/notebookCells/Markdown.tsx | 44 ++++++------ .../common/utils/commandWithTimeout.ts | 72 +++++++++++++++++++ 3 files changed, 127 insertions(+), 34 deletions(-) create mode 100644 src/vs/workbench/contrib/positronNotebook/common/utils/commandWithTimeout.ts diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx index f83c5ae8753..204a641fab5 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx @@ -10,6 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { dirname } from 'vs/base/common/resources'; import { localize } from 'vs/nls'; +import { commandWithTimeout } from '../../common/utils/commandWithTimeout'; /** * This should match the error message defined in the command definition @@ -54,21 +55,37 @@ export function DeferredImage({ src = 'no-source', ...props }: React.ComponentPr const [results, setResults] = React.useState({ status: 'pending' }); React.useEffect(() => { - services.commandService.executeCommand( - 'positronNotebookHelpers.convertImageToBase64', - src, baseLocation - ).then((res: unknown) => { - if (typeof res === 'string') { - setResults({ status: 'success', data: res }); - } else if (isConversionErrorMsg(res)) { - services.logService.error(localize('failedToConvert', 'Failed to convert image to base64'), src, res.message); - setResults(res); - } else { - const unexpectedResponseString = localize('unexpectedResponse', 'Unexpected response from convertImageToBase64'); - services.logService.error(unexpectedResponseString, res); - setResults({ status: 'error', message: unexpectedResponseString }); + const commandTimeout = commandWithTimeout( + { + command: 'positronNotebookHelpers.convertImageToBase64', + args: [src, baseLocation], + timeoutMs: 3000, + commandService: services.commandService, + onSuccess: (payload) => { + if (typeof payload === 'string') { + setResults({ status: 'success', data: payload }); + } else if (isConversionErrorMsg(payload)) { + services.logService.error(localize('failedToConvert', 'Failed to convert image to base64:'), src, payload.message); + setResults(payload); + } else { + const unexpectedResponseString = localize('unexpectedResponse', 'Unexpected response from convertImageToBase64'); + services.logService.error(unexpectedResponseString, payload); + setResults({ status: 'error', message: unexpectedResponseString }); + } + }, + onError: (error) => { + setResults({ status: 'error', message: error.message }); + }, + onTimeout: () => { + setResults({ + status: 'error', + message: localize('imageLoadTimeout', 'Image load timeout') + }); + } } - }); + ); + + return () => clearTimeout(commandTimeout); }, [src, baseLocation, services]); switch (results.status) { diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx index c2c6ffa05c3..4622b539024 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx @@ -9,6 +9,7 @@ import { DeferredImage } from './DeferredImage'; import { useServices } from 'vs/workbench/contrib/positronNotebook/browser/ServicesProvider'; import { ExternalLink } from 'vs/base/browser/ui/ExternalLink/ExternalLink'; import { localize } from 'vs/nls'; +import { commandWithTimeout } from 'vs/workbench/contrib/positronNotebook/common/utils/commandWithTimeout'; /** * Component that render markdown content from a string. @@ -48,14 +49,20 @@ function useMarkdown(content: string): MarkdownRenderResults { React.useEffect(() => { - // Use an async function so we get the easier-to-read syntax of `await` instead of `.then()` - async function renderMarkdown() { - try { - const html = await services.commandService.executeCommand( - 'markdown.api.render', - content - ); + const renderTimeout = commandWithTimeout({ + command: 'markdown.api.render', + args: [content], + timeoutMs: 3000, + commandService: services.commandService, + onSuccess: (html) => { + if (typeof html !== 'string') { + setRenderedHtml({ + status: 'error', + errorMsg: localize('noHtmlResult', 'Failed to render markdown: No HTML result returned') + }); + return; + } setRenderedHtml({ status: 'success', nodes: renderHtml(html, { @@ -65,25 +72,22 @@ function useMarkdown(content: string): MarkdownRenderResults { } }) }); - } catch (error) { + }, + onTimeout: () => { + setRenderedHtml({ + status: 'error', + errorMsg: localize('renderingMdTimeout', "Rendering markdown timed out after {0} ms", 3000) + }); + }, + onError: (error) => { setRenderedHtml({ status: 'error', errorMsg: error.message }); } - } - // Run a timeout to catch if rendering takes too long - const timeoutMs = 3000; - const timeout = setTimeout(() => { - setRenderedHtml({ - status: 'error', - errorMsg: localize('renderingMdTimeout', "Rendering markdown timed out after {0} ms", timeoutMs) - }); - }, timeoutMs); - - renderMarkdown().finally(() => clearTimeout(timeout)); + }); - return () => clearTimeout(timeout); + return () => clearTimeout(renderTimeout); }, [content, services]); return renderedHtml; diff --git a/src/vs/workbench/contrib/positronNotebook/common/utils/commandWithTimeout.ts b/src/vs/workbench/contrib/positronNotebook/common/utils/commandWithTimeout.ts new file mode 100644 index 00000000000..cccf1569cd2 --- /dev/null +++ b/src/vs/workbench/contrib/positronNotebook/common/utils/commandWithTimeout.ts @@ -0,0 +1,72 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2024 Posit Software, PBC. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { ICommandService } from 'vs/platform/commands/common/commands'; + +interface CommandWithTimeoutArgs { + /** + * Command to run. + */ + command: string; + /** + * Arguments for command bundled into an array + */ + args: any[]; + /** + * Timeout in milliseconds. + */ + timeoutMs: number; + /** + * Command service to run the command on. + */ + commandService: ICommandService; + /** + * Function to run on successful command execution. + */ + onSuccess: (res: T) => void; + /** + * Function to run on command error. + */ + onError: (err: Error) => void; + /** + * Function to run on command timeout. + */ + onTimeout: () => void; +} + + +/** + * Run a command on the command service with a timeout. + * + * Useful for running in things like `React.useEffect()`s. + * @param args Arguments in the form of `CommandWithTimeoutArgs` + * @returns Timeout ID for the command that can be used to clear the timeout with `clearTimeout()` + */ +export function commandWithTimeout({ + command, args, timeoutMs = 5000, commandService, onSuccess, onTimeout, onError, +}: CommandWithTimeoutArgs): NodeJS.Timeout { + + const timeout = setTimeout(() => { + onTimeout(); + }, timeoutMs); + + async function runCommand() { + try { + const res = await commandService.executeCommand(command, ...args); + if (!res) { + onError(new Error('Unexpected null response from command')); + return; + } + onSuccess(res); + } catch (error) { + onError(error); + } + + clearTimeout(timeout); + } + + runCommand(); + + return timeout; +} From 0d5a5e38922516d578c81aceacb223940fad162e Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Fri, 29 Mar 2024 16:44:53 -0400 Subject: [PATCH 47/58] Restructure timeout command function to not allow callbacks to be called after timeout --- .../browser/notebookCells/DeferredImage.tsx | 9 +++++---- .../browser/notebookCells/Markdown.tsx | 10 ++++++---- .../common/utils/commandWithTimeout.ts | 20 +++++++++++++++---- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx index 204a641fab5..e9f151ee856 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx @@ -55,11 +55,12 @@ export function DeferredImage({ src = 'no-source', ...props }: React.ComponentPr const [results, setResults] = React.useState({ status: 'pending' }); React.useEffect(() => { - const commandTimeout = commandWithTimeout( + const timeoutMs = 3000; + const convertCommand = commandWithTimeout( { command: 'positronNotebookHelpers.convertImageToBase64', args: [src, baseLocation], - timeoutMs: 3000, + timeoutMs, commandService: services.commandService, onSuccess: (payload) => { if (typeof payload === 'string') { @@ -79,13 +80,13 @@ export function DeferredImage({ src = 'no-source', ...props }: React.ComponentPr onTimeout: () => { setResults({ status: 'error', - message: localize('imageLoadTimeout', 'Image load timeout') + message: localize('imageConversionTimeout', "Gather image data timed out after {0} ms", timeoutMs) }); } } ); - return () => clearTimeout(commandTimeout); + return () => convertCommand.clear(); }, [src, baseLocation, services]); switch (results.status) { diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx index 4622b539024..37936b052b9 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx @@ -50,10 +50,12 @@ function useMarkdown(content: string): MarkdownRenderResults { React.useEffect(() => { - const renderTimeout = commandWithTimeout({ + const timeoutMs = 5000; + + const renderCommand = commandWithTimeout({ command: 'markdown.api.render', args: [content], - timeoutMs: 3000, + timeoutMs, commandService: services.commandService, onSuccess: (html) => { if (typeof html !== 'string') { @@ -76,7 +78,7 @@ function useMarkdown(content: string): MarkdownRenderResults { onTimeout: () => { setRenderedHtml({ status: 'error', - errorMsg: localize('renderingMdTimeout', "Rendering markdown timed out after {0} ms", 3000) + errorMsg: localize('renderingMdTimeout', "Rendering markdown timed out after {0} ms", timeoutMs) }); }, onError: (error) => { @@ -87,7 +89,7 @@ function useMarkdown(content: string): MarkdownRenderResults { } }); - return () => clearTimeout(renderTimeout); + return () => renderCommand.clear(); }, [content, services]); return renderedHtml; diff --git a/src/vs/workbench/contrib/positronNotebook/common/utils/commandWithTimeout.ts b/src/vs/workbench/contrib/positronNotebook/common/utils/commandWithTimeout.ts index cccf1569cd2..b96ebd44f59 100644 --- a/src/vs/workbench/contrib/positronNotebook/common/utils/commandWithTimeout.ts +++ b/src/vs/workbench/contrib/positronNotebook/common/utils/commandWithTimeout.ts @@ -45,28 +45,40 @@ interface CommandWithTimeoutArgs { */ export function commandWithTimeout({ command, args, timeoutMs = 5000, commandService, onSuccess, onTimeout, onError, -}: CommandWithTimeoutArgs): NodeJS.Timeout { +}: CommandWithTimeoutArgs): { clear: () => void } { + + // Variable used to keep track of whether the timeout has been cleared. + // This is needed in the case that the command completes after the timeout time has elapsed or + // the clear function is called before the timeout is reached. + let isCleared = false; const timeout = setTimeout(() => { onTimeout(); + isCleared = true; }, timeoutMs); + const clear = () => { + isCleared = true; + clearTimeout(timeout); + }; + async function runCommand() { try { const res = await commandService.executeCommand(command, ...args); + if (isCleared) { return; } if (!res) { onError(new Error('Unexpected null response from command')); return; } onSuccess(res); } catch (error) { + if (isCleared) { return; } onError(error); } - - clearTimeout(timeout); + clear(); } runCommand(); - return timeout; + return { clear }; } From b7402a88c3ba309e972bf599d5fa6edbb4869eeb Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Mon, 1 Apr 2024 11:49:24 -0400 Subject: [PATCH 48/58] Update timeout command to use existing cancel token class and to go back to being more typical promise format --- .../browser/notebookCells/DeferredImage.tsx | 54 +++++----- .../browser/notebookCells/Markdown.tsx | 61 +++++------ .../common/utils/commandWithTimeout.ts | 100 +++++------------- 3 files changed, 74 insertions(+), 141 deletions(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx index e9f151ee856..e564ca80220 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx @@ -10,7 +10,8 @@ import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { dirname } from 'vs/base/common/resources'; import { localize } from 'vs/nls'; -import { commandWithTimeout } from '../../common/utils/commandWithTimeout'; +import { promiseWithTimeout } from '../../common/utils/commandWithTimeout'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; /** * This should match the error message defined in the command definition @@ -56,37 +57,30 @@ export function DeferredImage({ src = 'no-source', ...props }: React.ComponentPr React.useEffect(() => { const timeoutMs = 3000; - const convertCommand = commandWithTimeout( - { - command: 'positronNotebookHelpers.convertImageToBase64', - args: [src, baseLocation], - timeoutMs, - commandService: services.commandService, - onSuccess: (payload) => { - if (typeof payload === 'string') { - setResults({ status: 'success', data: payload }); - } else if (isConversionErrorMsg(payload)) { - services.logService.error(localize('failedToConvert', 'Failed to convert image to base64:'), src, payload.message); - setResults(payload); - } else { - const unexpectedResponseString = localize('unexpectedResponse', 'Unexpected response from convertImageToBase64'); - services.logService.error(unexpectedResponseString, payload); - setResults({ status: 'error', message: unexpectedResponseString }); - } - }, - onError: (error) => { - setResults({ status: 'error', message: error.message }); - }, - onTimeout: () => { - setResults({ - status: 'error', - message: localize('imageConversionTimeout', "Gather image data timed out after {0} ms", timeoutMs) - }); - } + + const tokenSource = new CancellationTokenSource(); + + + promiseWithTimeout( + services.commandService.executeCommand('positronNotebookHelpers.convertImageToBase64', src, baseLocation), + timeoutMs, + tokenSource.token + ).then((payload) => { + if (typeof payload === 'string') { + setResults({ status: 'success', data: payload }); + } else if (isConversionErrorMsg(payload)) { + services.logService.error(localize('failedToConvert', 'Failed to convert image to base64:'), src, payload.message); + setResults(payload); + } else { + const unexpectedResponseString = localize('unexpectedResponse', 'Unexpected response from convertImageToBase64'); + services.logService.error(unexpectedResponseString, payload); + setResults({ status: 'error', message: unexpectedResponseString }); } - ); + }).catch((err) => { + setResults({ status: 'error', message: err.message }); + }); - return () => convertCommand.clear(); + return () => tokenSource.cancel(); }, [src, baseLocation, services]); switch (results.status) { diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx index 37936b052b9..3594900596a 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx @@ -9,7 +9,8 @@ import { DeferredImage } from './DeferredImage'; import { useServices } from 'vs/workbench/contrib/positronNotebook/browser/ServicesProvider'; import { ExternalLink } from 'vs/base/browser/ui/ExternalLink/ExternalLink'; import { localize } from 'vs/nls'; -import { commandWithTimeout } from 'vs/workbench/contrib/positronNotebook/common/utils/commandWithTimeout'; +import { promiseWithTimeout } from 'vs/workbench/contrib/positronNotebook/common/utils/commandWithTimeout'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; /** * Component that render markdown content from a string. @@ -47,49 +48,39 @@ function useMarkdown(content: string): MarkdownRenderResults { status: 'rendering' }); - React.useEffect(() => { - const timeoutMs = 5000; + const tokenSource = new CancellationTokenSource(); - const renderCommand = commandWithTimeout({ - command: 'markdown.api.render', - args: [content], - timeoutMs, - commandService: services.commandService, - onSuccess: (html) => { - if (typeof html !== 'string') { - setRenderedHtml({ - status: 'error', - errorMsg: localize('noHtmlResult', 'Failed to render markdown: No HTML result returned') - }); - return; - } - setRenderedHtml({ - status: 'success', - nodes: renderHtml(html, { - componentOverrides: { - img: DeferredImage, - a: (props) => - } - }) - }); - }, - onTimeout: () => { - setRenderedHtml({ - status: 'error', - errorMsg: localize('renderingMdTimeout', "Rendering markdown timed out after {0} ms", timeoutMs) - }); - }, - onError: (error) => { + promiseWithTimeout( + services.commandService.executeCommand('markdown.api.render', content), + 5000, + tokenSource.token + ).then((html) => { + if (typeof html !== 'string') { setRenderedHtml({ status: 'error', - errorMsg: error.message + errorMsg: localize('noHtmlResult', 'Failed to render markdown: No HTML result returned') }); + return; } + setRenderedHtml({ + status: 'success', + nodes: renderHtml(html, { + componentOverrides: { + img: DeferredImage, + a: (props) => + } + }) + }); + }).catch((error) => { + setRenderedHtml({ + status: 'error', + errorMsg: error.message + }); }); - return () => renderCommand.clear(); + return () => tokenSource.cancel(); }, [content, services]); return renderedHtml; diff --git a/src/vs/workbench/contrib/positronNotebook/common/utils/commandWithTimeout.ts b/src/vs/workbench/contrib/positronNotebook/common/utils/commandWithTimeout.ts index b96ebd44f59..be6aae8d761 100644 --- a/src/vs/workbench/contrib/positronNotebook/common/utils/commandWithTimeout.ts +++ b/src/vs/workbench/contrib/positronNotebook/common/utils/commandWithTimeout.ts @@ -2,83 +2,31 @@ * Copyright (C) 2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ -import { ICommandService } from 'vs/platform/commands/common/commands'; - -interface CommandWithTimeoutArgs { - /** - * Command to run. - */ - command: string; - /** - * Arguments for command bundled into an array - */ - args: any[]; - /** - * Timeout in milliseconds. - */ - timeoutMs: number; - /** - * Command service to run the command on. - */ - commandService: ICommandService; - /** - * Function to run on successful command execution. - */ - onSuccess: (res: T) => void; - /** - * Function to run on command error. - */ - onError: (err: Error) => void; - /** - * Function to run on command timeout. - */ - onTimeout: () => void; -} - +import { CancellationToken } from 'vs/base/common/cancellation'; /** - * Run a command on the command service with a timeout. - * - * Useful for running in things like `React.useEffect()`s. - * @param args Arguments in the form of `CommandWithTimeoutArgs` - * @returns Timeout ID for the command that can be used to clear the timeout with `clearTimeout()` + * Function to wrap a promise in a timeout. Also allows for the unsubscribing to the promise output. + * @param promise Promise to wrap. + * @param timeoutMs Timeout in milliseconds. + * @param cancelToken Token to cancel the promise as returned by `CancellationTokenSource.token`. */ -export function commandWithTimeout({ - command, args, timeoutMs = 5000, commandService, onSuccess, onTimeout, onError, -}: CommandWithTimeoutArgs): { clear: () => void } { - - // Variable used to keep track of whether the timeout has been cleared. - // This is needed in the case that the command completes after the timeout time has elapsed or - // the clear function is called before the timeout is reached. - let isCleared = false; - - const timeout = setTimeout(() => { - onTimeout(); - isCleared = true; - }, timeoutMs); - - const clear = () => { - isCleared = true; - clearTimeout(timeout); - }; - - async function runCommand() { - try { - const res = await commandService.executeCommand(command, ...args); - if (isCleared) { return; } - if (!res) { - onError(new Error('Unexpected null response from command')); - return; - } - onSuccess(res); - } catch (error) { - if (isCleared) { return; } - onError(error); - } - clear(); - } - - runCommand(); - - return { clear }; +export function promiseWithTimeout(promise: Promise, timeoutMs: number, cancelToken: CancellationToken): Promise { + + return new Promise((resolve, reject) => { + cancelToken.onCancellationRequested(() => { + reject(new Error('Promise cancelled')); + }); + + const timeout = setTimeout(() => { + reject(new Error('Promise timed out')); + }, timeoutMs); + + promise.then((res) => { + clearTimeout(timeout); + resolve(res); + }).catch((err) => { + clearTimeout(timeout); + reject(err); + }); + }); } From ff2afc60269ca723faad6cb8b18642d8a1af9933 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Mon, 1 Apr 2024 12:06:05 -0400 Subject: [PATCH 49/58] Dont attempt to convert remote images --- .../browser/notebookCells/DeferredImage.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx index e564ca80220..335f3c71a06 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx @@ -56,11 +56,17 @@ export function DeferredImage({ src = 'no-source', ...props }: React.ComponentPr const [results, setResults] = React.useState({ status: 'pending' }); React.useEffect(() => { + + // Check for prefix of http or https to avoid converting remote images + if (src.startsWith('http://') || src.startsWith('https://')) { + setResults({ status: 'success', data: src }); + return; + } + const timeoutMs = 3000; const tokenSource = new CancellationTokenSource(); - promiseWithTimeout( services.commandService.executeCommand('positronNotebookHelpers.convertImageToBase64', src, baseLocation), timeoutMs, From a51d37989068471de33444358e3a5092842936d0 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Mon, 1 Apr 2024 12:23:18 -0400 Subject: [PATCH 50/58] Only show most recent error message for conversion and clear stale messages --- .../browser/notebookCells/DeferredImage.tsx | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx index 335f3c71a06..d8460c9deb7 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx @@ -63,30 +63,41 @@ export function DeferredImage({ src = 'no-source', ...props }: React.ComponentPr return; } - const timeoutMs = 3000; - + const conversionTimeoutMs = 3000; + const errorTimeoutMs = 1000; const tokenSource = new CancellationTokenSource(); + let delayedErrorMsg: NodeJS.Timeout; + promiseWithTimeout( services.commandService.executeCommand('positronNotebookHelpers.convertImageToBase64', src, baseLocation), - timeoutMs, + conversionTimeoutMs, tokenSource.token ).then((payload) => { if (typeof payload === 'string') { setResults({ status: 'success', data: payload }); } else if (isConversionErrorMsg(payload)) { - services.logService.error(localize('failedToConvert', 'Failed to convert image to base64:'), src, payload.message); + + delayedErrorMsg = setTimeout(() => { + services.logService.error(localize('failedToConvert', 'Failed to convert image to base64:'), src, payload.message); + }, errorTimeoutMs); + setResults(payload); } else { const unexpectedResponseString = localize('unexpectedResponse', 'Unexpected response from convertImageToBase64'); - services.logService.error(unexpectedResponseString, payload); + delayedErrorMsg = setTimeout(() => { + services.logService.error(unexpectedResponseString, payload); + }, errorTimeoutMs); setResults({ status: 'error', message: unexpectedResponseString }); } }).catch((err) => { setResults({ status: 'error', message: err.message }); }); - return () => tokenSource.cancel(); + return () => { + clearTimeout(delayedErrorMsg); + tokenSource.cancel(); + }; }, [src, baseLocation, services]); switch (results.status) { From 1ea9e77046c9bf0d40b5e09251fedee081448abc Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Mon, 1 Apr 2024 13:51:14 -0400 Subject: [PATCH 51/58] Don't pass source to error state to avoid failed `GET` request --- .../positronNotebook/browser/notebookCells/DeferredImage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx index d8460c9deb7..407b49dae1e 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx @@ -111,7 +111,7 @@ export function DeferredImage({ src = 'no-source', ...props }: React.ComponentPr case 'error': // Show image tag without attempt to convert. Probably will be broken but will provide // clue as to what's going on. - return ; + return ; case 'success': return ; } From fbf825a1c1435f3579736012cb937bbcb369693d Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Mon, 1 Apr 2024 13:54:38 -0400 Subject: [PATCH 52/58] Move `renderHtml()` into positron subfolder --- src/vs/base/browser/{ => positron}/renderHtml.tsx | 0 .../positronConsole/browser/components/activityOutputHtml.tsx | 2 +- .../positronModalDialogs/browser/positronModalDialogs.tsx | 2 +- .../contrib/positronNotebook/browser/notebookCells/Markdown.tsx | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename src/vs/base/browser/{ => positron}/renderHtml.tsx (100%) diff --git a/src/vs/base/browser/renderHtml.tsx b/src/vs/base/browser/positron/renderHtml.tsx similarity index 100% rename from src/vs/base/browser/renderHtml.tsx rename to src/vs/base/browser/positron/renderHtml.tsx diff --git a/src/vs/workbench/contrib/positronConsole/browser/components/activityOutputHtml.tsx b/src/vs/workbench/contrib/positronConsole/browser/components/activityOutputHtml.tsx index a41bf75ea85..9d73c6d758b 100644 --- a/src/vs/workbench/contrib/positronConsole/browser/components/activityOutputHtml.tsx +++ b/src/vs/workbench/contrib/positronConsole/browser/components/activityOutputHtml.tsx @@ -5,7 +5,7 @@ import 'vs/css!./activityOutputHtml'; import * as React from 'react'; import { ActivityItemOutputHtml } from 'vs/workbench/services/positronConsole/browser/classes/activityItemOutputHtml'; -import { renderHtml } from 'vs/base/browser/renderHtml'; +import { renderHtml } from 'vs/base/browser/positron/renderHtml'; // ActivityOutputHtml interface. export interface ActivityOutputHtmlProps { diff --git a/src/vs/workbench/contrib/positronModalDialogs/browser/positronModalDialogs.tsx b/src/vs/workbench/contrib/positronModalDialogs/browser/positronModalDialogs.tsx index 0ad4de06207..1b2cf7725bc 100644 --- a/src/vs/workbench/contrib/positronModalDialogs/browser/positronModalDialogs.tsx +++ b/src/vs/workbench/contrib/positronModalDialogs/browser/positronModalDialogs.tsx @@ -10,7 +10,7 @@ import * as React from 'react'; // Other dependencies. import { Emitter } from 'vs/base/common/event'; -import { renderHtml } from 'vs/base/browser/renderHtml'; +import { renderHtml } from 'vs/base/browser/positron/renderHtml'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx index 3594900596a..d5b732f7129 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx @@ -4,7 +4,7 @@ import 'vs/css!./Markdown'; import * as React from 'react'; -import { renderHtml } from 'vs/base/browser/renderHtml'; +import { renderHtml } from 'vs/base/browser/positron/renderHtml'; import { DeferredImage } from './DeferredImage'; import { useServices } from 'vs/workbench/contrib/positronNotebook/browser/ServicesProvider'; import { ExternalLink } from 'vs/base/browser/ui/ExternalLink/ExternalLink'; From ea726b71256222feb2517c6def663cb920d60643 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Mon, 1 Apr 2024 13:59:35 -0400 Subject: [PATCH 53/58] Fix name of script containing promiseWithTimeout function --- .../positronNotebook/browser/notebookCells/DeferredImage.tsx | 2 +- .../contrib/positronNotebook/browser/notebookCells/Markdown.tsx | 2 +- .../utils/{commandWithTimeout.ts => promiseWithTimeout.ts} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/vs/workbench/contrib/positronNotebook/common/utils/{commandWithTimeout.ts => promiseWithTimeout.ts} (100%) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx index 407b49dae1e..6decc5468a9 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx @@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { dirname } from 'vs/base/common/resources'; import { localize } from 'vs/nls'; -import { promiseWithTimeout } from '../../common/utils/commandWithTimeout'; +import { promiseWithTimeout } from '../../common/utils/promiseWithTimeout'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; /** diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx index d5b732f7129..1a841c0e0bd 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx @@ -9,7 +9,7 @@ import { DeferredImage } from './DeferredImage'; import { useServices } from 'vs/workbench/contrib/positronNotebook/browser/ServicesProvider'; import { ExternalLink } from 'vs/base/browser/ui/ExternalLink/ExternalLink'; import { localize } from 'vs/nls'; -import { promiseWithTimeout } from 'vs/workbench/contrib/positronNotebook/common/utils/commandWithTimeout'; +import { promiseWithTimeout } from 'vs/workbench/contrib/positronNotebook/common/utils/promiseWithTimeout'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; /** diff --git a/src/vs/workbench/contrib/positronNotebook/common/utils/commandWithTimeout.ts b/src/vs/workbench/contrib/positronNotebook/common/utils/promiseWithTimeout.ts similarity index 100% rename from src/vs/workbench/contrib/positronNotebook/common/utils/commandWithTimeout.ts rename to src/vs/workbench/contrib/positronNotebook/common/utils/promiseWithTimeout.ts From be302cb5c12fb2b30a6580ae7391d14e7c7fadd2 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Mon, 1 Apr 2024 14:01:18 -0400 Subject: [PATCH 54/58] Add slightly better error message in timeout case --- .../contrib/positronNotebook/common/utils/promiseWithTimeout.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/positronNotebook/common/utils/promiseWithTimeout.ts b/src/vs/workbench/contrib/positronNotebook/common/utils/promiseWithTimeout.ts index be6aae8d761..c8220a5a26a 100644 --- a/src/vs/workbench/contrib/positronNotebook/common/utils/promiseWithTimeout.ts +++ b/src/vs/workbench/contrib/positronNotebook/common/utils/promiseWithTimeout.ts @@ -18,7 +18,7 @@ export function promiseWithTimeout(promise: Promise, timeoutMs: number, ca }); const timeout = setTimeout(() => { - reject(new Error('Promise timed out')); + reject(new Error(`Promise timed out after ${timeoutMs}ms`)); }, timeoutMs); promise.then((res) => { From af266b074e9911d674efe0040176049b0b95c865 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Mon, 1 Apr 2024 14:13:24 -0400 Subject: [PATCH 55/58] Add styles for rendering and error messages in markdown component --- .../browser/notebookCells/Markdown.css | 24 +++++++++++-------- .../browser/notebookCells/Markdown.tsx | 4 ++-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.css b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.css index ca3f05ee7f2..a6136b98b84 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.css +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.css @@ -2,15 +2,19 @@ * Copyright (C) 2024 Posit Software, PBC. All rights reserved. *--------------------------------------------------------------------------------------------*/ -/* These styles are taken from the vscode notebook markdown rendering styles found in - extensions/markdown-language-features/notebook/index.ts */ -.positron-markdown-rendered { - .emptyMarkdownCell::before { - content: var(--notebook-empty-markdown-cell-content); - font-style: italic; - opacity: 0.6; - } +.positron-markdown-rendering { + text-align: center; + opacity: 0.6; + font-style: italic; +} +.positron-markdown-error { + color: var(--vscode-editorError-foreground, orangered); + font-style: italic; +} + +/* These styles are taken from the vscode notebook markdown rendering styles found in extensions/markdown-language-features/notebook/index.ts */ +.positron-markdown-rendered { img { max-width: 100%; max-height: 100%; @@ -99,7 +103,7 @@ /* makes all markdown cells consistent */ div { - min-height: var(--notebook-markdown-min-height); + min-height: 1rem; } table { @@ -137,7 +141,7 @@ code { font-size: 1em; - font-family: var(--vscode-editor-font-family); + font-family: var(--vscode-repl-font-family); } pre code { diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx index 1a841c0e0bd..5ec77573617 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx @@ -23,9 +23,9 @@ export function Markdown({ content }: { content: string }) { switch (renderedHtml.status) { case 'error': - return
{localize('errorRenderingMd', 'Error rendering markdown:')} {renderedHtml.errorMsg}
; + return
{localize('errorRenderingMd', 'Error rendering markdown:')} {renderedHtml.errorMsg}
; case 'rendering': - return
{localize('renderingMd', "Rendering markdown...")}
; + return
{localize('renderingMd', "Rendering markdown...")}
; case 'success': return
{renderedHtml.nodes}
; } From a59f4a7cf3dc79f9a642c670f221d475ef59f293 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Mon, 1 Apr 2024 14:58:10 -0400 Subject: [PATCH 56/58] Fix utterly unneccesary string concatenation. --- .../positronNotebook/browser/PositronNotebookComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.tsx b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.tsx index 5a1eae12d00..6b6b988982b 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookComponent.tsx @@ -33,7 +33,7 @@ export function PositronNotebookComponent() { > {localize('runAllCells', 'Run all cells')} -
+
From b829e7f53a76f1fb988273940095c96404a853cf Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Mon, 1 Apr 2024 15:12:09 -0400 Subject: [PATCH 57/58] Fix case-sensitivity of mime-type checking. --- extensions/positron-notebooks/src/extension.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/positron-notebooks/src/extension.ts b/extensions/positron-notebooks/src/extension.ts index c9112d6dbf8..e32f6d43d9c 100644 --- a/extensions/positron-notebooks/src/extension.ts +++ b/extensions/positron-notebooks/src/extension.ts @@ -25,7 +25,7 @@ export function activate(context: vscode.ExtensionContext) { async (imageSrc: string, baseLoc: string) => new Promise((resolve) => { const fullImagePath = path.join(baseLoc, imageSrc); const fileExtension = path.extname(imageSrc).slice(1); - const mimeType = mimeTypeMap[fileExtension]; + const mimeType = mimeTypeMap[fileExtension.toLowerCase()]; if (!mimeType) { resolve({ status: 'error', From 1182b4ff80c77e19203f574649a694805632afc4 Mon Sep 17 00:00:00 2001 From: Nick Strayer Date: Mon, 1 Apr 2024 15:52:32 -0400 Subject: [PATCH 58/58] Replace custom promiseWithTimeout() with existing functions --- .../browser/notebookCells/DeferredImage.tsx | 15 ++++----- .../browser/notebookCells/Markdown.tsx | 14 ++++---- .../common/utils/promiseWithTimeout.ts | 32 ------------------- 3 files changed, 13 insertions(+), 48 deletions(-) delete mode 100644 src/vs/workbench/contrib/positronNotebook/common/utils/promiseWithTimeout.ts diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx index 6decc5468a9..d6dd79ac1c7 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/DeferredImage.tsx @@ -10,8 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { dirname } from 'vs/base/common/resources'; import { localize } from 'vs/nls'; -import { promiseWithTimeout } from '../../common/utils/promiseWithTimeout'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { createCancelablePromise, raceTimeout } from 'vs/base/common/async'; /** * This should match the error message defined in the command definition @@ -65,15 +64,15 @@ export function DeferredImage({ src = 'no-source', ...props }: React.ComponentPr const conversionTimeoutMs = 3000; const errorTimeoutMs = 1000; - const tokenSource = new CancellationTokenSource(); let delayedErrorMsg: NodeJS.Timeout; - promiseWithTimeout( + const conversionCancellablePromise = createCancelablePromise(() => raceTimeout( services.commandService.executeCommand('positronNotebookHelpers.convertImageToBase64', src, baseLocation), - conversionTimeoutMs, - tokenSource.token - ).then((payload) => { + conversionTimeoutMs + )); + + conversionCancellablePromise.then((payload) => { if (typeof payload === 'string') { setResults({ status: 'success', data: payload }); } else if (isConversionErrorMsg(payload)) { @@ -96,7 +95,7 @@ export function DeferredImage({ src = 'no-source', ...props }: React.ComponentPr return () => { clearTimeout(delayedErrorMsg); - tokenSource.cancel(); + conversionCancellablePromise.cancel(); }; }, [src, baseLocation, services]); diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx index 5ec77573617..5300cc8e7e2 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/Markdown.tsx @@ -9,8 +9,7 @@ import { DeferredImage } from './DeferredImage'; import { useServices } from 'vs/workbench/contrib/positronNotebook/browser/ServicesProvider'; import { ExternalLink } from 'vs/base/browser/ui/ExternalLink/ExternalLink'; import { localize } from 'vs/nls'; -import { promiseWithTimeout } from 'vs/workbench/contrib/positronNotebook/common/utils/promiseWithTimeout'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { createCancelablePromise, raceTimeout } from 'vs/base/common/async'; /** * Component that render markdown content from a string. @@ -50,13 +49,12 @@ function useMarkdown(content: string): MarkdownRenderResults { React.useEffect(() => { - const tokenSource = new CancellationTokenSource(); - - promiseWithTimeout( + const conversionCancellablePromise = createCancelablePromise(() => raceTimeout( services.commandService.executeCommand('markdown.api.render', content), 5000, - tokenSource.token - ).then((html) => { + )); + + conversionCancellablePromise.then((html) => { if (typeof html !== 'string') { setRenderedHtml({ status: 'error', @@ -80,7 +78,7 @@ function useMarkdown(content: string): MarkdownRenderResults { }); }); - return () => tokenSource.cancel(); + return () => conversionCancellablePromise.cancel(); }, [content, services]); return renderedHtml; diff --git a/src/vs/workbench/contrib/positronNotebook/common/utils/promiseWithTimeout.ts b/src/vs/workbench/contrib/positronNotebook/common/utils/promiseWithTimeout.ts deleted file mode 100644 index c8220a5a26a..00000000000 --- a/src/vs/workbench/contrib/positronNotebook/common/utils/promiseWithTimeout.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (C) 2024 Posit Software, PBC. All rights reserved. - *--------------------------------------------------------------------------------------------*/ - -import { CancellationToken } from 'vs/base/common/cancellation'; - -/** - * Function to wrap a promise in a timeout. Also allows for the unsubscribing to the promise output. - * @param promise Promise to wrap. - * @param timeoutMs Timeout in milliseconds. - * @param cancelToken Token to cancel the promise as returned by `CancellationTokenSource.token`. - */ -export function promiseWithTimeout(promise: Promise, timeoutMs: number, cancelToken: CancellationToken): Promise { - - return new Promise((resolve, reject) => { - cancelToken.onCancellationRequested(() => { - reject(new Error('Promise cancelled')); - }); - - const timeout = setTimeout(() => { - reject(new Error(`Promise timed out after ${timeoutMs}ms`)); - }, timeoutMs); - - promise.then((res) => { - clearTimeout(timeout); - resolve(res); - }).catch((err) => { - clearTimeout(timeout); - reject(err); - }); - }); -}