diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCells/IPositronNotebookCell.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCells/IPositronNotebookCell.ts index 925011458a8..becbe5da116 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCells/IPositronNotebookCell.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCells/IPositronNotebookCell.ts @@ -175,6 +175,11 @@ export interface IPositronNotebookCell extends Disposable { * Detach the editor from the cell */ detachEditor(): void; + + /** + * Whether the cell has error output + */ + readonly hasError: IObservable; } diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCells/PositronNotebookCell.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCells/PositronNotebookCell.ts index ce40bc69cc3..f16e522fdab 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCells/PositronNotebookCell.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCells/PositronNotebookCell.ts @@ -32,6 +32,7 @@ export abstract class PositronNotebookCellGeneral extends Disposable implements public readonly executionStatus; public readonly selectionStatus = observableValue('cellSelectionStatus', CellSelectionStatus.Unselected); public readonly editorFocusRequested: IObservableSignal = this._editorFocusRequested; + public readonly hasError = observableValue('hasError', false); constructor( public readonly cellModel: NotebookCellTextModel, diff --git a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCells/PositronNotebookCodeCell.ts b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCells/PositronNotebookCodeCell.ts index 0a13b601961..644311f8ba4 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCells/PositronNotebookCodeCell.ts +++ b/src/vs/workbench/contrib/positronNotebook/browser/PositronNotebookCells/PositronNotebookCodeCell.ts @@ -37,7 +37,11 @@ export class PositronNotebookCodeCell extends PositronNotebookCellGeneral implem this.outputs = observableFromEvent(this, this.cellModel.onDidChangeOutputs, () => { /** @description cellOutputs */ - return this.parseCellOutputs(); + const parsedOutputs = this.parseCellOutputs(); + // Update hasError when outputs change + const hasError = parsedOutputs.some(o => o.parsed.type === 'error'); + this.hasError.set(hasError, undefined); + return parsedOutputs; }); // Execution timing observables diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.css b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.css index 98165be44a0..9f134e1ecc9 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.css +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellEditorMonacoWidget.css @@ -3,20 +3,28 @@ * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. *--------------------------------------------------------------------------------------------*/ .positron-cell-editor-monaco-widget { - /* Override the default editor background */ - --vscode-editor-background: var(--vscode-checkbox-background, #f8f8f8); - --vscode-editorGutter-background: var(--vscode-editor-background); border: var(--vscode-positronNotebook-border); + border-left: none; /* The selection bar on the left handles the left-side border */ border-radius: var(--vscode-positronNotebook-cell-radius); + /* The selection bar handles the left-side border radius */ + border-bottom-left-radius: 0; + border-top-left-radius: 0; padding: 0; - overflow: hidden; + overflow: hidden; /* Clip content to respect rounded corners */ +} + +/* Show focus outline with default VS Code focus color */ +.positron-cell-editor-monaco-widget:focus-within { + outline: 1px solid var(--vscode-focusBorder); + outline-offset: -1px; +} - &:focus-within { - outline: 1px solid var(--vscode-focusBorder); - outline-offset: -1px; - } +/* When the parent cell has an error, highlight the editor with error color */ +.positron-notebook-cell[data-has-error="true"] .positron-cell-editor-monaco-widget:focus-within { + outline-color: var(--vscode-positronNotebook-error-color); +} - .positron-monaco-editor-container { - min-height: 1rem; - } +/* Ensure the editor container has minimum height for empty cells */ +.positron-monaco-editor-container { + min-height: 1rem; } diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellLeftActionMenu.css b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellLeftActionMenu.css index 823c755b547..a935e406a56 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellLeftActionMenu.css +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellLeftActionMenu.css @@ -122,6 +122,11 @@ font-family: var(--monaco-monospace-font); } +/* Error state styling - use error color for execution badge */ +.execution-order-badge-container[data-has-error="true"] { + color: var(--vscode-errorForeground); +} + /* Animation for running state */ @keyframes spin { 0% { diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellLeftActionMenu.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellLeftActionMenu.tsx index c3b9558ed1b..ada11d00ff6 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellLeftActionMenu.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/CellLeftActionMenu.tsx @@ -51,6 +51,7 @@ export function CellLeftActionMenu({ cell }: CellLeftActionMenuProps) { const executionStatus = useObservedValue(cell.executionStatus); const duration = useObservedValue(cell.lastExecutionDuration); const lastRunEndTime = useObservedValue(cell.lastRunEndTime); + const hasError = useObservedValue(cell.hasError); // Derived state const isRunning = executionStatus === 'running'; @@ -110,6 +111,7 @@ export function CellLeftActionMenu({ cell }: CellLeftActionMenuProps) { cellSelected={isSelected} executionOrder={executionOrder} executionStatus={dataExecutionStatus} + hasError={hasError} isHovered={isHovered} showPending={showPending} /> diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/ExecutionStatusBadge.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/ExecutionStatusBadge.tsx index e815bbce278..035579159a7 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/ExecutionStatusBadge.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/ExecutionStatusBadge.tsx @@ -11,6 +11,7 @@ interface ExecutionStatusBadgeProps { executionOrder?: number; showPending: boolean; executionStatus: string; + hasError: boolean; } /** @@ -18,7 +19,7 @@ interface ExecutionStatusBadgeProps { * Used when the cell is not selected/hovered and showing static execution status. * Only renders when the cell is in the 'idle' execution state. */ -export function ExecutionStatusBadge({ cellSelected, isHovered, executionOrder, showPending, executionStatus }: ExecutionStatusBadgeProps) { +export function ExecutionStatusBadge({ cellSelected, isHovered, executionOrder, showPending, executionStatus, hasError }: ExecutionStatusBadgeProps) { if (cellSelected || isHovered) { // We show action buttons in this case @@ -36,7 +37,7 @@ export function ExecutionStatusBadge({ cellSelected, isHovered, executionOrder, if (executionOrder !== undefined) { return ( -
+
[ {String(executionOrder)} ] diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSelection.css b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSelection.css index 573e1622602..1e38357d4ca 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSelection.css +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellSelection.css @@ -9,6 +9,7 @@ --_positron-notebook-selection-bar-width: 7px; --_positron-notebook-selection-bar-top-offset: 5px; --_positron-notebook-selection-bar-inset: 0; + --_positron-notebook-selection-bar-gap: 2px; } /* Base styles for selected and editing states */ @@ -27,46 +28,79 @@ position: relative; } -/* Common base styles for all selection bars */ -.positron-notebook-cell.selected .positron-notebook-editor-container::before, -.positron-notebook-cell.editing .positron-notebook-editor-container::before, -.positron-notebook-cell.selected .positron-notebook-cell-outputs::before, -.positron-notebook-cell.editing .positron-notebook-cell-outputs::before { +/* Add permanent left padding to editor container to make room for selection bar */ +.positron-notebook-editor-container { + position: relative; + padding-left: calc(var(--_positron-notebook-selection-bar-width) + var(--_positron-notebook-selection-bar-gap)); +} + +/* Permanent spacer/selection bar for editor - always visible, changes color when selected */ +.positron-notebook-editor-container::after { content: ""; position: absolute; width: var(--_positron-notebook-selection-bar-width); - background-color: var(--vscode-focusBorder); - z-index: 1; - left: var(--_positron-notebook-selection-bar-inset); + background-color: var(--vscode-editor-background); + left: var(--_positron-notebook-selection-bar-gap); top: var(--_positron-notebook-selection-bar-inset); bottom: var(--_positron-notebook-selection-bar-inset); + border: var(--vscode-positronNotebook-border); + border-right: none; + border-top-left-radius: var(--_positron-notebook-selection-bar-radius); + border-bottom-left-radius: var(--_positron-notebook-selection-bar-radius); + transition: border-bottom-left-radius 0.15s ease; } -/* Don't round bottom corner for editor bar to make it look like a continuous bar */ -.positron-notebook-cell.selected .positron-notebook-editor-container::before, -.positron-notebook-cell.editing .positron-notebook-editor-container::before { - border-top-left-radius: var(--_positron-notebook-selection-bar-radius); +/* When selected/editing: change to focus color and remove bottom rounding by default */ +.positron-notebook-cell.selected .positron-notebook-editor-container::after, +.positron-notebook-cell.editing .positron-notebook-editor-container::after { + background-color: var(--vscode-focusBorder); border-bottom-left-radius: 0; } -/* Specific positioning for outputs bar */ -.positron-notebook-cell.selected .positron-notebook-cell-outputs::before, -.positron-notebook-cell.editing .positron-notebook-cell-outputs::before { +/* Selection bar for outputs - conditional (only when selected/editing) */ +.positron-notebook-cell.selected .positron-notebook-cell-outputs::after, +.positron-notebook-cell.editing .positron-notebook-cell-outputs::after { + content: ""; + position: absolute; + width: var(--_positron-notebook-selection-bar-width); + background-color: var(--vscode-focusBorder); /* Push bar down slightly so there's a gap between the editor and outputs */ top: var(--_positron-notebook-selection-bar-top-offset); + bottom: var(--_positron-notebook-selection-bar-inset); + left: var(--_positron-notebook-selection-bar-gap); + border: var(--vscode-positronNotebook-border); + border-right: none; /* Don't round top corner for outputs bar to make it look like a continuous bar*/ border-top-left-radius: 0; border-bottom-left-radius: var(--_positron-notebook-selection-bar-radius); } -/* When outputs are empty, round both corners of editor bar */ -.positron-notebook-cell.selected:has(.positron-notebook-cell-outputs.no-outputs) .positron-notebook-editor-container::before, -.positron-notebook-cell.editing:has(.positron-notebook-cell-outputs.no-outputs) .positron-notebook-editor-container::before { +/* When selected/editing AND no outputs: restore bottom rounding */ +.positron-notebook-cell.selected:has(.positron-notebook-cell-outputs.no-outputs) .positron-notebook-editor-container::after, +.positron-notebook-cell.editing:has(.positron-notebook-cell-outputs.no-outputs) .positron-notebook-editor-container::after { border-bottom-left-radius: var(--_positron-notebook-selection-bar-radius); } /* When editor container is empty, round both corners of outputs bar (This is the case when markdown has been rendered but doesn't have an editor open) */ -.positron-notebook-cell.selected:has(.positron-notebook-editor-container.editor-hidden) .positron-notebook-cell-outputs::before { +.positron-notebook-cell.selected:has(.positron-notebook-editor-container.editor-hidden) .positron-notebook-cell-outputs::after { top: var(--_positron-notebook-selection-bar-inset); border-top-left-radius: var(--_positron-notebook-selection-bar-radius); } + +/* Hide the editor selection bar when the editor is hidden (e.g., collapsed markdown cells) */ +.positron-notebook-editor-container.editor-hidden::after { + display: none; +} + +/* Error state styling - use error color instead of focus color when selected */ +.positron-notebook-cell[data-has-error="true"]:is(.selected, .editing) .positron-notebook-editor-container::after, +.positron-notebook-cell[data-has-error="true"]:is(.selected, .editing) .positron-notebook-cell-outputs::after { + background-color: var(--vscode-errorForeground); +} + +/* Respect user's motion preferences */ +@media (prefers-reduced-motion: reduce) { + .positron-notebook-editor-container::after { + transition: none; + } +} diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellWrapper.css b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellWrapper.css index ea9971c38f2..772f7423939 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellWrapper.css +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellWrapper.css @@ -4,6 +4,10 @@ *--------------------------------------------------------------------------------------------*/ .positron-notebook-cell { + /* Override the default editor background */ + --vscode-editor-background: var(--vscode-checkbox-background, #f8f8f8); + --vscode-editorGutter-background: var(--vscode-editor-background); + position: relative; margin-left: 12px; diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellWrapper.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellWrapper.tsx index 404583131ca..e7bc6097475 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellWrapper.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookCellWrapper.tsx @@ -34,6 +34,7 @@ export function NotebookCellWrapper({ cell, actionBarChildren, children }: { const environment = useEnvironment(); const selectionStatus = useObservedValue(cell.selectionStatus); const executionStatus = useObservedValue(cell.executionStatus); + const hasError = useObservedValue(cell.hasError); React.useEffect(() => { if (cellRef.current) { @@ -85,6 +86,7 @@ export function NotebookCellWrapper({ cell, actionBarChildren, children }: { aria-label={localize('notebookCell', '{0} cell', cellType)} aria-selected={isSelected} className={`positron-notebook-cell positron-notebook-${cell.kind === CellKind.Code ? 'code' : 'markdown'}-cell ${selectionStatus}`} + data-has-error={hasError} data-is-running={executionStatus === 'running'} data-testid='notebook-cell' role='article' diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkdownCell.css b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkdownCell.css index 9799ea489b3..08d68cc4126 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkdownCell.css +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkdownCell.css @@ -3,7 +3,7 @@ * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. *--------------------------------------------------------------------------------------------*/ -.positron-notebook-markup-rendered { +.positron-notebook-markdown-rendered { padding-inline: 1rem; padding-block: 0.5rem; diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkdownCell.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkdownCell.tsx index fd1416d68bc..54d17234294 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkdownCell.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/NotebookMarkdownCell.tsx @@ -29,14 +29,14 @@ export function NotebookMarkdownCell({ cell }: { cell: PositronNotebookMarkdownC {editorShown ? : null}
-
{ +
{ cell.toggleEditor(); }}> { markdownString.length > 0 ? :
- Empty markup cell. {editorShown ? '' : 'Double click to edit'} + Empty markdown cell. {editorShown ? '' : 'Double click to edit'}
}
diff --git a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/actionBar/NotebookCellMoreActionsMenu.tsx b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/actionBar/NotebookCellMoreActionsMenu.tsx index d0ba565a43e..8380b9b567c 100644 --- a/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/actionBar/NotebookCellMoreActionsMenu.tsx +++ b/src/vs/workbench/contrib/positronNotebook/browser/notebookCells/actionBar/NotebookCellMoreActionsMenu.tsx @@ -3,10 +3,6 @@ * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. *--------------------------------------------------------------------------------------------*/ - -// CSS. -import '../../../../../../base/browser/ui/positronComponents/button/button.css'; - // React. import React, { useRef } from 'react'; @@ -18,6 +14,7 @@ import { IPositronNotebookCell } from '../../PositronNotebookCells/IPositronNote import { buildMoreActionsMenuItems } from './actionBarMenuItems.js'; import { INotebookCellActionBarItem } from './actionBarRegistry.js'; import { usePositronReactServicesContext } from '../../../../../../base/browser/positronReactRendererContext.js'; +import { ActionButton } from '../../utilityComponents/ActionButton.js'; interface NotebookCellMoreActionsMenuProps { instance: IPositronNotebookInstance; @@ -70,15 +67,14 @@ export function NotebookCellMoreActionsMenu({ }; return ( - + ); }