mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 08:16:13 +03:00
terminal: selectionString uses arraylist to build results
This commit is contained in:
@ -2140,62 +2140,14 @@ pub fn selectionString(
|
|||||||
// Get the slices for the string
|
// Get the slices for the string
|
||||||
const slices = self.selectionSlices(sel);
|
const slices = self.selectionSlices(sel);
|
||||||
|
|
||||||
// We can now know how much space we'll need to store the string. We loop
|
// Use an ArrayList so that we can grow the array as we go. We
|
||||||
// over and UTF8-encode and calculate the exact size required. We will be
|
// build an initial capacity of just our rows in our selection times
|
||||||
// off here by at most "newlines" values in the worst case that every
|
// columns. It can be more or less based on graphemes, newlines, etc.
|
||||||
// single line is soft-wrapped.
|
var strbuilder = try std.ArrayList(u8).initCapacity(alloc, slices.rows * self.cols);
|
||||||
const chars = chars: {
|
defer strbuilder.deinit();
|
||||||
var count: usize = 0;
|
|
||||||
|
|
||||||
// We need to keep track of our x/y so that we can get graphemes.
|
|
||||||
var y: usize = slices.sel.start.y;
|
|
||||||
var x: usize = 0;
|
|
||||||
var row: Row = undefined;
|
|
||||||
|
|
||||||
const arr = [_][]StorageCell{ slices.top, slices.bot };
|
|
||||||
for (arr) |slice| {
|
|
||||||
for (slice, 0..) |cell, i| {
|
|
||||||
// detect row headers
|
|
||||||
if (@mod(i, self.cols + 1) == 0) {
|
|
||||||
// We use each row header as an opportunity to "count"
|
|
||||||
// a new row, and therefore count a possible newline.
|
|
||||||
count += 1;
|
|
||||||
|
|
||||||
// Increase our row count and get our next row
|
|
||||||
y += 1;
|
|
||||||
x = 0;
|
|
||||||
row = self.getRow(.{ .screen = y - 1 });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf: [4]u8 = undefined;
|
|
||||||
const char = if (cell.cell.char > 0) cell.cell.char else ' ';
|
|
||||||
count += try std.unicode.utf8Encode(@intCast(char), &buf);
|
|
||||||
|
|
||||||
// We need to also count any grapheme chars
|
|
||||||
var it = row.codepointIterator(x);
|
|
||||||
while (it.next()) |cp| {
|
|
||||||
count += try std.unicode.utf8Encode(cp, &buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
x += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break :chars count;
|
|
||||||
};
|
|
||||||
const buf = try alloc.alloc(u8, chars + 1);
|
|
||||||
errdefer alloc.free(buf);
|
|
||||||
|
|
||||||
// Special case the empty case
|
|
||||||
if (chars == 0) {
|
|
||||||
buf[0] = 0;
|
|
||||||
return buf[0..0 :0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect the text from the two slices
|
// Connect the text from the two slices
|
||||||
const arr = [_][]StorageCell{ slices.top, slices.bot };
|
const arr = [_][]StorageCell{ slices.top, slices.bot };
|
||||||
var buf_i: usize = 0;
|
|
||||||
var row_count: usize = 0;
|
var row_count: usize = 0;
|
||||||
for (arr) |slice| {
|
for (arr) |slice| {
|
||||||
const row_start: usize = row_count;
|
const row_start: usize = row_count;
|
||||||
@ -2215,6 +2167,13 @@ pub fn selectionString(
|
|||||||
// the first row.
|
// the first row.
|
||||||
var skip: usize = if (row_count == 0) slices.top_offset else 0;
|
var skip: usize = if (row_count == 0) slices.top_offset else 0;
|
||||||
|
|
||||||
|
// If we have runtime safety we need to initialize the row
|
||||||
|
// so that the proper union tag is set. In release modes we
|
||||||
|
// don't need to do this because we zero the memory.
|
||||||
|
if (std.debug.runtime_safety) {
|
||||||
|
_ = self.getRow(.{ .screen = slices.sel.start.y + row_i });
|
||||||
|
}
|
||||||
|
|
||||||
const row: Row = .{ .screen = self, .storage = slice[start_idx..end_idx] };
|
const row: Row = .{ .screen = self, .storage = slice[start_idx..end_idx] };
|
||||||
var it = row.cellIterator();
|
var it = row.cellIterator();
|
||||||
var x: usize = 0;
|
var x: usize = 0;
|
||||||
@ -2230,50 +2189,60 @@ pub fn selectionString(
|
|||||||
if (cell.attrs.wide_spacer_head or
|
if (cell.attrs.wide_spacer_head or
|
||||||
cell.attrs.wide_spacer_tail) continue;
|
cell.attrs.wide_spacer_tail) continue;
|
||||||
|
|
||||||
|
var buf: [4]u8 = undefined;
|
||||||
const char = if (cell.char > 0) cell.char else ' ';
|
const char = if (cell.char > 0) cell.char else ' ';
|
||||||
buf_i += try std.unicode.utf8Encode(@intCast(char), buf[buf_i..]);
|
{
|
||||||
|
const encode_len = try std.unicode.utf8Encode(@intCast(char), &buf);
|
||||||
|
try strbuilder.appendSlice(buf[0..encode_len]);
|
||||||
|
}
|
||||||
|
|
||||||
var cp_it = row.codepointIterator(x);
|
var cp_it = row.codepointIterator(x);
|
||||||
while (cp_it.next()) |cp| {
|
while (cp_it.next()) |cp| {
|
||||||
buf_i += try std.unicode.utf8Encode(cp, buf[buf_i..]);
|
const encode_len = try std.unicode.utf8Encode(cp, &buf);
|
||||||
|
try strbuilder.appendSlice(buf[0..encode_len]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this row is not soft-wrapped, add a newline
|
// If this row is not soft-wrapped, add a newline
|
||||||
if (!row.header().flags.wrap) {
|
if (!row.header().flags.wrap) try strbuilder.append('\n');
|
||||||
buf[buf_i] = '\n';
|
|
||||||
buf_i += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove our trailing newline, its never correct.
|
// Remove our trailing newline, its never correct.
|
||||||
if (buf_i > 0 and buf[buf_i - 1] == '\n') buf_i -= 1;
|
if (strbuilder.items.len > 0 and
|
||||||
|
strbuilder.items[strbuilder.items.len - 1] == '\n')
|
||||||
|
{
|
||||||
|
strbuilder.items.len -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
// 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 (trim) {
|
||||||
var it = std.mem.tokenize(u8, buf[0..buf_i], "\n");
|
var it = std.mem.tokenize(u8, strbuilder.items, "\n");
|
||||||
buf_i = 0;
|
|
||||||
|
// 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| {
|
while (it.next()) |line| {
|
||||||
const trimmed = std.mem.trimRight(u8, line, " \t");
|
const trimmed = std.mem.trimRight(u8, line, " \t");
|
||||||
std.mem.copy(u8, buf[buf_i..], trimmed);
|
const i = strbuilder.items.len;
|
||||||
buf_i += trimmed.len;
|
strbuilder.items.len += trimmed.len;
|
||||||
buf[buf_i] = '\n';
|
std.mem.copyForwards(u8, strbuilder.items[i..], trimmed);
|
||||||
buf_i += 1;
|
strbuilder.appendAssumeCapacity('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove our trailing newline again
|
// Remove our trailing newline again
|
||||||
if (buf_i > 0) buf_i -= 1;
|
if (strbuilder.items.len > 0) strbuilder.items.len -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add null termination
|
// Get our final string
|
||||||
buf[buf_i] = 0;
|
const string = try strbuilder.toOwnedSliceSentinel(0);
|
||||||
|
errdefer alloc.free(string);
|
||||||
|
|
||||||
// Realloc so our free length is exactly correct
|
return string;
|
||||||
const result = try alloc.realloc(buf, buf_i + 1);
|
|
||||||
return result[0..buf_i :0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the slices that make up the selection, in order. There are at most
|
/// Returns the slices that make up the selection, in order. There are at most
|
||||||
|
Reference in New Issue
Block a user