This commit is contained in:
bkellam 2025-11-30 13:53:30 -08:00
parent 41a6eb48a0
commit 29994d6011
8 changed files with 100 additions and 32 deletions

View file

@ -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({
<SearchBar
size="sm"
defaults={{
query: `repo:${repoName}${revisionName ? ` rev:${revisionName}` : ''} `,
query: `repo:^${escapeStringRegexp(repoName)}$${revisionName ? ` rev:${revisionName}` : ''} `,
}}
className="w-full"
/>

View file

@ -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 (
<div className="inline-flex items-center" aria-label={label || `Keyboard shortcut: ${shortcut}`}>
<div className={cn("inline-flex items-center", className)} aria-label={label || `Keyboard shortcut: ${shortcut}`}>
<kbd
className="px-2 py-1 font-semibold font-sans border rounded-md"
style={{

View file

@ -7,7 +7,7 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const toggleVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 gap-2",
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 gap-2 cursor-pointer",
{
variants: {
variant: {
@ -16,7 +16,7 @@ const toggleVariants = cva(
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
},
size: {
default: "h-10 px-3 min-w-10",
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",
},

View file

@ -1,19 +1,22 @@
'use client';
import { useBrowseState } from "@/app/[domain]/browse/hooks/useBrowseState";
import { findSearchBasedSymbolReferences, findSearchBasedSymbolDefinitions} from "@/app/api/(client)/client";
import { findSearchBasedSymbolDefinitions, findSearchBasedSymbolReferences } from "@/app/api/(client)/client";
import { AnimatedResizableHandle } from "@/components/ui/animatedResizableHandle";
import { Badge } from "@/components/ui/badge";
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 { useQuery } from "@tanstack/react-query";
import clsx from "clsx";
import { Loader2 } from "lucide-react";
import { useMemo } from "react";
import { GlobeIcon, Loader2 } from "lucide-react";
import { useMemo, useState } from "react";
import { VscSymbolMisc } from "react-icons/vsc";
import { ReferenceList } from "./referenceList";
import { KeyboardShortcutHint } from "@/app/components/keyboardShortcutHint";
import { useHotkeys } from "react-hotkeys-hook";
interface ExploreMenuProps {
selectedSymbolInfo: {
@ -34,18 +37,21 @@ export const ExploreMenu = ({
updateBrowseState,
} = useBrowseState();
const [isGlobalSearchEnabled, setIsGlobalSearchEnabled] = useState(false);
const {
data: referencesResponse,
isError: isReferencesResponseError,
isPending: isReferencesResponsePending,
isLoading: isReferencesResponseLoading,
} = useQuery({
queryKey: ["references", selectedSymbolInfo.symbolName, selectedSymbolInfo.repoName, selectedSymbolInfo.revisionName, selectedSymbolInfo.language, domain],
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
})
),
});
@ -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,8 +113,11 @@ export const ExploreMenu = ({
<ResizablePanel
minSize={10}
maxSize={20}
className="flex flex-col h-full"
>
<div className="flex flex-col p-2">
<div className="flex flex-row items-center justify-between">
<Tooltip
delayDuration={100}
>
@ -116,11 +134,31 @@ export const ExploreMenu = ({
</TooltipTrigger>
<TooltipContent
side="top"
align="start"
align="center"
>
Symbol references and definitions found using a best-guess search heuristic.
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<span>
<Toggle
pressed={isGlobalSearchEnabled}
onPressedChange={setIsGlobalSearchEnabled}
>
<GlobeIcon className="w-4 h-4" />
</Toggle>
</span>
</TooltipTrigger>
<TooltipContent side="top" align="center">
{isGlobalSearchEnabled ? "Search in current repository only" : "Search all repositories"}
<KeyboardShortcutHint
shortcut="⇧ A"
className="ml-2"
/>
</TooltipContent>
</Tooltip>
</div>
<div className="flex flex-col gap-1 mt-4">
<Entry
name="References"

View file

@ -26,8 +26,9 @@ export const findSymbolReferencesTool = tool({
inputSchema: z.object({
symbol: z.string().describe("The symbol to find references to"),
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";
@ -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)) {

View file

@ -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)}$`,
}
}]: [])
]
}
}

View file

@ -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<typeof findRelatedSymbolsRequestSchema>;

View file

@ -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 <hash>:<path>` 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)}$`,
},
},
{