From 388da313c74935b4e2a876f1d71a5c64636866b7 Mon Sep 17 00:00:00 2001 From: AfeefRazick Date: Thu, 2 Apr 2026 19:00:24 +0530 Subject: [PATCH] Add refresh-aware search command Expose a top-level search command that reuses the local index and can refresh it on demand before searching. --- README.md | 2 + internal/cmd/experimental.go | 82 +++++++++++++++++++++++++++--------- internal/cmd/root.go | 1 + 3 files changed, 65 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 052911e..3e6b9c2 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,8 @@ coda wait ### Experimental local search ```sh +coda search "quarterly plan" +coda search "status.*blocked" --ignore-case --refresh coda experimental index coda experimental index --force --json coda experimental grep "quarterly plan" diff --git a/internal/cmd/experimental.go b/internal/cmd/experimental.go index de01428..76e33bc 100644 --- a/internal/cmd/experimental.go +++ b/internal/cmd/experimental.go @@ -164,34 +164,76 @@ func newExperimentalCommand() *cobra.Command { if err != nil { return err } - jsonOut, _ := cmd.Flags().GetBool("json") - if jsonOut { - return printJSONMarshal(map[string]any{"matches": matches, "count": len(matches)}) - } - for _, match := range matches { - location := match.DocName + " / " + match.PageName - if match.ParentName != "" { - location += " (parent: " + match.ParentName + ")" + return printExperimentalGrepMatches(cmd, matches) + }, + } + addExperimentalSearchFlags(grepCmd) + cmd.AddCommand(grepCmd) + + return cmd +} + +func newSearchCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "search ", + Short: "Search the local workspace index", + Long: "Search the local workspace index built from Coda docs and pages. Use --refresh to update the index before searching.", + Args: exactArgsFor("coda search ", 1), + RunE: func(cmd *cobra.Command, args []string) error { + refresh, _ := cmd.Flags().GetBool("refresh") + if refresh { + client, _, err := api.NewClient() + if err != nil { + return err + } + workers, _ := cmd.Flags().GetInt("workers") + force, _ := cmd.Flags().GetBool("force-refresh") + if _, err := runExperimentalIndex(cmd.Context(), client, workers, force); err != nil { + return err } - fmt.Printf("%s:%d:%s\n", location, match.Line, match.Text) } - if len(matches) == 0 { - fmt.Println("No matches found.") + + matches, err := runExperimentalGrep(cmd, args[0]) + if err != nil { + return err } - return nil + return printExperimentalGrepMatches(cmd, matches) }, } - grepCmd.Flags().Bool("ignore-case", false, "Match case-insensitively") - grepCmd.Flags().Bool("fixed-strings", false, "Treat the pattern as a literal string instead of a regular expression") - grepCmd.Flags().String("doc", "", "Only search docs whose name or ID contains this value") - grepCmd.Flags().String("page", "", "Only search pages whose name or ID contains this value") - grepCmd.Flags().Int("limit", 100, "Maximum number of matches to print") - grepCmd.Flags().Bool("json", false, "Print matches as JSON") - cmd.AddCommand(grepCmd) - + addExperimentalSearchFlags(cmd) + cmd.Flags().Bool("refresh", false, "Refresh the local index before searching") + cmd.Flags().Int("workers", 6, "Number of concurrent page-content fetches when --refresh is used") + cmd.Flags().Bool("force-refresh", false, "Re-fetch all docs when --refresh is used") return cmd } +func addExperimentalSearchFlags(cmd *cobra.Command) { + cmd.Flags().Bool("ignore-case", false, "Match case-insensitively") + cmd.Flags().Bool("fixed-strings", false, "Treat the pattern as a literal string instead of a regular expression") + cmd.Flags().String("doc", "", "Only search docs whose name or ID contains this value") + cmd.Flags().String("page", "", "Only search pages whose name or ID contains this value") + cmd.Flags().Int("limit", 100, "Maximum number of matches to print") + cmd.Flags().Bool("json", false, "Print matches as JSON") +} + +func printExperimentalGrepMatches(cmd *cobra.Command, matches []experimentalGrepMatch) error { + jsonOut, _ := cmd.Flags().GetBool("json") + if jsonOut { + return printJSONMarshal(map[string]any{"matches": matches, "count": len(matches)}) + } + for _, match := range matches { + location := match.DocName + " / " + match.PageName + if match.ParentName != "" { + location += " (parent: " + match.ParentName + ")" + } + fmt.Printf("%s:%d:%s\n", location, match.Line, match.Text) + } + if len(matches) == 0 { + fmt.Println("No matches found.") + } + return nil +} + type experimentalIndexResult struct { IndexDir string `json:"indexDir"` State experimentalIndexState `json:"state"` diff --git a/internal/cmd/root.go b/internal/cmd/root.go index 9dc22c7..e0c23ea 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -18,6 +18,7 @@ func NewRootCommand() *cobra.Command { cmd.AddCommand(newTablesCommand()) cmd.AddCommand(newColumnsCommand()) cmd.AddCommand(newRowsCommand()) + cmd.AddCommand(newSearchCommand()) cmd.AddCommand(newExperimentalCommand()) return cmd