From 6b1fd2b9eb17e6f7d3244853bba5366738074dbf Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 20 Dec 2023 20:50:10 -0800 Subject: [PATCH] terminal: Screen.promptPath --- src/terminal/Screen.zig | 123 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 122 insertions(+), 1 deletion(-) diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index 2fe5da85c..d7fda6094 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -1922,6 +1922,39 @@ pub fn selectPrompt(self: *Screen, pt: point.ScreenPoint) ?Selection { }; } +/// Returns the change in x/y that is needed to reach "to" from "from" +/// within a prompt. If "to" is before or after the prompt bounds then +/// the result will be bounded to the prompt. +/// +/// This feature requires shell integration. If shell integration is not +/// enabled, this will always return zero for both x and y (no path). +pub fn promptPath( + self: *Screen, + from: point.ScreenPoint, + to: point.ScreenPoint, +) struct { + x: isize, + y: isize, +} { + // Get our prompt bounds assuming "from" is at a prompt. + const bounds = self.selectPrompt(from) orelse return .{ .x = 0, .y = 0 }; + + // Get our actual "to" point clamped to the bounds of the prompt. + const to_clamped = if (bounds.contains(to)) + to + else if (to.before(bounds.start)) + bounds.start + else + bounds.end; + + // Basic math to calculate our path. + const from_x: isize = @intCast(from.x); + const from_y: isize = @intCast(from.y); + const to_x: isize = @intCast(to_clamped.x); + const to_y: isize = @intCast(to_clamped.y); + return .{ .x = to_x - from_x, .y = to_y - from_y }; +} + /// Scroll behaviors for the scroll function. pub const Scroll = union(enum) { /// Scroll to the top of the scroll buffer. The first line of the @@ -4722,6 +4755,7 @@ test "Screen: selectOutput" { try s.testWriteString("output3\n"); // 8 try s.testWriteString("output3"); // 9 } + // zig fmt: on var row = s.getRow(.{ .screen = 2 }); row.setSemanticPrompt(.prompt); @@ -4794,6 +4828,7 @@ test "Screen: selectPrompt basics" { try s.testWriteString("output3\n"); // 8 try s.testWriteString("output3"); // 9 } + // zig fmt: on var row = s.getRow(.{ .screen = 2 }); row.setSemanticPrompt(.prompt); @@ -4850,6 +4885,7 @@ test "Screen: selectPrompt prompt at start" { try s.testWriteString("output2\n"); // 2 try s.testWriteString("output2\n"); // 3 } + // zig fmt: on var row = s.getRow(.{ .screen = 0 }); row.setSemanticPrompt(.prompt); @@ -4887,6 +4923,7 @@ test "Screen: selectPrompt prompt at end" { try s.testWriteString("prompt1\n"); // 2 try s.testWriteString("input1\n"); // 3 } + // zig fmt: on var row = s.getRow(.{ .screen = 2 }); row.setSemanticPrompt(.prompt); @@ -4909,6 +4946,91 @@ test "Screen: selectPrompt prompt at end" { } } +test "Screen: promtpPath" { + const testing = std.testing; + const alloc = testing.allocator; + + var s = try init(alloc, 15, 10, 0); + defer s.deinit(); + + // zig fmt: off + { + // line number: + try s.testWriteString("output1\n"); // 0 + try s.testWriteString("output1\n"); // 1 + try s.testWriteString("prompt2\n"); // 2 + try s.testWriteString("input2\n"); // 3 + try s.testWriteString("output2\n"); // 4 + try s.testWriteString("output2\n"); // 5 + try s.testWriteString("prompt3$ input3\n"); // 6 + try s.testWriteString("output3\n"); // 7 + try s.testWriteString("output3\n"); // 8 + try s.testWriteString("output3"); // 9 + } + // zig fmt: on + + var row = s.getRow(.{ .screen = 2 }); + row.setSemanticPrompt(.prompt); + row = s.getRow(.{ .screen = 3 }); + row.setSemanticPrompt(.input); + row = s.getRow(.{ .screen = 4 }); + row.setSemanticPrompt(.command); + row = s.getRow(.{ .screen = 6 }); + row.setSemanticPrompt(.input); + row = s.getRow(.{ .screen = 7 }); + row.setSemanticPrompt(.command); + + // From is not in the prompt + { + const path = s.promptPath( + .{ .x = 0, .y = 1 }, + .{ .x = 0, .y = 2 }, + ); + try testing.expectEqual(@as(isize, 0), path.x); + try testing.expectEqual(@as(isize, 0), path.y); + } + + // Same line + { + const path = s.promptPath( + .{ .x = 6, .y = 2 }, + .{ .x = 3, .y = 2 }, + ); + try testing.expectEqual(@as(isize, -3), path.x); + try testing.expectEqual(@as(isize, 0), path.y); + } + + // Different lines + { + const path = s.promptPath( + .{ .x = 6, .y = 2 }, + .{ .x = 3, .y = 3 }, + ); + try testing.expectEqual(@as(isize, -3), path.x); + try testing.expectEqual(@as(isize, 1), path.y); + } + + // To is out of bounds before + { + const path = s.promptPath( + .{ .x = 6, .y = 2 }, + .{ .x = 3, .y = 1 }, + ); + try testing.expectEqual(@as(isize, -6), path.x); + try testing.expectEqual(@as(isize, 0), path.y); + } + + // To is out of bounds after + { + const path = s.promptPath( + .{ .x = 6, .y = 2 }, + .{ .x = 3, .y = 9 }, + ); + try testing.expectEqual(@as(isize, 3), path.x); + try testing.expectEqual(@as(isize, 1), path.y); + } +} + test "Screen: scrollRegionUp single" { const testing = std.testing; const alloc = testing.allocator; @@ -5647,7 +5769,6 @@ test "Screen: selectionString, rectangle, more complex w/breaks" { try testing.expectEqualStrings(expected, contents); } - test "Screen: dirty with getCellPtr" { const testing = std.testing; const alloc = testing.allocator;