/*--------------------------------------------------------------------------------------------- * 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 { CancellationTokenSource } from 'vs/base/common/cancellation'; 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, 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, IOutput, IRenderOutput, NotebookCellMetadata, NotebookDocumentMetadata, INotebookKernelInfo, IEditor } 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'; 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_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 // 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 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; } 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; editorHeight?: boolean; } export interface ICellViewModel { readonly model: NotebookCellTextModel; readonly id: string; readonly textBuffer: IReadonlyTextBuffer; dragging: boolean; handle: number; uri: URI; language: string; cellKind: CellKind; editState: CellEditState; readonly runState: CellRunState; currentTokenSource: CancellationTokenSource | undefined; focusMode: CellFocusMode; getText(): string; metadata: NotebookCellMetadata | undefined; textModel: ITextModel | undefined; hasModel(): this is IEditableCellViewModel; resolveTextModel(): Promise; getEvaluatedMetadata(documentMetadata: NotebookDocumentMetadata | undefined): NotebookCellMetadata; getSelectionsStartPosition(): IPosition[] | undefined; } 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?(): any; /** * Restore view state. */ restoreViewState?(state: any): void; } export interface INotebookEditor extends IEditor { /** * 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 | undefined; readonly onDidChangeKernel: Event; 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; /** * Move a cell above or below another cell */ moveCell(cell: ICellViewModel, relativeToCell: ICellViewModel, direction: 'above' | 'below'): Promise; /** * Switch the cell into editing mode. * * For code cell, the monaco editor will be focused. * For markdown cell, it will switch from preview mode to editing mode, which focuses the monaco editor. */ editNotebookCell(cell: ICellViewModel): void; /** * Quit cell editing mode. */ saveNotebookCell(cell: ICellViewModel): void; /** * 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: IOutput, shadowContent: string, offset: number): void; /** * Remove the output from the webview layer */ removeInset(output: IOutput): void; /** * Send message to the webview for outputs. */ postMessage(message: any): 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. */ revealLineInView(cell: ICellViewModel, line: number): void; /** * Reveal a line in notebook cell into viewport center. */ revealLineInCenter(cell: ICellViewModel, line: number): void; /** * Reveal a line in notebook cell into viewport center. */ revealLineInCenterIfOutsideViewport(cell: ICellViewModel, line: number): void; /** * Reveal a range in notebook cell into viewport with minimal scrolling. */ revealRangeInView(cell: ICellViewModel, range: Range): void; /** * Reveal a range in notebook cell into viewport center. */ revealRangeInCenter(cell: ICellViewModel, range: Range): void; /** * Reveal a range in notebook cell into viewport center. */ revealRangeInCenterIfOutsideViewport(cell: ICellViewModel, range: Range): void; /** * 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. */ changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => any): any; /** * 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 { 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; revealElementLineInView(element: ICellViewModel, line: number): void; revealElementLineInCenter(element: ICellViewModel, line: number): void; revealElementLineInCenterIfOutsideViewport(element: ICellViewModel, line: number): void; revealElementRangeInView(element: ICellViewModel, range: Range): void; revealElementRangeInCenter(element: ICellViewModel, range: Range): void; revealElementRangeInCenterIfOutsideViewport(element: ICellViewModel, range: Range): void; 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 { contextKeyService: IContextKeyService; container: HTMLElement; cellContainer: HTMLElement; toolbar: ToolBar; focusIndicator: HTMLElement; disposables: DisposableStore; elementDisposables: DisposableStore; bottomCellContainer: HTMLElement; currentRenderedCell?: ICellViewModel; statusBarContainer: HTMLElement; languageStatusBarItem: CellLanguageStatusBarItem; toJSON: () => any; } export interface MarkdownCellRenderTemplate extends BaseCellRenderTemplate { editorPart: HTMLElement; editorContainer: HTMLElement; foldingIndicator: HTMLElement; currentEditor?: ICodeEditor; } export interface CodeCellRenderTemplate extends BaseCellRenderTemplate { cellRunStatusContainer: HTMLElement; cellStatusMessageContainer: HTMLElement; runToolbar: ToolBar; runButtonContainer: HTMLElement; executionOrderLabel: HTMLElement; outputContainer: HTMLElement; editor: ICodeEditor; progressBar: ProgressBar; timer: TimerRenderer; } export interface IOutputTransformContribution { /** * Dispose this contribution. */ dispose(): void; render(output: IOutput, 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 CellRunState { Idle, Running } 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 CellFocusMode { Container, Editor } export enum CursorAtBoundary { None, Top, Bottom, Both } export interface CellViewModelStateChangeEvent { metadataChanged?: boolean; selectionChanged?: boolean; focusModeChanged?: boolean; runStateChanged?: 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: any[] = []; 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; }