Skip to content
TwinScope0.3.10

Search and filter

One box in the toolbar, two behaviours. In a text diff it finds and hides nothing; in a JSON or folder comparison it filters and rows disappear. Which one you get is a property of the engine, and the box tells you.

Working with resultsEdit this page
GIF · press play
A find, not a filter. Every row stays; the count tells you where you are among the hits.

The box belongs to the chassis#

The search field lives in the workspace toolbar, next to Export, and it appears once a comparison has finished. It is owned by the frame rather than by any engine view — but only the view knows what searching means for its rows. So the frame owns the input and the query, and a view opts in.

⌘F focuses it from anywhere in the workspace. Its placeholder changes with the engine, which is the first hint about which behaviour you are getting.

EngineBehaviourPlaceholder
Text and codeFind — every row staysSearch in diff…
JSONFilter — non-matching rows disappearFilter by path or value…
FoldersFilter — non-matching rows disappear_Filter by name or _.ts…*
Images, binaryNot applicableThe box is disabled

In a text diff: a find#

Nothing is hidden. You get a match count, keys to walk the hits, and a key to clear.

KeyAction
⌘FFocus the box
Next hit
⇧⏎Previous hit
EscClear the query and hand focus back to the diff

An n/m badge replaces the ⌘F hint as soon as there is something to count, and it reads for the position until you take your first step. Walking wraps at both ends, exactly like change navigation — and for the same reason: the store owns which match is current, so the badge, the keys and the scroll position are all reading one number.

Hits are counted over what is actually painted, in reading order. On a modified line that means the before side's hits, then the after side's.

In JSON and folders: a filter#

Rows disappear. This is the right behaviour for a tree, where the thing you are looking for is a path, and being shown three hundred sibling rows you did not ask about is the problem rather than the point.

  • JSON matches case-insensitively against the key, the JSONPath, the values on both sides and any note on the row. Ancestors of a match always survive, so a hit is never orphaned from its path.
  • Folders treat anything containing * or ? as a shell glob against the file name, and anything else as a case-insensitive substring. A directory survives whenever any of its children does.

Because rows leave, the change stepper's count follows them: ‹ n/m › counts what the filter left. A filtering view registers no match total, so there is no n/m badge in the box — the count that matters is in the strip.

Marks, hits and syntax tokens, resolved together#

Three systems want to colour the same characters: the engine's changed-word marks, your search hits, and syntax highlighting. They are resolved in one pass, never layered.

That is what makes both of these work:

Search: timeout — 2 / 7
42 timeout: 3000,
42 timeout: 10000,
44 connectTimeout: 500,
  • A hit inside a changed word keeps both highlights. Searching 300 inside ⟦3000⟧ shows you that it is a hit and that the word changed. Layering one treatment over the other would lose whichever went underneath.
  • A hit spanning a mark boundary is still found. Searching timeout matches a changed word the engine emitted as ⟦time⟧out, because matching runs against the text with the markers stripped and the ranges are re-zipped afterwards.