diff --git a/src/Surface.zig b/src/Surface.zig index 484daacee..70b276658 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -2534,6 +2534,14 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool } else log.warn("runtime doesn't implement toggleFullscreen", .{}); }, + .select_all => { + const sel = self.io.terminal.screen.selectAll(); + if (sel) |s| { + self.setSelection(s); + try self.queueRender(); + } + }, + .inspector => |mode| { if (@hasDecl(apprt.Surface, "controlInspector")) { self.rt_surface.controlInspector(mode); diff --git a/src/config/Config.zig b/src/config/Config.zig index ecf596e59..28d3cea89 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -913,6 +913,13 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config { } } + // Select all + try result.keybind.set.put( + alloc, + .{ .key = .a, .mods = ctrlOrSuper(.{}) }, + .{ .select_all = {} }, + ); + // Toggle fullscreen try result.keybind.set.put( alloc, diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 25bf9df4c..6a66e1d6f 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -152,6 +152,9 @@ pub const Action = union(enum) { /// Clear the screen. This also clears all scrollback. clear_screen: void, + /// Select all text on the screen. + select_all: void, + /// Scroll the screen varying amounts. scroll_to_top: void, scroll_to_bottom: void, diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index a92175e15..52b00baea 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -1392,6 +1392,73 @@ pub fn clear(self: *Screen, mode: ClearMode) !void { } } +/// Return the selection for all contents on the screen. Surrounding +/// whitespace is omitted. If there is no selection, this returns null. +pub fn selectAll(self: *Screen) ?Selection { + const whitespace = &[_]u32{ 0, ' ', '\t' }; + const y_max = self.rowsWritten() - 1; + + const start: point.ScreenPoint = start: { + var y: usize = 0; + while (y <= y_max) : (y += 1) { + const current_row = self.getRow(.{ .screen = y }); + var x: usize = 0; + while (x < self.cols) : (x += 1) { + const cell = current_row.getCell(x); + + // Empty is whitespace + if (cell.empty()) continue; + + // Non-empty means we found it. + const this_whitespace = std.mem.indexOfAny( + u32, + whitespace, + &[_]u32{cell.char}, + ) != null; + if (this_whitespace) continue; + + break :start .{ .x = x, .y = y }; + } + } + + // There is no start point and therefore no line that can be selected. + return null; + }; + + const end: point.ScreenPoint = end: { + var y: usize = y_max; + while (true) { + const current_row = self.getRow(.{ .screen = y }); + + var x: usize = 0; + while (x < self.cols) : (x += 1) { + const real_x = self.cols - x - 1; + const cell = current_row.getCell(real_x); + + // Empty or whitespace, ignore. + if (cell.empty()) continue; + const this_whitespace = std.mem.indexOfAny( + u32, + whitespace, + &[_]u32{cell.char}, + ) != null; + if (this_whitespace) continue; + + // Got it + break :end .{ .x = real_x, .y = y }; + } + + if (y == 0) break; + y -= 1; + } + }; + + return Selection{ + .start = start, + .end = end, + }; +} + /// Select the line under the given point. This will select across soft-wrapped /// lines and will omit the leading and trailing whitespace. If the point is /// over whitespace but the line has non-whitespace characters elsewhere, the @@ -3856,6 +3923,31 @@ test "Screen: selectLine" { try testing.expectEqual(@as(usize, 0), sel.end.y); } } +test "Screen: selectAll" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, 10, 10, 0); + defer s.deinit(); + + { + try s.testWriteString("ABC DEF\n 123\n456"); + const sel = s.selectAll().?; + try testing.expectEqual(@as(usize, 0), sel.start.x); + try testing.expectEqual(@as(usize, 0), sel.start.y); + try testing.expectEqual(@as(usize, 2), sel.end.x); + try testing.expectEqual(@as(usize, 2), sel.end.y); + } + + { + try s.testWriteString("\nFOO\n BAR\n BAZ\n QWERTY\n 12345678"); + const sel = s.selectAll().?; + try testing.expectEqual(@as(usize, 0), sel.start.x); + try testing.expectEqual(@as(usize, 0), sel.start.y); + try testing.expectEqual(@as(usize, 8), sel.end.x); + try testing.expectEqual(@as(usize, 7), sel.end.y); + } +} test "Screen: selectLine across soft-wrap" { const testing = std.testing;