search: thread dispatches selection notices, messages
parent
a2a771bb6f
commit
333dd08c97
|
|
@ -1376,6 +1376,7 @@ fn searchCallback_(
|
|||
},
|
||||
|
||||
// Unhandled, so far.
|
||||
.selected_match,
|
||||
.total_matches,
|
||||
.complete,
|
||||
=> {},
|
||||
|
|
|
|||
|
|
@ -43,6 +43,10 @@ pub const Untracked = struct {
|
|||
self.end,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn eql(self: Untracked, other: Untracked) bool {
|
||||
return self.start.eql(other.start) and self.end.eql(other.end);
|
||||
}
|
||||
};
|
||||
|
||||
/// A tracked highlight is a highlight that stores its highlighted
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ const internal_os = @import("../../os/main.zig");
|
|||
const BlockingQueue = @import("../../datastruct/main.zig").BlockingQueue;
|
||||
const point = @import("../point.zig");
|
||||
const FlattenedHighlight = @import("../highlight.zig").Flattened;
|
||||
const UntrackedHighlight = @import("../highlight.zig").Untracked;
|
||||
const PageList = @import("../PageList.zig");
|
||||
const Screen = @import("../Screen.zig");
|
||||
const ScreenSet = @import("../ScreenSet.zig");
|
||||
|
|
@ -242,10 +243,23 @@ fn drainMailbox(self: *Thread) !void {
|
|||
log.debug("mailbox message={}", .{message});
|
||||
switch (message) {
|
||||
.change_needle => |v| try self.changeNeedle(v),
|
||||
.select => |v| try self.select(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn select(self: *Thread, sel: ScreenSearch.Select) !void {
|
||||
const s = if (self.search) |*s| s else return;
|
||||
const screen_search = s.screens.getPtr(s.last_screen.key) orelse return;
|
||||
|
||||
self.opts.mutex.lock();
|
||||
defer self.opts.mutex.unlock();
|
||||
|
||||
// The selection will trigger a selection change notification
|
||||
// if it did change.
|
||||
try screen_search.select(sel);
|
||||
}
|
||||
|
||||
/// Change the search term to the given value.
|
||||
fn changeNeedle(self: *Thread, needle: []const u8) !void {
|
||||
log.debug("changing search needle to '{s}'", .{needle});
|
||||
|
|
@ -395,6 +409,9 @@ pub const Message = union(enum) {
|
|||
/// will start a search. If an existing search term is given this will
|
||||
/// stop the prior search and start a new one.
|
||||
change_needle: []const u8,
|
||||
|
||||
/// Select a search result.
|
||||
select: ScreenSearch.Select,
|
||||
};
|
||||
|
||||
/// Events that can be emitted from the search thread. The caller
|
||||
|
|
@ -409,9 +426,17 @@ pub const Event = union(enum) {
|
|||
/// Total matches on the current active screen have changed.
|
||||
total_matches: usize,
|
||||
|
||||
/// Selected match changed.
|
||||
selected_match: ?SelectedMatch,
|
||||
|
||||
/// Matches in the viewport have changed. The memory is owned by the
|
||||
/// search thread and is only valid during the callback.
|
||||
viewport_matches: []const FlattenedHighlight,
|
||||
|
||||
pub const SelectedMatch = struct {
|
||||
idx: usize,
|
||||
highlight: FlattenedHighlight,
|
||||
};
|
||||
};
|
||||
|
||||
/// Search state.
|
||||
|
|
@ -422,11 +447,9 @@ const Search = struct {
|
|||
/// The searchers for all the screens.
|
||||
screens: std.EnumMap(ScreenSet.Key, ScreenSearch),
|
||||
|
||||
/// The last active screen
|
||||
last_active_screen: ScreenSet.Key,
|
||||
|
||||
/// The last total matches reported.
|
||||
last_total: ?usize,
|
||||
/// All state related to screen switches, collected so that when
|
||||
/// we switch screens it makes everything related stale, too.
|
||||
last_screen: ScreenState,
|
||||
|
||||
/// True if we sent the complete notification yet.
|
||||
last_complete: bool,
|
||||
|
|
@ -434,6 +457,22 @@ const Search = struct {
|
|||
/// The last viewport matches we found.
|
||||
stale_viewport_matches: bool,
|
||||
|
||||
const ScreenState = struct {
|
||||
/// Last active screen key
|
||||
key: ScreenSet.Key,
|
||||
|
||||
/// Last notified total matches count
|
||||
total: ?usize = null,
|
||||
|
||||
/// Last notified selected match index
|
||||
selected: ?SelectedMatch = null,
|
||||
|
||||
const SelectedMatch = struct {
|
||||
idx: usize,
|
||||
highlight: UntrackedHighlight,
|
||||
};
|
||||
};
|
||||
|
||||
pub fn init(
|
||||
alloc: Allocator,
|
||||
needle: []const u8,
|
||||
|
|
@ -448,8 +487,7 @@ const Search = struct {
|
|||
return .{
|
||||
.viewport = vp,
|
||||
.screens = .init(.{}),
|
||||
.last_active_screen = .primary,
|
||||
.last_total = null,
|
||||
.last_screen = .{ .key = .primary },
|
||||
.last_complete = false,
|
||||
.stale_viewport_matches = true,
|
||||
};
|
||||
|
|
@ -528,9 +566,10 @@ const Search = struct {
|
|||
t: *Terminal,
|
||||
) void {
|
||||
// Update our active screen
|
||||
if (t.screens.active_key != self.last_active_screen) {
|
||||
self.last_active_screen = t.screens.active_key;
|
||||
self.last_total = null; // force notification
|
||||
if (t.screens.active_key != self.last_screen.key) {
|
||||
// The default values will force resets of a bunch of other
|
||||
// state too to force recalculations and notifications.
|
||||
self.last_screen = .{ .key = t.screens.active_key };
|
||||
}
|
||||
|
||||
// Reconcile our screens with the terminal screens. Remove
|
||||
|
|
@ -621,13 +660,13 @@ const Search = struct {
|
|||
cb: EventCallback,
|
||||
ud: ?*anyopaque,
|
||||
) void {
|
||||
const screen_search = self.screens.get(self.last_active_screen) orelse return;
|
||||
const screen_search = self.screens.get(self.last_screen.key) orelse return;
|
||||
|
||||
// Check our total match data
|
||||
const total = screen_search.matchesLen();
|
||||
if (total != self.last_total) {
|
||||
if (total != self.last_screen.total) {
|
||||
log.debug("notifying total matches={}", .{total});
|
||||
self.last_total = total;
|
||||
self.last_screen.total = total;
|
||||
cb(.{ .total_matches = total }, ud);
|
||||
}
|
||||
|
||||
|
|
@ -666,6 +705,40 @@ const Search = struct {
|
|||
cb(.{ .viewport_matches = results.items }, ud);
|
||||
}
|
||||
|
||||
// Check our last selected match data.
|
||||
if (screen_search.selected) |m| match: {
|
||||
const flattened = screen_search.selectedMatch() orelse break :match;
|
||||
const untracked = flattened.untracked();
|
||||
if (self.last_screen.selected) |prev| {
|
||||
if (prev.idx == m.idx and prev.highlight.eql(untracked)) {
|
||||
// Same selection, don't update it.
|
||||
break :match;
|
||||
}
|
||||
}
|
||||
|
||||
// New selection, notify!
|
||||
self.last_screen.selected = .{
|
||||
.idx = m.idx,
|
||||
.highlight = untracked,
|
||||
};
|
||||
|
||||
log.debug("notifying selection updated idx={}", .{m.idx});
|
||||
cb(
|
||||
.{ .selected_match = .{
|
||||
.idx = m.idx,
|
||||
.highlight = flattened,
|
||||
} },
|
||||
ud,
|
||||
);
|
||||
} else if (self.last_screen.selected != null) {
|
||||
log.debug("notifying selection cleared", .{});
|
||||
self.last_screen.selected = null;
|
||||
cb(
|
||||
.{ .selected_match = null },
|
||||
ud,
|
||||
);
|
||||
}
|
||||
|
||||
// Send our complete notification if we just completed.
|
||||
if (!self.last_complete and self.isComplete()) {
|
||||
log.debug("notifying search complete", .{});
|
||||
|
|
@ -675,40 +748,42 @@ const Search = struct {
|
|||
}
|
||||
};
|
||||
|
||||
const TestUserData = struct {
|
||||
const Self = @This();
|
||||
reset: std.Thread.ResetEvent = .{},
|
||||
total: usize = 0,
|
||||
selected: ?Event.SelectedMatch = null,
|
||||
viewport: []FlattenedHighlight = &.{},
|
||||
|
||||
fn deinit(self: *Self) void {
|
||||
for (self.viewport) |*hl| hl.deinit(testing.allocator);
|
||||
testing.allocator.free(self.viewport);
|
||||
}
|
||||
|
||||
fn callback(event: Event, userdata: ?*anyopaque) void {
|
||||
const ud: *Self = @ptrCast(@alignCast(userdata.?));
|
||||
switch (event) {
|
||||
.quit => {},
|
||||
.complete => ud.reset.set(),
|
||||
.total_matches => |v| ud.total = v,
|
||||
.selected_match => |v| ud.selected = v,
|
||||
.viewport_matches => |v| {
|
||||
for (ud.viewport) |*hl| hl.deinit(testing.allocator);
|
||||
testing.allocator.free(ud.viewport);
|
||||
|
||||
ud.viewport = testing.allocator.alloc(
|
||||
FlattenedHighlight,
|
||||
v.len,
|
||||
) catch unreachable;
|
||||
for (ud.viewport, v) |*dst, src| {
|
||||
dst.* = src.clone(testing.allocator) catch unreachable;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
test {
|
||||
const UserData = struct {
|
||||
const Self = @This();
|
||||
reset: std.Thread.ResetEvent = .{},
|
||||
total: usize = 0,
|
||||
viewport: []FlattenedHighlight = &.{},
|
||||
|
||||
fn deinit(self: *Self) void {
|
||||
for (self.viewport) |*hl| hl.deinit(testing.allocator);
|
||||
testing.allocator.free(self.viewport);
|
||||
}
|
||||
|
||||
fn callback(event: Event, userdata: ?*anyopaque) void {
|
||||
const ud: *Self = @ptrCast(@alignCast(userdata.?));
|
||||
switch (event) {
|
||||
.quit => {},
|
||||
.complete => ud.reset.set(),
|
||||
.total_matches => |v| ud.total = v,
|
||||
.viewport_matches => |v| {
|
||||
for (ud.viewport) |*hl| hl.deinit(testing.allocator);
|
||||
testing.allocator.free(ud.viewport);
|
||||
|
||||
ud.viewport = testing.allocator.alloc(
|
||||
FlattenedHighlight,
|
||||
v.len,
|
||||
) catch unreachable;
|
||||
for (ud.viewport, v) |*dst, src| {
|
||||
dst.* = src.clone(testing.allocator) catch unreachable;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var mutex: std.Thread.Mutex = .{};
|
||||
var t: Terminal = try .init(alloc, .{ .cols = 20, .rows = 2 });
|
||||
|
|
@ -718,12 +793,12 @@ test {
|
|||
defer stream.deinit();
|
||||
try stream.nextSlice("Hello, world");
|
||||
|
||||
var ud: UserData = .{};
|
||||
var ud: TestUserData = .{};
|
||||
defer ud.deinit();
|
||||
var thread: Thread = try .init(alloc, .{
|
||||
.mutex = &mutex,
|
||||
.terminal = &t,
|
||||
.event_cb = &UserData.callback,
|
||||
.event_cb = &TestUserData.callback,
|
||||
.event_userdata = &ud,
|
||||
});
|
||||
defer thread.deinit();
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ pub const ScreenSearch = struct {
|
|||
history_results: std.ArrayList(FlattenedHighlight),
|
||||
active_results: std.ArrayList(FlattenedHighlight),
|
||||
|
||||
const SelectedMatch = struct {
|
||||
pub const SelectedMatch = struct {
|
||||
/// Index from the end of the match list (0 = most recent match)
|
||||
idx: usize,
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue