mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-12 04:15:30 +00:00
wip
This commit is contained in:
parent
41a6eb48a0
commit
29994d6011
8 changed files with 100 additions and 32 deletions
|
|
@ -10,6 +10,7 @@ import { useBrowseParams } from "./hooks/useBrowseParams";
|
||||||
import { FileSearchCommandDialog } from "./components/fileSearchCommandDialog";
|
import { FileSearchCommandDialog } from "./components/fileSearchCommandDialog";
|
||||||
import { useDomain } from "@/hooks/useDomain";
|
import { useDomain } from "@/hooks/useDomain";
|
||||||
import { SearchBar } from "../components/searchBar";
|
import { SearchBar } from "../components/searchBar";
|
||||||
|
import escapeStringRegexp from "escape-string-regexp";
|
||||||
|
|
||||||
interface LayoutProps {
|
interface LayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
|
@ -30,7 +31,7 @@ export default function Layout({
|
||||||
<SearchBar
|
<SearchBar
|
||||||
size="sm"
|
size="sm"
|
||||||
defaults={{
|
defaults={{
|
||||||
query: `repo:${repoName}${revisionName ? ` rev:${revisionName}` : ''} `,
|
query: `repo:^${escapeStringRegexp(repoName)}$${revisionName ? ` rev:${revisionName}` : ''} `,
|
||||||
}}
|
}}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
interface KeyboardShortcutHintProps {
|
interface KeyboardShortcutHintProps {
|
||||||
shortcut: string
|
shortcut: string
|
||||||
label?: string
|
label?: string
|
||||||
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function KeyboardShortcutHint({ shortcut, label }: KeyboardShortcutHintProps) {
|
export function KeyboardShortcutHint({ shortcut, label, className }: KeyboardShortcutHintProps) {
|
||||||
return (
|
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
|
<kbd
|
||||||
className="px-2 py-1 font-semibold font-sans border rounded-md"
|
className="px-2 py-1 font-semibold font-sans border rounded-md"
|
||||||
style={{
|
style={{
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const toggleVariants = cva(
|
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: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
|
|
@ -16,7 +16,7 @@ const toggleVariants = cva(
|
||||||
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
|
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
|
||||||
},
|
},
|
||||||
size: {
|
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",
|
sm: "h-9 px-2.5 min-w-9",
|
||||||
lg: "h-11 px-5 min-w-11",
|
lg: "h-11 px-5 min-w-11",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,22 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useBrowseState } from "@/app/[domain]/browse/hooks/useBrowseState";
|
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 { AnimatedResizableHandle } from "@/components/ui/animatedResizableHandle";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
|
import { ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
|
||||||
|
import { Toggle } from "@/components/ui/toggle";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { useDomain } from "@/hooks/useDomain";
|
import { useDomain } from "@/hooks/useDomain";
|
||||||
import { unwrapServiceError } from "@/lib/utils";
|
import { unwrapServiceError } from "@/lib/utils";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { Loader2 } from "lucide-react";
|
import { GlobeIcon, Loader2 } from "lucide-react";
|
||||||
import { useMemo } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { VscSymbolMisc } from "react-icons/vsc";
|
import { VscSymbolMisc } from "react-icons/vsc";
|
||||||
import { ReferenceList } from "./referenceList";
|
import { ReferenceList } from "./referenceList";
|
||||||
|
import { KeyboardShortcutHint } from "@/app/components/keyboardShortcutHint";
|
||||||
|
import { useHotkeys } from "react-hotkeys-hook";
|
||||||
|
|
||||||
interface ExploreMenuProps {
|
interface ExploreMenuProps {
|
||||||
selectedSymbolInfo: {
|
selectedSymbolInfo: {
|
||||||
|
|
@ -34,18 +37,21 @@ export const ExploreMenu = ({
|
||||||
updateBrowseState,
|
updateBrowseState,
|
||||||
} = useBrowseState();
|
} = useBrowseState();
|
||||||
|
|
||||||
|
const [isGlobalSearchEnabled, setIsGlobalSearchEnabled] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: referencesResponse,
|
data: referencesResponse,
|
||||||
isError: isReferencesResponseError,
|
isError: isReferencesResponseError,
|
||||||
isPending: isReferencesResponsePending,
|
isPending: isReferencesResponsePending,
|
||||||
isLoading: isReferencesResponseLoading,
|
isLoading: isReferencesResponseLoading,
|
||||||
} = useQuery({
|
} = 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(
|
queryFn: () => unwrapServiceError(
|
||||||
findSearchBasedSymbolReferences({
|
findSearchBasedSymbolReferences({
|
||||||
symbolName: selectedSymbolInfo.symbolName,
|
symbolName: selectedSymbolInfo.symbolName,
|
||||||
language: selectedSymbolInfo.language,
|
language: selectedSymbolInfo.language,
|
||||||
revisionName: selectedSymbolInfo.revisionName,
|
revisionName: selectedSymbolInfo.revisionName,
|
||||||
|
repoName: isGlobalSearchEnabled ? undefined : selectedSymbolInfo.repoName
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
@ -56,16 +62,25 @@ export const ExploreMenu = ({
|
||||||
isPending: isDefinitionsResponsePending,
|
isPending: isDefinitionsResponsePending,
|
||||||
isLoading: isDefinitionsResponseLoading,
|
isLoading: isDefinitionsResponseLoading,
|
||||||
} = useQuery({
|
} = 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(
|
queryFn: () => unwrapServiceError(
|
||||||
findSearchBasedSymbolDefinitions({
|
findSearchBasedSymbolDefinitions({
|
||||||
symbolName: selectedSymbolInfo.symbolName,
|
symbolName: selectedSymbolInfo.symbolName,
|
||||||
language: selectedSymbolInfo.language,
|
language: selectedSymbolInfo.language,
|
||||||
revisionName: selectedSymbolInfo.revisionName,
|
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 isPending = isReferencesResponsePending || isDefinitionsResponsePending;
|
||||||
const isLoading = isReferencesResponseLoading || isDefinitionsResponseLoading;
|
const isLoading = isReferencesResponseLoading || isDefinitionsResponseLoading;
|
||||||
const isError = isDefinitionsResponseError || isReferencesResponseError;
|
const isError = isDefinitionsResponseError || isReferencesResponseError;
|
||||||
|
|
@ -98,29 +113,52 @@ export const ExploreMenu = ({
|
||||||
<ResizablePanel
|
<ResizablePanel
|
||||||
minSize={10}
|
minSize={10}
|
||||||
maxSize={20}
|
maxSize={20}
|
||||||
|
className="flex flex-col h-full"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col p-2">
|
<div className="flex flex-col p-2">
|
||||||
<Tooltip
|
<div className="flex flex-row items-center justify-between">
|
||||||
delayDuration={100}
|
|
||||||
>
|
<Tooltip
|
||||||
<TooltipTrigger
|
delayDuration={100}
|
||||||
disabled={true}
|
|
||||||
className="mr-auto"
|
|
||||||
>
|
>
|
||||||
<Badge
|
<TooltipTrigger
|
||||||
variant="outline"
|
disabled={true}
|
||||||
className="w-fit h-fit flex-shrink-0 select-none"
|
className="mr-auto"
|
||||||
>
|
>
|
||||||
Search Based
|
<Badge
|
||||||
</Badge>
|
variant="outline"
|
||||||
</TooltipTrigger>
|
className="w-fit h-fit flex-shrink-0 select-none"
|
||||||
<TooltipContent
|
>
|
||||||
side="top"
|
Search Based
|
||||||
align="start"
|
</Badge>
|
||||||
>
|
</TooltipTrigger>
|
||||||
Symbol references and definitions found using a best-guess search heuristic.
|
<TooltipContent
|
||||||
</TooltipContent>
|
side="top"
|
||||||
</Tooltip>
|
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">
|
<div className="flex flex-col gap-1 mt-4">
|
||||||
<Entry
|
<Entry
|
||||||
name="References"
|
name="References"
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,9 @@ export const findSymbolReferencesTool = tool({
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
symbol: z.string().describe("The symbol to find references to"),
|
symbol: z.string().describe("The symbol to find references to"),
|
||||||
language: z.string().describe("The programming language of the symbol"),
|
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.
|
// @todo: make revision configurable.
|
||||||
const revision = "HEAD";
|
const revision = "HEAD";
|
||||||
|
|
||||||
|
|
@ -35,6 +36,7 @@ export const findSymbolReferencesTool = tool({
|
||||||
symbolName: symbol,
|
symbolName: symbol,
|
||||||
language,
|
language,
|
||||||
revisionName: "HEAD",
|
revisionName: "HEAD",
|
||||||
|
repoName: repository,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isServiceError(response)) {
|
if (isServiceError(response)) {
|
||||||
|
|
@ -63,8 +65,9 @@ export const findSymbolDefinitionsTool = tool({
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
symbol: z.string().describe("The symbol to find definitions of"),
|
symbol: z.string().describe("The symbol to find definitions of"),
|
||||||
language: z.string().describe("The programming language of the symbol"),
|
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.
|
// @todo: make revision configurable.
|
||||||
const revision = "HEAD";
|
const revision = "HEAD";
|
||||||
|
|
||||||
|
|
@ -72,6 +75,7 @@ export const findSymbolDefinitionsTool = tool({
|
||||||
symbolName: symbol,
|
symbolName: symbol,
|
||||||
language,
|
language,
|
||||||
revisionName: revision,
|
revisionName: revision,
|
||||||
|
repoName: repository,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isServiceError(response)) {
|
if (isServiceError(response)) {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { withOptionalAuthV2 } from "@/withAuthV2";
|
||||||
import { SearchResponse } from "../search/types";
|
import { SearchResponse } from "../search/types";
|
||||||
import { FindRelatedSymbolsRequest, FindRelatedSymbolsResponse } from "./types";
|
import { FindRelatedSymbolsRequest, FindRelatedSymbolsResponse } from "./types";
|
||||||
import { QueryIR } from '../search/ir';
|
import { QueryIR } from '../search/ir';
|
||||||
|
import escapeStringRegexp from "escape-string-regexp";
|
||||||
|
|
||||||
// The maximum number of matches to return from the search API.
|
// The maximum number of matches to return from the search API.
|
||||||
const MAX_REFERENCE_COUNT = 1000;
|
const MAX_REFERENCE_COUNT = 1000;
|
||||||
|
|
@ -18,6 +19,7 @@ export const findSearchBasedSymbolReferences = async (props: FindRelatedSymbolsR
|
||||||
symbolName,
|
symbolName,
|
||||||
language,
|
language,
|
||||||
revisionName = "HEAD",
|
revisionName = "HEAD",
|
||||||
|
repoName,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const languageFilter = getExpandedLanguageFilter(language);
|
const languageFilter = getExpandedLanguageFilter(language);
|
||||||
|
|
@ -40,6 +42,11 @@ export const findSearchBasedSymbolReferences = async (props: FindRelatedSymbolsR
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
languageFilter,
|
languageFilter,
|
||||||
|
...(repoName ? [{
|
||||||
|
repo: {
|
||||||
|
regexp: `^${escapeStringRegexp(repoName)}$`,
|
||||||
|
}
|
||||||
|
}]: [])
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -67,6 +74,7 @@ export const findSearchBasedSymbolDefinitions = async (props: FindRelatedSymbols
|
||||||
symbolName,
|
symbolName,
|
||||||
language,
|
language,
|
||||||
revisionName = "HEAD",
|
revisionName = "HEAD",
|
||||||
|
repoName
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const languageFilter = getExpandedLanguageFilter(language);
|
const languageFilter = getExpandedLanguageFilter(language);
|
||||||
|
|
@ -93,6 +101,11 @@ export const findSearchBasedSymbolDefinitions = async (props: FindRelatedSymbols
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
languageFilter,
|
languageFilter,
|
||||||
|
...(repoName ? [{
|
||||||
|
repo: {
|
||||||
|
regexp: `^${escapeStringRegexp(repoName)}$`,
|
||||||
|
}
|
||||||
|
}]: [])
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,16 @@ import { rangeSchema, repositoryInfoSchema } from "../search/types";
|
||||||
export const findRelatedSymbolsRequestSchema = z.object({
|
export const findRelatedSymbolsRequestSchema = z.object({
|
||||||
symbolName: z.string(),
|
symbolName: z.string(),
|
||||||
language: 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(),
|
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>;
|
export type FindRelatedSymbolsRequest = z.infer<typeof findRelatedSymbolsRequestSchema>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { search } from "./searchApi";
|
||||||
import { sew } from "@/actions";
|
import { sew } from "@/actions";
|
||||||
import { withOptionalAuthV2 } from "@/withAuthV2";
|
import { withOptionalAuthV2 } from "@/withAuthV2";
|
||||||
import { QueryIR } from './ir';
|
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.
|
// @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
|
// 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: [
|
children: [
|
||||||
{
|
{
|
||||||
repo: {
|
repo: {
|
||||||
regexp: `^${repository}$`,
|
regexp: `^${escapeStringRegexp(repository)}$`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue