terminal2: selectionString beginning

This commit is contained in:
Mitchell Hashimoto
2024-03-06 22:10:58 -08:00
parent 7ee6447191
commit 44986a0dcc
2 changed files with 179 additions and 12 deletions

View File

@ -5543,6 +5543,7 @@ test "Screen: clear above cursor with history" {
try testing.expectEqual(@as(usize, 0), s.cursor.y);
}
// X
test "Screen: selectionString basic" {
const testing = std.testing;
const alloc = testing.allocator;
@ -5563,6 +5564,7 @@ test "Screen: selectionString basic" {
}
}
// X
test "Screen: selectionString start outside of written area" {
const testing = std.testing;
const alloc = testing.allocator;
@ -5583,6 +5585,7 @@ test "Screen: selectionString start outside of written area" {
}
}
// X
test "Screen: selectionString end outside of written area" {
const testing = std.testing;
const alloc = testing.allocator;
@ -5603,6 +5606,7 @@ test "Screen: selectionString end outside of written area" {
}
}
// X
test "Screen: selectionString trim space" {
const testing = std.testing;
const alloc = testing.allocator;

View File

@ -830,18 +830,83 @@ pub fn manualStyleUpdate(self: *Screen) !void {
/// Returns the raw text associated with a selection. This will unwrap
/// 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).
// pub fn selectionString(
// self: *Screen,
// alloc: Allocator,
// sel: Selection,
// trim: bool,
// ) ![:0]const u8 {
// _ = self;
// _ = alloc;
// _ = sel;
// _ = trim;
// @panic("TODO");
// }
pub fn selectionString(
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
// build an initial capacity of just our rows in our selection times
// columns. It can be more or less based on graphemes, newlines, etc.
var strbuilder = std.ArrayList(u8).init(alloc);
defer strbuilder.deinit();
const sel_ordered = sel.ordered(self, .forward);
var page_it = sel.start().pageIterator(.right_down, sel.end());
var row_count: usize = 0;
while (page_it.next()) |chunk| {
const rows = chunk.rows();
for (rows) |row| {
const cells_ptr = row.cells.ptr(chunk.page.data.memory);
const start_x = if (row_count == 0 or sel_ordered.rectangle)
sel_ordered.start().x
else
0;
const end_x = if (row_count == rows.len - 1 or sel_ordered.rectangle)
sel_ordered.end().x + 1
else
self.pages.cols;
const cells = cells_ptr[start_x..end_x];
for (cells) |cell| {
if (!cell.hasText()) continue;
const char = if (cell.content.codepoint > 0) cell.content.codepoint else ' ';
var buf: [4]u8 = undefined;
const encode_len = try std.unicode.utf8Encode(char, &buf);
try strbuilder.appendSlice(buf[0..encode_len]);
}
// TODO: graphemes
if (!row.wrap or sel_ordered.rectangle) {
try strbuilder.append('\n');
}
row_count += 1;
}
}
// 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
// this is simple.
if (trim) {
var it = std.mem.tokenizeScalar(u8, strbuilder.items, '\n');
// Reset our items. We retain our capacity. Because we're only
// removing bytes, we know that the trimmed string must be no longer
// than the original string so we copy directly back into our
// allocated memory.
strbuilder.clearRetainingCapacity();
while (it.next()) |line| {
const trimmed = std.mem.trimRight(u8, line, " \t");
const i = strbuilder.items.len;
strbuilder.items.len += trimmed.len;
std.mem.copyForwards(u8, strbuilder.items[i..], trimmed);
strbuilder.appendAssumeCapacity('\n');
}
// Remove our trailing newline again
if (strbuilder.items.len > 0) strbuilder.items.len -= 1;
}
// Get our final string
const string = try strbuilder.toOwnedSliceSentinel(0);
errdefer alloc.free(string);
return string;
}
/// 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
@ -5001,3 +5066,101 @@ test "Screen: promptPath" {
try testing.expectEqual(@as(isize, 1), path.y);
}
}
test "Screen: selectionString basic" {
const testing = std.testing;
const alloc = testing.allocator;
var s = try init(alloc, 5, 3, 0);
defer s.deinit();
const str = "1ABCD\n2EFGH\n3IJKL";
try s.testWriteString(str);
{
const sel = Selection.init(
s.pages.pin(.{ .screen = .{ .x = 0, .y = 1 } }).?,
s.pages.pin(.{ .screen = .{ .x = 2, .y = 2 } }).?,
false,
);
const contents = try s.selectionString(alloc, sel, true);
defer alloc.free(contents);
const expected = "2EFGH\n3IJ";
try testing.expectEqualStrings(expected, contents);
}
}
test "Screen: selectionString start outside of written area" {
const testing = std.testing;
const alloc = testing.allocator;
var s = try init(alloc, 5, 10, 0);
defer s.deinit();
const str = "1ABCD\n2EFGH\n3IJKL";
try s.testWriteString(str);
{
const sel = Selection.init(
s.pages.pin(.{ .screen = .{ .x = 0, .y = 5 } }).?,
s.pages.pin(.{ .screen = .{ .x = 2, .y = 6 } }).?,
false,
);
const contents = try s.selectionString(alloc, sel, true);
defer alloc.free(contents);
const expected = "";
try testing.expectEqualStrings(expected, contents);
}
}
test "Screen: selectionString end outside of written area" {
const testing = std.testing;
const alloc = testing.allocator;
var s = try init(alloc, 5, 10, 0);
defer s.deinit();
const str = "1ABCD\n2EFGH\n3IJKL";
try s.testWriteString(str);
{
const sel = Selection.init(
s.pages.pin(.{ .screen = .{ .x = 0, .y = 2 } }).?,
s.pages.pin(.{ .screen = .{ .x = 2, .y = 6 } }).?,
false,
);
const contents = try s.selectionString(alloc, sel, true);
defer alloc.free(contents);
const expected = "3IJKL";
try testing.expectEqualStrings(expected, contents);
}
}
test "Screen: selectionString trim space" {
const testing = std.testing;
const alloc = testing.allocator;
var s = try init(alloc, 5, 3, 0);
defer s.deinit();
const str = "1AB \n2EFGH\n3IJKL";
try s.testWriteString(str);
const sel = Selection.init(
s.pages.pin(.{ .screen = .{ .x = 0, .y = 0 } }).?,
s.pages.pin(.{ .screen = .{ .x = 2, .y = 1 } }).?,
false,
);
{
const contents = try s.selectionString(alloc, sel, true);
defer alloc.free(contents);
const expected = "1AB\n2EF";
try testing.expectEqualStrings(expected, contents);
}
// No trim
// TODO(paged-terminal): we need to trim unwritten space
// {
// const contents = try s.selectionString(alloc, sel, false);
// defer alloc.free(contents);
// const expected = "1AB \n2EF";
// try testing.expectEqualStrings(expected, contents);
// }
}