mirror of
https://github.com/microsoft/vscode.git
synced 2026-06-05 15:16:06 +01:00
ab04541f58
and don't render new outputs when the cell output is collapsed Fix #104580
1985 lines
76 KiB
TypeScript
1985 lines
76 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import { getZoomLevel } from 'vs/base/browser/browser';
|
|
import * as DOM from 'vs/base/browser/dom';
|
|
import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
|
import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list';
|
|
import { IAction, Separator } from 'vs/base/common/actions';
|
|
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
|
import { Color, RGBA } from 'vs/base/common/color';
|
|
import { onUnexpectedError } from 'vs/base/common/errors';
|
|
import { Emitter, Event } from 'vs/base/common/event';
|
|
import { combinedDisposable, Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
|
import { ScrollEvent } from 'vs/base/common/scrollable';
|
|
import { URI } from 'vs/base/common/uri';
|
|
import { generateUuid } from 'vs/base/common/uuid';
|
|
import 'vs/css!./media/notebook';
|
|
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
|
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
|
import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
|
|
import { Range } from 'vs/editor/common/core/range';
|
|
import { IEditor } from 'vs/editor/common/editorCommon';
|
|
import * as nls from 'vs/nls';
|
|
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
|
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
|
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
|
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
|
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
|
|
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
|
import { contrastBorder, diffInserted, diffRemoved, editorBackground, errorForeground, focusBorder, foreground, listFocusBackground, listInactiveSelectionBackground, registerColor, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, textPreformatForeground, transparent } from 'vs/platform/theme/common/colorRegistry';
|
|
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
|
import { EditorMemento } from 'vs/workbench/browser/parts/editor/editorPane';
|
|
import { IEditorMemento } from 'vs/workbench/common/editor';
|
|
import { Memento, MementoObject } from 'vs/workbench/common/memento';
|
|
import { PANEL_BORDER } from 'vs/workbench/common/theme';
|
|
import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugToolBar';
|
|
import { BOTTOM_CELL_TOOLBAR_GAP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CELL_MARGIN, CELL_RUN_GUTTER, CELL_TOP_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT, SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants';
|
|
import { CellEditState, CellFocusMode, ICellRange, ICellViewModel, INotebookCellList, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, NotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
|
import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
|
|
import { NotebookKernelProviderAssociations, notebookKernelProviderAssociationsSettingId } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation';
|
|
import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList';
|
|
import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer';
|
|
import { BackLayerWebView } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView';
|
|
import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys';
|
|
import { CodeCellRenderer, ListTopCellToolbar, MarkdownCellRenderer, NotebookCellListDelegate } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer';
|
|
import { CellDragAndDropController } from 'vs/workbench/contrib/notebook/browser/view/renderers/dnd';
|
|
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
|
|
import { NotebookEventDispatcher, NotebookLayoutChangedEvent } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher';
|
|
import { CellViewModel, IModelDecorationsChangeAccessor, INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
|
|
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
|
|
import { CellKind, CellToolbarLocKey, IInsetRenderOutput, INotebookKernelInfo, INotebookKernelInfo2, INotebookKernelInfoDto, IProcessedOutput, NotebookCellRunState, NotebookRunState, ShowCellStatusbarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
|
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
|
|
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
|
|
import { editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator';
|
|
import { Webview } from 'vs/workbench/contrib/webview/browser/webview';
|
|
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
|
|
|
const $ = DOM.$;
|
|
|
|
const NotebookEditorActiveKernelCache = 'workbench.editor.notebook.activeKernel';
|
|
|
|
export class NotebookEditorWidget extends Disposable implements INotebookEditor {
|
|
static readonly ID: string = 'workbench.editor.notebook';
|
|
private static readonly EDITOR_MEMENTOS = new Map<string, EditorMemento<unknown>>();
|
|
private _overlayContainer!: HTMLElement;
|
|
private _body!: HTMLElement;
|
|
private _overflowContainer!: HTMLElement;
|
|
private _webview: BackLayerWebView | null = null;
|
|
private _webviewResolved: boolean = false;
|
|
private _webviewResolvePromise: Promise<BackLayerWebView | null> | null = null;
|
|
private _webviewTransparentCover: HTMLElement | null = null;
|
|
private _list: INotebookCellList | undefined;
|
|
private _dndController: CellDragAndDropController | null = null;
|
|
private _listTopCellToolbar: ListTopCellToolbar | null = null;
|
|
private _renderedEditors: Map<ICellViewModel, ICodeEditor | undefined> = new Map();
|
|
private _eventDispatcher: NotebookEventDispatcher | undefined;
|
|
private _notebookViewModel: NotebookViewModel | undefined;
|
|
private _localStore: DisposableStore = this._register(new DisposableStore());
|
|
private _fontInfo: BareFontInfo | undefined;
|
|
private _dimension: DOM.Dimension | null = null;
|
|
private _shadowElementViewInfo: { height: number, width: number, top: number; left: number; } | null = null;
|
|
|
|
private _editorFocus: IContextKey<boolean> | null = null;
|
|
private _outputFocus: IContextKey<boolean> | null = null;
|
|
private _editorEditable: IContextKey<boolean> | null = null;
|
|
private _editorRunnable: IContextKey<boolean> | null = null;
|
|
private _notebookExecuting: IContextKey<boolean> | null = null;
|
|
private _notebookHasMultipleKernels: IContextKey<boolean> | null = null;
|
|
private _outputRenderer: OutputRenderer;
|
|
protected readonly _contributions: { [key: string]: INotebookEditorContribution; };
|
|
private _scrollBeyondLastLine: boolean;
|
|
private readonly _memento: Memento;
|
|
private readonly _activeKernelMemento: Memento;
|
|
private readonly _onDidFocusEmitter = this._register(new Emitter<void>());
|
|
public readonly onDidFocus = this._onDidFocusEmitter.event;
|
|
private readonly _onWillScroll = this._register(new Emitter<ScrollEvent>());
|
|
public readonly onWillScroll: Event<ScrollEvent> = this._onWillScroll.event;
|
|
private readonly _onWillDispose = this._register(new Emitter<void>());
|
|
public readonly onWillDispose: Event<void> = this._onWillDispose.event;
|
|
|
|
set scrollTop(top: number) {
|
|
if (this._list) {
|
|
this._list.scrollTop = top;
|
|
}
|
|
}
|
|
|
|
private _cellContextKeyManager: CellContextKeyManager | null = null;
|
|
private _isVisible = false;
|
|
private readonly _uuid = generateUuid();
|
|
private _webiewFocused: boolean = false;
|
|
|
|
private _isDisposed: boolean = false;
|
|
|
|
get isDisposed() {
|
|
return this._isDisposed;
|
|
}
|
|
|
|
private readonly _onDidChangeModel = this._register(new Emitter<NotebookTextModel | undefined>());
|
|
readonly onDidChangeModel: Event<NotebookTextModel | undefined> = this._onDidChangeModel.event;
|
|
|
|
private readonly _onDidFocusEditorWidget = this._register(new Emitter<void>());
|
|
readonly onDidFocusEditorWidget = this._onDidFocusEditorWidget.event;
|
|
|
|
set viewModel(newModel: NotebookViewModel | undefined) {
|
|
this._notebookViewModel = newModel;
|
|
this._onDidChangeModel.fire(newModel?.notebookDocument);
|
|
}
|
|
|
|
get viewModel() {
|
|
return this._notebookViewModel;
|
|
}
|
|
|
|
get uri() {
|
|
return this._notebookViewModel?.uri;
|
|
}
|
|
|
|
get textModel() {
|
|
return this._notebookViewModel?.notebookDocument;
|
|
}
|
|
|
|
private _activeKernel: INotebookKernelInfo | INotebookKernelInfo2 | undefined = undefined;
|
|
private readonly _onDidChangeKernel = this._register(new Emitter<void>());
|
|
readonly onDidChangeKernel: Event<void> = this._onDidChangeKernel.event;
|
|
private readonly _onDidChangeAvailableKernels = this._register(new Emitter<void>());
|
|
readonly onDidChangeAvailableKernels: Event<void> = this._onDidChangeAvailableKernels.event;
|
|
|
|
get activeKernel() {
|
|
return this._activeKernel;
|
|
}
|
|
|
|
set activeKernel(kernel: INotebookKernelInfo | INotebookKernelInfo2 | undefined) {
|
|
if (this._isDisposed) {
|
|
return;
|
|
}
|
|
|
|
if (this._activeKernel === kernel) {
|
|
return;
|
|
}
|
|
|
|
this._activeKernel = kernel;
|
|
this._activeKernelResolvePromise = undefined;
|
|
|
|
const memento = this._activeKernelMemento.getMemento(StorageScope.GLOBAL);
|
|
memento[this.viewModel!.viewType] = this._activeKernel?.id;
|
|
this._activeKernelMemento.saveMemento();
|
|
this._onDidChangeKernel.fire();
|
|
}
|
|
|
|
private _activeKernelResolvePromise: Promise<void> | undefined = undefined;
|
|
|
|
private _currentKernelTokenSource: CancellationTokenSource | undefined = undefined;
|
|
private _multipleKernelsAvailable: boolean = false;
|
|
|
|
get multipleKernelsAvailable() {
|
|
return this._multipleKernelsAvailable;
|
|
}
|
|
|
|
set multipleKernelsAvailable(state: boolean) {
|
|
this._multipleKernelsAvailable = state;
|
|
this._onDidChangeAvailableKernels.fire();
|
|
}
|
|
|
|
private readonly _onDidChangeActiveEditor = this._register(new Emitter<this>());
|
|
readonly onDidChangeActiveEditor: Event<this> = this._onDidChangeActiveEditor.event;
|
|
|
|
get activeCodeEditor(): IEditor | undefined {
|
|
if (this._isDisposed) {
|
|
return;
|
|
}
|
|
|
|
const [focused] = this._list!.getFocusedElements();
|
|
return this._renderedEditors.get(focused);
|
|
}
|
|
|
|
private readonly _onDidChangeActiveCell = this._register(new Emitter<void>());
|
|
readonly onDidChangeActiveCell: Event<void> = this._onDidChangeActiveCell.event;
|
|
|
|
private readonly _onDidScroll = this._register(new Emitter<ScrollEvent>());
|
|
|
|
readonly onDidScroll: Event<ScrollEvent> = this._onDidScroll.event;
|
|
|
|
private _cursorNavigationMode: boolean = false;
|
|
get cursorNavigationMode(): boolean {
|
|
return this._cursorNavigationMode;
|
|
}
|
|
|
|
set cursorNavigationMode(v: boolean) {
|
|
this._cursorNavigationMode = v;
|
|
}
|
|
|
|
readonly isEmbedded: boolean;
|
|
|
|
constructor(
|
|
readonly creationOptions: INotebookEditorCreationOptions,
|
|
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
|
@IStorageService storageService: IStorageService,
|
|
@INotebookService private notebookService: INotebookService,
|
|
@IConfigurationService private readonly configurationService: IConfigurationService,
|
|
@IContextKeyService readonly contextKeyService: IContextKeyService,
|
|
@ILayoutService private readonly layoutService: ILayoutService,
|
|
@IContextMenuService private readonly contextMenuService: IContextMenuService,
|
|
@IMenuService private readonly menuService: IMenuService,
|
|
) {
|
|
super();
|
|
this.isEmbedded = creationOptions.isEmbedded || false;
|
|
this._memento = new Memento(NotebookEditorWidget.ID, storageService);
|
|
this._activeKernelMemento = new Memento(NotebookEditorActiveKernelCache, storageService);
|
|
|
|
this._outputRenderer = new OutputRenderer(this, this.instantiationService);
|
|
this._contributions = {};
|
|
this._scrollBeyondLastLine = this.configurationService.getValue<boolean>('editor.scrollBeyondLastLine');
|
|
|
|
this.configurationService.onDidChangeConfiguration(e => {
|
|
if (e.affectsConfiguration('editor.scrollBeyondLastLine')) {
|
|
this._scrollBeyondLastLine = this.configurationService.getValue<boolean>('editor.scrollBeyondLastLine');
|
|
if (this._dimension && this._isVisible) {
|
|
this.layout(this._dimension);
|
|
}
|
|
}
|
|
|
|
if (e.affectsConfiguration(CellToolbarLocKey) || e.affectsConfiguration(ShowCellStatusbarKey)) {
|
|
this._updateForNotebookConfiguration();
|
|
}
|
|
});
|
|
|
|
this.notebookService.addNotebookEditor(this);
|
|
}
|
|
|
|
/**
|
|
* EditorId
|
|
*/
|
|
public getId(): string {
|
|
return this._uuid;
|
|
}
|
|
|
|
hasModel() {
|
|
return !!this._notebookViewModel;
|
|
}
|
|
|
|
//#region Editor Core
|
|
|
|
protected getEditorMemento<T>(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
|
|
const mementoKey = `${NotebookEditorWidget.ID}${key}`;
|
|
|
|
let editorMemento = NotebookEditorWidget.EDITOR_MEMENTOS.get(mementoKey);
|
|
if (!editorMemento) {
|
|
editorMemento = new EditorMemento(NotebookEditorWidget.ID, key, this.getMemento(StorageScope.WORKSPACE), limit, editorGroupService);
|
|
NotebookEditorWidget.EDITOR_MEMENTOS.set(mementoKey, editorMemento);
|
|
}
|
|
|
|
return editorMemento as IEditorMemento<T>;
|
|
}
|
|
|
|
protected getMemento(scope: StorageScope): MementoObject {
|
|
return this._memento.getMemento(scope);
|
|
}
|
|
|
|
public get isNotebookEditor() {
|
|
return true;
|
|
}
|
|
|
|
private _updateForNotebookConfiguration() {
|
|
if (!this._overlayContainer) {
|
|
return;
|
|
}
|
|
|
|
const cellToolbarLocation = this.configurationService.getValue<string>(CellToolbarLocKey);
|
|
this._overlayContainer.classList.remove('cell-title-toolbar-left');
|
|
this._overlayContainer.classList.remove('cell-title-toolbar-right');
|
|
this._overlayContainer.classList.remove('cell-title-toolbar-hidden');
|
|
|
|
if (cellToolbarLocation === 'left' || cellToolbarLocation === 'right' || cellToolbarLocation === 'hidden') {
|
|
this._overlayContainer.classList.add(`cell-title-toolbar-${cellToolbarLocation}`);
|
|
}
|
|
|
|
const showCellStatusBar = this.configurationService.getValue<boolean>(ShowCellStatusbarKey);
|
|
this._overlayContainer.classList.toggle('cell-statusbar-hidden', !showCellStatusBar);
|
|
}
|
|
|
|
updateEditorFocus() {
|
|
// Note - focus going to the webview will fire 'blur', but the webview element will be
|
|
// a descendent of the notebook editor root.
|
|
const focused = DOM.isAncestor(document.activeElement, this._overlayContainer);
|
|
this._editorFocus?.set(focused);
|
|
this._notebookViewModel?.setFocus(focused);
|
|
}
|
|
|
|
hasFocus() {
|
|
return this._editorFocus?.get() || false;
|
|
}
|
|
|
|
hasWebviewFocus() {
|
|
return this._webiewFocused;
|
|
}
|
|
|
|
hasOutputTextSelection() {
|
|
if (!this.hasFocus()) {
|
|
return false;
|
|
}
|
|
|
|
const windowSelection = window.getSelection();
|
|
if (windowSelection?.rangeCount !== 1) {
|
|
return false;
|
|
}
|
|
|
|
const activeSelection = windowSelection.getRangeAt(0);
|
|
if (activeSelection.endOffset - activeSelection.startOffset === 0) {
|
|
return false;
|
|
}
|
|
|
|
let container: any = activeSelection.commonAncestorContainer;
|
|
|
|
if (!this._body.contains(container)) {
|
|
return false;
|
|
}
|
|
|
|
while (container
|
|
&&
|
|
container !== this._body) {
|
|
|
|
if (DOM.hasClass(container as HTMLElement, 'output')) {
|
|
return true;
|
|
}
|
|
|
|
container = container.parentNode;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
createEditor(): void {
|
|
this._overlayContainer = document.createElement('div');
|
|
const id = generateUuid();
|
|
this._overlayContainer.id = `notebook-${id}`;
|
|
this._overlayContainer.className = 'notebookOverlay';
|
|
DOM.addClass(this._overlayContainer, 'notebook-editor');
|
|
this._overlayContainer.style.visibility = 'hidden';
|
|
|
|
this.layoutService.container.appendChild(this._overlayContainer);
|
|
this._createBody(this._overlayContainer);
|
|
this._generateFontInfo();
|
|
this._editorFocus = NOTEBOOK_EDITOR_FOCUSED.bindTo(this.contextKeyService);
|
|
this._isVisible = true;
|
|
this._outputFocus = NOTEBOOK_OUTPUT_FOCUSED.bindTo(this.contextKeyService);
|
|
this._editorEditable = NOTEBOOK_EDITOR_EDITABLE.bindTo(this.contextKeyService);
|
|
this._editorEditable.set(true);
|
|
this._editorRunnable = NOTEBOOK_EDITOR_RUNNABLE.bindTo(this.contextKeyService);
|
|
this._editorRunnable.set(true);
|
|
this._notebookExecuting = NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK.bindTo(this.contextKeyService);
|
|
this._notebookHasMultipleKernels = NOTEBOOK_HAS_MULTIPLE_KERNELS.bindTo(this.contextKeyService);
|
|
this._notebookHasMultipleKernels.set(false);
|
|
|
|
let contributions: INotebookEditorContributionDescription[];
|
|
if (Array.isArray(this.creationOptions.contributions)) {
|
|
contributions = this.creationOptions.contributions;
|
|
} else {
|
|
contributions = NotebookEditorExtensionsRegistry.getEditorContributions();
|
|
}
|
|
|
|
for (const desc of contributions) {
|
|
try {
|
|
const contribution = this.instantiationService.createInstance(desc.ctor, this);
|
|
this._contributions[desc.id] = contribution;
|
|
} catch (err) {
|
|
onUnexpectedError(err);
|
|
}
|
|
}
|
|
|
|
this._updateForNotebookConfiguration();
|
|
}
|
|
|
|
private _generateFontInfo(): void {
|
|
const editorOptions = this.configurationService.getValue<IEditorOptions>('editor');
|
|
this._fontInfo = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel());
|
|
}
|
|
|
|
private _createBody(parent: HTMLElement): void {
|
|
this._body = document.createElement('div');
|
|
DOM.addClass(this._body, 'cell-list-container');
|
|
this._createCellList();
|
|
DOM.append(parent, this._body);
|
|
|
|
this._overflowContainer = document.createElement('div');
|
|
DOM.addClass(this._overflowContainer, 'notebook-overflow-widget-container');
|
|
DOM.addClass(this._overflowContainer, 'monaco-editor');
|
|
DOM.append(parent, this._overflowContainer);
|
|
}
|
|
|
|
private _createCellList(): void {
|
|
DOM.addClass(this._body, 'cell-list-container');
|
|
|
|
this._dndController = this._register(new CellDragAndDropController(this, this._body));
|
|
const getScopedContextKeyService = (container?: HTMLElement) => this._list!.contextKeyService.createScoped(container);
|
|
const renderers = [
|
|
this.instantiationService.createInstance(CodeCellRenderer, this, this._renderedEditors, this._dndController, getScopedContextKeyService),
|
|
this.instantiationService.createInstance(MarkdownCellRenderer, this, this._dndController, this._renderedEditors, getScopedContextKeyService),
|
|
];
|
|
|
|
this._list = this.instantiationService.createInstance(
|
|
NotebookCellList,
|
|
'NotebookCellList',
|
|
this._overlayContainer,
|
|
this._body,
|
|
this.instantiationService.createInstance(NotebookCellListDelegate),
|
|
renderers,
|
|
this.contextKeyService,
|
|
{
|
|
setRowLineHeight: false,
|
|
setRowHeight: false,
|
|
supportDynamicHeights: true,
|
|
horizontalScrolling: false,
|
|
keyboardSupport: false,
|
|
mouseSupport: true,
|
|
multipleSelectionSupport: false,
|
|
enableKeyboardNavigation: true,
|
|
additionalScrollHeight: 0,
|
|
transformOptimization: false, //(isMacintosh && isNative) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native',
|
|
styleController: (_suffix: string) => { return this._list!; },
|
|
overrideStyles: {
|
|
listBackground: editorBackground,
|
|
listActiveSelectionBackground: editorBackground,
|
|
listActiveSelectionForeground: foreground,
|
|
listFocusAndSelectionBackground: editorBackground,
|
|
listFocusAndSelectionForeground: foreground,
|
|
listFocusBackground: editorBackground,
|
|
listFocusForeground: foreground,
|
|
listHoverForeground: foreground,
|
|
listHoverBackground: editorBackground,
|
|
listHoverOutline: focusBorder,
|
|
listFocusOutline: focusBorder,
|
|
listInactiveSelectionBackground: editorBackground,
|
|
listInactiveSelectionForeground: foreground,
|
|
listInactiveFocusBackground: editorBackground,
|
|
listInactiveFocusOutline: editorBackground,
|
|
},
|
|
accessibilityProvider: {
|
|
getAriaLabel() { return null; },
|
|
getWidgetAriaLabel() {
|
|
return nls.localize('notebookTreeAriaLabel', "Notebook");
|
|
}
|
|
},
|
|
focusNextPreviousDelegate: {
|
|
onFocusNext: (applyFocusNext: () => void) => this._updateForCursorNavigationMode(applyFocusNext),
|
|
onFocusPrevious: (applyFocusPrevious: () => void) => this._updateForCursorNavigationMode(applyFocusPrevious),
|
|
}
|
|
},
|
|
);
|
|
this._dndController.setList(this._list);
|
|
|
|
// create Webview
|
|
|
|
this._register(this._list);
|
|
this._register(combinedDisposable(...renderers));
|
|
|
|
// top cell toolbar
|
|
this._listTopCellToolbar = this._register(this.instantiationService.createInstance(ListTopCellToolbar, this, this._list.rowsContainer));
|
|
|
|
// transparent cover
|
|
this._webviewTransparentCover = DOM.append(this._list.rowsContainer, $('.webview-cover'));
|
|
this._webviewTransparentCover.style.display = 'none';
|
|
|
|
this._register(DOM.addStandardDisposableGenericMouseDownListner(this._overlayContainer, (e: StandardMouseEvent) => {
|
|
if (DOM.hasClass(e.target, 'slider') && this._webviewTransparentCover) {
|
|
this._webviewTransparentCover.style.display = 'block';
|
|
}
|
|
}));
|
|
|
|
this._register(DOM.addStandardDisposableGenericMouseUpListner(this._overlayContainer, () => {
|
|
if (this._webviewTransparentCover) {
|
|
// no matter when
|
|
this._webviewTransparentCover.style.display = 'none';
|
|
}
|
|
}));
|
|
|
|
this._register(this._list.onMouseDown(e => {
|
|
if (e.element) {
|
|
this._onMouseDown.fire({ event: e.browserEvent, target: e.element });
|
|
}
|
|
}));
|
|
|
|
this._register(this._list.onMouseUp(e => {
|
|
if (e.element) {
|
|
this._onMouseUp.fire({ event: e.browserEvent, target: e.element });
|
|
}
|
|
}));
|
|
|
|
this._register(this._list.onDidChangeFocus(_e => {
|
|
this._onDidChangeActiveEditor.fire(this);
|
|
this._onDidChangeActiveCell.fire();
|
|
this._cursorNavigationMode = false;
|
|
}));
|
|
|
|
this._register(this._list.onContextMenu(e => {
|
|
this.showListContextMenu(e);
|
|
}));
|
|
|
|
this._register(this._list.onDidScroll((e) => {
|
|
this._onDidScroll.fire(e);
|
|
}));
|
|
|
|
const widgetFocusTracker = DOM.trackFocus(this.getDomNode());
|
|
this._register(widgetFocusTracker);
|
|
this._register(widgetFocusTracker.onDidFocus(() => this._onDidFocusEmitter.fire()));
|
|
}
|
|
|
|
private showListContextMenu(e: IListContextMenuEvent<CellViewModel>) {
|
|
this.contextMenuService.showContextMenu({
|
|
getActions: () => {
|
|
const result: IAction[] = [];
|
|
const menu = this.menuService.createMenu(MenuId.NotebookCellTitle, this.contextKeyService);
|
|
const groups = menu.getActions();
|
|
menu.dispose();
|
|
|
|
for (let group of groups) {
|
|
const [, actions] = group;
|
|
result.push(...actions);
|
|
result.push(new Separator());
|
|
}
|
|
|
|
result.pop(); // remove last separator
|
|
return result;
|
|
},
|
|
getAnchor: () => e.anchor
|
|
});
|
|
}
|
|
|
|
private _updateForCursorNavigationMode(applyFocusChange: () => void): void {
|
|
if (this._cursorNavigationMode) {
|
|
// Will fire onDidChangeFocus, resetting the state to Container
|
|
applyFocusChange();
|
|
|
|
const newFocusedCell = this._list!.getFocusedElements()[0];
|
|
if (newFocusedCell.cellKind === CellKind.Code || newFocusedCell.editState === CellEditState.Editing) {
|
|
this.focusNotebookCell(newFocusedCell, 'editor');
|
|
} else {
|
|
// Reset to "Editor", the state has not been consumed
|
|
this._cursorNavigationMode = true;
|
|
}
|
|
} else {
|
|
applyFocusChange();
|
|
}
|
|
}
|
|
|
|
getDomNode() {
|
|
return this._overlayContainer;
|
|
}
|
|
|
|
getOverflowContainerDomNode() {
|
|
return this._overflowContainer;
|
|
}
|
|
|
|
onWillHide() {
|
|
this._isVisible = false;
|
|
this._editorFocus?.set(false);
|
|
this._overlayContainer.style.visibility = 'hidden';
|
|
this._overlayContainer.style.left = '-50000px';
|
|
}
|
|
|
|
getInnerWebview(): Webview | undefined {
|
|
return this._webview?.webview;
|
|
}
|
|
|
|
focus() {
|
|
this._isVisible = true;
|
|
this._editorFocus?.set(true);
|
|
|
|
if (this._webiewFocused) {
|
|
this._webview?.focusWebview();
|
|
} else {
|
|
const focus = this._list?.getFocus()[0];
|
|
if (typeof focus === 'number') {
|
|
const element = this._notebookViewModel!.viewCells[focus];
|
|
|
|
if (element.focusMode === CellFocusMode.Editor) {
|
|
element.editState = CellEditState.Editing;
|
|
element.focusMode = CellFocusMode.Editor;
|
|
this._onDidFocusEditorWidget.fire();
|
|
return;
|
|
}
|
|
|
|
}
|
|
this._list?.domFocus();
|
|
}
|
|
|
|
this._onDidFocusEditorWidget.fire();
|
|
}
|
|
|
|
async setModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined): Promise<void> {
|
|
if (this._notebookViewModel === undefined || !this._notebookViewModel.equal(textModel)) {
|
|
this._detachModel();
|
|
await this._attachModel(textModel, viewState);
|
|
} else {
|
|
this.restoreListViewState(viewState);
|
|
}
|
|
|
|
// clear state
|
|
this._dndController?.clearGlobalDragState();
|
|
|
|
this._currentKernelTokenSource = new CancellationTokenSource();
|
|
this._localStore.add(this._currentKernelTokenSource);
|
|
// we don't await for it, otherwise it will slow down the file opening
|
|
this._setKernels(textModel, this._currentKernelTokenSource);
|
|
|
|
this._localStore.add(this.notebookService.onDidChangeKernels(async () => {
|
|
this._currentKernelTokenSource?.cancel();
|
|
this._currentKernelTokenSource = new CancellationTokenSource();
|
|
await this._setKernels(textModel, this._currentKernelTokenSource);
|
|
}));
|
|
|
|
this._localStore.add(this._list!.onDidChangeFocus(() => {
|
|
const focused = this._list!.getFocusedElements()[0];
|
|
if (focused) {
|
|
if (!this._cellContextKeyManager) {
|
|
this._cellContextKeyManager = this._localStore.add(new CellContextKeyManager(this.contextKeyService, this, textModel, focused as CellViewModel));
|
|
}
|
|
|
|
this._cellContextKeyManager.updateForElement(focused as CellViewModel);
|
|
}
|
|
}));
|
|
}
|
|
|
|
async setOptions(options: NotebookEditorOptions | undefined) {
|
|
// reveal cell if editor options tell to do so
|
|
if (options?.cellOptions) {
|
|
const cellOptions = options.cellOptions;
|
|
const cell = this._notebookViewModel!.viewCells.find(cell => cell.uri.toString() === cellOptions.resource.toString());
|
|
if (cell) {
|
|
this.selectElement(cell);
|
|
this.revealInCenterIfOutsideViewport(cell);
|
|
const editor = this._renderedEditors.get(cell)!;
|
|
if (editor) {
|
|
if (cellOptions.options?.selection) {
|
|
const { selection } = cellOptions.options;
|
|
editor.setSelection({
|
|
...selection,
|
|
endLineNumber: selection.endLineNumber || selection.startLineNumber,
|
|
endColumn: selection.endColumn || selection.startColumn
|
|
});
|
|
editor.revealPositionInCenterIfOutsideViewport({
|
|
lineNumber: selection.startLineNumber,
|
|
column: selection.startColumn
|
|
});
|
|
}
|
|
if (!cellOptions.options?.preserveFocus) {
|
|
editor.focus();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private _detachModel() {
|
|
this._localStore.clear();
|
|
this._list?.detachViewModel();
|
|
this.viewModel?.dispose();
|
|
// avoid event
|
|
this._notebookViewModel = undefined;
|
|
// this.webview?.clearInsets();
|
|
// this.webview?.clearPreloadsCache();
|
|
this._webview?.dispose();
|
|
this._webview?.element.remove();
|
|
this._webview = null;
|
|
this._list?.clear();
|
|
}
|
|
|
|
private async _setKernels(textModel: NotebookTextModel, tokenSource: CancellationTokenSource) {
|
|
const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0];
|
|
const availableKernels2 = await this.notebookService.getContributedNotebookKernels2(textModel.viewType, textModel.uri, tokenSource.token);
|
|
|
|
if (tokenSource.token.isCancellationRequested) {
|
|
return;
|
|
}
|
|
|
|
const availableKernels = this.notebookService.getContributedNotebookKernels(textModel.viewType, textModel.uri);
|
|
|
|
if (tokenSource.token.isCancellationRequested) {
|
|
return;
|
|
}
|
|
|
|
if ((availableKernels.length + availableKernels2.length) > 1) {
|
|
this._notebookHasMultipleKernels!.set(true);
|
|
this.multipleKernelsAvailable = true;
|
|
} else {
|
|
this._notebookHasMultipleKernels!.set(false);
|
|
this.multipleKernelsAvailable = false;
|
|
}
|
|
|
|
const activeKernelStillExist = [...availableKernels2, ...availableKernels].find(kernel => kernel.id === this.activeKernel?.id && this.activeKernel?.id !== undefined);
|
|
|
|
if (activeKernelStillExist) {
|
|
// the kernel still exist, we don't want to modify the selection otherwise user's temporary preference is lost
|
|
return;
|
|
}
|
|
|
|
if (availableKernels2.length) {
|
|
return this._setKernelsFromProviders(provider, availableKernels2, tokenSource);
|
|
}
|
|
|
|
// the provider doesn't have a builtin kernel, choose a kernel
|
|
this.activeKernel = availableKernels[0];
|
|
if (this.activeKernel) {
|
|
await this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel);
|
|
}
|
|
|
|
tokenSource.dispose();
|
|
}
|
|
|
|
private async _setKernelsFromProviders(provider: NotebookProviderInfo, kernels: INotebookKernelInfo2[], tokenSource: CancellationTokenSource) {
|
|
const rawAssociations = this.configurationService.getValue<NotebookKernelProviderAssociations>(notebookKernelProviderAssociationsSettingId) || [];
|
|
const userSetKernelProvider = rawAssociations.filter(e => e.viewType === this.viewModel?.viewType)[0]?.kernelProvider;
|
|
const memento = this._activeKernelMemento.getMemento(StorageScope.GLOBAL);
|
|
|
|
if (userSetKernelProvider) {
|
|
const filteredKernels = kernels.filter(kernel => kernel.extension.value === userSetKernelProvider);
|
|
|
|
if (filteredKernels.length) {
|
|
const cachedKernelId = memento[provider.id];
|
|
this.activeKernel =
|
|
filteredKernels.find(kernel => kernel.isPreferred)
|
|
|| filteredKernels.find(kernel => kernel.id === cachedKernelId)
|
|
|| filteredKernels[0];
|
|
} else {
|
|
this.activeKernel = undefined;
|
|
}
|
|
|
|
if (this.activeKernel) {
|
|
await this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel);
|
|
|
|
if (tokenSource.token.isCancellationRequested) {
|
|
return;
|
|
}
|
|
|
|
this._activeKernelResolvePromise = this.activeKernel.resolve(this.viewModel!.uri, this.getId(), tokenSource.token);
|
|
await this._activeKernelResolvePromise;
|
|
|
|
if (tokenSource.token.isCancellationRequested) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
memento[provider.id] = this._activeKernel?.id;
|
|
this._activeKernelMemento.saveMemento();
|
|
|
|
tokenSource.dispose();
|
|
return;
|
|
}
|
|
|
|
// choose a preferred kernel
|
|
const kernelsFromSameExtension = kernels.filter(kernel => kernel.extension.value === provider.providerExtensionId);
|
|
if (kernelsFromSameExtension.length) {
|
|
const cachedKernelId = memento[provider.id];
|
|
|
|
const preferedKernel = kernelsFromSameExtension.find(kernel => kernel.isPreferred)
|
|
|| kernelsFromSameExtension.find(kernel => kernel.id === cachedKernelId)
|
|
|| kernelsFromSameExtension[0];
|
|
this.activeKernel = preferedKernel;
|
|
await this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel);
|
|
|
|
if (tokenSource.token.isCancellationRequested) {
|
|
return;
|
|
}
|
|
|
|
await preferedKernel.resolve(this.viewModel!.uri, this.getId(), tokenSource.token);
|
|
|
|
if (tokenSource.token.isCancellationRequested) {
|
|
return;
|
|
}
|
|
|
|
memento[provider.id] = this._activeKernel?.id;
|
|
this._activeKernelMemento.saveMemento();
|
|
tokenSource.dispose();
|
|
return;
|
|
}
|
|
|
|
// the provider doesn't have a builtin kernel, choose a kernel
|
|
this.activeKernel = kernels[0];
|
|
if (this.activeKernel) {
|
|
await this._loadKernelPreloads(this.activeKernel.extensionLocation, this.activeKernel);
|
|
if (tokenSource.token.isCancellationRequested) {
|
|
return;
|
|
}
|
|
|
|
await this.activeKernel.resolve(this.viewModel!.uri, this.getId(), tokenSource.token);
|
|
if (tokenSource.token.isCancellationRequested) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
tokenSource.dispose();
|
|
}
|
|
|
|
private async _loadKernelPreloads(extensionLocation: URI, kernel: INotebookKernelInfoDto) {
|
|
if (kernel.preloads && kernel.preloads.length) {
|
|
await this._resolveWebview();
|
|
this._webview?.updateKernelPreloads([extensionLocation], kernel.preloads.map(preload => URI.revive(preload)));
|
|
}
|
|
}
|
|
|
|
private _updateForMetadata(): void {
|
|
const notebookMetadata = this.viewModel!.metadata;
|
|
this._editorEditable?.set(!!notebookMetadata?.editable);
|
|
this._editorRunnable?.set(!!notebookMetadata?.runnable);
|
|
DOM.toggleClass(this._overlayContainer, 'notebook-editor-editable', !!notebookMetadata?.editable);
|
|
DOM.toggleClass(this.getDomNode(), 'notebook-editor-editable', !!notebookMetadata?.editable);
|
|
|
|
this._notebookExecuting?.set(notebookMetadata.runState === NotebookRunState.Running);
|
|
}
|
|
|
|
private async _resolveWebview(): Promise<BackLayerWebView | null> {
|
|
if (!this.textModel) {
|
|
return null;
|
|
}
|
|
|
|
if (this._webviewResolvePromise) {
|
|
return this._webviewResolvePromise;
|
|
}
|
|
|
|
if (!this._webview) {
|
|
this._webview = this.instantiationService.createInstance(BackLayerWebView, this, this.getId(), this.textModel!.uri);
|
|
// attach the webview container to the DOM tree first
|
|
this._list?.rowsContainer.insertAdjacentElement('afterbegin', this._webview.element);
|
|
}
|
|
|
|
this._webviewResolvePromise = new Promise(async resolve => {
|
|
await this._webview!.createWebview();
|
|
this._webview!.webview!.onDidBlur(() => {
|
|
this._outputFocus?.set(false);
|
|
this.updateEditorFocus();
|
|
|
|
if (this._overlayContainer.contains(document.activeElement)) {
|
|
this._webiewFocused = false;
|
|
}
|
|
});
|
|
this._webview!.webview!.onDidFocus(() => {
|
|
this._outputFocus?.set(true);
|
|
this.updateEditorFocus();
|
|
this._onDidFocusEmitter.fire();
|
|
|
|
if (this._overlayContainer.contains(document.activeElement)) {
|
|
this._webiewFocused = true;
|
|
}
|
|
});
|
|
|
|
this._localStore.add(this._webview!.onMessage(({ message, forRenderer }) => {
|
|
if (this.viewModel) {
|
|
this.notebookService.onDidReceiveMessage(this.viewModel.viewType, this.getId(), forRenderer, message);
|
|
}
|
|
}));
|
|
|
|
this._webviewResolved = true;
|
|
|
|
resolve(this._webview!);
|
|
});
|
|
|
|
return this._webviewResolvePromise;
|
|
}
|
|
|
|
private async _createWebview(id: string, resource: URI): Promise<void> {
|
|
this._webview = this.instantiationService.createInstance(BackLayerWebView, this, id, resource);
|
|
// attach the webview container to the DOM tree first
|
|
this._list?.rowsContainer.insertAdjacentElement('afterbegin', this._webview.element);
|
|
}
|
|
|
|
private async _attachModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined) {
|
|
await this._createWebview(this.getId(), textModel.uri);
|
|
|
|
this._eventDispatcher = new NotebookEventDispatcher();
|
|
this.viewModel = this.instantiationService.createInstance(NotebookViewModel, textModel.viewType, textModel, this._eventDispatcher, this.getLayoutInfo());
|
|
this._eventDispatcher.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]);
|
|
|
|
this._updateForMetadata();
|
|
this._localStore.add(this._eventDispatcher.onDidChangeMetadata(() => {
|
|
this._updateForMetadata();
|
|
}));
|
|
|
|
// restore view states, including contributions
|
|
|
|
{
|
|
// restore view state
|
|
this.viewModel.restoreEditorViewState(viewState);
|
|
|
|
// contribution state restore
|
|
|
|
const contributionsState = viewState?.contributionsState || {};
|
|
const keys = Object.keys(this._contributions);
|
|
for (let i = 0, len = keys.length; i < len; i++) {
|
|
const id = keys[i];
|
|
const contribution = this._contributions[id];
|
|
if (typeof contribution.restoreViewState === 'function') {
|
|
contribution.restoreViewState(contributionsState[id]);
|
|
}
|
|
}
|
|
}
|
|
|
|
this._localStore.add(this._list!.onWillScroll(e => {
|
|
this._onWillScroll.fire(e);
|
|
if (!this._webviewResolved) {
|
|
return;
|
|
}
|
|
|
|
this._webview?.updateViewScrollTop(-e.scrollTop, true, []);
|
|
this._webviewTransparentCover!.style.top = `${e.scrollTop}px`;
|
|
}));
|
|
|
|
this._localStore.add(this._list!.onDidChangeContentHeight(() => {
|
|
DOM.scheduleAtNextAnimationFrame(() => {
|
|
if (this._isDisposed) {
|
|
return;
|
|
}
|
|
|
|
const scrollTop = this._list?.scrollTop || 0;
|
|
const scrollHeight = this._list?.scrollHeight || 0;
|
|
|
|
if (!this._webviewResolved) {
|
|
return;
|
|
}
|
|
|
|
this._webview!.element.style.height = `${scrollHeight}px`;
|
|
|
|
if (this._webview?.insetMapping) {
|
|
const updateItems: { cell: CodeCellViewModel, output: IProcessedOutput, cellTop: number }[] = [];
|
|
const removedItems: IProcessedOutput[] = [];
|
|
this._webview?.insetMapping.forEach((value, key) => {
|
|
const cell = value.cell;
|
|
const viewIndex = this._list?.getViewIndex(cell);
|
|
|
|
if (viewIndex === undefined) {
|
|
return;
|
|
}
|
|
|
|
if (cell.outputs.indexOf(key) < 0) {
|
|
// output is already gone
|
|
removedItems.push(key);
|
|
}
|
|
|
|
const cellTop = this._list?.getAbsoluteTopOfElement(cell) || 0;
|
|
if (this._webview!.shouldUpdateInset(cell, key, cellTop)) {
|
|
updateItems.push({
|
|
cell: cell,
|
|
output: key,
|
|
cellTop: cellTop
|
|
});
|
|
}
|
|
});
|
|
|
|
removedItems.forEach(output => this._webview?.removeInset(output));
|
|
|
|
if (updateItems.length) {
|
|
this._webview?.updateViewScrollTop(-scrollTop, false, updateItems);
|
|
}
|
|
}
|
|
});
|
|
}));
|
|
|
|
this._list!.attachViewModel(this.viewModel);
|
|
this._localStore.add(this._list!.onDidRemoveOutput(output => {
|
|
this.removeInset(output);
|
|
}));
|
|
this._localStore.add(this._list!.onDidHideOutput(output => {
|
|
this.hideInset(output);
|
|
}));
|
|
|
|
if (this._dimension) {
|
|
this._list?.layout(this._dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP, this._dimension.width);
|
|
} else {
|
|
this._list!.layout();
|
|
}
|
|
|
|
this._dndController?.clearGlobalDragState();
|
|
|
|
// restore list state at last, it must be after list layout
|
|
this.restoreListViewState(viewState);
|
|
}
|
|
|
|
restoreListViewState(viewState: INotebookEditorViewState | undefined): void {
|
|
if (viewState?.scrollPosition !== undefined) {
|
|
this._list!.scrollTop = viewState!.scrollPosition.top;
|
|
this._list!.scrollLeft = viewState!.scrollPosition.left;
|
|
} else {
|
|
this._list!.scrollTop = 0;
|
|
this._list!.scrollLeft = 0;
|
|
}
|
|
|
|
const focusIdx = typeof viewState?.focus === 'number' ? viewState.focus : 0;
|
|
if (focusIdx < this._list!.length) {
|
|
this._list!.setFocus([focusIdx]);
|
|
this._list!.setSelection([focusIdx]);
|
|
} else if (this._list!.length > 0) {
|
|
this._list!.setFocus([0]);
|
|
}
|
|
|
|
if (viewState?.editorFocused) {
|
|
const cell = this._notebookViewModel?.viewCells[focusIdx];
|
|
if (cell) {
|
|
cell.focusMode = CellFocusMode.Editor;
|
|
}
|
|
}
|
|
}
|
|
|
|
getEditorViewState(): INotebookEditorViewState {
|
|
const state = this._notebookViewModel?.getEditorViewState();
|
|
if (!state) {
|
|
return {
|
|
editingCells: {},
|
|
editorViewStates: {}
|
|
};
|
|
}
|
|
|
|
if (this._list) {
|
|
state.scrollPosition = { left: this._list.scrollLeft, top: this._list.scrollTop };
|
|
const cellHeights: { [key: number]: number } = {};
|
|
for (let i = 0; i < this.viewModel!.length; i++) {
|
|
const elm = this.viewModel!.viewCells[i] as CellViewModel;
|
|
if (elm.cellKind === CellKind.Code) {
|
|
cellHeights[i] = elm.layoutInfo.totalHeight;
|
|
} else {
|
|
cellHeights[i] = elm.layoutInfo.totalHeight;
|
|
}
|
|
}
|
|
|
|
state.cellTotalHeights = cellHeights;
|
|
|
|
const focus = this._list.getFocus()[0];
|
|
if (typeof focus === 'number') {
|
|
const element = this._notebookViewModel!.viewCells[focus];
|
|
if (element) {
|
|
const itemDOM = this._list?.domElementOfElement(element);
|
|
const editorFocused = !!(document.activeElement && itemDOM && itemDOM.contains(document.activeElement));
|
|
|
|
state.editorFocused = editorFocused;
|
|
state.focus = focus;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Save contribution view states
|
|
const contributionsState: { [key: string]: unknown } = {};
|
|
|
|
const keys = Object.keys(this._contributions);
|
|
for (const id of keys) {
|
|
const contribution = this._contributions[id];
|
|
if (typeof contribution.saveViewState === 'function') {
|
|
contributionsState[id] = contribution.saveViewState();
|
|
}
|
|
}
|
|
|
|
state.contributionsState = contributionsState;
|
|
return state;
|
|
}
|
|
|
|
// private saveEditorViewState(input: NotebookEditorInput): void {
|
|
// if (this.group && this.notebookViewModel) {
|
|
// }
|
|
// }
|
|
|
|
// private loadTextEditorViewState(): INotebookEditorViewState | undefined {
|
|
// return this.editorMemento.loadEditorState(this.group, input.resource);
|
|
// }
|
|
|
|
layout(dimension: DOM.Dimension, shadowElement?: HTMLElement): void {
|
|
if (!shadowElement && this._shadowElementViewInfo === null) {
|
|
this._dimension = dimension;
|
|
return;
|
|
}
|
|
|
|
if (shadowElement) {
|
|
const containerRect = shadowElement.getBoundingClientRect();
|
|
|
|
this._shadowElementViewInfo = {
|
|
height: containerRect.height,
|
|
width: containerRect.width,
|
|
top: containerRect.top,
|
|
left: containerRect.left
|
|
};
|
|
}
|
|
|
|
this._dimension = new DOM.Dimension(dimension.width, dimension.height);
|
|
DOM.size(this._body, dimension.width, dimension.height);
|
|
this._list?.updateOptions({ additionalScrollHeight: this._scrollBeyondLastLine ? dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP : 0 });
|
|
this._list?.layout(dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP, dimension.width);
|
|
|
|
this._overlayContainer.style.visibility = 'visible';
|
|
this._overlayContainer.style.display = 'block';
|
|
this._overlayContainer.style.position = 'absolute';
|
|
this._overlayContainer.style.top = `${this._shadowElementViewInfo!.top}px`;
|
|
this._overlayContainer.style.left = `${this._shadowElementViewInfo!.left}px`;
|
|
this._overlayContainer.style.width = `${dimension ? dimension.width : this._shadowElementViewInfo!.width}px`;
|
|
this._overlayContainer.style.height = `${dimension ? dimension.height : this._shadowElementViewInfo!.height}px`;
|
|
|
|
if (this._webviewTransparentCover) {
|
|
this._webviewTransparentCover.style.height = `${dimension.height}px`;
|
|
this._webviewTransparentCover.style.width = `${dimension.width}px`;
|
|
}
|
|
|
|
this._eventDispatcher?.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]);
|
|
}
|
|
|
|
// protected saveState(): void {
|
|
// if (this.input instanceof NotebookEditorInput) {
|
|
// this.saveEditorViewState(this.input);
|
|
// }
|
|
|
|
// super.saveState();
|
|
// }
|
|
|
|
//#endregion
|
|
|
|
//#region Editor Features
|
|
|
|
selectElement(cell: ICellViewModel) {
|
|
this._list?.selectElement(cell);
|
|
// this.viewModel!.selectionHandles = [cell.handle];
|
|
}
|
|
|
|
revealInView(cell: ICellViewModel) {
|
|
this._list?.revealElementInView(cell);
|
|
}
|
|
|
|
revealInCenterIfOutsideViewport(cell: ICellViewModel) {
|
|
this._list?.revealElementInCenterIfOutsideViewport(cell);
|
|
}
|
|
|
|
revealInCenter(cell: ICellViewModel) {
|
|
this._list?.revealElementInCenter(cell);
|
|
}
|
|
|
|
async revealLineInViewAsync(cell: ICellViewModel, line: number): Promise<void> {
|
|
return this._list?.revealElementLineInViewAsync(cell, line);
|
|
}
|
|
|
|
async revealLineInCenterAsync(cell: ICellViewModel, line: number): Promise<void> {
|
|
return this._list?.revealElementLineInCenterAsync(cell, line);
|
|
}
|
|
|
|
async revealLineInCenterIfOutsideViewportAsync(cell: ICellViewModel, line: number): Promise<void> {
|
|
return this._list?.revealElementLineInCenterIfOutsideViewportAsync(cell, line);
|
|
}
|
|
|
|
async revealRangeInViewAsync(cell: ICellViewModel, range: Range): Promise<void> {
|
|
return this._list?.revealElementRangeInViewAsync(cell, range);
|
|
}
|
|
|
|
async revealRangeInCenterAsync(cell: ICellViewModel, range: Range): Promise<void> {
|
|
return this._list?.revealElementRangeInCenterAsync(cell, range);
|
|
}
|
|
|
|
async revealRangeInCenterIfOutsideViewportAsync(cell: ICellViewModel, range: Range): Promise<void> {
|
|
return this._list?.revealElementRangeInCenterIfOutsideViewportAsync(cell, range);
|
|
}
|
|
|
|
setCellSelection(cell: ICellViewModel, range: Range): void {
|
|
this._list?.setCellSelection(cell, range);
|
|
}
|
|
|
|
changeModelDecorations<T>(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null {
|
|
return this._notebookViewModel?.changeModelDecorations<T>(callback) || null;
|
|
}
|
|
|
|
setHiddenAreas(_ranges: ICellRange[]): boolean {
|
|
return this._list!.setHiddenAreas(_ranges, true);
|
|
}
|
|
|
|
//#endregion
|
|
|
|
//#region Mouse Events
|
|
private readonly _onMouseUp: Emitter<INotebookEditorMouseEvent> = this._register(new Emitter<INotebookEditorMouseEvent>());
|
|
public readonly onMouseUp: Event<INotebookEditorMouseEvent> = this._onMouseUp.event;
|
|
|
|
private readonly _onMouseDown: Emitter<INotebookEditorMouseEvent> = this._register(new Emitter<INotebookEditorMouseEvent>());
|
|
public readonly onMouseDown: Event<INotebookEditorMouseEvent> = this._onMouseDown.event;
|
|
|
|
private pendingLayouts = new WeakMap<ICellViewModel, IDisposable>();
|
|
|
|
//#endregion
|
|
|
|
//#region Cell operations
|
|
async layoutNotebookCell(cell: ICellViewModel, height: number): Promise<void> {
|
|
const viewIndex = this._list!.getViewIndex(cell);
|
|
if (viewIndex === undefined) {
|
|
// the cell is hidden
|
|
return;
|
|
}
|
|
|
|
const relayout = (cell: ICellViewModel, height: number) => {
|
|
if (this._isDisposed) {
|
|
return;
|
|
}
|
|
|
|
this._list?.updateElementHeight2(cell, height);
|
|
};
|
|
|
|
if (this.pendingLayouts.has(cell)) {
|
|
this.pendingLayouts.get(cell)!.dispose();
|
|
}
|
|
|
|
let r: () => void;
|
|
const layoutDisposable = DOM.scheduleAtNextAnimationFrame(() => {
|
|
if (this._isDisposed) {
|
|
return;
|
|
}
|
|
|
|
this.pendingLayouts.delete(cell);
|
|
|
|
relayout(cell, height);
|
|
r();
|
|
});
|
|
|
|
this.pendingLayouts.set(cell, toDisposable(() => {
|
|
layoutDisposable.dispose();
|
|
r();
|
|
}));
|
|
|
|
return new Promise(resolve => { r = resolve; });
|
|
}
|
|
|
|
insertNotebookCell(cell: ICellViewModel | undefined, type: CellKind, direction: 'above' | 'below' = 'above', initialText: string = '', ui: boolean = false): CellViewModel | null {
|
|
if (!this._notebookViewModel!.metadata.editable) {
|
|
return null;
|
|
}
|
|
|
|
const index = cell ? this._notebookViewModel!.getCellIndex(cell) : 0;
|
|
const nextIndex = ui ? this._notebookViewModel!.getNextVisibleCellIndex(index) : index + 1;
|
|
const newLanguages = this._notebookViewModel!.languages;
|
|
const language = (cell?.cellKind === CellKind.Code && type === CellKind.Code)
|
|
? cell.language
|
|
: ((type === CellKind.Code && newLanguages && newLanguages.length) ? newLanguages[0] : 'markdown');
|
|
const insertIndex = cell ?
|
|
(direction === 'above' ? index : nextIndex) :
|
|
index;
|
|
const newCell = this._notebookViewModel!.createCell(insertIndex, initialText, language, type, undefined, true);
|
|
return newCell as CellViewModel;
|
|
}
|
|
|
|
async splitNotebookCell(cell: ICellViewModel): Promise<CellViewModel[] | null> {
|
|
if (!this._notebookViewModel!.metadata.editable) {
|
|
return null;
|
|
}
|
|
|
|
const index = this._notebookViewModel!.getCellIndex(cell);
|
|
|
|
return this._notebookViewModel!.splitNotebookCell(index);
|
|
}
|
|
|
|
async joinNotebookCells(cell: ICellViewModel, direction: 'above' | 'below', constraint?: CellKind): Promise<ICellViewModel | null> {
|
|
if (!this._notebookViewModel!.metadata.editable) {
|
|
return null;
|
|
}
|
|
|
|
const index = this._notebookViewModel!.getCellIndex(cell);
|
|
const ret = await this._notebookViewModel!.joinNotebookCells(index, direction, constraint);
|
|
|
|
if (ret) {
|
|
ret.deletedCells.forEach(cell => {
|
|
if (this.pendingLayouts.has(cell)) {
|
|
this.pendingLayouts.get(cell)!.dispose();
|
|
}
|
|
});
|
|
|
|
return ret.cell;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async deleteNotebookCell(cell: ICellViewModel): Promise<boolean> {
|
|
if (!this._notebookViewModel!.metadata.editable) {
|
|
return false;
|
|
}
|
|
|
|
if (this.pendingLayouts.has(cell)) {
|
|
this.pendingLayouts.get(cell)!.dispose();
|
|
}
|
|
|
|
const index = this._notebookViewModel!.getCellIndex(cell);
|
|
this._notebookViewModel!.deleteCell(index, true);
|
|
return true;
|
|
}
|
|
|
|
async moveCellDown(cell: ICellViewModel): Promise<ICellViewModel | null> {
|
|
if (!this._notebookViewModel!.metadata.editable) {
|
|
return null;
|
|
}
|
|
|
|
const index = this._notebookViewModel!.getCellIndex(cell);
|
|
if (index === this._notebookViewModel!.length - 1) {
|
|
return null;
|
|
}
|
|
|
|
const newIdx = index + 2; // This is the adjustment for the index before the cell has been "removed" from its original index
|
|
return this._moveCellToIndex(index, 1, newIdx);
|
|
}
|
|
|
|
async moveCellUp(cell: ICellViewModel): Promise<ICellViewModel | null> {
|
|
if (!this._notebookViewModel!.metadata.editable) {
|
|
return null;
|
|
}
|
|
|
|
const index = this._notebookViewModel!.getCellIndex(cell);
|
|
if (index === 0) {
|
|
return null;
|
|
}
|
|
|
|
const newIdx = index - 1;
|
|
return this._moveCellToIndex(index, 1, newIdx);
|
|
}
|
|
|
|
async moveCell(cell: ICellViewModel, relativeToCell: ICellViewModel, direction: 'above' | 'below'): Promise<ICellViewModel | null> {
|
|
if (!this._notebookViewModel!.metadata.editable) {
|
|
return null;
|
|
}
|
|
|
|
if (cell === relativeToCell) {
|
|
return null;
|
|
}
|
|
|
|
const originalIdx = this._notebookViewModel!.getCellIndex(cell);
|
|
const relativeToIndex = this._notebookViewModel!.getCellIndex(relativeToCell);
|
|
|
|
const newIdx = direction === 'above' ? relativeToIndex : relativeToIndex + 1;
|
|
return this._moveCellToIndex(originalIdx, 1, newIdx);
|
|
}
|
|
|
|
async moveCellsToIdx(index: number, length: number, toIdx: number): Promise<ICellViewModel | null> {
|
|
if (!this._notebookViewModel!.metadata.editable) {
|
|
return null;
|
|
}
|
|
|
|
return this._moveCellToIndex(index, length, toIdx);
|
|
}
|
|
|
|
/**
|
|
* @param index The current index of the cell
|
|
* @param desiredIndex The desired index, in an index scheme for the state of the tree before the current cell has been "removed".
|
|
* @example to move the cell from index 0 down one spot, call with (0, 2)
|
|
*/
|
|
private async _moveCellToIndex(index: number, length: number, desiredIndex: number): Promise<ICellViewModel | null> {
|
|
if (index < desiredIndex) {
|
|
// The cell is moving "down", it will free up one index spot and consume a new one
|
|
desiredIndex -= length;
|
|
}
|
|
|
|
if (index === desiredIndex) {
|
|
return null;
|
|
}
|
|
|
|
if (!this._notebookViewModel!.moveCellToIdx(index, length, desiredIndex, true)) {
|
|
throw new Error('Notebook Editor move cell, index out of range');
|
|
}
|
|
|
|
let r: (val: ICellViewModel | null) => void;
|
|
DOM.scheduleAtNextAnimationFrame(() => {
|
|
if (this._isDisposed) {
|
|
r(null);
|
|
}
|
|
|
|
const viewCell = this._notebookViewModel!.viewCells[desiredIndex];
|
|
this._list?.revealElementInView(viewCell);
|
|
r(viewCell);
|
|
});
|
|
|
|
return new Promise(resolve => { r = resolve; });
|
|
}
|
|
|
|
editNotebookCell(cell: CellViewModel): void {
|
|
if (!cell.getEvaluatedMetadata(this._notebookViewModel!.metadata).editable) {
|
|
return;
|
|
}
|
|
|
|
cell.editState = CellEditState.Editing;
|
|
|
|
this._renderedEditors.get(cell)?.focus();
|
|
}
|
|
|
|
getActiveCell() {
|
|
const elements = this._list?.getFocusedElements();
|
|
|
|
if (elements && elements.length) {
|
|
return elements[0];
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
async cancelNotebookExecution(): Promise<void> {
|
|
if (this._notebookViewModel?.metadata.runState !== NotebookRunState.Running) {
|
|
return;
|
|
}
|
|
|
|
return this._cancelNotebookExecution();
|
|
}
|
|
|
|
private async _cancelNotebookExecution(): Promise<void> {
|
|
const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0];
|
|
if (provider && this._activeKernel) {
|
|
await (this._activeKernel as INotebookKernelInfo2).cancelNotebookCell!(this._notebookViewModel!.uri, undefined);
|
|
}
|
|
}
|
|
|
|
async executeNotebook(): Promise<void> {
|
|
if (!this._notebookViewModel!.metadata.runnable) {
|
|
return;
|
|
}
|
|
|
|
return this._executeNotebook();
|
|
}
|
|
|
|
private async _executeNotebook(): Promise<void> {
|
|
const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0];
|
|
if (provider && this._activeKernel) {
|
|
// TODO@rebornix temp any cast, should be removed once we remove legacy kernel support
|
|
if ((this._activeKernel as INotebookKernelInfo2).executeNotebookCell) {
|
|
if (this._activeKernelResolvePromise) {
|
|
await this._activeKernelResolvePromise;
|
|
}
|
|
|
|
await (this._activeKernel as INotebookKernelInfo2).executeNotebookCell!(this._notebookViewModel!.uri, undefined);
|
|
} else {
|
|
await this.notebookService.executeNotebook(this._notebookViewModel!.viewType, this._notebookViewModel!.uri, this._activeKernel.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
async cancelNotebookCellExecution(cell: ICellViewModel): Promise<void> {
|
|
if (cell.cellKind !== CellKind.Code) {
|
|
return;
|
|
}
|
|
|
|
const metadata = cell.getEvaluatedMetadata(this._notebookViewModel!.metadata);
|
|
if (!metadata.runnable) {
|
|
return;
|
|
}
|
|
|
|
if (metadata.runState !== NotebookCellRunState.Running) {
|
|
return;
|
|
}
|
|
|
|
await this._cancelNotebookCell(cell);
|
|
}
|
|
|
|
private async _cancelNotebookCell(cell: ICellViewModel): Promise<void> {
|
|
const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0];
|
|
if (provider && this._activeKernel) {
|
|
return await (this._activeKernel as INotebookKernelInfo2).cancelNotebookCell!(this._notebookViewModel!.uri, cell.handle);
|
|
}
|
|
}
|
|
|
|
async executeNotebookCell(cell: ICellViewModel): Promise<void> {
|
|
if (cell.cellKind === CellKind.Markdown) {
|
|
this.focusNotebookCell(cell, 'container');
|
|
return;
|
|
}
|
|
|
|
if (!cell.getEvaluatedMetadata(this._notebookViewModel!.metadata).runnable) {
|
|
return;
|
|
}
|
|
|
|
await this._executeNotebookCell(cell);
|
|
}
|
|
|
|
private async _executeNotebookCell(cell: ICellViewModel): Promise<void> {
|
|
const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0];
|
|
if (provider) {
|
|
const viewType = provider.id;
|
|
const notebookUri = this._notebookViewModel!.uri;
|
|
|
|
if (this._activeKernel) {
|
|
// TODO@rebornix temp any cast, should be removed once we remove legacy kernel support
|
|
if ((this._activeKernel as INotebookKernelInfo2).executeNotebookCell) {
|
|
await (this._activeKernel as INotebookKernelInfo2).executeNotebookCell!(this._notebookViewModel!.uri, cell.handle);
|
|
} else {
|
|
|
|
return await this.notebookService.executeNotebookCell(viewType, notebookUri, cell.handle, this._activeKernel.id);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
focusNotebookCell(cell: ICellViewModel, focusItem: 'editor' | 'container' | 'output') {
|
|
if (this._isDisposed) {
|
|
return;
|
|
}
|
|
|
|
if (focusItem === 'editor') {
|
|
this.selectElement(cell);
|
|
this._list?.focusView();
|
|
|
|
cell.editState = CellEditState.Editing;
|
|
cell.focusMode = CellFocusMode.Editor;
|
|
this.revealInCenterIfOutsideViewport(cell);
|
|
} else if (focusItem === 'output') {
|
|
this.selectElement(cell);
|
|
this._list?.focusView();
|
|
|
|
if (!this._webview) {
|
|
return;
|
|
}
|
|
this._webview.focusOutput(cell.id);
|
|
|
|
cell.editState = CellEditState.Preview;
|
|
cell.focusMode = CellFocusMode.Container;
|
|
this.revealInCenterIfOutsideViewport(cell);
|
|
} else {
|
|
const itemDOM = this._list?.domElementOfElement(cell);
|
|
if (document.activeElement && itemDOM && itemDOM.contains(document.activeElement)) {
|
|
(document.activeElement as HTMLElement).blur();
|
|
}
|
|
|
|
cell.editState = CellEditState.Preview;
|
|
cell.focusMode = CellFocusMode.Container;
|
|
|
|
this.selectElement(cell);
|
|
this.revealInCenterIfOutsideViewport(cell);
|
|
this._list?.focusView();
|
|
}
|
|
}
|
|
|
|
//#endregion
|
|
|
|
//#region MISC
|
|
|
|
deltaCellDecorations(oldDecorations: string[], newDecorations: INotebookDeltaDecoration[]): string[] {
|
|
return this._notebookViewModel?.deltaCellDecorations(oldDecorations, newDecorations) || [];
|
|
}
|
|
|
|
deltaCellOutputContainerClassNames(cellId: string, added: string[], removed: string[]) {
|
|
this._webview?.deltaCellOutputContainerClassNames(cellId, added, removed);
|
|
}
|
|
|
|
getLayoutInfo(): NotebookLayoutInfo {
|
|
if (!this._list) {
|
|
throw new Error('Editor is not initalized successfully');
|
|
}
|
|
|
|
return {
|
|
width: this._dimension!.width,
|
|
height: this._dimension!.height,
|
|
fontInfo: this._fontInfo!
|
|
};
|
|
}
|
|
|
|
triggerScroll(event: IMouseWheelEvent) {
|
|
this._list?.triggerScrollFromMouseWheelEvent(event);
|
|
}
|
|
|
|
async createInset(cell: CodeCellViewModel, output: IInsetRenderOutput, offset: number) {
|
|
if (!this._webview) {
|
|
return;
|
|
}
|
|
|
|
await this._resolveWebview();
|
|
|
|
if (!this._webview!.insetMapping.has(output.source)) {
|
|
const cellTop = this._list?.getAbsoluteTopOfElement(cell) || 0;
|
|
await this._webview!.createInset(cell, output, cellTop, offset);
|
|
} else {
|
|
const cellTop = this._list?.getAbsoluteTopOfElement(cell) || 0;
|
|
const scrollTop = this._list?.scrollTop || 0;
|
|
|
|
this._webview!.updateViewScrollTop(-scrollTop, true, [{ cell, output: output.source, cellTop }]);
|
|
}
|
|
}
|
|
|
|
removeInset(output: IProcessedOutput) {
|
|
if (!this._webview || !this._webviewResolved) {
|
|
return;
|
|
}
|
|
|
|
this._webview!.removeInset(output);
|
|
}
|
|
|
|
hideInset(output: IProcessedOutput) {
|
|
if (!this._webview || !this._webviewResolved) {
|
|
return;
|
|
}
|
|
|
|
this._webview!.hideInset(output);
|
|
}
|
|
|
|
getOutputRenderer(): OutputRenderer {
|
|
return this._outputRenderer;
|
|
}
|
|
|
|
postMessage(forRendererId: string | undefined, message: any) {
|
|
if (!this._webview || !this._webviewResolved) {
|
|
return;
|
|
}
|
|
|
|
if (forRendererId === undefined) {
|
|
this._webview.webview?.postMessage(message);
|
|
} else {
|
|
this._webview.postRendererMessage(forRendererId, message);
|
|
}
|
|
}
|
|
|
|
toggleClassName(className: string) {
|
|
DOM.toggleClass(this._overlayContainer, className);
|
|
}
|
|
|
|
addClassName(className: string) {
|
|
DOM.addClass(this._overlayContainer, className);
|
|
}
|
|
|
|
removeClassName(className: string) {
|
|
DOM.removeClass(this._overlayContainer, className);
|
|
}
|
|
|
|
|
|
//#endregion
|
|
|
|
//#region Editor Contributions
|
|
public getContribution<T extends INotebookEditorContribution>(id: string): T {
|
|
return <T>(this._contributions[id] || null);
|
|
}
|
|
|
|
//#endregion
|
|
|
|
dispose() {
|
|
this._isDisposed = true;
|
|
this._onWillDispose.fire();
|
|
// dispose webview first
|
|
this._webview?.dispose();
|
|
|
|
this.notebookService.removeNotebookEditor(this);
|
|
const keys = Object.keys(this._contributions);
|
|
for (let i = 0, len = keys.length; i < len; i++) {
|
|
const contributionId = keys[i];
|
|
this._contributions[contributionId].dispose();
|
|
}
|
|
|
|
this._localStore.clear();
|
|
this._list?.dispose();
|
|
this._listTopCellToolbar?.dispose();
|
|
|
|
this._overlayContainer.remove();
|
|
this.viewModel?.dispose();
|
|
|
|
// this._layoutService.container.removeChild(this.overlayContainer);
|
|
|
|
super.dispose();
|
|
}
|
|
|
|
toJSON(): object {
|
|
return {
|
|
notebookHandle: this.viewModel?.handle
|
|
};
|
|
}
|
|
}
|
|
|
|
export const notebookCellBorder = registerColor('notebook.cellBorderColor', {
|
|
dark: transparent(PANEL_BORDER, .4),
|
|
light: transparent(listInactiveSelectionBackground, 1),
|
|
hc: PANEL_BORDER
|
|
}, nls.localize('notebook.cellBorderColor', "The border color for notebook cells."));
|
|
|
|
export const focusedEditorBorderColor = registerColor('notebook.focusedEditorBorder', {
|
|
light: focusBorder,
|
|
dark: focusBorder,
|
|
hc: focusBorder
|
|
}, nls.localize('notebook.focusedEditorBorder', "The color of the notebook cell editor border."));
|
|
|
|
export const cellStatusIconSuccess = registerColor('notebookStatusSuccessIcon.foreground', {
|
|
light: debugIconStartForeground,
|
|
dark: debugIconStartForeground,
|
|
hc: debugIconStartForeground
|
|
}, nls.localize('notebookStatusSuccessIcon.foreground', "The error icon color of notebook cells in the cell status bar."));
|
|
|
|
export const cellStatusIconError = registerColor('notebookStatusErrorIcon.foreground', {
|
|
light: errorForeground,
|
|
dark: errorForeground,
|
|
hc: errorForeground
|
|
}, nls.localize('notebookStatusErrorIcon.foreground', "The error icon color of notebook cells in the cell status bar."));
|
|
|
|
export const cellStatusIconRunning = registerColor('notebookStatusRunningIcon.foreground', {
|
|
light: foreground,
|
|
dark: foreground,
|
|
hc: foreground
|
|
}, nls.localize('notebookStatusRunningIcon.foreground', "The running icon color of notebook cells in the cell status bar."));
|
|
|
|
export const notebookOutputContainerColor = registerColor('notebook.outputContainerBackgroundColor', {
|
|
dark: notebookCellBorder,
|
|
light: transparent(listFocusBackground, .4),
|
|
hc: null
|
|
}, nls.localize('notebook.outputContainerBackgroundColor', "The Color of the notebook output container background."));
|
|
|
|
// TODO currently also used for toolbar border, if we keep all of this, pick a generic name
|
|
export const CELL_TOOLBAR_SEPERATOR = registerColor('notebook.cellToolbarSeparator', {
|
|
dark: Color.fromHex('#808080').transparent(0.35),
|
|
light: Color.fromHex('#808080').transparent(0.35),
|
|
hc: contrastBorder
|
|
}, nls.localize('notebook.cellToolbarSeparator', "The color of the seperator in the cell bottom toolbar"));
|
|
|
|
export const focusedCellBackground = registerColor('notebook.focusedCellBackground', {
|
|
dark: transparent(PANEL_BORDER, .4),
|
|
light: transparent(listFocusBackground, .4),
|
|
hc: null
|
|
}, nls.localize('focusedCellBackground', "The background color of a cell when the cell is focused."));
|
|
|
|
export const cellHoverBackground = registerColor('notebook.cellHoverBackground', {
|
|
dark: transparent(focusedCellBackground, .5),
|
|
light: transparent(focusedCellBackground, .7),
|
|
hc: null
|
|
}, nls.localize('notebook.cellHoverBackground', "The background color of a cell when the cell is hovered."));
|
|
|
|
export const focusedCellBorder = registerColor('notebook.focusedCellBorder', {
|
|
dark: Color.white.transparent(0.12),
|
|
light: Color.black.transparent(0.12),
|
|
hc: focusBorder
|
|
}, nls.localize('notebook.focusedCellBorder', "The color of the cell's top and bottom border when the cell is focused."));
|
|
|
|
export const cellStatusBarItemHover = registerColor('notebook.cellStatusBarItemHoverBackground', {
|
|
light: new Color(new RGBA(0, 0, 0, 0.08)),
|
|
dark: new Color(new RGBA(255, 255, 255, 0.15)),
|
|
hc: new Color(new RGBA(255, 255, 255, 0.15)),
|
|
}, nls.localize('notebook.cellStatusBarItemHoverBackground', "The background color of notebook cell status bar items."));
|
|
|
|
export const cellInsertionIndicator = registerColor('notebook.cellInsertionIndicator', {
|
|
light: focusBorder,
|
|
dark: focusBorder,
|
|
hc: focusBorder
|
|
}, nls.localize('notebook.cellInsertionIndicator', "The color of the notebook cell insertion indicator."));
|
|
|
|
|
|
export const listScrollbarSliderBackground = registerColor('notebookScrollbarSlider.background', {
|
|
dark: scrollbarSliderBackground,
|
|
light: scrollbarSliderBackground,
|
|
hc: scrollbarSliderBackground
|
|
}, nls.localize('notebookScrollbarSliderBackground', "Notebook scrollbar slider background color."));
|
|
|
|
export const listScrollbarSliderHoverBackground = registerColor('notebookScrollbarSlider.hoverBackground', {
|
|
dark: scrollbarSliderHoverBackground,
|
|
light: scrollbarSliderHoverBackground,
|
|
hc: scrollbarSliderHoverBackground
|
|
}, nls.localize('notebookScrollbarSliderHoverBackground', "Notebook scrollbar slider background color when hovering."));
|
|
|
|
export const listScrollbarSliderActiveBackground = registerColor('notebookScrollbarSlider.activeBackground', {
|
|
dark: scrollbarSliderActiveBackground,
|
|
light: scrollbarSliderActiveBackground,
|
|
hc: scrollbarSliderActiveBackground
|
|
}, nls.localize('notebookScrollbarSliderActiveBackground', "Notebook scrollbar slider background color when clicked on."));
|
|
|
|
export const cellSymbolHighlight = registerColor('notebook.symbolHighlightBackground', {
|
|
dark: Color.fromHex('#ffffff0b'),
|
|
light: Color.fromHex('#fdff0033'),
|
|
hc: null
|
|
}, nls.localize('notebook.symbolHighlightBackground', "Background color of highlighted cell"));
|
|
|
|
registerThemingParticipant((theme, collector) => {
|
|
collector.addRule(`.notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element,
|
|
.notebookOverlay > .cell-list-container > .notebook-gutter > .monaco-list > .monaco-scrollable-element {
|
|
padding-top: ${SCROLLABLE_ELEMENT_PADDING_TOP}px;
|
|
box-sizing: border-box;
|
|
}`);
|
|
|
|
const link = theme.getColor(textLinkForeground);
|
|
if (link) {
|
|
collector.addRule(`.notebookOverlay .output a,
|
|
.notebookOverlay .cell.markdown a { color: ${link};} `);
|
|
}
|
|
const activeLink = theme.getColor(textLinkActiveForeground);
|
|
if (activeLink) {
|
|
collector.addRule(`.notebookOverlay .output a:hover,
|
|
.notebookOverlay .cell .output a:active { color: ${activeLink}; }`);
|
|
}
|
|
const shortcut = theme.getColor(textPreformatForeground);
|
|
if (shortcut) {
|
|
collector.addRule(`.notebookOverlay code,
|
|
.notebookOverlay .shortcut { color: ${shortcut}; }`);
|
|
}
|
|
const border = theme.getColor(contrastBorder);
|
|
if (border) {
|
|
collector.addRule(`.notebookOverlay .monaco-editor { border-color: ${border}; }`);
|
|
}
|
|
const quoteBackground = theme.getColor(textBlockQuoteBackground);
|
|
if (quoteBackground) {
|
|
collector.addRule(`.notebookOverlay blockquote { background: ${quoteBackground}; }`);
|
|
}
|
|
const quoteBorder = theme.getColor(textBlockQuoteBorder);
|
|
if (quoteBorder) {
|
|
collector.addRule(`.notebookOverlay blockquote { border-color: ${quoteBorder}; }`);
|
|
}
|
|
|
|
const containerBackground = theme.getColor(notebookOutputContainerColor);
|
|
if (containerBackground) {
|
|
collector.addRule(`.notebookOverlay .output { background-color: ${containerBackground}; }`);
|
|
collector.addRule(`.notebookOverlay .output-element { background-color: ${containerBackground}; }`);
|
|
}
|
|
|
|
const editorBackgroundColor = theme.getColor(editorBackground);
|
|
if (editorBackgroundColor) {
|
|
collector.addRule(`.notebookOverlay .cell .monaco-editor-background,
|
|
.notebookOverlay .cell .margin-view-overlays,
|
|
.notebookOverlay .cell .cell-statusbar-container { background: ${editorBackgroundColor}; }`);
|
|
collector.addRule(`.notebookOverlay .cell-drag-image .cell-editor-container > div { background: ${editorBackgroundColor} !important; }`);
|
|
|
|
collector.addRule(`.notebookOverlay .monaco-list-row .cell-title-toolbar { background-color: ${editorBackgroundColor}; }`);
|
|
collector.addRule(`.notebookOverlay .monaco-list-row.cell-drag-image { background-color: ${editorBackgroundColor}; }`);
|
|
collector.addRule(`.notebookOverlay .cell-bottom-toolbar-container .action-item { background-color: ${editorBackgroundColor} }`);
|
|
collector.addRule(`.notebookOverlay .cell-list-top-cell-toolbar-container .action-item { background-color: ${editorBackgroundColor} }`);
|
|
}
|
|
|
|
const cellToolbarSeperator = theme.getColor(CELL_TOOLBAR_SEPERATOR);
|
|
if (cellToolbarSeperator) {
|
|
collector.addRule(`.notebookOverlay .monaco-list-row .cell-title-toolbar { border: solid 1px ${cellToolbarSeperator}; }`);
|
|
collector.addRule(`.notebookOverlay .cell-bottom-toolbar-container .action-item { border: solid 1px ${cellToolbarSeperator} }`);
|
|
collector.addRule(`.notebookOverlay .cell-list-top-cell-toolbar-container .action-item { border: solid 1px ${cellToolbarSeperator} }`);
|
|
collector.addRule(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-collapsed-part { border-bottom: solid 1px ${cellToolbarSeperator} }`);
|
|
collector.addRule(`.notebookOverlay .monaco-action-bar .action-item.verticalSeparator { background-color: ${cellToolbarSeperator} }`);
|
|
}
|
|
|
|
const focusedCellBackgroundColor = theme.getColor(focusedCellBackground);
|
|
if (focusedCellBackgroundColor) {
|
|
collector.addRule(`.notebookOverlay .code-cell-row.focused .cell-focus-indicator,
|
|
.notebookOverlay .markdown-cell-row.focused { background-color: ${focusedCellBackgroundColor} !important; }`);
|
|
collector.addRule(`.notebookOverlay .code-cell-row.focused .cell-collapsed-part { background-color: ${focusedCellBackgroundColor} !important; }`);
|
|
}
|
|
|
|
const cellHoverBackgroundColor = theme.getColor(cellHoverBackground);
|
|
if (cellHoverBackgroundColor) {
|
|
collector.addRule(`.notebookOverlay .code-cell-row:not(.focused):hover .cell-focus-indicator,
|
|
.notebookOverlay .code-cell-row:not(.focused).cell-output-hover .cell-focus-indicator,
|
|
.notebookOverlay .markdown-cell-row:not(.focused):hover { background-color: ${cellHoverBackgroundColor} !important; }`);
|
|
collector.addRule(`.notebookOverlay .code-cell-row:not(.focused):hover .cell-collapsed-part,
|
|
.notebookOverlay .code-cell-row:not(.focused).cell-output-hover .cell-collapsed-part { background-color: ${cellHoverBackgroundColor}; }`);
|
|
}
|
|
|
|
const focusedCellBorderColor = theme.getColor(focusedCellBorder);
|
|
collector.addRule(`.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-top:before,
|
|
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-bottom:before,
|
|
.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused:before,
|
|
.monaco-workbench .notebookOverlay .monaco-list .markdown-cell-row.focused:after {
|
|
border-color: ${focusedCellBorderColor} !important;
|
|
}`);
|
|
|
|
const cellSymbolHighlightColor = theme.getColor(cellSymbolHighlight);
|
|
if (cellSymbolHighlightColor) {
|
|
collector.addRule(`.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row .nb-symbolHighlight .cell-focus-indicator,
|
|
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.markdown-cell-row .nb-symbolHighlight {
|
|
background-color: ${cellSymbolHighlightColor} !important;
|
|
}`);
|
|
}
|
|
|
|
const focusedEditorBorderColorColor = theme.getColor(focusedEditorBorderColor);
|
|
if (focusedEditorBorderColorColor) {
|
|
collector.addRule(`.notebookOverlay .monaco-list-row .cell-editor-focus .cell-editor-part:before { outline: solid 1px ${focusedEditorBorderColorColor}; }`);
|
|
}
|
|
|
|
const cellBorderColor = theme.getColor(notebookCellBorder);
|
|
if (cellBorderColor) {
|
|
collector.addRule(`.notebookOverlay .cell.markdown h1 { border-color: ${cellBorderColor}; }`);
|
|
collector.addRule(`.notebookOverlay .monaco-list-row .cell-editor-part:before { outline: solid 1px ${cellBorderColor}; }`);
|
|
}
|
|
|
|
const cellStatusSuccessIcon = theme.getColor(cellStatusIconSuccess);
|
|
if (cellStatusSuccessIcon) {
|
|
collector.addRule(`.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-run-status .codicon-check { color: ${cellStatusSuccessIcon} }`);
|
|
}
|
|
|
|
const cellStatusErrorIcon = theme.getColor(cellStatusIconError);
|
|
if (cellStatusErrorIcon) {
|
|
collector.addRule(`.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-run-status .codicon-error { color: ${cellStatusErrorIcon} }`);
|
|
}
|
|
|
|
const cellStatusRunningIcon = theme.getColor(cellStatusIconRunning);
|
|
if (cellStatusRunningIcon) {
|
|
collector.addRule(`.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-run-status .codicon-sync { color: ${cellStatusRunningIcon} }`);
|
|
}
|
|
|
|
const cellStatusBarHoverBg = theme.getColor(cellStatusBarItemHover);
|
|
if (cellStatusBarHoverBg) {
|
|
collector.addRule(`.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-language-picker:hover,
|
|
.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-status-item.cell-status-item-has-command:hover { background-color: ${cellStatusBarHoverBg}; }`);
|
|
}
|
|
|
|
const cellInsertionIndicatorColor = theme.getColor(cellInsertionIndicator);
|
|
if (cellInsertionIndicatorColor) {
|
|
collector.addRule(`.notebookOverlay > .cell-list-container > .cell-list-insertion-indicator { background-color: ${cellInsertionIndicatorColor}; }`);
|
|
}
|
|
|
|
const scrollbarSliderBackgroundColor = theme.getColor(listScrollbarSliderBackground);
|
|
if (scrollbarSliderBackgroundColor) {
|
|
collector.addRule(` .notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .scrollbar > .slider { background: ${editorBackgroundColor}; } `);
|
|
collector.addRule(` .notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .scrollbar > .slider:before { content: ""; width: 100%; height: 100%; position: absolute; background: ${scrollbarSliderBackgroundColor}; } `); /* hack to not have cells see through scroller */
|
|
}
|
|
|
|
const scrollbarSliderHoverBackgroundColor = theme.getColor(listScrollbarSliderHoverBackground);
|
|
if (scrollbarSliderHoverBackgroundColor) {
|
|
collector.addRule(` .notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .scrollbar > .slider:hover { background: ${editorBackgroundColor}; } `);
|
|
collector.addRule(` .notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .scrollbar > .slider:hover:before { content: ""; width: 100%; height: 100%; position: absolute; background: ${scrollbarSliderHoverBackgroundColor}; } `); /* hack to not have cells see through scroller */
|
|
}
|
|
|
|
const scrollbarSliderActiveBackgroundColor = theme.getColor(listScrollbarSliderActiveBackground);
|
|
if (scrollbarSliderActiveBackgroundColor) {
|
|
collector.addRule(` .notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .scrollbar > .slider.active { background: ${editorBackgroundColor}; } `);
|
|
collector.addRule(` .notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .scrollbar > .slider.active:before { content: ""; width: 100%; height: 100%; position: absolute; background: ${scrollbarSliderActiveBackgroundColor}; } `); /* hack to not have cells see through scroller */
|
|
}
|
|
|
|
// case ChangeType.Modify: return theme.getColor(editorGutterModifiedBackground);
|
|
// case ChangeType.Add: return theme.getColor(editorGutterAddedBackground);
|
|
// case ChangeType.Delete: return theme.getColor(editorGutterDeletedBackground);
|
|
// diff
|
|
|
|
const modifiedBackground = theme.getColor(editorGutterModifiedBackground);
|
|
if (modifiedBackground) {
|
|
collector.addRule(`
|
|
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row .nb-cell-modified .cell-focus-indicator {
|
|
background-color: ${modifiedBackground} !important;
|
|
}
|
|
|
|
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.markdown-cell-row .nb-cell-modified {
|
|
background-color: ${modifiedBackground} !important;
|
|
}`);
|
|
}
|
|
|
|
const addedBackground = theme.getColor(diffInserted);
|
|
if (addedBackground) {
|
|
collector.addRule(`
|
|
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row .nb-cell-added .cell-focus-indicator {
|
|
background-color: ${addedBackground} !important;
|
|
}
|
|
|
|
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.markdown-cell-row .nb-cell-added {
|
|
background-color: ${addedBackground} !important;
|
|
}`);
|
|
}
|
|
const deletedBackground = theme.getColor(diffRemoved);
|
|
if (deletedBackground) {
|
|
collector.addRule(`
|
|
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row .nb-cell-deleted .cell-focus-indicator {
|
|
background-color: ${deletedBackground} !important;
|
|
}
|
|
|
|
.monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.markdown-cell-row .nb-cell-deleted {
|
|
background-color: ${deletedBackground} !important;
|
|
}`);
|
|
}
|
|
|
|
// Cell Margin
|
|
collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row div.cell { margin: 0px ${CELL_MARGIN * 2}px 0px ${CELL_MARGIN}px; }`);
|
|
collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row div.cell.code { margin-left: ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`);
|
|
collector.addRule(`.notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .run-button-container { width: ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`);
|
|
collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > .cell-inner-container { padding-top: ${CELL_TOP_MARGIN}px; }`);
|
|
collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row > .cell-inner-container { padding-bottom: ${CELL_BOTTOM_MARGIN}px; }`);
|
|
collector.addRule(`.notebookOverlay .output { margin: 0px ${CELL_MARGIN}px 0px ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`);
|
|
collector.addRule(`.notebookOverlay .output { width: calc(100% - ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER + (CELL_MARGIN * 2)}px); }`);
|
|
|
|
collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row div.cell.markdown { padding-left: ${CELL_RUN_GUTTER}px; }`);
|
|
collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-top { height: ${CELL_TOP_MARGIN}px; }`);
|
|
collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-side { bottom: ${BOTTOM_CELL_TOOLBAR_GAP}px; }`);
|
|
collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row.code-cell-row .cell-focus-indicator-left,
|
|
.notebookOverlay .monaco-list .monaco-list-row.code-cell-row .cell-drag-handle { width: ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`);
|
|
collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row.markdown-cell-row .cell-focus-indicator-left { width: ${CODE_CELL_LEFT_MARGIN}px; }`);
|
|
collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator.cell-focus-indicator-right { width: ${CELL_MARGIN * 2}px; }`);
|
|
collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-bottom { height: ${CELL_BOTTOM_MARGIN}px; }`);
|
|
collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-shadow-container-bottom { top: ${CELL_BOTTOM_MARGIN}px; }`);
|
|
|
|
collector.addRule(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-collapsed-part { margin-left: ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; height: ${COLLAPSED_INDICATOR_HEIGHT}px; }`);
|
|
collector.addRule(`.notebookOverlay .cell-list-top-cell-toolbar-container { top: -${SCROLLABLE_ELEMENT_PADDING_TOP}px }`);
|
|
|
|
collector.addRule(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container { height: ${BOTTOM_CELL_TOOLBAR_HEIGHT}px }`);
|
|
});
|