@@ -117,6 +117,25 @@ func (m *Model) cycleStringSectionFilter() {
117117
118118// recomputeStrings rebuilds stringsFiltered from the current filter text,
119119// matching on the string text and its owning section.
120+ // looksLikePath reports whether a string is a plausible filesystem path or URL:
121+ // it contains a path separator and alphanumerics, and no whitespace or control
122+ // characters. Deliberately inclusive (paths, URLs, "a/b") — the goal is to surface
123+ // every path-ish string, which the text filter can then narrow.
124+ func looksLikePath (b []byte ) bool {
125+ hasSep , hasAlnum := false , false
126+ for _ , c := range b {
127+ switch {
128+ case c == '/' || c == '\\' :
129+ hasSep = true
130+ case c <= ' ' , c == 0x7f :
131+ return false // whitespace/control → not a clean path
132+ case (c >= 'a' && c <= 'z' ) || (c >= 'A' && c <= 'Z' ) || (c >= '0' && c <= '9' ):
133+ hasAlnum = true
134+ }
135+ }
136+ return hasSep && hasAlnum
137+ }
138+
120139func (m * Model ) recomputeStrings () {
121140 m .clearStringCaches ()
122141 needle := strings .ToLower (m .stringsFilter .Value ())
@@ -127,7 +146,11 @@ func (m *Model) recomputeStrings() {
127146 }
128147 // Filter on the raw bytes (zero-copy) so scanning millions of strings on
129148 // each keystroke doesn't allocate a copy per entry.
130- if needle == "" || containsFoldBytes (m .file .StringBytes (s ), needle ) || containsFold (s .Section , needle ) {
149+ b := m .file .StringBytes (s )
150+ if m .stringsPathsOnly && ! looksLikePath (b ) {
151+ continue
152+ }
153+ if needle == "" || containsFoldBytes (b , needle ) || containsFold (s .Section , needle ) {
131154 m .stringsFiltered = append (m .stringsFiltered , i )
132155 }
133156 }
@@ -201,8 +224,9 @@ func (m *Model) updateStrings(key string) (tea.Model, tea.Cmd) {
201224 case "/" :
202225 m .stringsFilter .Focus ()
203226 case "esc" :
204- dirty := m .stringsSecOn || m .stringsFilter .Value () != "" || m .stringsFilter .Focused ()
227+ dirty := m .stringsSecOn || m .stringsPathsOnly || m . stringsFilter .Value () != "" || m .stringsFilter .Focused ()
205228 m .stringsSecOn = false
229+ m .stringsPathsOnly = false
206230 m .stringsFilter .SetValue ("" )
207231 m .stringsFilter .Blur ()
208232 m .stringsCur , m .stringsTop = 0 , 0
@@ -214,6 +238,15 @@ func (m *Model) updateStrings(key string) (tea.Model, tea.Cmd) {
214238 m .cycleStringSectionFilter ()
215239 m .stringsCur , m .stringsTop = 0 , 0
216240 m .recomputeStrings ()
241+ case "alt+p" :
242+ m .stringsPathsOnly = ! m .stringsPathsOnly
243+ m .stringsCur , m .stringsTop = 0 , 0
244+ m .recomputeStrings ()
245+ state := "off"
246+ if m .stringsPathsOnly {
247+ state = "on"
248+ }
249+ m .setStatus ("paths only: " + state , false )
217250 case "s" :
218251 m .stringsSort = (m .stringsSort + 1 ) % 3
219252 m .stringsCur , m .stringsTop = 0 , 0
@@ -290,8 +323,13 @@ func (m *Model) renderStrings() string {
290323 if m .stringsSortDesc {
291324 dir = "↓"
292325 }
326+ pathsLabel := "off"
327+ if m .stringsPathsOnly {
328+ pathsLabel = "on"
329+ }
293330 filterRow = m .theme .footerStyle .Render (fmt .Sprintf ("/ %s (%d / %d) " , m .stringsFilter .Value (), len (m .stringsFiltered ), len (m .stringsList ))) +
294331 m .theme .helpKeyStyle .Render (altKeys ("s" )) + m .theme .footerStyle .Render (" section:" + secLabel ) +
332+ m .theme .footerStyle .Render (" " ) + m .theme .helpKeyStyle .Render (altKeys ("p" )) + m .theme .footerStyle .Render (" paths:" + pathsLabel ) +
295333 m .theme .footerStyle .Render (" " ) + m .theme .helpKeyStyle .Render ("s" ) + m .theme .footerStyle .Render (" sort:" + m .stringsSort .String ()+ dir )
296334 }
297335
0 commit comments