diff --git a/src/Surface.zig b/src/Surface.zig index ea130213a..bb351a9c4 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3471,6 +3471,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool .page_down => .page_down, .home => .home, .end => .end, + .beginning_of_line => .beginning_of_line, + .end_of_line => .end_of_line, }); // If the selection endpoint is outside of the current viewpoint, diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 7b9faa80d..ff596f4e9 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -304,6 +304,8 @@ pub const Action = union(enum) { page_down, home, end, + beginning_of_line, + end_of_line, }; pub const SplitDirection = enum { diff --git a/src/terminal/Selection.zig b/src/terminal/Selection.zig index d1bd4accb..aad69c6f2 100644 --- a/src/terminal/Selection.zig +++ b/src/terminal/Selection.zig @@ -344,6 +344,8 @@ pub const Adjustment = enum { end, page_up, page_down, + beginning_of_line, + end_of_line, }; /// Adjust the selection by some given adjustment. An adjustment allows @@ -353,16 +355,16 @@ pub fn adjust( s: *const Screen, adjustment: Adjustment, ) void { - // Note that we always adjusts "end" because end always represents + // Note that we always adjust "end" because end always represents // the last point of the selection by mouse, not necessarilly the - // top/bottom visually. So this results in the right behavior + // top/bottom visually. So this results in the correct behavior // whether the user drags up or down. const end_pin = self.endPtr(); switch (adjustment) { .up => if (end_pin.up(1)) |new_end| { end_pin.* = new_end; } else { - end_pin.x = 0; + self.adjust(s, .beginning_of_line); }, .down => { @@ -377,7 +379,7 @@ pub fn adjust( } } else { // If we're at the bottom, just go to the end of the line - end_pin.x = end_pin.page.data.size.cols - 1; + self.adjust(s, .end_of_line); } }, @@ -440,6 +442,10 @@ pub fn adjust( } } }, + + .beginning_of_line => end_pin.x = 0, + + .end_of_line => end_pin.x = end_pin.page.data.size.cols - 1, } } @@ -786,6 +792,144 @@ test "Selection: adjust end with not full screen" { } } +test "Selection: adjust beginning of line" { + const testing = std.testing; + var s = try Screen.init(testing.allocator, 8, 10, 0); + defer s.deinit(); + try s.testWriteString("A12 B34\nC12 D34"); + + // Not at beginning of the line + { + var sel = Selection.init( + s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?, + s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?, + false, + ); + defer sel.deinit(&s); + sel.adjust(&s, .beginning_of_line); + + // Start line + try testing.expectEqual(point.Point{ .screen = .{ + .x = 5, + .y = 1, + } }, s.pages.pointFromPin(.screen, sel.start()).?); + try testing.expectEqual(point.Point{ .screen = .{ + .x = 0, + .y = 1, + } }, s.pages.pointFromPin(.screen, sel.end()).?); + } + + // Already at beginning of the line + { + var sel = Selection.init( + s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?, + s.pages.pin(.{ .screen = .{ .x = 0, .y = 1 } }).?, + false, + ); + defer sel.deinit(&s); + sel.adjust(&s, .beginning_of_line); + + // Start line + try testing.expectEqual(point.Point{ .screen = .{ + .x = 5, + .y = 1, + } }, s.pages.pointFromPin(.screen, sel.start()).?); + try testing.expectEqual(point.Point{ .screen = .{ + .x = 0, + .y = 1, + } }, s.pages.pointFromPin(.screen, sel.end()).?); + } + + // End pin moves to start pin + { + var sel = Selection.init( + s.pages.pin(.{ .screen = .{ .x = 0, .y = 1 } }).?, + s.pages.pin(.{ .screen = .{ .x = 5, .y = 1 } }).?, + false, + ); + defer sel.deinit(&s); + sel.adjust(&s, .beginning_of_line); + + // Start line + try testing.expectEqual(point.Point{ .screen = .{ + .x = 0, + .y = 1, + } }, s.pages.pointFromPin(.screen, sel.start()).?); + try testing.expectEqual(point.Point{ .screen = .{ + .x = 0, + .y = 1, + } }, s.pages.pointFromPin(.screen, sel.end()).?); + } +} + +test "Selection: adjust end of line" { + const testing = std.testing; + var s = try Screen.init(testing.allocator, 8, 10, 0); + defer s.deinit(); + try s.testWriteString("A12 B34\nC12 D34"); + + // Not at end of the line + { + var sel = Selection.init( + s.pages.pin(.{ .screen = .{ .x = 1, .y = 0 } }).?, + s.pages.pin(.{ .screen = .{ .x = 1, .y = 0 } }).?, + false, + ); + defer sel.deinit(&s); + sel.adjust(&s, .end_of_line); + + try testing.expectEqual(point.Point{ .screen = .{ + .x = 1, + .y = 0, + } }, s.pages.pointFromPin(.screen, sel.start()).?); + try testing.expectEqual(point.Point{ .screen = .{ + .x = 7, + .y = 0, + } }, s.pages.pointFromPin(.screen, sel.end()).?); + } + + // Already at end of the line + { + var sel = Selection.init( + s.pages.pin(.{ .screen = .{ .x = 1, .y = 0 } }).?, + s.pages.pin(.{ .screen = .{ .x = 7, .y = 0 } }).?, + false, + ); + defer sel.deinit(&s); + sel.adjust(&s, .end_of_line); + + try testing.expectEqual(point.Point{ .screen = .{ + .x = 1, + .y = 0, + } }, s.pages.pointFromPin(.screen, sel.start()).?); + try testing.expectEqual(point.Point{ .screen = .{ + .x = 7, + .y = 0, + } }, s.pages.pointFromPin(.screen, sel.end()).?); + } + + // End pin moves to start pin + { + var sel = Selection.init( + s.pages.pin(.{ .screen = .{ .x = 7, .y = 0 } }).?, + s.pages.pin(.{ .screen = .{ .x = 1, .y = 0 } }).?, + false, + ); + defer sel.deinit(&s); + sel.adjust(&s, .end_of_line); + + // Start line + try testing.expectEqual(point.Point{ .screen = .{ + .x = 7, + .y = 0, + } }, s.pages.pointFromPin(.screen, sel.start()).?); + try testing.expectEqual(point.Point{ .screen = .{ + .x = 7, + .y = 0, + } }, s.pages.pointFromPin(.screen, sel.end()).?); + } +} + test "Selection: order, standard" { const testing = std.testing; const alloc = testing.allocator;