From a040ee1e07516a9827f48289748832e268eeafab Mon Sep 17 00:00:00 2001 From: bkellam Date: Thu, 13 Nov 2025 21:17:17 -0800 Subject: [PATCH 01/40] generate protobuf types --- packages/web/.eslintignore | 3 +- packages/web/package.json | 3 + .../web/src/proto/google/protobuf/Duration.ts | 13 ++ .../src/proto/google/protobuf/Timestamp.ts | 13 ++ packages/web/src/proto/query.ts | 55 ++++++ packages/web/src/proto/webserver.ts | 112 +++++++++++ .../web/src/proto/zoekt/webserver/v1/And.ts | 17 ++ .../web/src/proto/zoekt/webserver/v1/Boost.ts | 19 ++ .../src/proto/zoekt/webserver/v1/Branch.ts | 24 +++ .../proto/zoekt/webserver/v1/BranchRepos.ts | 26 +++ .../proto/zoekt/webserver/v1/BranchesRepos.ts | 17 ++ .../proto/zoekt/webserver/v1/ChunkMatch.ts | 67 +++++++ .../src/proto/zoekt/webserver/v1/FileMatch.ts | 132 +++++++++++++ .../proto/zoekt/webserver/v1/FileNameSet.ts | 16 ++ .../proto/zoekt/webserver/v1/FlushReason.ts | 20 ++ .../proto/zoekt/webserver/v1/IndexMetadata.ts | 26 +++ .../src/proto/zoekt/webserver/v1/Language.ts | 10 + .../zoekt/webserver/v1/LineFragmentMatch.ts | 38 ++++ .../src/proto/zoekt/webserver/v1/LineMatch.ts | 50 +++++ .../proto/zoekt/webserver/v1/ListOptions.ts | 34 ++++ .../proto/zoekt/webserver/v1/ListRequest.ts | 14 ++ .../proto/zoekt/webserver/v1/ListResponse.ts | 40 ++++ .../src/proto/zoekt/webserver/v1/Location.ts | 32 +++ .../webserver/v1/MinimalRepoListEntry.ts | 16 ++ .../web/src/proto/zoekt/webserver/v1/Not.ts | 17 ++ .../web/src/proto/zoekt/webserver/v1/Or.ts | 17 ++ .../src/proto/zoekt/webserver/v1/Progress.ts | 42 ++++ .../web/src/proto/zoekt/webserver/v1/Q.ts | 63 ++++++ .../web/src/proto/zoekt/webserver/v1/Range.ts | 25 +++ .../src/proto/zoekt/webserver/v1/RawConfig.ts | 46 +++++ .../src/proto/zoekt/webserver/v1/Regexp.ts | 22 +++ .../web/src/proto/zoekt/webserver/v1/Repo.ts | 10 + .../src/proto/zoekt/webserver/v1/RepoIds.ts | 24 +++ .../proto/zoekt/webserver/v1/RepoListEntry.ts | 17 ++ .../proto/zoekt/webserver/v1/RepoRegexp.ts | 10 + .../src/proto/zoekt/webserver/v1/RepoSet.ts | 16 ++ .../src/proto/zoekt/webserver/v1/RepoStats.ts | 97 +++++++++ .../proto/zoekt/webserver/v1/Repository.ts | 184 ++++++++++++++++++ .../zoekt/webserver/v1/RepositoryBranch.ts | 20 ++ .../proto/zoekt/webserver/v1/SearchOptions.ts | 156 +++++++++++++++ .../proto/zoekt/webserver/v1/SearchRequest.ts | 14 ++ .../zoekt/webserver/v1/SearchResponse.ts | 17 ++ .../web/src/proto/zoekt/webserver/v1/Stats.ts | 181 +++++++++++++++++ .../zoekt/webserver/v1/StreamSearchRequest.ts | 11 ++ .../webserver/v1/StreamSearchResponse.ts | 11 ++ .../src/proto/zoekt/webserver/v1/Substring.ts | 28 +++ .../src/proto/zoekt/webserver/v1/Symbol.ts | 11 ++ .../proto/zoekt/webserver/v1/SymbolInfo.ts | 16 ++ .../web/src/proto/zoekt/webserver/v1/Type.ts | 46 +++++ .../zoekt/webserver/v1/WebserverService.ts | 63 ++++++ yarn.lock | 12 ++ 51 files changed, 1972 insertions(+), 1 deletion(-) create mode 100644 packages/web/src/proto/google/protobuf/Duration.ts create mode 100644 packages/web/src/proto/google/protobuf/Timestamp.ts create mode 100644 packages/web/src/proto/query.ts create mode 100644 packages/web/src/proto/webserver.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/And.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/Boost.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/Branch.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/BranchRepos.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/BranchesRepos.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/ChunkMatch.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/FileMatch.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/FileNameSet.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/FlushReason.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/IndexMetadata.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/Language.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/LineFragmentMatch.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/LineMatch.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/ListOptions.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/ListRequest.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/ListResponse.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/Location.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/MinimalRepoListEntry.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/Not.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/Or.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/Progress.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/Q.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/Range.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/RawConfig.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/Regexp.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/Repo.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/RepoIds.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/RepoListEntry.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/RepoRegexp.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/RepoSet.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/RepoStats.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/Repository.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/RepositoryBranch.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/SearchOptions.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/SearchRequest.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/SearchResponse.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/Stats.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/StreamSearchRequest.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/StreamSearchResponse.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/Substring.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/Symbol.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/SymbolInfo.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/Type.ts create mode 100644 packages/web/src/proto/zoekt/webserver/v1/WebserverService.ts diff --git a/packages/web/.eslintignore b/packages/web/.eslintignore index b1a300673..820122622 100644 --- a/packages/web/.eslintignore +++ b/packages/web/.eslintignore @@ -1,3 +1,4 @@ # shadcn components src/components/ -next-env.d.ts \ No newline at end of file +next-env.d.ts +src/proto/** \ No newline at end of file diff --git a/packages/web/package.json b/packages/web/package.json index ba3c3dbc4..4cea2bace 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -8,6 +8,7 @@ "start": "next start", "lint": "cross-env SKIP_ENV_VALIDATION=1 eslint .", "test": "cross-env SKIP_ENV_VALIDATION=1 vitest", + "generate:protos": "proto-loader-gen-types --includeComments --longs=Number --enums=String --defaults --oneofs --grpcLib=@grpc/grpc-js --keepCase --includeDirs=../../vendor/zoekt/grpc/protos --outDir=src/proto zoekt/webserver/v1/webserver.proto zoekt/webserver/v1/query.proto", "dev:emails": "email dev --dir ./src/emails", "stripe:listen": "stripe listen --forward-to http://localhost:3000/api/stripe" }, @@ -52,6 +53,8 @@ "@codemirror/state": "^6.4.1", "@codemirror/view": "^6.33.0", "@floating-ui/react": "^0.27.2", + "@grpc/grpc-js": "^1.14.1", + "@grpc/proto-loader": "^0.8.0", "@hookform/resolvers": "^3.9.0", "@iconify/react": "^5.1.0", "@iizukak/codemirror-lang-wgsl": "^0.3.0", diff --git a/packages/web/src/proto/google/protobuf/Duration.ts b/packages/web/src/proto/google/protobuf/Duration.ts new file mode 100644 index 000000000..f24c347f3 --- /dev/null +++ b/packages/web/src/proto/google/protobuf/Duration.ts @@ -0,0 +1,13 @@ +// Original file: null + +import type { Long } from '@grpc/proto-loader'; + +export interface Duration { + 'seconds'?: (number | string | Long); + 'nanos'?: (number); +} + +export interface Duration__Output { + 'seconds': (number); + 'nanos': (number); +} diff --git a/packages/web/src/proto/google/protobuf/Timestamp.ts b/packages/web/src/proto/google/protobuf/Timestamp.ts new file mode 100644 index 000000000..154167e2c --- /dev/null +++ b/packages/web/src/proto/google/protobuf/Timestamp.ts @@ -0,0 +1,13 @@ +// Original file: null + +import type { Long } from '@grpc/proto-loader'; + +export interface Timestamp { + 'seconds'?: (number | string | Long); + 'nanos'?: (number); +} + +export interface Timestamp__Output { + 'seconds': (number); + 'nanos': (number); +} diff --git a/packages/web/src/proto/query.ts b/packages/web/src/proto/query.ts new file mode 100644 index 000000000..93631b521 --- /dev/null +++ b/packages/web/src/proto/query.ts @@ -0,0 +1,55 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { MessageTypeDefinition } from '@grpc/proto-loader'; + +import type { And as _zoekt_webserver_v1_And, And__Output as _zoekt_webserver_v1_And__Output } from './zoekt/webserver/v1/And'; +import type { Boost as _zoekt_webserver_v1_Boost, Boost__Output as _zoekt_webserver_v1_Boost__Output } from './zoekt/webserver/v1/Boost'; +import type { Branch as _zoekt_webserver_v1_Branch, Branch__Output as _zoekt_webserver_v1_Branch__Output } from './zoekt/webserver/v1/Branch'; +import type { BranchRepos as _zoekt_webserver_v1_BranchRepos, BranchRepos__Output as _zoekt_webserver_v1_BranchRepos__Output } from './zoekt/webserver/v1/BranchRepos'; +import type { BranchesRepos as _zoekt_webserver_v1_BranchesRepos, BranchesRepos__Output as _zoekt_webserver_v1_BranchesRepos__Output } from './zoekt/webserver/v1/BranchesRepos'; +import type { FileNameSet as _zoekt_webserver_v1_FileNameSet, FileNameSet__Output as _zoekt_webserver_v1_FileNameSet__Output } from './zoekt/webserver/v1/FileNameSet'; +import type { Language as _zoekt_webserver_v1_Language, Language__Output as _zoekt_webserver_v1_Language__Output } from './zoekt/webserver/v1/Language'; +import type { Not as _zoekt_webserver_v1_Not, Not__Output as _zoekt_webserver_v1_Not__Output } from './zoekt/webserver/v1/Not'; +import type { Or as _zoekt_webserver_v1_Or, Or__Output as _zoekt_webserver_v1_Or__Output } from './zoekt/webserver/v1/Or'; +import type { Q as _zoekt_webserver_v1_Q, Q__Output as _zoekt_webserver_v1_Q__Output } from './zoekt/webserver/v1/Q'; +import type { RawConfig as _zoekt_webserver_v1_RawConfig, RawConfig__Output as _zoekt_webserver_v1_RawConfig__Output } from './zoekt/webserver/v1/RawConfig'; +import type { Regexp as _zoekt_webserver_v1_Regexp, Regexp__Output as _zoekt_webserver_v1_Regexp__Output } from './zoekt/webserver/v1/Regexp'; +import type { Repo as _zoekt_webserver_v1_Repo, Repo__Output as _zoekt_webserver_v1_Repo__Output } from './zoekt/webserver/v1/Repo'; +import type { RepoIds as _zoekt_webserver_v1_RepoIds, RepoIds__Output as _zoekt_webserver_v1_RepoIds__Output } from './zoekt/webserver/v1/RepoIds'; +import type { RepoRegexp as _zoekt_webserver_v1_RepoRegexp, RepoRegexp__Output as _zoekt_webserver_v1_RepoRegexp__Output } from './zoekt/webserver/v1/RepoRegexp'; +import type { RepoSet as _zoekt_webserver_v1_RepoSet, RepoSet__Output as _zoekt_webserver_v1_RepoSet__Output } from './zoekt/webserver/v1/RepoSet'; +import type { Substring as _zoekt_webserver_v1_Substring, Substring__Output as _zoekt_webserver_v1_Substring__Output } from './zoekt/webserver/v1/Substring'; +import type { Symbol as _zoekt_webserver_v1_Symbol, Symbol__Output as _zoekt_webserver_v1_Symbol__Output } from './zoekt/webserver/v1/Symbol'; +import type { Type as _zoekt_webserver_v1_Type, Type__Output as _zoekt_webserver_v1_Type__Output } from './zoekt/webserver/v1/Type'; + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + zoekt: { + webserver: { + v1: { + And: MessageTypeDefinition<_zoekt_webserver_v1_And, _zoekt_webserver_v1_And__Output> + Boost: MessageTypeDefinition<_zoekt_webserver_v1_Boost, _zoekt_webserver_v1_Boost__Output> + Branch: MessageTypeDefinition<_zoekt_webserver_v1_Branch, _zoekt_webserver_v1_Branch__Output> + BranchRepos: MessageTypeDefinition<_zoekt_webserver_v1_BranchRepos, _zoekt_webserver_v1_BranchRepos__Output> + BranchesRepos: MessageTypeDefinition<_zoekt_webserver_v1_BranchesRepos, _zoekt_webserver_v1_BranchesRepos__Output> + FileNameSet: MessageTypeDefinition<_zoekt_webserver_v1_FileNameSet, _zoekt_webserver_v1_FileNameSet__Output> + Language: MessageTypeDefinition<_zoekt_webserver_v1_Language, _zoekt_webserver_v1_Language__Output> + Not: MessageTypeDefinition<_zoekt_webserver_v1_Not, _zoekt_webserver_v1_Not__Output> + Or: MessageTypeDefinition<_zoekt_webserver_v1_Or, _zoekt_webserver_v1_Or__Output> + Q: MessageTypeDefinition<_zoekt_webserver_v1_Q, _zoekt_webserver_v1_Q__Output> + RawConfig: MessageTypeDefinition<_zoekt_webserver_v1_RawConfig, _zoekt_webserver_v1_RawConfig__Output> + Regexp: MessageTypeDefinition<_zoekt_webserver_v1_Regexp, _zoekt_webserver_v1_Regexp__Output> + Repo: MessageTypeDefinition<_zoekt_webserver_v1_Repo, _zoekt_webserver_v1_Repo__Output> + RepoIds: MessageTypeDefinition<_zoekt_webserver_v1_RepoIds, _zoekt_webserver_v1_RepoIds__Output> + RepoRegexp: MessageTypeDefinition<_zoekt_webserver_v1_RepoRegexp, _zoekt_webserver_v1_RepoRegexp__Output> + RepoSet: MessageTypeDefinition<_zoekt_webserver_v1_RepoSet, _zoekt_webserver_v1_RepoSet__Output> + Substring: MessageTypeDefinition<_zoekt_webserver_v1_Substring, _zoekt_webserver_v1_Substring__Output> + Symbol: MessageTypeDefinition<_zoekt_webserver_v1_Symbol, _zoekt_webserver_v1_Symbol__Output> + Type: MessageTypeDefinition<_zoekt_webserver_v1_Type, _zoekt_webserver_v1_Type__Output> + } + } + } +} + diff --git a/packages/web/src/proto/webserver.ts b/packages/web/src/proto/webserver.ts new file mode 100644 index 000000000..0dc98637e --- /dev/null +++ b/packages/web/src/proto/webserver.ts @@ -0,0 +1,112 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from './google/protobuf/Duration'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from './google/protobuf/Timestamp'; +import type { And as _zoekt_webserver_v1_And, And__Output as _zoekt_webserver_v1_And__Output } from './zoekt/webserver/v1/And'; +import type { Boost as _zoekt_webserver_v1_Boost, Boost__Output as _zoekt_webserver_v1_Boost__Output } from './zoekt/webserver/v1/Boost'; +import type { Branch as _zoekt_webserver_v1_Branch, Branch__Output as _zoekt_webserver_v1_Branch__Output } from './zoekt/webserver/v1/Branch'; +import type { BranchRepos as _zoekt_webserver_v1_BranchRepos, BranchRepos__Output as _zoekt_webserver_v1_BranchRepos__Output } from './zoekt/webserver/v1/BranchRepos'; +import type { BranchesRepos as _zoekt_webserver_v1_BranchesRepos, BranchesRepos__Output as _zoekt_webserver_v1_BranchesRepos__Output } from './zoekt/webserver/v1/BranchesRepos'; +import type { ChunkMatch as _zoekt_webserver_v1_ChunkMatch, ChunkMatch__Output as _zoekt_webserver_v1_ChunkMatch__Output } from './zoekt/webserver/v1/ChunkMatch'; +import type { FileMatch as _zoekt_webserver_v1_FileMatch, FileMatch__Output as _zoekt_webserver_v1_FileMatch__Output } from './zoekt/webserver/v1/FileMatch'; +import type { FileNameSet as _zoekt_webserver_v1_FileNameSet, FileNameSet__Output as _zoekt_webserver_v1_FileNameSet__Output } from './zoekt/webserver/v1/FileNameSet'; +import type { IndexMetadata as _zoekt_webserver_v1_IndexMetadata, IndexMetadata__Output as _zoekt_webserver_v1_IndexMetadata__Output } from './zoekt/webserver/v1/IndexMetadata'; +import type { Language as _zoekt_webserver_v1_Language, Language__Output as _zoekt_webserver_v1_Language__Output } from './zoekt/webserver/v1/Language'; +import type { LineFragmentMatch as _zoekt_webserver_v1_LineFragmentMatch, LineFragmentMatch__Output as _zoekt_webserver_v1_LineFragmentMatch__Output } from './zoekt/webserver/v1/LineFragmentMatch'; +import type { LineMatch as _zoekt_webserver_v1_LineMatch, LineMatch__Output as _zoekt_webserver_v1_LineMatch__Output } from './zoekt/webserver/v1/LineMatch'; +import type { ListOptions as _zoekt_webserver_v1_ListOptions, ListOptions__Output as _zoekt_webserver_v1_ListOptions__Output } from './zoekt/webserver/v1/ListOptions'; +import type { ListRequest as _zoekt_webserver_v1_ListRequest, ListRequest__Output as _zoekt_webserver_v1_ListRequest__Output } from './zoekt/webserver/v1/ListRequest'; +import type { ListResponse as _zoekt_webserver_v1_ListResponse, ListResponse__Output as _zoekt_webserver_v1_ListResponse__Output } from './zoekt/webserver/v1/ListResponse'; +import type { Location as _zoekt_webserver_v1_Location, Location__Output as _zoekt_webserver_v1_Location__Output } from './zoekt/webserver/v1/Location'; +import type { MinimalRepoListEntry as _zoekt_webserver_v1_MinimalRepoListEntry, MinimalRepoListEntry__Output as _zoekt_webserver_v1_MinimalRepoListEntry__Output } from './zoekt/webserver/v1/MinimalRepoListEntry'; +import type { Not as _zoekt_webserver_v1_Not, Not__Output as _zoekt_webserver_v1_Not__Output } from './zoekt/webserver/v1/Not'; +import type { Or as _zoekt_webserver_v1_Or, Or__Output as _zoekt_webserver_v1_Or__Output } from './zoekt/webserver/v1/Or'; +import type { Progress as _zoekt_webserver_v1_Progress, Progress__Output as _zoekt_webserver_v1_Progress__Output } from './zoekt/webserver/v1/Progress'; +import type { Q as _zoekt_webserver_v1_Q, Q__Output as _zoekt_webserver_v1_Q__Output } from './zoekt/webserver/v1/Q'; +import type { Range as _zoekt_webserver_v1_Range, Range__Output as _zoekt_webserver_v1_Range__Output } from './zoekt/webserver/v1/Range'; +import type { RawConfig as _zoekt_webserver_v1_RawConfig, RawConfig__Output as _zoekt_webserver_v1_RawConfig__Output } from './zoekt/webserver/v1/RawConfig'; +import type { Regexp as _zoekt_webserver_v1_Regexp, Regexp__Output as _zoekt_webserver_v1_Regexp__Output } from './zoekt/webserver/v1/Regexp'; +import type { Repo as _zoekt_webserver_v1_Repo, Repo__Output as _zoekt_webserver_v1_Repo__Output } from './zoekt/webserver/v1/Repo'; +import type { RepoIds as _zoekt_webserver_v1_RepoIds, RepoIds__Output as _zoekt_webserver_v1_RepoIds__Output } from './zoekt/webserver/v1/RepoIds'; +import type { RepoListEntry as _zoekt_webserver_v1_RepoListEntry, RepoListEntry__Output as _zoekt_webserver_v1_RepoListEntry__Output } from './zoekt/webserver/v1/RepoListEntry'; +import type { RepoRegexp as _zoekt_webserver_v1_RepoRegexp, RepoRegexp__Output as _zoekt_webserver_v1_RepoRegexp__Output } from './zoekt/webserver/v1/RepoRegexp'; +import type { RepoSet as _zoekt_webserver_v1_RepoSet, RepoSet__Output as _zoekt_webserver_v1_RepoSet__Output } from './zoekt/webserver/v1/RepoSet'; +import type { RepoStats as _zoekt_webserver_v1_RepoStats, RepoStats__Output as _zoekt_webserver_v1_RepoStats__Output } from './zoekt/webserver/v1/RepoStats'; +import type { Repository as _zoekt_webserver_v1_Repository, Repository__Output as _zoekt_webserver_v1_Repository__Output } from './zoekt/webserver/v1/Repository'; +import type { RepositoryBranch as _zoekt_webserver_v1_RepositoryBranch, RepositoryBranch__Output as _zoekt_webserver_v1_RepositoryBranch__Output } from './zoekt/webserver/v1/RepositoryBranch'; +import type { SearchOptions as _zoekt_webserver_v1_SearchOptions, SearchOptions__Output as _zoekt_webserver_v1_SearchOptions__Output } from './zoekt/webserver/v1/SearchOptions'; +import type { SearchRequest as _zoekt_webserver_v1_SearchRequest, SearchRequest__Output as _zoekt_webserver_v1_SearchRequest__Output } from './zoekt/webserver/v1/SearchRequest'; +import type { SearchResponse as _zoekt_webserver_v1_SearchResponse, SearchResponse__Output as _zoekt_webserver_v1_SearchResponse__Output } from './zoekt/webserver/v1/SearchResponse'; +import type { Stats as _zoekt_webserver_v1_Stats, Stats__Output as _zoekt_webserver_v1_Stats__Output } from './zoekt/webserver/v1/Stats'; +import type { StreamSearchRequest as _zoekt_webserver_v1_StreamSearchRequest, StreamSearchRequest__Output as _zoekt_webserver_v1_StreamSearchRequest__Output } from './zoekt/webserver/v1/StreamSearchRequest'; +import type { StreamSearchResponse as _zoekt_webserver_v1_StreamSearchResponse, StreamSearchResponse__Output as _zoekt_webserver_v1_StreamSearchResponse__Output } from './zoekt/webserver/v1/StreamSearchResponse'; +import type { Substring as _zoekt_webserver_v1_Substring, Substring__Output as _zoekt_webserver_v1_Substring__Output } from './zoekt/webserver/v1/Substring'; +import type { Symbol as _zoekt_webserver_v1_Symbol, Symbol__Output as _zoekt_webserver_v1_Symbol__Output } from './zoekt/webserver/v1/Symbol'; +import type { SymbolInfo as _zoekt_webserver_v1_SymbolInfo, SymbolInfo__Output as _zoekt_webserver_v1_SymbolInfo__Output } from './zoekt/webserver/v1/SymbolInfo'; +import type { Type as _zoekt_webserver_v1_Type, Type__Output as _zoekt_webserver_v1_Type__Output } from './zoekt/webserver/v1/Type'; +import type { WebserverServiceClient as _zoekt_webserver_v1_WebserverServiceClient, WebserverServiceDefinition as _zoekt_webserver_v1_WebserverServiceDefinition } from './zoekt/webserver/v1/WebserverService'; + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + google: { + protobuf: { + Duration: MessageTypeDefinition<_google_protobuf_Duration, _google_protobuf_Duration__Output> + Timestamp: MessageTypeDefinition<_google_protobuf_Timestamp, _google_protobuf_Timestamp__Output> + } + } + zoekt: { + webserver: { + v1: { + And: MessageTypeDefinition<_zoekt_webserver_v1_And, _zoekt_webserver_v1_And__Output> + Boost: MessageTypeDefinition<_zoekt_webserver_v1_Boost, _zoekt_webserver_v1_Boost__Output> + Branch: MessageTypeDefinition<_zoekt_webserver_v1_Branch, _zoekt_webserver_v1_Branch__Output> + BranchRepos: MessageTypeDefinition<_zoekt_webserver_v1_BranchRepos, _zoekt_webserver_v1_BranchRepos__Output> + BranchesRepos: MessageTypeDefinition<_zoekt_webserver_v1_BranchesRepos, _zoekt_webserver_v1_BranchesRepos__Output> + ChunkMatch: MessageTypeDefinition<_zoekt_webserver_v1_ChunkMatch, _zoekt_webserver_v1_ChunkMatch__Output> + FileMatch: MessageTypeDefinition<_zoekt_webserver_v1_FileMatch, _zoekt_webserver_v1_FileMatch__Output> + FileNameSet: MessageTypeDefinition<_zoekt_webserver_v1_FileNameSet, _zoekt_webserver_v1_FileNameSet__Output> + FlushReason: EnumTypeDefinition + IndexMetadata: MessageTypeDefinition<_zoekt_webserver_v1_IndexMetadata, _zoekt_webserver_v1_IndexMetadata__Output> + Language: MessageTypeDefinition<_zoekt_webserver_v1_Language, _zoekt_webserver_v1_Language__Output> + LineFragmentMatch: MessageTypeDefinition<_zoekt_webserver_v1_LineFragmentMatch, _zoekt_webserver_v1_LineFragmentMatch__Output> + LineMatch: MessageTypeDefinition<_zoekt_webserver_v1_LineMatch, _zoekt_webserver_v1_LineMatch__Output> + ListOptions: MessageTypeDefinition<_zoekt_webserver_v1_ListOptions, _zoekt_webserver_v1_ListOptions__Output> + ListRequest: MessageTypeDefinition<_zoekt_webserver_v1_ListRequest, _zoekt_webserver_v1_ListRequest__Output> + ListResponse: MessageTypeDefinition<_zoekt_webserver_v1_ListResponse, _zoekt_webserver_v1_ListResponse__Output> + Location: MessageTypeDefinition<_zoekt_webserver_v1_Location, _zoekt_webserver_v1_Location__Output> + MinimalRepoListEntry: MessageTypeDefinition<_zoekt_webserver_v1_MinimalRepoListEntry, _zoekt_webserver_v1_MinimalRepoListEntry__Output> + Not: MessageTypeDefinition<_zoekt_webserver_v1_Not, _zoekt_webserver_v1_Not__Output> + Or: MessageTypeDefinition<_zoekt_webserver_v1_Or, _zoekt_webserver_v1_Or__Output> + Progress: MessageTypeDefinition<_zoekt_webserver_v1_Progress, _zoekt_webserver_v1_Progress__Output> + Q: MessageTypeDefinition<_zoekt_webserver_v1_Q, _zoekt_webserver_v1_Q__Output> + Range: MessageTypeDefinition<_zoekt_webserver_v1_Range, _zoekt_webserver_v1_Range__Output> + RawConfig: MessageTypeDefinition<_zoekt_webserver_v1_RawConfig, _zoekt_webserver_v1_RawConfig__Output> + Regexp: MessageTypeDefinition<_zoekt_webserver_v1_Regexp, _zoekt_webserver_v1_Regexp__Output> + Repo: MessageTypeDefinition<_zoekt_webserver_v1_Repo, _zoekt_webserver_v1_Repo__Output> + RepoIds: MessageTypeDefinition<_zoekt_webserver_v1_RepoIds, _zoekt_webserver_v1_RepoIds__Output> + RepoListEntry: MessageTypeDefinition<_zoekt_webserver_v1_RepoListEntry, _zoekt_webserver_v1_RepoListEntry__Output> + RepoRegexp: MessageTypeDefinition<_zoekt_webserver_v1_RepoRegexp, _zoekt_webserver_v1_RepoRegexp__Output> + RepoSet: MessageTypeDefinition<_zoekt_webserver_v1_RepoSet, _zoekt_webserver_v1_RepoSet__Output> + RepoStats: MessageTypeDefinition<_zoekt_webserver_v1_RepoStats, _zoekt_webserver_v1_RepoStats__Output> + Repository: MessageTypeDefinition<_zoekt_webserver_v1_Repository, _zoekt_webserver_v1_Repository__Output> + RepositoryBranch: MessageTypeDefinition<_zoekt_webserver_v1_RepositoryBranch, _zoekt_webserver_v1_RepositoryBranch__Output> + SearchOptions: MessageTypeDefinition<_zoekt_webserver_v1_SearchOptions, _zoekt_webserver_v1_SearchOptions__Output> + SearchRequest: MessageTypeDefinition<_zoekt_webserver_v1_SearchRequest, _zoekt_webserver_v1_SearchRequest__Output> + SearchResponse: MessageTypeDefinition<_zoekt_webserver_v1_SearchResponse, _zoekt_webserver_v1_SearchResponse__Output> + Stats: MessageTypeDefinition<_zoekt_webserver_v1_Stats, _zoekt_webserver_v1_Stats__Output> + StreamSearchRequest: MessageTypeDefinition<_zoekt_webserver_v1_StreamSearchRequest, _zoekt_webserver_v1_StreamSearchRequest__Output> + StreamSearchResponse: MessageTypeDefinition<_zoekt_webserver_v1_StreamSearchResponse, _zoekt_webserver_v1_StreamSearchResponse__Output> + Substring: MessageTypeDefinition<_zoekt_webserver_v1_Substring, _zoekt_webserver_v1_Substring__Output> + Symbol: MessageTypeDefinition<_zoekt_webserver_v1_Symbol, _zoekt_webserver_v1_Symbol__Output> + SymbolInfo: MessageTypeDefinition<_zoekt_webserver_v1_SymbolInfo, _zoekt_webserver_v1_SymbolInfo__Output> + Type: MessageTypeDefinition<_zoekt_webserver_v1_Type, _zoekt_webserver_v1_Type__Output> + WebserverService: SubtypeConstructor & { service: _zoekt_webserver_v1_WebserverServiceDefinition } + } + } + } +} + diff --git a/packages/web/src/proto/zoekt/webserver/v1/And.ts b/packages/web/src/proto/zoekt/webserver/v1/And.ts new file mode 100644 index 000000000..61fff3c43 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/And.ts @@ -0,0 +1,17 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/query.proto + +import type { Q as _zoekt_webserver_v1_Q, Q__Output as _zoekt_webserver_v1_Q__Output } from '../../../zoekt/webserver/v1/Q'; + +/** + * And is matched when all its children are. + */ +export interface And { + 'children'?: (_zoekt_webserver_v1_Q)[]; +} + +/** + * And is matched when all its children are. + */ +export interface And__Output { + 'children': (_zoekt_webserver_v1_Q__Output)[]; +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/Boost.ts b/packages/web/src/proto/zoekt/webserver/v1/Boost.ts new file mode 100644 index 000000000..d5f03a808 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/Boost.ts @@ -0,0 +1,19 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/query.proto + +import type { Q as _zoekt_webserver_v1_Q, Q__Output as _zoekt_webserver_v1_Q__Output } from '../../../zoekt/webserver/v1/Q'; + +/** + * Boost multiplies the score of its child by the boost factor. + */ +export interface Boost { + 'child'?: (_zoekt_webserver_v1_Q | null); + 'boost'?: (number | string); +} + +/** + * Boost multiplies the score of its child by the boost factor. + */ +export interface Boost__Output { + 'child': (_zoekt_webserver_v1_Q__Output | null); + 'boost': (number); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/Branch.ts b/packages/web/src/proto/zoekt/webserver/v1/Branch.ts new file mode 100644 index 000000000..6b7afce8d --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/Branch.ts @@ -0,0 +1,24 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/query.proto + + +/** + * Branch limits search to a specific branch. + */ +export interface Branch { + 'pattern'?: (string); + /** + * exact is true if we want to Pattern to equal branch. + */ + 'exact'?: (boolean); +} + +/** + * Branch limits search to a specific branch. + */ +export interface Branch__Output { + 'pattern': (string); + /** + * exact is true if we want to Pattern to equal branch. + */ + 'exact': (boolean); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/BranchRepos.ts b/packages/web/src/proto/zoekt/webserver/v1/BranchRepos.ts new file mode 100644 index 000000000..7badc6d18 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/BranchRepos.ts @@ -0,0 +1,26 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/query.proto + + +/** + * BranchRepos is a (branch, sourcegraph repo ids bitmap) tuple. It is a + * Sourcegraph addition. + */ +export interface BranchRepos { + 'branch'?: (string); + /** + * a serialized roaring bitmap of the target repo ids + */ + 'repos'?: (Buffer | Uint8Array | string); +} + +/** + * BranchRepos is a (branch, sourcegraph repo ids bitmap) tuple. It is a + * Sourcegraph addition. + */ +export interface BranchRepos__Output { + 'branch': (string); + /** + * a serialized roaring bitmap of the target repo ids + */ + 'repos': (Buffer); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/BranchesRepos.ts b/packages/web/src/proto/zoekt/webserver/v1/BranchesRepos.ts new file mode 100644 index 000000000..b22b5a086 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/BranchesRepos.ts @@ -0,0 +1,17 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/query.proto + +import type { BranchRepos as _zoekt_webserver_v1_BranchRepos, BranchRepos__Output as _zoekt_webserver_v1_BranchRepos__Output } from '../../../zoekt/webserver/v1/BranchRepos'; + +/** + * BranchesRepos is a slice of BranchRepos to match. + */ +export interface BranchesRepos { + 'list'?: (_zoekt_webserver_v1_BranchRepos)[]; +} + +/** + * BranchesRepos is a slice of BranchRepos to match. + */ +export interface BranchesRepos__Output { + 'list': (_zoekt_webserver_v1_BranchRepos__Output)[]; +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/ChunkMatch.ts b/packages/web/src/proto/zoekt/webserver/v1/ChunkMatch.ts new file mode 100644 index 000000000..66d47099b --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/ChunkMatch.ts @@ -0,0 +1,67 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + +import type { Location as _zoekt_webserver_v1_Location, Location__Output as _zoekt_webserver_v1_Location__Output } from '../../../zoekt/webserver/v1/Location'; +import type { Range as _zoekt_webserver_v1_Range, Range__Output as _zoekt_webserver_v1_Range__Output } from '../../../zoekt/webserver/v1/Range'; +import type { SymbolInfo as _zoekt_webserver_v1_SymbolInfo, SymbolInfo__Output as _zoekt_webserver_v1_SymbolInfo__Output } from '../../../zoekt/webserver/v1/SymbolInfo'; + +export interface ChunkMatch { + /** + * A contiguous range of complete lines that fully contains Ranges. + */ + 'content'?: (Buffer | Uint8Array | string); + /** + * The location (inclusive) of the beginning of content + * relative to the beginning of the file. It will always be at the + * beginning of a line (Column will always be 1). + */ + 'content_start'?: (_zoekt_webserver_v1_Location | null); + /** + * True if this match is a match on the file name, in + * which case Content will contain the file name. + */ + 'file_name'?: (boolean); + /** + * A set of matching ranges within this chunk. Each range is relative + * to the beginning of the file (not the beginning of Content). + */ + 'ranges'?: (_zoekt_webserver_v1_Range)[]; + /** + * The symbol information associated with Ranges. If it is non-nil, + * its length will equal that of Ranges. Any of its elements may be nil. + */ + 'symbol_info'?: (_zoekt_webserver_v1_SymbolInfo)[]; + 'score'?: (number | string); + 'debug_score'?: (string); + 'best_line_match'?: (number); +} + +export interface ChunkMatch__Output { + /** + * A contiguous range of complete lines that fully contains Ranges. + */ + 'content': (Buffer); + /** + * The location (inclusive) of the beginning of content + * relative to the beginning of the file. It will always be at the + * beginning of a line (Column will always be 1). + */ + 'content_start': (_zoekt_webserver_v1_Location__Output | null); + /** + * True if this match is a match on the file name, in + * which case Content will contain the file name. + */ + 'file_name': (boolean); + /** + * A set of matching ranges within this chunk. Each range is relative + * to the beginning of the file (not the beginning of Content). + */ + 'ranges': (_zoekt_webserver_v1_Range__Output)[]; + /** + * The symbol information associated with Ranges. If it is non-nil, + * its length will equal that of Ranges. Any of its elements may be nil. + */ + 'symbol_info': (_zoekt_webserver_v1_SymbolInfo__Output)[]; + 'score': (number); + 'debug_score': (string); + 'best_line_match': (number); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/FileMatch.ts b/packages/web/src/proto/zoekt/webserver/v1/FileMatch.ts new file mode 100644 index 000000000..7150fbc5b --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/FileMatch.ts @@ -0,0 +1,132 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + +import type { LineMatch as _zoekt_webserver_v1_LineMatch, LineMatch__Output as _zoekt_webserver_v1_LineMatch__Output } from '../../../zoekt/webserver/v1/LineMatch'; +import type { ChunkMatch as _zoekt_webserver_v1_ChunkMatch, ChunkMatch__Output as _zoekt_webserver_v1_ChunkMatch__Output } from '../../../zoekt/webserver/v1/ChunkMatch'; + +/** + * FileMatch contains all the matches within a file. + */ +export interface FileMatch { + /** + * Ranking; the higher, the better. + */ + 'score'?: (number | string); + /** + * For debugging. Needs DebugScore set, but public so tests in + * other packages can print some diagnostics. + */ + 'debug'?: (string); + /** + * The repository-relative path to the file. + * 🚨 Warning: file_name might not be a valid UTF-8 string. + */ + 'file_name'?: (Buffer | Uint8Array | string); + /** + * Repository is the globally unique name of the repo of the + * match + */ + 'repository'?: (string); + 'branches'?: (string)[]; + /** + * One of line_matches or chunk_matches will be returned depending on whether + * the SearchOptions.ChunkMatches is set. + */ + 'line_matches'?: (_zoekt_webserver_v1_LineMatch)[]; + 'chunk_matches'?: (_zoekt_webserver_v1_ChunkMatch)[]; + /** + * repository_id is a Sourcegraph extension. This is the ID of Repository in + * Sourcegraph. + */ + 'repository_id'?: (number); + 'repository_priority'?: (number | string); + /** + * Only set if requested + */ + 'content'?: (Buffer | Uint8Array | string); + /** + * Checksum of the content. + */ + 'checksum'?: (Buffer | Uint8Array | string); + /** + * Detected language of the result. + */ + 'language'?: (string); + /** + * sub_repository_name is the globally unique name of the repo, + * if it came from a subrepository + */ + 'sub_repository_name'?: (string); + /** + * sub_repository_path holds the prefix where the subrepository + * was mounted. + */ + 'sub_repository_path'?: (string); + /** + * Commit SHA1 (hex) of the (sub)repo holding the file. + */ + 'version'?: (string); +} + +/** + * FileMatch contains all the matches within a file. + */ +export interface FileMatch__Output { + /** + * Ranking; the higher, the better. + */ + 'score': (number); + /** + * For debugging. Needs DebugScore set, but public so tests in + * other packages can print some diagnostics. + */ + 'debug': (string); + /** + * The repository-relative path to the file. + * 🚨 Warning: file_name might not be a valid UTF-8 string. + */ + 'file_name': (Buffer); + /** + * Repository is the globally unique name of the repo of the + * match + */ + 'repository': (string); + 'branches': (string)[]; + /** + * One of line_matches or chunk_matches will be returned depending on whether + * the SearchOptions.ChunkMatches is set. + */ + 'line_matches': (_zoekt_webserver_v1_LineMatch__Output)[]; + 'chunk_matches': (_zoekt_webserver_v1_ChunkMatch__Output)[]; + /** + * repository_id is a Sourcegraph extension. This is the ID of Repository in + * Sourcegraph. + */ + 'repository_id': (number); + 'repository_priority': (number); + /** + * Only set if requested + */ + 'content': (Buffer); + /** + * Checksum of the content. + */ + 'checksum': (Buffer); + /** + * Detected language of the result. + */ + 'language': (string); + /** + * sub_repository_name is the globally unique name of the repo, + * if it came from a subrepository + */ + 'sub_repository_name': (string); + /** + * sub_repository_path holds the prefix where the subrepository + * was mounted. + */ + 'sub_repository_path': (string); + /** + * Commit SHA1 (hex) of the (sub)repo holding the file. + */ + 'version': (string); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/FileNameSet.ts b/packages/web/src/proto/zoekt/webserver/v1/FileNameSet.ts new file mode 100644 index 000000000..38aea627d --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/FileNameSet.ts @@ -0,0 +1,16 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/query.proto + + +/** + * FileNameSet is a list of file names to match. + */ +export interface FileNameSet { + 'set'?: (string)[]; +} + +/** + * FileNameSet is a list of file names to match. + */ +export interface FileNameSet__Output { + 'set': (string)[]; +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/FlushReason.ts b/packages/web/src/proto/zoekt/webserver/v1/FlushReason.ts new file mode 100644 index 000000000..2cdad17d7 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/FlushReason.ts @@ -0,0 +1,20 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + +export const FlushReason = { + FLUSH_REASON_UNKNOWN_UNSPECIFIED: 'FLUSH_REASON_UNKNOWN_UNSPECIFIED', + FLUSH_REASON_TIMER_EXPIRED: 'FLUSH_REASON_TIMER_EXPIRED', + FLUSH_REASON_FINAL_FLUSH: 'FLUSH_REASON_FINAL_FLUSH', + FLUSH_REASON_MAX_SIZE: 'FLUSH_REASON_MAX_SIZE', +} as const; + +export type FlushReason = + | 'FLUSH_REASON_UNKNOWN_UNSPECIFIED' + | 0 + | 'FLUSH_REASON_TIMER_EXPIRED' + | 1 + | 'FLUSH_REASON_FINAL_FLUSH' + | 2 + | 'FLUSH_REASON_MAX_SIZE' + | 3 + +export type FlushReason__Output = typeof FlushReason[keyof typeof FlushReason] diff --git a/packages/web/src/proto/zoekt/webserver/v1/IndexMetadata.ts b/packages/web/src/proto/zoekt/webserver/v1/IndexMetadata.ts new file mode 100644 index 000000000..3de1dbf68 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/IndexMetadata.ts @@ -0,0 +1,26 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { Long } from '@grpc/proto-loader'; + +export interface IndexMetadata { + 'index_format_version'?: (number | string | Long); + 'index_feature_version'?: (number | string | Long); + 'index_min_reader_version'?: (number | string | Long); + 'index_time'?: (_google_protobuf_Timestamp | null); + 'plain_ascii'?: (boolean); + 'language_map'?: ({[key: string]: number}); + 'zoekt_version'?: (string); + 'id'?: (string); +} + +export interface IndexMetadata__Output { + 'index_format_version': (number); + 'index_feature_version': (number); + 'index_min_reader_version': (number); + 'index_time': (_google_protobuf_Timestamp__Output | null); + 'plain_ascii': (boolean); + 'language_map': ({[key: string]: number}); + 'zoekt_version': (string); + 'id': (string); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/Language.ts b/packages/web/src/proto/zoekt/webserver/v1/Language.ts new file mode 100644 index 000000000..4f2a9e6bd --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/Language.ts @@ -0,0 +1,10 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/query.proto + + +export interface Language { + 'language'?: (string); +} + +export interface Language__Output { + 'language': (string); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/LineFragmentMatch.ts b/packages/web/src/proto/zoekt/webserver/v1/LineFragmentMatch.ts new file mode 100644 index 000000000..7c21fa20e --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/LineFragmentMatch.ts @@ -0,0 +1,38 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + +import type { SymbolInfo as _zoekt_webserver_v1_SymbolInfo, SymbolInfo__Output as _zoekt_webserver_v1_SymbolInfo__Output } from '../../../zoekt/webserver/v1/SymbolInfo'; +import type { Long } from '@grpc/proto-loader'; + +export interface LineFragmentMatch { + /** + * Offset within the line, in bytes. + */ + 'line_offset'?: (number | string | Long); + /** + * Offset from file start, in bytes. + */ + 'offset'?: (number); + /** + * Number bytes that match. + */ + 'match_length'?: (number | string | Long); + 'symbol_info'?: (_zoekt_webserver_v1_SymbolInfo | null); + '_symbol_info'?: "symbol_info"; +} + +export interface LineFragmentMatch__Output { + /** + * Offset within the line, in bytes. + */ + 'line_offset': (number); + /** + * Offset from file start, in bytes. + */ + 'offset': (number); + /** + * Number bytes that match. + */ + 'match_length': (number); + 'symbol_info'?: (_zoekt_webserver_v1_SymbolInfo__Output | null); + '_symbol_info'?: "symbol_info"; +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/LineMatch.ts b/packages/web/src/proto/zoekt/webserver/v1/LineMatch.ts new file mode 100644 index 000000000..f58361bdf --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/LineMatch.ts @@ -0,0 +1,50 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + +import type { LineFragmentMatch as _zoekt_webserver_v1_LineFragmentMatch, LineFragmentMatch__Output as _zoekt_webserver_v1_LineFragmentMatch__Output } from '../../../zoekt/webserver/v1/LineFragmentMatch'; +import type { Long } from '@grpc/proto-loader'; + +export interface LineMatch { + 'line'?: (Buffer | Uint8Array | string); + 'line_start'?: (number | string | Long); + 'line_end'?: (number | string | Long); + 'line_number'?: (number | string | Long); + /** + * before and after are only set when SearchOptions.NumContextLines is > 0 + */ + 'before'?: (Buffer | Uint8Array | string); + 'after'?: (Buffer | Uint8Array | string); + /** + * If set, this was a match on the filename. + */ + 'file_name'?: (boolean); + /** + * The higher the better. Only ranks the quality of the match + * within the file, does not take rank of file into account + */ + 'score'?: (number | string); + 'debug_score'?: (string); + 'line_fragments'?: (_zoekt_webserver_v1_LineFragmentMatch)[]; +} + +export interface LineMatch__Output { + 'line': (Buffer); + 'line_start': (number); + 'line_end': (number); + 'line_number': (number); + /** + * before and after are only set when SearchOptions.NumContextLines is > 0 + */ + 'before': (Buffer); + 'after': (Buffer); + /** + * If set, this was a match on the filename. + */ + 'file_name': (boolean); + /** + * The higher the better. Only ranks the quality of the match + * within the file, does not take rank of file into account + */ + 'score': (number); + 'debug_score': (string); + 'line_fragments': (_zoekt_webserver_v1_LineFragmentMatch__Output)[]; +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/ListOptions.ts b/packages/web/src/proto/zoekt/webserver/v1/ListOptions.ts new file mode 100644 index 000000000..1b1784384 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/ListOptions.ts @@ -0,0 +1,34 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + + +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + +export const _zoekt_webserver_v1_ListOptions_RepoListField = { + REPO_LIST_FIELD_UNKNOWN_UNSPECIFIED: 'REPO_LIST_FIELD_UNKNOWN_UNSPECIFIED', + REPO_LIST_FIELD_REPOS: 'REPO_LIST_FIELD_REPOS', + REPO_LIST_FIELD_REPOS_MAP: 'REPO_LIST_FIELD_REPOS_MAP', +} as const; + +export type _zoekt_webserver_v1_ListOptions_RepoListField = + | 'REPO_LIST_FIELD_UNKNOWN_UNSPECIFIED' + | 0 + | 'REPO_LIST_FIELD_REPOS' + | 1 + | 'REPO_LIST_FIELD_REPOS_MAP' + | 3 + +export type _zoekt_webserver_v1_ListOptions_RepoListField__Output = typeof _zoekt_webserver_v1_ListOptions_RepoListField[keyof typeof _zoekt_webserver_v1_ListOptions_RepoListField] + +export interface ListOptions { + /** + * Field decides which field to populate in RepoList response. + */ + 'field'?: (_zoekt_webserver_v1_ListOptions_RepoListField); +} + +export interface ListOptions__Output { + /** + * Field decides which field to populate in RepoList response. + */ + 'field': (_zoekt_webserver_v1_ListOptions_RepoListField__Output); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/ListRequest.ts b/packages/web/src/proto/zoekt/webserver/v1/ListRequest.ts new file mode 100644 index 000000000..90fd117f9 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/ListRequest.ts @@ -0,0 +1,14 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + +import type { Q as _zoekt_webserver_v1_Q, Q__Output as _zoekt_webserver_v1_Q__Output } from '../../../zoekt/webserver/v1/Q'; +import type { ListOptions as _zoekt_webserver_v1_ListOptions, ListOptions__Output as _zoekt_webserver_v1_ListOptions__Output } from '../../../zoekt/webserver/v1/ListOptions'; + +export interface ListRequest { + 'query'?: (_zoekt_webserver_v1_Q | null); + 'opts'?: (_zoekt_webserver_v1_ListOptions | null); +} + +export interface ListRequest__Output { + 'query': (_zoekt_webserver_v1_Q__Output | null); + 'opts': (_zoekt_webserver_v1_ListOptions__Output | null); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/ListResponse.ts b/packages/web/src/proto/zoekt/webserver/v1/ListResponse.ts new file mode 100644 index 000000000..909ca2213 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/ListResponse.ts @@ -0,0 +1,40 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + +import type { RepoListEntry as _zoekt_webserver_v1_RepoListEntry, RepoListEntry__Output as _zoekt_webserver_v1_RepoListEntry__Output } from '../../../zoekt/webserver/v1/RepoListEntry'; +import type { MinimalRepoListEntry as _zoekt_webserver_v1_MinimalRepoListEntry, MinimalRepoListEntry__Output as _zoekt_webserver_v1_MinimalRepoListEntry__Output } from '../../../zoekt/webserver/v1/MinimalRepoListEntry'; +import type { RepoStats as _zoekt_webserver_v1_RepoStats, RepoStats__Output as _zoekt_webserver_v1_RepoStats__Output } from '../../../zoekt/webserver/v1/RepoStats'; +import type { Long } from '@grpc/proto-loader'; + +export interface ListResponse { + /** + * Returned when ListOptions.Field is RepoListFieldRepos. + */ + 'repos'?: (_zoekt_webserver_v1_RepoListEntry)[]; + /** + * ReposMap is set when ListOptions.Field is RepoListFieldReposMap. + */ + 'repos_map'?: ({[key: number]: _zoekt_webserver_v1_MinimalRepoListEntry}); + 'crashes'?: (number | string | Long); + /** + * Stats response to a List request. + * This is the aggregate RepoStats of all repos matching the input query. + */ + 'stats'?: (_zoekt_webserver_v1_RepoStats | null); +} + +export interface ListResponse__Output { + /** + * Returned when ListOptions.Field is RepoListFieldRepos. + */ + 'repos': (_zoekt_webserver_v1_RepoListEntry__Output)[]; + /** + * ReposMap is set when ListOptions.Field is RepoListFieldReposMap. + */ + 'repos_map': ({[key: number]: _zoekt_webserver_v1_MinimalRepoListEntry__Output}); + 'crashes': (number); + /** + * Stats response to a List request. + * This is the aggregate RepoStats of all repos matching the input query. + */ + 'stats': (_zoekt_webserver_v1_RepoStats__Output | null); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/Location.ts b/packages/web/src/proto/zoekt/webserver/v1/Location.ts new file mode 100644 index 000000000..8f840a7be --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/Location.ts @@ -0,0 +1,32 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + + +export interface Location { + /** + * 0-based byte offset from the beginning of the file + */ + 'byte_offset'?: (number); + /** + * 1-based line number from the beginning of the file + */ + 'line_number'?: (number); + /** + * 1-based column number (in runes) from the beginning of line + */ + 'column'?: (number); +} + +export interface Location__Output { + /** + * 0-based byte offset from the beginning of the file + */ + 'byte_offset': (number); + /** + * 1-based line number from the beginning of the file + */ + 'line_number': (number); + /** + * 1-based column number (in runes) from the beginning of line + */ + 'column': (number); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/MinimalRepoListEntry.ts b/packages/web/src/proto/zoekt/webserver/v1/MinimalRepoListEntry.ts new file mode 100644 index 000000000..ba3618886 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/MinimalRepoListEntry.ts @@ -0,0 +1,16 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + +import type { RepositoryBranch as _zoekt_webserver_v1_RepositoryBranch, RepositoryBranch__Output as _zoekt_webserver_v1_RepositoryBranch__Output } from '../../../zoekt/webserver/v1/RepositoryBranch'; +import type { Long } from '@grpc/proto-loader'; + +export interface MinimalRepoListEntry { + 'has_symbols'?: (boolean); + 'branches'?: (_zoekt_webserver_v1_RepositoryBranch)[]; + 'index_time_unix'?: (number | string | Long); +} + +export interface MinimalRepoListEntry__Output { + 'has_symbols': (boolean); + 'branches': (_zoekt_webserver_v1_RepositoryBranch__Output)[]; + 'index_time_unix': (number); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/Not.ts b/packages/web/src/proto/zoekt/webserver/v1/Not.ts new file mode 100644 index 000000000..6f7e43b70 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/Not.ts @@ -0,0 +1,17 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/query.proto + +import type { Q as _zoekt_webserver_v1_Q, Q__Output as _zoekt_webserver_v1_Q__Output } from '../../../zoekt/webserver/v1/Q'; + +/** + * Not inverts the meaning of its child. + */ +export interface Not { + 'child'?: (_zoekt_webserver_v1_Q | null); +} + +/** + * Not inverts the meaning of its child. + */ +export interface Not__Output { + 'child': (_zoekt_webserver_v1_Q__Output | null); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/Or.ts b/packages/web/src/proto/zoekt/webserver/v1/Or.ts new file mode 100644 index 000000000..0e3e5974b --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/Or.ts @@ -0,0 +1,17 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/query.proto + +import type { Q as _zoekt_webserver_v1_Q, Q__Output as _zoekt_webserver_v1_Q__Output } from '../../../zoekt/webserver/v1/Q'; + +/** + * Or is matched when any of its children is matched. + */ +export interface Or { + 'children'?: (_zoekt_webserver_v1_Q)[]; +} + +/** + * Or is matched when any of its children is matched. + */ +export interface Or__Output { + 'children': (_zoekt_webserver_v1_Q__Output)[]; +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/Progress.ts b/packages/web/src/proto/zoekt/webserver/v1/Progress.ts new file mode 100644 index 000000000..1d3f36b24 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/Progress.ts @@ -0,0 +1,42 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + + +/** + * Progress contains information about the global progress of the running search query. + * This is used by the frontend to reorder results and emit them when stable. + * Sourcegraph specific: this is used when querying multiple zoekt-webserver instances. + */ +export interface Progress { + /** + * Priority of the shard that was searched. + */ + 'priority'?: (number | string); + /** + * max_pending_priority is the maximum priority of pending result that is being searched in parallel. + * This is used to reorder results when the result set is known to be stable-- that is, when a result's + * Priority is greater than the max(MaxPendingPriority) from the latest results of each backend, it can be returned to the user. + * + * max_pending_priority decreases monotonically in each SearchResult. + */ + 'max_pending_priority'?: (number | string); +} + +/** + * Progress contains information about the global progress of the running search query. + * This is used by the frontend to reorder results and emit them when stable. + * Sourcegraph specific: this is used when querying multiple zoekt-webserver instances. + */ +export interface Progress__Output { + /** + * Priority of the shard that was searched. + */ + 'priority': (number); + /** + * max_pending_priority is the maximum priority of pending result that is being searched in parallel. + * This is used to reorder results when the result set is known to be stable-- that is, when a result's + * Priority is greater than the max(MaxPendingPriority) from the latest results of each backend, it can be returned to the user. + * + * max_pending_priority decreases monotonically in each SearchResult. + */ + 'max_pending_priority': (number); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/Q.ts b/packages/web/src/proto/zoekt/webserver/v1/Q.ts new file mode 100644 index 000000000..bf66e0272 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/Q.ts @@ -0,0 +1,63 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/query.proto + +import type { RawConfig as _zoekt_webserver_v1_RawConfig, RawConfig__Output as _zoekt_webserver_v1_RawConfig__Output } from '../../../zoekt/webserver/v1/RawConfig'; +import type { Regexp as _zoekt_webserver_v1_Regexp, Regexp__Output as _zoekt_webserver_v1_Regexp__Output } from '../../../zoekt/webserver/v1/Regexp'; +import type { Symbol as _zoekt_webserver_v1_Symbol, Symbol__Output as _zoekt_webserver_v1_Symbol__Output } from '../../../zoekt/webserver/v1/Symbol'; +import type { Language as _zoekt_webserver_v1_Language, Language__Output as _zoekt_webserver_v1_Language__Output } from '../../../zoekt/webserver/v1/Language'; +import type { Repo as _zoekt_webserver_v1_Repo, Repo__Output as _zoekt_webserver_v1_Repo__Output } from '../../../zoekt/webserver/v1/Repo'; +import type { RepoRegexp as _zoekt_webserver_v1_RepoRegexp, RepoRegexp__Output as _zoekt_webserver_v1_RepoRegexp__Output } from '../../../zoekt/webserver/v1/RepoRegexp'; +import type { BranchesRepos as _zoekt_webserver_v1_BranchesRepos, BranchesRepos__Output as _zoekt_webserver_v1_BranchesRepos__Output } from '../../../zoekt/webserver/v1/BranchesRepos'; +import type { RepoIds as _zoekt_webserver_v1_RepoIds, RepoIds__Output as _zoekt_webserver_v1_RepoIds__Output } from '../../../zoekt/webserver/v1/RepoIds'; +import type { RepoSet as _zoekt_webserver_v1_RepoSet, RepoSet__Output as _zoekt_webserver_v1_RepoSet__Output } from '../../../zoekt/webserver/v1/RepoSet'; +import type { FileNameSet as _zoekt_webserver_v1_FileNameSet, FileNameSet__Output as _zoekt_webserver_v1_FileNameSet__Output } from '../../../zoekt/webserver/v1/FileNameSet'; +import type { Type as _zoekt_webserver_v1_Type, Type__Output as _zoekt_webserver_v1_Type__Output } from '../../../zoekt/webserver/v1/Type'; +import type { Substring as _zoekt_webserver_v1_Substring, Substring__Output as _zoekt_webserver_v1_Substring__Output } from '../../../zoekt/webserver/v1/Substring'; +import type { And as _zoekt_webserver_v1_And, And__Output as _zoekt_webserver_v1_And__Output } from '../../../zoekt/webserver/v1/And'; +import type { Or as _zoekt_webserver_v1_Or, Or__Output as _zoekt_webserver_v1_Or__Output } from '../../../zoekt/webserver/v1/Or'; +import type { Not as _zoekt_webserver_v1_Not, Not__Output as _zoekt_webserver_v1_Not__Output } from '../../../zoekt/webserver/v1/Not'; +import type { Branch as _zoekt_webserver_v1_Branch, Branch__Output as _zoekt_webserver_v1_Branch__Output } from '../../../zoekt/webserver/v1/Branch'; +import type { Boost as _zoekt_webserver_v1_Boost, Boost__Output as _zoekt_webserver_v1_Boost__Output } from '../../../zoekt/webserver/v1/Boost'; + +export interface Q { + 'raw_config'?: (_zoekt_webserver_v1_RawConfig | null); + 'regexp'?: (_zoekt_webserver_v1_Regexp | null); + 'symbol'?: (_zoekt_webserver_v1_Symbol | null); + 'language'?: (_zoekt_webserver_v1_Language | null); + 'const'?: (boolean); + 'repo'?: (_zoekt_webserver_v1_Repo | null); + 'repo_regexp'?: (_zoekt_webserver_v1_RepoRegexp | null); + 'branches_repos'?: (_zoekt_webserver_v1_BranchesRepos | null); + 'repo_ids'?: (_zoekt_webserver_v1_RepoIds | null); + 'repo_set'?: (_zoekt_webserver_v1_RepoSet | null); + 'file_name_set'?: (_zoekt_webserver_v1_FileNameSet | null); + 'type'?: (_zoekt_webserver_v1_Type | null); + 'substring'?: (_zoekt_webserver_v1_Substring | null); + 'and'?: (_zoekt_webserver_v1_And | null); + 'or'?: (_zoekt_webserver_v1_Or | null); + 'not'?: (_zoekt_webserver_v1_Not | null); + 'branch'?: (_zoekt_webserver_v1_Branch | null); + 'boost'?: (_zoekt_webserver_v1_Boost | null); + 'query'?: "raw_config"|"regexp"|"symbol"|"language"|"const"|"repo"|"repo_regexp"|"branches_repos"|"repo_ids"|"repo_set"|"file_name_set"|"type"|"substring"|"and"|"or"|"not"|"branch"|"boost"; +} + +export interface Q__Output { + 'raw_config'?: (_zoekt_webserver_v1_RawConfig__Output | null); + 'regexp'?: (_zoekt_webserver_v1_Regexp__Output | null); + 'symbol'?: (_zoekt_webserver_v1_Symbol__Output | null); + 'language'?: (_zoekt_webserver_v1_Language__Output | null); + 'const'?: (boolean); + 'repo'?: (_zoekt_webserver_v1_Repo__Output | null); + 'repo_regexp'?: (_zoekt_webserver_v1_RepoRegexp__Output | null); + 'branches_repos'?: (_zoekt_webserver_v1_BranchesRepos__Output | null); + 'repo_ids'?: (_zoekt_webserver_v1_RepoIds__Output | null); + 'repo_set'?: (_zoekt_webserver_v1_RepoSet__Output | null); + 'file_name_set'?: (_zoekt_webserver_v1_FileNameSet__Output | null); + 'type'?: (_zoekt_webserver_v1_Type__Output | null); + 'substring'?: (_zoekt_webserver_v1_Substring__Output | null); + 'and'?: (_zoekt_webserver_v1_And__Output | null); + 'or'?: (_zoekt_webserver_v1_Or__Output | null); + 'not'?: (_zoekt_webserver_v1_Not__Output | null); + 'branch'?: (_zoekt_webserver_v1_Branch__Output | null); + 'boost'?: (_zoekt_webserver_v1_Boost__Output | null); + 'query'?: "raw_config"|"regexp"|"symbol"|"language"|"const"|"repo"|"repo_regexp"|"branches_repos"|"repo_ids"|"repo_set"|"file_name_set"|"type"|"substring"|"and"|"or"|"not"|"branch"|"boost"; +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/Range.ts b/packages/web/src/proto/zoekt/webserver/v1/Range.ts new file mode 100644 index 000000000..a6e69d3d8 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/Range.ts @@ -0,0 +1,25 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + +import type { Location as _zoekt_webserver_v1_Location, Location__Output as _zoekt_webserver_v1_Location__Output } from '../../../zoekt/webserver/v1/Location'; + +export interface Range { + /** + * The inclusive beginning of the range. + */ + 'start'?: (_zoekt_webserver_v1_Location | null); + /** + * The exclusive end of the range. + */ + 'end'?: (_zoekt_webserver_v1_Location | null); +} + +export interface Range__Output { + /** + * The inclusive beginning of the range. + */ + 'start': (_zoekt_webserver_v1_Location__Output | null); + /** + * The exclusive end of the range. + */ + 'end': (_zoekt_webserver_v1_Location__Output | null); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/RawConfig.ts b/packages/web/src/proto/zoekt/webserver/v1/RawConfig.ts new file mode 100644 index 000000000..6379dbbe7 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/RawConfig.ts @@ -0,0 +1,46 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/query.proto + + +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/query.proto + +export const _zoekt_webserver_v1_RawConfig_Flag = { + FLAG_UNKNOWN_UNSPECIFIED: 'FLAG_UNKNOWN_UNSPECIFIED', + FLAG_ONLY_PUBLIC: 'FLAG_ONLY_PUBLIC', + FLAG_ONLY_PRIVATE: 'FLAG_ONLY_PRIVATE', + FLAG_ONLY_FORKS: 'FLAG_ONLY_FORKS', + FLAG_NO_FORKS: 'FLAG_NO_FORKS', + FLAG_ONLY_ARCHIVED: 'FLAG_ONLY_ARCHIVED', + FLAG_NO_ARCHIVED: 'FLAG_NO_ARCHIVED', +} as const; + +export type _zoekt_webserver_v1_RawConfig_Flag = + | 'FLAG_UNKNOWN_UNSPECIFIED' + | 0 + | 'FLAG_ONLY_PUBLIC' + | 1 + | 'FLAG_ONLY_PRIVATE' + | 2 + | 'FLAG_ONLY_FORKS' + | 4 + | 'FLAG_NO_FORKS' + | 8 + | 'FLAG_ONLY_ARCHIVED' + | 16 + | 'FLAG_NO_ARCHIVED' + | 32 + +export type _zoekt_webserver_v1_RawConfig_Flag__Output = typeof _zoekt_webserver_v1_RawConfig_Flag[keyof typeof _zoekt_webserver_v1_RawConfig_Flag] + +/** + * RawConfig filters repositories based on their encoded RawConfig map. + */ +export interface RawConfig { + 'flags'?: (_zoekt_webserver_v1_RawConfig_Flag)[]; +} + +/** + * RawConfig filters repositories based on their encoded RawConfig map. + */ +export interface RawConfig__Output { + 'flags': (_zoekt_webserver_v1_RawConfig_Flag__Output)[]; +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/Regexp.ts b/packages/web/src/proto/zoekt/webserver/v1/Regexp.ts new file mode 100644 index 000000000..67582b031 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/Regexp.ts @@ -0,0 +1,22 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/query.proto + + +/** + * Regexp is a query looking for regular expressions matches. + */ +export interface Regexp { + 'regexp'?: (string); + 'file_name'?: (boolean); + 'content'?: (boolean); + 'case_sensitive'?: (boolean); +} + +/** + * Regexp is a query looking for regular expressions matches. + */ +export interface Regexp__Output { + 'regexp': (string); + 'file_name': (boolean); + 'content': (boolean); + 'case_sensitive': (boolean); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/Repo.ts b/packages/web/src/proto/zoekt/webserver/v1/Repo.ts new file mode 100644 index 000000000..a10958831 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/Repo.ts @@ -0,0 +1,10 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/query.proto + + +export interface Repo { + 'regexp'?: (string); +} + +export interface Repo__Output { + 'regexp': (string); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/RepoIds.ts b/packages/web/src/proto/zoekt/webserver/v1/RepoIds.ts new file mode 100644 index 000000000..7ba23d27d --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/RepoIds.ts @@ -0,0 +1,24 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/query.proto + + +/** + * Similar to BranchRepos but will be used to match only by repoid and + * therefore matches all branches + */ +export interface RepoIds { + /** + * a serialized roaring bitmap of the target repo ids + */ + 'repos'?: (Buffer | Uint8Array | string); +} + +/** + * Similar to BranchRepos but will be used to match only by repoid and + * therefore matches all branches + */ +export interface RepoIds__Output { + /** + * a serialized roaring bitmap of the target repo ids + */ + 'repos': (Buffer); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/RepoListEntry.ts b/packages/web/src/proto/zoekt/webserver/v1/RepoListEntry.ts new file mode 100644 index 000000000..a216311df --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/RepoListEntry.ts @@ -0,0 +1,17 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + +import type { Repository as _zoekt_webserver_v1_Repository, Repository__Output as _zoekt_webserver_v1_Repository__Output } from '../../../zoekt/webserver/v1/Repository'; +import type { IndexMetadata as _zoekt_webserver_v1_IndexMetadata, IndexMetadata__Output as _zoekt_webserver_v1_IndexMetadata__Output } from '../../../zoekt/webserver/v1/IndexMetadata'; +import type { RepoStats as _zoekt_webserver_v1_RepoStats, RepoStats__Output as _zoekt_webserver_v1_RepoStats__Output } from '../../../zoekt/webserver/v1/RepoStats'; + +export interface RepoListEntry { + 'repository'?: (_zoekt_webserver_v1_Repository | null); + 'index_metadata'?: (_zoekt_webserver_v1_IndexMetadata | null); + 'stats'?: (_zoekt_webserver_v1_RepoStats | null); +} + +export interface RepoListEntry__Output { + 'repository': (_zoekt_webserver_v1_Repository__Output | null); + 'index_metadata': (_zoekt_webserver_v1_IndexMetadata__Output | null); + 'stats': (_zoekt_webserver_v1_RepoStats__Output | null); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/RepoRegexp.ts b/packages/web/src/proto/zoekt/webserver/v1/RepoRegexp.ts new file mode 100644 index 000000000..1ee2da099 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/RepoRegexp.ts @@ -0,0 +1,10 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/query.proto + + +export interface RepoRegexp { + 'regexp'?: (string); +} + +export interface RepoRegexp__Output { + 'regexp': (string); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/RepoSet.ts b/packages/web/src/proto/zoekt/webserver/v1/RepoSet.ts new file mode 100644 index 000000000..3638f1dca --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/RepoSet.ts @@ -0,0 +1,16 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/query.proto + + +/** + * RepoSet is a list of repos to match. + */ +export interface RepoSet { + 'set'?: ({[key: string]: boolean}); +} + +/** + * RepoSet is a list of repos to match. + */ +export interface RepoSet__Output { + 'set': ({[key: string]: boolean}); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/RepoStats.ts b/packages/web/src/proto/zoekt/webserver/v1/RepoStats.ts new file mode 100644 index 000000000..db507c230 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/RepoStats.ts @@ -0,0 +1,97 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * RepoStats is a collection of statistics for a set of repositories. + */ +export interface RepoStats { + /** + * repos is used for aggregrating the number of repositories. + */ + 'repos'?: (number | string | Long); + /** + * shards is the total number of search shards. + */ + 'shards'?: (number | string | Long); + /** + * documents holds the number of documents or files. + */ + 'documents'?: (number | string | Long); + /** + * index_bytes is the amount of RAM used for index overhead. + */ + 'index_bytes'?: (number | string | Long); + /** + * content_bytes is the amount of RAM used for raw content. + */ + 'content_bytes'?: (number | string | Long); + /** + * new_lines_count is the number of newlines "\n" that appear in the zoekt + * indexed documents. This is not exactly the same as line count, since it + * will not include lines not terminated by "\n" (eg a file with no "\n", or + * a final line without "\n"). Note: Zoekt deduplicates documents across + * branches, so if a path has the same contents on multiple branches, there + * is only one document for it. As such that document's newlines is only + * counted once. See DefaultBranchNewLinesCount and AllBranchesNewLinesCount + * for counts which do not deduplicate. + */ + 'new_lines_count'?: (number | string | Long); + /** + * default_branch_new_lines_count is the number of newlines "\n" in the default + * branch. + */ + 'default_branch_new_lines_count'?: (number | string | Long); + /** + * other_branches_new_lines_count is the number of newlines "\n" in all branches + * except the default branch. + */ + 'other_branches_new_lines_count'?: (number | string | Long); +} + +/** + * RepoStats is a collection of statistics for a set of repositories. + */ +export interface RepoStats__Output { + /** + * repos is used for aggregrating the number of repositories. + */ + 'repos': (number); + /** + * shards is the total number of search shards. + */ + 'shards': (number); + /** + * documents holds the number of documents or files. + */ + 'documents': (number); + /** + * index_bytes is the amount of RAM used for index overhead. + */ + 'index_bytes': (number); + /** + * content_bytes is the amount of RAM used for raw content. + */ + 'content_bytes': (number); + /** + * new_lines_count is the number of newlines "\n" that appear in the zoekt + * indexed documents. This is not exactly the same as line count, since it + * will not include lines not terminated by "\n" (eg a file with no "\n", or + * a final line without "\n"). Note: Zoekt deduplicates documents across + * branches, so if a path has the same contents on multiple branches, there + * is only one document for it. As such that document's newlines is only + * counted once. See DefaultBranchNewLinesCount and AllBranchesNewLinesCount + * for counts which do not deduplicate. + */ + 'new_lines_count': (number); + /** + * default_branch_new_lines_count is the number of newlines "\n" in the default + * branch. + */ + 'default_branch_new_lines_count': (number); + /** + * other_branches_new_lines_count is the number of newlines "\n" in all branches + * except the default branch. + */ + 'other_branches_new_lines_count': (number); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/Repository.ts b/packages/web/src/proto/zoekt/webserver/v1/Repository.ts new file mode 100644 index 000000000..347f26f34 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/Repository.ts @@ -0,0 +1,184 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + +import type { RepositoryBranch as _zoekt_webserver_v1_RepositoryBranch, RepositoryBranch__Output as _zoekt_webserver_v1_RepositoryBranch__Output } from '../../../zoekt/webserver/v1/RepositoryBranch'; +import type { Repository as _zoekt_webserver_v1_Repository, Repository__Output as _zoekt_webserver_v1_Repository__Output } from '../../../zoekt/webserver/v1/Repository'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { Long } from '@grpc/proto-loader'; + +export interface Repository { + /** + * Sourcegraph's repository ID + */ + 'id'?: (number); + /** + * The repository name + */ + 'name'?: (string); + /** + * The repository URL. + */ + 'url'?: (string); + /** + * The physical source where this repo came from, eg. full + * path to the zip filename or git repository directory. This + * will not be exposed in the UI, but can be used to detect + * orphaned index shards. + */ + 'source'?: (string); + /** + * The branches indexed in this repo. + */ + 'branches'?: (_zoekt_webserver_v1_RepositoryBranch)[]; + /** + * Nil if this is not the super project. + */ + 'sub_repo_map'?: ({[key: string]: _zoekt_webserver_v1_Repository}); + /** + * URL template to link to the commit of a branch + */ + 'commit_url_template'?: (string); + /** + * The repository URL for getting to a file. Has access to + * {{.Version}}, {{.Path}} + */ + 'file_url_template'?: (string); + /** + * The URL fragment to add to a file URL for line numbers. has + * access to {{.LineNumber}}. The fragment should include the + * separator, generally '#' or ';'. + */ + 'line_fragment_template'?: (string); + /** + * Perf optimization: priority is set when we load the shard. It corresponds to + * the value of "priority" stored in RawConfig. + */ + 'priority'?: (number | string); + /** + * All zoekt.* configuration settings. + */ + 'raw_config'?: ({[key: string]: string}); + /** + * Importance of the repository, bigger is more important + */ + 'rank'?: (number); + /** + * index_options is a hash of the options used to create the index for the + * repo. + */ + 'index_options'?: (string); + /** + * has_symbols is true if this repository has indexed ctags + * output. Sourcegraph specific: This field is more appropriate for + * IndexMetadata. However, we store it here since the Sourcegraph frontend + * can read this structure but not IndexMetadata. + */ + 'has_symbols'?: (boolean); + /** + * tombstone is true if we are not allowed to search this repo. + */ + 'tombstone'?: (boolean); + /** + * latest_commit_date is the date of the latest commit among all indexed Branches. + * The date might be time.Time's 0-value if the repository was last indexed + * before this field was added. + */ + 'latest_commit_date'?: (_google_protobuf_Timestamp | null); + /** + * file_tombstones is a set of file paths that should be ignored across all branches + * in this shard. + */ + 'file_tombstones'?: (string)[]; + /** + * tenant_id is the tenant ID of the repository. + */ + 'tenant_id'?: (number | string | Long); +} + +export interface Repository__Output { + /** + * Sourcegraph's repository ID + */ + 'id': (number); + /** + * The repository name + */ + 'name': (string); + /** + * The repository URL. + */ + 'url': (string); + /** + * The physical source where this repo came from, eg. full + * path to the zip filename or git repository directory. This + * will not be exposed in the UI, but can be used to detect + * orphaned index shards. + */ + 'source': (string); + /** + * The branches indexed in this repo. + */ + 'branches': (_zoekt_webserver_v1_RepositoryBranch__Output)[]; + /** + * Nil if this is not the super project. + */ + 'sub_repo_map': ({[key: string]: _zoekt_webserver_v1_Repository__Output}); + /** + * URL template to link to the commit of a branch + */ + 'commit_url_template': (string); + /** + * The repository URL for getting to a file. Has access to + * {{.Version}}, {{.Path}} + */ + 'file_url_template': (string); + /** + * The URL fragment to add to a file URL for line numbers. has + * access to {{.LineNumber}}. The fragment should include the + * separator, generally '#' or ';'. + */ + 'line_fragment_template': (string); + /** + * Perf optimization: priority is set when we load the shard. It corresponds to + * the value of "priority" stored in RawConfig. + */ + 'priority': (number); + /** + * All zoekt.* configuration settings. + */ + 'raw_config': ({[key: string]: string}); + /** + * Importance of the repository, bigger is more important + */ + 'rank': (number); + /** + * index_options is a hash of the options used to create the index for the + * repo. + */ + 'index_options': (string); + /** + * has_symbols is true if this repository has indexed ctags + * output. Sourcegraph specific: This field is more appropriate for + * IndexMetadata. However, we store it here since the Sourcegraph frontend + * can read this structure but not IndexMetadata. + */ + 'has_symbols': (boolean); + /** + * tombstone is true if we are not allowed to search this repo. + */ + 'tombstone': (boolean); + /** + * latest_commit_date is the date of the latest commit among all indexed Branches. + * The date might be time.Time's 0-value if the repository was last indexed + * before this field was added. + */ + 'latest_commit_date': (_google_protobuf_Timestamp__Output | null); + /** + * file_tombstones is a set of file paths that should be ignored across all branches + * in this shard. + */ + 'file_tombstones': (string)[]; + /** + * tenant_id is the tenant ID of the repository. + */ + 'tenant_id': (number); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/RepositoryBranch.ts b/packages/web/src/proto/zoekt/webserver/v1/RepositoryBranch.ts new file mode 100644 index 000000000..d18ee9554 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/RepositoryBranch.ts @@ -0,0 +1,20 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + + +/** + * RepositoryBranch describes an indexed branch, which is a name + * combined with a version. + */ +export interface RepositoryBranch { + 'name'?: (string); + 'version'?: (string); +} + +/** + * RepositoryBranch describes an indexed branch, which is a name + * combined with a version. + */ +export interface RepositoryBranch__Output { + 'name': (string); + 'version': (string); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/SearchOptions.ts b/packages/web/src/proto/zoekt/webserver/v1/SearchOptions.ts new file mode 100644 index 000000000..a3fea5a21 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/SearchOptions.ts @@ -0,0 +1,156 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../google/protobuf/Duration'; +import type { Long } from '@grpc/proto-loader'; + +export interface SearchOptions { + /** + * Return an upper-bound estimate of eligible documents in + * stats.ShardFilesConsidered. + */ + 'estimate_doc_count'?: (boolean); + /** + * Return the whole file. + */ + 'whole'?: (boolean); + /** + * Maximum number of matches: skip all processing an index + * shard after we found this many non-overlapping matches. + */ + 'shard_max_match_count'?: (number | string | Long); + /** + * Maximum number of matches: stop looking for more matches + * once we have this many matches across shards. + */ + 'total_max_match_count'?: (number | string | Long); + /** + * Maximum number of matches: skip processing documents for a repository in + * a shard once we have found ShardRepoMaxMatchCount. + * + * A compound shard may contain multiple repositories. This will most often + * be set to 1 to find all repositories containing a result. + */ + 'shard_repo_max_match_count'?: (number | string | Long); + /** + * Abort the search after this much time has passed. + */ + 'max_wall_time'?: (_google_protobuf_Duration | null); + /** + * FlushWallTime if non-zero will stop streaming behaviour at first and + * instead will collate and sort results. At FlushWallTime the results will + * be sent and then the behaviour will revert to the normal streaming. + */ + 'flush_wall_time'?: (_google_protobuf_Duration | null); + /** + * Truncates the number of documents (i.e. files) after collating and + * sorting the results. + */ + 'max_doc_display_count'?: (number | string | Long); + /** + * If set to a number greater than zero then up to this many number + * of context lines will be added before and after each matched line. + * Note that the included context lines might contain matches and + * it's up to the consumer of the result to remove those lines. + */ + 'num_context_lines'?: (number | string | Long); + /** + * If true, ChunkMatches will be returned in each FileMatch rather than LineMatches + * EXPERIMENTAL: the behavior of this flag may be changed in future versions. + */ + 'chunk_matches'?: (boolean); + /** + * Trace turns on opentracing for this request if true and if the Jaeger address was provided as + * a command-line flag + */ + 'trace'?: (boolean); + /** + * If set, the search results will contain debug information for scoring. + */ + 'debug_score'?: (boolean); + /** + * EXPERIMENTAL. If true, use text search scoring instead of the default scoring formula. + * Currently, this treats each match in a file as a term and computes an approximation to BM25. + * When enabled, all other scoring signals are ignored, including document ranks. + */ + 'use_bm25_scoring'?: (boolean); + /** + * Truncates the number of matchs after collating and sorting the results. + */ + 'max_match_display_count'?: (number | string | Long); +} + +export interface SearchOptions__Output { + /** + * Return an upper-bound estimate of eligible documents in + * stats.ShardFilesConsidered. + */ + 'estimate_doc_count': (boolean); + /** + * Return the whole file. + */ + 'whole': (boolean); + /** + * Maximum number of matches: skip all processing an index + * shard after we found this many non-overlapping matches. + */ + 'shard_max_match_count': (number); + /** + * Maximum number of matches: stop looking for more matches + * once we have this many matches across shards. + */ + 'total_max_match_count': (number); + /** + * Maximum number of matches: skip processing documents for a repository in + * a shard once we have found ShardRepoMaxMatchCount. + * + * A compound shard may contain multiple repositories. This will most often + * be set to 1 to find all repositories containing a result. + */ + 'shard_repo_max_match_count': (number); + /** + * Abort the search after this much time has passed. + */ + 'max_wall_time': (_google_protobuf_Duration__Output | null); + /** + * FlushWallTime if non-zero will stop streaming behaviour at first and + * instead will collate and sort results. At FlushWallTime the results will + * be sent and then the behaviour will revert to the normal streaming. + */ + 'flush_wall_time': (_google_protobuf_Duration__Output | null); + /** + * Truncates the number of documents (i.e. files) after collating and + * sorting the results. + */ + 'max_doc_display_count': (number); + /** + * If set to a number greater than zero then up to this many number + * of context lines will be added before and after each matched line. + * Note that the included context lines might contain matches and + * it's up to the consumer of the result to remove those lines. + */ + 'num_context_lines': (number); + /** + * If true, ChunkMatches will be returned in each FileMatch rather than LineMatches + * EXPERIMENTAL: the behavior of this flag may be changed in future versions. + */ + 'chunk_matches': (boolean); + /** + * Trace turns on opentracing for this request if true and if the Jaeger address was provided as + * a command-line flag + */ + 'trace': (boolean); + /** + * If set, the search results will contain debug information for scoring. + */ + 'debug_score': (boolean); + /** + * EXPERIMENTAL. If true, use text search scoring instead of the default scoring formula. + * Currently, this treats each match in a file as a term and computes an approximation to BM25. + * When enabled, all other scoring signals are ignored, including document ranks. + */ + 'use_bm25_scoring': (boolean); + /** + * Truncates the number of matchs after collating and sorting the results. + */ + 'max_match_display_count': (number); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/SearchRequest.ts b/packages/web/src/proto/zoekt/webserver/v1/SearchRequest.ts new file mode 100644 index 000000000..39a9fe3cc --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/SearchRequest.ts @@ -0,0 +1,14 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + +import type { Q as _zoekt_webserver_v1_Q, Q__Output as _zoekt_webserver_v1_Q__Output } from '../../../zoekt/webserver/v1/Q'; +import type { SearchOptions as _zoekt_webserver_v1_SearchOptions, SearchOptions__Output as _zoekt_webserver_v1_SearchOptions__Output } from '../../../zoekt/webserver/v1/SearchOptions'; + +export interface SearchRequest { + 'query'?: (_zoekt_webserver_v1_Q | null); + 'opts'?: (_zoekt_webserver_v1_SearchOptions | null); +} + +export interface SearchRequest__Output { + 'query': (_zoekt_webserver_v1_Q__Output | null); + 'opts': (_zoekt_webserver_v1_SearchOptions__Output | null); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/SearchResponse.ts b/packages/web/src/proto/zoekt/webserver/v1/SearchResponse.ts new file mode 100644 index 000000000..51a64177c --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/SearchResponse.ts @@ -0,0 +1,17 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + +import type { Stats as _zoekt_webserver_v1_Stats, Stats__Output as _zoekt_webserver_v1_Stats__Output } from '../../../zoekt/webserver/v1/Stats'; +import type { Progress as _zoekt_webserver_v1_Progress, Progress__Output as _zoekt_webserver_v1_Progress__Output } from '../../../zoekt/webserver/v1/Progress'; +import type { FileMatch as _zoekt_webserver_v1_FileMatch, FileMatch__Output as _zoekt_webserver_v1_FileMatch__Output } from '../../../zoekt/webserver/v1/FileMatch'; + +export interface SearchResponse { + 'stats'?: (_zoekt_webserver_v1_Stats | null); + 'progress'?: (_zoekt_webserver_v1_Progress | null); + 'files'?: (_zoekt_webserver_v1_FileMatch)[]; +} + +export interface SearchResponse__Output { + 'stats': (_zoekt_webserver_v1_Stats__Output | null); + 'progress': (_zoekt_webserver_v1_Progress__Output | null); + 'files': (_zoekt_webserver_v1_FileMatch__Output)[]; +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/Stats.ts b/packages/web/src/proto/zoekt/webserver/v1/Stats.ts new file mode 100644 index 000000000..348eea642 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/Stats.ts @@ -0,0 +1,181 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../google/protobuf/Duration'; +import type { FlushReason as _zoekt_webserver_v1_FlushReason, FlushReason__Output as _zoekt_webserver_v1_FlushReason__Output } from '../../../zoekt/webserver/v1/FlushReason'; +import type { Long } from '@grpc/proto-loader'; + +export interface Stats { + /** + * Amount of I/O for reading contents. + */ + 'content_bytes_loaded'?: (number | string | Long); + /** + * Amount of I/O for reading from index. + */ + 'index_bytes_loaded'?: (number | string | Long); + /** + * Number of search shards that had a crash. + */ + 'crashes'?: (number | string | Long); + /** + * Wall clock time for this search + */ + 'duration'?: (_google_protobuf_Duration | null); + /** + * Number of files containing a match. + */ + 'file_count'?: (number | string | Long); + /** + * Number of files in shards that we considered. + */ + 'shard_files_considered'?: (number | string | Long); + /** + * Files that we evaluated. Equivalent to files for which all + * atom matches (including negations) evaluated to true. + */ + 'files_considered'?: (number | string | Long); + /** + * Files for which we loaded file content to verify substring matches + */ + 'files_loaded'?: (number | string | Long); + /** + * Candidate files whose contents weren't examined because we + * gathered enough matches. + */ + 'files_skipped'?: (number | string | Long); + /** + * Shards that we scanned to find matches. + */ + 'shards_scanned'?: (number | string | Long); + /** + * Shards that we did not process because a query was canceled. + */ + 'shards_skipped'?: (number | string | Long); + /** + * Shards that we did not process because the query was rejected by the + * ngram filter indicating it had no matches. + */ + 'shards_skipped_filter'?: (number | string | Long); + /** + * Number of non-overlapping matches + */ + 'match_count'?: (number | string | Long); + /** + * Number of candidate matches as a result of searching ngrams. + */ + 'ngram_matches'?: (number | string | Long); + /** + * Wall clock time for queued search. + */ + 'wait'?: (_google_protobuf_Duration | null); + /** + * Number of times regexp was called on files that we evaluated. + */ + 'regexps_considered'?: (number | string | Long); + /** + * FlushReason explains why results were flushed. + */ + 'flush_reason'?: (_zoekt_webserver_v1_FlushReason); + /** + * NgramLookups is the number of times we accessed an ngram in the index. + */ + 'ngram_lookups'?: (number | string | Long); + /** + * Aggregate wall clock time spent constructing and pruning the match tree. + * This accounts for time such as lookups in the trigram index. + */ + 'match_tree_construction'?: (_google_protobuf_Duration | null); + /** + * Aggregate wall clock time spent searching the match tree. This accounts + * for the bulk of search work done looking for matches. + */ + 'match_tree_search'?: (_google_protobuf_Duration | null); +} + +export interface Stats__Output { + /** + * Amount of I/O for reading contents. + */ + 'content_bytes_loaded': (number); + /** + * Amount of I/O for reading from index. + */ + 'index_bytes_loaded': (number); + /** + * Number of search shards that had a crash. + */ + 'crashes': (number); + /** + * Wall clock time for this search + */ + 'duration': (_google_protobuf_Duration__Output | null); + /** + * Number of files containing a match. + */ + 'file_count': (number); + /** + * Number of files in shards that we considered. + */ + 'shard_files_considered': (number); + /** + * Files that we evaluated. Equivalent to files for which all + * atom matches (including negations) evaluated to true. + */ + 'files_considered': (number); + /** + * Files for which we loaded file content to verify substring matches + */ + 'files_loaded': (number); + /** + * Candidate files whose contents weren't examined because we + * gathered enough matches. + */ + 'files_skipped': (number); + /** + * Shards that we scanned to find matches. + */ + 'shards_scanned': (number); + /** + * Shards that we did not process because a query was canceled. + */ + 'shards_skipped': (number); + /** + * Shards that we did not process because the query was rejected by the + * ngram filter indicating it had no matches. + */ + 'shards_skipped_filter': (number); + /** + * Number of non-overlapping matches + */ + 'match_count': (number); + /** + * Number of candidate matches as a result of searching ngrams. + */ + 'ngram_matches': (number); + /** + * Wall clock time for queued search. + */ + 'wait': (_google_protobuf_Duration__Output | null); + /** + * Number of times regexp was called on files that we evaluated. + */ + 'regexps_considered': (number); + /** + * FlushReason explains why results were flushed. + */ + 'flush_reason': (_zoekt_webserver_v1_FlushReason__Output); + /** + * NgramLookups is the number of times we accessed an ngram in the index. + */ + 'ngram_lookups': (number); + /** + * Aggregate wall clock time spent constructing and pruning the match tree. + * This accounts for time such as lookups in the trigram index. + */ + 'match_tree_construction': (_google_protobuf_Duration__Output | null); + /** + * Aggregate wall clock time spent searching the match tree. This accounts + * for the bulk of search work done looking for matches. + */ + 'match_tree_search': (_google_protobuf_Duration__Output | null); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/StreamSearchRequest.ts b/packages/web/src/proto/zoekt/webserver/v1/StreamSearchRequest.ts new file mode 100644 index 000000000..219e34773 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/StreamSearchRequest.ts @@ -0,0 +1,11 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + +import type { SearchRequest as _zoekt_webserver_v1_SearchRequest, SearchRequest__Output as _zoekt_webserver_v1_SearchRequest__Output } from '../../../zoekt/webserver/v1/SearchRequest'; + +export interface StreamSearchRequest { + 'request'?: (_zoekt_webserver_v1_SearchRequest | null); +} + +export interface StreamSearchRequest__Output { + 'request': (_zoekt_webserver_v1_SearchRequest__Output | null); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/StreamSearchResponse.ts b/packages/web/src/proto/zoekt/webserver/v1/StreamSearchResponse.ts new file mode 100644 index 000000000..7b9276ee3 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/StreamSearchResponse.ts @@ -0,0 +1,11 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + +import type { SearchResponse as _zoekt_webserver_v1_SearchResponse, SearchResponse__Output as _zoekt_webserver_v1_SearchResponse__Output } from '../../../zoekt/webserver/v1/SearchResponse'; + +export interface StreamSearchResponse { + 'response_chunk'?: (_zoekt_webserver_v1_SearchResponse | null); +} + +export interface StreamSearchResponse__Output { + 'response_chunk': (_zoekt_webserver_v1_SearchResponse__Output | null); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/Substring.ts b/packages/web/src/proto/zoekt/webserver/v1/Substring.ts new file mode 100644 index 000000000..07520aa32 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/Substring.ts @@ -0,0 +1,28 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/query.proto + + +export interface Substring { + 'pattern'?: (string); + 'case_sensitive'?: (boolean); + /** + * Match only filename + */ + 'file_name'?: (boolean); + /** + * Match only content + */ + 'content'?: (boolean); +} + +export interface Substring__Output { + 'pattern': (string); + 'case_sensitive': (boolean); + /** + * Match only filename + */ + 'file_name': (boolean); + /** + * Match only content + */ + 'content': (boolean); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/Symbol.ts b/packages/web/src/proto/zoekt/webserver/v1/Symbol.ts new file mode 100644 index 000000000..7ccf5c71d --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/Symbol.ts @@ -0,0 +1,11 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/query.proto + +import type { Q as _zoekt_webserver_v1_Q, Q__Output as _zoekt_webserver_v1_Q__Output } from '../../../zoekt/webserver/v1/Q'; + +export interface Symbol { + 'expr'?: (_zoekt_webserver_v1_Q | null); +} + +export interface Symbol__Output { + 'expr': (_zoekt_webserver_v1_Q__Output | null); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/SymbolInfo.ts b/packages/web/src/proto/zoekt/webserver/v1/SymbolInfo.ts new file mode 100644 index 000000000..5622d50fe --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/SymbolInfo.ts @@ -0,0 +1,16 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + + +export interface SymbolInfo { + 'sym'?: (string); + 'kind'?: (string); + 'parent'?: (string); + 'parent_kind'?: (string); +} + +export interface SymbolInfo__Output { + 'sym': (string); + 'kind': (string); + 'parent': (string); + 'parent_kind': (string); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/Type.ts b/packages/web/src/proto/zoekt/webserver/v1/Type.ts new file mode 100644 index 000000000..6f0163d33 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/Type.ts @@ -0,0 +1,46 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/query.proto + +import type { Q as _zoekt_webserver_v1_Q, Q__Output as _zoekt_webserver_v1_Q__Output } from '../../../zoekt/webserver/v1/Q'; + +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/query.proto + +export const _zoekt_webserver_v1_Type_Kind = { + KIND_UNKNOWN_UNSPECIFIED: 'KIND_UNKNOWN_UNSPECIFIED', + KIND_FILE_MATCH: 'KIND_FILE_MATCH', + KIND_FILE_NAME: 'KIND_FILE_NAME', + KIND_REPO: 'KIND_REPO', +} as const; + +export type _zoekt_webserver_v1_Type_Kind = + | 'KIND_UNKNOWN_UNSPECIFIED' + | 0 + | 'KIND_FILE_MATCH' + | 1 + | 'KIND_FILE_NAME' + | 2 + | 'KIND_REPO' + | 3 + +export type _zoekt_webserver_v1_Type_Kind__Output = typeof _zoekt_webserver_v1_Type_Kind[keyof typeof _zoekt_webserver_v1_Type_Kind] + +/** + * Type changes the result type returned. + */ +export interface Type { + 'child'?: (_zoekt_webserver_v1_Q | null); + /** + * TODO: type constants + */ + 'type'?: (_zoekt_webserver_v1_Type_Kind); +} + +/** + * Type changes the result type returned. + */ +export interface Type__Output { + 'child': (_zoekt_webserver_v1_Q__Output | null); + /** + * TODO: type constants + */ + 'type': (_zoekt_webserver_v1_Type_Kind__Output); +} diff --git a/packages/web/src/proto/zoekt/webserver/v1/WebserverService.ts b/packages/web/src/proto/zoekt/webserver/v1/WebserverService.ts new file mode 100644 index 000000000..c7c80eab5 --- /dev/null +++ b/packages/web/src/proto/zoekt/webserver/v1/WebserverService.ts @@ -0,0 +1,63 @@ +// Original file: ../../vendor/zoekt/grpc/protos/zoekt/webserver/v1/webserver.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { ListRequest as _zoekt_webserver_v1_ListRequest, ListRequest__Output as _zoekt_webserver_v1_ListRequest__Output } from '../../../zoekt/webserver/v1/ListRequest'; +import type { ListResponse as _zoekt_webserver_v1_ListResponse, ListResponse__Output as _zoekt_webserver_v1_ListResponse__Output } from '../../../zoekt/webserver/v1/ListResponse'; +import type { SearchRequest as _zoekt_webserver_v1_SearchRequest, SearchRequest__Output as _zoekt_webserver_v1_SearchRequest__Output } from '../../../zoekt/webserver/v1/SearchRequest'; +import type { SearchResponse as _zoekt_webserver_v1_SearchResponse, SearchResponse__Output as _zoekt_webserver_v1_SearchResponse__Output } from '../../../zoekt/webserver/v1/SearchResponse'; +import type { StreamSearchRequest as _zoekt_webserver_v1_StreamSearchRequest, StreamSearchRequest__Output as _zoekt_webserver_v1_StreamSearchRequest__Output } from '../../../zoekt/webserver/v1/StreamSearchRequest'; +import type { StreamSearchResponse as _zoekt_webserver_v1_StreamSearchResponse, StreamSearchResponse__Output as _zoekt_webserver_v1_StreamSearchResponse__Output } from '../../../zoekt/webserver/v1/StreamSearchResponse'; + +export interface WebserverServiceClient extends grpc.Client { + /** + * List lists repositories. The query `q` can only contain + * query.Repo atoms. + */ + List(argument: _zoekt_webserver_v1_ListRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_zoekt_webserver_v1_ListResponse__Output>): grpc.ClientUnaryCall; + List(argument: _zoekt_webserver_v1_ListRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_zoekt_webserver_v1_ListResponse__Output>): grpc.ClientUnaryCall; + List(argument: _zoekt_webserver_v1_ListRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_zoekt_webserver_v1_ListResponse__Output>): grpc.ClientUnaryCall; + List(argument: _zoekt_webserver_v1_ListRequest, callback: grpc.requestCallback<_zoekt_webserver_v1_ListResponse__Output>): grpc.ClientUnaryCall; + /** + * List lists repositories. The query `q` can only contain + * query.Repo atoms. + */ + list(argument: _zoekt_webserver_v1_ListRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_zoekt_webserver_v1_ListResponse__Output>): grpc.ClientUnaryCall; + list(argument: _zoekt_webserver_v1_ListRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_zoekt_webserver_v1_ListResponse__Output>): grpc.ClientUnaryCall; + list(argument: _zoekt_webserver_v1_ListRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_zoekt_webserver_v1_ListResponse__Output>): grpc.ClientUnaryCall; + list(argument: _zoekt_webserver_v1_ListRequest, callback: grpc.requestCallback<_zoekt_webserver_v1_ListResponse__Output>): grpc.ClientUnaryCall; + + Search(argument: _zoekt_webserver_v1_SearchRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_zoekt_webserver_v1_SearchResponse__Output>): grpc.ClientUnaryCall; + Search(argument: _zoekt_webserver_v1_SearchRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_zoekt_webserver_v1_SearchResponse__Output>): grpc.ClientUnaryCall; + Search(argument: _zoekt_webserver_v1_SearchRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_zoekt_webserver_v1_SearchResponse__Output>): grpc.ClientUnaryCall; + Search(argument: _zoekt_webserver_v1_SearchRequest, callback: grpc.requestCallback<_zoekt_webserver_v1_SearchResponse__Output>): grpc.ClientUnaryCall; + search(argument: _zoekt_webserver_v1_SearchRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_zoekt_webserver_v1_SearchResponse__Output>): grpc.ClientUnaryCall; + search(argument: _zoekt_webserver_v1_SearchRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_zoekt_webserver_v1_SearchResponse__Output>): grpc.ClientUnaryCall; + search(argument: _zoekt_webserver_v1_SearchRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_zoekt_webserver_v1_SearchResponse__Output>): grpc.ClientUnaryCall; + search(argument: _zoekt_webserver_v1_SearchRequest, callback: grpc.requestCallback<_zoekt_webserver_v1_SearchResponse__Output>): grpc.ClientUnaryCall; + + StreamSearch(argument: _zoekt_webserver_v1_StreamSearchRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_zoekt_webserver_v1_StreamSearchResponse__Output>; + StreamSearch(argument: _zoekt_webserver_v1_StreamSearchRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_zoekt_webserver_v1_StreamSearchResponse__Output>; + streamSearch(argument: _zoekt_webserver_v1_StreamSearchRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_zoekt_webserver_v1_StreamSearchResponse__Output>; + streamSearch(argument: _zoekt_webserver_v1_StreamSearchRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_zoekt_webserver_v1_StreamSearchResponse__Output>; + +} + +export interface WebserverServiceHandlers extends grpc.UntypedServiceImplementation { + /** + * List lists repositories. The query `q` can only contain + * query.Repo atoms. + */ + List: grpc.handleUnaryCall<_zoekt_webserver_v1_ListRequest__Output, _zoekt_webserver_v1_ListResponse>; + + Search: grpc.handleUnaryCall<_zoekt_webserver_v1_SearchRequest__Output, _zoekt_webserver_v1_SearchResponse>; + + StreamSearch: grpc.handleServerStreamingCall<_zoekt_webserver_v1_StreamSearchRequest__Output, _zoekt_webserver_v1_StreamSearchResponse>; + +} + +export interface WebserverServiceDefinition extends grpc.ServiceDefinition { + List: MethodDefinition<_zoekt_webserver_v1_ListRequest, _zoekt_webserver_v1_ListResponse, _zoekt_webserver_v1_ListRequest__Output, _zoekt_webserver_v1_ListResponse__Output> + Search: MethodDefinition<_zoekt_webserver_v1_SearchRequest, _zoekt_webserver_v1_SearchResponse, _zoekt_webserver_v1_SearchRequest__Output, _zoekt_webserver_v1_SearchResponse__Output> + StreamSearch: MethodDefinition<_zoekt_webserver_v1_StreamSearchRequest, _zoekt_webserver_v1_StreamSearchResponse, _zoekt_webserver_v1_StreamSearchRequest__Output, _zoekt_webserver_v1_StreamSearchResponse__Output> +} diff --git a/yarn.lock b/yarn.lock index f103fe788..2ee9f226d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2338,6 +2338,16 @@ __metadata: languageName: node linkType: hard +"@grpc/grpc-js@npm:^1.14.1": + version: 1.14.1 + resolution: "@grpc/grpc-js@npm:1.14.1" + dependencies: + "@grpc/proto-loader": "npm:^0.8.0" + "@js-sdsl/ordered-map": "npm:^4.4.2" + checksum: 10c0/a9a8fc7f4dfa374a34e37350b37ad2c092ed533b203fe16d45ba3220fe38195d17a87527dade2e5546afeeeccfcf68d3e914705d94e44e8df461321b0c02cc7a + languageName: node + linkType: hard + "@grpc/proto-loader@npm:^0.8.0": version: 0.8.0 resolution: "@grpc/proto-loader@npm:0.8.0" @@ -8060,6 +8070,8 @@ __metadata: "@codemirror/view": "npm:^6.33.0" "@eslint/eslintrc": "npm:^3" "@floating-ui/react": "npm:^0.27.2" + "@grpc/grpc-js": "npm:^1.14.1" + "@grpc/proto-loader": "npm:^0.8.0" "@hookform/resolvers": "npm:^3.9.0" "@iconify/react": "npm:^5.1.0" "@iizukak/codemirror-lang-wgsl": "npm:^0.3.0" From cca3d30b4a26e180601b9a9cd3b3869f48b18eea Mon Sep 17 00:00:00 2001 From: bkellam Date: Thu, 13 Nov 2025 21:18:08 -0800 Subject: [PATCH 02/40] stream poc over SSE --- .../src/app/[domain]/stream_search/page.tsx | 221 ++++++++++++++++++ .../src/app/[domain]/stream_search/types.ts | 69 ++++++ .../stream_search/useStreamingSearch.ts | 148 ++++++++++++ .../app/api/(server)/stream_search/route.ts | 174 ++++++++++++++ 4 files changed, 612 insertions(+) create mode 100644 packages/web/src/app/[domain]/stream_search/page.tsx create mode 100644 packages/web/src/app/[domain]/stream_search/types.ts create mode 100644 packages/web/src/app/[domain]/stream_search/useStreamingSearch.ts create mode 100644 packages/web/src/app/api/(server)/stream_search/route.ts diff --git a/packages/web/src/app/[domain]/stream_search/page.tsx b/packages/web/src/app/[domain]/stream_search/page.tsx new file mode 100644 index 000000000..86c1e9686 --- /dev/null +++ b/packages/web/src/app/[domain]/stream_search/page.tsx @@ -0,0 +1,221 @@ +'use client'; + +import { useState } from 'react'; +import { useStreamingSearch } from './useStreamingSearch'; +import type { FileMatch__Output } from './types'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Separator } from '@/components/ui/separator'; +import { Loader2 } from 'lucide-react'; + +// @nocheckin +export default function StreamSearchPage() { + const [query, setQuery] = useState('useMemo'); + const [matches, setMatches] = useState(10000); + const [contextLines, _setContextLines] = useState(5); + + const { + chunks, + isStreaming, + totalFiles, + totalMatches, + error, + streamSearch, + cancel, + reset + } = useStreamingSearch(); + + const handleSearch = () => { + streamSearch({ + query, + matches, + contextLines, + whole: false, + }); + }; + + return ( +
+
+
+

