/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { IListEvent, IListMouseEvent } from 'vs/base/browser/ui/list/list'; import { IListOptions, IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ScrollEvent } from 'vs/base/common/scrollable'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { IPosition } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { FindMatch, IReadonlyTextBuffer, ITextModel } from 'vs/editor/common/model'; import { ContextKeyExpr, RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; import { CellLanguageStatusBarItem, RunStateRenderer, TimerRenderer } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; import { CellViewModel, IModelDecorationsChangeAccessor, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { CellKind, IProcessedOutput, IRenderOutput, NotebookCellMetadata, NotebookDocumentMetadata, INotebookKernelInfo, IEditor, INotebookKernelInfo2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { IMenu } from 'vs/platform/actions/common/actions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export const KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED = new RawContextKey('notebookFindWidgetFocused', false); // Is Notebook export const NOTEBOOK_IS_ACTIVE_EDITOR = ContextKeyExpr.equals('activeEditor', 'workbench.editor.notebook'); // Editor keys export const NOTEBOOK_EDITOR_FOCUSED = new RawContextKey('notebookEditorFocused', false); export const NOTEBOOK_CELL_LIST_FOCUSED = new RawContextKey('notebookCellListFocused', false); export const NOTEBOOK_OUTPUT_FOCUSED = new RawContextKey('notebookOutputFocused', false); export const NOTEBOOK_EDITOR_EDITABLE = new RawContextKey('notebookEditable', true); export const NOTEBOOK_EDITOR_RUNNABLE = new RawContextKey('notebookRunnable', true); export const NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK = new RawContextKey('notebookExecuting', false); // Cell keys export const NOTEBOOK_VIEW_TYPE = new RawContextKey('notebookViewType', undefined); export const NOTEBOOK_CELL_TYPE = new RawContextKey('notebookCellType', undefined); // code, markdown export const NOTEBOOK_CELL_EDITABLE = new RawContextKey('notebookCellEditable', false); // bool export const NOTEBOOK_CELL_RUNNABLE = new RawContextKey('notebookCellRunnable', false); // bool export const NOTEBOOK_CELL_MARKDOWN_EDIT_MODE = new RawContextKey('notebookCellMarkdownEditMode', false); // bool export const NOTEBOOK_CELL_RUN_STATE = new RawContextKey('notebookCellRunState', undefined); // idle, running export const NOTEBOOK_CELL_HAS_OUTPUTS = new RawContextKey('notebookCellHasOutputs', false); // bool export const NOTEBOOK_CELL_INPUT_COLLAPSED = new RawContextKey('notebookCellInputIsCollapsed', false); // bool export const NOTEBOOK_CELL_OUTPUT_COLLAPSED = new RawContextKey('notebookCellOutputIsCollapsed', false); // bool // Kernels export const NOTEBOOK_HAS_MULTIPLE_KERNELS = new RawContextKey('notebookHasMultipleKernels', false); export interface NotebookLayoutInfo { width: number; height: number; fontInfo: BareFontInfo; } export interface NotebookLayoutChangeEvent { width?: boolean; height?: boolean; fontInfo?: boolean; } export enum CodeCellLayoutState { Uninitialized, Estimated, FromCache, Measured } export interface CodeCellLayoutInfo { readonly fontInfo: BareFontInfo | null; readonly editorHeight: number; readonly editorWidth: number; readonly totalHeight: number; readonly outputContainerOffset: number; readonly outputTotalHeight: number; readonly indicatorHeight: number; readonly bottomToolbarOffset: number; readonly layoutState: CodeCellLayoutState; } export interface CodeCellLayoutChangeEvent { editorHeight?: boolean; outputHeight?: boolean; totalHeight?: boolean; outerWidth?: number; font?: BareFontInfo; } export interface MarkdownCellLayoutInfo { readonly fontInfo: BareFontInfo | null; readonly editorWidth: number; readonly editorHeight: number; readonly bottomToolbarOffset: number; readonly totalHeight: number; } export interface MarkdownCellLayoutChangeEvent { font?: BareFontInfo; outerWidth?: number; totalHeight?: number; } export interface ICellViewModel { readonly model: NotebookCellTextModel; readonly id: string; readonly textBuffer: IReadonlyTextBuffer; collapseState: CellCollapseState; outputCollapseState: CellCollapseState; dragging: boolean; handle: number; uri: URI; language: string; cellKind: CellKind; editState: CellEditState; focusMode: CellFocusMode; getText(): string; getTextLength(): number; metadata: NotebookCellMetadata | undefined; textModel: ITextModel | undefined; hasModel(): this is IEditableCellViewModel; resolveTextModel(): Promise; getEvaluatedMetadata(documentMetadata: NotebookDocumentMetadata | undefined): NotebookCellMetadata; getSelectionsStartPosition(): IPosition[] | undefined; getCellDecorations(): INotebookCellDecorationOptions[]; } export interface IEditableCellViewModel extends ICellViewModel { textModel: ITextModel; } export interface INotebookEditorMouseEvent { readonly event: MouseEvent; readonly target: CellViewModel; } export interface INotebookEditorContribution { /** * Dispose this contribution. */ dispose(): void; /** * Store view state. */ saveViewState?(): unknown; /** * Restore view state. */ restoreViewState?(state: unknown): void; } export interface INotebookCellDecorationOptions { className?: string; outputClassName?: string; } export interface INotebookDeltaDecoration { handle: number; options: INotebookCellDecorationOptions; } export interface INotebookEditor extends IEditor { cursorNavigationMode: boolean; /** * Notebook view model attached to the current editor */ viewModel: NotebookViewModel | undefined; /** * An event emitted when the model of this editor has changed. * @event */ readonly onDidChangeModel: Event; readonly onDidFocusEditorWidget: Event; isNotebookEditor: boolean; activeKernel: INotebookKernelInfo | INotebookKernelInfo2 | undefined; multipleKernelsAvailable: boolean; readonly onDidChangeAvailableKernels: Event; readonly onDidChangeKernel: Event; isDisposed: boolean; getId(): string; getDomNode(): HTMLElement; getInnerWebview(): Webview | undefined; /** * Focus the notebook editor cell list */ focus(): void; hasFocus(): boolean; /** * Select & focus cell */ selectElement(cell: ICellViewModel): void; /** * Layout info for the notebook editor */ getLayoutInfo(): NotebookLayoutInfo; /** * Fetch the output renderers for notebook outputs. */ getOutputRenderer(): OutputRenderer; /** * Insert a new cell around `cell` */ insertNotebookCell(cell: ICellViewModel | undefined, type: CellKind, direction?: 'above' | 'below', initialText?: string, ui?: boolean): CellViewModel | null; /** * Split a given cell into multiple cells of the same type using the selection start positions. */ splitNotebookCell(cell: ICellViewModel): Promise; /** * Joins the given cell either with the cell above or the one below depending on the given direction. */ joinNotebookCells(cell: ICellViewModel, direction: 'above' | 'below', constraint?: CellKind): Promise; /** * Delete a cell from the notebook */ deleteNotebookCell(cell: ICellViewModel): Promise; /** * Move a cell up one spot */ moveCellUp(cell: ICellViewModel): Promise; /** * Move a cell down one spot */ moveCellDown(cell: ICellViewModel): Promise; /** * @deprecated Note that this method doesn't support batch operations, use #moveCellToIdx instead. * Move a cell above or below another cell */ moveCell(cell: ICellViewModel, relativeToCell: ICellViewModel, direction: 'above' | 'below'): Promise; /** * Move a cell to a specific position */ moveCellToIdx(cell: ICellViewModel, index: number): Promise; /** * Focus the container of a cell (the monaco editor inside is not focused). */ focusNotebookCell(cell: ICellViewModel, focus: 'editor' | 'container' | 'output'): void; /** * Execute the given notebook cell */ executeNotebookCell(cell: ICellViewModel): Promise; /** * Cancel the cell execution */ cancelNotebookCellExecution(cell: ICellViewModel): void; /** * Executes all notebook cells in order */ executeNotebook(): Promise; /** * Cancel the notebook execution */ cancelNotebookExecution(): void; /** * Get current active cell */ getActiveCell(): ICellViewModel | undefined; /** * Layout the cell with a new height */ layoutNotebookCell(cell: ICellViewModel, height: number): Promise; /** * Render the output in webview layer */ createInset(cell: ICellViewModel, output: IProcessedOutput, shadowContent: string, offset: number): Promise; /** * Remove the output from the webview layer */ removeInset(output: IProcessedOutput): void; /** * Hide the inset in the webview layer without removing it */ hideInset(output: IProcessedOutput): void; /** * Send message to the webview for outputs. */ postMessage(forRendererId: string | undefined, message: any): void; /** * Toggle class name on the notebook editor root DOM node. */ toggleClassName(className: string): void; /** * Remove class name on the notebook editor root DOM node. */ addClassName(className: string): void; /** * Remove class name on the notebook editor root DOM node. */ removeClassName(className: string): void; deltaCellOutputContainerClassNames(cellId: string, added: string[], removed: string[]): void; /** * Trigger the editor to scroll from scroll event programmatically */ triggerScroll(event: IMouseWheelEvent): void; /** * Reveal cell into viewport. */ revealInView(cell: ICellViewModel): void; /** * Reveal cell into viewport center. */ revealInCenter(cell: ICellViewModel): void; /** * Reveal cell into viewport center if cell is currently out of the viewport. */ revealInCenterIfOutsideViewport(cell: ICellViewModel): void; /** * Reveal a line in notebook cell into viewport with minimal scrolling. */ revealLineInViewAsync(cell: ICellViewModel, line: number): Promise; /** * Reveal a line in notebook cell into viewport center. */ revealLineInCenterAsync(cell: ICellViewModel, line: number): Promise; /** * Reveal a line in notebook cell into viewport center. */ revealLineInCenterIfOutsideViewportAsync(cell: ICellViewModel, line: number): Promise; /** * Reveal a range in notebook cell into viewport with minimal scrolling. */ revealRangeInViewAsync(cell: ICellViewModel, range: Range): Promise; /** * Reveal a range in notebook cell into viewport center. */ revealRangeInCenterAsync(cell: ICellViewModel, range: Range): Promise; /** * Reveal a range in notebook cell into viewport center. */ revealRangeInCenterIfOutsideViewportAsync(cell: ICellViewModel, range: Range): Promise; /** * Set hidden areas on cell text models. */ setHiddenAreas(_ranges: ICellRange[]): boolean; setCellSelection(cell: ICellViewModel, selection: Range): void; /** * Change the decorations on cells. * The notebook is virtualized and this method should be called to create/delete editor decorations safely. */ changeModelDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null; /** * An event emitted on a "mouseup". * @event */ onMouseUp(listener: (e: INotebookEditorMouseEvent) => void): IDisposable; /** * An event emitted on a "mousedown". * @event */ onMouseDown(listener: (e: INotebookEditorMouseEvent) => void): IDisposable; /** * Get a contribution of this editor. * @id Unique identifier of the contribution. * @return The contribution or null if contribution not found. */ getContribution(id: string): T; } export interface INotebookCellList { isDisposed: boolean readonly contextKeyService: IContextKeyService; elementAt(position: number): ICellViewModel | undefined; elementHeight(element: ICellViewModel): number; onWillScroll: Event; onDidChangeFocus: Event>; onDidChangeContentHeight: Event; scrollTop: number; scrollHeight: number; scrollLeft: number; length: number; rowsContainer: HTMLElement; readonly onDidRemoveOutput: Event; readonly onDidHideOutput: Event; readonly onMouseUp: Event>; readonly onMouseDown: Event>; detachViewModel(): void; attachViewModel(viewModel: NotebookViewModel): void; clear(): void; getViewIndex(cell: ICellViewModel): number | undefined; focusElement(element: ICellViewModel): void; selectElement(element: ICellViewModel): void; getFocusedElements(): ICellViewModel[]; revealElementInView(element: ICellViewModel): void; revealElementInCenterIfOutsideViewport(element: ICellViewModel): void; revealElementInCenter(element: ICellViewModel): void; revealElementLineInViewAsync(element: ICellViewModel, line: number): Promise; revealElementLineInCenterAsync(element: ICellViewModel, line: number): Promise; revealElementLineInCenterIfOutsideViewportAsync(element: ICellViewModel, line: number): Promise; revealElementRangeInViewAsync(element: ICellViewModel, range: Range): Promise; revealElementRangeInCenterAsync(element: ICellViewModel, range: Range): Promise; revealElementRangeInCenterIfOutsideViewportAsync(element: ICellViewModel, range: Range): Promise; setHiddenAreas(_ranges: ICellRange[], triggerViewUpdate: boolean): boolean; domElementOfElement(element: ICellViewModel): HTMLElement | null; focusView(): void; getAbsoluteTopOfElement(element: ICellViewModel): number; triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent): void; updateElementHeight2(element: ICellViewModel, size: number): void; domFocus(): void; setCellSelection(element: ICellViewModel, range: Range): void; style(styles: IListStyles): void; updateOptions(options: IListOptions): void; layout(height?: number, width?: number): void; dispose(): void; // TODO resolve differences between List and INotebookCellList getFocus(): number[]; setFocus(indexes: number[]): void; setSelection(indexes: number[]): void; } export interface BaseCellRenderTemplate { editorPart: HTMLElement; collapsedPart: HTMLElement; expandButton: HTMLElement; contextKeyService: IContextKeyService; container: HTMLElement; cellContainer: HTMLElement; toolbar: ToolBar; betweenCellToolbar: ToolBar; focusIndicatorLeft: HTMLElement; disposables: DisposableStore; elementDisposables: DisposableStore; bottomCellContainer: HTMLElement; currentRenderedCell?: ICellViewModel; statusBarContainer: HTMLElement; languageStatusBarItem: CellLanguageStatusBarItem; titleMenu: IMenu; toJSON: () => object; } export interface MarkdownCellRenderTemplate extends BaseCellRenderTemplate { editorContainer: HTMLElement; foldingIndicator: HTMLElement; currentEditor?: ICodeEditor; } export interface CodeCellRenderTemplate extends BaseCellRenderTemplate { cellRunState: RunStateRenderer; cellStatusMessageContainer: HTMLElement; runToolbar: ToolBar; runButtonContainer: HTMLElement; executionOrderLabel: HTMLElement; outputContainer: HTMLElement; focusSinkElement: HTMLElement; editor: ICodeEditor; progressBar: ProgressBar; timer: TimerRenderer; focusIndicatorRight: HTMLElement; focusIndicatorBottom: HTMLElement; } export function isCodeCellRenderTemplate(templateData: BaseCellRenderTemplate): templateData is CodeCellRenderTemplate { return !!(templateData as CodeCellRenderTemplate).runToolbar; } export interface IOutputTransformContribution { /** * Dispose this contribution. */ dispose(): void; render(output: IProcessedOutput, container: HTMLElement, preferredMimeType: string | undefined): IRenderOutput; } export interface CellFindMatch { cell: CellViewModel; matches: FindMatch[]; } export enum CellRevealType { Line, Range } export enum CellRevealPosition { Top, Center } export enum CellEditState { /** * Default state. * For markdown cell, it's Markdown preview. * For code cell, the browser focus should be on the container instead of the editor */ Preview, /** * Eding mode. Source for markdown or code is rendered in editors and the state will be persistent. */ Editing } export enum CellCollapseState { Normal, Collapsed } export enum CellFocusMode { Container, Editor } export enum CursorAtBoundary { None, Top, Bottom, Both } export interface CellViewModelStateChangeEvent { metadataChanged?: boolean; selectionChanged?: boolean; focusModeChanged?: boolean; editStateChanged?: boolean; languageChanged?: boolean; foldingStateChanged?: boolean; contentChanged?: boolean; outputIsHoveredChanged?: boolean; } /** * [start, end] */ export interface ICellRange { /** * zero based index */ start: number; /** * zero based index */ end: number; } /** * @param _ranges */ export function reduceCellRanges(_ranges: ICellRange[]): ICellRange[] { if (!_ranges.length) { return []; } let ranges = _ranges.sort((a, b) => a.start - b.start); let result: ICellRange[] = []; let currentRangeStart = ranges[0].start; let currentRangeEnd = ranges[0].end + 1; for (let i = 0, len = ranges.length; i < len; i++) { let range = ranges[i]; if (range.start > currentRangeEnd) { result.push({ start: currentRangeStart, end: currentRangeEnd - 1 }); currentRangeStart = range.start; currentRangeEnd = range.end + 1; } else if (range.end + 1 > currentRangeEnd) { currentRangeEnd = range.end + 1; } } result.push({ start: currentRangeStart, end: currentRangeEnd - 1 }); return result; } export function getVisibleCells(cells: CellViewModel[], hiddenRanges: ICellRange[]) { if (!hiddenRanges.length) { return cells; } let start = 0; let hiddenRangeIndex = 0; let result: CellViewModel[] = []; while (start < cells.length && hiddenRangeIndex < hiddenRanges.length) { if (start < hiddenRanges[hiddenRangeIndex].start) { result.push(...cells.slice(start, hiddenRanges[hiddenRangeIndex].start)); } start = hiddenRanges[hiddenRangeIndex].end + 1; hiddenRangeIndex++; } if (start < cells.length) { result.push(...cells.slice(start)); } return result; } export function getActiveNotebookEditor(editorService: IEditorService): INotebookEditor | undefined { // TODO can `isNotebookEditor` be on INotebookEditor to avoid a circular dependency? const activeEditorPane = editorService.activeEditorPane as unknown as { isNotebookEditor?: boolean } | undefined; return activeEditorPane?.isNotebookEditor ? (editorService.activeEditorPane?.getControl() as INotebookEditor) : undefined; }