mirror of
https://github.com/cline/cline.git
synced 2025-06-03 03:59:07 +00:00
searchFiles protobus migration (#3261)
This commit is contained in:
parent
10f7b8ca9e
commit
06fc419a15
5
.changeset/red-carrots-shout.md
Normal file
5
.changeset/red-carrots-shout.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
searchFiles protobus migration
|
@ -25,6 +25,9 @@ service FileService {
|
||||
|
||||
// Convert URIs to workspace-relative paths
|
||||
rpc getRelativePaths(RelativePathsRequest) returns (RelativePaths);
|
||||
|
||||
// Search for files in the workspace with fuzzy matching
|
||||
rpc searchFiles(FileSearchRequest) returns (FileSearchResults);
|
||||
}
|
||||
|
||||
// Request to convert a list of URIs to relative paths
|
||||
@ -38,6 +41,27 @@ message RelativePaths {
|
||||
repeated string paths = 1;
|
||||
}
|
||||
|
||||
// Request for file search operations
|
||||
message FileSearchRequest {
|
||||
Metadata metadata = 1;
|
||||
string query = 2; // Search query string
|
||||
optional string mentions_request_id = 3; // Optional request ID for tracking requests
|
||||
optional int32 limit = 4; // Optional limit for results (default: 20)
|
||||
}
|
||||
|
||||
// Result for file search operations
|
||||
message FileSearchResults {
|
||||
repeated FileInfo results = 1; // Array of file/folder results
|
||||
optional string mentions_request_id = 2; // Echo of the request ID for tracking
|
||||
}
|
||||
|
||||
// File information structure for search results
|
||||
message FileInfo {
|
||||
string path = 1; // Relative path from workspace root
|
||||
string type = 2; // "file" or "folder"
|
||||
optional string label = 3; // Display name (usually basename)
|
||||
}
|
||||
|
||||
// Response for searchCommits
|
||||
message GitCommits {
|
||||
repeated GitCommit commits = 1;
|
||||
@ -66,4 +90,3 @@ message RuleFile {
|
||||
string display_name = 2; // Filename for display purposes
|
||||
bool already_exists = 3; // For createRuleFile, indicates if file already existed
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ import { getRelativePaths } from "./getRelativePaths"
|
||||
import { openFile } from "./openFile"
|
||||
import { openImage } from "./openImage"
|
||||
import { searchCommits } from "./searchCommits"
|
||||
import { searchFiles } from "./searchFiles"
|
||||
|
||||
// Register all file service methods
|
||||
export function registerAllMethods(): void {
|
||||
@ -19,4 +20,5 @@ export function registerAllMethods(): void {
|
||||
registerMethod("openFile", openFile)
|
||||
registerMethod("openImage", openImage)
|
||||
registerMethod("searchCommits", searchCommits)
|
||||
registerMethod("searchFiles", searchFiles)
|
||||
}
|
||||
|
55
src/core/controller/file/searchFiles.ts
Normal file
55
src/core/controller/file/searchFiles.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { Controller } from ".."
|
||||
import { FileSearchRequest, FileSearchResults } from "@shared/proto/file"
|
||||
import { searchWorkspaceFiles } from "@services/search/file-search"
|
||||
import { getWorkspacePath } from "@utils/path"
|
||||
import { FileMethodHandler } from "./index"
|
||||
import { convertSearchResultsToProtoFileInfos } from "@shared/proto-conversions/file/search-result-conversion"
|
||||
|
||||
/**
|
||||
* Searches for files in the workspace with fuzzy matching
|
||||
* @param controller The controller instance
|
||||
* @param request The request containing search query and optionally a mentionsRequestId
|
||||
* @returns Results containing matching files/folders
|
||||
*/
|
||||
export const searchFiles: FileMethodHandler = async (
|
||||
controller: Controller,
|
||||
request: FileSearchRequest,
|
||||
): Promise<FileSearchResults> => {
|
||||
const workspacePath = getWorkspacePath()
|
||||
|
||||
if (!workspacePath) {
|
||||
// Handle case where workspace path is not available
|
||||
console.error("Error in searchFiles: No workspace path available")
|
||||
return FileSearchResults.create({
|
||||
results: [],
|
||||
mentionsRequestId: request.mentionsRequestId,
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
// Call file search service with query from request
|
||||
const searchResults = await searchWorkspaceFiles(
|
||||
request.query || "",
|
||||
workspacePath,
|
||||
request.limit || 20, // Use default limit of 20 if not specified
|
||||
)
|
||||
|
||||
// Convert search results to proto FileInfo objects using the conversion function
|
||||
const protoResults = convertSearchResultsToProtoFileInfos(searchResults)
|
||||
|
||||
// Return successful results
|
||||
return FileSearchResults.create({
|
||||
results: protoResults,
|
||||
mentionsRequestId: request.mentionsRequestId,
|
||||
})
|
||||
} catch (error) {
|
||||
// Log the error but don't include it in the response, following the pattern in searchCommits
|
||||
console.error("Error in searchFiles:", error instanceof Error ? error.message : String(error))
|
||||
|
||||
// Return empty results without error message
|
||||
return FileSearchResults.create({
|
||||
results: [],
|
||||
mentionsRequestId: request.mentionsRequestId,
|
||||
})
|
||||
}
|
||||
}
|
@ -626,49 +626,6 @@ export class Controller {
|
||||
this.postMessageToWebview({ type: "relinquishControl" })
|
||||
break
|
||||
}
|
||||
case "searchFiles": {
|
||||
const workspacePath = getWorkspacePath()
|
||||
|
||||
if (!workspacePath) {
|
||||
// Handle case where workspace path is not available
|
||||
await this.postMessageToWebview({
|
||||
type: "fileSearchResults",
|
||||
results: [],
|
||||
mentionsRequestId: message.mentionsRequestId,
|
||||
error: "No workspace path available",
|
||||
})
|
||||
break
|
||||
}
|
||||
try {
|
||||
// Call file search service with query from message
|
||||
const results = await searchWorkspaceFiles(
|
||||
message.query || "",
|
||||
workspacePath,
|
||||
20, // Use default limit, as filtering is now done in the backend
|
||||
)
|
||||
|
||||
// debug logging to be removed
|
||||
//console.log(`controller/index.ts: Search results: ${results.length}`)
|
||||
|
||||
// Send results back to webview
|
||||
await this.postMessageToWebview({
|
||||
type: "fileSearchResults",
|
||||
results,
|
||||
mentionsRequestId: message.mentionsRequestId,
|
||||
})
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
|
||||
// Send error response to webview
|
||||
await this.postMessageToWebview({
|
||||
type: "fileSearchResults",
|
||||
results: [],
|
||||
error: errorMessage,
|
||||
mentionsRequestId: message.mentionsRequestId,
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
case "toggleFavoriteModel": {
|
||||
if (message.modelId) {
|
||||
const { apiConfiguration } = await getAllExtensionState(this.context)
|
||||
|
@ -0,0 +1,27 @@
|
||||
import { FileInfo } from "@shared/proto/file"
|
||||
|
||||
/**
|
||||
* Converts domain search result objects to proto FileInfo objects
|
||||
*/
|
||||
export function convertSearchResultsToProtoFileInfos(
|
||||
results: { path: string; type: "file" | "folder"; label?: string }[],
|
||||
): FileInfo[] {
|
||||
return results.map((result) => ({
|
||||
path: result.path,
|
||||
type: result.type,
|
||||
label: result.label,
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts proto FileInfo objects to domain search result objects
|
||||
*/
|
||||
export function convertProtoFileInfosToSearchResults(
|
||||
protoResults: FileInfo[],
|
||||
): { path: string; type: "file" | "folder"; label?: string }[] {
|
||||
return protoResults.map((protoResult) => ({
|
||||
path: protoResult.path,
|
||||
type: protoResult.type as "file" | "folder",
|
||||
label: protoResult.label,
|
||||
}))
|
||||
}
|
@ -21,6 +21,35 @@ export interface RelativePaths {
|
||||
paths: string[]
|
||||
}
|
||||
|
||||
/** Request for file search operations */
|
||||
export interface FileSearchRequest {
|
||||
metadata?: Metadata | undefined
|
||||
/** Search query string */
|
||||
query: string
|
||||
/** Optional request ID for tracking requests */
|
||||
mentionsRequestId?: string | undefined
|
||||
/** Optional limit for results (default: 20) */
|
||||
limit?: number | undefined
|
||||
}
|
||||
|
||||
/** Result for file search operations */
|
||||
export interface FileSearchResults {
|
||||
/** Array of file/folder results */
|
||||
results: FileInfo[]
|
||||
/** Echo of the request ID for tracking */
|
||||
mentionsRequestId?: string | undefined
|
||||
}
|
||||
|
||||
/** File information structure for search results */
|
||||
export interface FileInfo {
|
||||
/** Relative path from workspace root */
|
||||
path: string
|
||||
/** "file" or "folder" */
|
||||
type: string
|
||||
/** Display name (usually basename) */
|
||||
label?: string | undefined
|
||||
}
|
||||
|
||||
/** Response for searchCommits */
|
||||
export interface GitCommits {
|
||||
commits: GitCommit[]
|
||||
@ -191,6 +220,283 @@ export const RelativePaths: MessageFns<RelativePaths> = {
|
||||
},
|
||||
}
|
||||
|
||||
function createBaseFileSearchRequest(): FileSearchRequest {
|
||||
return { metadata: undefined, query: "", mentionsRequestId: undefined, limit: undefined }
|
||||
}
|
||||
|
||||
export const FileSearchRequest: MessageFns<FileSearchRequest> = {
|
||||
encode(message: FileSearchRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.metadata !== undefined) {
|
||||
Metadata.encode(message.metadata, writer.uint32(10).fork()).join()
|
||||
}
|
||||
if (message.query !== "") {
|
||||
writer.uint32(18).string(message.query)
|
||||
}
|
||||
if (message.mentionsRequestId !== undefined) {
|
||||
writer.uint32(26).string(message.mentionsRequestId)
|
||||
}
|
||||
if (message.limit !== undefined) {
|
||||
writer.uint32(32).int32(message.limit)
|
||||
}
|
||||
return writer
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): FileSearchRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input)
|
||||
let end = length === undefined ? reader.len : reader.pos + length
|
||||
const message = createBaseFileSearchRequest()
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32()
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break
|
||||
}
|
||||
|
||||
message.metadata = Metadata.decode(reader, reader.uint32())
|
||||
continue
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break
|
||||
}
|
||||
|
||||
message.query = reader.string()
|
||||
continue
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break
|
||||
}
|
||||
|
||||
message.mentionsRequestId = reader.string()
|
||||
continue
|
||||
}
|
||||
case 4: {
|
||||
if (tag !== 32) {
|
||||
break
|
||||
}
|
||||
|
||||
message.limit = reader.int32()
|
||||
continue
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break
|
||||
}
|
||||
reader.skip(tag & 7)
|
||||
}
|
||||
return message
|
||||
},
|
||||
|
||||
fromJSON(object: any): FileSearchRequest {
|
||||
return {
|
||||
metadata: isSet(object.metadata) ? Metadata.fromJSON(object.metadata) : undefined,
|
||||
query: isSet(object.query) ? globalThis.String(object.query) : "",
|
||||
mentionsRequestId: isSet(object.mentionsRequestId) ? globalThis.String(object.mentionsRequestId) : undefined,
|
||||
limit: isSet(object.limit) ? globalThis.Number(object.limit) : undefined,
|
||||
}
|
||||
},
|
||||
|
||||
toJSON(message: FileSearchRequest): unknown {
|
||||
const obj: any = {}
|
||||
if (message.metadata !== undefined) {
|
||||
obj.metadata = Metadata.toJSON(message.metadata)
|
||||
}
|
||||
if (message.query !== "") {
|
||||
obj.query = message.query
|
||||
}
|
||||
if (message.mentionsRequestId !== undefined) {
|
||||
obj.mentionsRequestId = message.mentionsRequestId
|
||||
}
|
||||
if (message.limit !== undefined) {
|
||||
obj.limit = Math.round(message.limit)
|
||||
}
|
||||
return obj
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<FileSearchRequest>, I>>(base?: I): FileSearchRequest {
|
||||
return FileSearchRequest.fromPartial(base ?? ({} as any))
|
||||
},
|
||||
fromPartial<I extends Exact<DeepPartial<FileSearchRequest>, I>>(object: I): FileSearchRequest {
|
||||
const message = createBaseFileSearchRequest()
|
||||
message.metadata =
|
||||
object.metadata !== undefined && object.metadata !== null ? Metadata.fromPartial(object.metadata) : undefined
|
||||
message.query = object.query ?? ""
|
||||
message.mentionsRequestId = object.mentionsRequestId ?? undefined
|
||||
message.limit = object.limit ?? undefined
|
||||
return message
|
||||
},
|
||||
}
|
||||
|
||||
function createBaseFileSearchResults(): FileSearchResults {
|
||||
return { results: [], mentionsRequestId: undefined }
|
||||
}
|
||||
|
||||
export const FileSearchResults: MessageFns<FileSearchResults> = {
|
||||
encode(message: FileSearchResults, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
for (const v of message.results) {
|
||||
FileInfo.encode(v!, writer.uint32(10).fork()).join()
|
||||
}
|
||||
if (message.mentionsRequestId !== undefined) {
|
||||
writer.uint32(18).string(message.mentionsRequestId)
|
||||
}
|
||||
return writer
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): FileSearchResults {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input)
|
||||
let end = length === undefined ? reader.len : reader.pos + length
|
||||
const message = createBaseFileSearchResults()
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32()
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break
|
||||
}
|
||||
|
||||
message.results.push(FileInfo.decode(reader, reader.uint32()))
|
||||
continue
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break
|
||||
}
|
||||
|
||||
message.mentionsRequestId = reader.string()
|
||||
continue
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break
|
||||
}
|
||||
reader.skip(tag & 7)
|
||||
}
|
||||
return message
|
||||
},
|
||||
|
||||
fromJSON(object: any): FileSearchResults {
|
||||
return {
|
||||
results: globalThis.Array.isArray(object?.results) ? object.results.map((e: any) => FileInfo.fromJSON(e)) : [],
|
||||
mentionsRequestId: isSet(object.mentionsRequestId) ? globalThis.String(object.mentionsRequestId) : undefined,
|
||||
}
|
||||
},
|
||||
|
||||
toJSON(message: FileSearchResults): unknown {
|
||||
const obj: any = {}
|
||||
if (message.results?.length) {
|
||||
obj.results = message.results.map((e) => FileInfo.toJSON(e))
|
||||
}
|
||||
if (message.mentionsRequestId !== undefined) {
|
||||
obj.mentionsRequestId = message.mentionsRequestId
|
||||
}
|
||||
return obj
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<FileSearchResults>, I>>(base?: I): FileSearchResults {
|
||||
return FileSearchResults.fromPartial(base ?? ({} as any))
|
||||
},
|
||||
fromPartial<I extends Exact<DeepPartial<FileSearchResults>, I>>(object: I): FileSearchResults {
|
||||
const message = createBaseFileSearchResults()
|
||||
message.results = object.results?.map((e) => FileInfo.fromPartial(e)) || []
|
||||
message.mentionsRequestId = object.mentionsRequestId ?? undefined
|
||||
return message
|
||||
},
|
||||
}
|
||||
|
||||
function createBaseFileInfo(): FileInfo {
|
||||
return { path: "", type: "", label: undefined }
|
||||
}
|
||||
|
||||
export const FileInfo: MessageFns<FileInfo> = {
|
||||
encode(message: FileInfo, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
if (message.path !== "") {
|
||||
writer.uint32(10).string(message.path)
|
||||
}
|
||||
if (message.type !== "") {
|
||||
writer.uint32(18).string(message.type)
|
||||
}
|
||||
if (message.label !== undefined) {
|
||||
writer.uint32(26).string(message.label)
|
||||
}
|
||||
return writer
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): FileInfo {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input)
|
||||
let end = length === undefined ? reader.len : reader.pos + length
|
||||
const message = createBaseFileInfo()
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32()
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break
|
||||
}
|
||||
|
||||
message.path = reader.string()
|
||||
continue
|
||||
}
|
||||
case 2: {
|
||||
if (tag !== 18) {
|
||||
break
|
||||
}
|
||||
|
||||
message.type = reader.string()
|
||||
continue
|
||||
}
|
||||
case 3: {
|
||||
if (tag !== 26) {
|
||||
break
|
||||
}
|
||||
|
||||
message.label = reader.string()
|
||||
continue
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break
|
||||
}
|
||||
reader.skip(tag & 7)
|
||||
}
|
||||
return message
|
||||
},
|
||||
|
||||
fromJSON(object: any): FileInfo {
|
||||
return {
|
||||
path: isSet(object.path) ? globalThis.String(object.path) : "",
|
||||
type: isSet(object.type) ? globalThis.String(object.type) : "",
|
||||
label: isSet(object.label) ? globalThis.String(object.label) : undefined,
|
||||
}
|
||||
},
|
||||
|
||||
toJSON(message: FileInfo): unknown {
|
||||
const obj: any = {}
|
||||
if (message.path !== "") {
|
||||
obj.path = message.path
|
||||
}
|
||||
if (message.type !== "") {
|
||||
obj.type = message.type
|
||||
}
|
||||
if (message.label !== undefined) {
|
||||
obj.label = message.label
|
||||
}
|
||||
return obj
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<FileInfo>, I>>(base?: I): FileInfo {
|
||||
return FileInfo.fromPartial(base ?? ({} as any))
|
||||
},
|
||||
fromPartial<I extends Exact<DeepPartial<FileInfo>, I>>(object: I): FileInfo {
|
||||
const message = createBaseFileInfo()
|
||||
message.path = object.path ?? ""
|
||||
message.type = object.type ?? ""
|
||||
message.label = object.label ?? undefined
|
||||
return message
|
||||
},
|
||||
}
|
||||
|
||||
function createBaseGitCommits(): GitCommits {
|
||||
return { commits: [] }
|
||||
}
|
||||
@ -636,6 +942,15 @@ export const FileServiceDefinition = {
|
||||
responseStream: false,
|
||||
options: {},
|
||||
},
|
||||
/** Search for files in the workspace with fuzzy matching */
|
||||
searchFiles: {
|
||||
name: "searchFiles",
|
||||
requestType: FileSearchRequest,
|
||||
requestStream: false,
|
||||
responseType: FileSearchResults,
|
||||
responseStream: false,
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
} as const
|
||||
|
||||
|
@ -686,11 +686,19 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
|
||||
// Set a timeout to debounce the search requests
|
||||
searchTimeoutRef.current = setTimeout(() => {
|
||||
vscode.postMessage({
|
||||
type: "searchFiles",
|
||||
FileServiceClient.searchFiles({
|
||||
query: query,
|
||||
mentionsRequestId: query,
|
||||
})
|
||||
.then((results) => {
|
||||
setFileSearchResults(results.results || [])
|
||||
setSearchLoading(false)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error searching files:", error)
|
||||
setFileSearchResults([])
|
||||
setSearchLoading(false)
|
||||
})
|
||||
}, 200) // 200ms debounce
|
||||
} else {
|
||||
setSelectedMenuIndex(3) // Set to "File" option by default
|
||||
|
Loading…
Reference in New Issue
Block a user