Streaming Search Demo

+

+ Test the SSE streaming search API with real-time results +

+
+ + + + {/* Search Controls */} +
+
+
+ + setQuery(e.target.value)} + placeholder="Enter search query (e.g., useMemo, file:.tsx)" + onKeyDown={(e) => e.key === 'Enter' && handleSearch()} + disabled={isStreaming} + /> +
+
+ + setMatches(Number(e.target.value))} + placeholder="Max matches" + disabled={isStreaming} + /> +
+
+ +
+ + {isStreaming && ( + + )} + {chunks.length > 0 && !isStreaming && ( + + )} +
+
+ + + + {/* Results Stats */} + {(isStreaming || chunks.length > 0) && ( +
+
+
+ Status:{' '} + {isStreaming ? ( + + 🔄 Streaming... + + ) : ( + + ✓ Complete + + )} +
+
+ Chunks:{' '} + {chunks.length} +
+
+ Files:{' '} + {totalFiles} +
+
+ Matches:{' '} + {totalMatches} +
+
+
+ )} + + {/* Error Display */} + {error && ( +
+
+ Error occurred: +
+
+ {error.message} +
+
+ )} + + {/* Results Display */} + {chunks.length > 0 && ( +
+

+ Results ({chunks.length} chunks) +

+ +
+ {chunks.map((chunk, i) => ( +
+
+
+ Chunk {i + 1} +
+
+ {chunk.response_chunk?.files?.length || 0} files, {' '} + {chunk.response_chunk?.stats?.match_count || 0} matches +
+
+ + {chunk.response_chunk?.files && chunk.response_chunk.files.length > 0 && ( +
+ {chunk.response_chunk.files.map((file: FileMatch__Output, j: number) => { + // Decode file_name from Buffer to string + const fileName = file.file_name + ? Buffer.from(file.file_name).toString('utf-8') + : 'Unknown file'; + + return ( +
+
+ 📄 {fileName} +
+ {file.repository && ( +
+ {file.repository} +
+ )} + {file.language && ( +
+ Language: {file.language} +
+ )} +
+ ); + })} +
+ )} +
+ ))} +
+
+ )} + + {/* Empty State */} + {!isStreaming && chunks.length === 0 && !error && ( +
+

Enter a search query and click “Search” to start streaming results

+
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/stream_search/types.ts b/packages/web/src/app/[domain]/stream_search/types.ts new file mode 100644 index 000000000..e9e4aaf35 --- /dev/null +++ b/packages/web/src/app/[domain]/stream_search/types.ts @@ -0,0 +1,69 @@ +/** + * Types for streaming search functionality + */ + +import type { StreamSearchResponse__Output } from '@/proto/zoekt/webserver/v1/StreamSearchResponse'; +import type { SearchResponse__Output } from '@/proto/zoekt/webserver/v1/SearchResponse'; +import type { FileMatch__Output } from '@/proto/zoekt/webserver/v1/FileMatch'; +import type { Stats__Output } from '@/proto/zoekt/webserver/v1/Stats'; +import type { ChunkMatch__Output } from '@/proto/zoekt/webserver/v1/ChunkMatch'; +import type { Progress__Output } from '@/proto/zoekt/webserver/v1/Progress'; + +/** + * A single chunk received from the streaming search API + */ +export interface StreamSearchChunk { + response_chunk?: SearchResponse__Output | null; + error?: StreamSearchError; +} + +/** + * Error response from the streaming search + */ +export interface StreamSearchError { + code?: number; + message: string; +} + +/** + * Parameters for initiating a streaming search + */ +export interface StreamSearchParams { + query: string; + matches: number; + contextLines?: number; + whole?: boolean; +} + +/** + * State of the streaming search + */ +export interface StreamingSearchState { + chunks: StreamSearchChunk[]; + isStreaming: boolean; + error: Error | null; + totalFiles: number; + totalMatches: number; +} + +/** + * Return type of the useStreamingSearch hook + */ +export interface UseStreamingSearchReturn extends StreamingSearchState { + streamSearch: (params: StreamSearchParams) => Promise; + cancel: () => void; + reset: () => void; +} + +/** + * Re-export proto types for convenience + */ +export type { + StreamSearchResponse__Output, + SearchResponse__Output, + FileMatch__Output, + Stats__Output, + ChunkMatch__Output, + Progress__Output, +}; + diff --git a/packages/web/src/app/[domain]/stream_search/useStreamingSearch.ts b/packages/web/src/app/[domain]/stream_search/useStreamingSearch.ts new file mode 100644 index 000000000..d84eeaf03 --- /dev/null +++ b/packages/web/src/app/[domain]/stream_search/useStreamingSearch.ts @@ -0,0 +1,148 @@ +'use client'; + +import { useState, useCallback, useRef } from 'react'; +import type { + StreamSearchChunk, + StreamSearchParams, + StreamingSearchState, + UseStreamingSearchReturn, +} from './types'; + +export function useStreamingSearch(): UseStreamingSearchReturn { + const [state, setState] = useState({ + chunks: [], + isStreaming: false, + error: null, + totalFiles: 0, + totalMatches: 0, + }); + + const abortControllerRef = useRef(null); + + const streamSearch = useCallback(async (params: StreamSearchParams) => { + // Cancel any existing stream + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + + abortControllerRef.current = new AbortController(); + + setState({ + chunks: [], + isStreaming: true, + error: null, + totalFiles: 0, + totalMatches: 0, + }); + + try { + const response = await fetch('/api/stream_search', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(params), + signal: abortControllerRef.current.signal, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + if (!response.body) { + throw new Error('No response body'); + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ''; + + while (true as boolean) { + const { done, value } = await reader.read(); + + if (done) break; + + // Decode the chunk and add to buffer + buffer += decoder.decode(value, { stream: true }); + + // Process complete SSE messages (separated by \n\n) + const messages = buffer.split('\n\n'); + buffer = messages.pop() || ''; // Keep incomplete message in buffer + + for (const message of messages) { + if (!message.trim()) continue; + + // SSE messages start with "data: " + const dataMatch = message.match(/^data: (.+)$/); + if (!dataMatch) continue; + + const data = dataMatch[1]; + + // Check for completion signal + if (data === '[DONE]') { + setState(prev => ({ ...prev, isStreaming: false })); + return; + } + + // Parse the JSON chunk + try { + const chunk: StreamSearchChunk = JSON.parse(data); + + // Check for errors + if (chunk.error) { + throw new Error(chunk.error.message); + } + + // Update state with new chunk + setState(prev => ({ + ...prev, + chunks: [...prev.chunks, chunk], + totalFiles: prev.totalFiles + (chunk.response_chunk?.files?.length || 0), + totalMatches: prev.totalMatches + (chunk.response_chunk?.stats?.match_count || 0), + })); + } catch (parseError) { + console.error('Error parsing chunk:', parseError); + } + } + } + + setState(prev => ({ ...prev, isStreaming: false })); + } catch (error) { + if ((error as Error).name === 'AbortError') { + console.log('Stream aborted'); + } else { + setState(prev => ({ + ...prev, + isStreaming: false, + error: error as Error, + })); + } + } + }, []); + + const cancel = useCallback(() => { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + abortControllerRef.current = null; + } + setState(prev => ({ ...prev, isStreaming: false })); + }, []); + + const reset = useCallback(() => { + cancel(); + setState({ + chunks: [], + isStreaming: false, + error: null, + totalFiles: 0, + totalMatches: 0, + }); + }, [cancel]); + + return { + ...state, + streamSearch, + cancel, + reset, + }; +} \ No newline at end of file diff --git a/packages/web/src/app/api/(server)/stream_search/route.ts b/packages/web/src/app/api/(server)/stream_search/route.ts new file mode 100644 index 000000000..70a44e207 --- /dev/null +++ b/packages/web/src/app/api/(server)/stream_search/route.ts @@ -0,0 +1,174 @@ +'use server'; + +import { NextRequest } from 'next/server'; +import * as grpc from '@grpc/grpc-js'; +import * as protoLoader from '@grpc/proto-loader'; +import * as path from 'path'; +import type { ProtoGrpcType } from '@/proto/webserver'; +import type { WebserverServiceClient } from '@/proto/zoekt/webserver/v1/WebserverService'; +import type { SearchRequest } from '@/proto/zoekt/webserver/v1/SearchRequest'; +import type { StreamSearchRequest } from '@/proto/zoekt/webserver/v1/StreamSearchRequest'; +import type { StreamSearchResponse__Output } from '@/proto/zoekt/webserver/v1/StreamSearchResponse'; +import { env } from '@sourcebot/shared'; +import { schemaValidationError, serviceErrorResponse } from '@/lib/serviceError'; +import { searchRequestSchema } from '@/features/search/schemas'; + +/** + * Create a gRPC client for the Zoekt webserver + */ +function createGrpcClient(): WebserverServiceClient { + // Path to proto files - these should match your monorepo structure + const protoBasePath = path.join(process.cwd(), '../../vendor/zoekt/grpc/protos'); + const protoPath = path.join(protoBasePath, 'zoekt/webserver/v1/webserver.proto'); + + const packageDefinition = protoLoader.loadSync(protoPath, { + keepCase: true, + longs: Number, + enums: String, + defaults: true, + oneofs: true, + includeDirs: [protoBasePath], + }); + + const proto = grpc.loadPackageDefinition(packageDefinition) as unknown as ProtoGrpcType; + + // Extract host and port from ZOEKT_WEBSERVER_URL + const zoektUrl = new URL(env.ZOEKT_WEBSERVER_URL); + const grpcAddress = `${zoektUrl.hostname}:${zoektUrl.port}`; + + return new proto.zoekt.webserver.v1.WebserverService( + grpcAddress, + grpc.credentials.createInsecure(), + { + 'grpc.max_receive_message_length': 500 * 1024 * 1024, // 500MB + 'grpc.max_send_message_length': 500 * 1024 * 1024, // 500MB + } + ); +} + +/** + * POST handler for streaming search via SSE + */ +export const POST = async (request: NextRequest) => { + try { + // Parse and validate request body + const body = await request.json(); + const parsed = await searchRequestSchema.safeParseAsync(body); + + if (!parsed.success) { + return serviceErrorResponse(schemaValidationError(parsed.error)); + } + + const searchRequest: SearchRequest = { + query: { + and: { + children: [ + { + regexp: { + regexp: parsed.data.query, + case_sensitive: true, + } + } + ] + } + }, + opts: { + chunk_matches: true, + num_context_lines: parsed.data.contextLines ?? 5, + total_max_match_count: parsed.data.matches, + }, + }; + + // Create ReadableStream for SSE + const stream = new ReadableStream({ + async start(controller) { + const client = createGrpcClient(); + + try { + const metadata = new grpc.Metadata(); + metadata.add('x-sourcegraph-tenant-id', '1'); + + const streamRequest: StreamSearchRequest = { + request: searchRequest, + }; + + const grpcStream = client.StreamSearch(streamRequest, metadata); + + // Handle incoming data chunks + grpcStream.on('data', (chunk: StreamSearchResponse__Output) => { + try { + // SSE format: "data: {json}\n\n" + const sseData = `data: ${JSON.stringify(chunk)}\n\n`; + controller.enqueue(new TextEncoder().encode(sseData)); + } catch (error) { + console.error('Error encoding chunk:', error); + } + }); + + // Handle stream completion + grpcStream.on('end', () => { + // Send completion signal + controller.enqueue(new TextEncoder().encode('data: [DONE]\n\n')); + controller.close(); + client.close(); + }); + + // Handle errors + grpcStream.on('error', (error: grpc.ServiceError) => { + console.error('gRPC stream error:', error); + + // Send error as SSE event + const errorData = `data: ${JSON.stringify({ + error: { + code: error.code, + message: error.details || error.message, + } + })}\n\n`; + controller.enqueue(new TextEncoder().encode(errorData)); + + controller.close(); + client.close(); + }); + } catch (error) { + console.error('Stream initialization error:', error); + + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + const errorData = `data: ${JSON.stringify({ + error: { message: errorMessage } + })}\n\n`; + controller.enqueue(new TextEncoder().encode(errorData)); + + controller.close(); + client.close(); + } + }, + cancel() { + // Cleanup when client cancels the stream + console.log('SSE stream cancelled by client'); + } + }); + + // Return streaming response with SSE headers + return new Response(stream, { + headers: { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache, no-transform', + 'Connection': 'keep-alive', + 'X-Accel-Buffering': 'no', // Disable nginx buffering if applicable + }, + }); + } catch (error) { + console.error('Request handling error:', error); + return new Response( + JSON.stringify({ + error: { + message: error instanceof Error ? error.message : 'Internal server error' + } + }), + { + status: 500, + headers: { 'Content-Type': 'application/json' }, + } + ); + } +}; \ No newline at end of file From 9cd32362e88679538817a92b6df0b6b247aae349 Mon Sep 17 00:00:00 2001 From: bkellam Date: Fri, 14 Nov 2025 20:53:55 -0800 Subject: [PATCH 03/40] wip: make stream search api follow existing schema. Modify UI to support streaming --- .../search/components/searchResultsPage.tsx | 230 +++++------ .../app/[domain]/search/useStreamedSearch.ts | 171 ++++++++ .../src/app/[domain]/stream_search/page.tsx | 221 ---------- .../src/app/[domain]/stream_search/types.ts | 69 ---- .../stream_search/useStreamingSearch.ts | 148 ------- .../app/api/(server)/stream_search/route.ts | 377 ++++++++++++++---- packages/web/src/features/search/searchApi.ts | 2 +- 7 files changed, 583 insertions(+), 635 deletions(-) create mode 100644 packages/web/src/app/[domain]/search/useStreamedSearch.ts delete mode 100644 packages/web/src/app/[domain]/stream_search/page.tsx delete mode 100644 packages/web/src/app/[domain]/stream_search/types.ts delete mode 100644 packages/web/src/app/[domain]/stream_search/useStreamingSearch.ts diff --git a/packages/web/src/app/[domain]/search/components/searchResultsPage.tsx b/packages/web/src/app/[domain]/search/components/searchResultsPage.tsx index 9c33e11d4..7bacb12c8 100644 --- a/packages/web/src/app/[domain]/search/components/searchResultsPage.tsx +++ b/packages/web/src/app/[domain]/search/components/searchResultsPage.tsx @@ -12,24 +12,22 @@ import { import { Separator } from "@/components/ui/separator"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { RepositoryInfo, SearchResultFile, SearchStats } from "@/features/search/types"; -import useCaptureEvent from "@/hooks/useCaptureEvent"; import { useDomain } from "@/hooks/useDomain"; import { useNonEmptyQueryParam } from "@/hooks/useNonEmptyQueryParam"; import { useSearchHistory } from "@/hooks/useSearchHistory"; import { SearchQueryParams } from "@/lib/types"; -import { createPathWithQueryParams, measure, unwrapServiceError } from "@/lib/utils"; -import { InfoCircledIcon, SymbolIcon } from "@radix-ui/react-icons"; -import { useQuery } from "@tanstack/react-query"; +import { createPathWithQueryParams } from "@/lib/utils"; +import { InfoCircledIcon } from "@radix-ui/react-icons"; import { useLocalStorage } from "@uidotdev/usehooks"; -import { AlertTriangleIcon, BugIcon, FilterIcon } from "lucide-react"; +import { AlertTriangleIcon, BugIcon, FilterIcon, RefreshCcwIcon } from "lucide-react"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useHotkeys } from "react-hotkeys-hook"; import { ImperativePanelHandle } from "react-resizable-panels"; -import { search } from "../../../api/(client)/client"; import { CopyIconButton } from "../../components/copyIconButton"; import { SearchBar } from "../../components/searchBar"; import { TopBar } from "../../components/topBar"; +import { useStreamedSearch } from "../useStreamedSearch"; import { CodePreviewPanel } from "./codePreviewPanel"; import { FilterPanel } from "./filterPanel"; import { useFilteredMatches } from "./filterPanel/useFilterMatches"; @@ -46,7 +44,6 @@ export const SearchResultsPage = ({ }: SearchResultsPageProps) => { const router = useRouter(); const { setSearchHistory } = useSearchHistory(); - const captureEvent = useCaptureEvent(); const domain = useDomain(); const { toast } = useToast(); @@ -55,26 +52,17 @@ export const SearchResultsPage = ({ const maxMatchCount = isNaN(_maxMatchCount) ? defaultMaxMatchCount : _maxMatchCount; const { - data: searchResponse, - isPending: isSearchPending, - isFetching: isFetching, - error - } = useQuery({ - queryKey: ["search", searchQuery, maxMatchCount], - queryFn: () => measure(() => unwrapServiceError(search({ - query: searchQuery, - matches: maxMatchCount, - contextLines: 3, - whole: false, - })), "client.search"), - select: ({ data, durationMs }) => ({ - ...data, - totalClientSearchDurationMs: durationMs, - }), - enabled: searchQuery.length > 0, - refetchOnWindowFocus: false, - retry: false, - staleTime: 0, + error, + files, + repoInfo, + durationMs, + isStreaming, + numMatches, + } = useStreamedSearch({ + query: searchQuery, + matches: maxMatchCount, + contextLines: 3, + whole: false, }); useEffect(() => { @@ -102,38 +90,39 @@ export const SearchResultsPage = ({ ]) }, [searchQuery, setSearchHistory]); - useEffect(() => { - if (!searchResponse) { - return; - } + // @todo: capture search stats on completion. + // useEffect(() => { + // if (!searchResponse) { + // return; + // } - const fileLanguages = searchResponse.files?.map(file => file.language) || []; + // const fileLanguages = searchResponse.files?.map(file => file.language) || []; - captureEvent("search_finished", { - durationMs: searchResponse.totalClientSearchDurationMs, - fileCount: searchResponse.stats.fileCount, - matchCount: searchResponse.stats.totalMatchCount, - actualMatchCount: searchResponse.stats.actualMatchCount, - filesSkipped: searchResponse.stats.filesSkipped, - contentBytesLoaded: searchResponse.stats.contentBytesLoaded, - indexBytesLoaded: searchResponse.stats.indexBytesLoaded, - crashes: searchResponse.stats.crashes, - shardFilesConsidered: searchResponse.stats.shardFilesConsidered, - filesConsidered: searchResponse.stats.filesConsidered, - filesLoaded: searchResponse.stats.filesLoaded, - shardsScanned: searchResponse.stats.shardsScanned, - shardsSkipped: searchResponse.stats.shardsSkipped, - shardsSkippedFilter: searchResponse.stats.shardsSkippedFilter, - ngramMatches: searchResponse.stats.ngramMatches, - ngramLookups: searchResponse.stats.ngramLookups, - wait: searchResponse.stats.wait, - matchTreeConstruction: searchResponse.stats.matchTreeConstruction, - matchTreeSearch: searchResponse.stats.matchTreeSearch, - regexpsConsidered: searchResponse.stats.regexpsConsidered, - flushReason: searchResponse.stats.flushReason, - fileLanguages, - }); - }, [captureEvent, searchQuery, searchResponse]); + // captureEvent("search_finished", { + // durationMs: searchResponse.totalClientSearchDurationMs, + // fileCount: searchResponse.stats.fileCount, + // matchCount: searchResponse.stats.totalMatchCount, + // actualMatchCount: searchResponse.stats.actualMatchCount, + // filesSkipped: searchResponse.stats.filesSkipped, + // contentBytesLoaded: searchResponse.stats.contentBytesLoaded, + // indexBytesLoaded: searchResponse.stats.indexBytesLoaded, + // crashes: searchResponse.stats.crashes, + // shardFilesConsidered: searchResponse.stats.shardFilesConsidered, + // filesConsidered: searchResponse.stats.filesConsidered, + // filesLoaded: searchResponse.stats.filesLoaded, + // shardsScanned: searchResponse.stats.shardsScanned, + // shardsSkipped: searchResponse.stats.shardsSkipped, + // shardsSkippedFilter: searchResponse.stats.shardsSkippedFilter, + // ngramMatches: searchResponse.stats.ngramMatches, + // ngramLookups: searchResponse.stats.ngramLookups, + // wait: searchResponse.stats.wait, + // matchTreeConstruction: searchResponse.stats.matchTreeConstruction, + // matchTreeSearch: searchResponse.stats.matchTreeSearch, + // regexpsConsidered: searchResponse.stats.regexpsConsidered, + // flushReason: searchResponse.stats.flushReason, + // fileLanguages, + // }); + // }, [captureEvent, searchQuery, searchResponse]); const onLoadMoreResults = useCallback(() => { @@ -157,12 +146,7 @@ export const SearchResultsPage = ({ /> - {(isSearchPending || isFetching) ? ( -
- -

Searching...

-
- ) : error ? ( + {error ? (

Failed to search

@@ -170,14 +154,18 @@ export const SearchResultsPage = ({
) : ( )} @@ -186,10 +174,11 @@ export const SearchResultsPage = ({ interface PanelGroupProps { fileMatches: SearchResultFile[]; - isMoreResultsButtonVisible?: boolean; onLoadMoreResults: () => void; + isStreaming: boolean; + isMoreResultsButtonVisible?: boolean; isBranchFilteringEnabled: boolean; - repoInfo: RepositoryInfo[]; + repoInfo: Record; searchDurationMs: number; numMatches: number; searchStats?: SearchStats; @@ -198,9 +187,10 @@ interface PanelGroupProps { const PanelGroup = ({ fileMatches, isMoreResultsButtonVisible, + isStreaming, onLoadMoreResults, isBranchFilteringEnabled, - repoInfo: _repoInfo, + repoInfo, searchDurationMs: _searchDurationMs, numMatches, searchStats, @@ -228,13 +218,6 @@ const PanelGroup = ({ return Math.round(_searchDurationMs); }, [_searchDurationMs]); - const repoInfo = useMemo(() => { - return _repoInfo.reduce((acc, repo) => { - acc[repo.id] = repo; - return acc; - }, {} as Record); - }, [_repoInfo]); - return (
- - - - - -
- -

Search stats for nerds

- { - navigator.clipboard.writeText(JSON.stringify(searchStats, null, 2)); - return true; - }} - className="ml-auto" - /> -
- - {JSON.stringify(searchStats, null, 2)} - -
-
- { - fileMatches.length > 0 ? ( -

{`[${searchDurationMs} ms] Found ${numMatches} matches in ${fileMatches.length} ${fileMatches.length > 1 ? 'files' : 'file'}`}

- ) : ( -

No results

- ) - } - {isMoreResultsButtonVisible && ( -
- (load more) -
+ {isStreaming ? ( + <> + +

Searching...

+ {numMatches > 0 && ( +

{`Found ${numMatches} matches in ${fileMatches.length} ${fileMatches.length > 1 ? 'files' : 'file'}`}

+ )} + + ) : ( + <> + + + + + +
+ +

Search stats for nerds

+ { + navigator.clipboard.writeText(JSON.stringify(searchStats, null, 2)); + return true; + }} + className="ml-auto" + /> +
+ + {JSON.stringify(searchStats, null, 2)} + +
+
+ { + fileMatches.length > 0 ? ( +

{`[${searchDurationMs} ms] Found ${numMatches} matches in ${fileMatches.length} ${fileMatches.length > 1 ? 'files' : 'file'}`}

+ ) : ( +

No results

+ ) + } + {isMoreResultsButtonVisible && ( +
+ (load more) +
+ )} + )}
{filteredFileMatches.length > 0 ? ( @@ -340,6 +335,11 @@ const PanelGroup = ({ isBranchFilteringEnabled={isBranchFilteringEnabled} repoInfo={repoInfo} /> + ) : isStreaming ? ( +
+ +

Searching...

+
) : (

No results found

diff --git a/packages/web/src/app/[domain]/search/useStreamedSearch.ts b/packages/web/src/app/[domain]/search/useStreamedSearch.ts new file mode 100644 index 000000000..2369346a6 --- /dev/null +++ b/packages/web/src/app/[domain]/search/useStreamedSearch.ts @@ -0,0 +1,171 @@ +'use client'; + +import { RepositoryInfo, SearchRequest, SearchResponse, SearchResultFile } from '@/features/search/types'; +import { useState, useCallback, useRef, useEffect } from 'react'; + + +export const useStreamedSearch = ({ query, matches, contextLines, whole }: SearchRequest) => { + + const [state, setState] = useState<{ + isStreaming: boolean, + error: Error | null, + files: SearchResultFile[], + repoInfo: Record, + durationMs: number, + numMatches: number, + }>({ + isStreaming: false, + error: null, + files: [], + repoInfo: {}, + durationMs: 0, + numMatches: 0, + }); + + const abortControllerRef = useRef(null); + + const cancel = useCallback(() => { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + abortControllerRef.current = null; + } + setState(prev => ({ + ...prev, + isStreaming: false, + })); + }, []); + + useEffect(() => { + const search = async () => { + const startTime = performance.now(); + + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + abortControllerRef.current = new AbortController(); + + setState({ + isStreaming: true, + error: null, + files: [], + repoInfo: {}, + durationMs: 0, + numMatches: 0, + }); + + try { + const response = await fetch('/api/stream_search', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query, + matches, + contextLines, + whole, + }), + signal: abortControllerRef.current.signal, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + if (!response.body) { + throw new Error('No response body'); + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ''; + + while (true as boolean) { + const { done, value } = await reader.read(); + + if (done) { + break; + } + + // Decode the chunk and add to buffer + buffer += decoder.decode(value, { stream: true }); + + // Process complete SSE messages (separated by \n\n) + const messages = buffer.split('\n\n'); + + // Keep the last element (potentially incomplete message) in the buffer for the next chunk. + // Stream chunks can split messages mid-way, so we only process complete messages. + buffer = messages.pop() || ''; + + for (const message of messages) { + if (!message.trim()) { + continue; + } + + // SSE messages start with "data: " + const dataMatch = message.match(/^data: (.+)$/); + if (!dataMatch) continue; + + const data = dataMatch[1]; + + // Check for completion signal + if (data === '[DONE]') { + setState(prev => ({ ...prev, isStreaming: false })); + return; + } + + try { + const chunk: SearchResponse = JSON.parse(data); + setState(prev => ({ + ...prev, + files: [ + ...prev.files, + ...chunk.files + ], + repoInfo: { + ...prev.repoInfo, + ...chunk.repositoryInfo.reduce((acc, repo) => { + acc[repo.id] = repo; + return acc; + }, {} as Record), + }, + numMatches: prev.numMatches + chunk.stats.actualMatchCount, + })); + } catch (parseError) { + console.error('Error parsing chunk:', parseError); + } + } + } + + setState(prev => ({ ...prev, isStreaming: false })); + } catch (error) { + if ((error as Error).name === 'AbortError') { + console.log('Stream aborted'); + } else { + setState(prev => ({ + ...prev, + isStreaming: false, + error: error as Error, + })); + } + } finally { + const endTime = performance.now(); + const durationMs = endTime - startTime; + setState(prev => ({ + ...prev, + durationMs, + })); + } + } + + search(); + + return () => { + } + }, [query, matches, contextLines, whole]); + + return { + ...state, + cancel, + }; +} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/stream_search/page.tsx b/packages/web/src/app/[domain]/stream_search/page.tsx deleted file mode 100644 index 86c1e9686..000000000 --- a/packages/web/src/app/[domain]/stream_search/page.tsx +++ /dev/null @@ -1,221 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { useStreamingSearch } from './useStreamingSearch'; -import type { FileMatch__Output } from './types'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Separator } from '@/components/ui/separator'; -import { Loader2 } from 'lucide-react'; - -// @nocheckin -export default function StreamSearchPage() { - const [query, setQuery] = useState('useMemo'); - const [matches, setMatches] = useState(10000); - const [contextLines, _setContextLines] = useState(5); - - const { - chunks, - isStreaming, - totalFiles, - totalMatches, - error, - streamSearch, - cancel, - reset - } = useStreamingSearch(); - - const handleSearch = () => { - streamSearch({ - query, - matches, - contextLines, - whole: false, - }); - }; - - return ( -
-
-
-

Streaming Search Demo

-

- Test the SSE streaming search API with real-time results -

-
- - - - {/* Search Controls */} -
-
-
- - setQuery(e.target.value)} - placeholder="Enter search query (e.g., useMemo, file:.tsx)" - onKeyDown={(e) => e.key === 'Enter' && handleSearch()} - disabled={isStreaming} - /> -
-
- - setMatches(Number(e.target.value))} - placeholder="Max matches" - disabled={isStreaming} - /> -
-
- -
- - {isStreaming && ( - - )} - {chunks.length > 0 && !isStreaming && ( - - )} -
-
- - - - {/* Results Stats */} - {(isStreaming || chunks.length > 0) && ( -
-
-
- Status:{' '} - {isStreaming ? ( - - 🔄 Streaming... - - ) : ( - - ✓ Complete - - )} -
-
- Chunks:{' '} - {chunks.length} -
-
- Files:{' '} - {totalFiles} -
-
- Matches:{' '} - {totalMatches} -
-
-
- )} - - {/* Error Display */} - {error && ( -
-
- Error occurred: -
-
- {error.message} -
-
- )} - - {/* Results Display */} - {chunks.length > 0 && ( -
-

- Results ({chunks.length} chunks) -

- -
- {chunks.map((chunk, i) => ( -
-
-
- Chunk {i + 1} -
-
- {chunk.response_chunk?.files?.length || 0} files, {' '} - {chunk.response_chunk?.stats?.match_count || 0} matches -
-
- - {chunk.response_chunk?.files && chunk.response_chunk.files.length > 0 && ( -
- {chunk.response_chunk.files.map((file: FileMatch__Output, j: number) => { - // Decode file_name from Buffer to string - const fileName = file.file_name - ? Buffer.from(file.file_name).toString('utf-8') - : 'Unknown file'; - - return ( -
-
- 📄 {fileName} -
- {file.repository && ( -
- {file.repository} -
- )} - {file.language && ( -
- Language: {file.language} -
- )} -
- ); - })} -
- )} -
- ))} -
-
- )} - - {/* Empty State */} - {!isStreaming && chunks.length === 0 && !error && ( -
-

Enter a search query and click “Search” to start streaming results

-
- )} -
-
- ); -} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/stream_search/types.ts b/packages/web/src/app/[domain]/stream_search/types.ts deleted file mode 100644 index e9e4aaf35..000000000 --- a/packages/web/src/app/[domain]/stream_search/types.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Types for streaming search functionality - */ - -import type { StreamSearchResponse__Output } from '@/proto/zoekt/webserver/v1/StreamSearchResponse'; -import type { SearchResponse__Output } from '@/proto/zoekt/webserver/v1/SearchResponse'; -import type { FileMatch__Output } from '@/proto/zoekt/webserver/v1/FileMatch'; -import type { Stats__Output } from '@/proto/zoekt/webserver/v1/Stats'; -import type { ChunkMatch__Output } from '@/proto/zoekt/webserver/v1/ChunkMatch'; -import type { Progress__Output } from '@/proto/zoekt/webserver/v1/Progress'; - -/** - * A single chunk received from the streaming search API - */ -export interface StreamSearchChunk { - response_chunk?: SearchResponse__Output | null; - error?: StreamSearchError; -} - -/** - * Error response from the streaming search - */ -export interface StreamSearchError { - code?: number; - message: string; -} - -/** - * Parameters for initiating a streaming search - */ -export interface StreamSearchParams { - query: string; - matches: number; - contextLines?: number; - whole?: boolean; -} - -/** - * State of the streaming search - */ -export interface StreamingSearchState { - chunks: StreamSearchChunk[]; - isStreaming: boolean; - error: Error | null; - totalFiles: number; - totalMatches: number; -} - -/** - * Return type of the useStreamingSearch hook - */ -export interface UseStreamingSearchReturn extends StreamingSearchState { - streamSearch: (params: StreamSearchParams) => Promise; - cancel: () => void; - reset: () => void; -} - -/** - * Re-export proto types for convenience - */ -export type { - StreamSearchResponse__Output, - SearchResponse__Output, - FileMatch__Output, - Stats__Output, - ChunkMatch__Output, - Progress__Output, -}; - diff --git a/packages/web/src/app/[domain]/stream_search/useStreamingSearch.ts b/packages/web/src/app/[domain]/stream_search/useStreamingSearch.ts deleted file mode 100644 index d84eeaf03..000000000 --- a/packages/web/src/app/[domain]/stream_search/useStreamingSearch.ts +++ /dev/null @@ -1,148 +0,0 @@ -'use client'; - -import { useState, useCallback, useRef } from 'react'; -import type { - StreamSearchChunk, - StreamSearchParams, - StreamingSearchState, - UseStreamingSearchReturn, -} from './types'; - -export function useStreamingSearch(): UseStreamingSearchReturn { - const [state, setState] = useState({ - chunks: [], - isStreaming: false, - error: null, - totalFiles: 0, - totalMatches: 0, - }); - - const abortControllerRef = useRef(null); - - const streamSearch = useCallback(async (params: StreamSearchParams) => { - // Cancel any existing stream - if (abortControllerRef.current) { - abortControllerRef.current.abort(); - } - - abortControllerRef.current = new AbortController(); - - setState({ - chunks: [], - isStreaming: true, - error: null, - totalFiles: 0, - totalMatches: 0, - }); - - try { - const response = await fetch('/api/stream_search', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(params), - signal: abortControllerRef.current.signal, - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - if (!response.body) { - throw new Error('No response body'); - } - - const reader = response.body.getReader(); - const decoder = new TextDecoder(); - let buffer = ''; - - while (true as boolean) { - const { done, value } = await reader.read(); - - if (done) break; - - // Decode the chunk and add to buffer - buffer += decoder.decode(value, { stream: true }); - - // Process complete SSE messages (separated by \n\n) - const messages = buffer.split('\n\n'); - buffer = messages.pop() || ''; // Keep incomplete message in buffer - - for (const message of messages) { - if (!message.trim()) continue; - - // SSE messages start with "data: " - const dataMatch = message.match(/^data: (.+)$/); - if (!dataMatch) continue; - - const data = dataMatch[1]; - - // Check for completion signal - if (data === '[DONE]') { - setState(prev => ({ ...prev, isStreaming: false })); - return; - } - - // Parse the JSON chunk - try { - const chunk: StreamSearchChunk = JSON.parse(data); - - // Check for errors - if (chunk.error) { - throw new Error(chunk.error.message); - } - - // Update state with new chunk - setState(prev => ({ - ...prev, - chunks: [...prev.chunks, chunk], - totalFiles: prev.totalFiles + (chunk.response_chunk?.files?.length || 0), - totalMatches: prev.totalMatches + (chunk.response_chunk?.stats?.match_count || 0), - })); - } catch (parseError) { - console.error('Error parsing chunk:', parseError); - } - } - } - - setState(prev => ({ ...prev, isStreaming: false })); - } catch (error) { - if ((error as Error).name === 'AbortError') { - console.log('Stream aborted'); - } else { - setState(prev => ({ - ...prev, - isStreaming: false, - error: error as Error, - })); - } - } - }, []); - - const cancel = useCallback(() => { - if (abortControllerRef.current) { - abortControllerRef.current.abort(); - abortControllerRef.current = null; - } - setState(prev => ({ ...prev, isStreaming: false })); - }, []); - - const reset = useCallback(() => { - cancel(); - setState({ - chunks: [], - isStreaming: false, - error: null, - totalFiles: 0, - totalMatches: 0, - }); - }, [cancel]); - - return { - ...state, - streamSearch, - cancel, - reset, - }; -} \ No newline at end of file diff --git a/packages/web/src/app/api/(server)/stream_search/route.ts b/packages/web/src/app/api/(server)/stream_search/route.ts index 70a44e207..ac834d357 100644 --- a/packages/web/src/app/api/(server)/stream_search/route.ts +++ b/packages/web/src/app/api/(server)/stream_search/route.ts @@ -1,17 +1,23 @@ 'use server'; -import { NextRequest } from 'next/server'; -import * as grpc from '@grpc/grpc-js'; -import * as protoLoader from '@grpc/proto-loader'; -import * as path from 'path'; +import { searchRequestSchema } from '@/features/search/schemas'; +import { SearchResponse, SourceRange } from '@/features/search/types'; +import { schemaValidationError, serviceErrorResponse } from '@/lib/serviceError'; +import { prisma } from '@/prisma'; import type { ProtoGrpcType } from '@/proto/webserver'; -import type { WebserverServiceClient } from '@/proto/zoekt/webserver/v1/WebserverService'; +import { Range__Output } from '@/proto/zoekt/webserver/v1/Range'; import type { SearchRequest } from '@/proto/zoekt/webserver/v1/SearchRequest'; import type { StreamSearchRequest } from '@/proto/zoekt/webserver/v1/StreamSearchRequest'; import type { StreamSearchResponse__Output } from '@/proto/zoekt/webserver/v1/StreamSearchResponse'; -import { env } from '@sourcebot/shared'; -import { schemaValidationError, serviceErrorResponse } from '@/lib/serviceError'; -import { searchRequestSchema } from '@/features/search/schemas'; +import type { WebserverServiceClient } from '@/proto/zoekt/webserver/v1/WebserverService'; +import * as grpc from '@grpc/grpc-js'; +import * as protoLoader from '@grpc/proto-loader'; +import { PrismaClient, Repo } from '@sourcebot/db'; +import { createLogger, env } from '@sourcebot/shared'; +import { NextRequest } from 'next/server'; +import * as path from 'path'; + +const logger = createLogger('streamSearchApi'); /** * Create a gRPC client for the Zoekt webserver @@ -54,18 +60,22 @@ export const POST = async (request: NextRequest) => { // Parse and validate request body const body = await request.json(); const parsed = await searchRequestSchema.safeParseAsync(body); - + if (!parsed.success) { return serviceErrorResponse(schemaValidationError(parsed.error)); } + const { query, matches, contextLines, whole } = parsed.data; + const searchRequest: SearchRequest = { query: { and: { + // @todo: we should use repo_ids to filter out repositories that the user + // has access to (if permission syncing is enabled!). children: [ { regexp: { - regexp: parsed.data.query, + regexp: query, case_sensitive: true, } } @@ -74,79 +84,20 @@ export const POST = async (request: NextRequest) => { }, opts: { chunk_matches: true, - num_context_lines: parsed.data.contextLines ?? 5, - total_max_match_count: parsed.data.matches, + max_match_display_count: matches, + total_max_match_count: matches + 1, + num_context_lines: contextLines, + whole: !!whole, + shard_max_match_count: -1, + max_wall_time: { + seconds: 0, + } }, }; - // Create ReadableStream for SSE - const stream = new ReadableStream({ - async start(controller) { - const client = createGrpcClient(); - - try { - const metadata = new grpc.Metadata(); - metadata.add('x-sourcegraph-tenant-id', '1'); - - const streamRequest: StreamSearchRequest = { - request: searchRequest, - }; - - const grpcStream = client.StreamSearch(streamRequest, metadata); - - // Handle incoming data chunks - grpcStream.on('data', (chunk: StreamSearchResponse__Output) => { - try { - // SSE format: "data: {json}\n\n" - const sseData = `data: ${JSON.stringify(chunk)}\n\n`; - controller.enqueue(new TextEncoder().encode(sseData)); - } catch (error) { - console.error('Error encoding chunk:', error); - } - }); - - // Handle stream completion - grpcStream.on('end', () => { - // Send completion signal - controller.enqueue(new TextEncoder().encode('data: [DONE]\n\n')); - controller.close(); - client.close(); - }); - - // Handle errors - grpcStream.on('error', (error: grpc.ServiceError) => { - console.error('gRPC stream error:', error); - - // Send error as SSE event - const errorData = `data: ${JSON.stringify({ - error: { - code: error.code, - message: error.details || error.message, - } - })}\n\n`; - controller.enqueue(new TextEncoder().encode(errorData)); - - controller.close(); - client.close(); - }); - } catch (error) { - console.error('Stream initialization error:', error); - - const errorMessage = error instanceof Error ? error.message : 'Unknown error'; - const errorData = `data: ${JSON.stringify({ - error: { message: errorMessage } - })}\n\n`; - controller.enqueue(new TextEncoder().encode(errorData)); - - controller.close(); - client.close(); - } - }, - cancel() { - // Cleanup when client cancels the stream - console.log('SSE stream cancelled by client'); - } - }); + // @nocheckin: this should be using the `prisma` instance from the auth context. + const stream = await createSSESearchStream(searchRequest, prisma); + // Return streaming response with SSE headers return new Response(stream, { @@ -171,4 +122,268 @@ export const POST = async (request: NextRequest) => { } ); } -}; \ No newline at end of file +}; + +const createSSESearchStream = async (searchRequest: SearchRequest, prisma: PrismaClient): Promise => { + const client = createGrpcClient(); + let grpcStream: ReturnType | null = null; + let isStreamActive = true; + + return new ReadableStream({ + async start(controller) { + try { + // @todo: we should just disable tenant enforcement for now. + const metadata = new grpc.Metadata(); + metadata.add('x-sourcegraph-tenant-id', '1'); + + const streamRequest: StreamSearchRequest = { + request: searchRequest, + }; + + grpcStream = client.StreamSearch(streamRequest, metadata); + + // @note (2025-05-12): in zoekt, repositories are identified by the `RepositoryID` field + // which corresponds to the `id` in the Repo table. In order to efficiently fetch repository + // metadata when transforming (potentially thousands) of file matches, we aggregate a unique + // set of repository ids* and map them to their corresponding Repo record. + // + // *Q: Why is `RepositoryID` optional? And why are we falling back to `Repository`? + // A: Prior to this change, the repository id was not plumbed into zoekt, so RepositoryID was + // always undefined. To make this a non-breaking change, we fallback to using the repository's name + // (`Repository`) as the identifier in these cases. This is not guaranteed to be unique, but in + // practice it is since the repository name includes the host and path (e.g., 'github.com/org/repo', + // 'gitea.com/org/repo', etc.). + // + // Note: When a repository is re-indexed (every hour) this ID will be populated. + // @see: https://github.com/sourcebot-dev/zoekt/pull/6 + const repos = new Map(); + + // Handle incoming data chunks + grpcStream.on('data', async (chunk: StreamSearchResponse__Output) => { + console.log('chunk'); + + if (!isStreamActive) { + return; + } + + // grpcStream.on doesn't actually await on our handler, so we need to + // explicitly pause the stream here to prevent the stream from completing + // prior to our asynchronous work being completed. + grpcStream?.pause(); + + try { + if (!chunk.response_chunk) { + logger.warn('No response chunk received'); + return; + } + + const files = (await Promise.all(chunk.response_chunk.files.map(async (file) => { + const fileNameChunks = file.chunk_matches.filter((chunk) => chunk.file_name); + + const identifier = file.repository_id ?? file.repository; + + // If the repository is not in the map, fetch it from the database. + if (!repos.has(identifier)) { + const repo = typeof identifier === 'number' ? + await prisma.repo.findUnique({ + where: { + id: identifier, + }, + }) : + await prisma.repo.findFirst({ + where: { + name: identifier, + }, + }); + + if (repo) { + repos.set(identifier, repo); + } + } + + + const repo = repos.get(identifier); + + // This can happen if the user doesn't have access to the repository. + if (!repo) { + return undefined; + } + + // @todo: address "file_name might not be a valid UTF-8 string" warning. + const fileName = file.file_name.toString('utf-8'); + + const convertRange = (range: Range__Output): SourceRange => ({ + start: { + byteOffset: range.start?.byte_offset ?? 0, + column: range.start?.column ?? 0, + lineNumber: range.start?.line_number ?? 0, + }, + end: { + byteOffset: range.end?.byte_offset ?? 0, + column: range.end?.column ?? 0, + lineNumber: range.end?.line_number ?? 0, + } + }) + + return { + fileName: { + text: fileName, + matchRanges: fileNameChunks.length === 1 ? fileNameChunks[0].ranges.map(convertRange) : [], + }, + repository: repo.name, + repositoryId: repo.id, + language: file.language, + // @todo: we will need to have a mechanism of forming the file's web url. + webUrl: '', + chunks: file.chunk_matches + .filter((chunk) => !chunk.file_name) // filter out filename chunks. + .map((chunk) => { + return { + content: chunk.content.toString('utf-8'), + matchRanges: chunk.ranges.map(convertRange), + contentStart: chunk.content_start ? { + byteOffset: chunk.content_start.byte_offset ?? 0, + column: chunk.content_start.column ?? 0, + lineNumber: chunk.content_start.line_number ?? 0, + // @nocheckin: Will need to figure out how to handle this case. + } : { + byteOffset: 0, + column: 0, + lineNumber: 0, + }, + symbols: chunk.symbol_info.map((symbol) => { + return { + symbol: symbol.sym, + kind: symbol.kind, + parent: symbol.parent ? { + symbol: symbol.parent, + kind: symbol.parent_kind, + } : undefined, + } + }) + } + }), + branches: file.branches, + content: file.content ? file.content.toString('utf-8') : undefined, + } + }))).filter(file => file !== undefined); + + const actualMatchCount = files.reduce( + (acc, file) => + // Match count is the sum of the number of chunk matches and file name matches. + acc + file.chunks.reduce( + (acc, chunk) => acc + chunk.matchRanges.length, + 0, + ) + file.fileName.matchRanges.length, + 0, + ); + + const response: SearchResponse = { + files, + repositoryInfo: Array.from(repos.values()).map((repo) => ({ + id: repo.id, + codeHostType: repo.external_codeHostType, + name: repo.name, + displayName: repo.displayName ?? undefined, + webUrl: repo.webUrl ?? undefined, + })), + isBranchFilteringEnabled: false, + // @todo: we will need to figure out how to handle if a search is exhaustive or not + isSearchExhaustive: false, + stats: { + actualMatchCount, + // @todo: todo - + totalMatchCount: 0, + duration: chunk.response_chunk.stats?.duration?.nanos ?? 0, + fileCount: chunk.response_chunk.stats?.file_count.valueOf() ?? 0, + filesSkipped: chunk.response_chunk.stats?.files_skipped.valueOf() ?? 0, + contentBytesLoaded: chunk.response_chunk.stats?.content_bytes_loaded.valueOf() ?? 0, + indexBytesLoaded: chunk.response_chunk.stats?.index_bytes_loaded.valueOf() ?? 0, + crashes: chunk.response_chunk.stats?.crashes.valueOf() ?? 0, + shardFilesConsidered: chunk.response_chunk.stats?.shard_files_considered.valueOf() ?? 0, + filesConsidered: chunk.response_chunk.stats?.files_considered.valueOf() ?? 0, + filesLoaded: chunk.response_chunk.stats?.files_loaded.valueOf() ?? 0, + shardsScanned: chunk.response_chunk.stats?.shards_scanned.valueOf() ?? 0, + shardsSkipped: chunk.response_chunk.stats?.shards_skipped.valueOf() ?? 0, + shardsSkippedFilter: chunk.response_chunk.stats?.shards_skipped_filter.valueOf() ?? 0, + ngramMatches: chunk.response_chunk.stats?.ngram_matches.valueOf() ?? 0, + ngramLookups: chunk.response_chunk.stats?.ngram_lookups.valueOf() ?? 0, + wait: chunk.response_chunk.stats?.wait?.nanos ?? 0, + matchTreeConstruction: chunk.response_chunk.stats?.match_tree_construction?.nanos ?? 0, + matchTreeSearch: chunk.response_chunk.stats?.match_tree_search?.nanos ?? 0, + regexpsConsidered: chunk.response_chunk.stats?.regexps_considered.valueOf() ?? 0, + // @todo: handle this. + flushReason: 0, + } + } + + const sseData = `data: ${JSON.stringify(response)}\n\n`; + controller.enqueue(new TextEncoder().encode(sseData)); + } catch (error) { + console.error('Error encoding chunk:', error); + } finally { + grpcStream?.resume(); + } + }); + + // Handle stream completion + grpcStream.on('end', () => { + if (!isStreamActive) { + return; + } + isStreamActive = false; + + // Send completion signal + controller.enqueue(new TextEncoder().encode('data: [DONE]\n\n')); + controller.close(); + console.log('SSE stream completed'); + client.close(); + }); + + // Handle errors + grpcStream.on('error', (error: grpc.ServiceError) => { + console.error('gRPC stream error:', error); + + if (!isStreamActive) { + return; + } + isStreamActive = false; + + // Send error as SSE event + const errorData = `data: ${JSON.stringify({ + error: { + code: error.code, + message: error.details || error.message, + } + })}\n\n`; + controller.enqueue(new TextEncoder().encode(errorData)); + + controller.close(); + client.close(); + }); + } catch (error) { + console.error('Stream initialization error:', error); + + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + const errorData = `data: ${JSON.stringify({ + error: { message: errorMessage } + })}\n\n`; + controller.enqueue(new TextEncoder().encode(errorData)); + + controller.close(); + client.close(); + } + }, + cancel() { + console.log('SSE stream cancelled by client'); + isStreamActive = false; + + // Cancel the gRPC stream to stop receiving data + if (grpcStream) { + grpcStream.cancel(); + } + + client.close(); + } + }); +} \ No newline at end of file diff --git a/packages/web/src/features/search/searchApi.ts b/packages/web/src/features/search/searchApi.ts index 1ca57ef46..ee7037590 100644 --- a/packages/web/src/features/search/searchApi.ts +++ b/packages/web/src/features/search/searchApi.ts @@ -216,7 +216,7 @@ export const search = async ({ query, matches, contextLines, whole }: SearchRequ return invalidZoektResponse(searchResponse); } - const transformZoektSearchResponse = async ({ Result }: ZoektSearchResponse) => { + const transformZoektSearchResponse = async ({ Result }: ZoektSearchResponse): Promise => { // @note (2025-05-12): in zoekt, repositories are identified by the `RepositoryID` field // which corresponds to the `id` in the Repo table. In order to efficiently fetch repository // metadata when transforming (potentially thousands) of file matches, we aggregate a unique From 9f66c458c4509290ea1a1cd7de19dd9e7c7fdd9a Mon Sep 17 00:00:00 2001 From: bkellam Date: Fri, 14 Nov 2025 21:21:56 -0800 Subject: [PATCH 04/40] fix scrolling issue --- .../search/components/filterPanel/index.tsx | 5 +++ .../search/components/searchResultsPage.tsx | 7 +++- .../components/searchResultsPanel/index.tsx | 36 ++++++++++--------- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/packages/web/src/app/[domain]/search/components/filterPanel/index.tsx b/packages/web/src/app/[domain]/search/components/filterPanel/index.tsx index 231cda184..2826d78bb 100644 --- a/packages/web/src/app/[domain]/search/components/filterPanel/index.tsx +++ b/packages/web/src/app/[domain]/search/components/filterPanel/index.tsx @@ -15,6 +15,7 @@ import { useGetSelectedFromQuery } from "./useGetSelectedFromQuery"; interface FilePanelProps { matches: SearchResultFile[]; repoInfo: Record; + onFilterChange?: () => void; } /** @@ -31,10 +32,12 @@ interface FilePanelProps { * * @param matches - Array of search result files to filter * @param repoInfo - Information about repositories including their display names and icons + * @param onFilterChange - Optional callback that is called whenever a filter is applied or removed */ export const FilterPanel = ({ matches, repoInfo, + onFilterChange, }: FilePanelProps) => { const router = useRouter(); const searchParams = useSearchParams(); @@ -148,6 +151,7 @@ export const FilterPanel = ({ if (newParams.toString() !== searchParams.toString()) { router.replace(`?${newParams.toString()}`, { scroll: false }); + onFilterChange?.(); } }} className="max-h-[50%]" @@ -170,6 +174,7 @@ export const FilterPanel = ({ if (newParams.toString() !== searchParams.toString()) { router.replace(`?${newParams.toString()}`, { scroll: false }); + onFilterChange?.(); } }} className="overflow-auto" diff --git a/packages/web/src/app/[domain]/search/components/searchResultsPage.tsx b/packages/web/src/app/[domain]/search/components/searchResultsPage.tsx index 7bacb12c8..bbbc6727e 100644 --- a/packages/web/src/app/[domain]/search/components/searchResultsPage.tsx +++ b/packages/web/src/app/[domain]/search/components/searchResultsPage.tsx @@ -31,7 +31,7 @@ import { useStreamedSearch } from "../useStreamedSearch"; import { CodePreviewPanel } from "./codePreviewPanel"; import { FilterPanel } from "./filterPanel"; import { useFilteredMatches } from "./filterPanel/useFilterMatches"; -import { SearchResultsPanel } from "./searchResultsPanel"; +import { SearchResultsPanel, SearchResultsPanelHandle } from "./searchResultsPanel"; interface SearchResultsPageProps { searchQuery: string; @@ -198,6 +198,7 @@ const PanelGroup = ({ const [previewedFile, setPreviewedFile] = useState(undefined); const filteredFileMatches = useFilteredMatches(fileMatches); const filterPanelRef = useRef(null); + const searchResultsPanelRef = useRef(null); const [selectedMatchIndex, setSelectedMatchIndex] = useState(0); const [isFilterPanelCollapsed, setIsFilterPanelCollapsed] = useLocalStorage('isFilterPanelCollapsed', false); @@ -238,6 +239,9 @@ const PanelGroup = ({ { + searchResultsPanelRef.current?.resetScroll(); + }} /> {isFilterPanelCollapsed && ( @@ -325,6 +329,7 @@ const PanelGroup = ({
{filteredFileMatches.length > 0 ? ( { setSelectedMatchIndex(matchIndex ?? 0); diff --git a/packages/web/src/app/[domain]/search/components/searchResultsPanel/index.tsx b/packages/web/src/app/[domain]/search/components/searchResultsPanel/index.tsx index 61e413322..978a254d5 100644 --- a/packages/web/src/app/[domain]/search/components/searchResultsPanel/index.tsx +++ b/packages/web/src/app/[domain]/search/components/searchResultsPanel/index.tsx @@ -3,8 +3,8 @@ import { RepositoryInfo, SearchResultFile } from "@/features/search/types"; import { FileMatchContainer, MAX_MATCHES_TO_PREVIEW } from "./fileMatchContainer"; import { useVirtualizer, VirtualItem } from "@tanstack/react-virtual"; -import { useCallback, useEffect, useRef, useState } from "react"; -import { useDebounce, usePrevious } from "@uidotdev/usehooks"; +import { useCallback, useEffect, useImperativeHandle, useRef, useState, forwardRef } from "react"; +import { useDebounce } from "@uidotdev/usehooks"; interface SearchResultsPanelProps { fileMatches: SearchResultFile[]; @@ -15,6 +15,10 @@ interface SearchResultsPanelProps { repoInfo: Record; } +export interface SearchResultsPanelHandle { + resetScroll: () => void; +} + const ESTIMATED_LINE_HEIGHT_PX = 20; const ESTIMATED_NUMBER_OF_LINES_PER_CODE_CELL = 10; const ESTIMATED_MATCH_CONTAINER_HEIGHT_PX = 30; @@ -25,14 +29,14 @@ type ScrollHistoryState = { showAllMatchesStates?: boolean[]; } -export const SearchResultsPanel = ({ +export const SearchResultsPanel = forwardRef(({ fileMatches, onOpenFilePreview, isLoadMoreButtonVisible, onLoadMoreButtonClicked, isBranchFilteringEnabled, repoInfo, -}: SearchResultsPanelProps) => { +}, ref) => { const parentRef = useRef(null); // Restore the scroll offset, measurements cache, and other state from the history @@ -73,18 +77,16 @@ export const SearchResultsPanel = ({ debug: false, }); - // When the number of file matches changes, we need to reset our scroll state. - const prevFileMatches = usePrevious(fileMatches); - useEffect(() => { - if (!prevFileMatches) { - return; - } + const resetScroll = useCallback(() => { + setShowAllMatchesStates(Array(fileMatches.length).fill(false)); + virtualizer.scrollToIndex(0); + }, [fileMatches.length, virtualizer]); + + // Expose the resetScroll function to parent components + useImperativeHandle(ref, () => ({ + resetScroll, + }), [resetScroll]); - if (prevFileMatches.length !== fileMatches.length) { - setShowAllMatchesStates(Array(fileMatches.length).fill(false)); - virtualizer.scrollToIndex(0); - } - }, [fileMatches.length, prevFileMatches, virtualizer]); // Save the scroll state to the history stack. const debouncedScrollOffset = useDebounce(virtualizer.scrollOffset, 100); @@ -177,4 +179,6 @@ export const SearchResultsPanel = ({ )} ) -} \ No newline at end of file +}); + +SearchResultsPanel.displayName = 'SearchResultsPanel'; \ No newline at end of file From 4f394519fd754b5e7291c5bc7e32ff83814beb42 Mon Sep 17 00:00:00 2001 From: bkellam Date: Sat, 15 Nov 2025 11:32:42 -0800 Subject: [PATCH 05/40] Dockerfile --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 41c677121..eb352435f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -217,6 +217,9 @@ COPY --from=zoekt-builder \ /cmd/zoekt-index \ /usr/local/bin/ +# Copy zoekt proto files (needed for gRPC client at runtime) +COPY vendor/zoekt/grpc/protos /app/vendor/zoekt/grpc/protos + # Copy all of the things COPY --from=web-builder /app/packages/web/public ./packages/web/public COPY --from=web-builder /app/packages/web/.next/standalone ./ From cfdadf29e064ab31359bbafd7e13ed5c15c5d7e6 Mon Sep 17 00:00:00 2001 From: bkellam Date: Sat, 15 Nov 2025 15:23:32 -0800 Subject: [PATCH 06/40] wip on lezer parser grammar for query language --- packages/queryLanguage/.gitignore | 2 + packages/queryLanguage/package.json | 19 + packages/queryLanguage/src/parser.terms.ts | 10 + packages/queryLanguage/src/parser.ts | 18 + packages/queryLanguage/src/query.grammar | 89 ++++ packages/queryLanguage/src/tokens.ts | 61 +++ packages/queryLanguage/test.ts | 46 ++ packages/queryLanguage/test/basic.txt | 72 +++ packages/queryLanguage/test/grammar.test.ts | 21 + packages/queryLanguage/test/grouping.txt | 120 +++++ packages/queryLanguage/test/negation.txt | 287 ++++++++++++ packages/queryLanguage/test/operators.txt | 271 +++++++++++ packages/queryLanguage/test/prefixes.txt | 336 +++++++++++++ packages/queryLanguage/test/quoted.txt | 495 ++++++++++++++++++++ packages/queryLanguage/tsconfig.json | 23 + packages/queryLanguage/vitest.config.ts | 8 + yarn.lock | 41 ++ 17 files changed, 1919 insertions(+) create mode 100644 packages/queryLanguage/.gitignore create mode 100644 packages/queryLanguage/package.json create mode 100644 packages/queryLanguage/src/parser.terms.ts create mode 100644 packages/queryLanguage/src/parser.ts create mode 100644 packages/queryLanguage/src/query.grammar create mode 100644 packages/queryLanguage/src/tokens.ts create mode 100644 packages/queryLanguage/test.ts create mode 100644 packages/queryLanguage/test/basic.txt create mode 100644 packages/queryLanguage/test/grammar.test.ts create mode 100644 packages/queryLanguage/test/grouping.txt create mode 100644 packages/queryLanguage/test/negation.txt create mode 100644 packages/queryLanguage/test/operators.txt create mode 100644 packages/queryLanguage/test/prefixes.txt create mode 100644 packages/queryLanguage/test/quoted.txt create mode 100644 packages/queryLanguage/tsconfig.json create mode 100644 packages/queryLanguage/vitest.config.ts diff --git a/packages/queryLanguage/.gitignore b/packages/queryLanguage/.gitignore new file mode 100644 index 000000000..81d9910ba --- /dev/null +++ b/packages/queryLanguage/.gitignore @@ -0,0 +1,2 @@ +/node_modules/ +/dist diff --git a/packages/queryLanguage/package.json b/packages/queryLanguage/package.json new file mode 100644 index 000000000..f1659da10 --- /dev/null +++ b/packages/queryLanguage/package.json @@ -0,0 +1,19 @@ +{ + "name": "@sourcebot/query-language", + "private": true, + "scripts": { + "build": "lezer-generator src/query.grammar -o src/parser --typeScript --names && tsc", + "test": "vitest", + "asdf": "tsx test.ts" + }, + "devDependencies": { + "@lezer/generator": "^1.8.0", + "tsx": "^4.19.1", + "typescript": "^5.7.3", + "vitest": "^2.1.9" + }, + "dependencies": { + "@lezer/common": "^1.3.0", + "@lezer/lr": "^1.4.3" + } +} diff --git a/packages/queryLanguage/src/parser.terms.ts b/packages/queryLanguage/src/parser.terms.ts new file mode 100644 index 000000000..a123cf2bd --- /dev/null +++ b/packages/queryLanguage/src/parser.terms.ts @@ -0,0 +1,10 @@ +// This file was generated by lezer-generator. You probably shouldn't edit it. +export const + negate = 24, + Program = 1, + OrExpr = 2, + AndExpr = 3, + NegateExpr = 4, + PrefixExpr = 5, + ParenExpr = 19, + Term = 20 diff --git a/packages/queryLanguage/src/parser.ts b/packages/queryLanguage/src/parser.ts new file mode 100644 index 000000000..f1cb1153e --- /dev/null +++ b/packages/queryLanguage/src/parser.ts @@ -0,0 +1,18 @@ +// This file was generated by lezer-generator. You probably shouldn't edit it. +import {LRParser} from "@lezer/lr" +import {negateToken} from "./tokens" +export const parser = LRParser.deserialize({ + version: 14, + states: "'hOVQROOO!^QQO'#CbO!^QQO'#CcO!^QQO'#CdO!^QQO'#CeO!^QQO'#CfO!^QQO'#CgO!^QQO'#ChO!^QQO'#CiO!^QQO'#CjO!^QQO'#CkO!^QQO'#ClO!^QQO'#CmO!^QQO'#CnOOQP'#Ca'#CaOVQRO'#CoO!fQQO'#C`OOQP'#Cp'#CpOOQP'#Cy'#CyO#dQRO'#CxO#qQQO'#CxO#|QQO'#C^OOQO'#Cw'#CwQOQQOOOOQP'#C{'#C{OOQP,58|,58|OOQP,58},58}OOQP,59O,59OOOQP,59P,59POOQP,59Q,59QOOQP,59R,59ROOQP,59S,59SOOQP,59T,59TOOQP,59U,59UOOQP,59V,59VOOQP,59W,59WOOQP,59X,59XOOQP,59Y,59YO$RQQO,59ZOOQP,58z,58zOOQP'#Cq'#CqO$WQRO,58yOVQRO'#CrO$eQQO,58xOOQP1G.u1G.uOOQP-E6o-E6oO$pQRO'#CxOOQO'#Cx'#CxOOQO,59^,59^OOQO-E6p-E6p", + stateData: "%a~OjOS~Oh`OnPOpaOqaOrQOsROtSOuTOvUOwVOxWOyXOzYO{ZO|[O}]O!O_O~OphOqhO~OnPOrQOsROtSOuTOvUOwVOxWOyXOzYO{ZO|[O}]O!O_O~OgkX!QlX!PkX~PVOgkX!QlX!PkX~O!QzO~O!P|O~OgRa!QRa!PRa~PVO!QzOgQa!PQa~OglX!QlX!PlX~PVOpnrstuvwxyz{|}!Qqx~", + goto: "$ZpPPqu|!U!a!a!a!a!a!a!a!a!a!a!a!a!a!U|!j!qPPPP!w!}#UP#bTfO_SdO_R!Pz]bO_cyz!O[bO_cyz!ORw`_^O_`cyz!OSyc!OR}yQ{eR!R{QgORv_SeO_R!QzScO_Uxcy!OR!OzQiPQjQQkRQlSQmTQnUQoVQpWQqXQrYQsZQt[Ru]", + nodeNames: "⚠ Program OrExpr AndExpr NegateExpr PrefixExpr ArchivedExpr BranchExpr ContentExpr CaseExpr FileExpr ForkExpr PublicExpr RepoExpr RegexExpr LangExpr SymExpr TypeExpr RepoSetExpr ParenExpr Term", + maxTerm: 48, + skippedNodes: [0], + repeatNodeCount: 2, + tokenData: "!=_~RlOX!yXY#wYZ#wZp!ypq#wqr!yrs$Vsx!yxy%yyz&Oz#T!y#T#U&T#U#V.g#V#W4}#W#Y!y#Y#Z@V#Z#`!y#`#aHe#a#c!y#c#dMP#d#e!!m#e#f!y#f#g!)T#g#h!5V#h#i!8s#i;'S!y;'S;=`#q<%lO!y~#OZq~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!];'S!y;'S;=`#q<%lO!y~#tP;=`<%l!y~#|Rj~XY#wYZ#wpq#w~$YWOY$VZr$Vrs$rs#O$V#O#P$w#P;'S$V;'S;=`%s<%lO$V~$wOp~~$zRO;'S$V;'S;=`%T;=`O$V~%WXOY$VZr$Vrs$rs#O$V#O#P$w#P;'S$V;'S;=`%s;=`<%l$V<%lO$V~%vP;=`<%l$V~&OO!O~~&TO!P~~&Y]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#f!y#f#g'R#g;'S!y;'S;=`#q<%lO!y~'W]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#V!y#V#W(P#W;'S!y;'S;=`#q<%lO!y~(U]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#[!y#[#](}#];'S!y;'S;=`#q<%lO!y~)S]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#]!y#]#^){#^;'S!y;'S;=`#q<%lO!y~*Q]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#j!y#j#k*y#k;'S!y;'S;=`#q<%lO!y~+O]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#X!y#X#Y+w#Y;'S!y;'S;=`#q<%lO!y~+|]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#W!y#W#X,u#X;'S!y;'S;=`#q<%lO!y~,zZq~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]-m!];'S!y;'S;=`#q<%lO!y~-tZn~q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!];'S!y;'S;=`#q<%lO!y~.l]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]/e!]#f!y#f#g0_#g;'S!y;'S;=`#q<%lO!y~/lZr~q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!];'S!y;'S;=`#q<%lO!y~0d]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#T!y#T#U1]#U;'S!y;'S;=`#q<%lO!y~1b]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#b!y#b#c2Z#c;'S!y;'S;=`#q<%lO!y~2`]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#V!y#V#W3X#W;'S!y;'S;=`#q<%lO!y~3^]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#[!y#[#]4V#];'S!y;'S;=`#q<%lO!y~4[Zq~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]/e!];'S!y;'S;=`#q<%lO!y~5S_q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]6R!]#T!y#T#U6{#U#c!y#c#d:i#d;'S!y;'S;=`#q<%lO!y~6YZs~q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!];'S!y;'S;=`#q<%lO!y~7Q]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#g!y#g#h7y#h;'S!y;'S;=`#q<%lO!y~8O]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#X!y#X#Y8w#Y;'S!y;'S;=`#q<%lO!y~8|Zq~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]9o!];'S!y;'S;=`#q<%lO!y~9vZt~q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!];'S!y;'S;=`#q<%lO!y~:n]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#b!y#b#c;g#c;'S!y;'S;=`#q<%lO!y~;l]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#h!y#h#ia#c;'S!y;'S;=`#q<%lO!y~>f]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#h!y#h#i?_#i;'S!y;'S;=`#q<%lO!y~?dZq~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]6R!];'S!y;'S;=`#q<%lO!y~@[_q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]AZ!]#]!y#]#^BT#^#c!y#c#dDw#d;'S!y;'S;=`#q<%lO!y~AbZu~q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!];'S!y;'S;=`#q<%lO!y~BY]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#`!y#`#aCR#a;'S!y;'S;=`#q<%lO!y~CW]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#X!y#X#YDP#Y;'S!y;'S;=`#q<%lO!y~DUZq~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]AZ!];'S!y;'S;=`#q<%lO!y~D|]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#f!y#f#gEu#g;'S!y;'S;=`#q<%lO!y~Ez]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#_!y#_#`Fs#`;'S!y;'S;=`#q<%lO!y~FxZq~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]Gk!];'S!y;'S;=`#q<%lO!y~GrZv~q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!];'S!y;'S;=`#q<%lO!y~Hj]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#T!y#T#UIc#U;'S!y;'S;=`#q<%lO!y~Ih]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#b!y#b#cJa#c;'S!y;'S;=`#q<%lO!y~Jf]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#Z!y#Z#[K_#[;'S!y;'S;=`#q<%lO!y~KdZq~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]LV!];'S!y;'S;=`#q<%lO!y~L^Zz~q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!];'S!y;'S;=`#q<%lO!y~MU]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#f!y#f#gM}#g;'S!y;'S;=`#q<%lO!y~NSfq~OX! hXZ!!bZp! hpq!!bqr! hrs!!bsx! hxz!!bz}! h}!O! h!O!Q! h!Q![!y![!]! h!]!c! h!c!}!y!}#R! h#R#S!y#S#T! h#T#o!y#o;'S! h;'S;=`!!g<%lO! h~! oZ!Q~q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!];'S!y;'S;=`#q<%lO!y~!!gO!Q~~!!jP;=`<%l! h~!!r]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#i!y#i#j!#k#j;'S!y;'S;=`#q<%lO!y~!#p]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#U!y#U#V!$i#V;'S!y;'S;=`#q<%lO!y~!$n]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#`!y#`#a!%g#a;'S!y;'S;=`#q<%lO!y~!%l]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#]!y#]#^!&e#^;'S!y;'S;=`#q<%lO!y~!&j]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#V!y#V#W!'c#W;'S!y;'S;=`#q<%lO!y~!'hZq~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!(Z!];'S!y;'S;=`#q<%lO!y~!(bZw~q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!];'S!y;'S;=`#q<%lO!y~!)Y]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!*R!]#X!y#X#Y!*{#Y;'S!y;'S;=`#q<%lO!y~!*YZx~q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!];'S!y;'S;=`#q<%lO!y~!+Q_q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#Z!y#Z#[!,P#[#d!y#d#e!/m#e;'S!y;'S;=`#q<%lO!y~!,U]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#X!y#X#Y!,}#Y;'S!y;'S;=`#q<%lO!y~!-S]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#l!y#l#m!-{#m;'S!y;'S;=`#q<%lO!y~!.QZq~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!.s!];'S!y;'S;=`#q<%lO!y~!.zZy~q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!];'S!y;'S;=`#q<%lO!y~!/r]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#c!y#c#d!0k#d;'S!y;'S;=`#q<%lO!y~!0p]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!*R!]#g!y#g#h!1i#h;'S!y;'S;=`#q<%lO!y~!1n]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#X!y#X#Y!2g#Y;'S!y;'S;=`#q<%lO!y~!2l]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#h!y#h#i!3e#i;'S!y;'S;=`#q<%lO!y~!3jZq~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!4]!];'S!y;'S;=`#q<%lO!y~!4dZ}~q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!];'S!y;'S;=`#q<%lO!y~!5[]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#m!y#m#n!6T#n;'S!y;'S;=`#q<%lO!y~!6Y]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#a!y#a#b!7R#b;'S!y;'S;=`#q<%lO!y~!7WZq~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!7y!];'S!y;'S;=`#q<%lO!y~!8QZ{~q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!];'S!y;'S;=`#q<%lO!y~!8x]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!9q!]#m!y#m#n!:k#n;'S!y;'S;=`#q<%lO!y~!9xZ|~q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!];'S!y;'S;=`#q<%lO!y~!:p]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#d!y#d#e!;i#e;'S!y;'S;=`#q<%lO!y~!;n]q~OX!yZp!yqr!ysx!yz}!y}!O!y!O![!y![!]!y!]#X!y#X#Y! { + if (input.next !== 45 /* '-' */) return; // Not a dash + + const startPos = input.pos; + + // Look ahead to see what follows the dash + input.advance(); + + // Skip whitespace + let ch = input.next; + while (ch === 32 || ch === 9 || ch === 10) { + input.advance(); + ch = input.next; + } + + // Check if followed by opening paren + if (ch === 40 /* '(' */) { + input.acceptToken(negate, -input.pos + startPos + 1); // Accept just the dash + return; + } + + // Check if followed by a prefix keyword (by checking for keyword followed by colon) + // We need to look ahead to find the colon + const checkPos = input.pos; + let foundColon = false; + let charCount = 0; + + // Look ahead up to 10 characters to find a colon + while (charCount < 10 && ch >= 0) { + if (ch === 58 /* ':' */) { + foundColon = true; + break; + } + if (ch === 32 || ch === 9 || ch === 10 || ch === 40 || ch === 41 || ch === 34) { + // Hit whitespace, paren, or quote - not a prefix + break; + } + input.advance(); + ch = input.next; + charCount++; + } + + // Reset position + while (input.pos > checkPos) { + input.advance(-1); + } + + if (foundColon) { + // It's a prefix keyword, accept as negate + input.acceptToken(negate, -input.pos + startPos + 1); + return; + } + + // Otherwise, don't tokenize as negate (let word handle it) +}); + diff --git a/packages/queryLanguage/test.ts b/packages/queryLanguage/test.ts new file mode 100644 index 000000000..e7a6e3787 --- /dev/null +++ b/packages/queryLanguage/test.ts @@ -0,0 +1,46 @@ +import { parser } from "./src/parser"; + +const input = "hello case:yes"; +const tree = parser.parse(input); + +const prettyPrint = (tree: ReturnType, input: string) => { + let result = ""; + let lastPos = 0; + + tree.iterate({ + enter: (node) => { + // If this is a leaf node (terminal), collect its text + if (node.from >= node.to) { + // Empty node, skip + return; + } + + // Check if this node has any children by checking the tree structure + const nodeTree = node.node; + const isLeaf = !nodeTree.firstChild; + + if (isLeaf) { + // Add any whitespace between the last position and this node + if (node.from > lastPos) { + result += input.slice(lastPos, node.from); + } + + // Add the node's text + result += input.slice(node.from, node.to); + lastPos = node.to; + } + } + }); + + // Add any trailing content + if (lastPos < input.length) { + result += input.slice(lastPos, input.length); + } + + return result; +} + +const reconstructed = prettyPrint(tree, input); +console.log("Original:", input); +console.log("Reconstructed:", reconstructed); +console.log("Match:", input === reconstructed); \ No newline at end of file diff --git a/packages/queryLanguage/test/basic.txt b/packages/queryLanguage/test/basic.txt new file mode 100644 index 000000000..de8bb93bd --- /dev/null +++ b/packages/queryLanguage/test/basic.txt @@ -0,0 +1,72 @@ +# Single term + +hello + +==> + +Program(Term) + +# Multiple terms + +hello world + +==> + +Program(AndExpr(Term,Term)) + +# Multiple terms with various characters + +console.log error_handler + +==> + +Program(AndExpr(Term,Term)) + +# Term with underscores + +my_variable_name + +==> + +Program(Term) + +# Term with dots + +com.example.package + +==> + +Program(Term) + +# Term with numbers + +func123 test_456 + +==> + +Program(AndExpr(Term,Term)) + +# Regex pattern + +[a-z]+ + +==> + +Program(Term) + +# Wildcard pattern + +test.* + +==> + +Program(Term) + +# Multiple regex patterns + +\w+ [0-9]+ \s* + +==> + +Program(AndExpr(Term,Term,Term)) + diff --git a/packages/queryLanguage/test/grammar.test.ts b/packages/queryLanguage/test/grammar.test.ts new file mode 100644 index 000000000..a02862856 --- /dev/null +++ b/packages/queryLanguage/test/grammar.test.ts @@ -0,0 +1,21 @@ +import { parser } from "../src/parser"; +import { fileTests } from "@lezer/generator/dist/test"; +import { describe, it } from "vitest"; +import { fileURLToPath } from "url" +import * as fs from "fs"; +import * as path from "path"; + +const caseDir = path.dirname(fileURLToPath(import.meta.url)) + +for (const file of fs.readdirSync(caseDir)) { + if (!/\.txt$/.test(file)) { + continue; + } + + let name = /^[^\.]*/.exec(file)?.[0]; + describe(name ?? "unknown", () => { + for (const { name, run } of fileTests(fs.readFileSync(path.join(caseDir, file), "utf8"), file)) { + it(name, () => run(parser)); + } + }); +} \ No newline at end of file diff --git a/packages/queryLanguage/test/grouping.txt b/packages/queryLanguage/test/grouping.txt new file mode 100644 index 000000000..e8c7798eb --- /dev/null +++ b/packages/queryLanguage/test/grouping.txt @@ -0,0 +1,120 @@ +# Empty parentheses + +() + +==> + +Program(ParenExpr(Term(⚠))) + +# Simple grouping + +(test) + +==> + +Program(ParenExpr(Term)) + +# Multiple terms in group + +(hello world) + +==> + +Program(ParenExpr(AndExpr(Term,Term))) + +# Nested parentheses + +((test)) + +==> + +Program(ParenExpr(ParenExpr(Term))) + +# Multiple groups + +(first) (second) + +==> + +Program(AndExpr(ParenExpr(Term),ParenExpr(Term))) + +# Group with multiple terms + +(one two three) + +==> + +Program(ParenExpr(AndExpr(Term,Term,Term))) + +# Mixed grouped and ungrouped + +test (grouped) another + +==> + +Program(AndExpr(Term,ParenExpr(Term),Term)) + +# Deeply nested + +(((nested))) + +==> + +Program(ParenExpr(ParenExpr(ParenExpr(Term)))) + +# Multiple nested groups + +((a b) (c d)) + +==> + +Program(ParenExpr(AndExpr(ParenExpr(AndExpr(Term,Term)),ParenExpr(AndExpr(Term,Term))))) + +# Group at start + +(start) middle end + +==> + +Program(AndExpr(ParenExpr(Term),Term,Term)) + +# Group at end + +start middle (end) + +==> + +Program(AndExpr(Term,Term,ParenExpr(Term))) + +# Complex grouping pattern + +(a (b c) d) + +==> + +Program(ParenExpr(AndExpr(Term,ParenExpr(AndExpr(Term,Term)),Term))) + +# Sequential groups + +(a)(b)(c) + +==> + +Program(AndExpr(ParenExpr(Term),ParenExpr(Term),ParenExpr(Term))) + +# Group with regex + +([a-z]+) + +==> + +Program(ParenExpr(Term)) + +# Group with dots + +(com.example.test) + +==> + +Program(ParenExpr(Term)) + diff --git a/packages/queryLanguage/test/negation.txt b/packages/queryLanguage/test/negation.txt new file mode 100644 index 000000000..bb61e7dbf --- /dev/null +++ b/packages/queryLanguage/test/negation.txt @@ -0,0 +1,287 @@ +# Literal dash term + +-test + +==> + +Program(Term) + +# Quoted dash term + +"-excluded" + +==> + +Program(Term) + +# Dash in middle + +test-case + +==> + +Program(Term) + +# Multiple dash terms + +-one -two -three + +==> + +Program(AndExpr(Term,Term,Term)) + +# Negate file prefix + +-file:test.js + +==> + +Program(NegateExpr(PrefixExpr(FileExpr))) + +# Negate repo prefix + +-repo:archived + +==> + +Program(NegateExpr(PrefixExpr(RepoExpr))) + +# Negate lang prefix + +-lang:python + +==> + +Program(NegateExpr(PrefixExpr(LangExpr))) + +# Negate content prefix + +-content:TODO + +==> + +Program(NegateExpr(PrefixExpr(ContentExpr))) + +# Negate branch prefix + +-branch:develop + +==> + +Program(NegateExpr(PrefixExpr(BranchExpr))) + +# Negate case prefix + +-case:yes + +==> + +Program(NegateExpr(PrefixExpr(CaseExpr))) + +# Negate archived prefix + +-archived:yes + +==> + +Program(NegateExpr(PrefixExpr(ArchivedExpr))) + +# Negate fork prefix + +-fork:yes + +==> + +Program(NegateExpr(PrefixExpr(ForkExpr))) + +# Negate public prefix + +-public:no + +==> + +Program(NegateExpr(PrefixExpr(PublicExpr))) + +# Negate symbol prefix + +-sym:OldClass + +==> + +Program(NegateExpr(PrefixExpr(SymExpr))) + +# Negate type prefix + +-type:repo + +==> + +Program(NegateExpr(PrefixExpr(TypeExpr))) + +# Negate regex prefix + +-regex:test.* + +==> + +Program(NegateExpr(PrefixExpr(RegexExpr))) + +# Negate parentheses + +-(test) + +==> + +Program(NegateExpr(ParenExpr(Term))) + +# Negate group with multiple terms + +-(test exclude) + +==> + +Program(NegateExpr(ParenExpr(AndExpr(Term,Term)))) + +# Negate group with prefix + +-(file:test.js console.log) + +==> + +Program(NegateExpr(ParenExpr(AndExpr(PrefixExpr(FileExpr),Term)))) + +# Prefix with negated term + +file:test.js -console + +==> + +Program(AndExpr(PrefixExpr(FileExpr),Term)) + +# Multiple prefixes with negation + +file:test.js -lang:python + +==> + +Program(AndExpr(PrefixExpr(FileExpr),NegateExpr(PrefixExpr(LangExpr)))) + +# Complex negation pattern + +function -file:test.js -lang:java + +==> + +Program(AndExpr(Term,NegateExpr(PrefixExpr(FileExpr)),NegateExpr(PrefixExpr(LangExpr)))) + +# Negation inside parentheses + +(-file:test.js) + +==> + +Program(ParenExpr(NegateExpr(PrefixExpr(FileExpr)))) + +# Multiple negations in group + +(-file:a.js -lang:python) + +==> + +Program(ParenExpr(AndExpr(NegateExpr(PrefixExpr(FileExpr)),NegateExpr(PrefixExpr(LangExpr))))) + +# Mixed in parentheses + +(include -file:test.js) + +==> + +Program(ParenExpr(AndExpr(Term,NegateExpr(PrefixExpr(FileExpr))))) + +# Negate nested group + +-((file:test.js)) + +==> + +Program(NegateExpr(ParenExpr(ParenExpr(PrefixExpr(FileExpr))))) + +# Negate short form prefix + +-f:test.js + +==> + +Program(NegateExpr(PrefixExpr(FileExpr))) + +# Negate short form repo + +-r:myrepo + +==> + +Program(NegateExpr(PrefixExpr(RepoExpr))) + +# Negate short form branch + +-b:main + +==> + +Program(NegateExpr(PrefixExpr(BranchExpr))) + +# Negate short form content + +-c:console + +==> + +Program(NegateExpr(PrefixExpr(ContentExpr))) + +# Negate short form type + +-t:file + +==> + +Program(NegateExpr(PrefixExpr(TypeExpr))) + +# Negate with prefix in quotes + +-file:"test file.js" + +==> + +Program(NegateExpr(PrefixExpr(FileExpr))) + +# Complex with multiple negated prefixes + +lang:typescript -file:*.test.ts -file:*.spec.ts + +==> + +Program(AndExpr(PrefixExpr(LangExpr),NegateExpr(PrefixExpr(FileExpr)),NegateExpr(PrefixExpr(FileExpr)))) + +# Negated group with prefix + +-(file:test.js lang:python) + +==> + +Program(NegateExpr(ParenExpr(AndExpr(PrefixExpr(FileExpr),PrefixExpr(LangExpr))))) + +# Negate empty group + +-() + +==> + +Program(NegateExpr(ParenExpr(Term(⚠)))) + +# Negate with space after dash + +- file:test.js + +==> + +Program(NegateExpr(PrefixExpr(FileExpr))) diff --git a/packages/queryLanguage/test/operators.txt b/packages/queryLanguage/test/operators.txt new file mode 100644 index 000000000..a1aa9a44a --- /dev/null +++ b/packages/queryLanguage/test/operators.txt @@ -0,0 +1,271 @@ +# Simple OR + +test or example + +==> + +Program(OrExpr(Term,Term)) + +# Multiple OR + +one or two or three + +==> + +Program(OrExpr(Term,Term,Term)) + +# OR with prefixes + +file:test.js or file:example.js + +==> + +Program(OrExpr(PrefixExpr(FileExpr),PrefixExpr(FileExpr))) + +# OR with negation + +test or -file:excluded.js + +==> + +Program(OrExpr(Term,NegateExpr(PrefixExpr(FileExpr)))) + +# OR with quoted strings + +"first option" or "second option" + +==> + +Program(OrExpr(Term,Term)) + +# OR with different prefixes + +lang:python or lang:javascript + +==> + +Program(OrExpr(PrefixExpr(LangExpr),PrefixExpr(LangExpr))) + +# Multiple terms with OR + +function test or class example + +==> + +Program(OrExpr(AndExpr(Term,Term),AndExpr(Term,Term))) + +# OR in parentheses + +(test or example) + +==> + +Program(ParenExpr(OrExpr(Term,Term))) + +# OR with parentheses outside + +(test) or (example) + +==> + +Program(OrExpr(ParenExpr(Term),ParenExpr(Term))) + +# Complex OR with grouping + +(file:*.js lang:javascript) or (file:*.ts lang:typescript) + +==> + +Program(OrExpr(ParenExpr(AndExpr(PrefixExpr(FileExpr),PrefixExpr(LangExpr))),ParenExpr(AndExpr(PrefixExpr(FileExpr),PrefixExpr(LangExpr))))) + +# OR with mixed content + +test or file:example.js + +==> + +Program(OrExpr(Term,PrefixExpr(FileExpr))) + +# Prefix OR term + +file:test.js or example + +==> + +Program(OrExpr(PrefixExpr(FileExpr),Term)) + +# OR with short form prefixes + +f:test.js or r:myrepo + +==> + +Program(OrExpr(PrefixExpr(FileExpr),PrefixExpr(RepoExpr))) + +# OR with repo prefixes + +repo:project1 or repo:project2 + +==> + +Program(OrExpr(PrefixExpr(RepoExpr),PrefixExpr(RepoExpr))) + +# OR with branch prefixes + +branch:main or branch:develop + +==> + +Program(OrExpr(PrefixExpr(BranchExpr),PrefixExpr(BranchExpr))) + +# OR with lang prefixes + +lang:rust or lang:go + +==> + +Program(OrExpr(PrefixExpr(LangExpr),PrefixExpr(LangExpr))) + +# OR with content + +content:TODO or content:FIXME + +==> + +Program(OrExpr(PrefixExpr(ContentExpr),PrefixExpr(ContentExpr))) + +# OR with negated terms + +-file:test.js or -file:spec.js + +==> + +Program(OrExpr(NegateExpr(PrefixExpr(FileExpr)),NegateExpr(PrefixExpr(FileExpr)))) + +# OR in nested parentheses + +((a or b) or (c or d)) + +==> + +Program(ParenExpr(OrExpr(ParenExpr(OrExpr(Term,Term)),ParenExpr(OrExpr(Term,Term))))) + +# Multiple OR with parentheses and implicit AND + +(a or b) and (c or d) + +==> + +Program(AndExpr(ParenExpr(OrExpr(Term,Term)),Term,ParenExpr(OrExpr(Term,Term)))) + +# OR with wildcards + +*.test.js or *.spec.js + +==> + +Program(OrExpr(Term,Term)) + +# OR with regex patterns + +[a-z]+ or [0-9]+ + +==> + +Program(OrExpr(Term,Term)) + +# OR with dots + +com.example.test or org.example.test + +==> + +Program(OrExpr(Term,Term)) + +# OR with dashes + +test-one or test-two + +==> + +Program(OrExpr(Term,Term)) + +# Word containing 'or' + +order + +==> + +Program(Term) + +# Word containing 'or' in middle + +before + +==> + +Program(Term) + +# OR at start + +or test + +==> + +Program(⚠,Term) + +# OR at end (or becomes term) + +test or + +==> + +Program(AndExpr(Term,Term)) + +# Multiple consecutive OR + +test or or example + +==> + +Program(OrExpr(Term,⚠,Term)) + +# OR with all prefix types + +file:*.js or repo:myrepo or lang:javascript + +==> + +Program(OrExpr(PrefixExpr(FileExpr),PrefixExpr(RepoExpr),PrefixExpr(LangExpr))) + +# Complex query with OR and negation + +(lang:python or lang:ruby) -file:test.py + +==> + +Program(AndExpr(ParenExpr(OrExpr(PrefixExpr(LangExpr),PrefixExpr(LangExpr))),NegateExpr(PrefixExpr(FileExpr)))) + +# OR with quoted prefix values + +file:"test one.js" or file:"test two.js" + +==> + +Program(OrExpr(PrefixExpr(FileExpr),PrefixExpr(FileExpr))) + +# OR with empty parentheses + +() or () + +==> + +Program(OrExpr(ParenExpr(Term(⚠)),ParenExpr(Term(⚠)))) + +# OR with negated groups + +-(file:a.js) or -(file:b.js) + +==> + +Program(OrExpr(NegateExpr(ParenExpr(PrefixExpr(FileExpr))),NegateExpr(ParenExpr(PrefixExpr(FileExpr))))) diff --git a/packages/queryLanguage/test/prefixes.txt b/packages/queryLanguage/test/prefixes.txt new file mode 100644 index 000000000..1213526dc --- /dev/null +++ b/packages/queryLanguage/test/prefixes.txt @@ -0,0 +1,336 @@ +# File prefix + +file:README.md + +==> + +Program(PrefixExpr(FileExpr)) + +# File prefix short form + +f:index.ts + +==> + +Program(PrefixExpr(FileExpr)) + +# Repo prefix + +repo:myproject + +==> + +Program(PrefixExpr(RepoExpr)) + +# Repo prefix short form + +r:github.com/user/repo + +==> + +Program(PrefixExpr(RepoExpr)) + +# Content prefix + +content:function + +==> + +Program(PrefixExpr(ContentExpr)) + +# Content prefix short form + +c:console.log + +==> + +Program(PrefixExpr(ContentExpr)) + +# Branch prefix + +branch:main + +==> + +Program(PrefixExpr(BranchExpr)) + +# Branch prefix short form + +b:develop + +==> + +Program(PrefixExpr(BranchExpr)) + +# Lang prefix + +lang:typescript + +==> + +Program(PrefixExpr(LangExpr)) + +# Case prefix + +case:yes + +==> + +Program(PrefixExpr(CaseExpr)) + +# Archived prefix + +archived:no + +==> + +Program(PrefixExpr(ArchivedExpr)) + +# Fork prefix + +fork:yes + +==> + +Program(PrefixExpr(ForkExpr)) + +# Public prefix + +public:yes + +==> + +Program(PrefixExpr(PublicExpr)) + +# Symbol prefix + +sym:MyClass + +==> + +Program(PrefixExpr(SymExpr)) + +# Type prefix + +type:file + +==> + +Program(PrefixExpr(TypeExpr)) + +# Type prefix short form + +t:repo + +==> + +Program(PrefixExpr(TypeExpr)) + +# Regex prefix + +regex:test.* + +==> + +Program(PrefixExpr(RegexExpr)) + +# RepoSet prefix + +reposet:repo1,repo2 + +==> + +Program(PrefixExpr(RepoSetExpr)) + +# File with wildcard + +file:*.ts + +==> + +Program(PrefixExpr(FileExpr)) + +# File with path + +file:src/components/Button.tsx + +==> + +Program(PrefixExpr(FileExpr)) + +# Repo with full URL + +repo:github.com/org/project + +==> + +Program(PrefixExpr(RepoExpr)) + +# Multiple prefixes + +file:test.js repo:myproject + +==> + +Program(AndExpr(PrefixExpr(FileExpr),PrefixExpr(RepoExpr))) + +# Prefix with term + +file:test.js console.log + +==> + +Program(AndExpr(PrefixExpr(FileExpr),Term)) + +# Term then prefix + +console.log file:handler.ts + +==> + +Program(AndExpr(Term,PrefixExpr(FileExpr))) + +# Multiple prefixes and terms + +lang:typescript function file:handler.ts + +==> + +Program(AndExpr(PrefixExpr(LangExpr),Term,PrefixExpr(FileExpr))) + +# Prefix with regex pattern + +file:[a-z]+\.test\.js + +==> + +Program(PrefixExpr(FileExpr)) + +# Content with spaces in value (no quotes) + +content:hello + +==> + +Program(PrefixExpr(ContentExpr)) + +# Branch with slashes + +branch:feature/new-feature + +==> + +Program(PrefixExpr(BranchExpr)) + +# Case values + +case:auto + +==> + +Program(PrefixExpr(CaseExpr)) + +# RepoSet with multiple repos + +reposet:repo1,repo2,repo3 + +==> + +Program(PrefixExpr(RepoSetExpr)) + +# Symbol with dots + +sym:package.Class.method + +==> + +Program(PrefixExpr(SymExpr)) + +# Type variations + +type:filename + +==> + +Program(PrefixExpr(TypeExpr)) + +# Lang with various languages + +lang:python + +==> + +Program(PrefixExpr(LangExpr)) + +# Archived values + +archived:yes + +==> + +Program(PrefixExpr(ArchivedExpr)) + +# Fork values + +fork:no + +==> + +Program(PrefixExpr(ForkExpr)) + +# Public values + +public:no + +==> + +Program(PrefixExpr(PublicExpr)) + +# Regex with complex pattern + +regex:\w+\s*=\s*\d+ + +==> + +Program(PrefixExpr(RegexExpr)) + +# File with dashes + +file:my-component.tsx + +==> + +Program(PrefixExpr(FileExpr)) + +# Repo with numbers + +repo:project123 + +==> + +Program(PrefixExpr(RepoExpr)) + +# Content with special chars + +content:@Component + +==> + +Program(PrefixExpr(ContentExpr)) + +# Prefix in parentheses + +(file:test.js) + +==> + +Program(ParenExpr(PrefixExpr(FileExpr))) + +# Multiple prefixes in group + +(file:*.ts lang:typescript) + +==> + +Program(ParenExpr(AndExpr(PrefixExpr(FileExpr),PrefixExpr(LangExpr)))) + diff --git a/packages/queryLanguage/test/quoted.txt b/packages/queryLanguage/test/quoted.txt new file mode 100644 index 000000000..d55089d22 --- /dev/null +++ b/packages/queryLanguage/test/quoted.txt @@ -0,0 +1,495 @@ +# Simple quoted string + +"hello" + +==> + +Program(Term) + +# Quoted string with spaces + +"hello world" + +==> + +Program(Term) + +# Multiple words in quotes + +"this is a search term" + +==> + +Program(Term) + +# Quoted string with escaped quote + +"hello \"world\"" + +==> + +Program(Term) + +# Quoted string with escaped backslash + +"path\\to\\file" + +==> + +Program(Term) + +# Double backslash + +"test\\\\path" + +==> + +Program(Term) + +# Multiple escaped quotes + +"\"quoted\" \"words\"" + +==> + +Program(Term) + +# Mixed escaped characters + +"test\\nvalue\"quoted" + +==> + +Program(Term) + +# Empty quoted string + +"" + +==> + +Program(Term) + +# Quoted string with only spaces + +" " + +==> + +Program(Term) + +# Quoted string in file prefix + +file:"my file.txt" + +==> + +Program(PrefixExpr(FileExpr)) + +# Quoted string in repo prefix + +repo:"github.com/user/repo name" + +==> + +Program(PrefixExpr(RepoExpr)) + +# Quoted string in content prefix + +content:"console.log" + +==> + +Program(PrefixExpr(ContentExpr)) + +# Quoted string in branch prefix + +branch:"feature/my feature" + +==> + +Program(PrefixExpr(BranchExpr)) + +# Multiple quoted strings + +"first string" "second string" + +==> + +Program(AndExpr(Term,Term)) + +# Quoted and unquoted mixed + +unquoted "quoted string" another + +==> + +Program(AndExpr(Term,Term,Term)) + +# Quoted string with parentheses inside + +"(test)" + +==> + +Program(Term) + +# Quoted string with brackets + +"[a-z]+" + +==> + +Program(Term) + +# Quoted string with special chars + +"test@example.com" + +==> + +Program(Term) + +# Quoted string with colons + +"key:value" + +==> + +Program(Term) + +# Quoted string with dashes + +"test-case-example" + +==> + +Program(Term) + +# Quoted string with dots + +"com.example.package" + +==> + +Program(Term) + +# Quoted string with regex pattern + +"\\w+\\s*=\\s*\\d+" + +==> + +Program(Term) + +# Quoted string with forward slashes + +"path/to/file" + +==> + +Program(Term) + +# Quoted string with underscores + +"my_variable_name" + +==> + +Program(Term) + +# Quoted string with numbers + +"test123" + +==> + +Program(Term) + +# Quoted string with mixed case + +"CamelCaseTest" + +==> + +Program(Term) + +# Quoted prefix value with spaces + +file:"test file.js" + +==> + +Program(PrefixExpr(FileExpr)) + +# Multiple prefixes with quoted values + +file:"my file.txt" repo:"my repo" + +==> + +Program(AndExpr(PrefixExpr(FileExpr),PrefixExpr(RepoExpr))) + +# Quoted string in parentheses + +("quoted term") + +==> + +Program(ParenExpr(Term)) + +# Multiple quoted in parentheses + +("first" "second") + +==> + +Program(ParenExpr(AndExpr(Term,Term))) + +# Quoted with escaped newline + +"line1\\nline2" + +==> + +Program(Term) + +# Quoted with tab character + +"value\\ttab" + +==> + +Program(Term) + +# Lang prefix with quoted value + +lang:"objective-c" + +==> + +Program(PrefixExpr(LangExpr)) + +# Sym prefix with quoted value + +sym:"My Class" + +==> + +Program(PrefixExpr(SymExpr)) + +# Content with quoted phrase + +content:"TODO: fix this" + +==> + +Program(PrefixExpr(ContentExpr)) + +# Regex prefix with quoted pattern + +regex:"func\\s+\\w+" + +==> + +Program(PrefixExpr(RegexExpr)) + +# Case prefix with quoted value + +case:"yes" + +==> + +Program(PrefixExpr(CaseExpr)) + +# Quoted string with at symbol + +"@decorator" + +==> + +Program(Term) + +# Quoted string with hash + +"#define" + +==> + +Program(Term) + +# Quoted string with dollar sign + +"$variable" + +==> + +Program(Term) + +# Quoted string with percent + +"100%" + +==> + +Program(Term) + +# Quoted string with ampersand + +"foo&bar" + +==> + +Program(Term) + +# Quoted string with asterisk + +"test*" + +==> + +Program(Term) + +# Quoted string with plus + +"a+b" + +==> + +Program(Term) + +# Quoted string with equals + +"a=b" + +==> + +Program(Term) + +# Quoted string with angle brackets + +"