diff --git a/packages/web/src/app/[domain]/browse/layout.tsx b/packages/web/src/app/[domain]/browse/layout.tsx index d8c7efd2..d7c9f145 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 0bbff3c0..8e0e4703 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 d721dbe9..83e0a887 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 b1dace76..4df8f685 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 3ea4aa73..a3945f23 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)}$`, }, }, {