@marimo-team/islands 0.23.12-dev2 → 0.23.12-dev20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ConnectedDataExplorerComponent-WqG-xX4l.js → ConnectedDataExplorerComponent-Du3_nUzI.js} +13 -13
- package/dist/{ErrorBoundary-BNx_OSVo.js → ErrorBoundary-DE6tzZf-.js} +2 -2
- package/dist/{any-language-editor-rPSlOll9.js → any-language-editor-DN1R-1KZ.js} +5 -5
- package/dist/{button-vQhauTmO.js → button-BacYv-bE.js} +7 -1
- package/dist/{capabilities-BEHzIS99.js → capabilities-D_4LYhSU.js} +1 -1
- package/dist/{chat-ui-k2kqhCv5.js → chat-ui-CsPewo4h.js} +16 -16
- package/dist/{check-nrzHDi45.js → check-C9OoNtR4.js} +1 -1
- package/dist/{code-visibility-DZ_6U5hT.js → code-visibility-02AuLxDs.js} +664 -663
- package/dist/{copy-UhDed7D4.js → copy-COam1EG7.js} +2 -2
- package/dist/{dist-DYGLrbYQ.js → dist--2Bqjvs0.js} +2 -2
- package/dist/{error-banner-BHAkVFc2.js → error-banner-DFPfz_Qf.js} +2 -2
- package/dist/{esm-Bqu9AE2K.js → esm-M837UxV5.js} +1 -1
- package/dist/{extends-9Yl5BEcg.js → extends-9MVIxxRo.js} +4 -4
- package/dist/{formats-BV4bOfMI.js → formats-d6MhLuQ9.js} +4 -4
- package/dist/{glide-data-editor-BDTq6YUb.js → glide-data-editor-DkzAInWG.js} +9 -9
- package/dist/{html-to-image-C86pQALH.js → html-to-image-DXwLcQ6l.js} +95 -88
- package/dist/{input-AKkGXdyV.js → input-CbEz_aj_.js} +6 -6
- package/dist/{label-E3ZJXHu8.js → label-WfTSU8L4.js} +2 -2
- package/dist/{loader-YPuQvn1Y.js → loader-Boph2xIS.js} +1 -1
- package/dist/main.js +1753 -1626
- package/dist/{mermaid-QFAR9YgY.js → mermaid-CJW9vIyO.js} +5 -5
- package/dist/{process-output-nNw4OpSj.js → process-output-C6_e1pT_.js} +3 -3
- package/dist/{reveal-component-BxDb5eK0.js → reveal-component-CX0nM3qj.js} +11 -11
- package/dist/{spec-B45_YCNI.js → spec-Bv-XlYiv.js} +4 -4
- package/dist/{strings-Cq2s9_EQ.js → strings-Dq_j3Rxw.js} +4 -4
- package/dist/style.css +2 -2
- package/dist/{swiper-component-BNa_4kh2.js → swiper-component-5HoSsPi1.js} +2 -2
- package/dist/{toDate-Do1xRzAo.js → toDate-D-l5s8nn.js} +3 -3
- package/dist/{tooltip-Bz3OAwrU.js → tooltip-Czds6Qr8.js} +3 -3
- package/dist/{types-D8gEGs4R.js → types-C2Ir191_.js} +1 -1
- package/dist/{useAsyncData-CL3o2p4i.js → useAsyncData-1Dhzjfwf.js} +1 -1
- package/dist/{useDateFormatter-BC6iSz9g.js → useDateFormatter-CMnRuVmN.js} +2 -2
- package/dist/{useDeepCompareMemoize-BPx2MuOK.js → useDeepCompareMemoize-CDWT3BDz.js} +1 -1
- package/dist/{useIframeCapabilities-C6Ta3EyP.js → useIframeCapabilities-DWIYvDh7.js} +1 -1
- package/dist/{useLifecycle-C3Ec71q0.js → useLifecycle-AHlswLw-.js} +3 -3
- package/dist/{useTheme-ZhT6uIu3.js → useTheme-BrYvK-_A.js} +2 -2
- package/dist/{vega-component-C3AWYGAL.js → vega-component-Pk6lyc_a.js} +10 -10
- package/dist/{zod-DXqkaI_w.js → zod-CijjQh4u.js} +1 -1
- package/package.json +3 -3
- package/src/components/ai/display-helpers.tsx +5 -5
- package/src/components/app-config/ai-config.tsx +5 -5
- package/src/components/app-config/mcp-config.tsx +3 -3
- package/src/components/chat/acp/agent-panel.tsx +3 -3
- package/src/components/chat/acp/blocks.tsx +36 -38
- package/src/components/chat/acp/common.tsx +12 -16
- package/src/components/chat/acp/scroll-to-bottom-button.tsx +1 -1
- package/src/components/chat/acp/session-tabs.tsx +2 -2
- package/src/components/chat/chat-history-popover.tsx +1 -1
- package/src/components/chat/chat-panel.tsx +47 -23
- package/src/components/data-table/TableBottomBar.tsx +4 -1
- package/src/components/data-table/columns.tsx +2 -2
- package/src/components/data-table/data-table.tsx +26 -17
- package/src/components/data-table/filter-pill-editor.tsx +1 -1
- package/src/components/dependency-graph/minimap-content.tsx +1 -1
- package/src/components/editor/RecoveryButton.tsx +1 -1
- package/src/components/editor/actions/pair-with-agent-modal.tsx +2 -2
- package/src/components/editor/actions/useNotebookActions.tsx +4 -4
- package/src/components/editor/ai/__tests__/completion-utils.test.ts +91 -1
- package/src/components/editor/ai/ai-completion-editor.tsx +1 -1
- package/src/components/editor/ai/completion-utils.ts +86 -1
- package/src/components/editor/cell/CreateCellButton.tsx +1 -1
- package/src/components/editor/chrome/panels/empty-state.tsx +1 -1
- package/src/components/editor/chrome/panels/outline/floating-outline.tsx +1 -1
- package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +1 -1
- package/src/components/editor/columns/cell-column.tsx +1 -1
- package/src/components/editor/columns/sortable-column.tsx +2 -2
- package/src/components/editor/output/MarimoErrorOutput.tsx +1 -1
- package/src/components/editor/output/TextOutput.tsx +2 -2
- package/src/components/home/components.tsx +4 -4
- package/src/components/icons/github.tsx +21 -0
- package/src/components/icons/youtube.tsx +21 -0
- package/src/components/slides/minimap.tsx +2 -2
- package/src/components/slides/reveal-component.tsx +1 -1
- package/src/components/storage/components.tsx +3 -7
- package/src/components/ui/alert.tsx +1 -1
- package/src/components/ui/command.tsx +2 -2
- package/src/components/ui/reorderable-list.tsx +1 -1
- package/src/components/ui/table.tsx +2 -5
- package/src/core/codemirror/go-to-definition/__tests__/commands.test.ts +67 -0
- package/src/core/codemirror/go-to-definition/__tests__/utils.test.ts +47 -0
- package/src/core/codemirror/go-to-definition/commands.ts +47 -30
- package/src/core/codemirror/go-to-definition/utils.ts +0 -1
- package/src/core/codemirror/language/languages/sql/renderers.tsx +60 -68
- package/src/core/codemirror/reactive-references/__tests__/analyzer.test.ts +54 -0
- package/src/core/codemirror/reactive-references/analyzer.ts +44 -35
- package/src/core/hotkeys/hotkeys.ts +1 -0
- package/src/core/islands/__tests__/bridge.test.ts +25 -0
- package/src/core/islands/__tests__/parse.test.ts +585 -1
- package/src/core/islands/__tests__/test-utils.tsx +10 -1
- package/src/core/islands/bridge.ts +6 -1
- package/src/core/islands/constants.ts +2 -0
- package/src/core/islands/parse.ts +293 -13
- package/src/plugins/impl/DataTablePlugin.tsx +20 -1
- package/src/plugins/impl/FileBrowserPlugin.tsx +165 -74
- package/src/plugins/impl/MatrixPlugin.tsx +2 -2
- package/src/plugins/impl/TabsPlugin.tsx +1 -1
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +141 -1
- package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +314 -0
- package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +4 -1
- package/src/plugins/impl/anywidget/__tests__/AnyWidgetPlugin.test.tsx +34 -0
- package/src/plugins/impl/anywidget/__tests__/model.test.ts +19 -0
- package/src/plugins/impl/anywidget/model.ts +15 -0
- package/src/plugins/impl/matplotlib/matplotlib-renderer.ts +1 -1
- package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +155 -98
- package/src/plugins/impl/mpl-interactive/__tests__/MplInteractivePlugin.test.tsx +154 -1
- package/src/plugins/impl/mpl-interactive/mpl-websocket-shim.ts +10 -0
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
DatabaseIcon,
|
|
10
10
|
FileIcon,
|
|
11
11
|
FileTextIcon,
|
|
12
|
-
GithubIcon,
|
|
13
12
|
GraduationCapIcon,
|
|
14
13
|
GridIcon,
|
|
15
14
|
LayoutIcon,
|
|
@@ -17,10 +16,11 @@ import {
|
|
|
17
16
|
MessagesSquareIcon,
|
|
18
17
|
OrbitIcon,
|
|
19
18
|
PackageIcon,
|
|
20
|
-
YoutubeIcon,
|
|
21
19
|
} from "lucide-react";
|
|
22
20
|
import type React from "react";
|
|
23
21
|
import { MarkdownIcon } from "@/components/editor/cell/code/icons";
|
|
22
|
+
import { GitHubIcon } from "@/components/icons/github";
|
|
23
|
+
import { YouTubeIcon } from "@/components/icons/youtube";
|
|
24
24
|
import { Button } from "@/components/ui/button";
|
|
25
25
|
import {
|
|
26
26
|
DropdownMenu,
|
|
@@ -130,7 +130,7 @@ const RESOURCES = [
|
|
|
130
130
|
{
|
|
131
131
|
title: "GitHub",
|
|
132
132
|
description: "View source code, report issues, or contribute",
|
|
133
|
-
icon:
|
|
133
|
+
icon: GitHubIcon,
|
|
134
134
|
url: Constants.githubPage,
|
|
135
135
|
},
|
|
136
136
|
{
|
|
@@ -148,7 +148,7 @@ const RESOURCES = [
|
|
|
148
148
|
{
|
|
149
149
|
title: "YouTube",
|
|
150
150
|
description: "Watch tutorials and demos",
|
|
151
|
-
icon:
|
|
151
|
+
icon: YouTubeIcon,
|
|
152
152
|
url: Constants.youtube,
|
|
153
153
|
},
|
|
154
154
|
{
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import type { SVGProps } from "react";
|
|
3
|
+
|
|
4
|
+
// Artwork from Simple Icons (https://simpleicons.org/?q=github), licensed CC0 1.0.
|
|
5
|
+
// The GitHub name and logo are trademarks of GitHub, Inc.
|
|
6
|
+
export const GitHubIcon = (props: SVGProps<SVGSVGElement>) => {
|
|
7
|
+
return (
|
|
8
|
+
<svg
|
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
10
|
+
width="1em"
|
|
11
|
+
height="1em"
|
|
12
|
+
viewBox="0 0 24 24"
|
|
13
|
+
fill="currentColor"
|
|
14
|
+
aria-hidden="true"
|
|
15
|
+
focusable="false"
|
|
16
|
+
{...props}
|
|
17
|
+
>
|
|
18
|
+
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import type { SVGProps } from "react";
|
|
3
|
+
|
|
4
|
+
// Artwork from Simple Icons (https://simpleicons.org/?q=youtube), licensed CC0 1.0.
|
|
5
|
+
// The YouTube name and logo are trademarks of Google LLC.
|
|
6
|
+
export const YouTubeIcon = (props: SVGProps<SVGSVGElement>) => {
|
|
7
|
+
return (
|
|
8
|
+
<svg
|
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
10
|
+
width="1em"
|
|
11
|
+
height="1em"
|
|
12
|
+
viewBox="0 0 24 24"
|
|
13
|
+
fill="currentColor"
|
|
14
|
+
aria-hidden="true"
|
|
15
|
+
focusable="false"
|
|
16
|
+
{...props}
|
|
17
|
+
>
|
|
18
|
+
<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
@@ -514,7 +514,7 @@ const SlideThumbnailRow = ({
|
|
|
514
514
|
tabIndex={0}
|
|
515
515
|
data-cell-id={cell.id}
|
|
516
516
|
className={cn(
|
|
517
|
-
"relative shrink-0 appearance-none text-left p-0 bg-transparent outline-
|
|
517
|
+
"relative shrink-0 appearance-none text-left p-0 bg-transparent outline-hidden",
|
|
518
518
|
className,
|
|
519
519
|
)}
|
|
520
520
|
style={rowStyle}
|
|
@@ -591,7 +591,7 @@ const InsertCellLine = ({
|
|
|
591
591
|
data-testid="minimap-insert-cell"
|
|
592
592
|
className={cn(
|
|
593
593
|
"absolute left-0 right-0 z-30 flex h-3 items-center justify-center",
|
|
594
|
-
"opacity-0 transition-opacity hover:opacity-80 focus-visible:opacity-100 focus:outline-
|
|
594
|
+
"opacity-0 transition-opacity hover:opacity-80 focus-visible:opacity-100 focus:outline-hidden",
|
|
595
595
|
position === "below"
|
|
596
596
|
? "bottom-0 translate-y-1/2"
|
|
597
597
|
: "top-0 -translate-y-1/2",
|
|
@@ -684,7 +684,7 @@ const RevealSlidesComponent = ({
|
|
|
684
684
|
<div className="group relative" style={{ width, height }}>
|
|
685
685
|
<Deck
|
|
686
686
|
deckRef={deckRef}
|
|
687
|
-
className="aspect-video w-full overflow-hidden border rounded bg-background mo-slides-theme prose-slides focus:outline-
|
|
687
|
+
className="aspect-video w-full overflow-hidden border rounded bg-background mo-slides-theme prose-slides focus:outline-hidden focus-visible:outline-hidden"
|
|
688
688
|
config={revealConfig}
|
|
689
689
|
onReady={handleDeckReady}
|
|
690
690
|
onSlideChange={handleSlideChange}
|
|
@@ -6,14 +6,10 @@ import AzureIcon from "@marimo-team/llm-info/icons/azure.svg?inline";
|
|
|
6
6
|
import CloudflareIcon from "@marimo-team/llm-info/icons/cloudflare.svg?inline";
|
|
7
7
|
import CoreweaveIcon from "@marimo-team/llm-info/icons/coreweave.svg?inline";
|
|
8
8
|
import CoreweaveDarkIcon from "@marimo-team/llm-info/icons/coreweave-dark.svg?inline";
|
|
9
|
-
import {
|
|
10
|
-
DatabaseZapIcon,
|
|
11
|
-
GithubIcon,
|
|
12
|
-
GlobeIcon,
|
|
13
|
-
HardDriveIcon,
|
|
14
|
-
} from "lucide-react";
|
|
9
|
+
import { DatabaseZapIcon, GlobeIcon, HardDriveIcon } from "lucide-react";
|
|
15
10
|
import GoogleCloudIcon from "@/components/databases/icons/google-cloud-storage.svg?inline";
|
|
16
11
|
import GoogleDriveIcon from "@/components/databases/icons/google-drive.svg?inline";
|
|
12
|
+
import { GitHubIcon } from "@/components/icons/github";
|
|
17
13
|
import type { KnownStorageProtocol } from "@/core/storage/types";
|
|
18
14
|
import { useTheme } from "@/theme/useTheme";
|
|
19
15
|
import { cn } from "@/utils/cn";
|
|
@@ -32,7 +28,7 @@ const PROTOCOL_ICONS: Record<KnownStorageProtocol, IconEntry> = {
|
|
|
32
28
|
file: HardDriveIcon,
|
|
33
29
|
"in-memory": DatabaseZapIcon,
|
|
34
30
|
gdrive: { src: GoogleDriveIcon },
|
|
35
|
-
github:
|
|
31
|
+
github: GitHubIcon,
|
|
36
32
|
};
|
|
37
33
|
|
|
38
34
|
export const ProtocolIcon: React.FC<{
|
|
@@ -6,7 +6,7 @@ import * as React from "react";
|
|
|
6
6
|
import { cn } from "@/utils/cn";
|
|
7
7
|
|
|
8
8
|
const alertVariants = cva(
|
|
9
|
-
"relative w-full rounded-lg border p-4 [&>svg]:absolute [&>svg]:text-foreground [&>svg]:left-4 [&>svg]:top-4 [&>svg+div]:translate-y-[-3px] [
|
|
9
|
+
"relative w-full rounded-lg border p-4 [&>svg]:absolute [&>svg]:text-foreground [&>svg]:left-4 [&>svg]:top-4 [&>svg+div]:translate-y-[-3px] has-[svg]:pl-11",
|
|
10
10
|
{
|
|
11
11
|
variants: {
|
|
12
12
|
variant: {
|
|
@@ -44,7 +44,7 @@ const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
|
|
44
44
|
className="overflow-hidden p-0 shadow-2xl"
|
|
45
45
|
usePortal={true}
|
|
46
46
|
>
|
|
47
|
-
<Command className="[
|
|
47
|
+
<Command className="**:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 **:[[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 **:[[cmdk-input]]:h-12 **:[[cmdk-item]]:px-2 **:[[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
|
48
48
|
{children}
|
|
49
49
|
</Command>
|
|
50
50
|
</DialogContent>
|
|
@@ -113,7 +113,7 @@ const CommandGroup = React.forwardRef<
|
|
|
113
113
|
<CommandPrimitive.Group
|
|
114
114
|
ref={ref}
|
|
115
115
|
className={cn(
|
|
116
|
-
"overflow-hidden p-1 text-foreground [
|
|
116
|
+
"overflow-hidden p-1 text-foreground **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground",
|
|
117
117
|
className,
|
|
118
118
|
)}
|
|
119
119
|
{...props}
|
|
@@ -300,7 +300,7 @@ export const ReorderableList = <T extends object>({
|
|
|
300
300
|
<ListBoxItem
|
|
301
301
|
key={getKey(item)}
|
|
302
302
|
id={getKey(item)}
|
|
303
|
-
className="active:cursor-grabbing data-
|
|
303
|
+
className="active:cursor-grabbing data-dragging:opacity-60 outline-hidden"
|
|
304
304
|
onHoverStart={
|
|
305
305
|
onItemPreloadHint ? () => onItemPreloadHint(item) : undefined
|
|
306
306
|
}
|
|
@@ -75,7 +75,7 @@ const TableHead = React.forwardRef<
|
|
|
75
75
|
<th
|
|
76
76
|
ref={ref}
|
|
77
77
|
className={cn(
|
|
78
|
-
"h-10 px-2 text-left align-middle font-medium text-muted-foreground
|
|
78
|
+
"h-10 px-2 text-left align-middle font-medium text-muted-foreground has-[[role=checkbox]]:pr-0",
|
|
79
79
|
className,
|
|
80
80
|
)}
|
|
81
81
|
{...props}
|
|
@@ -89,10 +89,7 @@ const TableCell = React.forwardRef<
|
|
|
89
89
|
>(({ className, ...props }, ref) => (
|
|
90
90
|
<td
|
|
91
91
|
ref={ref}
|
|
92
|
-
className={cn(
|
|
93
|
-
"p-1.5 align-middle [&:has([role=checkbox])]:pr-0",
|
|
94
|
-
className,
|
|
95
|
-
)}
|
|
92
|
+
className={cn("p-1.5 align-middle has-[[role=checkbox]]:pr-0", className)}
|
|
96
93
|
{...props}
|
|
97
94
|
/>
|
|
98
95
|
));
|
|
@@ -253,6 +253,73 @@ a = 10`;
|
|
|
253
253
|
`);
|
|
254
254
|
});
|
|
255
255
|
|
|
256
|
+
test("from-import alias is the binding, not the imported name", async () => {
|
|
257
|
+
const code = `\
|
|
258
|
+
from math import sin as my_sin
|
|
259
|
+
print(my_sin)`;
|
|
260
|
+
view = createEditor(code);
|
|
261
|
+
const usagePosition = code.lastIndexOf("my_sin");
|
|
262
|
+
const result = goToVariableDefinition(view, "my_sin", usagePosition);
|
|
263
|
+
|
|
264
|
+
expect(result).toBe(true);
|
|
265
|
+
await tick();
|
|
266
|
+
// The alias `my_sin` (after `as`) is the real binding.
|
|
267
|
+
expect(renderEditorView(view)).toMatchInlineSnapshot(`
|
|
268
|
+
"
|
|
269
|
+
from math import sin as my_sin
|
|
270
|
+
^
|
|
271
|
+
print(my_sin)
|
|
272
|
+
"
|
|
273
|
+
`);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("module path in from-import is not a local definition", async () => {
|
|
277
|
+
const code = `\
|
|
278
|
+
from math import sin
|
|
279
|
+
print(math)`;
|
|
280
|
+
view = createEditor(code);
|
|
281
|
+
const usagePosition = code.lastIndexOf("math");
|
|
282
|
+
// `math` is a module reference in the from-clause, not a binding in this
|
|
283
|
+
// cell, so the scoped resolver should return false and let the caller fall
|
|
284
|
+
// through to cross-cell resolution.
|
|
285
|
+
const result = goToVariableDefinition(view, "math", usagePosition);
|
|
286
|
+
|
|
287
|
+
expect(result).toBe(false);
|
|
288
|
+
expect(view.state.selection.main.head).toBe(0);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("imported name without `as` is a local definition", async () => {
|
|
292
|
+
const code = `\
|
|
293
|
+
from math import sin
|
|
294
|
+
print(sin)`;
|
|
295
|
+
view = createEditor(code);
|
|
296
|
+
const usagePosition = code.lastIndexOf("sin");
|
|
297
|
+
const result = goToVariableDefinition(view, "sin", usagePosition);
|
|
298
|
+
|
|
299
|
+
expect(result).toBe(true);
|
|
300
|
+
await tick();
|
|
301
|
+
expect(renderEditorView(view)).toMatchInlineSnapshot(`
|
|
302
|
+
"
|
|
303
|
+
from math import sin
|
|
304
|
+
^
|
|
305
|
+
print(sin)
|
|
306
|
+
"
|
|
307
|
+
`);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test("imported name shadowed by `as` is not a binding", async () => {
|
|
311
|
+
const code = `\
|
|
312
|
+
from math import sin as my_sin
|
|
313
|
+
print(sin)`;
|
|
314
|
+
view = createEditor(code);
|
|
315
|
+
const usagePosition = code.lastIndexOf("sin");
|
|
316
|
+
// `sin` here refers to nothing in this cell (it was renamed to `my_sin`),
|
|
317
|
+
// so the scoped resolver should return false.
|
|
318
|
+
const result = goToVariableDefinition(view, "sin", usagePosition);
|
|
319
|
+
|
|
320
|
+
expect(result).toBe(false);
|
|
321
|
+
});
|
|
322
|
+
|
|
256
323
|
test("selects outer-scope function declaration", async () => {
|
|
257
324
|
view = createEditor(`\
|
|
258
325
|
def x():
|
|
@@ -133,4 +133,51 @@ output = _x + 10`;
|
|
|
133
133
|
await tick();
|
|
134
134
|
expect(view.state.selection.main.head).toBe(code.indexOf("_x = 10"));
|
|
135
135
|
});
|
|
136
|
+
|
|
137
|
+
test("falls through to cross-cell when in-cell occurrence is only a module path in a from-import", async () => {
|
|
138
|
+
// Regression: ImportStatement used to register every VariableName child
|
|
139
|
+
// (the module path and pre-`as` names) as in-cell declarations, so the
|
|
140
|
+
// local-first short-circuit would steal F12 from cross-cell resolution.
|
|
141
|
+
const moduleCell = cellId("module-cell");
|
|
142
|
+
const usageCell = cellId("usage-cell");
|
|
143
|
+
const moduleCode = `mymodule = 100`;
|
|
144
|
+
const usageCode = `\
|
|
145
|
+
from mymodule import something
|
|
146
|
+
print(mymodule)`;
|
|
147
|
+
|
|
148
|
+
const moduleView = createEditor(moduleCode, moduleCode.length);
|
|
149
|
+
const usageView = createEditor(
|
|
150
|
+
usageCode,
|
|
151
|
+
usageCode.lastIndexOf("mymodule"),
|
|
152
|
+
);
|
|
153
|
+
views.push(moduleView, usageView);
|
|
154
|
+
|
|
155
|
+
const notebook = initialNotebookState();
|
|
156
|
+
notebook.cellHandles[moduleCell] = {
|
|
157
|
+
current: { editorView: moduleView, editorViewOrNull: moduleView },
|
|
158
|
+
} as never;
|
|
159
|
+
notebook.cellHandles[usageCell] = {
|
|
160
|
+
current: { editorView: usageView, editorViewOrNull: usageView },
|
|
161
|
+
} as never;
|
|
162
|
+
|
|
163
|
+
store.set(notebookAtom, notebook);
|
|
164
|
+
store.set(variablesAtom, {
|
|
165
|
+
[variableName("mymodule")]: {
|
|
166
|
+
dataType: "int",
|
|
167
|
+
declaredBy: [moduleCell],
|
|
168
|
+
name: variableName("mymodule"),
|
|
169
|
+
usedBy: [usageCell],
|
|
170
|
+
value: "100",
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const result = goToDefinitionAtCursorPosition(usageView);
|
|
175
|
+
|
|
176
|
+
expect(result).toBe(true);
|
|
177
|
+
await tick();
|
|
178
|
+
// Cross-cell jump: moduleView's cursor should land on `mymodule = 100`.
|
|
179
|
+
expect(moduleView.state.selection.main.head).toBe(
|
|
180
|
+
moduleCode.indexOf("mymodule"),
|
|
181
|
+
);
|
|
182
|
+
});
|
|
136
183
|
});
|
|
@@ -307,33 +307,52 @@ function collectMatchingDeclarations(
|
|
|
307
307
|
break;
|
|
308
308
|
}
|
|
309
309
|
case "ImportStatement": {
|
|
310
|
+
// The grammar emits one ImportStatement for both `import x [as y]` and
|
|
311
|
+
// `from m import x [as y], ...`. Direct children include the keywords
|
|
312
|
+
// (`from`/`import`/`as`), commas, dots, and every VariableName from the
|
|
313
|
+
// module path AND the import list. We only want the names that actually
|
|
314
|
+
// bind in the current scope: the post-`as` alias if present, otherwise
|
|
315
|
+
// the imported name itself. Names before `import` (the from-path) and
|
|
316
|
+
// the original name when an alias follows it are NOT bindings.
|
|
310
317
|
const subCursor = node.cursor();
|
|
311
318
|
subCursor.firstChild();
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
319
|
+
let pastImport = false;
|
|
320
|
+
// Buffer the most recent post-`import` VariableName so we can defer
|
|
321
|
+
// committing it until we know whether `as` follows.
|
|
322
|
+
let pending: { from: number; matches: boolean } | null = null;
|
|
323
|
+
const commit = () => {
|
|
324
|
+
if (pending?.matches) {
|
|
325
|
+
addDeclaration(declarations, currentScope, pending.from);
|
|
318
326
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
case "ImportFromStatement": {
|
|
323
|
-
const subCursor = node.cursor();
|
|
324
|
-
subCursor.firstChild();
|
|
325
|
-
let foundImport = false;
|
|
327
|
+
pending = null;
|
|
328
|
+
};
|
|
326
329
|
do {
|
|
327
330
|
if (subCursor.name === "import") {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
331
|
+
pastImport = true;
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (!pastImport) {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (subCursor.name === "as") {
|
|
338
|
+
// Next VariableName is the alias and replaces `pending`.
|
|
339
|
+
pending = null;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (subCursor.name === "VariableName") {
|
|
343
|
+
// Flush any previous pending name (no `as` followed it).
|
|
344
|
+
commit();
|
|
345
|
+
pending = {
|
|
346
|
+
from: subCursor.from,
|
|
347
|
+
matches:
|
|
348
|
+
state.doc.sliceString(subCursor.from, subCursor.to) ===
|
|
349
|
+
variableName,
|
|
350
|
+
};
|
|
351
|
+
} else if (subCursor.name === ",") {
|
|
352
|
+
commit();
|
|
335
353
|
}
|
|
336
354
|
} while (subCursor.nextSibling());
|
|
355
|
+
commit();
|
|
337
356
|
break;
|
|
338
357
|
}
|
|
339
358
|
case "TryStatement":
|
|
@@ -410,23 +429,21 @@ function findScopedDefinitionPosition(
|
|
|
410
429
|
* @param view The editor view which contains the variable name.
|
|
411
430
|
* @param variableName The name of the variable to select, if found in the editor.
|
|
412
431
|
* @param usagePosition The position of the variable usage, if available.
|
|
413
|
-
* @param fallbackToFirstMatch Whether to fall back to the first matching
|
|
414
|
-
* variable name when no scoped definition is found. Defaults to true.
|
|
415
432
|
*/
|
|
416
433
|
export function goToVariableDefinition(
|
|
417
434
|
view: EditorView,
|
|
418
435
|
variableName: string,
|
|
419
436
|
usagePosition?: number,
|
|
420
|
-
fallbackToFirstMatch = true,
|
|
421
437
|
): boolean {
|
|
422
438
|
const { state } = view;
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
439
|
+
// When the caller knows the usage position, trust the scoped lookup. Falling
|
|
440
|
+
// back to first-match would defeat the local-vs-cross-cell decision in
|
|
441
|
+
// goToDefinition: if the symbol only appears as a module path in an import,
|
|
442
|
+
// scoped resolution returns null and we want the caller to try other cells.
|
|
443
|
+
const from =
|
|
444
|
+
usagePosition !== undefined
|
|
445
|
+
? findScopedDefinitionPosition(state, variableName, usagePosition)
|
|
446
|
+
: findFirstMatchingVariable(state, variableName);
|
|
430
447
|
|
|
431
448
|
if (from === null) {
|
|
432
449
|
return false;
|