Implement cell collapse for outputs

#100308
This commit is contained in:
Rob Lourens 2020-07-24 18:54:08 -07:00
parent c53cc6546f
commit 313381b678
10 changed files with 117 additions and 39 deletions

View file

@ -1510,20 +1510,20 @@ registerAction2(class extends NotebookCellAction {
id: COLLAPSE_CELL_OUTPUT_COMMAND_ID,
title: localize('notebookActions.collapseCellOutput', "Collapse Cell Output"),
keybinding: {
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED.toNegated(), InputFocusedContext.toNegated()),
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED.toNegated(), InputFocusedContext.toNegated(), NOTEBOOK_CELL_HAS_OUTPUTS),
primary: KeyChord(KeyCode.KEY_C, KeyCode.KEY_O),
weight: KeybindingWeight.WorkbenchContrib
},
menu: {
id: MenuId.NotebookCellTitle,
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED.toNegated()),
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED.toNegated(), NOTEBOOK_CELL_HAS_OUTPUTS),
group: '3_collapse',
}
});
}
async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise<void> {
context.cell.collapseState = CellCollapseState.Collapsed;
context.cell.outputCollapseState = CellCollapseState.Collapsed;
}
});
@ -1534,18 +1534,18 @@ registerAction2(class extends NotebookCellAction {
title: localize('notebookActions.expandCellOutput', "Expand Cell Output"),
keybinding: {
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED),
primary: KeyChord(KeyCode.KEY_C, KeyCode.KEY_C),
primary: KeyChord(KeyCode.KEY_C, KeyCode.KEY_O),
weight: KeybindingWeight.WorkbenchContrib
},
// menu: {
// id: MenuId.NotebookCellTitle,
// when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED),
// group: '3_collapse',
// }
menu: {
id: MenuId.NotebookCellTitle,
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED),
group: '3_collapse',
}
});
}
async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise<void> {
context.cell.collapseState = CellCollapseState.Normal;
context.cell.outputCollapseState = CellCollapseState.Normal;
}
});

View file

@ -212,14 +212,14 @@
}
.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-collapsed-part {
position: relative;
cursor: pointer;
box-sizing: border-box;
padding-left: 5px;
}
.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-collapsed-part .codicon {
margin-top: 2px;
margin-top: 4px;
position: relative;
left: -23px;
cursor: pointer;
}
.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.collapsed .notebook-folding-indicator,

View file

