| ... | @@ -90,6 +90,13 @@ import { usePreparedConnection } from "../state/session"; | ... | @@ -90,6 +90,13 @@ import { usePreparedConnection } from "../state/session"; |
| 90 | import { previewEnvironment } from "../state/preview"; | 90 | import { previewEnvironment } from "../state/preview"; |
| 91 | import { useAtomCommand } from "../state/use-atom-command"; | 91 | import { useAtomCommand } from "../state/use-atom-command"; |
| 92 | import { useAtomQueryRunner } from "../state/use-atom-query-runner"; | 92 | import { useAtomQueryRunner } from "../state/use-atom-query-runner"; |
| | 93 | import { projectEnvironment } from "../state/projects"; |
| | 94 | import { |
| | 95 | claimWorkspaceBasenameLookup, |
| | 96 | needsWorkspaceBasenameLookup, |
| | 97 | pickWorkspaceBasenameMatch, |
| | 98 | WORKSPACE_BASENAME_LOOKUP_LIMIT, |
| | 99 | } from "../workspaceBasenameLookup"; |
| 93 | import { useOpenChangeRequestLink } from "~/lib/openPullRequestLink"; | 100 | import { useOpenChangeRequestLink } from "~/lib/openPullRequestLink"; |
| 94 | import { writeTextToClipboard } from "../hooks/useCopyToClipboard"; | 101 | import { writeTextToClipboard } from "../hooks/useCopyToClipboard"; |
| 95 | import { isPreviewSupportedInRuntime } from "../previewStateStore"; | 102 | import { isPreviewSupportedInRuntime } from "../previewStateStore"; |
| ... | @@ -811,6 +818,7 @@ interface MarkdownFileLinkProps { | ... | @@ -811,6 +818,7 @@ interface MarkdownFileLinkProps { |
| 811 | theme: "light" | "dark"; | 818 | theme: "light" | "dark"; |
| 812 | threadRef?: ScopedThreadRef | undefined; | 819 | threadRef?: ScopedThreadRef | undefined; |
| 813 | onOpen: (targetPath: string) => Promise<AtomCommandResult<unknown, unknown>>; | 820 | onOpen: (targetPath: string) => Promise<AtomCommandResult<unknown, unknown>>; |
| | 821 | onOpenInPanel: (workspaceRelativePath: string, line: number | undefined) => void; |
| 814 | onOpenInBrowser?: (() => Promise<AtomCommandResult<unknown, unknown>>) | undefined; | 822 | onOpenInBrowser?: (() => Promise<AtomCommandResult<unknown, unknown>>) | undefined; |
| 815 | className?: string | undefined; | 823 | className?: string | undefined; |
| 816 | } | 824 | } |
| ... | @@ -1116,6 +1124,7 @@ const MarkdownFileLink = memo(function MarkdownFileLink({ | ... | @@ -1116,6 +1124,7 @@ const MarkdownFileLink = memo(function MarkdownFileLink({ |
| 1116 | theme, | 1124 | theme, |
| 1117 | threadRef, | 1125 | threadRef, |
| 1118 | onOpen, | 1126 | onOpen, |
| | 1127 | onOpenInPanel, |
| 1119 | onOpenInBrowser, | 1128 | onOpenInBrowser, |
| 1120 | className, | 1129 | className, |
| 1121 | }: MarkdownFileLinkProps) { | 1130 | }: MarkdownFileLinkProps) { |
| ... | @@ -1159,8 +1168,8 @@ const MarkdownFileLink = memo(function MarkdownFileLink({ | ... | @@ -1159,8 +1168,8 @@ const MarkdownFileLink = memo(function MarkdownFileLink({ |
| 1159 | handleOpenInEditor(); | 1168 | handleOpenInEditor(); |
| 1160 | return; | 1169 | return; |
| 1161 | } | 1170 | } |
| 1162 | useRightPanelStore.getState().openFile(threadRef, workspaceRelativePath, line); | 1171 | onOpenInPanel(workspaceRelativePath, line); |
| 1163 | }, [handleOpenInEditor, line, threadRef, workspaceRelativePath]); | 1172 | }, [handleOpenInEditor, line, onOpenInPanel, threadRef, workspaceRelativePath]); |
| 1164 | | 1173 | |
| 1165 | const handleOpenInBrowser = useCallback(() => { | 1174 | const handleOpenInBrowser = useCallback(() => { |
| 1166 | if (!onOpenInBrowser) { | 1175 | if (!onOpenInBrowser) { |
| ... | @@ -1336,6 +1345,7 @@ function areMarkdownFileLinkPropsEqual( | ... | @@ -1336,6 +1345,7 @@ function areMarkdownFileLinkPropsEqual( |
| 1336 | previous.theme === next.theme && | 1345 | previous.theme === next.theme && |
| 1337 | previous.threadRef === next.threadRef && | 1346 | previous.threadRef === next.threadRef && |
| 1338 | previous.onOpen === next.onOpen && | 1347 | previous.onOpen === next.onOpen && |
| | 1348 | previous.onOpenInPanel === next.onOpenInPanel && |
| 1339 | previous.onOpenInBrowser === next.onOpenInBrowser && | 1349 | previous.onOpenInBrowser === next.onOpenInBrowser && |
| 1340 | previous.className === next.className | 1350 | previous.className === next.className |
| 1341 | ); | 1351 | ); |
| ... | @@ -1355,6 +1365,9 @@ function ChatMarkdown({ | ... | @@ -1355,6 +1365,9 @@ function ChatMarkdown({ |
| 1355 | const createAssetUrl = useAtomQueryRunner(assetEnvironment.createUrl, { | 1365 | const createAssetUrl = useAtomQueryRunner(assetEnvironment.createUrl, { |
| 1356 | reportFailure: false, | 1366 | reportFailure: false, |
| 1357 | }); | 1367 | }); |
| | 1368 | const searchProjectEntries = useAtomQueryRunner(projectEnvironment.searchEntries, { |
| | 1369 | reportFailure: false, |
| | 1370 | }); |
| 1358 | const openPreview = useAtomCommand(previewEnvironment.open, { | 1371 | const openPreview = useAtomCommand(previewEnvironment.open, { |
| 1359 | reportFailure: false, | 1372 | reportFailure: false, |
| 1360 | }); | 1373 | }); |
| ... | @@ -1457,6 +1470,40 @@ function ChatMarkdown({ | ... | @@ -1457,6 +1470,40 @@ function ChatMarkdown({ |
| 1457 | }, | 1470 | }, |
| 1458 | [createAssetUrl, openPreview, preparedConnection, threadRef], | 1471 | [createAssetUrl, openPreview, preparedConnection, threadRef], |
| 1459 | ); | 1472 | ); |
| | 1473 | // A bare filename resolves to the workspace root, which is rarely where the |
| | 1474 | // file is, so ask the index before opening. |
| | 1475 | const openFileInPanel = useCallback( |
| | 1476 | (workspaceRelativePath: string, line: number | undefined) => { |
| | 1477 | if (!threadRef) return; |
| | 1478 | // Claimed on every open so a synchronous one supersedes a lookup already |
| | 1479 | // in flight. |
| | 1480 | const isLatestLookup = claimWorkspaceBasenameLookup(); |
| | 1481 | const openAt = (path: string) => |
| | 1482 | useRightPanelStore.getState().openFile(threadRef, path, line); |
| | 1483 | if (!cwd || !needsWorkspaceBasenameLookup(workspaceRelativePath)) { |
| | 1484 | openAt(workspaceRelativePath); |
| | 1485 | return; |
| | 1486 | } |
| | 1487 | void (async () => { |
| | 1488 | const result = await searchProjectEntries({ |
| | 1489 | environmentId: threadRef.environmentId, |
| | 1490 | input: { |
| | 1491 | cwd, |
| | 1492 | query: workspaceRelativePath, |
| | 1493 | limit: WORKSPACE_BASENAME_LOOKUP_LIMIT, |
| | 1494 | kind: "file", |
| | 1495 | }, |
| | 1496 | }); |
| | 1497 | const match = |
| | 1498 | result._tag === "Success" |
| | 1499 | ? pickWorkspaceBasenameMatch(workspaceRelativePath, result.value.entries) |
| | 1500 | : null; |
| | 1501 | if (!isLatestLookup()) return; |
| | 1502 | openAt(match ?? workspaceRelativePath); |
| | 1503 | })(); |
| | 1504 | }, |
| | 1505 | [cwd, searchProjectEntries, threadRef], |
| | 1506 | ); |
| 1460 | /* eslint-disable react/no-unstable-nested-components -- ReactMarkdown requires component | 1507 | /* eslint-disable react/no-unstable-nested-components -- ReactMarkdown requires component |
| 1461 | * renderers that close over this message's metadata. useMemo keeps them stable until that | 1508 | * renderers that close over this message's metadata. useMemo keeps them stable until that |
| 1462 | * metadata changes. */ | 1509 | * metadata changes. */ |
| ... | @@ -1490,6 +1537,7 @@ function ChatMarkdown({ | ... | @@ -1490,6 +1537,7 @@ function ChatMarkdown({ |
| 1490 | theme={resolvedTheme} | 1537 | theme={resolvedTheme} |
| 1491 | threadRef={threadRef} | 1538 | threadRef={threadRef} |
| 1492 | onOpen={openInPreferredEditor} | 1539 | onOpen={openInPreferredEditor} |
| | 1540 | onOpenInPanel={openFileInPanel} |
| 1493 | onOpenInBrowser={ | 1541 | onOpenInBrowser={ |
| 1494 | threadRef && | 1542 | threadRef && |
| 1495 | isPreviewSupportedInRuntime() && | 1543 | isPreviewSupportedInRuntime() && |
| ... | @@ -1718,6 +1766,7 @@ function ChatMarkdown({ | ... | @@ -1718,6 +1766,7 @@ function ChatMarkdown({ |
| 1718 | isStreaming, | 1766 | isStreaming, |
| 1719 | markdownFileLinkMetaByHref, | 1767 | markdownFileLinkMetaByHref, |
| 1720 | onTaskListChange, | 1768 | onTaskListChange, |
| | 1769 | openFileInPanel, |
| 1721 | openInPreferredEditor, | 1770 | openInPreferredEditor, |
| 1722 | openExternalLinkInPreview, | 1771 | openExternalLinkInPreview, |
| 1723 | openMarkdownFileInPreview, | 1772 | openMarkdownFileInPreview, |