Skip to content

Commit 44c66fd

Browse files
committed
exex: strings view: paths-only filter
Signed-off-by: Polina Simonenko <rabarbrablad@gmail.com>
1 parent 5946639 commit 44c66fd

3 files changed

Lines changed: 43 additions & 3 deletions

File tree

internal/ui/chrome.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ func (m *Model) renderHelpModal() string {
162162
blank,
163163
head("Strings"),
164164
row(altKeys("s"), "filter by section"),
165+
row(altKeys("p"), "filter to paths only"),
165166
row("t / ⇥", "table ↔ compact (· flow) layout"),
166167
}
167168

@@ -522,7 +523,7 @@ func (m *Model) viewHints() []footerHint {
522523
case modeRaw:
523524
return []footerHint{{"↵", "follow ptr"}, {"d", "disasm"}, {"[ ]", "section"}, {"t/⇧t", "ascii·interp"}, {"i", "inspect"}, {"/", "search"}, {"⇧a/⇧s/⇧p", "copy"}}
524525
case modeStrings:
525-
return []footerHint{{"↵", "jump"}, {"d/h/m", "go to"}, {"s/r", "sort/rev"}, {"t", "table/flow"}, {"/", "filter"}, {altKeys("s"), "section"}, {"⇧a/⇧s", "copy"}}
526+
return []footerHint{{"↵", "jump"}, {"d/h/m", "go to"}, {"s/r", "sort/rev"}, {"t", "table/flow"}, {"/", "filter"}, {altKeys("s"), "section"}, {altKeys("p"), "paths"}, {"⇧a/⇧s", "copy"}}
526527
case modeSources:
527528
if m.sourcesTree {
528529
return []footerHint{{"←/→", "fold/unfold"}, {"↵", "open/all below"}, {altKeys("a"), "present"}, {"t", "flat"}}

internal/ui/model.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ type stringsState struct {
308308
stringsSort stringSort // sort field for the (filtered) list
309309
stringsSortDesc bool // reverse the active sort
310310
stringsCompact bool // flow strings inline (· separated, no columns) vs the table
311+
stringsPathsOnly bool // show only path-like strings (filesystem paths / URLs)
311312
stringRowCache map[rowCacheKey]string
312313
stringHeightCache map[rowCacheKey]int
313314
}

internal/ui/view_strings.go

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
120139
func (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

Comments
 (0)