Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds new search “tweaks” across interfaces: deduplication of byte-identical matches, declaration-vs-usage filtering, and a TUI file viewer, alongside some UI rendering adjustments.
Changes:
- Add
--dedupsupport and plumb duplicate metadata (count/locations) through console/HTTP/TUI/MCP outputs. - Add
--only-declarations/--only-usagesfiltering via match-location classification heuristics. - Add a TUI file viewer overlay (keyboard/mouse navigation) and adjust selection styling / line-gap rendering.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tui.go | Adds viewer overlay, mouse interactions, dedup display, line-gap rendering, and extended filter cycling. |
| syntax.go | Removes background from “selected” syntax styles. |
| search.go | Applies declaration/usage filtering during search pipeline and adjusts content-type filtering condition. |
| pkg/ranker/dedup.go | Implements match-span hashing and result deduplication logic. |
| pkg/ranker/dedup_test.go | Adds tests for deduplication and hash behavior. |
| pkg/ranker/declarations.go | Adds declaration-pattern heuristics and match-location classification. |
| pkg/ranker/declarations_test.go | Adds tests for declaration detection and classification. |
| pkg/common/common.go | Extends FileJob with dedup-related fields (MatchHash, duplicate metadata). |
| mcp.go | Documents and parses new filters and dedup argument; applies dedup post-ranking. |
| main.go | Adds new flags, enforces mutual exclusivity, enables mouse support for TUI. |
| http.go | Adds gap rendering for line mode and includes duplicate metadata in JSON; adds request-time dedup and new filter parsing. |
| console.go | Applies dedup before limiting; prints duplicate metadata; adds gap lines in line output; extends JSON output schema. |
| config.go | Adds config flags for new filters and dedup; updates HasContentFilter and cache prefix. |
| asset/templates/*/search.html | Adds visual gaps between non-contiguous line results (template + JS rendering). |
| README.md | Updates docs for OR/grouping, dedup flag, and adds a section about declaration detection. |
Comments suppressed due to low confidence (6)
tui.go:1081
openViewerparsesr.LineRangewithfmt.Sscanfbut ignores the return values and always doesstartLine--. If parsing fails,startLineremains 0 and becomes -1, which is an unexpected sentinel and can leak intoviewStartLine. Consider only decrementing when the scan succeeded andstartLine > 0(or defaulting to 0 when parsing fails).
// Parse start line from LineRange (e.g. "42-58")
startLine := 0
if r.LineRange != "" {
fmt.Sscanf(r.LineRange, "%d", &startLine)
startLine-- // convert to 0-based
}
tui.go:1067
openViewerreads the entire file withos.ReadFile, ignoring the existingMaxReadSizeByteslimit used by the search pipeline. Opening a very large matched file could cause a big memory spike or stall the TUI. Consider reusing the same size-limited read helper (or adding a viewer-specific cap / streaming approach).
func (m *model) openViewer(r searchResult) error {
data, err := os.ReadFile(r.Location)
if err != nil {
return err
}
lines := strings.Split(string(data), "\n")
README.md:75
- This README section claims the structural ranker "uses declaration detection to boost matches that appear on declaration lines". In the current changes, declaration detection is used for filtering (
--only-declarations/--only-usages) in the search pipeline, but there's no ranker-side boost implemented. Please adjust the documentation to match the behavior (or implement the described boosting).
The structural ranker also uses declaration detection to boost matches that appear on declaration lines
(e.g. `func`, `class`, `def`) over plain usages. This currently works for the following languages:
Go, Python, JavaScript, TypeScript, TSX, Rust, Java, C, C++, C#, Ruby, PHP, Kotlin, Swift
For unsupported languages, all matches are treated as usages and ranked by text relevance only.
Structural filtering (`--only-code`, `--only-comments`, `--only-strings`) still works for any language recognised
by [scc](https://github.com/boyter/scc).
http.go:241
- In the
codeFilterswitch, theonly-declarations/only-usagescases (and thedefaultcase) don't clear the other mutually-exclusive filter flags. BecausesearchCfgis copied from the server config, a request can end up with multiple filters enabled (e.g. server started with--only-codeand request setscf=only-declarations), which also affectsHasContentFilter()and the search cache key prefix. Consider resetting allOnly*filter booleans tofalsebefore applying the requested filter (or explicitly clearing the others in every case).
switch codeFilter {
case "only-code":
searchCfg.OnlyCode = true
searchCfg.OnlyComments = false
searchCfg.OnlyStrings = false
case "only-comments":
searchCfg.OnlyCode = false
searchCfg.OnlyComments = true
searchCfg.OnlyStrings = false
case "only-strings":
searchCfg.OnlyCode = false
searchCfg.OnlyComments = false
searchCfg.OnlyStrings = true
case "only-declarations":
searchCfg.OnlyDeclarations = true
case "only-usages":
searchCfg.OnlyUsages = true
default:
searchCfg.OnlyCode = false
searchCfg.OnlyComments = false
searchCfg.OnlyStrings = false
}
mcp.go:327
- The MCP
code_filterargument handler sets a specificOnly*flag but never clears the other mutually-exclusive filter flags inherited fromcfg. This can lead to multiple filters being active at once (despite the docs stating they're mutually exclusive), and can also skew cache keys and ranking auto-selection viaHasContentFilter(). Reset allOnly*flags tofalsebefore setting the selected one.
if v, ok := request.GetArguments()["code_filter"]; ok {
if s, ok := v.(string); ok && s != "" {
switch s {
case "only-code":
searchCfg.OnlyCode = true
case "only-comments":
searchCfg.OnlyComments = true
case "only-strings":
searchCfg.OnlyStrings = true
case "only-declarations":
searchCfg.OnlyDeclarations = true
case "only-usages":
searchCfg.OnlyUsages = true
}
if searchCfg.HasContentFilter() {
searchCfg.Ranker = "structural"
}
}
tui.go:450
openViewerreturns an error, but the caller ignores it. If the file can't be read (deleted, permission error, etc.), the UI will silently do nothing. Consider capturing the error and surfacing it in the TUI (e.g., via a status line message) or at least logging it.
case tea.KeyF5, tea.KeyCtrlO, tea.KeyCtrlP:
if len(m.results) > 0 && m.selectedIndex < len(m.results) {
m.openViewer(m.results[m.selectedIndex])
}
return m, nil
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // cycleCodeFilter cycles: default → only-code → only-comments → only-strings → only-declarations → only-usages → default… | ||
| // Auto-switches ranker to "structural" when a filter is active. | ||
| func (m *model) cycleCodeFilter() { | ||
| switch { | ||
| case !m.cfg.OnlyCode && !m.cfg.OnlyComments && !m.cfg.OnlyStrings: | ||
| case !m.cfg.OnlyCode && !m.cfg.OnlyComments && !m.cfg.OnlyStrings && !m.cfg.OnlyDeclarations && !m.cfg.OnlyUsages: | ||
| m.cfg.OnlyCode = true | ||
| m.cfg.OnlyComments = false | ||
| m.cfg.OnlyStrings = false | ||
| case m.cfg.OnlyCode: | ||
| m.cfg.OnlyCode = false | ||
| m.cfg.OnlyComments = true | ||
| m.cfg.OnlyStrings = false | ||
| case m.cfg.OnlyComments: | ||
| m.cfg.OnlyCode = false | ||
| m.cfg.OnlyComments = false | ||
| m.cfg.OnlyStrings = true | ||
| default: | ||
| m.cfg.OnlyCode = false | ||
| m.cfg.OnlyComments = false | ||
| case m.cfg.OnlyStrings: | ||
| m.cfg.OnlyStrings = false | ||
| m.cfg.OnlyDeclarations = true | ||
| case m.cfg.OnlyDeclarations: | ||
| m.cfg.OnlyDeclarations = false | ||
| m.cfg.OnlyUsages = true | ||
| case m.cfg.OnlyUsages: | ||
| m.cfg.OnlyUsages = false | ||
| } |
There was a problem hiding this comment.
cycleCodeFilter now cycles through only-declarations and only-usages, but existing TUI unit tests (e.g. TestCycleCodeFilter in tui_test.go) still assert the old 4-step cycle ending at only-strings → default. As-is, this change will break the test suite; update the tests to reflect the new cycle order.
No description provided.