@@ -272,6 +272,92 @@ func getTopSections(sections []*parser.Section, maxDepth int) []*parser.Section
272272 return result
273273}
274274
275+ // SearchResult holds a matched section with its file context
276+ type SearchResult struct {
277+ Filename string
278+ Path string // e.g. "endpoints > Ban Member"
279+ Section * parser.Section
280+ }
281+
282+ // SearchResults searches all docs for sections matching the query and renders results
283+ func SearchResults (docs []* parser.Document , query string ) {
284+ query = strings .ToLower (query )
285+ var results []SearchResult
286+
287+ for _ , doc := range docs {
288+ searchSections (doc .Filename , doc .Sections , "" , query , & results )
289+ }
290+
291+ if len (results ) == 0 {
292+ fmt .Printf ("No sections matching '%s'\n " , query )
293+ return
294+ }
295+
296+ // Header
297+ fmt .Printf ("%s%d matches for '%s'%s\n \n " , bold , len (results ), query , reset )
298+
299+ for i , r := range results {
300+ isLast := i == len (results )- 1
301+ connector := "├── "
302+ if isLast {
303+ connector = "└── "
304+ }
305+
306+ tokenStr := dim + fmt .Sprintf ("(%s)" , formatTokens (r .Section .Tokens )) + reset
307+ filePart := dim + r .Filename + " > " + reset
308+ if r .Path != "" {
309+ filePart = dim + r .Filename + " > " + r .Path + " > " + reset
310+ }
311+ fmt .Printf ("%s%s%s%s%s%s %s\n " , dim , connector , reset , filePart , bold + cyan + r .Section .Title + reset , "" , tokenStr )
312+
313+ // Show children summary if present
314+ if len (r .Section .Children ) > 0 {
315+ childPrefix := "│ "
316+ if isLast {
317+ childPrefix = " "
318+ }
319+ for j , child := range r .Section .Children {
320+ if j >= 5 {
321+ fmt .Printf ("%s%s... %d more%s\n " , childPrefix , dim , len (r .Section .Children )- 5 , reset )
322+ break
323+ }
324+ childIsLast := j == len (r .Section .Children )- 1 || j == 4
325+ childConn := "├─ "
326+ if childIsLast {
327+ childConn = "└─ "
328+ }
329+ fmt .Printf ("%s%s%s%s %s\n " , childPrefix , dim , childConn , child .Title + reset , dim + fmt .Sprintf ("(%s)" , formatTokens (child .Tokens ))+ reset )
330+ }
331+ }
332+ }
333+
334+ fmt .Println ()
335+ }
336+
337+ func searchSections (filename string , sections []* parser.Section , parentPath string , query string , results * []SearchResult ) {
338+ for _ , s := range sections {
339+ titleMatch := strings .Contains (strings .ToLower (s .Title ), query )
340+ contentMatch := strings .Contains (strings .ToLower (s .Content ), query )
341+
342+ if titleMatch || contentMatch {
343+ * results = append (* results , SearchResult {
344+ Filename : filename ,
345+ Path : parentPath ,
346+ Section : s ,
347+ })
348+ }
349+
350+ // Search children
351+ childPath := parentPath
352+ if childPath != "" {
353+ childPath += " > " + s .Title
354+ } else {
355+ childPath = s .Title
356+ }
357+ searchSections (filename , s .Children , childPath , query , results )
358+ }
359+ }
360+
275361// RefsTree renders document references (links to other .md files)
276362func RefsTree (docs []* parser.Document , dirName string ) {
277363 // Build reference graph
0 commit comments