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

Disable clear all outputs in notebook main toolbar #13569

Merged
merged 4 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
18 changes: 18 additions & 0 deletions packages/core/src/common/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,3 +467,21 @@ export class AsyncEmitter<T extends WaitUntilEvent> extends Emitter<T> {
}

}

export class QueueableEmitter<T> extends Emitter<T[]> {

currentQueue?: T[];

queue(...arg: T[]): void {
if (!this.currentQueue) {
this.currentQueue = [];
}
this.currentQueue.push(...arg);
}

override fire(): void {
super.fire(this.currentQueue || []);
this.currentQueue = undefined;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { NotebookKernelQuickPickService } from '../service/notebook-kernel-quick
import { NotebookExecutionService } from '../service/notebook-execution-service';
import { NotebookEditorWidget } from '../notebook-editor-widget';
import { NotebookEditorWidgetService } from '../service/notebook-editor-widget-service';
import { NOTEBOOK_CELL_FOCUSED, NOTEBOOK_EDITOR_FOCUSED } from './notebook-context-keys';
import { NOTEBOOK_CELL_FOCUSED, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_HAS_OUTPUTS } from './notebook-context-keys';

export namespace NotebookCommands {
export const ADD_NEW_CELL_COMMAND = Command.toDefaultLocalizedCommand({
Expand Down Expand Up @@ -141,7 +141,10 @@ export class NotebookActionsContribution implements CommandContribution, MenuCon
));

commands.registerCommand(NotebookCommands.CLEAR_ALL_OUTPUTS_COMMAND, this.editableCommandHandler(
notebookModel => notebookModel.cells.forEach(cell => cell.spliceNotebookCellOutputs({ start: 0, deleteCount: cell.outputs.length, newOutputs: [] }))
notebookModel => notebookModel.applyEdits(notebookModel.cells.map(cell => ({
editType: CellEditType.Output,
handle: cell.handle, deleteCount: cell.outputs.length, outputs: []
})), false)
));

commands.registerCommand(NotebookCommands.CHANGE_SELECTED_CELL,
Expand Down Expand Up @@ -217,7 +220,8 @@ export class NotebookActionsContribution implements CommandContribution, MenuCon
commandId: NotebookCommands.CLEAR_ALL_OUTPUTS_COMMAND.id,
label: nls.localizeByDefault('Clear All Outputs'),
icon: codicon('clear-all'),
order: '30'
order: '30',
when: NOTEBOOK_HAS_OUTPUTS
});
// other items
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,13 @@ export class NotebookCellActionContribution implements MenuContribution, Command
}
});
commands.registerCommand(NotebookCellCommands.CLEAR_OUTPUTS_COMMAND, this.editableCellCommandHandler(
(_, cell) => cell.spliceNotebookCellOutputs({ start: 0, deleteCount: (cell ?? this.getSelectedCell()).outputs.length, newOutputs: [] })
(notebook, cell) => notebook.applyEdits([{
editType: CellEditType.Output,
handle: cell.handle,
outputs: [],
deleteCount: cell.outputs.length,
append: false
}], true)
));
commands.registerCommand(NotebookCellCommands.CHANGE_OUTPUT_PRESENTATION_COMMAND, this.editableCellCommandHandler(
(_, __, output) => output?.requestOutputPresentationUpdate()
Expand Down
2 changes: 2 additions & 0 deletions packages/notebook/src/browser/notebook-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,15 @@ export interface CellOutputEdit {
editType: CellEditType.Output;
index: number;
outputs: CellOutput[];
deleteCount?: number;
append?: boolean;
}

export interface CellOutputEditByHandle {
editType: CellEditType.Output;
handle: number;
outputs: CellOutput[];
deleteCount?: number;
append?: boolean;
}

Expand Down
13 changes: 11 additions & 2 deletions packages/notebook/src/browser/service/notebook-context-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ import {
NOTEBOOK_CELL_EDITABLE,
NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE,
NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE,
NOTEBOOK_CELL_TYPE, NOTEBOOK_KERNEL, NOTEBOOK_KERNEL_SELECTED,
NOTEBOOK_CELL_TYPE, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_KERNEL, NOTEBOOK_KERNEL_SELECTED,
NOTEBOOK_VIEW_TYPE
} from '../contributions/notebook-context-keys';
import { NotebookEditorWidget } from '../notebook-editor-widget';
import { NotebookCellModel } from '../view-model/notebook-cell-model';
import { CellKind } from '../../common';
import { CellKind, NotebookCellsChangeType } from '../../common';
import { NotebookExecutionStateService } from './notebook-execution-state-service';

@injectable()
Expand Down Expand Up @@ -73,6 +73,15 @@ export class NotebookContextManager {
}
}));

