Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix notebook output scrolling and text rendering #14016

Merged
merged 2 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/core/src/browser/style/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,8 @@ blockquote {
-o-user-select: none;
}

:focus {
/* Since an iframe has its own focus tracking, we don't show focus on iframes */
:focus:not(iframe) {
outline-width: 1px;
outline-style: solid;
outline-offset: -1px;
Expand Down
3 changes: 2 additions & 1 deletion packages/notebook/src/browser/service/notebook-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const notebookOutputOptionsRelevantPreferences = [

export interface NotebookOutputOptions {
// readonly outputNodePadding: number;
// readonly outputNodeLeftPadding: number;
readonly outputNodeLeftPadding: number;
// readonly previewNodePadding: number;
// readonly markdownLeftMargin: number;
// readonly leftMargin: number;
Expand Down Expand Up @@ -95,6 +95,7 @@ export class NotebookOptionsService {
fontSize,
outputFontSize: outputFontSize,
fontFamily: this.preferenceService.get<string>('editor.fontFamily')!,
outputNodeLeftPadding: 8,
outputFontFamily: this.getNotebookPreferenceWithDefault<string>(NotebookPreferences.OUTPUT_FONT_FAMILY),
outputLineHeight: this.computeOutputLineHeight(outputLineHeight, outputFontSize ?? fontSize),
outputScrolling: this.getNotebookPreferenceWithDefault<boolean>(NotebookPreferences.OUTPUT_SCROLLING)!,
Expand Down
20 changes: 10 additions & 10 deletions packages/notebook/src/browser/style/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
width: calc(100% - 46px);
flex: 1;
outline: 1px solid var(--theia-notebook-cellBorderColor);
margin: 0px 10px;
margin: 0px 16px 0px 10px;
}

.theia-notebook-cell.focused .theia-notebook-cell-editor-container {
Expand Down Expand Up @@ -288,7 +288,7 @@

.theia-notebook-cell-output-webview {
padding: 5px 0px;
margin: 0px 10px;
margin: 0px 15px 0px 9px;
width: 100%;
}

Expand Down Expand Up @@ -350,7 +350,7 @@
transform: translateY(calc(-100% - 10px));
}

.theia-notebook-find-widget.search-mode > * > *:nth-child(2) {
.theia-notebook-find-widget.search-mode>*>*:nth-child(2) {
display: none;
}

Expand Down Expand Up @@ -379,9 +379,9 @@
align-items: center;
}

.theia-notebook-find-widget-buttons-first > div,
.theia-notebook-find-widget-buttons-second > div {
margin-right: 4px;
.theia-notebook-find-widget-buttons-first>div,
.theia-notebook-find-widget-buttons-second>div {
margin-right: 4px;
}

.theia-notebook-find-widget-buttons-second {
Expand Down Expand Up @@ -457,11 +457,11 @@
}

mark.theia-find-match {
color: var(--theia-editor-findMatchHighlightForeground);
background-color: var(--theia-editor-findMatchHighlightBackground);
color: var(--theia-editor-findMatchHighlightForeground);
background-color: var(--theia-editor-findMatchHighlightBackground);
}

mark.theia-find-match.theia-find-match-selected {
color: var(--theia-editor-findMatchForeground);
background-color: var(--theia-editor-findMatchBackground);
color: var(--theia-editor-findMatchForeground);
background-color: var(--theia-editor-findMatchBackground);
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,83 @@ export function createCellOutputWebviewContainer(ctx: interfaces.Container, cell
return child;
}

// Should be kept up-to-date with:
// https://github.com/microsoft/vscode/blob/main/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewThemeMapping.ts
const mapping: ReadonlyMap<string, string> = new Map([
['theme-font-family', 'vscode-font-family'],
['theme-font-weight', 'vscode-font-weight'],
['theme-font-size', 'vscode-font-size'],
['theme-code-font-family', 'vscode-editor-font-family'],
['theme-code-font-weight', 'vscode-editor-font-weight'],
['theme-code-font-size', 'vscode-editor-font-size'],
['theme-scrollbar-background', 'vscode-scrollbarSlider-background'],
['theme-scrollbar-hover-background', 'vscode-scrollbarSlider-hoverBackground'],
['theme-scrollbar-active-background', 'vscode-scrollbarSlider-activeBackground'],
['theme-quote-background', 'vscode-textBlockQuote-background'],
['theme-quote-border', 'vscode-textBlockQuote-border'],
['theme-code-foreground', 'vscode-textPreformat-foreground'],
// Editor
['theme-background', 'vscode-editor-background'],
['theme-foreground', 'vscode-editor-foreground'],
['theme-ui-foreground', 'vscode-foreground'],
['theme-link', 'vscode-textLink-foreground'],
['theme-link-active', 'vscode-textLink-activeForeground'],
// Buttons
['theme-button-background', 'vscode-button-background'],
['theme-button-hover-background', 'vscode-button-hoverBackground'],
['theme-button-foreground', 'vscode-button-foreground'],
['theme-button-secondary-background', 'vscode-button-secondaryBackground'],
['theme-button-secondary-hover-background', 'vscode-button-secondaryHoverBackground'],
['theme-button-secondary-foreground', 'vscode-button-secondaryForeground'],
['theme-button-hover-foreground', 'vscode-button-foreground'],
['theme-button-focus-foreground', 'vscode-button-foreground'],
['theme-button-secondary-hover-foreground', 'vscode-button-secondaryForeground'],
['theme-button-secondary-focus-foreground', 'vscode-button-secondaryForeground'],
// Inputs
['theme-input-background', 'vscode-input-background'],
['theme-input-foreground', 'vscode-input-foreground'],
['theme-input-placeholder-foreground', 'vscode-input-placeholderForeground'],
['theme-input-focus-border-color', 'vscode-focusBorder'],
// Menus
['theme-menu-background', 'vscode-menu-background'],
['theme-menu-foreground', 'vscode-menu-foreground'],
['theme-menu-hover-background', 'vscode-menu-selectionBackground'],
['theme-menu-focus-background', 'vscode-menu-selectionBackground'],
['theme-menu-hover-foreground', 'vscode-menu-selectionForeground'],
['theme-menu-focus-foreground', 'vscode-menu-selectionForeground'],
// Errors
['theme-error-background', 'vscode-inputValidation-errorBackground'],
['theme-error-foreground', 'vscode-foreground'],
['theme-warning-background', 'vscode-inputValidation-warningBackground'],
['theme-warning-foreground', 'vscode-foreground'],
['theme-info-background', 'vscode-inputValidation-infoBackground'],
['theme-info-foreground', 'vscode-foreground'],
// Notebook:
['theme-notebook-output-background', 'vscode-notebook-outputContainerBackgroundColor'],
['theme-notebook-output-border', 'vscode-notebook-outputContainerBorderColor'],
['theme-notebook-cell-selected-background', 'vscode-notebook-selectedCellBackground'],
['theme-notebook-symbol-highlight-background', 'vscode-notebook-symbolHighlightBackground'],
['theme-notebook-diff-removed-background', 'vscode-diffEditor-removedTextBackground'],
['theme-notebook-diff-inserted-background', 'vscode-diffEditor-insertedTextBackground'],
]);

const constants: Record<string, string> = {
'theme-input-border-width': '1px',
'theme-button-primary-hover-shadow': 'none',
'theme-button-secondary-hover-shadow': 'none',
'theme-input-border-color': 'transparent',
};

export const DEFAULT_NOTEBOOK_OUTPUT_CSS = `
:root {
${Array.from(mapping.entries()).map(([key, value]) => `--${key}: var(--${value});`).join('\n')}
${Object.entries(constants).map(([key, value]) => `--${key}: ${value};`).join('\n')}
}

body {
padding: 0;
}

table {
border-collapse: collapse;
border-spacing: 0;
Expand Down Expand Up @@ -323,6 +399,7 @@ export class CellOutputWebviewImpl implements CellOutputWebview, Disposable {

protected generateStyles(): { [key: string]: string } {
return {
'notebook-output-node-left-padding': `${this.options.outputNodeLeftPadding}px`,
'notebook-cell-output-font-size': `${this.options.outputFontSize || this.options.fontSize}px`,
'notebook-cell-output-line-height': `${this.options.outputLineHeight}px`,
'notebook-cell-output-max-height': `${this.options.outputLineHeight * this.options.outputLineLimit}px`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,10 @@ export async function outputWebviewPreload(ctx: PreloadContext): Promise<void> {
renderer: Renderer;

element: HTMLElement;
container: HTMLElement;

constructor(output: webviewCommunication.Output, items: rendererApi.OutputItem[]) {
this.element = document.createElement('div');
// padding for scrollbars
this.element.style.paddingBottom = '10px';
this.element.style.paddingRight = '10px';
this.element.id = output.id;
document.body.appendChild(this.element);
this.createHtmlElement(output.id);
this.outputId = output.id;
this.allItems = items;
}
Expand All @@ -172,6 +168,25 @@ export async function outputWebviewPreload(ctx: PreloadContext): Promise<void> {
this.renderer?.disposeOutputItem?.(this.renderedItem?.id);
this.element.innerHTML = '';
}

private createHtmlElement(id: string): void {
// Recreates the output container structure used in VS Code
this.container = document.createElement('div');
this.container.id = 'container';
this.container.classList.add('widgetarea');
const cellContainer = document.createElement('div');
cellContainer.classList.add('cell_container');
cellContainer.id = id;
this.container.appendChild(cellContainer);
const outputContainer = document.createElement('div');
outputContainer.classList.add('output-container');
cellContainer.appendChild(outputContainer);
this.element = document.createElement('div');
this.element.id = id;
this.element.classList.add('output');
outputContainer.appendChild(this.element);
document.body.appendChild(this.container);
}
}

const outputs: Output[] = [];
Expand Down Expand Up @@ -469,7 +484,7 @@ export async function outputWebviewPreload(ctx: PreloadContext): Promise<void> {

function clearOutput(output: Output): void {
output.clear();
output.element.remove();
output.container.remove();
}

function outputsChanged(changedEvent: webviewCommunication.OutputChangedMessage): void {
Expand Down Expand Up @@ -504,40 +519,48 @@ export async function outputWebviewPreload(ctx: PreloadContext): Promise<void> {
}
}

function scrollParent(event: WheelEvent): boolean {
function shouldHandleScroll(event: WheelEvent): boolean {
for (let node = event.target as Node | null; node; node = node.parentNode) {
if (!(node instanceof Element)) {
continue;
return false;
}

// scroll up
if (event.deltaY < 0 && node.scrollTop > 0) {
// there is still some content to scroll
return false;
return true;
}

// scroll down
if (event.deltaY > 0 && node.scrollTop + node.clientHeight < node.scrollHeight) {
// per https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight
// scrollTop is not rounded but scrollHeight and clientHeight are
// so we need to check if the difference is less than some threshold
if (node.scrollHeight - node.scrollTop - node.clientHeight > 2) {
return false;
if (node.scrollHeight - node.scrollTop - node.clientHeight < 2) {
continue;
}

// if the node is not scrollable, we can continue. We don't check the computed style always as it's expensive
if (window.getComputedStyle(node).overflowY === 'hidden' || window.getComputedStyle(node).overflowY === 'visible') {
continue;
}

return true;
}
}

return true;
return false;
}

const handleWheel = (event: WheelEvent & { wheelDeltaX?: number; wheelDeltaY?: number; wheelDelta?: number }) => {
if (scrollParent(event)) {
theia.postMessage({
type: 'did-scroll-wheel',
deltaY: event.deltaY,
deltaX: event.deltaX,
});
if (event.defaultPrevented || shouldHandleScroll(event)) {
return;
}
theia.postMessage({
type: 'did-scroll-wheel',
deltaY: event.deltaY,
deltaX: event.deltaX,
});
};

window.addEventListener('message', async rawEvent => {
Expand Down
Loading