/* Copyright 2026 Marimo. All rights reserved. */ import { horizontalListSortingStrategy, SortableContext, verticalListSortingStrategy, } from "@dnd-kit/sortable"; import { useAtomValue } from "jotai"; import { DatabaseIcon, SparklesIcon, SquareCodeIcon, SquareMIcon, } from "lucide-react"; import { useEffect } from "react"; import { useOpenSettingsToTab } from "@/components/app-config/state"; import { StartupLogsAlert } from "@/components/editor/alerts/startup-logs-alert"; import { Cell } from "@/components/editor/notebook-cell"; import { PackageAlert } from "@/components/editor/package-alert"; import { SortableCellsProvider } from "@/components/sort/SortableCellsProvider"; import { Button } from "@/components/ui/button"; import { Tooltip } from "@/components/ui/tooltip"; import { maybeAddMarimoImport } from "@/core/cells/add-missing-import"; import { SETUP_CELL_ID } from "@/core/cells/ids"; import { LanguageAdapters } from "@/core/codemirror/language/LanguageAdapters"; import { MARKDOWN_INITIAL_HIDE_CODE } from "@/core/codemirror/language/languages/markdown"; import { aiEnabledAtom } from "@/core/config/config"; import { canInteractWithAppAtom } from "@/core/network/connection"; import { useBoolean } from "@/hooks/useBoolean"; import { cn } from "@/utils/cn"; import { Functions } from "@/utils/functions"; import type { CellColumnId } from "@/utils/id-tree"; import { invariant } from "@/utils/invariant"; import { columnIdsAtom, useCellActions, useCellIds, useScrollKey, } from "../../../core/cells/cells"; import { formatAll } from "../../../core/codemirror/format"; import type { AppConfig, UserConfig } from "../../../core/config/config-schema"; import type { AppMode } from "../../../core/mode"; import { useHotkey } from "../../../hooks/useHotkey"; import { type Theme, useTheme } from "../../../theme/useTheme"; import { AddCellWithAI } from "../ai/add-cell-with-ai"; import { ConnectingAlert, NotStartedConnectionAlert, } from "../alerts/connecting-alert"; import { FloatingOutline } from "../chrome/panels/outline/floating-outline"; import { useChromeActions } from "../chrome/state"; import { Column } from "../columns/cell-column"; import { NotebookBanner } from "../notebook-banner"; import { StdinBlockingAlert } from "../stdin-blocking-alert"; import { useFocusFirstEditor } from "./vertical-layout/useFocusFirstEditor"; import { VerticalLayoutWrapper } from "./vertical-layout/vertical-layout-wrapper"; interface CellArrayProps { mode: AppMode; userConfig: UserConfig; appConfig: AppConfig; } export const CellArray: React.FC = (props) => { const columnIds = useAtomValue(columnIdsAtom); // Setup context for sorting return ( ); }; const CellArrayInternal: React.FC = ({ mode, userConfig, appConfig, }) => { const actions = useCellActions(); const { theme } = useTheme(); const { toggleSidebarPanel } = useChromeActions(); // Side-effects useFocusFirstEditor(); // HOTKEYS useHotkey("global.focusTop", actions.focusTopCell); useHotkey("global.focusBottom", actions.focusBottomCell); useHotkey("global.toggleSidebar", toggleSidebarPanel); useHotkey("global.foldCode", actions.foldAll); useHotkey("global.unfoldCode", actions.unfoldAll); useHotkey("global.formatAll", () => { formatAll(); }); // Catch all to avoid native OS behavior // Otherwise a user might try to hide a cell and accidentally hide the OS window useHotkey("cell.hideCode", Functions.NOOP); useHotkey("cell.format", Functions.NOOP); const cellIds = useCellIds(); const scrollKey = useScrollKey(); const columnIds = cellIds.getColumnIds(); // Scroll to a cell targeted by a previous action const scrollToTarget = actions.scrollToTarget; useEffect(() => { if (scrollKey !== null) { scrollToTarget(); } }, [cellIds, scrollKey, scrollToTarget]); return ( {/* Only show if not cells, otherwise running a single cell will start the connection */} {cellIds.idLength === 0 && }
{columnIds.map((columnId, index) => ( ))}
); }; /** * A single column of cells. */ const CellColumn: React.FC<{ columnId: CellColumnId; index: number; columnsLength: number; appConfig: AppConfig; mode: AppMode; userConfig: UserConfig; theme: Theme; }> = ({ columnId, index, columnsLength, appConfig, mode, userConfig, theme, }) => { const cellIds = useCellIds(); const column = cellIds.get(columnId); invariant(column, `Expected column for: ${columnId}`); const hasOnlyOneCell = cellIds.hasOnlyOneId(); const hasSetupCell = cellIds.inOrderIds.includes(SETUP_CELL_ID); return ( 0} canMoveRight={index < columnsLength - 1} width={appConfig.width} canDelete={columnsLength > 1} footer={ } > {/* Render the setup cell first, always */} {index === 0 && hasSetupCell && ( )} {column.topLevelIds.map((cellId) => { // Skip the setup cell later if (cellId === SETUP_CELL_ID) { return null; } return ( ); })} ); }; const AddCellButtons: React.FC<{ columnId: CellColumnId; className?: string; }> = ({ columnId, className }) => { const { createNewCell } = useCellActions(); const [isAiButtonOpen, isAiButtonOpenActions] = useBoolean(false); const aiEnabled = useAtomValue(aiEnabledAtom); const canInteractWithApp = useAtomValue(canInteractWithAppAtom); const { handleClick } = useOpenSettingsToTab(); const buttonClass = cn( "mb-0 rounded-none sm:px-4 md:px-5 lg:px-8 tracking-wide no-wrap whitespace-nowrap", "font-semibold opacity-70 hover:opacity-90 uppercase text-xs", ); const renderBody = () => { if (isAiButtonOpen) { return ; } return ( <> AI provider not found or Edit model not selected ) } delayDuration={100} asChild={false} > ); }; return (
{renderBody()}
); };