/* Copyright 2026 Marimo. All rights reserved. */ import type { JSX } from "react"; /* Copyright 2026 Marimo. All rights reserved. */ import { useCellActions, useCellIds, useCellNames } from "@/core/cells/cells"; import { type CellId, HTMLCellId, SCRATCH_CELL_ID } from "@/core/cells/ids"; import { displayCellName } from "@/core/cells/names"; import { goToCellLine } from "@/core/codemirror/go-to-definition/utils"; import { useFilename } from "@/core/saving/filename"; import { cn } from "@/utils/cn"; import { Logger } from "../../../utils/Logger"; interface Props { cellId: CellId; className?: string; shouldScroll?: boolean; skipScroll?: boolean; onClick?: () => void; formatCellName?: (name: string) => string; variant?: "destructive" | "focus"; } /* Component that adds a link to a cell, with styling. */ export const CellLink = (props: Props): JSX.Element => { const { className, cellId, variant, onClick, formatCellName, skipScroll } = props; const cellName = useCellNames()[cellId] ?? ""; const cellIndex = useCellIds().inOrderIds.indexOf(cellId); const { showCellIfHidden } = useCellActions(); const formatName = formatCellName ?? ((name: string) => name); return (
{ // Scratch causes a crash since scratch is not registered like a // normal cell. if (cellId === SCRATCH_CELL_ID) { return false; } showCellIfHidden({ cellId }); e.stopPropagation(); e.preventDefault(); requestAnimationFrame(() => { const succeeded = scrollAndHighlightCell(cellId, variant, skipScroll); if (succeeded) { onClick?.(); } }); }} > {formatName(displayCellName(cellName, cellIndex))}
); }; /* Component that adds a link to a cell, for use in a MarimoError. */ export const CellLinkError = ( props: Pick, ): JSX.Element => { return ; }; /* Component that adds a link to a cell, for use in tracebacks. */ export const CellLinkTraceback = ({ cellId, lineNumber, }: { cellId: CellId; lineNumber: number; }): JSX.Element => { const filename = useFilename(); return ( goToCellLine(cellId, lineNumber)} skipScroll={true} variant={"destructive"} className="traceback-cell-link" formatCellName={(name: string) => cellId === SCRATCH_CELL_ID ? "scratch" : `marimo://${filename || "untitled"}#cell=${name}` } /> ); }; export function scrollAndHighlightCell( cellId: CellId, variant?: "destructive" | "focus", skipScroll?: boolean, ): boolean { const cellHtmlId = HTMLCellId.create(cellId); const cell: HTMLElement | null = document.getElementById(cellHtmlId); const isCellErrored = cell?.classList.contains("has-error"); if (cell === null) { Logger.error(`Cell ${cellHtmlId} not found on page.`); return false; } if (!skipScroll) { cell.scrollIntoView({ behavior: "smooth", block: "center" }); } if (variant === "destructive" || (isCellErrored && variant === undefined)) { cell.classList.add("error-outline"); setTimeout(() => { cell.classList.remove("error-outline"); }, 2000); } if (variant === "focus" || (!isCellErrored && variant === undefined)) { cell.classList.add("focus-outline"); setTimeout(() => { cell.classList.remove("focus-outline"); }, 2000); } return true; }