diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index 385f6d865d8..3366c8ca041 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -145,7 +145,7 @@ function renderError( if (err.stack) { outputElement.classList.add('traceback'); - const outputScrolling = ctx.settings.outputScrolling; + const outputScrolling = scrollingEnabled(outputInfo, ctx.settings); const content = createOutputContent(outputInfo.id, [err.stack ?? ''], ctx.settings.lineLimit, outputScrolling, true); const contentParent = document.createElement('div'); contentParent.classList.toggle('word-wrap', ctx.settings.outputWordWrap); @@ -153,10 +153,6 @@ function renderError( contentParent.classList.toggle('word-wrap', e.outputWordWrap); })); contentParent.classList.toggle('scrollable', outputScrolling); - outputElement.classList.toggle('hide-refresh', !outputScrolling); - disposableStore.push(ctx.onDidChangeSettings(e => { - outputElement.classList.toggle('hide-refresh', !e.outputScrolling); - })); outputElement.classList.toggle('remove-padding', outputScrolling); contentParent.appendChild(content); @@ -221,9 +217,13 @@ function findScrolledHeight(scrollableElement: HTMLElement): number | undefined return undefined; } +function scrollingEnabled(output: OutputItem, options: RenderOptions) { + return output.metadata?.scrollable !== undefined ? output.metadata?.scrollable : options.outputScrolling; +} + function renderStream(outputInfo: OutputItem, outputElement: HTMLElement, error: boolean, ctx: IRichRenderContext): IDisposable { const disposableStore = createDisposableStore(); - const outputScrolling = ctx.settings.outputScrolling; + const outputScrolling = scrollingEnabled(outputInfo, ctx.settings); outputElement.classList.add('output-stream'); outputElement.classList.toggle('remove-padding', outputScrolling); @@ -252,11 +252,6 @@ function renderStream(outputInfo: OutputItem, outputElement: HTMLElement, error: const contentParent = document.createElement('div'); contentParent.appendChild(content); contentParent.classList.toggle('scrollable', outputScrolling); - outputElement.classList.toggle('hide-refresh', !outputScrolling); - disposableStore.push(ctx.onDidChangeSettings(e => { - outputElement.classList.toggle('hide-refresh', !e.outputScrolling); - })); - contentParent.classList.toggle('word-wrap', ctx.settings.outputWordWrap); disposableStore.push(ctx.onDidChangeSettings(e => { contentParent.classList.toggle('word-wrap', e.outputWordWrap); @@ -278,18 +273,14 @@ function renderText(outputInfo: OutputItem, outputElement: HTMLElement, ctx: IRi clearContainer(outputElement); const text = outputInfo.text(); + const outputScrolling = scrollingEnabled(outputInfo, ctx.settings); const content = createOutputContent(outputInfo.id, [text], ctx.settings.lineLimit, ctx.settings.outputScrolling, false); content.classList.add('output-plaintext'); if (ctx.settings.outputWordWrap) { content.classList.add('word-wrap'); } - const outputScrolling = ctx.settings.outputScrolling; content.classList.toggle('scrollable', outputScrolling); - outputElement.classList.toggle('hide-refresh', !outputScrolling); - disposableStore.push(ctx.onDidChangeSettings(e => { - outputElement.classList.toggle('hide-refresh', !e.outputScrolling); - })); outputElement.classList.toggle('remove-padding', outputScrolling); outputElement.appendChild(content); initializeScroll(content, disposableStore); @@ -352,9 +343,6 @@ export const activate: ActivationFunction = (ctx) => { #container div.output .scrollable.scrollbar-visible { border-color: var(--vscode-editorWidget-border); } - #container div.output.hide-refresh .scroll-refresh { - display: none; - } .output-plaintext .code-bold, .output-stream .code-bold, .traceback .code-bold { diff --git a/extensions/notebook-renderers/src/textHelper.ts b/extensions/notebook-renderers/src/textHelper.ts index d4ae6542990..99edbf8465c 100644 --- a/extensions/notebook-renderers/src/textHelper.ts +++ b/extensions/notebook-renderers/src/textHelper.ts @@ -30,7 +30,7 @@ function generateViewMoreElement(outputId: string) { const refreshSpan = document.createElement('span'); refreshSpan.classList.add('scroll-refresh'); const fifth = document.createElement('span'); - fifth.textContent = '. Refresh to view '; + fifth.textContent = ', or view in a '; const sixth = document.createElement('a'); sixth.textContent = 'scrollable element'; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts index 9cc977b5c51..f0e25966d45 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts @@ -15,12 +15,13 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits'; import { changeCellToKind, computeCellLinesContents, copyCellRange, joinCellsWithSurrounds, joinSelectedCells, moveCellRange } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; import { cellExecutionArgs, CellOverflowToolbarGroups, CellToolbarOrder, CELL_TITLE_CELL_GROUP_ID, INotebookCellActionContext, INotebookCellToolbarActionContext, INotebookCommandContext, NotebookCellAction, NotebookMultiCellAction, parseMultiCellExecutionArgs } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -import { CellFocusMode, EXPAND_CELL_INPUT_COMMAND_ID, EXPAND_CELL_OUTPUT_COMMAND_ID, ICellViewModel, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellFocusMode, EXPAND_CELL_INPUT_COMMAND_ID, EXPAND_CELL_OUTPUT_COMMAND_ID, ICellOutputViewModel, ICellViewModel, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { CellEditType, CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; //#region Move/Copy cells const MOVE_CELL_UP_COMMAND_ID = 'notebook.cell.moveUp'; @@ -381,6 +382,7 @@ const EXPAND_ALL_CELL_INPUTS_COMMAND_ID = 'notebook.cell.expandAllCellInputs'; const COLLAPSE_ALL_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.collapseAllCellOutputs'; const EXPAND_ALL_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.expandAllCellOutputs'; const TOGGLE_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.toggleOutputs'; +const TOGGLE_CELL_OUTPUT_SCROLLING = 'notebook.cell.toggleOutputScrolling'; registerAction2(class CollapseCellInputAction extends NotebookMultiCellAction { constructor() { @@ -592,6 +594,52 @@ registerAction2(class ExpandAllCellOutputsAction extends NotebookMultiCellAction } }); +registerAction2(class ToggleCellOutputScrolling extends NotebookMultiCellAction { + constructor() { + super({ + id: TOGGLE_CELL_OUTPUT_SCROLLING, + title: { + value: localize('notebookActions.toggleScrolling', "Toggle Scroll Cell Output"), + original: 'Toggle Scroll Cell Output' + }, + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, InputFocusedContext.toNegated(), NOTEBOOK_CELL_HAS_OUTPUTS), + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyY), + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + private toggleOutputScrolling(viewModel: ICellOutputViewModel, globalScrollSetting: boolean, collapsed: boolean) { + const cellMetadata = viewModel.model.metadata; + // TODO: when is cellMetadata undefined? Is that a case we need to support? It is currently a read-only property. + if (cellMetadata) { + const currentlyEnabled = cellMetadata['scrollable'] !== undefined ? cellMetadata['scrollable'] : globalScrollSetting; + const shouldEnableScrolling = collapsed || !currentlyEnabled; + console.log(`shouldEnableScrolling: ${shouldEnableScrolling}, currentlyEnabled: ${currentlyEnabled}, globalSetting: ${globalScrollSetting}, metadata: ${JSON.stringify(cellMetadata)}`); + cellMetadata['scrollable'] = shouldEnableScrolling; + viewModel.resetRenderer(); + } + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCommandContext | INotebookCellToolbarActionContext): Promise { + const globalScrolling = accessor.get(IConfigurationService).getValue(NotebookSetting.outputScrolling); + if (context.ui) { + context.cell.outputsViewModels.forEach((viewModel) => { + this.toggleOutputScrolling(viewModel, globalScrolling, context.cell.isOutputCollapsed); + }); + context.cell.isOutputCollapsed = false; + } else { + context.selectedCells.forEach(cell => { + cell.outputsViewModels.forEach((viewModel) => { + this.toggleOutputScrolling(viewModel, globalScrolling, cell.isOutputCollapsed); + }); + cell.isOutputCollapsed = false; + }); + } + } +}); + //#endregion function forEachCell(editor: INotebookEditor, callback: (cell: ICellViewModel, index: number) => void) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 940fb0ebb10..d6ae250f330 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -53,6 +53,8 @@ import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { INotebookLoggingService } from 'vs/workbench/contrib/notebook/common/notebookLoggingService'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; const LINE_COLUMN_REGEX = /:([\d]+)(?::([\d]+))?$/; const LineQueryRegex = /line=(\d+)$/; @@ -170,6 +172,7 @@ export class BackLayerWebView extends Themable { @IPathService private readonly pathService: IPathService, @INotebookLoggingService private readonly notebookLogService: INotebookLoggingService, @IThemeService themeService: IThemeService, + @ITelemetryService private readonly telemetryService: ITelemetryService ) { super(themeService); @@ -709,10 +712,17 @@ export class BackLayerWebView extends Themable { const cell = this.reversedInsetMapping.get(outputId); if (cell) { - cell.resetRenderer(); + this.telemetryService.publicLog2 + ('workbenchActionExecuted', { id: 'notebook.cell.toggleOutputScrolling', from: 'inlineLink' }); + + cell.cellViewModel.outputsViewModels.forEach((vm) => { + if (vm.model.metadata) { + vm.model.metadata['scrollable'] = true; + vm.resetRenderer(); + } + }); } - console.log(outputId); return; }