From 29994d6011c8cdd3fd795cda0278828b596e7a09 Mon Sep 17 00:00:00 2001 From: bkellam Date: Sun, 30 Nov 2025 13:53:30 -0800 Subject: [PATCH 1/8] wip --- .../web/src/app/[domain]/browse/layout.tsx | 3 +- .../app/components/keyboardShortcutHint.tsx | 6 +- packages/web/src/components/ui/toggle.tsx | 4 +- .../codeNav/components/exploreMenu/index.tsx | 86 +++++++++++++------ packages/web/src/features/chat/tools.ts | 8 +- packages/web/src/features/codeNav/api.ts | 13 +++ packages/web/src/features/codeNav/types.ts | 9 ++ .../web/src/features/search/fileSourceApi.ts | 3 +- 8 files changed, 100 insertions(+), 32 deletions(-) diff --git a/packages/web/src/app/[domain]/browse/layout.tsx b/packages/web/src/app/[domain]/browse/layout.tsx index d8c7efd20..d7c9f1453 100644 --- a/packages/web/src/app/[domain]/browse/layout.tsx +++ b/packages/web/src/app/[domain]/browse/layout.tsx @@ -10,6 +10,7 @@ import { useBrowseParams } from "./hooks/useBrowseParams"; import { FileSearchCommandDialog } from "./components/fileSearchCommandDialog"; import { useDomain } from "@/hooks/useDomain"; import { SearchBar } from "../components/searchBar"; +import escapeStringRegexp from "escape-string-regexp"; interface LayoutProps { children: React.ReactNode; @@ -30,7 +31,7 @@ export default function Layout({ diff --git a/packages/web/src/app/components/keyboardShortcutHint.tsx b/packages/web/src/app/components/keyboardShortcutHint.tsx index 0bbff3c0f..8e0e4703b 100644 --- a/packages/web/src/app/components/keyboardShortcutHint.tsx +++ b/packages/web/src/app/components/keyboardShortcutHint.tsx @@ -1,13 +1,15 @@ +import { cn } from '@/lib/utils' import React from 'react' interface KeyboardShortcutHintProps { shortcut: string label?: string + className?: string } -export function KeyboardShortcutHint({ shortcut, label }: KeyboardShortcutHintProps) { +export function KeyboardShortcutHint({ shortcut, label, className }: KeyboardShortcutHintProps) { return ( -
+
unwrapServiceError( findSearchBasedSymbolReferences({ symbolName: selectedSymbolInfo.symbolName, language: selectedSymbolInfo.language, revisionName: selectedSymbolInfo.revisionName, + repoName: isGlobalSearchEnabled ? undefined : selectedSymbolInfo.repoName }) ), }); @@ -56,16 +62,25 @@ export const ExploreMenu = ({ isPending: isDefinitionsResponsePending, isLoading: isDefinitionsResponseLoading, } = useQuery({ - queryKey: ["definitions", selectedSymbolInfo.symbolName, selectedSymbolInfo.repoName, selectedSymbolInfo.revisionName, selectedSymbolInfo.language, domain], + queryKey: ["definitions", selectedSymbolInfo.symbolName, selectedSymbolInfo.repoName, selectedSymbolInfo.revisionName, selectedSymbolInfo.language, domain, isGlobalSearchEnabled], queryFn: () => unwrapServiceError( findSearchBasedSymbolDefinitions({ symbolName: selectedSymbolInfo.symbolName, language: selectedSymbolInfo.language, revisionName: selectedSymbolInfo.revisionName, + repoName: isGlobalSearchEnabled ? undefined : selectedSymbolInfo.repoName }) ), }); + useHotkeys('shift+a', () => { + setIsGlobalSearchEnabled(!isGlobalSearchEnabled); + }, { + enableOnFormTags: true, + enableOnContentEditable: true, + description: "Search all repositories", + }); + const isPending = isReferencesResponsePending || isDefinitionsResponsePending; const isLoading = isReferencesResponseLoading || isDefinitionsResponseLoading; const isError = isDefinitionsResponseError || isReferencesResponseError; @@ -98,29 +113,52 @@ export const ExploreMenu = ({
- - + + - - Search Based - - - - Symbol references and definitions found using a best-guess search heuristic. - - + + Search Based + + + + Symbol references and definitions found using a best-guess search heuristic. + + + + + + + + + + + + {isGlobalSearchEnabled ? "Search in current repository only" : "Search all repositories"} + + + +
{ + execute: async ({ symbol, language, repository }) => { // @todo: make revision configurable. const revision = "HEAD"; @@ -35,6 +36,7 @@ export const findSymbolReferencesTool = tool({ symbolName: symbol, language, revisionName: "HEAD", + repoName: repository, }); if (isServiceError(response)) { @@ -63,8 +65,9 @@ export const findSymbolDefinitionsTool = tool({ inputSchema: z.object({ symbol: z.string().describe("The symbol to find definitions of"), language: z.string().describe("The programming language of the symbol"), + repository: z.string().describe("The repository to scope the search to").optional(), }), - execute: async ({ symbol, language }) => { + execute: async ({ symbol, language, repository }) => { // @todo: make revision configurable. const revision = "HEAD"; @@ -72,6 +75,7 @@ export const findSymbolDefinitionsTool = tool({ symbolName: symbol, language, revisionName: revision, + repoName: repository, }); if (isServiceError(response)) { diff --git a/packages/web/src/features/codeNav/api.ts b/packages/web/src/features/codeNav/api.ts index d721dbe98..83e0a8873 100644 --- a/packages/web/src/features/codeNav/api.ts +++ b/packages/web/src/features/codeNav/api.ts @@ -8,6 +8,7 @@ import { withOptionalAuthV2 } from "@/withAuthV2"; import { SearchResponse } from "../search/types"; import { FindRelatedSymbolsRequest, FindRelatedSymbolsResponse } from "./types"; import { QueryIR } from '../search/ir'; +import escapeStringRegexp from "escape-string-regexp"; // The maximum number of matches to return from the search API. const MAX_REFERENCE_COUNT = 1000; @@ -18,6 +19,7 @@ export const findSearchBasedSymbolReferences = async (props: FindRelatedSymbolsR symbolName, language, revisionName = "HEAD", + repoName, } = props; const languageFilter = getExpandedLanguageFilter(language); @@ -40,6 +42,11 @@ export const findSearchBasedSymbolReferences = async (props: FindRelatedSymbolsR } }, languageFilter, + ...(repoName ? [{ + repo: { + regexp: `^${escapeStringRegexp(repoName)}$`, + } + }]: []) ] } } @@ -67,6 +74,7 @@ export const findSearchBasedSymbolDefinitions = async (props: FindRelatedSymbols symbolName, language, revisionName = "HEAD", + repoName } = props; const languageFilter = getExpandedLanguageFilter(language); @@ -93,6 +101,11 @@ export const findSearchBasedSymbolDefinitions = async (props: FindRelatedSymbols } }, languageFilter, + ...(repoName ? [{ + repo: { + regexp: `^${escapeStringRegexp(repoName)}$`, + } + }]: []) ] } } diff --git a/packages/web/src/features/codeNav/types.ts b/packages/web/src/features/codeNav/types.ts index b1dace76c..4df8f6853 100644 --- a/packages/web/src/features/codeNav/types.ts +++ b/packages/web/src/features/codeNav/types.ts @@ -4,7 +4,16 @@ import { rangeSchema, repositoryInfoSchema } from "../search/types"; export const findRelatedSymbolsRequestSchema = z.object({ symbolName: z.string(), language: z.string(), + /** + * Optional revision name to scope search to. + * If not provided, the search will be scoped to HEAD. + */ revisionName: z.string().optional(), + /** + * Optional repository name to scope search to. + * If not provided, the search will be across all repositories. + */ + repoName: z.string().optional(), }); export type FindRelatedSymbolsRequest = z.infer; diff --git a/packages/web/src/features/search/fileSourceApi.ts b/packages/web/src/features/search/fileSourceApi.ts index 3ea4aa739..a3945f237 100644 --- a/packages/web/src/features/search/fileSourceApi.ts +++ b/packages/web/src/features/search/fileSourceApi.ts @@ -6,6 +6,7 @@ import { search } from "./searchApi"; import { sew } from "@/actions"; import { withOptionalAuthV2 } from "@/withAuthV2"; import { QueryIR } from './ir'; +import escapeStringRegexp from "escape-string-regexp"; // @todo (bkellam) #574 : We should really be using `git show :` to fetch file contents here. // This will allow us to support permalinks to files at a specific revision that may not be indexed @@ -18,7 +19,7 @@ export const getFileSource = async ({ fileName, repository, branch }: FileSource children: [ { repo: { - regexp: `^${repository}$`, + regexp: `^${escapeStringRegexp(repository)}$`, }, }, { From 2767c2b08871a96a378786ebefeb5dc88c49364c Mon Sep 17 00:00:00 2001 From: bkellam Date: Sun, 30 Nov 2025 15:06:17 -0800 Subject: [PATCH 2/8] refactor code nav callbacks into symbolHoverPopup --- .../components/pureCodePreviewPanel.tsx | 81 +--------- .../codePreviewPanel/codePreview.tsx | 95 +---------- .../codeNav/components/exploreMenu/index.tsx | 2 +- .../components/symbolHoverPopup/index.tsx | 150 ++++++++++++++++-- .../useHoveredOverSymbolInfo.ts | 7 +- .../referencedFileSourceListItem.tsx | 92 +---------- 6 files changed, 157 insertions(+), 270 deletions(-) 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 bdf6f8787..71cf1929f 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 d500bbe9c..264be5925 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 3775c3209..00cfc8e10 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 b8336a93c..a537fe53c 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 ba6201d4e..4d8b641d6 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 && ( )} From 23057d8a051c2b3c11c76aed82366f546798fa7e Mon Sep 17 00:00:00 2001 From: bkellam Date: Sun, 30 Nov 2025 16:35:24 -0800 Subject: [PATCH 3/8] highlight the selected reference --- .../components/symbolHoverPopup/index.tsx | 27 ++++------ .../useHoveredOverSymbolInfo.ts | 50 ++++++++++++++++++- 2 files changed, 60 insertions(+), 17 deletions(-) diff --git a/packages/web/src/ee/features/codeNav/components/symbolHoverPopup/index.tsx b/packages/web/src/ee/features/codeNav/components/symbolHoverPopup/index.tsx index 9399a2ed0..a786f600e 100644 --- a/packages/web/src/ee/features/codeNav/components/symbolHoverPopup/index.tsx +++ b/packages/web/src/ee/features/codeNav/components/symbolHoverPopup/index.tsx @@ -163,7 +163,11 @@ export const SymbolHoverPopup: React.FC = ({ symbolInfo ]); - const onFindReferences = useCallback((symbolName: string) => { + const onFindReferences = useCallback(() => { + if (!symbolInfo) { + return; + } + captureEvent('wa_find_references_pressed', { source, }); @@ -171,7 +175,7 @@ export const SymbolHoverPopup: React.FC = ({ createAuditAction({ action: "user.performed_find_references", metadata: { - message: symbolName, + message: symbolInfo.symbolName, }, }) @@ -180,9 +184,10 @@ export const SymbolHoverPopup: React.FC = ({ revisionName, path: fileName, pathType: 'blob', + highlightRange: symbolInfo.range, setBrowseState: { selectedSymbolInfo: { - symbolName, + symbolName: symbolInfo.symbolName, repoName, revisionName, language, @@ -191,15 +196,7 @@ export const SymbolHoverPopup: React.FC = ({ isBottomPanelCollapsed: false, } }) - }, [ - captureEvent, - fileName, - language, - navigateToPath, - repoName, - revisionName, - source - ]); + }, [captureEvent, fileName, language, navigateToPath, repoName, revisionName, source, symbolInfo]); // @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 @@ -216,9 +213,7 @@ export const SymbolHoverPopup: React.FC = ({ }, [symbolInfo, onGotoDefinition]); useHotkeys('alt+shift+f12', () => { - if (symbolInfo?.symbolName) { - onFindReferences(symbolInfo.symbolName); - } + onFindReferences(); }, { enableOnFormTags: true, enableOnContentEditable: true, @@ -302,7 +297,7 @@ export const SymbolHoverPopup: React.FC = ({ 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 a537fe53c..44b5d8046 100644 --- a/packages/web/src/ee/features/codeNav/components/symbolHoverPopup/useHoveredOverSymbolInfo.ts +++ b/packages/web/src/ee/features/codeNav/components/symbolHoverPopup/useHoveredOverSymbolInfo.ts @@ -27,6 +27,7 @@ export type SymbolDefinition = { interface HoveredOverSymbolInfo { element: HTMLElement; symbolName: string; + range: SourceRange; isSymbolDefinitionsLoading: boolean; symbolDefinitions?: SymbolDefinition[]; } @@ -127,17 +128,64 @@ export const useHoveredOverSymbolInfo = ({ }; }, [editorRef, domain, clearTimers]); + // Extract the highlight range of the symbolElement from the editor view. + const highlightRange = useMemo((): SourceRange | undefined => { + if (!symbolElement || !editorRef.view) { + return undefined; + } + + const view = editorRef.view; + const rect = symbolElement.getBoundingClientRect(); + + // Get the start position (left edge, middle vertically) + const startPos = view.posAtCoords({ + x: rect.left, + y: rect.top + rect.height / 2, + }); + + // Get the end position (right edge, middle vertically) + const endPos = view.posAtCoords({ + x: rect.right, + y: rect.top + rect.height / 2, + }); + + if (startPos === null || endPos === null) { + return undefined; + } + + // Convert CodeMirror positions to SourceRange format + const startLine = view.state.doc.lineAt(startPos); + const endLine = view.state.doc.lineAt(endPos); + + const startColumn = startPos - startLine.from + 1; // 1-based column + const endColumn = endPos - endLine.from + 1; // 1-based column + + return { + start: { + byteOffset: startPos, // 0-based byte offset + lineNumber: startLine.number, // 1-based line number + column: startColumn, // 1-based column + }, + end: { + byteOffset: endPos, // 0-based byte offset + lineNumber: endLine.number, // 1-based line number + column: endColumn, // 1-based column + }, + }; + }, [symbolElement, editorRef.view]); + if (!isVisible && !isSticky) { return undefined; } - if (!symbolElement || !symbolName) { + if (!symbolElement || !symbolName || !highlightRange) { return undefined; } return { element: symbolElement, symbolName, + range: highlightRange, isSymbolDefinitionsLoading: isSymbolDefinitionsLoading, symbolDefinitions, }; From e165d5e590ffde822c6b02911b568d32fe20d472 Mon Sep 17 00:00:00 2001 From: bkellam Date: Sun, 30 Nov 2025 17:31:26 -0800 Subject: [PATCH 4/8] improve scroll experience --- .../components/pureCodePreviewPanel.tsx | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) 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 71cf1929f..02e3b91c1 100644 --- a/packages/web/src/app/[domain]/browse/[...path]/components/pureCodePreviewPanel.tsx +++ b/packages/web/src/app/[domain]/browse/[...path]/components/pureCodePreviewPanel.tsx @@ -80,7 +80,6 @@ export const PureCodePreviewPanel = ({ } } } - }, [highlightRangeQuery]); const extensions = useMemo(() => { @@ -108,20 +107,27 @@ export const PureCodePreviewPanel = ({ // Scroll the highlighted range into view. useEffect(() => { - if (!highlightRange || !editorRef || !editorRef.state) { + if (!highlightRange || !editorRef || !editorRef.state || !editorRef.view) { return; } const doc = editorRef.state.doc; const { start, end } = highlightRange; - const selection = EditorSelection.range( - doc.line(start.lineNumber).from, - doc.line(end.lineNumber).from, - ); + const from = doc.line(start.lineNumber).from; + const to = doc.line(end.lineNumber).to; + const selection = EditorSelection.range(from, to); + + // When the selection is in view, we don't want to perform any scrolling + // as it could be jarring for the user. If it is not in view, scroll to the + // center of the viewport. + const viewport = editorRef.view.viewport; + const isInView = from >= viewport.from && to <= viewport.to; + const scrollStrategy = isInView ? "nearest" : "center"; + editorRef.view?.dispatch({ effects: [ - EditorView.scrollIntoView(selection, { y: "center" }), + EditorView.scrollIntoView(selection, { y: scrollStrategy }), ] }); }, [editorRef, highlightRange]); From c868109ea88fa3c99635ee21b888acd6da0593ad Mon Sep 17 00:00:00 2001 From: bkellam Date: Sun, 30 Nov 2025 17:59:27 -0800 Subject: [PATCH 5/8] changelog & docs --- CHANGELOG.md | 4 ++++ docs/docs/features/code-navigation.mdx | 1 + 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bbcee6a6..3962640d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added `ALWAYS_INDEX_FILE_PATTERNS` environment variable to allow specifying a comma seperated list of glob patterns matching file paths that should always be indexed, regardless of size or # of trigrams. [#631](https://github.com/sourcebot-dev/sourcebot/pull/631) +- Added button to explore menu to toggle cross-repository search. [#647](https://github.com/sourcebot-dev/sourcebot/pull/647) ### Fixed - Fixed issue where single quotes could not be used in search queries. [#629](https://github.com/sourcebot-dev/sourcebot/pull/629) @@ -16,6 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed Ask performance issues. [#632](https://github.com/sourcebot-dev/sourcebot/pull/632) - Fixed regression where creating a new Ask thread when unauthenticated would result in a 404. [#641](https://github.com/sourcebot-dev/sourcebot/pull/641) +### Changed +- Changed the default behaviour for code nav to scope references & definitions search to the current repository. [#647](https://github.com/sourcebot-dev/sourcebot/pull/647) + ## [4.10.0] - 2025-11-24 ### Added diff --git a/docs/docs/features/code-navigation.mdx b/docs/docs/features/code-navigation.mdx index 6720556d4..9e9f7db65 100644 --- a/docs/docs/features/code-navigation.mdx +++ b/docs/docs/features/code-navigation.mdx @@ -21,6 +21,7 @@ import LicenseKeyRequired from '/snippets/license-key-required.mdx' | **Go to definition** | Clicking the "go to definition" button in the popover or clicking the symbol name navigates to the symbol's definition. | | **Find references** | Clicking the "find all references" button in the popover lists all references in the explore panel. | | **Explore panel** | Lists all references and definitions for the symbol selected in the popover. | +| **Cross-repository navigation** | You can search across all repositories by clicking the globe icon in the explore panel. By default, references and definitions are scoped to the repository where the symbol is being resolved. | ## How does it work? From a80e92eb3dfb1b301a56b602761be3b8ab042a16 Mon Sep 17 00:00:00 2001 From: bkellam Date: Sun, 30 Nov 2025 18:17:07 -0800 Subject: [PATCH 6/8] PostHog event for measuring duration of resolving symbol definitions for hover preview --- .../useHoveredOverSymbolInfo.ts | 37 ++++++++++++------- packages/web/src/lib/posthogEvents.ts | 4 ++ 2 files changed, 28 insertions(+), 13 deletions(-) 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 44b5d8046..d5ee3c02d 100644 --- a/packages/web/src/ee/features/codeNav/components/symbolHoverPopup/useHoveredOverSymbolInfo.ts +++ b/packages/web/src/ee/features/codeNav/components/symbolHoverPopup/useHoveredOverSymbolInfo.ts @@ -1,7 +1,8 @@ import { findSearchBasedSymbolDefinitions } from "@/app/api/(client)/client"; import { SourceRange } from "@/features/search"; +import useCaptureEvent from "@/hooks/useCaptureEvent"; import { useDomain } from "@/hooks/useDomain"; -import { unwrapServiceError } from "@/lib/utils"; +import { measure, unwrapServiceError } from "@/lib/utils"; import { useQuery } from "@tanstack/react-query"; import { ReactCodeMirrorRef } from "@uiw/react-codemirror"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; @@ -53,16 +54,26 @@ export const useHoveredOverSymbolInfo = ({ return (symbolElement && symbolElement.textContent) ?? undefined; }, [symbolElement]); + const captureEvent = useCaptureEvent(); + const { data: symbolDefinitions, isLoading: isSymbolDefinitionsLoading } = useQuery({ queryKey: ["definitions", symbolName, revisionName, language, domain, repoName], - queryFn: () => unwrapServiceError( - findSearchBasedSymbolDefinitions({ - symbolName: symbolName!, - language, - revisionName, - repoName, - }) - ), + queryFn: async () => { + const response = await measure(() => unwrapServiceError( + findSearchBasedSymbolDefinitions({ + symbolName: symbolName!, + language, + revisionName, + repoName, + }) + ), 'findSearchBasedSymbolDefinitions', false); + + captureEvent('wa_find_hovered_over_symbol_definitions', { + durationMs: response.durationMs, + }); + + return response.data; + }, select: ((data) => { return data.files.flatMap((file) => { return file.matches.map((match) => { @@ -113,7 +124,7 @@ export const useHoveredOverSymbolInfo = ({ const handleMouseOut = () => { clearTimers(); - + mouseOutTimerRef.current = setTimeout(() => { setIsVisible(false); }, SYMBOL_HOVER_POPUP_MOUSE_OUT_TIMEOUT_MS); @@ -136,13 +147,13 @@ export const useHoveredOverSymbolInfo = ({ const view = editorRef.view; const rect = symbolElement.getBoundingClientRect(); - + // Get the start position (left edge, middle vertically) const startPos = view.posAtCoords({ x: rect.left, y: rect.top + rect.height / 2, }); - + // Get the end position (right edge, middle vertically) const endPos = view.posAtCoords({ x: rect.right, @@ -156,7 +167,7 @@ export const useHoveredOverSymbolInfo = ({ // Convert CodeMirror positions to SourceRange format const startLine = view.state.doc.lineAt(startPos); const endLine = view.state.doc.lineAt(endPos); - + const startColumn = startPos - startLine.from + 1; // 1-based column const endColumn = endPos - endLine.from + 1; // 1-based column diff --git a/packages/web/src/lib/posthogEvents.ts b/packages/web/src/lib/posthogEvents.ts index 126ecf45e..e7124c1a6 100644 --- a/packages/web/src/lib/posthogEvents.ts +++ b/packages/web/src/lib/posthogEvents.ts @@ -302,5 +302,9 @@ export type PosthogEventMap = { ////////////////////////////////////////////////////////////////// wa_github_star_toast_displayed: {}, wa_github_star_toast_clicked: {}, + ////////////////////////////////////////////////////////////////// + wa_find_hovered_over_symbol_definitions: { + durationMs: number, + } } export type PosthogEvent = keyof PosthogEventMap; \ No newline at end of file From 5fb056b91048a8eb017d1b15294f210b78843185 Mon Sep 17 00:00:00 2001 From: bkellam Date: Sun, 30 Nov 2025 18:26:51 -0800 Subject: [PATCH 7/8] posthog events --- .../codeNav/components/exploreMenu/index.tsx | 51 +++++++++++++------ .../useHoveredOverSymbolInfo.ts | 2 +- packages/web/src/lib/posthogEvents.ts | 28 ++++++---- 3 files changed, 54 insertions(+), 27 deletions(-) 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 00cfc8e10..0ac60a730 100644 --- a/packages/web/src/ee/features/codeNav/components/exploreMenu/index.tsx +++ b/packages/web/src/ee/features/codeNav/components/exploreMenu/index.tsx @@ -8,7 +8,7 @@ import { ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable"; import { Toggle } from "@/components/ui/toggle"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useDomain } from "@/hooks/useDomain"; -import { unwrapServiceError } from "@/lib/utils"; +import { measure, unwrapServiceError } from "@/lib/utils"; import { useQuery } from "@tanstack/react-query"; import clsx from "clsx"; import { GlobeIcon, Loader2 } from "lucide-react"; @@ -17,6 +17,7 @@ import { VscSymbolMisc } from "react-icons/vsc"; import { ReferenceList } from "./referenceList"; import { KeyboardShortcutHint } from "@/app/components/keyboardShortcutHint"; import { useHotkeys } from "react-hotkeys-hook"; +import useCaptureEvent from "@/hooks/useCaptureEvent"; interface ExploreMenuProps { selectedSymbolInfo: { @@ -30,8 +31,8 @@ interface ExploreMenuProps { export const ExploreMenu = ({ selectedSymbolInfo, }: ExploreMenuProps) => { - const domain = useDomain(); + const captureEvent = useCaptureEvent(); const { state: { activeExploreMenuTab }, updateBrowseState, @@ -46,14 +47,23 @@ export const ExploreMenu = ({ isLoading: isReferencesResponseLoading, } = useQuery({ queryKey: ["references", selectedSymbolInfo.symbolName, selectedSymbolInfo.repoName, selectedSymbolInfo.revisionName, selectedSymbolInfo.language, domain, isGlobalSearchEnabled], - queryFn: () => unwrapServiceError( - findSearchBasedSymbolReferences({ - symbolName: selectedSymbolInfo.symbolName, - language: selectedSymbolInfo.language, - revisionName: selectedSymbolInfo.revisionName, - repoName: isGlobalSearchEnabled ? undefined : selectedSymbolInfo.repoName + queryFn: async () => { + const response = await measure(() => unwrapServiceError( + findSearchBasedSymbolReferences({ + symbolName: selectedSymbolInfo.symbolName, + language: selectedSymbolInfo.language, + revisionName: selectedSymbolInfo.revisionName, + repoName: isGlobalSearchEnabled ? undefined : selectedSymbolInfo.repoName + }) + ), 'findSearchBasedSymbolReferences', false); + + captureEvent('wa_explore_menu_references_loaded', { + durationMs: response.durationMs, + isGlobalSearchEnabled, }) - ), + + return response.data; + } }); const { @@ -63,14 +73,23 @@ export const ExploreMenu = ({ isLoading: isDefinitionsResponseLoading, } = useQuery({ queryKey: ["definitions", selectedSymbolInfo.symbolName, selectedSymbolInfo.repoName, selectedSymbolInfo.revisionName, selectedSymbolInfo.language, domain, isGlobalSearchEnabled], - queryFn: () => unwrapServiceError( - findSearchBasedSymbolDefinitions({ - symbolName: selectedSymbolInfo.symbolName, - language: selectedSymbolInfo.language, - revisionName: selectedSymbolInfo.revisionName, - repoName: isGlobalSearchEnabled ? undefined : selectedSymbolInfo.repoName + queryFn: async () => { + const response = await measure(() => unwrapServiceError( + findSearchBasedSymbolDefinitions({ + symbolName: selectedSymbolInfo.symbolName, + language: selectedSymbolInfo.language, + revisionName: selectedSymbolInfo.revisionName, + repoName: isGlobalSearchEnabled ? undefined : selectedSymbolInfo.repoName + }) + ), 'findSearchBasedSymbolDefinitions', false); + + captureEvent('wa_explore_menu_definitions_loaded', { + durationMs: response.durationMs, + isGlobalSearchEnabled, }) - ), + + return response.data; + } }); useHotkeys('shift+a', () => { 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 d5ee3c02d..bca813165 100644 --- a/packages/web/src/ee/features/codeNav/components/symbolHoverPopup/useHoveredOverSymbolInfo.ts +++ b/packages/web/src/ee/features/codeNav/components/symbolHoverPopup/useHoveredOverSymbolInfo.ts @@ -68,7 +68,7 @@ export const useHoveredOverSymbolInfo = ({ }) ), 'findSearchBasedSymbolDefinitions', false); - captureEvent('wa_find_hovered_over_symbol_definitions', { + captureEvent('wa_symbol_hover_popup_definitions_loaded', { durationMs: response.durationMs, }); diff --git a/packages/web/src/lib/posthogEvents.ts b/packages/web/src/lib/posthogEvents.ts index e7124c1a6..6b340fe96 100644 --- a/packages/web/src/lib/posthogEvents.ts +++ b/packages/web/src/lib/posthogEvents.ts @@ -273,15 +273,6 @@ export type PosthogEventMap = { wa_api_key_created: {}, wa_api_key_creation_fail: {}, ////////////////////////////////////////////////////////////////// - wa_goto_definition_pressed: { - source: 'chat' | 'browse' | 'preview', - }, - wa_find_references_pressed: { - source: 'chat' | 'browse' | 'preview', - }, - ////////////////////////////////////////////////////////////////// - wa_explore_menu_reference_clicked: {}, - ////////////////////////////////////////////////////////////////// wa_chat_feedback_submitted: { feedback: 'like' | 'dislike', chatId: string, @@ -303,8 +294,25 @@ export type PosthogEventMap = { wa_github_star_toast_displayed: {}, wa_github_star_toast_clicked: {}, ////////////////////////////////////////////////////////////////// - wa_find_hovered_over_symbol_definitions: { + wa_goto_definition_pressed: { + source: 'chat' | 'browse' | 'preview', + }, + wa_find_references_pressed: { + source: 'chat' | 'browse' | 'preview', + }, + wa_symbol_hover_popup_definitions_loaded: { + durationMs: number, + }, + wa_explore_menu_reference_clicked: {}, + wa_explore_menu_references_loaded: { + durationMs: number, + // Whether or not the user is searching all repositories. + isGlobalSearchEnabled: boolean, + }, + wa_explore_menu_definitions_loaded: { durationMs: number, + // Whether or not the user is searching all repositories. + isGlobalSearchEnabled: boolean, } } export type PosthogEvent = keyof PosthogEventMap; \ No newline at end of file From b75b622999a0cffbd30b62d6b6f90763e2afd1a6 Mon Sep 17 00:00:00 2001 From: bkellam Date: Sun, 30 Nov 2025 18:48:27 -0800 Subject: [PATCH 8/8] feedback --- packages/web/src/components/ui/toggle.tsx | 2 -- .../src/ee/features/codeNav/components/exploreMenu/index.tsx | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/web/src/components/ui/toggle.tsx b/packages/web/src/components/ui/toggle.tsx index 5553edc9b..9f378daaa 100644 --- a/packages/web/src/components/ui/toggle.tsx +++ b/packages/web/src/components/ui/toggle.tsx @@ -17,8 +17,6 @@ const toggleVariants = cva( }, size: { default: "h-7 w-7 min-w-7 p-0", - sm: "h-9 px-2.5 min-w-9", - lg: "h-11 px-5 min-w-11", }, }, defaultVariants: { 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 0ac60a730..30e09b000 100644 --- a/packages/web/src/ee/features/codeNav/components/exploreMenu/index.tsx +++ b/packages/web/src/ee/features/codeNav/components/exploreMenu/index.tsx @@ -93,7 +93,7 @@ export const ExploreMenu = ({ }); useHotkeys('shift+a', () => { - setIsGlobalSearchEnabled(!isGlobalSearchEnabled); + setIsGlobalSearchEnabled(prev => !prev); }, { enableOnFormTags: true, enableOnContentEditable: true,