Search wrap behavior (#11449)

Search wrapping has been highly requested.

some examples
https://github.com/ghostty-org/ghostty/discussions/11080
https://github.com/ghostty-org/ghostty/discussions/11440
https://github.com/ghostty-org/ghostty/discussions/11441
https://github.com/ghostty-org/ghostty/discussions/9762
https://github.com/ghostty-org/ghostty/discussions/9790

I also think it makes sense as its the default behavior in browsers (and
I assume other apps)

I tested where nothing is outputting and a loop where active was going
into history not anything where pages would start to get reused though

the following comment seems to me it should be safe to have wrap around
behavior but maybe there was something else I missed about the active +
history buffer on why that isn't true, testing basic cases it worked
just fine for me


https://github.com/ghostty-org/ghostty/blob/main/src/terminal/highlight.zig#L107-L111
This commit is contained in:
Mitchell Hashimoto
2026-03-13 10:16:47 -07:00
committed by GitHub

View File

@@ -740,13 +740,9 @@ pub const ScreenSearch = struct {
return true;
};
const next_idx = prev.idx + 1;
const active_len = self.active_results.items.len;
const history_len = self.history_results.items.len;
if (next_idx >= active_len + history_len) {
// No more matches. We don't wrap or reset the match currently.
return false;
}
const next_idx = if (prev.idx + 1 >= active_len + history_len) 0 else prev.idx + 1;
const hl: FlattenedHighlight = if (next_idx < active_len)
self.active_results.items[active_len - 1 - next_idx]
else
@@ -800,14 +796,10 @@ pub const ScreenSearch = struct {
return true;
};
// Can't go below zero
if (prev.idx == 0) {
// No more matches. We don't wrap or reset the match currently.
return false;
}
const next_idx = prev.idx - 1;
const active_len = self.active_results.items.len;
const history_len = self.history_results.items.len;
const next_idx = if (prev.idx != 0) prev.idx - 1 else active_len - 1 + history_len;
const hl: FlattenedHighlight = if (next_idx < active_len)
self.active_results.items[active_len - 1 - next_idx]
else
@@ -1083,17 +1075,17 @@ test "select next" {
} }, t.screens.active.pages.pointFromPin(.screen, sel.end).?);
}
// Next match (no wrap)
// Next match (wrap)
_ = try search.select(.next);
{
const sel = search.selectedMatch().?.untracked();
try testing.expectEqual(point.Point{ .screen = .{
.x = 0,
.y = 0,
.y = 2,
} }, t.screens.active.pages.pointFromPin(.screen, sel.start).?);
try testing.expectEqual(point.Point{ .screen = .{
.x = 3,
.y = 0,
.y = 2,
} }, t.screens.active.pages.pointFromPin(.screen, sel.end).?);
}
}
@@ -1278,17 +1270,17 @@ test "select prev" {
} }, t.screens.active.pages.pointFromPin(.screen, sel.end).?);
}
// Prev match (no wrap, stays at newest)
// Prev match (wrap)
_ = try search.select(.prev);
{
const sel = search.selectedMatch().?.untracked();
try testing.expectEqual(point.Point{ .screen = .{
.x = 0,
.y = 2,
.y = 0,
} }, t.screens.active.pages.pointFromPin(.screen, sel.start).?);
try testing.expectEqual(point.Point{ .screen = .{
.x = 3,
.y = 2,
.y = 0,
} }, t.screens.active.pages.pointFromPin(.screen, sel.end).?);
}
}