diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 5287bc531106e..9a3d06cd9663c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -483,6 +483,7 @@ export interface INotebookEditor { readonly onDidFocusWidget: Event; readonly onDidBlurWidget: Event; readonly onDidScroll: Event; + readonly onDidChangeLayout: Event; readonly onDidChangeActiveCell: Event; readonly onDidChangeActiveKernel: Event; readonly onMouseUp: Event; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 27c63d82ddad9..3d63f27aae66b 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -101,6 +101,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { PixelRatio } from 'vs/base/browser/pixelRatio'; import { PreventDefaultContextMenuItemsContextKeyName } from 'vs/workbench/contrib/webview/browser/webview.contribution'; import { NotebookAccessibilityProvider } from 'vs/workbench/contrib/notebook/browser/notebookAccessibilityProvider'; +import { NotebookHorizontalTracker } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker'; const $ = DOM.$; @@ -150,6 +151,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD readonly onDidChangeDecorations: Event = this._onDidChangeDecorations.event; private readonly _onDidScroll = this._register(new Emitter()); readonly onDidScroll: Event = this._onDidScroll.event; + private readonly _onDidChangeLayout = this._register(new Emitter()); + readonly onDidChangeLayout: Event = this._onDidChangeLayout.event; private readonly _onDidChangeActiveCell = this._register(new Emitter()); readonly onDidChangeActiveCell: Event = this._onDidChangeActiveCell.event; private readonly _onDidChangeFocus = this._register(new Emitter()); @@ -323,6 +326,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._notebookOptions, eventDispatcher, language => this.getBaseCellEditorOptions(language)); + this._register(this._viewContext.eventDispatcher.onDidChangeLayout(() => { + this._onDidChangeLayout.fire(); + })); this._register(this._viewContext.eventDispatcher.onDidChangeCellState(e => { this._onDidChangeCellState.fire(e); })); @@ -610,6 +616,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._list.scrollableElement.appendChild(this._notebookOverviewRulerContainer); this._registerNotebookOverviewRuler(); + this._register(this.instantiationService.createInstance(NotebookHorizontalTracker, this, this._list.scrollableElement)); + this._overflowContainer = document.createElement('div'); this._overflowContainer.classList.add('notebook-overflow-widget-container', 'monaco-editor'); DOM.append(parent, this._overflowContainer); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts index 4370f0365584f..b739911bf788c 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts @@ -10,6 +10,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { Event } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { clamp } from 'vs/base/common/numbers'; import * as strings from 'vs/base/common/strings'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IDimension } from 'vs/editor/common/core/dimension'; @@ -66,6 +67,7 @@ export class CodeCell extends Disposable { this.initializeEditor(editorHeight); this._renderedInputCollapseState = false; // editor is always expanded initially + this.registerNotebookEditorListeners(); this.registerViewCellLayoutChange(); this.registerCellEditorEventListeners(); this.registerDecorations(); @@ -264,12 +266,52 @@ export class CodeCell extends Disposable { } } + private registerNotebookEditorListeners() { + this._register(this.notebookEditor.onDidScroll(() => { + this.adjustEditorPosition(); + })); + + this._register(this.notebookEditor.onDidChangeLayout(() => { + this.adjustEditorPosition(); + this.onCellWidthChange(); + })); + } + + private adjustEditorPosition() { + const extraOffset = - 6 /** distance to the top of the cell editor, which is 6px under the focus indicator */ - 1 /** border */; + const min = 0; + + const scrollTop = this.notebookEditor.scrollTop; + const elementTop = this.notebookEditor.getAbsoluteTopOfElement(this.viewCell); + const diff = scrollTop - elementTop + extraOffset; + + const notebookEditorLayout = this.notebookEditor.getLayoutInfo(); + + // we should stop adjusting the top when users are viewing the bottom of the cell editor + const editorMaxHeight = notebookEditorLayout.height + - notebookEditorLayout.stickyHeight + - 26 /** notebook toolbar */; + + const maxTop = + this.viewCell.layoutInfo.editorHeight + // + this.viewCell.layoutInfo.statusBarHeight + - editorMaxHeight + ; + const top = maxTop > 20 ? + clamp(min, diff, maxTop) : + min; + this.templateData.editorPart.style.top = `${top}px`; + // scroll the editor with top + this.templateData.editor?.setScrollTop(top); + } + private registerViewCellLayoutChange() { this._register(this.viewCell.onDidChangeLayout((e) => { if (e.outerWidth !== undefined) { const layoutInfo = this.templateData.editor.getLayoutInfo(); if (layoutInfo.width !== this.viewCell.layoutInfo.editorWidth) { this.onCellWidthChange(); + this.adjustEditorPosition(); } } })); @@ -532,7 +574,17 @@ export class CodeCell extends Disposable { } private layoutEditor(dimension: IDimension): void { - this.templateData.editor?.layout(dimension, true); + const editorLayout = this.notebookEditor.getLayoutInfo(); + const maxHeight = Math.min( + editorLayout.height + - editorLayout.stickyHeight + - 26 /** notebook toolbar */, + dimension.height + ); + this.templateData.editor?.layout({ + width: dimension.width, + height: maxHeight + }, true); } private onCellWidthChange(): void { diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index a7cd34ca45375..cd401a3b4c8de 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -1152,9 +1152,6 @@ export class NotebookCellList extends WorkbenchList implements ID // Scrolled into view from above this.view.setScrollTop(positionTop - 30); } - - - element.revealRangeInCenter(range); } //#endregion diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 564ddd0641403..52fdd57b68041 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -288,6 +288,12 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende width: 0, height: 0 }, + scrollbar: { + vertical: 'hidden', + horizontal: 'auto', + handleMouseWheel: false, + useShadows: false, + }, }, { contributions: this.notebookEditor.creationOptions.cellEditorContributions }); diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker.ts new file mode 100644 index 0000000000000..22b678d781392 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { addDisposableListener, EventType, getWindow } from 'vs/base/browser/dom'; +import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { isChrome } from 'vs/base/common/platform'; +import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; +import { INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; + +export class NotebookHorizontalTracker extends Disposable { + constructor( + private readonly _notebookEditor: INotebookEditorDelegate, + private readonly _listViewScrollablement: HTMLElement, + ) { + super(); + + this._register(addDisposableListener(this._listViewScrollablement, EventType.MOUSE_WHEEL, (event: IMouseWheelEvent) => { + if (event.deltaX === 0) { + return; + } + + const hoveringOnEditor = this._notebookEditor.codeEditors.find(editor => { + const editorLayout = editor[1].getLayoutInfo(); + if (editorLayout.contentWidth === editorLayout.width) { + // no overflow + return false; + } + + const editorDOM = editor[1].getDomNode(); + if (editorDOM && editorDOM.contains(event.target as HTMLElement)) { + return true; + } + + return false; + }); + + if (!hoveringOnEditor) { + return; + } + + const targetWindow = getWindow(event); + const evt = { + deltaMode: event.deltaMode, + deltaX: event.deltaX, + deltaY: 0, + deltaZ: 0, + wheelDelta: event.wheelDelta && isChrome ? (event.wheelDelta / targetWindow.devicePixelRatio) : event.wheelDelta, + wheelDeltaX: event.wheelDeltaX && isChrome ? (event.wheelDeltaX / targetWindow.devicePixelRatio) : event.wheelDeltaX, + wheelDeltaY: 0, + detail: event.detail, + shiftKey: event.shiftKey, + type: event.type, + defaultPrevented: false, + preventDefault: () => { }, + stopPropagation: () => { } + }; + + (hoveringOnEditor[1] as CodeEditorWidget).delegateScrollFromMouseWheelEvent(evt as any); + })); + } +}