widget.model?.onDidChangeContent(events => {
if (events.some(e => e.kind === NotebookCellsChangeType.ModelChange || e.kind === NotebookCellsChangeType.Output)) {
this.scopedStore.setContext(NOTEBOOK_HAS_OUTPUTS, widget.model?.cells.some(cell => cell.outputs.length > 0));
this.onDidChangeContextEmitter.fire(this.createContextKeyChangedEvent([NOTEBOOK_HAS_OUTPUTS]));
}
});

this.scopedStore.setContext(NOTEBOOK_HAS_OUTPUTS, !!widget.model?.cells.find(cell => cell.outputs.length > 0));

// Cell Selection realted keys
this.scopedStore.setContext(NOTEBOOK_CELL_FOCUSED, !!widget.model?.selectedCell);
widget.model?.onDidChangeSelectedCell(e => {
Expand Down
5 changes: 3 additions & 2 deletions packages/notebook/src/browser/style/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,9 @@
cursor: pointer;
}

.theia-notebook-main-toolbar-item:hover {
background-color: var(--theia-toolbar-hoverBackground);
.theia-notebook-main-toolbar-item.theia-mod-disabled:hover {
background-color: transparent;
cursor: default;
}

.theia-notebook-main-toolbar-item-text {
Expand Down
30 changes: 18 additions & 12 deletions packages/notebook/src/browser/view-model/notebook-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { Disposable, Emitter, Event, Resource, URI } from '@theia/core';
import { Disposable, Emitter, Event, QueueableEmitter, Resource, URI } from '@theia/core';
import { Saveable, SaveOptions } from '@theia/core/lib/browser';
import {
CellData, CellEditType, CellUri, NotebookCellInternalMetadata,
Expand Down Expand Up @@ -65,7 +65,7 @@ export class NotebookModel implements Saveable, Disposable {
protected readonly onDidAddOrRemoveCellEmitter = new Emitter<NotebookModelWillAddRemoveEvent>();
readonly onDidAddOrRemoveCell = this.onDidAddOrRemoveCellEmitter.event;

protected readonly onDidChangeContentEmitter = new Emitter<NotebookContentChangedEvent[]>();
protected readonly onDidChangeContentEmitter = new QueueableEmitter<NotebookContentChangedEvent>();
readonly onDidChangeContent = this.onDidChangeContentEmitter.event;

protected readonly onDidChangeSelectedCellEmitter = new Emitter<NotebookCellModel | undefined>();
Expand Down Expand Up @@ -281,13 +281,18 @@ export class NotebookModel implements Saveable, Disposable {
} else {
// could definitely be more efficient. See vscode __spliceNotebookCellOutputs2
// For now, just replace the whole existing output with the new output
cell.spliceNotebookCellOutputs({ start: 0, deleteCount: cell.outputs.length, newOutputs: edit.outputs });
cell.spliceNotebookCellOutputs({ start: 0, deleteCount: edit.deleteCount ?? cell.outputs.length, newOutputs: edit.outputs });
}

this.onDidChangeContentEmitter.queue({ kind: NotebookCellsChangeType.Output, index: cellIndex, outputs: cell.outputs, append: edit.append ?? false });
break;
}
case CellEditType.OutputItems:
cell.changeOutputItems(edit.outputId, !!edit.append, edit.items);
this.onDidChangeContentEmitter.queue({
kind: NotebookCellsChangeType.OutputItem, index: cellIndex, outputItems: edit.items,
outputId: edit.outputId, append: edit.append ?? false
});

break;
case CellEditType.Metadata:
this.changeCellMetadata(this.cells[cellIndex], edit.metadata, computeUndoRedo);
Expand All @@ -308,12 +313,15 @@ export class NotebookModel implements Saveable, Disposable {
this.moveCellToIndex(cellIndex, edit.length, edit.newIdx, computeUndoRedo);
break;
}

// if selected cell is affected update it because it can potentially have been replaced
if (cell === this.selectedCell) {
this.setSelectedCell(this.cells[cellIndex]);
}
}

this.onDidChangeContentEmitter.fire();

}

protected async replaceCells(start: number, deleteCount: number, newCells: CellData[], computeUndoRedo: boolean): Promise<void> {
Expand Down Expand Up @@ -352,7 +360,7 @@ export class NotebookModel implements Saveable, Disposable {
await Promise.all(cells.map(cell => cell.resolveTextModel()));

this.onDidAddOrRemoveCellEmitter.fire({ rawEvent: { kind: NotebookCellsChangeType.ModelChange, changes }, newCellIds: cells.map(cell => cell.handle) });
this.onDidChangeContentEmitter.fire([{ kind: NotebookCellsChangeType.ModelChange, changes }]);
this.onDidChangeContentEmitter.queue({ kind: NotebookCellsChangeType.ModelChange, changes });
if (cells.length > 0) {
this.setSelectedCell(cells[cells.length - 1]);
cells[cells.length - 1].requestEdit();
Expand All @@ -370,9 +378,7 @@ export class NotebookModel implements Saveable, Disposable {
}

cell.internalMetadata = newInternalMetadata;
this.onDidChangeContentEmitter.fire([
{ kind: NotebookCellsChangeType.ChangeCellInternalMetadata, index: this.cells.indexOf(cell), internalMetadata: newInternalMetadata }
]);
this.onDidChangeContentEmitter.queue({ kind: NotebookCellsChangeType.ChangeCellInternalMetadata, index: this.cells.indexOf(cell), internalMetadata: newInternalMetadata });
}

protected updateNotebookMetadata(metadata: NotebookDocumentMetadata, computeUndoRedo: boolean): void {
Expand All @@ -385,7 +391,7 @@ export class NotebookModel implements Saveable, Disposable {
}

this.metadata = metadata;
this.onDidChangeContentEmitter.fire([{ kind: NotebookCellsChangeType.ChangeDocumentMetadata, metadata: this.metadata }]);
this.onDidChangeContentEmitter.queue({ kind: NotebookCellsChangeType.ChangeDocumentMetadata, metadata: this.metadata });
}

protected changeCellMetadataPartial(cell: NotebookCellModel, metadata: NullablePartialNotebookCellMetadata, computeUndoRedo: boolean): void {
Expand Down Expand Up @@ -417,7 +423,7 @@ export class NotebookModel implements Saveable, Disposable {
}

cell.metadata = metadata;
this.onDidChangeContentEmitter.fire([{ kind: NotebookCellsChangeType.ChangeCellMetadata, index: this.cells.indexOf(cell), metadata: cell.metadata }]);
this.onDidChangeContentEmitter.queue({ kind: NotebookCellsChangeType.ChangeCellMetadata, index: this.cells.indexOf(cell), metadata: cell.metadata });
}

protected changeCellLanguage(cell: NotebookCellModel, languageId: string, computeUndoRedo: boolean): void {
Expand All @@ -427,7 +433,7 @@ export class NotebookModel implements Saveable, Disposable {

cell.language = languageId;

this.onDidChangeContentEmitter.fire([{ kind: NotebookCellsChangeType.ChangeCellLanguage, index: this.cells.indexOf(cell), language: languageId }]);
this.onDidChangeContentEmitter.queue({ kind: NotebookCellsChangeType.ChangeCellLanguage, index: this.cells.indexOf(cell), language: languageId });
}

protected moveCellToIndex(fromIndex: number, length: number, toIndex: number, computeUndoRedo: boolean): boolean {
Expand All @@ -440,7 +446,7 @@ export class NotebookModel implements Saveable, Disposable {

const cells = this.cells.splice(fromIndex, length);
this.cells.splice(toIndex, 0, ...cells);
this.onDidChangeContentEmitter.fire([{ kind: NotebookCellsChangeType.Move, index: fromIndex, length, newIdx: toIndex, cells }]);
this.onDidChangeContentEmitter.queue({ kind: NotebookCellsChangeType.Move, index: fromIndex, length, newIdx: toIndex, cells });

return true;
}
Expand Down
23 changes: 15 additions & 8 deletions packages/notebook/src/browser/view/notebook-main-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ export class NotebookMainToolbar extends React.Component<NotebookMainToolbarProp

protected toDispose = new DisposableCollection();

protected nativeSubmenus = [
NotebookMenus.NOTEBOOK_MAIN_TOOLBAR_CELL_ADD_GROUP[NotebookMenus.NOTEBOOK_MAIN_TOOLBAR_CELL_ADD_GROUP.length - 1],
NotebookMenus.NOTEBOOK_MAIN_TOOLBAR_EXECUTION_GROUP[NotebookMenus.NOTEBOOK_MAIN_TOOLBAR_EXECUTION_GROUP.length - 1]];

constructor(props: NotebookMainToolbarProps) {
super(props);

Expand Down Expand Up @@ -107,22 +111,22 @@ export class NotebookMainToolbar extends React.Component<NotebookMainToolbarProp
</div>;
}

protected renderMenuItem(item: MenuNode): React.ReactNode {
protected renderMenuItem(item: MenuNode, submenu?: string): React.ReactNode {
if (item.role === CompoundMenuNodeRole.Group) {
const itemNodes = ArrayUtils.coalesce(item.children?.map(child => this.renderMenuItem(child)) ?? []);
const itemNodes = ArrayUtils.coalesce(item.children?.map(child => this.renderMenuItem(child, item.id)) ?? []);
return <React.Fragment key={item.id}>
{itemNodes}
{itemNodes && itemNodes.length > 0 && <span key={`${item.id}-separator`} className='theia-notebook-toolbar-separator'></span>}
</React.Fragment>;
} else if (!item.when || this.props.contextKeyService.match(item.when, this.props.editorNode)) {
} else if ((this.nativeSubmenus.includes(submenu ?? '')) || !item.when || this.props.contextKeyService.match(item.when, this.props.editorNode)) {
const visibleCommand = Boolean(this.props.commandRegistry.getVisibleHandler(item.command ?? '', this.props.notebookModel));
if (!visibleCommand) {
return undefined;
}
const title = (this.props.commandRegistry.getCommand(item.command ?? '') as NotebookCommand)?.tooltip ?? item.label;
return <div key={item.id} title={title} className='theia-notebook-main-toolbar-item action-label'
return <div key={item.id} title={title} className={`theia-notebook-main-toolbar-item action-label${this.getAdditionalClasses(item)}`}
onClick={() => {
if (item.command) {
if (item.command && (!item.when || this.props.contextKeyService.match(item.when, this.props.editorNode))) {
this.props.commandRegistry.executeCommand(item.command, this.props.notebookModel);
}
}}>
Expand All @@ -133,15 +137,18 @@ export class NotebookMainToolbar extends React.Component<NotebookMainToolbarProp
return undefined;
}

private getMenuItems(): readonly MenuNode[] {
protected getMenuItems(): readonly MenuNode[] {
const menuPath = NotebookMenus.NOTEBOOK_MAIN_TOOLBAR;
const pluginCommands = this.props.menuRegistry.getMenuNode(menuPath).children;
const theiaCommands = this.props.menuRegistry.getMenu([menuPath]).children;
// TODO add specifc arguments to commands
return theiaCommands.concat(pluginCommands);
}

private getAllContextKeys(menus: readonly MenuNode[], keySet: Set<string>): void {
protected getAdditionalClasses(item: MenuNode): string {
return !item.when || this.props.contextKeyService.match(item.when, this.props.editorNode) ? '' : ' theia-mod-disabled';
}

protected getAllContextKeys(menus: readonly MenuNode[], keySet: Set<string>): void {
menus.filter(item => item.when)
.forEach(item => this.props.contextKeyService.parseKeys(item.when!)?.forEach(key => keySet.add(key)));

Expand Down
10 changes: 5 additions & 5 deletions packages/plugin-ext/src/plugin/type-converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -749,9 +749,9 @@ export function isUriComponents(arg: unknown): arg is UriComponents {
return isObject<UriComponents>(arg) &&
typeof arg.scheme === 'string' &&
(arg['$mid'] === 1 || (
typeof arg.path === 'string' &&
typeof arg.query === 'string' &&
typeof arg.fragment === 'string'));
typeof arg.path === 'string' &&
typeof arg.query === 'string' &&
typeof arg.fragment === 'string'));
}

export function isModelCallHierarchyItem(arg: unknown): arg is model.CallHierarchyItem {
Expand Down Expand Up @@ -1522,8 +1522,8 @@ export namespace NotebookCellData {
cellKind: NotebookCellKind.from(data.kind),
language: data.languageId,
source: data.value,
// metadata: data.metadata,
// internalMetadata: NotebookCellExecutionSummary.from(data.executionSummary ?? {}),
metadata: data.metadata,
internalMetadata: NotebookCellExecutionSummary.from(data.executionSummary ?? {}),
outputs: data.outputs ? data.outputs.map(NotebookCellOutputConverter.from) : []
};
}
Expand Down
Loading