terminal: selectionString takes a struct for opts

This commit is contained in:
Mitchell Hashimoto
2024-03-15 20:07:49 -07:00
parent 2de86ce500
commit bca51ee771
3 changed files with 112 additions and 45 deletions

View File

@ -946,7 +946,10 @@ pub fn selectionString(self: *Surface, alloc: Allocator) !?[]const u8 {
self.renderer_state.mutex.lock(); self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock(); defer self.renderer_state.mutex.unlock();
const sel = self.io.terminal.screen.selection orelse return null; const sel = self.io.terminal.screen.selection orelse return null;
return try self.io.terminal.screen.selectionString(alloc, sel, false); return try self.io.terminal.screen.selectionString(alloc, .{
.sel = sel,
.trim = false,
});
} }
/// Returns the pwd of the terminal, if any. This is always copied because /// Returns the pwd of the terminal, if any. This is always copied because
@ -1075,11 +1078,10 @@ fn setSelection(self: *Surface, sel_: ?terminal.Selection) !void {
} }
} }
const buf = self.io.terminal.screen.selectionString( const buf = self.io.terminal.screen.selectionString(self.alloc, .{
self.alloc, .sel = sel,
sel, .trim = self.config.clipboard_trim_trailing_spaces,
self.config.clipboard_trim_trailing_spaces, }) catch |err| {
) catch |err| {
log.err("error reading selection string err={}", .{err}); log.err("error reading selection string err={}", .{err});
return; return;
}; };
@ -2536,11 +2538,10 @@ fn processLinks(self: *Surface, pos: apprt.CursorPos) !bool {
const link, const sel = try self.linkAtPos(pos) orelse return false; const link, const sel = try self.linkAtPos(pos) orelse return false;
switch (link.action) { switch (link.action) {
.open => { .open => {
const str = try self.io.terminal.screen.selectionString( const str = try self.io.terminal.screen.selectionString(self.alloc, .{
self.alloc, .sel = sel,
sel, .trim = false,
false, });
);
defer self.alloc.free(str); defer self.alloc.free(str);
try internal_os.open(self.alloc, str); try internal_os.open(self.alloc, str);
}, },
@ -3104,11 +3105,10 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
// We can read from the renderer state without holding // We can read from the renderer state without holding
// the lock because only we will write to this field. // the lock because only we will write to this field.
if (self.io.terminal.screen.selection) |sel| { if (self.io.terminal.screen.selection) |sel| {
const buf = self.io.terminal.screen.selectionString( const buf = self.io.terminal.screen.selectionString(self.alloc, .{
self.alloc, .sel = sel,
sel, .trim = self.config.clipboard_trim_trailing_spaces,
self.config.clipboard_trim_trailing_spaces, }) catch |err| {
) catch |err| {
log.err("error reading selection string err={}", .{err}); log.err("error reading selection string err={}", .{err});
return true; return true;
}; };

View File

@ -1106,22 +1106,25 @@ pub fn clearSelection(self: *Screen) void {
self.selection = null; self.selection = null;
} }
pub const SelectionString = struct {
/// The selection to convert to a string.
sel: Selection,
/// If true, trim whitespace around the selection.
trim: bool,
};
/// Returns the raw text associated with a selection. This will unwrap /// Returns the raw text associated with a selection. This will unwrap
/// soft-wrapped edges. The returned slice is owned by the caller and allocated /// soft-wrapped edges. The returned slice is owned by the caller and allocated
/// using alloc, not the allocator associated with the screen (unless they match). /// using alloc, not the allocator associated with the screen (unless they match).
pub fn selectionString( pub fn selectionString(self: *Screen, alloc: Allocator, opts: SelectionString) ![:0]const u8 {
self: *Screen,
alloc: Allocator,
sel: Selection,
trim: bool,
) ![:0]const u8 {
// Use an ArrayList so that we can grow the array as we go. We // Use an ArrayList so that we can grow the array as we go. We
// build an initial capacity of just our rows in our selection times // build an initial capacity of just our rows in our selection times
// columns. It can be more or less based on graphemes, newlines, etc. // columns. It can be more or less based on graphemes, newlines, etc.
var strbuilder = std.ArrayList(u8).init(alloc); var strbuilder = std.ArrayList(u8).init(alloc);
defer strbuilder.deinit(); defer strbuilder.deinit();
const sel_ordered = sel.ordered(self, .forward); const sel_ordered = opts.sel.ordered(self, .forward);
const sel_start = start: { const sel_start = start: {
var start = sel_ordered.start(); var start = sel_ordered.start();
const cell = start.rowAndCell().cell; const cell = start.rowAndCell().cell;
@ -1199,7 +1202,7 @@ pub fn selectionString(
// Remove any trailing spaces on lines. We could do optimize this by // Remove any trailing spaces on lines. We could do optimize this by
// doing this in the loop above but this isn't very hot path code and // doing this in the loop above but this isn't very hot path code and
// this is simple. // this is simple.
if (trim) { if (opts.trim) {
var it = std.mem.tokenizeScalar(u8, strbuilder.items, '\n'); var it = std.mem.tokenizeScalar(u8, strbuilder.items, '\n');
// Reset our items. We retain our capacity. Because we're only // Reset our items. We retain our capacity. Because we're only
@ -6020,7 +6023,10 @@ test "Screen: selectionString basic" {
s.pages.pin(.{ .screen = .{ .x = 2, .y = 2 } }).?, s.pages.pin(.{ .screen = .{ .x = 2, .y = 2 } }).?,
false, false,
); );
const contents = try s.selectionString(alloc, sel, true); const contents = try s.selectionString(alloc, .{
.sel = sel,
.trim = true,
});
defer alloc.free(contents); defer alloc.free(contents);
const expected = "2EFGH\n3IJ"; const expected = "2EFGH\n3IJ";
try testing.expectEqualStrings(expected, contents); try testing.expectEqualStrings(expected, contents);
@ -6042,7 +6048,10 @@ test "Screen: selectionString start outside of written area" {
s.pages.pin(.{ .screen = .{ .x = 2, .y = 6 } }).?, s.pages.pin(.{ .screen = .{ .x = 2, .y = 6 } }).?,
false, false,
); );
const contents = try s.selectionString(alloc, sel, true); const contents = try s.selectionString(alloc, .{
.sel = sel,
.trim = true,
});
defer alloc.free(contents); defer alloc.free(contents);
const expected = ""; const expected = "";
try testing.expectEqualStrings(expected, contents); try testing.expectEqualStrings(expected, contents);
@ -6064,7 +6073,10 @@ test "Screen: selectionString end outside of written area" {
s.pages.pin(.{ .screen = .{ .x = 2, .y = 6 } }).?, s.pages.pin(.{ .screen = .{ .x = 2, .y = 6 } }).?,
false, false,
); );
const contents = try s.selectionString(alloc, sel, true); const contents = try s.selectionString(alloc, .{
.sel = sel,
.trim = true,
});
defer alloc.free(contents); defer alloc.free(contents);
const expected = "3IJKL"; const expected = "3IJKL";
try testing.expectEqualStrings(expected, contents); try testing.expectEqualStrings(expected, contents);
@ -6087,7 +6099,10 @@ test "Screen: selectionString trim space" {
); );
{ {
const contents = try s.selectionString(alloc, sel, true); const contents = try s.selectionString(alloc, .{
.sel = sel,
.trim = true,
});
defer alloc.free(contents); defer alloc.free(contents);
const expected = "1AB\n2EF"; const expected = "1AB\n2EF";
try testing.expectEqualStrings(expected, contents); try testing.expectEqualStrings(expected, contents);
@ -6095,7 +6110,10 @@ test "Screen: selectionString trim space" {
// No trim // No trim
{ {
const contents = try s.selectionString(alloc, sel, false); const contents = try s.selectionString(alloc, .{
.sel = sel,
.trim = false,
});
defer alloc.free(contents); defer alloc.free(contents);
const expected = "1AB \n2EF"; const expected = "1AB \n2EF";
try testing.expectEqualStrings(expected, contents); try testing.expectEqualStrings(expected, contents);
@ -6118,7 +6136,10 @@ test "Screen: selectionString trim empty line" {
); );
{ {
const contents = try s.selectionString(alloc, sel, true); const contents = try s.selectionString(alloc, .{
.sel = sel,
.trim = true,
});
defer alloc.free(contents); defer alloc.free(contents);
const expected = "1AB\n\n2EF"; const expected = "1AB\n\n2EF";
try testing.expectEqualStrings(expected, contents); try testing.expectEqualStrings(expected, contents);
@ -6126,7 +6147,10 @@ test "Screen: selectionString trim empty line" {
// No trim // No trim
{ {
const contents = try s.selectionString(alloc, sel, false); const contents = try s.selectionString(alloc, .{
.sel = sel,
.trim = false,
});
defer alloc.free(contents); defer alloc.free(contents);
const expected = "1AB \n \n2EF"; const expected = "1AB \n \n2EF";
try testing.expectEqualStrings(expected, contents); try testing.expectEqualStrings(expected, contents);
@ -6148,7 +6172,10 @@ test "Screen: selectionString soft wrap" {
s.pages.pin(.{ .screen = .{ .x = 2, .y = 2 } }).?, s.pages.pin(.{ .screen = .{ .x = 2, .y = 2 } }).?,
false, false,
); );
const contents = try s.selectionString(alloc, sel, true); const contents = try s.selectionString(alloc, .{
.sel = sel,
.trim = true,
});
defer alloc.free(contents); defer alloc.free(contents);
const expected = "2EFGH3IJ"; const expected = "2EFGH3IJ";
try testing.expectEqualStrings(expected, contents); try testing.expectEqualStrings(expected, contents);
@ -6170,7 +6197,10 @@ test "Screen: selectionString wide char" {
s.pages.pin(.{ .screen = .{ .x = 3, .y = 0 } }).?, s.pages.pin(.{ .screen = .{ .x = 3, .y = 0 } }).?,
false, false,
); );
const contents = try s.selectionString(alloc, sel, true); const contents = try s.selectionString(alloc, .{
.sel = sel,
.trim = true,
});
defer alloc.free(contents); defer alloc.free(contents);
const expected = str; const expected = str;
try testing.expectEqualStrings(expected, contents); try testing.expectEqualStrings(expected, contents);
@ -6182,7 +6212,10 @@ test "Screen: selectionString wide char" {
s.pages.pin(.{ .screen = .{ .x = 2, .y = 0 } }).?, s.pages.pin(.{ .screen = .{ .x = 2, .y = 0 } }).?,
false, false,
); );
const contents = try s.selectionString(alloc, sel, true); const contents = try s.selectionString(alloc, .{
.sel = sel,
.trim = true,
});
defer alloc.free(contents); defer alloc.free(contents);
const expected = str; const expected = str;
try testing.expectEqualStrings(expected, contents); try testing.expectEqualStrings(expected, contents);
@ -6194,7 +6227,10 @@ test "Screen: selectionString wide char" {
s.pages.pin(.{ .screen = .{ .x = 3, .y = 0 } }).?, s.pages.pin(.{ .screen = .{ .x = 3, .y = 0 } }).?,
false, false,
); );
const contents = try s.selectionString(alloc, sel, true); const contents = try s.selectionString(alloc, .{
.sel = sel,
.trim = true,
});
defer alloc.free(contents); defer alloc.free(contents);
const expected = ""; const expected = "";
try testing.expectEqualStrings(expected, contents); try testing.expectEqualStrings(expected, contents);
@ -6216,7 +6252,10 @@ test "Screen: selectionString wide char with header" {
s.pages.pin(.{ .screen = .{ .x = 4, .y = 0 } }).?, s.pages.pin(.{ .screen = .{ .x = 4, .y = 0 } }).?,
false, false,
); );
const contents = try s.selectionString(alloc, sel, true); const contents = try s.selectionString(alloc, .{
.sel = sel,
.trim = true,
});
defer alloc.free(contents); defer alloc.free(contents);
const expected = str; const expected = str;
try testing.expectEqualStrings(expected, contents); try testing.expectEqualStrings(expected, contents);
@ -6247,7 +6286,10 @@ test "Screen: selectionString empty with soft wrap" {
s.pages.pin(.{ .screen = .{ .x = 2, .y = 0 } }).?, s.pages.pin(.{ .screen = .{ .x = 2, .y = 0 } }).?,
false, false,
); );
const contents = try s.selectionString(alloc, sel, true); const contents = try s.selectionString(alloc, .{
.sel = sel,
.trim = true,
});
defer alloc.free(contents); defer alloc.free(contents);
const expected = "👨"; const expected = "👨";
try testing.expectEqualStrings(expected, contents); try testing.expectEqualStrings(expected, contents);
@ -6280,7 +6322,10 @@ test "Screen: selectionString with zero width joiner" {
s.pages.pin(.{ .screen = .{ .x = 1, .y = 0 } }).?, s.pages.pin(.{ .screen = .{ .x = 1, .y = 0 } }).?,
false, false,
); );
const contents = try s.selectionString(alloc, sel, true); const contents = try s.selectionString(alloc, .{
.sel = sel,
.trim = true,
});
defer alloc.free(contents); defer alloc.free(contents);
const expected = "👨‍"; const expected = "👨‍";
try testing.expectEqualStrings(expected, contents); try testing.expectEqualStrings(expected, contents);
@ -6312,7 +6357,10 @@ test "Screen: selectionString, rectangle, basic" {
; ;
try s.testWriteString(str); try s.testWriteString(str);
const contents = try s.selectionString(alloc, sel, true); const contents = try s.selectionString(alloc, .{
.sel = sel,
.trim = true,
});
defer alloc.free(contents); defer alloc.free(contents);
try testing.expectEqualStrings(expected, contents); try testing.expectEqualStrings(expected, contents);
} }
@ -6344,7 +6392,10 @@ test "Screen: selectionString, rectangle, w/EOL" {
; ;
try s.testWriteString(str); try s.testWriteString(str);
const contents = try s.selectionString(alloc, sel, true); const contents = try s.selectionString(alloc, .{
.sel = sel,
.trim = true,
});
defer alloc.free(contents); defer alloc.free(contents);
try testing.expectEqualStrings(expected, contents); try testing.expectEqualStrings(expected, contents);
} }
@ -6380,7 +6431,10 @@ test "Screen: selectionString, rectangle, more complex w/breaks" {
; ;
try s.testWriteString(str); try s.testWriteString(str);
const contents = try s.selectionString(alloc, sel, true); const contents = try s.selectionString(alloc, .{
.sel = sel,
.trim = true,
});
defer alloc.free(contents); defer alloc.free(contents);
try testing.expectEqualStrings(expected, contents); try testing.expectEqualStrings(expected, contents);
} }
@ -6398,13 +6452,19 @@ test "Screen: lineIterator" {
var iter = s.lineIterator(s.pages.pin(.{ .viewport = .{} }).?); var iter = s.lineIterator(s.pages.pin(.{ .viewport = .{} }).?);
{ {
const sel = iter.next().?; const sel = iter.next().?;
const actual = try s.selectionString(alloc, sel, false); const actual = try s.selectionString(alloc, .{
.sel = sel,
.trim = false,
});
defer alloc.free(actual); defer alloc.free(actual);
try testing.expectEqualStrings("1ABCD", actual); try testing.expectEqualStrings("1ABCD", actual);
} }
{ {
const sel = iter.next().?; const sel = iter.next().?;
const actual = try s.selectionString(alloc, sel, false); const actual = try s.selectionString(alloc, .{
.sel = sel,
.trim = false,
});
defer alloc.free(actual); defer alloc.free(actual);
try testing.expectEqualStrings("2EFGH", actual); try testing.expectEqualStrings("2EFGH", actual);
} }
@ -6423,13 +6483,19 @@ test "Screen: lineIterator soft wrap" {
var iter = s.lineIterator(s.pages.pin(.{ .viewport = .{} }).?); var iter = s.lineIterator(s.pages.pin(.{ .viewport = .{} }).?);
{ {
const sel = iter.next().?; const sel = iter.next().?;
const actual = try s.selectionString(alloc, sel, false); const actual = try s.selectionString(alloc, .{
.sel = sel,
.trim = false,
});
defer alloc.free(actual); defer alloc.free(actual);
try testing.expectEqualStrings("1ABCD2EFGH", actual); try testing.expectEqualStrings("1ABCD2EFGH", actual);
} }
{ {
const sel = iter.next().?; const sel = iter.next().?;
const actual = try s.selectionString(alloc, sel, false); const actual = try s.selectionString(alloc, .{
.sel = sel,
.trim = false,
});
defer alloc.free(actual); defer alloc.free(actual);
try testing.expectEqualStrings("3ABCD", actual); try testing.expectEqualStrings("3ABCD", actual);
} }

View File

@ -35,6 +35,7 @@ pub const Pin = PageList.Pin;
pub const Screen = @import("Screen.zig"); pub const Screen = @import("Screen.zig");
pub const ScreenType = Terminal.ScreenType; pub const ScreenType = Terminal.ScreenType;
pub const Selection = @import("Selection.zig"); pub const Selection = @import("Selection.zig");
//pub const StringMap = @import("StringMap.zig");
pub const Style = style.Style; pub const Style = style.Style;
pub const Terminal = @import("Terminal.zig"); pub const Terminal = @import("Terminal.zig");
pub const Stream = stream.Stream; pub const Stream = stream.Stream;