@ -403,6 +403,8 @@ export interface INotebookEditor extends IEditor {
* @return The contribution or null if contribution not found.
*/
getContribution<T extends INotebookEditorContribution>(id: string): T;
hideInset(output: IProcessedOutput): void;
}
export interface INotebookCellList {
@ -460,12 +462,13 @@ export interface INotebookCellList {
export interface BaseCellRenderTemplate {
editorPart: HTMLElement;
collapsedPart: HTMLElement;
expandButton: HTMLElement;
contextKeyService: IContextKeyService;
container: HTMLElement;
cellContainer: HTMLElement;
toolbar: ToolBar;
betweenCellToolbar: ToolBar;
focusIndicator: HTMLElement;
focusIndicatorLeft: HTMLElement;
disposables: DisposableStore;
elementDisposables: DisposableStore;
bottomCellContainer: HTMLElement;

View file

@ -1706,6 +1706,7 @@ registerThemingParticipant((theme, collector) => {
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);
@ -1713,6 +1714,8 @@ registerThemingParticipant((theme, collector) => {
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);
@ -1801,9 +1804,9 @@ registerThemingParticipant((theme, collector) => {
collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row.code-cell-row .cell-focus-indicator-left { 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.collapsed .cell-focus-indicator.cell-focus-indicator-right { width: initial; left: ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px }`);
// collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row.collapsed .cell-focus-indicator.cell-focus-indicator-right { width: initial; left: ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}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}px; height: ${COLLAPSED_INDICATOR_HEIGHT}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; }`);
});

View file

@ -14,7 +14,7 @@ import * as UUID from 'vs/base/common/uuid';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener';
import { CELL_MARGIN, CELL_RUN_GUTTER, CODE_CELL_LEFT_MARGIN, CELL_OUTPUT_PADDING } from 'vs/workbench/contrib/notebook/browser/constants';
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellCollapseState, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
import { CellOutputKind, IProcessedOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
@ -561,6 +561,10 @@ ${loaderJs}
return;
}
if (cell.outputCollapseState === CellCollapseState.Collapsed) {
return false;
}
let outputCache = this.insetMapping.get(output)!;
let outputIndex = cell.outputs.indexOf(output);
let outputOffset = cellTop + cell.getOutputOffset(outputIndex);

View file

@ -271,13 +271,13 @@ abstract class AbstractCellRenderer {
if (actions.primary.length || actions.secondary.length) {
templateData.container.classList.add('cell-has-toolbar-actions');
if (isCodeCellRenderTemplate(templateData)) {
templateData.focusIndicator.style.top = `${EDITOR_TOOLBAR_HEIGHT + CELL_TOP_MARGIN}px`;
templateData.focusIndicatorLeft.style.top = `${EDITOR_TOOLBAR_HEIGHT + CELL_TOP_MARGIN}px`;
templateData.focusIndicatorRight.style.top = `${EDITOR_TOOLBAR_HEIGHT + CELL_TOP_MARGIN}px`;
}
} else {
templateData.container.classList.remove('cell-has-toolbar-actions');
if (isCodeCellRenderTemplate(templateData)) {
templateData.focusIndicator.style.top = `${CELL_TOP_MARGIN}px`;
templateData.focusIndicatorLeft.style.top = `${CELL_TOP_MARGIN}px`;
templateData.focusIndicatorRight.style.top = `${CELL_TOP_MARGIN}px`;
}
}
@ -300,7 +300,7 @@ abstract class AbstractCellRenderer {
}
}, true));
this.setupCollapsedPart(templateData);
this.addExpandListener(templateData);
}
protected commonRenderElement(element: ICellViewModel, index: number, templateData: BaseCellRenderTemplate): void {
@ -311,17 +311,28 @@ abstract class AbstractCellRenderer {
}
}
protected setupCollapsedPart(templateData: BaseCellRenderTemplate): void {
templateData.collapsedPart.innerHTML = renderCodicons('$(unfold)');
DOM.hide(templateData.collapsedPart);
templateData.disposables.add(domEvent(templateData.collapsedPart, DOM.EventType.CLICK)(() => {
protected addExpandListener(templateData: BaseCellRenderTemplate): void {
templateData.disposables.add(domEvent(templateData.expandButton, DOM.EventType.CLICK)(() => {
if (!templateData.currentRenderedCell) {
return;
}
templateData.currentRenderedCell.collapseState = CellCollapseState.Normal;
if (templateData.currentRenderedCell.collapseState === CellCollapseState.Collapsed) {
templateData.currentRenderedCell.collapseState = CellCollapseState.Normal;
} else if (templateData.currentRenderedCell.outputCollapseState === CellCollapseState.Collapsed) {
templateData.currentRenderedCell.outputCollapseState = CellCollapseState.Normal;
}
}));
}
protected setupCollapsedPart(container: HTMLElement): { collapsedPart: HTMLElement, expandButton: HTMLElement } {
const collapsedPart = DOM.append(container, $('.cell.cell-collapsed-part'));
collapsedPart.innerHTML = renderCodicons('$(unfold)');
const expandButton = collapsedPart.querySelector('.codicon') as HTMLElement;
DOM.hide(collapsedPart);
return { collapsedPart, expandButton };
}
}
export class MarkdownCellRenderer extends AbstractCellRenderer implements IListRenderer<MarkdownCellViewModel, MarkdownCellRenderTemplate> {
@ -361,7 +372,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR
const innerContent = DOM.append(container, $('.cell.markdown'));
const foldingIndicator = DOM.append(container, DOM.$('.notebook-folding-indicator'));
const collapsedPart = DOM.append(container, $('.cell.cell-collapsed-part'));
const { collapsedPart, expandButton } = this.setupCollapsedPart(container);
const bottomCellContainer = DOM.append(container, $('.cell-bottom-toolbar-container'));
const betweenCellToolbar = disposables.add(this.createBetweenCellToolbar(bottomCellContainer, disposables, contextKeyService));
@ -371,12 +382,13 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR
const templateData: MarkdownCellRenderTemplate = {
collapsedPart,
expandButton,
contextKeyService,
container,
cellContainer: innerContent,
editorPart,
editorContainer,
focusIndicator,
focusIndicatorLeft: focusIndicator,
foldingIndicator,
disposables,
elementDisposables: new DisposableStore(),
@ -390,6 +402,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR
};
this.dndController.registerDragHandle(templateData, () => this.getDragImage(templateData));
this.commonRenderTemplate(templateData);
return templateData;
}
@ -673,7 +686,7 @@ export class CellDragAndDropController extends Disposable {
registerDragHandle(templateData: BaseCellRenderTemplate, dragImageProvider: DragImageProvider): void {
const container = templateData.container;
const dragHandle = templateData.focusIndicator;
const dragHandle = templateData.focusIndicatorLeft;
templateData.disposables.add(domEvent(dragHandle, DOM.EventType.DRAG_END)(() => {
// Note, templateData may have a different element rendered into it by now
@ -943,7 +956,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende
disposables.add(this.editorOptions.onDidChange(newValue => editor.updateOptions(newValue)));
const collapsedPart = DOM.append(container, $('.cell.cell-collapsed-part'));
const { collapsedPart, expandButton } = this.setupCollapsedPart(container);
const progressBar = new ProgressBar(editorPart);
progressBar.hide();
@ -968,6 +981,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende
const templateData: CodeCellRenderTemplate = {
editorPart,
collapsedPart,
expandButton,
contextKeyService,
container,
cellContainer,
@ -976,7 +990,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende
cellStatusMessageContainer: statusBar.cellStatusMessageContainer,
languageStatusBarItem: statusBar.languageStatusBarItem,
progressBar,
focusIndicator,
focusIndicatorLeft: focusIndicator,
focusIndicatorRight,
focusIndicatorBottom,
toolbar,
@ -1087,7 +1101,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende
}
private updateForLayout(element: CodeCellViewModel, templateData: CodeCellRenderTemplate): void {
templateData.focusIndicator.style.height = `${element.layoutInfo.indicatorHeight}px`;
templateData.focusIndicatorLeft.style.height = `${element.layoutInfo.indicatorHeight}px`;
templateData.focusIndicatorRight.style.height = `${element.layoutInfo.indicatorHeight}px`;
templateData.focusIndicatorBottom.style.top = `${element.layoutInfo.totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT - CELL_BOTTOM_MARGIN}px`;
templateData.outputContainer.style.top = `${element.layoutInfo.outputContainerOffset}px`;

View file

@ -113,7 +113,6 @@ export class CodeCell extends Disposable {
}
if (e.collapseStateChanged) {
// meh
this.viewCell.layoutChange({});
this.relayoutCell();
}
@ -308,25 +307,75 @@ export class CodeCell extends Disposable {
}
private viewUpdate(): void {
if (this.viewCell.collapseState === CellCollapseState.Collapsed) {
this.viewUpdateCollapsed();
if (this.viewCell.collapseState === CellCollapseState.Collapsed && this.viewCell.outputCollapseState === CellCollapseState.Collapsed) {
this.viewUpdateAllCollapsed();
} else if (this.viewCell.collapseState === CellCollapseState.Collapsed) {
this.viewUpdateInputCollapsed();
} else if (this.viewCell.outputCollapseState === CellCollapseState.Collapsed && this.viewCell.outputs.length) {
this.viewUpdateOutputCollapsed();
} else {
this.viewUpdateExpanded();
}
}
private viewUpdateCollapsed(): void {
private viewUpdateShowOutputs(): void {
for (let index = 0; index < this.viewCell.outputs.length; index++) {
const currOutput = this.viewCell.outputs[index];
if (currOutput.outputKind === CellOutputKind.Rich) {
this.renderOutput(currOutput, index, undefined);
}
}
}
private viewUpdateInputCollapsed(): void {
DOM.hide(this.templateData.cellContainer);
DOM.show(this.templateData.collapsedPart);
DOM.show(this.templateData.outputContainer);
this.templateData.container.classList.toggle('collapsed', true);
this.viewUpdateShowOutputs();
this.relayoutCell();
}
private viewUpdateOutputCollapsed(): void {
DOM.show(this.templateData.cellContainer);
DOM.show(this.templateData.collapsedPart);
DOM.hide(this.templateData.outputContainer);
for (let e of this.outputElements.keys()) {
this.notebookEditor.hideInset(e);
}
this.templateData.container.classList.toggle('collapsed', false);
this.templateData.container.classList.toggle('output-collapsed', true);
this.relayoutCell();
}
private viewUpdateAllCollapsed(): void {
DOM.hide(this.templateData.cellContainer);
DOM.show(this.templateData.collapsedPart);
DOM.hide(this.templateData.outputContainer);
this.templateData.container.classList.toggle('collapsed', true);
this.templateData.container.classList.toggle('output-collapsed', true);
for (let e of this.outputElements.keys()) {
this.notebookEditor.hideInset(e);
}
this.relayoutCell();
}
private viewUpdateExpanded(): void {
DOM.show(this.templateData.cellContainer);
DOM.hide(this.templateData.collapsedPart);
DOM.show(this.templateData.outputContainer);
this.templateData.container.classList.toggle('collapsed', false);
this.templateData.container.classList.toggle('output-collapsed', false);
this.viewUpdateShowOutputs();
this.relayoutCell();
}
@ -587,7 +636,7 @@ export class CodeCell extends Disposable {
value.dispose();
});
this.templateData.focusIndicator!.style.height = 'initial';
this.templateData.focusIndicatorLeft!.style.height = 'initial';
super.dispose();
}

View file

@ -76,7 +76,7 @@ export abstract class BaseCellViewModel extends Disposable {
}
public set outputCollapseState(v: CellCollapseState) {
this._collapseState = v;
this._outputCollapseState = v;
this._onDidChangeState.fire({ collapseStateChanged: true });
}

View file

@ -99,7 +99,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod
layoutChange(state: CodeCellLayoutChangeEvent) {
// recompute
this._ensureOutputsTop();
const outputTotalHeight = this._outputsTop!.getTotalValue();
let outputTotalHeight = this.outputCollapseState === CellCollapseState.Normal ? this._outputsTop!.getTotalValue() : COLLAPSED_INDICATOR_HEIGHT;
if (this.collapseState === CellCollapseState.Normal) {
let newState: CodeCellLayoutState;
@ -138,6 +138,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod
layoutState: newState
};
} else {
outputTotalHeight = this.collapseState === CellCollapseState.Collapsed && this.outputCollapseState === CellCollapseState.Collapsed ? 0 : outputTotalHeight;
const indicatorHeight = COLLAPSED_INDICATOR_HEIGHT + outputTotalHeight;
const outputContainerOffset = CELL_TOP_MARGIN + COLLAPSED_INDICATOR_HEIGHT;
const totalHeight = CELL_TOP_MARGIN + COLLAPSED_INDICATOR_HEIGHT + CELL_BOTTOM_MARGIN + BOTTOM_CELL_TOOLBAR_HEIGHT + outputTotalHeight;

View file

@ -62,6 +62,10 @@ export class TestNotebookEditor implements INotebookEditor {
constructor(
) { }
hideInset(output: IProcessedOutput): void {
throw new Error('Method not implemented.');
}
multipleKernelsAvailable: boolean = false;
onDidChangeAvailableKernels: Event<void> = new Emitter<void>().event;