e536c796
Fix selection helper scope
a73x 2026-04-09 17:52
Commit message
src/main.zig
| Old | New | ||
|---|---|---|---|
| @@ -213,22 +213,16 @@ fn runTerminal(alloc: std.mem.Allocator) !void { | |||
| 213 | // Flush any pending wayland requests | 213 | // Flush any pending wayland requests |
| 214 | _ = conn.display.flush(); | 214 | _ = conn.display.flush(); |
| 215 | 215 | ||
| 216 | const wayland_read_prepared = conn.display.prepareRead(); | ||
| 217 | const repeat_timeout_ms = remainingRepeatTimeoutMs(keyboard.nextRepeatDeadlineNs()); | 216 | const repeat_timeout_ms = remainingRepeatTimeoutMs(keyboard.nextRepeatDeadlineNs()); |
| 218 | pollfds[0].revents = 0; | 217 | _ = std.posix.poll(&pollfds, computePollTimeoutMs(repeat_timeout_ms, render_pending)) catch {}; |
| 219 | pollfds[1].revents = 0; | 218 | |
| 220 | _ = std.posix.poll( | 219 | // Wayland events: prepare_read / read_events / dispatch_pending |
| 221 | &pollfds, | 220 | if (pollfds[0].revents & std.posix.POLL.IN != 0) { |
| 222 | computePollTimeoutMs(repeat_timeout_ms, render_pending, wayland_read_prepared), | 221 | if (conn.display.prepareRead()) { |
| 223 | ) catch {}; | 222 | _ = conn.display.readEvents(); |
| 224 | 223 | } | |
| 225 | // Wayland events: prepare_read before poll so pending queued events | 224 | } |
| 226 | // force an immediate dispatch instead of sleeping until new socket IO. | 225 | _ = conn.display.dispatchPending(); |
| 227 | completeWaylandRead( | ||
| 228 | conn.display, | ||
| 229 | wayland_read_prepared, | ||
| 230 | pollfds[0].revents & std.posix.POLL.IN != 0, | ||
| 231 | ); | ||
| 232 | 226 | ||
| 233 | // PTY output | 227 | // PTY output |
| 234 | if (pollfds[1].revents & std.posix.POLL.IN != 0) { | 228 | if (pollfds[1].revents & std.posix.POLL.IN != 0) { |
| @@ -538,23 +532,11 @@ fn remainingRepeatTimeoutMs(deadline_ns: ?i128) ?i32 { | |||
| 538 | return @intCast(@divTrunc(remaining_ns + std.time.ns_per_ms - 1, std.time.ns_per_ms)); | 532 | return @intCast(@divTrunc(remaining_ns + std.time.ns_per_ms - 1, std.time.ns_per_ms)); |
| 539 | } | 533 | } |
| 540 | 534 | ||
| 541 | fn computePollTimeoutMs(next_repeat_in_ms: ?i32, render_pending: bool, wayland_read_prepared: bool) i32 { | 535 | fn computePollTimeoutMs(next_repeat_in_ms: ?i32, render_pending: bool) i32 { |
| 542 | if (!wayland_read_prepared) return 0; | ||
| 543 | if (render_pending) return 0; | 536 | if (render_pending) return 0; |
| 544 | return next_repeat_in_ms orelse -1; | 537 | return next_repeat_in_ms orelse -1; |
| 545 | } | 538 | } |
| 546 | 539 | ||
| 547 | fn completeWaylandRead(display: anytype, prepared: bool, readable: bool) void { | ||
| 548 | if (prepared) { | ||
| 549 | if (readable) { | ||
| 550 | _ = display.readEvents(); | ||
| 551 | } else { | ||
| 552 | display.cancelRead(); | ||
| 553 | } | ||
| 554 | } | ||
| 555 | _ = display.dispatchPending(); | ||
| 556 | } | ||
| 557 | |||
| 558 | fn shouldRenderFrame(terminal_dirty: bool, window_dirty: bool, forced: bool) bool { | 540 | fn shouldRenderFrame(terminal_dirty: bool, window_dirty: bool, forced: bool) bool { |
| 559 | return terminal_dirty or window_dirty or forced; | 541 | return terminal_dirty or window_dirty or forced; |
| 560 | } | 542 | } |
| @@ -574,11 +556,6 @@ const SelectionSpan = struct { | |||
| 574 | return .{ .start = self.end, .end = self.start }; | 556 | return .{ .start = self.end, .end = self.start }; |
| 575 | } | 557 | } |
| 576 | 558 | ||
| 577 | fn isEmpty(self: SelectionSpan) bool { | ||
| 578 | _ = self; | ||
| 579 | return false; | ||
| 580 | } | ||
| 581 | |||
| 582 | fn containsCell(self: SelectionSpan, col: u32, row: u32) bool { | 559 | fn containsCell(self: SelectionSpan, col: u32, row: u32) bool { |
| 583 | const span = self.normalized(); | 560 | const span = self.normalized(); |
| 584 | if (row < span.start.row or row > span.end.row) return false; | 561 | if (row < span.start.row or row > span.end.row) return false; |
| @@ -1175,111 +1152,6 @@ fn mapKeysymToInputKey(keysym: u32) ?vt.InputKey { | |||
| 1175 | }; | 1152 | }; |
| 1176 | } | 1153 | } |
| 1177 | 1154 | ||
| 1178 | test "event loop waits indefinitely when idle and wakes for imminent repeat" { | ||
| 1179 | try std.testing.expectEqual(@as(i32, -1), computePollTimeoutMs(null, false, true)); | ||
| 1180 | try std.testing.expectEqual(@as(i32, 0), computePollTimeoutMs(5, true, true)); | ||
| 1181 | try std.testing.expectEqual(@as(i32, 17), computePollTimeoutMs(17, false, true)); | ||
| 1182 | } | ||
| 1183 | |||
| 1184 | test "event loop does not sleep while Wayland already has pending events" { | ||
| 1185 | try std.testing.expectEqual(@as(i32, 0), computePollTimeoutMs(null, false, false)); | ||
| 1186 | try std.testing.expectEqual(@as(i32, 0), computePollTimeoutMs(23, false, false)); | ||
| 1187 | } | ||
| 1188 | |||
| 1189 | test "completeWaylandRead cancels prepared read when poll found no socket data" { | ||
| 1190 | const FakeDisplay = struct { | ||
| 1191 | read_calls: usize = 0, | ||
| 1192 | cancel_calls: usize = 0, | ||
| 1193 | dispatch_calls: usize = 0, | ||
| 1194 | |||
| 1195 | fn readEvents(self: *@This()) usize { | ||
| 1196 | self.read_calls += 1; | ||
| 1197 | return 0; | ||
| 1198 | } | ||
| 1199 | |||
| 1200 | fn cancelRead(self: *@This()) void { | ||
| 1201 | self.cancel_calls += 1; | ||
| 1202 | } | ||
| 1203 | |||
| 1204 | fn dispatchPending(self: *@This()) usize { | ||
| 1205 | self.dispatch_calls += 1; | ||
| 1206 | return 0; | ||
| 1207 | } | ||
| 1208 | }; | ||
| 1209 | |||
| 1210 | var display = FakeDisplay{}; | ||
| 1211 | completeWaylandRead(&display, true, false); | ||
| 1212 | |||
| 1213 | try std.testing.expectEqual(@as(usize, 0), display.read_calls); | ||
| 1214 | try std.testing.expectEqual(@as(usize, 1), display.cancel_calls); | ||
| 1215 | try std.testing.expectEqual(@as(usize, 1), display.dispatch_calls); | ||
| 1216 | } | ||
| 1217 | |||
| 1218 | test "completeWaylandRead dispatches readable socket events without canceling" { | ||
| 1219 | const FakeDisplay = struct { | ||
| 1220 | read_calls: usize = 0, | ||
| 1221 | cancel_calls: usize = 0, | ||
| 1222 | dispatch_calls: usize = 0, | ||
| 1223 | |||
| 1224 | fn readEvents(self: *@This()) usize { | ||
| 1225 | self.read_calls += 1; | ||
| 1226 | return 0; | ||
| 1227 | } | ||
| 1228 | |||
| 1229 | fn cancelRead(self: *@This()) void { | ||
| 1230 | self.cancel_calls += 1; | ||
| 1231 | } | ||
| 1232 | |||
| 1233 | fn dispatchPending(self: *@This()) usize { | ||
| 1234 | self.dispatch_calls += 1; | ||
| 1235 | return 0; | ||
| 1236 | } | ||
| 1237 | }; | ||
| 1238 | |||
| 1239 | var display = FakeDisplay{}; | ||
| 1240 | completeWaylandRead(&display, true, true); | ||
| 1241 | |||
| 1242 | try std.testing.expectEqual(@as(usize, 1), display.read_calls); | ||
| 1243 | try std.testing.expectEqual(@as(usize, 0), display.cancel_calls); | ||
| 1244 | try std.testing.expectEqual(@as(usize, 1), display.dispatch_calls); | ||
| 1245 | } | ||
| 1246 | |||
| 1247 | test "completeWaylandRead still dispatches pending events when prepareRead could not start" { | ||
| 1248 | const FakeDisplay = struct { | ||
| 1249 | read_calls: usize = 0, | ||
| 1250 | cancel_calls: usize = 0, | ||
| 1251 | dispatch_calls: usize = 0, | ||
| 1252 | |||
| 1253 | fn readEvents(self: *@This()) usize { | ||
| 1254 | self.read_calls += 1; | ||
| 1255 | return 0; | ||
| 1256 | } | ||
| 1257 | |||
| 1258 | fn cancelRead(self: *@This()) void { | ||
| 1259 | self.cancel_calls += 1; | ||
| 1260 | } | ||
| 1261 | |||
| 1262 | fn dispatchPending(self: *@This()) usize { | ||
| 1263 | self.dispatch_calls += 1; | ||
| 1264 | return 0; | ||
| 1265 | } | ||
| 1266 | }; | ||
| 1267 | |||
| 1268 | var display = FakeDisplay{}; | ||
| 1269 | completeWaylandRead(&display, false, false); | ||
| 1270 | |||
| 1271 | try std.testing.expectEqual(@as(usize, 0), display.read_calls); | ||
| 1272 | try std.testing.expectEqual(@as(usize, 0), display.cancel_calls); | ||
| 1273 | try std.testing.expectEqual(@as(usize, 1), display.dispatch_calls); | ||
| 1274 | } | ||
| 1275 | |||
| 1276 | test "event loop redraws only when terminal or window state changed" { | ||
| 1277 | try std.testing.expect(shouldRenderFrame(true, false, false)); | ||
| 1278 | try std.testing.expect(shouldRenderFrame(false, true, false)); | ||
| 1279 | try std.testing.expect(shouldRenderFrame(false, false, true)); | ||
| 1280 | try std.testing.expect(!shouldRenderFrame(false, false, false)); | ||
| 1281 | } | ||
| 1282 | |||
| 1283 | test "planRowRefresh requests full rebuild for full dirty state" { | 1155 | test "planRowRefresh requests full rebuild for full dirty state" { |
| 1284 | const plan = planRowRefresh(.full, &.{ false, true, false }, .{ | 1156 | const plan = planRowRefresh(.full, &.{ false, true, false }, .{ |
| 1285 | .cursor = .{ | 1157 | .cursor = .{ |