mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
terminal2: selectionString beginning
This commit is contained in:
@ -5543,6 +5543,7 @@ test "Screen: clear above cursor with history" {
|
|||||||
try testing.expectEqual(@as(usize, 0), s.cursor.y);
|
try testing.expectEqual(@as(usize, 0), s.cursor.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// X
|
||||||
test "Screen: selectionString basic" {
|
test "Screen: selectionString basic" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
@ -5563,6 +5564,7 @@ test "Screen: selectionString basic" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// X
|
||||||
test "Screen: selectionString start outside of written area" {
|
test "Screen: selectionString start outside of written area" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
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" {
|
test "Screen: selectionString end outside of written area" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
@ -5603,6 +5606,7 @@ test "Screen: selectionString end outside of written area" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// X
|
||||||
test "Screen: selectionString trim space" {
|
test "Screen: selectionString trim space" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
@ -830,18 +830,83 @@ pub fn manualStyleUpdate(self: *Screen) !void {
|
|||||||
/// 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,
|
self: *Screen,
|
||||||
// alloc: Allocator,
|
alloc: Allocator,
|
||||||
// sel: Selection,
|
sel: Selection,
|
||||||
// trim: bool,
|
trim: bool,
|
||||||
// ) ![:0]const u8 {
|
) ![:0]const u8 {
|
||||||
// _ = self;
|
// Use an ArrayList so that we can grow the array as we go. We
|
||||||
// _ = alloc;
|
// build an initial capacity of just our rows in our selection times
|
||||||
// _ = sel;
|
// columns. It can be more or less based on graphemes, newlines, etc.
|
||||||
// _ = trim;
|
var strbuilder = std.ArrayList(u8).init(alloc);
|
||||||
// @panic("TODO");
|
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
|
/// 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
|
/// 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);
|
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);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user