/* Copyright 2026 Marimo. All rights reserved. */ import { Provider, useAtomValue } from "jotai"; import { DatabaseZapIcon } from "lucide-react"; import type React from "react"; import { useEffect } from "react"; import { flushSync } from "react-dom"; import { createRoot } from "react-dom/client"; import { useLocale } from "react-aria"; import { ProtocolIcon } from "@/components/storage/components"; import { Badge } from "@/components/ui/badge"; import { cacheInfoAtom } from "@/core/cache/requests"; import { dataConnectionsMapAtom } from "@/core/datasets/data-source-connections"; import type { ConnectionName } from "@/core/datasets/engines"; import { datasetTablesAtom } from "@/core/datasets/state"; import { getRequestClient } from "@/core/network/requests"; import { store } from "@/core/state/jotai"; import { storageNamespacesAtom } from "@/core/storage/state"; import { variablesAtom } from "@/core/variables/state"; import type { VariableName } from "@/core/variables/types"; import { assertNever } from "@/utils/assertNever"; import { formatTime } from "@/utils/formatting"; import { prettyNumber } from "@/utils/numbers"; import { CONTAINER_STYLES, MetadataRow, renderDatasourceInfo, renderEmptyInfo, renderTableInfo, SectionHeader, } from "../language/languages/sql/renderers"; import type { CodeLensSpec } from "./entities"; /** * Mounts the hover popover for a code lens into `dom`; returns a dispose * function. */ export function mountLensPopover( dom: HTMLElement, spec: CodeLensSpec, ): () => void { const root = createRoot(dom); // CodeMirror measures and positions the tooltip as soon as this function // returns. Render now so it measures the popover content, not an empty // container that later grows over the hovered icon. flushSync(() => { root.render( , ); }); // Defer so tearing the tooltip down from inside a React event (e.g. the // icon's click handler) doesn't unmount mid-render return () => queueMicrotask(() => root.unmount()); } const LensPopover: React.FC<{ spec: CodeLensSpec }> = ({ spec }) => { switch (spec.kind) { case "table": return ; case "connection": return ; case "bucket": return ; case "cache": return ( ); default: assertNever(spec.kind); } }; const TablePopover: React.FC<{ name: string }> = ({ name }) => { const tables = useAtomValue(datasetTablesAtom); const table = tables.find((t) => t.variable_name === name); return table ? renderTableInfo(table) : renderEmptyInfo("table"); }; const ConnectionPopover: React.FC<{ name: string }> = ({ name }) => { const connections = useAtomValue(dataConnectionsMapAtom); const connection = connections.get(name as ConnectionName); if (!connection) { return renderEmptyInfo("database"); } return renderDatasourceInfo(connection); }; const BucketPopover: React.FC<{ name: string }> = ({ name }) => { const namespaces = useAtomValue(storageNamespacesAtom); const namespace = namespaces.find((ns) => ns.name === name); if (!namespace) { return (
No storage information available.
); } return (
} title={namespace.displayName} badge={ {namespace.protocol} } />
{namespace.name} } /> {namespace.rootPath || "(root)"} } /> {namespace.backendType}} />
); }; // `_cache_call.__repr__` starts with "hits=N misses=N", which survives the // variable-preview truncation const CACHE_STATS_PATTERN = /\bhits=(\d+) misses=(\d+)/; // The popover remounts on every hover (see `mountLensPopover`), so this has // to live at module scope rather than in component state: it debounces the // notebook-wide `getCacheInfo` fetch across repeated hovers. let lastCacheInfoFetchAt: number | null = null; const CACHE_INFO_REFETCH_INTERVAL_MS = 5000; function maybeFetchCacheInfo(): void { const now = Date.now(); if ( lastCacheInfoFetchAt != null && now - lastCacheInfoFetchAt < CACHE_INFO_REFETCH_INTERVAL_MS ) { return; } lastCacheInfoFetchAt = now; getRequestClient() .getCacheInfo() .catch(() => { // Allow the next hover to retry rather than waiting out the interval. lastCacheInfoFetchAt = null; }); } const CachePopover: React.FC<{ boundName: string | null; cacheName: string | null; }> = ({ boundName, cacheName }) => { const { locale } = useLocale(); const variables = useAtomValue(variablesAtom); const cacheInfo = useAtomValue(cacheInfoAtom); const variable = boundName ? variables[boundName as VariableName] : undefined; const stats = variable?.dataType?.startsWith("_cache_call") ? variable.value?.match(CACHE_STATS_PATTERN) : undefined; const hasLocalStats = Boolean(stats); // Only fetch the notebook-wide fallback when there are no per-cache stats // to show, and throttle across repeated hovers so mousing over a lens // several times in a row doesn't fire a fresh request every time. useEffect(() => { if (hasLocalStats) { return; } maybeFetchCacheInfo(); }, [hasLocalStats]); return (
} title={cacheName ?? boundName ?? "Cache"} /> {stats ? (
{prettyNumber(Number(stats[1]), locale)} } /> {prettyNumber(Number(stats[2]), locale)} } />
) : ( cacheInfo && (
Notebook-wide
) )}
Click to open the cache panel
); };