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 { 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"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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={{
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,29 +113,52 @@ export const ExploreMenu = ({
|
|||
<ResizablePanel
|
||||
minSize={10}
|
||||
maxSize={20}
|
||||
className="flex flex-col h-full"
|
||||
>
|
||||
<div className="flex flex-col p-2">
|
||||
<Tooltip
|
||||
delayDuration={100}
|
||||
>
|
||||
<TooltipTrigger
|
||||
disabled={true}
|
||||
className="mr-auto"
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
|
||||
<Tooltip
|
||||
delayDuration={100}
|
||||
>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="w-fit h-fit flex-shrink-0 select-none"
|
||||
<TooltipTrigger
|
||||
disabled={true}
|
||||
className="mr-auto"
|
||||
>
|
||||
Search Based
|
||||
</Badge>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="top"
|
||||
align="start"
|
||||
>
|
||||
Symbol references and definitions found using a best-guess search heuristic.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="w-fit h-fit flex-shrink-0 select-none"
|
||||
>
|
||||
Search Based
|
||||
</Badge>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="top"
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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)}$`,
|
||||
}
|
||||
}]: [])
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)}$`,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue