From 08eadb3b46dc70a297ff89308dd1cd26237785d6 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 24 Aug 2026 22:18:24 -0300 Subject: syntax + pdf_pane + file_pane: prose grammars paint, pdf Esc cancels, gj/gk walk wrapped rows --- src/pdf_pane.zig | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) (limited to 'src/pdf_pane.zig') diff --git a/src/pdf_pane.zig b/src/pdf_pane.zig index f34a457d..8f163488 100644 --- a/src/pdf_pane.zig +++ b/src/pdf_pane.zig @@ -424,6 +424,18 @@ pub const State = if (enabled) struct { if (changed) state.invalidateRaster(state.page); } + /// Escape's cancel: everything transient a reader can SEE — the mouse + /// selection and the search overlay — and nothing that says WHERE in the + /// document they are. Page, scroll, fit and tint are what the pane is, not + /// chrome. Allocation-free, so it cannot half-cancel. + pub fn cancelChrome(state: *@This(), gpa: std.mem.Allocator) void { + state.clearDrag(); + state.clearSelection(gpa); + if (state.search_query.len == 0) return; + state.dropSearchQuery(gpa); + state.invalidateRaster(state.page); + } + fn invalidatePage(state: *@This(), gpa: std.mem.Allocator) void { state.dropSearchResults(gpa); state.dropSelection(gpa); @@ -438,13 +450,23 @@ pub const State = if (enabled) struct { state.search_hit = 0; } + /// Forget the query, its hits, and every flag derived from them. Shared by + /// the cancel above and by the replacement below, which owns new bytes the + /// caller allocated before anything here was dropped. + fn dropSearchQuery(state: *@This(), gpa: std.mem.Allocator) void { + if (state.search_query.len > 0) gpa.free(state.search_query); + state.search_query = &.{}; + state.search_hit = 0; + state.dropSearchResults(gpa); + state.highlights_dirty = false; + state.search_reveal_pending = false; + } + pub fn setSearchQuery(state: *@This(), gpa: std.mem.Allocator, query: []const u8) !void { if (std.mem.eql(u8, state.search_query, query)) return; const owned = try gpa.dupe(u8, query); - if (state.search_query.len > 0) gpa.free(state.search_query); + state.dropSearchQuery(gpa); state.search_query = owned; - state.search_hit = 0; - state.dropSearchResults(gpa); state.highlights_dirty = query.len > 0; state.search_reveal_pending = query.len > 0; state.invalidateRaster(state.page); @@ -709,7 +731,6 @@ pub const ScrollResult = struct { active_page: usize }; pub const CellPixels = struct { w: u16, h: u16 }; pub const NormalHost = enum { none, - last, leader, command_line, search, @@ -1384,7 +1405,11 @@ pub fn applyNormal( if (comptime !enabled) return .{}; var result: NormalResult = .{}; switch (semantic) { - .escape => result.host = .last, + // Escape stays in the document and cancels what is drawn over it. The + // way OUT of a PDF is Shift-Escape, which the host takes before this + // parse ever runs — a reader who pressed Escape to drop a selection + // was not asking to be moved to another pane. + .escape => state.cancelChrome(gpa), .move => |move| switch (move.motion) { .down => result.page_changed = moveRows( state, -- cgit v1.3