support search contexts

This commit is contained in:
bkellam 2025-11-17 18:47:13 -08:00
parent e6859cbf5c
commit 307e17e8d6
7 changed files with 92 additions and 27 deletions

View file

@ -1,6 +1,6 @@
// This file was generated by lezer-generator. You probably shouldn't edit it. // This file was generated by lezer-generator. You probably shouldn't edit it.
export const export const
negate = 21, negate = 22,
Program = 1, Program = 1,
OrExpr = 2, OrExpr = 2,
AndExpr = 3, AndExpr = 3,
@ -9,12 +9,13 @@ export const
ArchivedExpr = 6, ArchivedExpr = 6,
RevisionExpr = 7, RevisionExpr = 7,
ContentExpr = 8, ContentExpr = 8,
FileExpr = 9, ContextExpr = 9,
ForkExpr = 10, FileExpr = 10,
VisibilityExpr = 11, ForkExpr = 11,
RepoExpr = 12, VisibilityExpr = 12,
LangExpr = 13, RepoExpr = 13,
SymExpr = 14, LangExpr = 14,
RepoSetExpr = 15, SymExpr = 15,
ParenExpr = 16, RepoSetExpr = 16,
Term = 17 ParenExpr = 17,
Term = 18

File diff suppressed because one or more lines are too long

View file

