mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-11 20:05:25 +00:00
Symbol suggestions (#98)
This commit is contained in:
parent
b115218be9
commit
120d84a046
9 changed files with 201 additions and 75 deletions
|
|
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added symbol suggestions as suggestion type. ([#98](https://github.com/sourcebot-dev/sourcebot/pull/98))
|
||||||
|
|
||||||
## [2.5.2] - 2024-11-27
|
## [2.5.2] - 2024-11-27
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
||||||
|
|
@ -392,3 +392,9 @@ Or if you are [building locally](#build-from-source), create a `.env.local` file
|
||||||
SOURCEBOT_TELEMETRY_DISABLED=1
|
SOURCEBOT_TELEMETRY_DISABLED=1
|
||||||
NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED=1
|
NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED=1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Attributions
|
||||||
|
|
||||||
|
Sourcebot makes use of the following libraries:
|
||||||
|
|
||||||
|
- [@vscode/codicons](https://github.com/microsoft/vscode-codicons) under the [CC BY 4.0 License](https://github.com/microsoft/vscode-codicons/blob/main/LICENSE).
|
||||||
|
|
@ -59,6 +59,7 @@
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-hook-form": "^7.53.0",
|
"react-hook-form": "^7.53.0",
|
||||||
"react-hotkeys-hook": "^4.5.1",
|
"react-hotkeys-hook": "^4.5.1",
|
||||||
|
"react-icons": "^5.3.0",
|
||||||
"react-resizable-panels": "^2.1.1",
|
"react-resizable-panels": "^2.1.1",
|
||||||
"server-only": "^0.0.1",
|
"server-only": "^0.0.1",
|
||||||
"sharp": "^0.33.5",
|
"sharp": "^0.33.5",
|
||||||
|
|
|
||||||
|
|
@ -255,13 +255,16 @@ export const SearchBar = ({
|
||||||
setIsSuggestionsBoxFocused(document.activeElement === suggestionBoxRef.current);
|
setIsSuggestionsBoxFocused(document.activeElement === suggestionBoxRef.current);
|
||||||
}}
|
}}
|
||||||
cursorPosition={cursorPosition}
|
cursorPosition={cursorPosition}
|
||||||
data={suggestionData}
|
onSuggestionModeChanged={(newSuggestionMode) => {
|
||||||
onSuggestionModeChanged={(suggestionMode) => {
|
if (suggestionMode !== newSuggestionMode) {
|
||||||
setSuggestionMode(suggestionMode);
|
console.debug(`Suggestion mode changed: ${suggestionMode} -> ${newSuggestionMode}`);
|
||||||
|
}
|
||||||
|
setSuggestionMode(newSuggestionMode);
|
||||||
}}
|
}}
|
||||||
onSuggestionQueryChanged={(suggestionQuery) => {
|
onSuggestionQueryChanged={(suggestionQuery) => {
|
||||||
setSuggestionQuery(suggestionQuery);
|
setSuggestionQuery(suggestionQuery);
|
||||||
}}
|
}}
|
||||||
|
{...suggestionData}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { isDefined } from "@/lib/utils";
|
import { isDefined } from "@/lib/utils";
|
||||||
import { CommitIcon, MixerVerticalIcon } from "@radix-ui/react-icons";
|
|
||||||
import { IconProps } from "@radix-ui/react-icons/dist/types";
|
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import escapeStringRegexp from "escape-string-regexp";
|
import escapeStringRegexp from "escape-string-regexp";
|
||||||
|
|
@ -16,13 +14,14 @@ import {
|
||||||
refineModeSuggestions,
|
refineModeSuggestions,
|
||||||
suggestionModeMappings
|
suggestionModeMappings
|
||||||
} from "./constants";
|
} from "./constants";
|
||||||
|
import { IconType } from "react-icons/lib";
|
||||||
type Icon = React.ForwardRefExoticComponent<IconProps & React.RefAttributes<SVGSVGElement>>;
|
import { VscFile, VscFilter, VscRepo, VscSymbolMisc } from "react-icons/vsc";
|
||||||
|
|
||||||
export type Suggestion = {
|
export type Suggestion = {
|
||||||
value: string;
|
value: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
spotlight?: boolean;
|
spotlight?: boolean;
|
||||||
|
Icon?: IconType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SuggestionMode =
|
export type SuggestionMode =
|
||||||
|
|
@ -50,18 +49,17 @@ interface SearchSuggestionsBoxProps {
|
||||||
onSuggestionModeChanged: (suggestionMode: SuggestionMode) => void;
|
onSuggestionModeChanged: (suggestionMode: SuggestionMode) => void;
|
||||||
onSuggestionQueryChanged: (suggestionQuery: string) => void;
|
onSuggestionQueryChanged: (suggestionQuery: string) => void;
|
||||||
|
|
||||||
data: {
|
isLoadingSuggestions: boolean;
|
||||||
repos: Suggestion[];
|
repoSuggestions: Suggestion[];
|
||||||
languages: Suggestion[];
|
fileSuggestions: Suggestion[];
|
||||||
files: Suggestion[];
|
symbolSuggestions: Suggestion[];
|
||||||
}
|
languageSuggestions: Suggestion[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchSuggestionsBox = forwardRef(({
|
const SearchSuggestionsBox = forwardRef(({
|
||||||
query,
|
query,
|
||||||
onCompletion,
|
onCompletion,
|
||||||
isEnabled,
|
isEnabled,
|
||||||
data,
|
|
||||||
cursorPosition,
|
cursorPosition,
|
||||||
isFocused,
|
isFocused,
|
||||||
onFocus,
|
onFocus,
|
||||||
|
|
@ -69,11 +67,24 @@ const SearchSuggestionsBox = forwardRef(({
|
||||||
onReturnFocus,
|
onReturnFocus,
|
||||||
onSuggestionModeChanged,
|
onSuggestionModeChanged,
|
||||||
onSuggestionQueryChanged,
|
onSuggestionQueryChanged,
|
||||||
|
isLoadingSuggestions,
|
||||||
|
repoSuggestions,
|
||||||
|
fileSuggestions,
|
||||||
|
symbolSuggestions,
|
||||||
|
languageSuggestions,
|
||||||
}: SearchSuggestionsBoxProps, ref: Ref<HTMLDivElement>) => {
|
}: SearchSuggestionsBoxProps, ref: Ref<HTMLDivElement>) => {
|
||||||
|
|
||||||
const [highlightedSuggestionIndex, setHighlightedSuggestionIndex] = useState(0);
|
const [highlightedSuggestionIndex, setHighlightedSuggestionIndex] = useState(0);
|
||||||
|
|
||||||
const { suggestionQuery, suggestionMode } = useMemo<{ suggestionQuery?: string, suggestionMode?: SuggestionMode }>(() => {
|
const { suggestionQuery, suggestionMode } = useMemo<{ suggestionQuery?: string, suggestionMode?: SuggestionMode }>(() => {
|
||||||
|
// Only re-calculate the suggestion mode and query if the box is enabled.
|
||||||
|
// This is to avoid transitioning the suggestion mode and causing a fetch
|
||||||
|
// when it is not needed.
|
||||||
|
// @see: useSuggestionsData.ts
|
||||||
|
if (!isEnabled) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
const { queryParts, cursorIndex } = splitQuery(query, cursorPosition);
|
const { queryParts, cursorIndex } = splitQuery(query, cursorPosition);
|
||||||
if (queryParts.length === 0) {
|
if (queryParts.length === 0) {
|
||||||
return {};
|
return {};
|
||||||
|
|
@ -107,10 +118,10 @@ const SearchSuggestionsBox = forwardRef(({
|
||||||
suggestionQuery: part,
|
suggestionQuery: part,
|
||||||
suggestionMode: "refine",
|
suggestionMode: "refine",
|
||||||
}
|
}
|
||||||
}, [cursorPosition, query]);
|
}, [cursorPosition, isEnabled, query]);
|
||||||
|
|
||||||
const { suggestions, isHighlightEnabled, Icon, onSuggestionClicked } = useMemo(() => {
|
const { suggestions, isHighlightEnabled, DefaultIcon, onSuggestionClicked } = useMemo(() => {
|
||||||
if (!isDefined(suggestionQuery) || !isDefined(suggestionMode)) {
|
if (!isEnabled || !isDefined(suggestionQuery) || !isDefined(suggestionMode)) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,7 +155,7 @@ const SearchSuggestionsBox = forwardRef(({
|
||||||
isSpotlightEnabled = false,
|
isSpotlightEnabled = false,
|
||||||
isClientSideSearchEnabled = true,
|
isClientSideSearchEnabled = true,
|
||||||
onSuggestionClicked,
|
onSuggestionClicked,
|
||||||
Icon,
|
DefaultIcon,
|
||||||
} = ((): {
|
} = ((): {
|
||||||
threshold?: number,
|
threshold?: number,
|
||||||
limit?: number,
|
limit?: number,
|
||||||
|
|
@ -153,7 +164,7 @@ const SearchSuggestionsBox = forwardRef(({
|
||||||
isSpotlightEnabled?: boolean,
|
isSpotlightEnabled?: boolean,
|
||||||
isClientSideSearchEnabled?: boolean,
|
isClientSideSearchEnabled?: boolean,
|
||||||
onSuggestionClicked: (value: string) => void,
|
onSuggestionClicked: (value: string) => void,
|
||||||
Icon?: Icon
|
DefaultIcon?: IconType
|
||||||
} => {
|
} => {
|
||||||
switch (suggestionMode) {
|
switch (suggestionMode) {
|
||||||
case "public":
|
case "public":
|
||||||
|
|
@ -178,13 +189,13 @@ const SearchSuggestionsBox = forwardRef(({
|
||||||
}
|
}
|
||||||
case "repo":
|
case "repo":
|
||||||
return {
|
return {
|
||||||
list: data.repos,
|
list: repoSuggestions,
|
||||||
Icon: CommitIcon,
|
DefaultIcon: VscRepo,
|
||||||
onSuggestionClicked: createOnSuggestionClickedHandler({ regexEscaped: true }),
|
onSuggestionClicked: createOnSuggestionClickedHandler({ regexEscaped: true }),
|
||||||
}
|
}
|
||||||
case "language": {
|
case "language": {
|
||||||
return {
|
return {
|
||||||
list: data.languages,
|
list: languageSuggestions,
|
||||||
onSuggestionClicked: createOnSuggestionClickedHandler(),
|
onSuggestionClicked: createOnSuggestionClickedHandler(),
|
||||||
isSpotlightEnabled: true,
|
isSpotlightEnabled: true,
|
||||||
}
|
}
|
||||||
|
|
@ -195,18 +206,25 @@ const SearchSuggestionsBox = forwardRef(({
|
||||||
list: refineModeSuggestions,
|
list: refineModeSuggestions,
|
||||||
isHighlightEnabled: true,
|
isHighlightEnabled: true,
|
||||||
isSpotlightEnabled: true,
|
isSpotlightEnabled: true,
|
||||||
Icon: MixerVerticalIcon,
|
DefaultIcon: VscFilter,
|
||||||
onSuggestionClicked: createOnSuggestionClickedHandler({ trailingSpace: false }),
|
onSuggestionClicked: createOnSuggestionClickedHandler({ trailingSpace: false }),
|
||||||
}
|
}
|
||||||
case "file":
|
case "file":
|
||||||
return {
|
return {
|
||||||
list: data.files,
|
list: fileSuggestions,
|
||||||
onSuggestionClicked: createOnSuggestionClickedHandler(),
|
onSuggestionClicked: createOnSuggestionClickedHandler(),
|
||||||
isClientSideSearchEnabled: false,
|
isClientSideSearchEnabled: false,
|
||||||
|
DefaultIcon: VscFile,
|
||||||
|
}
|
||||||
|
case "symbol":
|
||||||
|
return {
|
||||||
|
list: symbolSuggestions,
|
||||||
|
onSuggestionClicked: createOnSuggestionClickedHandler(),
|
||||||
|
isClientSideSearchEnabled: false,
|
||||||
|
DefaultIcon: VscSymbolMisc,
|
||||||
}
|
}
|
||||||
case "revision":
|
case "revision":
|
||||||
case "content":
|
case "content":
|
||||||
case "symbol":
|
|
||||||
return {
|
return {
|
||||||
list: [],
|
list: [],
|
||||||
onSuggestionClicked: createOnSuggestionClickedHandler(),
|
onSuggestionClicked: createOnSuggestionClickedHandler(),
|
||||||
|
|
@ -252,11 +270,11 @@ const SearchSuggestionsBox = forwardRef(({
|
||||||
return {
|
return {
|
||||||
suggestions,
|
suggestions,
|
||||||
isHighlightEnabled,
|
isHighlightEnabled,
|
||||||
Icon,
|
DefaultIcon,
|
||||||
onSuggestionClicked,
|
onSuggestionClicked,
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [suggestionQuery, suggestionMode, query, cursorPosition, onCompletion, data.repos, data.files, data.languages]);
|
}, [isEnabled, suggestionQuery, suggestionMode, query, cursorPosition, onCompletion, repoSuggestions, fileSuggestions, symbolSuggestions, languageSuggestions]);
|
||||||
|
|
||||||
// When the list of suggestions change, reset the highlight index
|
// When the list of suggestions change, reset the highlight index
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -283,7 +301,13 @@ const SearchSuggestionsBox = forwardRef(({
|
||||||
case "repo":
|
case "repo":
|
||||||
return "Repositories";
|
return "Repositories";
|
||||||
case "refine":
|
case "refine":
|
||||||
return "Refine search"
|
return "Refine search";
|
||||||
|
case "file":
|
||||||
|
return "Files";
|
||||||
|
case "symbol":
|
||||||
|
return "Symbols";
|
||||||
|
case "language":
|
||||||
|
return "Languages";
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
@ -291,12 +315,15 @@ const SearchSuggestionsBox = forwardRef(({
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!isEnabled ||
|
!isEnabled ||
|
||||||
!suggestions ||
|
!suggestions
|
||||||
suggestions.length === 0
|
|
||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (suggestions.length === 0 && !isLoadingSuggestions) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|
@ -305,6 +332,9 @@ const SearchSuggestionsBox = forwardRef(({
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
if (highlightedSuggestionIndex < 0 || highlightedSuggestionIndex >= suggestions.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const value = suggestions[highlightedSuggestionIndex].value;
|
const value = suggestions[highlightedSuggestionIndex].value;
|
||||||
onSuggestionClicked(value);
|
onSuggestionClicked(value);
|
||||||
}
|
}
|
||||||
|
|
@ -334,7 +364,17 @@ const SearchSuggestionsBox = forwardRef(({
|
||||||
<p className="text-muted-foreground text-sm mb-1">
|
<p className="text-muted-foreground text-sm mb-1">
|
||||||
{suggestionModeText}
|
{suggestionModeText}
|
||||||
</p>
|
</p>
|
||||||
{suggestions.map((result, index) => (
|
{isLoadingSuggestions ? (
|
||||||
|
// Skeleton placeholder
|
||||||
|
<div className="animate-pulse flex flex-col gap-2 px-1 py-0.5">
|
||||||
|
{
|
||||||
|
Array.from({ length: 10 }).map((_, index) => (
|
||||||
|
<div key={index} className="h-4 bg-muted rounded-md w-full"></div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
) : suggestions.map((result, index) => (
|
||||||
|
// Suggestion list
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={clsx("flex flex-row items-center font-mono text-sm hover:bg-muted rounded-md px-1 py-0.5 cursor-pointer", {
|
className={clsx("flex flex-row items-center font-mono text-sm hover:bg-muted rounded-md px-1 py-0.5 cursor-pointer", {
|
||||||
|
|
@ -345,23 +385,24 @@ const SearchSuggestionsBox = forwardRef(({
|
||||||
onSuggestionClicked(result.value)
|
onSuggestionClicked(result.value)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Icon && (
|
{result.Icon ? (
|
||||||
<Icon className="w-3 h-3 mr-2" />
|
<result.Icon className="w-3 h-3 mr-2 flex-none" />
|
||||||
)}
|
) : DefaultIcon ? (
|
||||||
<div className="flex flex-row items-center">
|
<DefaultIcon className="w-3 h-3 mr-2 flex-none" />
|
||||||
<span
|
) : null}
|
||||||
className={clsx('mr-2 flex-none', {
|
<span
|
||||||
"text-highlight": isHighlightEnabled
|
className={clsx('mr-2', {
|
||||||
})}
|
"text-highlight": isHighlightEnabled,
|
||||||
>
|
"truncate": !result.description,
|
||||||
{result.value}
|
})}
|
||||||
|
>
|
||||||
|
{result.value}
|
||||||
|
</span>
|
||||||
|
{result.description && (
|
||||||
|
<span className="text-muted-foreground font-light">
|
||||||
|
{result.description}
|
||||||
</span>
|
</span>
|
||||||
{result.description && (
|
)}
|
||||||
<span className="text-muted-foreground font-light">
|
|
||||||
{result.description}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{isFocused && (
|
{isFocused && (
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,20 @@ import { useQuery } from "@tanstack/react-query";
|
||||||
import { Suggestion, SuggestionMode } from "./searchSuggestionsBox";
|
import { Suggestion, SuggestionMode } from "./searchSuggestionsBox";
|
||||||
import { getRepos, search } from "@/app/api/(client)/client";
|
import { getRepos, search } from "@/app/api/(client)/client";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
import { Symbol } from "@/lib/types";
|
||||||
import languages from "./languages";
|
import languages from "./languages";
|
||||||
|
import {
|
||||||
|
VscSymbolClass,
|
||||||
|
VscSymbolConstant,
|
||||||
|
VscSymbolEnum,
|
||||||
|
VscSymbolField,
|
||||||
|
VscSymbolInterface,
|
||||||
|
VscSymbolMethod,
|
||||||
|
VscSymbolProperty,
|
||||||
|
VscSymbolStructure,
|
||||||
|
VscSymbolVariable
|
||||||
|
} from "react-icons/vsc";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
suggestionMode: SuggestionMode;
|
suggestionMode: SuggestionMode;
|
||||||
|
|
@ -18,7 +31,7 @@ export const useSuggestionsData = ({
|
||||||
suggestionMode,
|
suggestionMode,
|
||||||
suggestionQuery,
|
suggestionQuery,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { data: repoSuggestions } = useQuery({
|
const { data: repoSuggestions, isLoading: _isLoadingRepos } = useQuery({
|
||||||
queryKey: ["repoSuggestions"],
|
queryKey: ["repoSuggestions"],
|
||||||
queryFn: getRepos,
|
queryFn: getRepos,
|
||||||
select: (data): Suggestion[] => {
|
select: (data): Suggestion[] => {
|
||||||
|
|
@ -30,8 +43,9 @@ export const useSuggestionsData = ({
|
||||||
},
|
},
|
||||||
enabled: suggestionMode === "repo",
|
enabled: suggestionMode === "repo",
|
||||||
});
|
});
|
||||||
|
const isLoadingRepos = useMemo(() => suggestionMode === "repo" && _isLoadingRepos, [_isLoadingRepos, suggestionMode]);
|
||||||
|
|
||||||
const { data: fileSuggestions } = useQuery({
|
const { data: fileSuggestions, isLoading: _isLoadingFiles } = useQuery({
|
||||||
queryKey: ["fileSuggestions", suggestionQuery],
|
queryKey: ["fileSuggestions", suggestionQuery],
|
||||||
queryFn: () => search({
|
queryFn: () => search({
|
||||||
query: `file:${suggestionQuery}`,
|
query: `file:${suggestionQuery}`,
|
||||||
|
|
@ -44,6 +58,32 @@ export const useSuggestionsData = ({
|
||||||
},
|
},
|
||||||
enabled: suggestionMode === "file"
|
enabled: suggestionMode === "file"
|
||||||
});
|
});
|
||||||
|
const isLoadingFiles = useMemo(() => suggestionMode === "file" && _isLoadingFiles, [_isLoadingFiles, suggestionMode]);
|
||||||
|
|
||||||
|
const { data: symbolSuggestions, isLoading: _isLoadingSymbols } = useQuery({
|
||||||
|
queryKey: ["symbolSuggestions", suggestionQuery],
|
||||||
|
queryFn: () => search({
|
||||||
|
query: `sym:${suggestionQuery.length > 0 ? suggestionQuery : ".*"}`,
|
||||||
|
maxMatchDisplayCount: 15,
|
||||||
|
}),
|
||||||
|
select: (data): Suggestion[] => {
|
||||||
|
const symbols = data.Result.Files?.flatMap((file) => file.ChunkMatches).flatMap((chunk) => chunk.SymbolInfo ?? []);
|
||||||
|
if (!symbols) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// De-duplicate on symbol name & kind.
|
||||||
|
const symbolMap = new Map<string, Symbol>(symbols.map((symbol: Symbol) => [`${symbol.Kind}.${symbol.Sym}`, symbol]));
|
||||||
|
const suggestions = Array.from(symbolMap.values()).map((symbol) => ({
|
||||||
|
value: symbol.Sym,
|
||||||
|
Icon: getSymbolIcon(symbol),
|
||||||
|
} satisfies Suggestion));
|
||||||
|
|
||||||
|
return suggestions;
|
||||||
|
},
|
||||||
|
enabled: suggestionMode === "symbol",
|
||||||
|
});
|
||||||
|
const isLoadingSymbols = useMemo(() => suggestionMode === "symbol" && _isLoadingSymbols, [suggestionMode, _isLoadingSymbols]);
|
||||||
|
|
||||||
const languageSuggestions = useMemo((): Suggestion[] => {
|
const languageSuggestions = useMemo((): Suggestion[] => {
|
||||||
return languages.map((lang) => {
|
return languages.map((lang) => {
|
||||||
|
|
@ -63,13 +103,45 @@ export const useSuggestionsData = ({
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const data = useMemo(() => {
|
const isLoadingSuggestions = useMemo(() => {
|
||||||
return {
|
return isLoadingSymbols || isLoadingFiles || isLoadingRepos;
|
||||||
repos: repoSuggestions ?? [],
|
}, [isLoadingFiles, isLoadingRepos, isLoadingSymbols]);
|
||||||
languages: languageSuggestions,
|
|
||||||
files: fileSuggestions ?? [],
|
|
||||||
}
|
|
||||||
}, [repoSuggestions, fileSuggestions, languageSuggestions]);
|
|
||||||
|
|
||||||
return data;
|
return {
|
||||||
|
repoSuggestions: repoSuggestions ?? [],
|
||||||
|
fileSuggestions: fileSuggestions ?? [],
|
||||||
|
symbolSuggestions: symbolSuggestions ?? [],
|
||||||
|
languageSuggestions,
|
||||||
|
isLoadingSuggestions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSymbolIcon = (symbol: Symbol) => {
|
||||||
|
switch (symbol.Kind) {
|
||||||
|
case "methodSpec":
|
||||||
|
case "method":
|
||||||
|
case "function":
|
||||||
|
case "func":
|
||||||
|
return VscSymbolMethod;
|
||||||
|
case "variable":
|
||||||
|
return VscSymbolVariable;
|
||||||
|
case "class":
|
||||||
|
return VscSymbolClass;
|
||||||
|
case "const":
|
||||||
|
case "macro":
|
||||||
|
case "constant":
|
||||||
|
return VscSymbolConstant;
|
||||||
|
case "property":
|
||||||
|
return VscSymbolProperty;
|
||||||
|
case "struct":
|
||||||
|
return VscSymbolStructure;
|
||||||
|
case "field":
|
||||||
|
case "member":
|
||||||
|
return VscSymbolField;
|
||||||
|
case "interface":
|
||||||
|
return VscSymbolInterface;
|
||||||
|
case "enum":
|
||||||
|
case "enumerator":
|
||||||
|
return VscSymbolEnum;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -46,6 +46,13 @@ export const searchResponseStats = {
|
||||||
FlushReason: z.number(),
|
FlushReason: z.number(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const symbolSchema = z.object({
|
||||||
|
Sym: z.string(),
|
||||||
|
Kind: z.string(),
|
||||||
|
Parent: z.string(),
|
||||||
|
ParentKind: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
// @see : https://github.com/sourcebot-dev/zoekt/blob/3780e68cdb537d5a7ed2c84d9b3784f80c7c5d04/api.go#L497
|
// @see : https://github.com/sourcebot-dev/zoekt/blob/3780e68cdb537d5a7ed2c84d9b3784f80c7c5d04/api.go#L497
|
||||||
export const zoektSearchResponseSchema = z.object({
|
export const zoektSearchResponseSchema = z.object({
|
||||||
Result: z.object({
|
Result: z.object({
|
||||||
|
|
@ -62,6 +69,7 @@ export const zoektSearchResponseSchema = z.object({
|
||||||
FileName: z.boolean(),
|
FileName: z.boolean(),
|
||||||
ContentStart: locationSchema,
|
ContentStart: locationSchema,
|
||||||
Score: z.number(),
|
Score: z.number(),
|
||||||
|
SymbolInfo: z.array(symbolSchema).nullable(),
|
||||||
})),
|
})),
|
||||||
Checksum: z.string(),
|
Checksum: z.string(),
|
||||||
Score: z.number(),
|
Score: z.number(),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { fileSourceRequestSchema, fileSourceResponseSchema, listRepositoriesResponseSchema, locationSchema, rangeSchema, repositorySchema, searchRequestSchema, searchResponseSchema } from "./schemas";
|
import { fileSourceRequestSchema, fileSourceResponseSchema, listRepositoriesResponseSchema, locationSchema, rangeSchema, repositorySchema, searchRequestSchema, searchResponseSchema, symbolSchema } from "./schemas";
|
||||||
|
|
||||||
export type KeymapType = "default" | "vim";
|
export type KeymapType = "default" | "vim";
|
||||||
|
|
||||||
|
|
@ -18,6 +18,8 @@ export type FileSourceResponse = z.infer<typeof fileSourceResponseSchema>;
|
||||||
export type ListRepositoriesResponse = z.infer<typeof listRepositoriesResponseSchema>;
|
export type ListRepositoriesResponse = z.infer<typeof listRepositoriesResponseSchema>;
|
||||||
export type Repository = z.infer<typeof repositorySchema>;
|
export type Repository = z.infer<typeof repositorySchema>;
|
||||||
|
|
||||||
|
export type Symbol = z.infer<typeof symbolSchema>;
|
||||||
|
|
||||||
export enum SearchQueryParams {
|
export enum SearchQueryParams {
|
||||||
query = "query",
|
query = "query",
|
||||||
maxMatchDisplayCount = "maxMatchDisplayCount",
|
maxMatchDisplayCount = "maxMatchDisplayCount",
|
||||||
|
|
|
||||||
25
yarn.lock
25
yarn.lock
|
|
@ -4604,6 +4604,11 @@ react-hotkeys-hook@^4.5.1:
|
||||||
resolved "https://registry.yarnpkg.com/react-hotkeys-hook/-/react-hotkeys-hook-4.5.1.tgz#990260ecc7e5a431414148a93b02a2f1a9707897"
|
resolved "https://registry.yarnpkg.com/react-hotkeys-hook/-/react-hotkeys-hook-4.5.1.tgz#990260ecc7e5a431414148a93b02a2f1a9707897"
|
||||||
integrity sha512-scAEJOh3Irm0g95NIn6+tQVf/OICCjsQsC9NBHfQws/Vxw4sfq1tDQut5fhTEvPraXhu/sHxRd9lOtxzyYuNAg==
|
integrity sha512-scAEJOh3Irm0g95NIn6+tQVf/OICCjsQsC9NBHfQws/Vxw4sfq1tDQut5fhTEvPraXhu/sHxRd9lOtxzyYuNAg==
|
||||||
|
|
||||||
|
react-icons@^5.3.0:
|
||||||
|
version "5.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.3.0.tgz#ccad07a30aebd40a89f8cfa7d82e466019203f1c"
|
||||||
|
integrity sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==
|
||||||
|
|
||||||
react-is@^16.13.1:
|
react-is@^16.13.1:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
|
|
@ -5075,16 +5080,7 @@ string-argv@^0.3.1:
|
||||||
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
|
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
|
||||||
integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
|
integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0":
|
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
|
||||||
version "4.2.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
|
||||||
dependencies:
|
|
||||||
emoji-regex "^8.0.0"
|
|
||||||
is-fullwidth-code-point "^3.0.0"
|
|
||||||
strip-ansi "^6.0.1"
|
|
||||||
|
|
||||||
string-width@^4.1.0:
|
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
|
|
@ -5181,14 +5177,7 @@ string_decoder@^1.1.1, string_decoder@^1.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "~5.2.0"
|
safe-buffer "~5.2.0"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||||
version "6.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
|
||||||
dependencies:
|
|
||||||
ansi-regex "^5.0.1"
|
|
||||||
|
|
||||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue