Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed
- Changed the default `/repos` pagination size to 20. [#706](https://github.com/sourcebot-dev/sourcebot/pull/706)
- Added checkbox to exclude archived and forked repositories from settings dropdown in the query search tab. [#663](https://github.com/sourcebot-dev/sourcebot/issues/663)

## [4.10.7] - 2025-12-29

Expand Down
2 changes: 2 additions & 0 deletions packages/mcp/src/schemas.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,8 @@ export const searchOptionsSchema = z.object({
whole: z.boolean().optional(), // Whether to return the whole file as part of the response.
isRegexEnabled: z.boolean().optional(), // Whether to enable regular expression search.
isCaseSensitivityEnabled: z.boolean().optional(), // Whether to enable case sensitivity.
isArchivedExcluded: z.boolean().optional(), // Whether to exclude archived repositories.
isForkedExcluded: z.boolean().optional(), // Whether to exclude forked repositories.
});

export const searchRequestSchema = z.object({
Expand Down
53 changes: 51 additions & 2 deletions packages/web/src/app/[domain]/components/searchBar/searchBar.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,14 +44,23 @@ import{Toggle } from "@/components/ui/toggle"
import{useDomain } from "@/hooks/useDomain"
import{createAuditAction } from "@/ee/features/audit/actions"
import tailwind from "@/tailwind"
import{CaseSensitiveIcon, RegexIcon } from "lucide-react"
import{CaseSensitiveIcon, RegexIcon, Settings2 } from "lucide-react"
import{
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuCheckboxItem,
} from "@/components/ui/dropdown-menu"

interface SearchBarProps{
className?: string;
size?: "default" | "sm"
defaults?:{
isRegexEnabled?: boolean;
isCaseSensitivityEnabled?: boolean;
isArchivedExcluded?: boolean;
isForkedExcluded?: boolean;
query?: string;
}
autoFocus?: boolean;
Expand DownExpand Up@@ -99,6 +108,8 @@ export const SearchBar = ({
defaults:{
isRegexEnabled: defaultIsRegexEnabled = false,
isCaseSensitivityEnabled: defaultIsCaseSensitivityEnabled = false,
isArchivedExcluded: defaultIsArchivedExcluded = false,
isForkedExcluded: defaultIsForkedExcluded = false,
query: defaultQuery = "",
} ={}
}: SearchBarProps) =>{
Expand All@@ -112,6 +123,8 @@ export const SearchBar = ({
const [isHistorySearchEnabled, setIsHistorySearchEnabled] = useState(false);
const [isRegexEnabled, setIsRegexEnabled] = useState(defaultIsRegexEnabled);
const [isCaseSensitivityEnabled, setIsCaseSensitivityEnabled] = useState(defaultIsCaseSensitivityEnabled);
const [isArchivedExcluded, setIsArchivedExcluded] = useState(defaultIsArchivedExcluded);
const [isForkedExcluded, setIsForkedExcluded] = useState(defaultIsForkedExcluded);

const focusEditor = useCallback(() => editorRef.current?.view?.focus(), []);
const focusSuggestionsBox = useCallback(() => suggestionBoxRef.current?.focus(), []);
Expand DownExpand Up@@ -227,9 +240,11 @@ export const SearchBar = ({
[SearchQueryParams.query, query],
[SearchQueryParams.isRegexEnabled, isRegexEnabled ? "true" : null],
[SearchQueryParams.isCaseSensitivityEnabled, isCaseSensitivityEnabled ? "true" : null],
[SearchQueryParams.isArchivedExcluded, isArchivedExcluded ? "true" : null],
[SearchQueryParams.isForkedExcluded, isForkedExcluded ? "true" : null],
);
router.push(url);
}, [domain, router, isRegexEnabled, isCaseSensitivityEnabled]);
}, [domain, router, isRegexEnabled, isCaseSensitivityEnabled, isArchivedExcluded, isForkedExcluded]);

return (
<div
Expand DownExpand Up@@ -288,6 +303,40 @@ export const SearchBar = ({
autoFocus={autoFocus ?? false}
/>
<div className="flex flex-row items-center gap-1 ml-1">
<DropdownMenu>
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuTrigger asChild>
<span>
<Toggle
className="h-7 w-7 min-w-7 p-0 cursor-pointer"
pressed={false}
onPressedChange={() =>{}}
>
<Settings2 className="w-4 h-4" />
</Toggle>
</span>
</DropdownMenuTrigger>
</TooltipTrigger>
<TooltipContent side="bottom">Search settings</TooltipContent>
</Tooltip>

<DropdownMenuContent align="start">
<DropdownMenuLabel>Search</DropdownMenuLabel>
<DropdownMenuCheckboxItem
checked={isArchivedExcluded}
onCheckedChange={(v: boolean | "mixed") => setIsArchivedExcluded(Boolean(v))}
>
Exclude archived repositories
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={isForkedExcluded}
onCheckedChange={(v: boolean | "mixed") => setIsForkedExcluded(Boolean(v))}
>
Exclude forked repositories
</DropdownMenuCheckboxItem>
</DropdownMenuContent>
</DropdownMenu>
<Tooltip>
<TooltipTrigger asChild>
<span>
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,13 +40,17 @@ interface SearchResultsPageProps{
defaultMaxMatchCount: number;
isRegexEnabled: boolean;
isCaseSensitivityEnabled: boolean;
isArchivedExcluded: boolean;
isForkedExcluded: boolean;
}

export const SearchResultsPage = ({
searchQuery,
defaultMaxMatchCount,
isRegexEnabled,
isCaseSensitivityEnabled,
isArchivedExcluded,
isForkedExcluded,
}: SearchResultsPageProps) =>{
const router = useRouter();
const{setSearchHistory } = useSearchHistory();
Expand DownExpand Up@@ -75,6 +79,8 @@ export const SearchResultsPage = ({
whole: false,
isRegexEnabled,
isCaseSensitivityEnabled,
isArchivedExcluded,
isForkedExcluded,
});

useEffect(() =>{
Expand DownExpand Up@@ -175,6 +181,8 @@ export const SearchResultsPage = ({
defaults={{
isRegexEnabled,
isCaseSensitivityEnabled,
isArchivedExcluded,
isForkedExcluded,
query: searchQuery,
}}
className="w-full"
Expand Down
6 changes: 6 additions & 0 deletions packages/web/src/app/[domain]/search/page.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,8 @@ interface SearchPageProps{
query?: string;
isRegexEnabled?: "true" | "false"
isCaseSensitivityEnabled?: "true" | "false"
isArchivedExcluded?: "true" | "false"
isForkedExcluded?: "true" | "false"
}>
}

Expand All@@ -17,6 +19,8 @@ export default async function SearchPage(props: SearchPageProps){
const query = searchParams?.query;
const isRegexEnabled = searchParams?.isRegexEnabled === "true"
const isCaseSensitivityEnabled = searchParams?.isCaseSensitivityEnabled === "true"
const isArchivedExcluded = searchParams?.isArchivedExcluded === "true"
const isForkedExcluded = searchParams?.isForkedExcluded === "true"

if (query === undefined || query.length === 0){
return <SearchLandingPage domain={domain} />
Expand All@@ -28,6 +32,8 @@ export default async function SearchPage(props: SearchPageProps){
defaultMaxMatchCount={env.DEFAULT_MAX_MATCH_COUNT}
isRegexEnabled={isRegexEnabled}
isCaseSensitivityEnabled={isCaseSensitivityEnabled}
isArchivedExcluded={isArchivedExcluded}
isForkedExcluded={isForkedExcluded}
/>
)
}
10 changes: 9 additions & 1 deletion packages/web/src/app/[domain]/search/useStreamedSearch.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,14 +27,16 @@ const createCacheKey = (params: SearchRequest): string =>{
whole: params.whole,
isRegexEnabled: params.isRegexEnabled,
isCaseSensitivityEnabled: params.isCaseSensitivityEnabled,
isArchivedExcluded: params.isArchivedExcluded,
isForkedExcluded: params.isForkedExcluded,
});
};

const isCacheValid = (entry: CacheEntry): boolean =>{
return Date.now() - entry.timestamp < CACHE_TTL;
};

export const useStreamedSearch = ({query, matches, contextLines, whole, isRegexEnabled, isCaseSensitivityEnabled }: SearchRequest) =>{
export const useStreamedSearch = ({query, matches, contextLines, whole, isRegexEnabled, isCaseSensitivityEnabled, isArchivedExcluded, isForkedExcluded }: SearchRequest) =>{
const [state, setState] = useState<{
isStreaming: boolean,
isExhaustive: boolean,
Expand DownExpand Up@@ -86,6 +88,8 @@ export const useStreamedSearch = ({query, matches, contextLines, whole, isRegex
whole,
isRegexEnabled,
isCaseSensitivityEnabled,
isArchivedExcluded,
isForkedExcluded,
});

// Check if we have a valid cached result. If so, use it.
Expand DownExpand Up@@ -129,6 +133,8 @@ export const useStreamedSearch = ({query, matches, contextLines, whole, isRegex
whole,
isRegexEnabled,
isCaseSensitivityEnabled,
isArchivedExcluded,
isForkedExcluded,
source: 'sourcebot-web-client'
} satisfies SearchRequest),
signal: abortControllerRef.current.signal,
Expand DownExpand Up@@ -279,6 +285,8 @@ export const useStreamedSearch = ({query, matches, contextLines, whole, isRegex
whole,
isRegexEnabled,
isCaseSensitivityEnabled,
isArchivedExcluded,
isForkedExcluded,
cancel,
]);

Expand Down
6 changes: 3 additions & 3 deletions packages/web/src/components/ui/dropdown-menu.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -105,9 +105,9 @@ const DropdownMenuCheckboxItem = React.forwardRef<
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
<span className="absolute left-2 flex h-4 w-4 items-center justify-center rounded-sm border border-muted bg-transparent">
<DropdownMenuPrimitive.ItemIndicator className="flex items-center justify-center text-current">
<Check className="h-3.5 w-3.5" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
Expand Down
61 changes: 60 additions & 1 deletion packages/web/src/features/search/parser.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -62,6 +62,8 @@ export const parseQuerySyntaxIntoIR = async ({
options:{
isCaseSensitivityEnabled?: boolean;
isRegexEnabled?: boolean;
isArchivedExcluded?: boolean;
isForkedExcluded?: boolean;
},
prisma: PrismaClient,
}): Promise<QueryIR> =>{
Expand All@@ -76,6 +78,8 @@ export const parseQuerySyntaxIntoIR = async ({
input: query,
isCaseSensitivityEnabled: options.isCaseSensitivityEnabled ?? false,
isRegexEnabled: options.isRegexEnabled ?? false,
isArchivedExcluded: options.isArchivedExcluded ?? false,
isForkedExcluded: options.isForkedExcluded ?? false,
onExpandSearchContext: async (contextName: string) =>{
const context = await prisma.searchContext.findUnique({
where:{
Expand DownExpand Up@@ -116,12 +120,16 @@ const transformTreeToIR = async ({
input,
isCaseSensitivityEnabled,
isRegexEnabled,
isArchivedExcluded,
isForkedExcluded,
onExpandSearchContext,
}:{
tree: Tree;
input: string;
isCaseSensitivityEnabled: boolean;
isRegexEnabled: boolean;
isArchivedExcluded: boolean;
isForkedExcluded: boolean;
onExpandSearchContext: (contextName: string) => Promise<string[]>
}): Promise<QueryIR> =>{
const transformNode = async (node: SyntaxNode): Promise<QueryIR> =>{
Expand DownExpand Up@@ -320,6 +328,9 @@ const transformTreeToIR = async ({
}

case ArchivedExpr:{
// We'll set the value of isArchivedExcluded to false as the query takes precedence over checkbox.
isArchivedExcluded = false;

const rawValue = value.toLowerCase();

if (!isArchivedValue(rawValue)){
Expand All@@ -344,6 +355,9 @@ const transformTreeToIR = async ({
};
}
case ForkExpr:{
// We'll set the value of isForkedExcluded to false as the query takes precedence over checkbox.
isForkedExcluded = false;

const rawValue = value.toLowerCase();

if (!isForkValue(rawValue)){
Expand DownExpand Up@@ -397,7 +411,52 @@ const transformTreeToIR = async ({
}
}

return transformNode(tree.topNode);

// return await transformNode(tree.topNode);
const root = await transformNode(tree.topNode);

// If the tree does not contain explicit archived/fork prefixes, add
// default raw_config flags to exclude archived/forks by default.
const defaultNodes: QueryIR[] = [];

if (isArchivedExcluded){
defaultNodes.push({
raw_config:{
flags: ['FLAG_NO_ARCHIVED']
},
query: 'raw_config'
});
}

if (isForkedExcluded){
defaultNodes.push({
raw_config:{
flags: ['FLAG_NO_FORKS']
},
query: 'raw_config'
});
}

if (defaultNodes.length === 0){
return root;
}

// If the root is already an AND, append defaults; otherwise create an AND
if (root.and && Array.isArray(root.and.children)){
return{
and:{
children: [...root.and.children, ...defaultNodes]
},
query: 'and'
};
}

return{
and:{
children: [root, ...defaultNodes]
},
query: 'and'
};
}

const getChildren = (node: SyntaxNode): SyntaxNode[] =>{
Expand Down
2 changes: 2 additions & 0 deletions packages/web/src/features/search/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -89,6 +89,8 @@ export const searchOptionsSchema = z.object({
whole: z.boolean().optional(), // Whether to return the whole file as part of the response.
isRegexEnabled: z.boolean().optional(), // Whether to enable regular expression search.
isCaseSensitivityEnabled: z.boolean().optional(), // Whether to enable case sensitivity.
isArchivedExcluded: z.boolean().optional(), // Whether to exclude archived repositories.
isForkedExcluded: z.boolean().optional(), // Whether to exclude forked repositories.
});
export type SearchOptions = z.infer<typeof searchOptionsSchema>

Expand Down
2 changes: 2 additions & 0 deletions packages/web/src/lib/types.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,6 +11,8 @@ export enum SearchQueryParams{
matches = "matches",
isRegexEnabled = "isRegexEnabled",
isCaseSensitivityEnabled = "isCaseSensitivityEnabled",
isArchivedExcluded = "isArchivedExcluded",
isForkedExcluded = "isForkedExcluded",
}

export type ApiKeyPayload ={
Expand Down