@ -35,6 +35,7 @@ PrefixExpr {
ArchivedExpr | ArchivedExpr |
RevisionExpr | RevisionExpr |
ContentExpr | ContentExpr |
ContextExpr |
FileExpr | FileExpr |
ForkExpr | ForkExpr |
VisibilityExpr | VisibilityExpr |
@ -46,6 +47,7 @@ PrefixExpr {
RevisionExpr { revisionKw value } RevisionExpr { revisionKw value }
ContentExpr { contentKw value } ContentExpr { contentKw value }
ContextExpr { contextKw value }
FileExpr { fileKw value } FileExpr { fileKw value }
RepoExpr { repoKw value } RepoExpr { repoKw value }
LangExpr { langKw value } LangExpr { langKw value }
@ -71,6 +73,7 @@ value { quotedString | word }
archivedKw { "archived:" } archivedKw { "archived:" }
revisionKw { "rev:" } revisionKw { "rev:" }
contentKw { "content:" | "c:" } contentKw { "content:" | "c:" }
contextKw { "context:" }
fileKw { "file:" | "f:" } fileKw { "file:" | "f:" }
forkKw { "fork:" } forkKw { "fork:" }
visibilityKw { "visibility:" } visibilityKw { "visibility:" }
@ -91,7 +94,7 @@ value { quotedString | word }
@precedence { @precedence {
quotedString, quotedString,
archivedKw, revisionKw, contentKw, fileKw, archivedKw, revisionKw, contentKw, contextKw, fileKw,
forkKw, visibilityKw, repoKw, langKw, forkKw, visibilityKw, repoKw, langKw,
symKw, reposetKw, or, symKw, reposetKw, or,
word word

View file

@ -94,6 +94,14 @@ Program(NegateExpr(PrefixExpr(ForkExpr)))
Program(NegateExpr(PrefixExpr(VisibilityExpr))) Program(NegateExpr(PrefixExpr(VisibilityExpr)))
# Negate context prefix
-context:backend
==>
Program(NegateExpr(PrefixExpr(ContextExpr)))
# Negate symbol prefix # Negate symbol prefix
-sym:OldClass -sym:OldClass

View file

@ -102,6 +102,14 @@ visibility:public
Program(PrefixExpr(VisibilityExpr)) Program(PrefixExpr(VisibilityExpr))
# Context prefix
context:web
==>
Program(PrefixExpr(ContextExpr))
# Symbol prefix # Symbol prefix
sym:MyClass sym:MyClass
@ -302,6 +310,14 @@ content:@Component
Program(PrefixExpr(ContentExpr)) Program(PrefixExpr(ContentExpr))
# Context with underscores
context:data_engineering
==>
Program(PrefixExpr(ContextExpr))
# Prefix in parentheses # Prefix in parentheses
(file:test.js) (file:test.js)

View file

@ -18,6 +18,7 @@ import { NextRequest } from 'next/server';
import * as path from 'path'; import * as path from 'path';
import { parser as _parser } from '@sourcebot/query-language'; import { parser as _parser } from '@sourcebot/query-language';
import { transformToZoektQuery } from './transformer'; import { transformToZoektQuery } from './transformer';
import { SINGLE_TENANT_ORG_ID } from '@/lib/constants';
const logger = createLogger('streamSearchApi'); const logger = createLogger('streamSearchApi');
@ -78,14 +79,33 @@ export const POST = async (request: NextRequest) => {
const parser = _parser.configure({ const parser = _parser.configure({
strict: true, strict: true,
}) });
const tree = parser.parse(query); const tree = parser.parse(query);
const zoektQuery = transformToZoektQuery({ const zoektQuery = await transformToZoektQuery({
tree, tree,
input: query, input: query,
isCaseSensitivityEnabled, isCaseSensitivityEnabled,
isRegexEnabled, isRegexEnabled,
onExpandSearchContext: async (contextName: string) => {
const context = await prisma.searchContext.findUnique({
where: {
name_orgId: {
name: contextName,
orgId: SINGLE_TENANT_ORG_ID,
}
},
include: {
repos: true,
}
});
if (!context) {
throw new Error(`Search context "${contextName}" not found`);
}
return context.repos.map((repo) => repo.name);
},
}); });
console.log(JSON.stringify(zoektQuery, null, 2)); console.log(JSON.stringify(zoektQuery, null, 2));

View file

@ -12,6 +12,7 @@ import {
RepoExpr, RepoExpr,
RevisionExpr, RevisionExpr,
ContentExpr, ContentExpr,
ContextExpr,
LangExpr, LangExpr,
SymExpr, SymExpr,
ArchivedExpr, ArchivedExpr,
@ -44,14 +45,16 @@ export const transformToZoektQuery = ({
input, input,
isCaseSensitivityEnabled, isCaseSensitivityEnabled,
isRegexEnabled, isRegexEnabled,
onExpandSearchContext,
}: { }: {
tree: Tree; tree: Tree;
input: string; input: string;
isCaseSensitivityEnabled: boolean; isCaseSensitivityEnabled: boolean;
isRegexEnabled: boolean; isRegexEnabled: boolean;
}): Q => { onExpandSearchContext: (contextName: string) => Promise<string[]>;
}): Promise<Q> => {
const transformNode = (node: SyntaxNode): Q => { const transformNode = async (node: SyntaxNode): Promise<Q> => {
switch (node.type.id) { switch (node.type.id) {
case Program: { case Program: {
// Program wraps the actual query - transform its child // Program wraps the actual query - transform its child
@ -65,7 +68,7 @@ export const transformToZoektQuery = ({
case AndExpr: case AndExpr:
return { return {
and: { and: {
children: getChildren(node).map(c => transformNode(c)) children: await Promise.all(getChildren(node).map(c => transformNode(c)))
}, },
query: "and" query: "and"
} }
@ -73,7 +76,7 @@ export const transformToZoektQuery = ({
case OrExpr: case OrExpr:
return { return {
or: { or: {
children: getChildren(node).map(c => transformNode(c)) children: await Promise.all(getChildren(node).map(c => transformNode(c)))
}, },
query: "or" query: "or"
}; };
@ -86,7 +89,7 @@ export const transformToZoektQuery = ({
} }
return { return {
not: { not: {
child: transformNode(negateChild) child: await transformNode(negateChild)
}, },
query: "not" query: "not"
}; };
@ -130,7 +133,7 @@ export const transformToZoektQuery = ({
} }
} }
const transformPrefixExpr = (node: SyntaxNode): Q => { const transformPrefixExpr = async (node: SyntaxNode): Promise<Q> => {
// Find which specific prefix type this is // Find which specific prefix type this is
const prefixNode = node.firstChild; const prefixNode = node.firstChild;
if (!prefixNode) { if (!prefixNode) {
@ -189,6 +192,7 @@ export const transformToZoektQuery = ({
query: "substring" query: "substring"
}; };
case LangExpr: case LangExpr:
return { return {
language: { language: {
@ -287,6 +291,19 @@ export const transformToZoektQuery = ({
}; };
} }
case ContextExpr: {
const repoNames = await onExpandSearchContext(value);
return {
repo_set: {
set: repoNames.reduce((acc, s) => {
acc[s.trim()] = true;
return acc;
}, {} as Record<string, boolean>)
},
query: "repo_set"
};
}
case RepoSetExpr: { case RepoSetExpr: {
return { return {
repo_set: { repo_set: {