diff --git a/packages/web/src/app/[domain]/browse/[...path]/components/pureCodePreviewPanel.tsx b/packages/web/src/app/[domain]/browse/[...path]/components/pureCodePreviewPanel.tsx index bdf6f878..71cf1929 100644 --- a/packages/web/src/app/[domain]/browse/[...path]/components/pureCodePreviewPanel.tsx +++ b/packages/web/src/app/[domain]/browse/[...path]/components/pureCodePreviewPanel.tsx @@ -3,7 +3,6 @@ import { ScrollArea } from "@/components/ui/scroll-area"; import { SymbolHoverPopup } from "@/ee/features/codeNav/components/symbolHoverPopup"; import { symbolHoverTargetsExtension } from "@/ee/features/codeNav/components/symbolHoverPopup/symbolHoverTargetsExtension"; -import { SymbolDefinition } from "@/ee/features/codeNav/components/symbolHoverPopup/useHoveredOverSymbolInfo"; import { useHasEntitlement } from "@/features/entitlements/useHasEntitlement"; import { useCodeMirrorLanguageExtension } from "@/hooks/useCodeMirrorLanguageExtension"; import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme"; @@ -11,14 +10,10 @@ import { useKeymapExtension } from "@/hooks/useKeymapExtension"; import { useNonEmptyQueryParam } from "@/hooks/useNonEmptyQueryParam"; import { search } from "@codemirror/search"; import CodeMirror, { EditorSelection, EditorView, ReactCodeMirrorRef, SelectionRange, ViewUpdate } from "@uiw/react-codemirror"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { EditorContextMenu } from "../../../components/editorContextMenu"; -import { useBrowseNavigation } from "../../hooks/useBrowseNavigation"; import { BrowseHighlightRange, HIGHLIGHT_RANGE_QUERY_PARAM } from "../../hooks/utils"; -import { useBrowseState } from "../../hooks/useBrowseState"; import { rangeHighlightingExtension } from "./rangeHighlightingExtension"; -import useCaptureEvent from "@/hooks/useCaptureEvent"; -import { createAuditAction } from "@/ee/features/audit/actions"; interface PureCodePreviewPanelProps { path: string; @@ -40,9 +35,6 @@ export const PureCodePreviewPanel = ({ const [currentSelection, setCurrentSelection] = useState(); const keymapExtension = useKeymapExtension(editorRef?.view); const hasCodeNavEntitlement = useHasEntitlement("code-nav"); - const { updateBrowseState } = useBrowseState(); - const { navigateToPath } = useBrowseNavigation(); - const captureEvent = useCaptureEvent(); const highlightRangeQuery = useNonEmptyQueryParam(HIGHLIGHT_RANGE_QUERY_PARAM); const highlightRange = useMemo((): BrowseHighlightRange | undefined => { @@ -134,72 +126,6 @@ export const PureCodePreviewPanel = ({ }); }, [editorRef, highlightRange]); - const onFindReferences = useCallback((symbolName: string) => { - captureEvent('wa_find_references_pressed', { - source: 'browse', - }); - createAuditAction({ - action: "user.performed_find_references", - metadata: { - message: symbolName, - }, - }) - - updateBrowseState({ - selectedSymbolInfo: { - repoName, - symbolName, - revisionName, - language, - }, - isBottomPanelCollapsed: false, - activeExploreMenuTab: "references", - }) - }, [captureEvent, updateBrowseState, repoName, revisionName, language]); - - - // If we resolve multiple matches, instead of navigating to the first match, we should - // instead popup the bottom sheet with the list of matches. - const onGotoDefinition = useCallback((symbolName: string, symbolDefinitions: SymbolDefinition[]) => { - captureEvent('wa_goto_definition_pressed', { - source: 'browse', - }); - createAuditAction({ - action: "user.performed_goto_definition", - metadata: { - message: symbolName, - }, - }) - - if (symbolDefinitions.length === 0) { - return; - } - - if (symbolDefinitions.length === 1) { - const symbolDefinition = symbolDefinitions[0]; - const { fileName, repoName } = symbolDefinition; - - navigateToPath({ - repoName, - revisionName, - path: fileName, - pathType: 'blob', - highlightRange: symbolDefinition.range, - }) - } else { - updateBrowseState({ - selectedSymbolInfo: { - symbolName, - repoName, - revisionName, - language, - }, - activeExploreMenuTab: "definitions", - isBottomPanelCollapsed: false, - }) - } - }, [captureEvent, navigateToPath, revisionName, updateBrowseState, repoName, language]); - const theme = useCodeMirrorTheme(); return ( @@ -223,11 +149,12 @@ export const PureCodePreviewPanel = ({ )} {editorRef && hasCodeNavEntitlement && ( )} diff --git a/packages/web/src/app/[domain]/search/components/codePreviewPanel/codePreview.tsx b/packages/web/src/app/[domain]/search/components/codePreviewPanel/codePreview.tsx index d500bbe9..264be592 100644 --- a/packages/web/src/app/[domain]/search/components/codePreviewPanel/codePreview.tsx +++ b/packages/web/src/app/[domain]/search/components/codePreviewPanel/codePreview.tsx @@ -1,12 +1,16 @@ 'use client'; +import { useBrowseNavigation } from "@/app/[domain]/browse/hooks/useBrowseNavigation"; import { EditorContextMenu } from "@/app/[domain]/components/editorContextMenu"; import { Button } from "@/components/ui/button"; import { ScrollArea } from "@/components/ui/scroll-area"; +import { SymbolHoverPopup } from "@/ee/features/codeNav/components/symbolHoverPopup"; +import { symbolHoverTargetsExtension } from "@/ee/features/codeNav/components/symbolHoverPopup/symbolHoverTargetsExtension"; +import { useHasEntitlement } from "@/features/entitlements/useHasEntitlement"; import { SearchResultChunk } from "@/features/search"; +import { useCodeMirrorLanguageExtension } from "@/hooks/useCodeMirrorLanguageExtension"; import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme"; import { useKeymapExtension } from "@/hooks/useKeymapExtension"; -import { useCodeMirrorLanguageExtension } from "@/hooks/useCodeMirrorLanguageExtension"; import { gutterWidthExtension } from "@/lib/extensions/gutterWidthExtension"; import { highlightRanges, searchResultHighlightExtension } from "@/lib/extensions/searchResultHighlightExtension"; import { search } from "@codemirror/search"; @@ -16,13 +20,6 @@ import { Scrollbar } from "@radix-ui/react-scroll-area"; import CodeMirror, { ReactCodeMirrorRef, SelectionRange } from '@uiw/react-codemirror'; import { ArrowDown, ArrowUp } from "lucide-react"; import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react"; -import { useBrowseNavigation } from "@/app/[domain]/browse/hooks/useBrowseNavigation"; -import { SymbolHoverPopup } from "@/ee/features/codeNav/components/symbolHoverPopup"; -import { symbolHoverTargetsExtension } from "@/ee/features/codeNav/components/symbolHoverPopup/symbolHoverTargetsExtension"; -import { useHasEntitlement } from "@/features/entitlements/useHasEntitlement"; -import { SymbolDefinition } from "@/ee/features/codeNav/components/symbolHoverPopup/useHoveredOverSymbolInfo"; -import { createAuditAction } from "@/ee/features/audit/actions"; -import useCaptureEvent from "@/hooks/useCaptureEvent"; export interface CodePreviewFile { content: string; @@ -59,8 +56,6 @@ export const CodePreview = ({ const languageExtension = useCodeMirrorLanguageExtension(file?.language ?? '', editorRef?.view); const [currentSelection, setCurrentSelection] = useState(); - const captureEvent = useCaptureEvent(); - const extensions = useMemo(() => { return [ keymapExtension, @@ -115,81 +110,6 @@ export const CodePreview = ({ onSelectedMatchIndexChange((prev) => prev + 1); }, [onSelectedMatchIndexChange]); - const onGotoDefinition = useCallback((symbolName: string, symbolDefinitions: SymbolDefinition[]) => { - captureEvent('wa_goto_definition_pressed', { - source: 'preview', - }); - createAuditAction({ - action: "user.performed_goto_definition", - metadata: { - message: symbolName, - }, - }) - - if (symbolDefinitions.length === 0) { - return; - } - - if (symbolDefinitions.length === 1) { - const symbolDefinition = symbolDefinitions[0]; - const { fileName, repoName } = symbolDefinition; - - navigateToPath({ - repoName, - revisionName: file.revision, - path: fileName, - pathType: 'blob', - highlightRange: symbolDefinition.range, - }) - } else { - navigateToPath({ - repoName, - revisionName: file.revision, - path: file.filepath, - pathType: 'blob', - setBrowseState: { - selectedSymbolInfo: { - symbolName, - repoName, - revisionName: file.revision, - language: file.language, - }, - activeExploreMenuTab: "definitions", - isBottomPanelCollapsed: false, - } - }); - } - }, [captureEvent, file.filepath, file.language, file.revision, navigateToPath, repoName]); - - const onFindReferences = useCallback((symbolName: string) => { - captureEvent('wa_find_references_pressed', { - source: 'preview', - }); - createAuditAction({ - action: "user.performed_find_references", - metadata: { - message: symbolName, - }, - }) - - navigateToPath({ - repoName, - revisionName: file.revision, - path: file.filepath, - pathType: 'blob', - setBrowseState: { - selectedSymbolInfo: { - repoName, - symbolName, - revisionName: file.revision, - language: file.language, - }, - activeExploreMenuTab: "references", - isBottomPanelCollapsed: false, - } - }) - }, [captureEvent, file.filepath, file.language, file.revision, navigateToPath, repoName]); - return (
@@ -286,11 +206,12 @@ export const CodePreview = ({ {editorRef && hasCodeNavEntitlement && ( )} diff --git a/packages/web/src/ee/features/codeNav/components/exploreMenu/index.tsx b/packages/web/src/ee/features/codeNav/components/exploreMenu/index.tsx index 3775c320..00cfc8e1 100644 --- a/packages/web/src/ee/features/codeNav/components/exploreMenu/index.tsx +++ b/packages/web/src/ee/features/codeNav/components/exploreMenu/index.tsx @@ -151,7 +151,7 @@ export const ExploreMenu = ({ - {isGlobalSearchEnabled ? "Search in current repository only" : "Search all repositories"} + Search all repositories void; - onGotoDefinition: (symbolName: string, symbolDefinitions: SymbolDefinition[]) => void; + repoName: string; + fileName: string; + source: 'browse' | 'preview' | 'chat'; } export const SymbolHoverPopup: React.FC = ({ editorRef, revisionName, language, - onFindReferences, - onGotoDefinition: _onGotoDefinition, + repoName, + fileName, + source, }) => { const ref = useRef(null); const [isSticky, setIsSticky] = useState(false); const { toast } = useToast(); + const { navigateToPath } = useBrowseNavigation(); + const captureEvent = useCaptureEvent(); const symbolInfo = useHoveredOverSymbolInfo({ editorRef, isSticky, revisionName, language, + repoName, }); // Positions the popup relative to the symbol @@ -77,13 +85,121 @@ export const SymbolHoverPopup: React.FC = ({ } }, [symbolInfo, editorRef]); + // Multiple symbol definitions can exist for the same symbol, but we can only navigate + // and display a preview of one. If the symbol definition exists in the current file, + // then we use that one, otherwise we fallback to the first definition in the list. + const previewedSymbolDefinition = useMemo(() => { + if (!symbolInfo?.symbolDefinitions || symbolInfo.symbolDefinitions.length === 0) { + return undefined; + } + + const matchingDefinition = symbolInfo.symbolDefinitions.find( + (definition) => ( + definition.fileName === fileName && definition.repoName === repoName + ) + ); + + if (matchingDefinition) { + return matchingDefinition; + } + + return symbolInfo.symbolDefinitions[0]; + }, [fileName, repoName, symbolInfo?.symbolDefinitions]); + const onGotoDefinition = useCallback(() => { - if (!symbolInfo || !symbolInfo.symbolDefinitions) { + if ( + !symbolInfo || + !symbolInfo.symbolDefinitions || + !previewedSymbolDefinition + ) { return; } - _onGotoDefinition(symbolInfo.symbolName, symbolInfo.symbolDefinitions); - }, [symbolInfo, _onGotoDefinition]); + captureEvent('wa_goto_definition_pressed', { + source, + }); + + createAuditAction({ + action: "user.performed_goto_definition", + metadata: { + message: symbolInfo.symbolName, + }, + }); + + const { + fileName, + repoName, + revisionName, + language, + range: highlightRange, + } = previewedSymbolDefinition; + + navigateToPath({ + // Always navigate to the preview symbol definition. + repoName, + revisionName, + path: fileName, + pathType: 'blob', + highlightRange, + // If there are multiple definitions, we should open the Explore panel with the definitions. + ...(symbolInfo.symbolDefinitions.length > 1 ? { + setBrowseState: { + selectedSymbolInfo: { + symbolName: symbolInfo.symbolName, + repoName, + revisionName, + language, + }, + activeExploreMenuTab: "definitions", + isBottomPanelCollapsed: false, + } + } : {}), + }); + }, [ + captureEvent, + previewedSymbolDefinition, + navigateToPath, + source, + symbolInfo + ]); + + const onFindReferences = useCallback((symbolName: string) => { + captureEvent('wa_find_references_pressed', { + source, + }); + + createAuditAction({ + action: "user.performed_find_references", + metadata: { + message: symbolName, + }, + }) + + navigateToPath({ + repoName, + revisionName, + path: fileName, + pathType: 'blob', + setBrowseState: { + selectedSymbolInfo: { + symbolName, + repoName, + revisionName, + language, + }, + activeExploreMenuTab: "references", + isBottomPanelCollapsed: false, + } + }) + }, [ + captureEvent, + fileName, + language, + navigateToPath, + repoName, + revisionName, + source + ]); // @todo: We should probably make the behaviour s.t., the ctrl / cmd key needs to be held // down to navigate to the definition. We should also only show the underline when the key @@ -147,9 +263,9 @@ export const SymbolHoverPopup: React.FC = ({ Loading...
- ) : symbolInfo.symbolDefinitions && symbolInfo.symbolDefinitions.length > 0 ? ( + ) : previewedSymbolDefinition ? ( ) : (

No hover info found

@@ -160,13 +276,13 @@ export const SymbolHoverPopup: React.FC = ({ { - !symbolInfo.isSymbolDefinitionsLoading && (!symbolInfo.symbolDefinitions || symbolInfo.symbolDefinitions.length === 0) ? + !symbolInfo.isSymbolDefinitionsLoading && !previewedSymbolDefinition ? "No definition found" : `Go to ${symbolInfo.symbolDefinitions && symbolInfo.symbolDefinitions.length > 1 ? "definitions" : "definition"}` } diff --git a/packages/web/src/ee/features/codeNav/components/symbolHoverPopup/useHoveredOverSymbolInfo.ts b/packages/web/src/ee/features/codeNav/components/symbolHoverPopup/useHoveredOverSymbolInfo.ts index b8336a93..a537fe53 100644 --- a/packages/web/src/ee/features/codeNav/components/symbolHoverPopup/useHoveredOverSymbolInfo.ts +++ b/packages/web/src/ee/features/codeNav/components/symbolHoverPopup/useHoveredOverSymbolInfo.ts @@ -12,6 +12,7 @@ interface UseHoveredOverSymbolInfoProps { isSticky: boolean; revisionName: string; language: string; + repoName: string; } export type SymbolDefinition = { @@ -19,6 +20,7 @@ export type SymbolDefinition = { language: string; fileName: string; repoName: string; + revisionName: string; range: SourceRange; } @@ -37,6 +39,7 @@ export const useHoveredOverSymbolInfo = ({ isSticky, revisionName, language, + repoName, }: UseHoveredOverSymbolInfoProps): HoveredOverSymbolInfo | undefined => { const mouseOverTimerRef = useRef(null); const mouseOutTimerRef = useRef(null); @@ -50,12 +53,13 @@ export const useHoveredOverSymbolInfo = ({ }, [symbolElement]); const { data: symbolDefinitions, isLoading: isSymbolDefinitionsLoading } = useQuery({ - queryKey: ["definitions", symbolName, revisionName, language, domain], + queryKey: ["definitions", symbolName, revisionName, language, domain, repoName], queryFn: () => unwrapServiceError( findSearchBasedSymbolDefinitions({ symbolName: symbolName!, language, revisionName, + repoName, }) ), select: ((data) => { @@ -66,6 +70,7 @@ export const useHoveredOverSymbolInfo = ({ language: file.language, fileName: file.fileName, repoName: file.repository, + revisionName: revisionName, range: match.range, } }) diff --git a/packages/web/src/features/chat/components/chatThread/referencedFileSourceListItem.tsx b/packages/web/src/features/chat/components/chatThread/referencedFileSourceListItem.tsx index ba6201d4..4d8b641d 100644 --- a/packages/web/src/features/chat/components/chatThread/referencedFileSourceListItem.tsx +++ b/packages/web/src/features/chat/components/chatThread/referencedFileSourceListItem.tsx @@ -1,10 +1,8 @@ 'use client'; -import { useBrowseNavigation } from "@/app/[domain]/browse/hooks/useBrowseNavigation"; import { PathHeader } from "@/app/[domain]/components/pathHeader"; import { SymbolHoverPopup } from '@/ee/features/codeNav/components/symbolHoverPopup'; import { symbolHoverTargetsExtension } from "@/ee/features/codeNav/components/symbolHoverPopup/symbolHoverTargetsExtension"; -import { SymbolDefinition } from '@/ee/features/codeNav/components/symbolHoverPopup/useHoveredOverSymbolInfo'; import { useHasEntitlement } from "@/features/entitlements/useHasEntitlement"; import { useCodeMirrorLanguageExtension } from "@/hooks/useCodeMirrorLanguageExtension"; import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme"; @@ -12,15 +10,13 @@ import { useKeymapExtension } from "@/hooks/useKeymapExtension"; import { cn } from "@/lib/utils"; import { Range } from "@codemirror/state"; import { Decoration, DecorationSet, EditorView } from '@codemirror/view'; +import { CodeHostType } from "@sourcebot/db"; import CodeMirror, { ReactCodeMirrorRef, StateField } from '@uiw/react-codemirror'; +import isEqual from "fast-deep-equal/react"; import { ChevronDown, ChevronRight } from "lucide-react"; import { forwardRef, memo, Ref, useCallback, useImperativeHandle, useMemo, useState } from "react"; import { FileReference } from "../../types"; import { createCodeFoldingExtension } from "./codeFoldingExtension"; -import useCaptureEvent from "@/hooks/useCaptureEvent"; -import { CodeHostType } from "@sourcebot/db"; -import { createAuditAction } from "@/ee/features/audit/actions"; -import isEqual from "fast-deep-equal/react"; const lineDecoration = Decoration.line({ attributes: { class: "cm-range-border-radius chat-lineHighlight" }, @@ -74,7 +70,6 @@ const ReferencedFileSourceListItem = ({ }: ReferencedFileSourceListItemProps, forwardedRef: Ref) => { const theme = useCodeMirrorTheme(); const [editorRef, setEditorRef] = useState(null); - const captureEvent = useCaptureEvent(); useImperativeHandle( forwardedRef, @@ -84,7 +79,6 @@ const ReferencedFileSourceListItem = ({ const hasCodeNavEntitlement = useHasEntitlement("code-nav"); const languageExtension = useCodeMirrorLanguageExtension(language, editorRef?.view); - const { navigateToPath } = useBrowseNavigation(); const getReferenceAtPos = useCallback((x: number, y: number, view: EditorView): FileReference | undefined => { const pos = view.posAtCoords({ x, y }); @@ -217,83 +211,6 @@ const ReferencedFileSourceListItem = ({ codeFoldingExtension, ]); - const onGotoDefinition = useCallback((symbolName: string, symbolDefinitions: SymbolDefinition[]) => { - if (symbolDefinitions.length === 0) { - return; - } - - captureEvent('wa_goto_definition_pressed', { - source: 'chat', - }); - createAuditAction({ - action: "user.performed_goto_definition", - metadata: { - message: symbolName, - }, - }); - - if (symbolDefinitions.length === 1) { - const symbolDefinition = symbolDefinitions[0]; - const { fileName, repoName } = symbolDefinition; - - navigateToPath({ - repoName, - revisionName: revision, - path: fileName, - pathType: 'blob', - highlightRange: symbolDefinition.range, - }) - } else { - navigateToPath({ - repoName, - revisionName: revision, - path: fileName, - pathType: 'blob', - setBrowseState: { - selectedSymbolInfo: { - symbolName, - repoName, - revisionName: revision, - language: language, - }, - activeExploreMenuTab: "definitions", - isBottomPanelCollapsed: false, - } - }); - - } - }, [captureEvent, navigateToPath, revision, repoName, fileName, language]); - - const onFindReferences = useCallback((symbolName: string) => { - captureEvent('wa_find_references_pressed', { - source: 'chat', - }); - createAuditAction({ - action: "user.performed_find_references", - metadata: { - message: symbolName, - }, - }); - - navigateToPath({ - repoName, - revisionName: revision, - path: fileName, - pathType: 'blob', - setBrowseState: { - selectedSymbolInfo: { - symbolName, - repoName, - revisionName: revision, - language: language, - }, - activeExploreMenuTab: "references", - isBottomPanelCollapsed: false, - } - }) - - }, [captureEvent, fileName, language, navigateToPath, repoName, revision]); - const ExpandCollapseIcon = useMemo(() => { return isExpanded ? ChevronDown : ChevronRight; }, [isExpanded]); @@ -341,11 +258,12 @@ const ReferencedFileSourceListItem = ({ > {editorRef && hasCodeNavEntitlement && ( )}