aboutsummaryrefslogtreecommitdiff
path: root/internal/claude/search.go
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-01-30 07:32:54 +0000
committerFuwn <[email protected]>2026-01-30 07:32:54 +0000
commit5f3eba126201e4d679539aa2517bf6a132f29cd0 (patch)
tree961afe2ae1d6ca0f23bdbb30930e37bc88884146 /internal/claude/search.go
downloadfaustus-5f3eba126201e4d679539aa2517bf6a132f29cd0.tar.xz
faustus-5f3eba126201e4d679539aa2517bf6a132f29cd0.zip
feat: Initial commit
Diffstat (limited to 'internal/claude/search.go')
-rw-r--r--internal/claude/search.go180
1 files changed, 180 insertions, 0 deletions
diff --git a/internal/claude/search.go b/internal/claude/search.go
new file mode 100644
index 0000000..c79f871
--- /dev/null
+++ b/internal/claude/search.go
@@ -0,0 +1,180 @@
+package claude
+
+import (
+ "bufio"
+ "encoding/json"
+ "os"
+ "strings"
+)
+
+type SearchResult struct {
+ Session *Session
+ MessageIndex int
+ Role string
+ Content string
+ MatchPosition int
+}
+
+func SearchAllSessions(sessions []Session, query string) []SearchResult {
+ if query == "" {
+ return nil
+ }
+
+ query = strings.ToLower(query)
+
+ var results []SearchResult
+
+ for sessionIndex := range sessions {
+ session := &sessions[sessionIndex]
+ matches := searchSession(session, query)
+
+ results = append(results, matches...)
+ }
+
+ return results
+}
+
+func searchSession(session *Session, query string) []SearchResult {
+ file, openError := os.Open(session.FullPath)
+
+ if openError != nil {
+ return nil
+ }
+
+ defer func() { _ = file.Close() }()
+
+ var results []SearchResult
+
+ scanner := bufio.NewScanner(file)
+ scanBuffer := make([]byte, 0, 64*1024)
+
+ scanner.Buffer(scanBuffer, 10*1024*1024)
+
+ messageIndex := 0
+
+ for scanner.Scan() {
+ line := scanner.Text()
+
+ if line == "" {
+ continue
+ }
+
+ var rawMessage RawMessage
+
+ if unmarshalError := json.Unmarshal([]byte(line), &rawMessage); unmarshalError != nil {
+ continue
+ }
+
+ matches := searchRawMessage(session, &rawMessage, query, messageIndex)
+
+ results = append(results, matches...)
+
+ if rawMessage.Type == "user" || rawMessage.Type == "assistant" {
+ messageIndex += 1
+ }
+ }
+
+ return results
+}
+
+func searchRawMessage(session *Session, rawMessage *RawMessage, query string, messageIndex int) []SearchResult {
+ var results []SearchResult
+
+ switch rawMessage.Type {
+ case "user":
+ var userMessage UserMessage
+
+ if unmarshalError := json.Unmarshal(rawMessage.Message, &userMessage); unmarshalError != nil {
+ return nil
+ }
+
+ contentLowercase := strings.ToLower(userMessage.Content)
+
+ if matchPosition := strings.Index(contentLowercase, query); matchPosition != -1 {
+ content := matchContext(userMessage.Content, matchPosition, len(query))
+
+ results = append(results, SearchResult{
+ Session: session,
+ MessageIndex: messageIndex,
+ Role: "user",
+ Content: content,
+ MatchPosition: matchPosition,
+ })
+ }
+
+ case "assistant":
+ var assistantMessage AssistantMessage
+
+ if unmarshalError := json.Unmarshal(rawMessage.Message, &assistantMessage); unmarshalError != nil {
+ return nil
+ }
+
+ for _, contentBlock := range assistantMessage.Content {
+ if contentBlock.Type == "text" {
+ textLowercase := strings.ToLower(contentBlock.Text)
+
+ if matchPosition := strings.Index(textLowercase, query); matchPosition != -1 {
+ content := matchContext(contentBlock.Text, matchPosition, len(query))
+
+ results = append(results, SearchResult{
+ Session: session,
+ MessageIndex: messageIndex,
+ Role: "assistant",
+ Content: content,
+ MatchPosition: matchPosition,
+ })
+ }
+ }
+ }
+ }
+
+ return results
+}
+
+func matchContext(text string, matchPosition, matchLength int) string {
+ const contextLength = 100
+
+ start := matchPosition - contextLength/2
+
+ if start < 0 {
+ start = 0
+ }
+
+ end := matchPosition + matchLength + contextLength/2
+
+ if end > len(text) {
+ end = len(text)
+ }
+
+ result := text[start:end]
+ result = strings.ReplaceAll(result, "\n", " ")
+ result = strings.Join(strings.Fields(result), " ")
+
+ if start > 0 {
+ result = "… " + result
+ }
+
+ if end < len(text) {
+ result = result + " …"
+ }
+
+ return result
+}
+
+func SearchPreview(preview *PreviewContent, query string) []int {
+ if preview == nil || query == "" {
+ return nil
+ }
+
+ query = strings.ToLower(query)
+
+ var matches []int
+
+ for messageIndex, previewMessage := range preview.Messages {
+ if strings.Contains(strings.ToLower(previewMessage.Content), query) {
+ matches = append(matches, messageIndex)
+ }
+ }
+
+ return matches
+}