mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
selection of wide chars and copy/paste works
This commit is contained in:
@ -25,8 +25,10 @@ const Screen = @This();
|
|||||||
// one day.
|
// one day.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const utf8proc = @import("utf8proc");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const color = @import("color.zig");
|
const color = @import("color.zig");
|
||||||
const point = @import("point.zig");
|
const point = @import("point.zig");
|
||||||
const Selection = @import("Selection.zig");
|
const Selection = @import("Selection.zig");
|
||||||
@ -941,6 +943,10 @@ pub fn selectionString(self: Screen, alloc: Allocator, sel: Selection) ![:0]cons
|
|||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip spacers
|
||||||
|
if (cell.attrs.wide_spacer_head == 1 or
|
||||||
|
cell.attrs.wide_spacer_tail == 1) continue;
|
||||||
|
|
||||||
const char = if (cell.char > 0) cell.char else ' ';
|
const char = if (cell.char > 0) cell.char else ' ';
|
||||||
i += try std.unicode.utf8Encode(@intCast(u21, char), buf[i..]);
|
i += try std.unicode.utf8Encode(@intCast(u21, char), buf[i..]);
|
||||||
}
|
}
|
||||||
@ -964,6 +970,10 @@ pub fn selectionString(self: Screen, alloc: Allocator, sel: Selection) ![:0]cons
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip spacers
|
||||||
|
if (cell.attrs.wide_spacer_head == 1 or
|
||||||
|
cell.attrs.wide_spacer_tail == 1) continue;
|
||||||
|
|
||||||
const char = if (cell.char > 0) cell.char else ' ';
|
const char = if (cell.char > 0) cell.char else ' ';
|
||||||
i += try std.unicode.utf8Encode(@intCast(u21, char), buf[i..]);
|
i += try std.unicode.utf8Encode(@intCast(u21, char), buf[i..]);
|
||||||
}
|
}
|
||||||
@ -979,7 +989,7 @@ pub fn selectionString(self: Screen, alloc: Allocator, sel: Selection) ![:0]cons
|
|||||||
/// 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
|
||||||
/// two parts to handle the ring buffer. If the selection fits in one contiguous
|
/// two parts to handle the ring buffer. If the selection fits in one contiguous
|
||||||
/// slice, then the second slice will have a length of zero.
|
/// slice, then the second slice will have a length of zero.
|
||||||
fn selectionSlices(self: Screen, sel: Selection) struct {
|
fn selectionSlices(self: Screen, sel_raw: Selection) struct {
|
||||||
// Top offset can be used to determine if a newline is required by
|
// Top offset can be used to determine if a newline is required by
|
||||||
// seeing if the cell index plus the offset cleanly divides by screen cols.
|
// seeing if the cell index plus the offset cleanly divides by screen cols.
|
||||||
top_offset: usize,
|
top_offset: usize,
|
||||||
@ -988,10 +998,35 @@ fn selectionSlices(self: Screen, sel: Selection) struct {
|
|||||||
} {
|
} {
|
||||||
// Note: this function is tested via selectionString
|
// Note: this function is tested via selectionString
|
||||||
|
|
||||||
assert(sel.start.y < self.totalRows());
|
assert(sel_raw.start.y < self.totalRows());
|
||||||
assert(sel.end.y < self.totalRows());
|
assert(sel_raw.end.y < self.totalRows());
|
||||||
assert(sel.start.x < self.cols);
|
assert(sel_raw.start.x < self.cols);
|
||||||
assert(sel.end.x < self.cols);
|
assert(sel_raw.end.x < self.cols);
|
||||||
|
|
||||||
|
const sel = sel: {
|
||||||
|
var sel = sel_raw;
|
||||||
|
|
||||||
|
// If the end of our selection is a wide char leader, include the
|
||||||
|
// first part of the next line.
|
||||||
|
if (sel.end.x == self.cols - 1) {
|
||||||
|
const row = self.getRow(.{ .screen = sel.end.y });
|
||||||
|
if (row[sel.end.x].attrs.wide_spacer_head == 1) {
|
||||||
|
sel.end.y += 1;
|
||||||
|
sel.end.x = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the start of our selection is a wide char spacer, include the
|
||||||
|
// wide char.
|
||||||
|
if (sel.start.x > 0) {
|
||||||
|
const row = self.getRow(.{ .screen = sel.start.y });
|
||||||
|
if (row[sel.start.x].attrs.wide_spacer_tail == 1) {
|
||||||
|
sel.end.x -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break :sel sel;
|
||||||
|
};
|
||||||
|
|
||||||
// Get the true "top" and "bottom"
|
// Get the true "top" and "bottom"
|
||||||
const sel_top = sel.topLeft();
|
const sel_top = sel.topLeft();
|
||||||
@ -1053,7 +1088,10 @@ pub fn testString(self: Screen, alloc: Allocator, tag: RowIndexTag) ![]const u8
|
|||||||
fn testWriteString(self: *Screen, text: []const u8) void {
|
fn testWriteString(self: *Screen, text: []const u8) void {
|
||||||
var y: usize = 0;
|
var y: usize = 0;
|
||||||
var x: usize = 0;
|
var x: usize = 0;
|
||||||
for (text) |c| {
|
|
||||||
|
const view = std.unicode.Utf8View.init(text) catch unreachable;
|
||||||
|
var iter = view.iterator();
|
||||||
|
while (iter.nextCodepoint()) |c| {
|
||||||
// Explicit newline forces a new row
|
// Explicit newline forces a new row
|
||||||
if (c == '\n') {
|
if (c == '\n') {
|
||||||
y += 1;
|
y += 1;
|
||||||
@ -1082,7 +1120,39 @@ fn testWriteString(self: *Screen, text: []const u8) void {
|
|||||||
row = self.getRow(.{ .active = y });
|
row = self.getRow(.{ .active = y });
|
||||||
}
|
}
|
||||||
|
|
||||||
row[x].char = @intCast(u32, c);
|
// If our character is double-width, handle it.
|
||||||
|
const width = utf8proc.charwidth(c);
|
||||||
|
assert(width == 1 or width == 2);
|
||||||
|
switch (width) {
|
||||||
|
1 => row[x].char = @intCast(u32, c),
|
||||||
|
|
||||||
|
2 => {
|
||||||
|
if (x == self.cols - 1) {
|
||||||
|
row[x].char = ' ';
|
||||||
|
row[x].attrs.wide_spacer_head = 1;
|
||||||
|
|
||||||
|
// wrap
|
||||||
|
row[x].attrs.wrap = 1;
|
||||||
|
y += 1;
|
||||||
|
x = 0;
|
||||||
|
if (y >= self.rows) {
|
||||||
|
y -= 1;
|
||||||
|
self.scroll(.{ .delta = 1 });
|
||||||
|
}
|
||||||
|
row = self.getRow(.{ .active = y });
|
||||||
|
}
|
||||||
|
|
||||||
|
row[x].char = @intCast(u32, c);
|
||||||
|
row[x].attrs.wide = 1;
|
||||||
|
|
||||||
|
x += 1;
|
||||||
|
row[x].char = ' ';
|
||||||
|
row[x].attrs.wide_spacer_tail = 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
|
||||||
x += 1;
|
x += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1468,6 +1538,66 @@ test "Screen: selectionString wrap around" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "Screen: selectionString wide char" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, 3, 5, 0);
|
||||||
|
defer s.deinit(alloc);
|
||||||
|
const str = "1A⚡";
|
||||||
|
s.testWriteString(str);
|
||||||
|
|
||||||
|
{
|
||||||
|
var contents = try s.selectionString(alloc, .{
|
||||||
|
.start = .{ .x = 0, .y = 0 },
|
||||||
|
.end = .{ .x = 3, .y = 0 },
|
||||||
|
});
|
||||||
|
defer alloc.free(contents);
|
||||||
|
const expected = str;
|
||||||
|
try testing.expectEqualStrings(expected, contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var contents = try s.selectionString(alloc, .{
|
||||||
|
.start = .{ .x = 0, .y = 0 },
|
||||||
|
.end = .{ .x = 2, .y = 0 },
|
||||||
|
});
|
||||||
|
defer alloc.free(contents);
|
||||||
|
const expected = str;
|
||||||
|
try testing.expectEqualStrings(expected, contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var contents = try s.selectionString(alloc, .{
|
||||||
|
.start = .{ .x = 3, .y = 0 },
|
||||||
|
.end = .{ .x = 3, .y = 0 },
|
||||||
|
});
|
||||||
|
defer alloc.free(contents);
|
||||||
|
const expected = "⚡";
|
||||||
|
try testing.expectEqualStrings(expected, contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Screen: selectionString wide char with header" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, 3, 5, 0);
|
||||||
|
defer s.deinit(alloc);
|
||||||
|
const str = "1ABC⚡";
|
||||||
|
s.testWriteString(str);
|
||||||
|
|
||||||
|
{
|
||||||
|
var contents = try s.selectionString(alloc, .{
|
||||||
|
.start = .{ .x = 0, .y = 0 },
|
||||||
|
.end = .{ .x = 4, .y = 0 },
|
||||||
|
});
|
||||||
|
defer alloc.free(contents);
|
||||||
|
const expected = str;
|
||||||
|
try testing.expectEqualStrings(expected, contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "Screen: resize more rows no scrollback" {
|
test "Screen: resize more rows no scrollback" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
@ -49,11 +49,11 @@ pub fn Stream(comptime Handler: type) type {
|
|||||||
//log.debug("char: {x}", .{c});
|
//log.debug("char: {x}", .{c});
|
||||||
const actions = self.parser.next(c);
|
const actions = self.parser.next(c);
|
||||||
for (actions) |action_opt| {
|
for (actions) |action_opt| {
|
||||||
if (action_opt) |action| {
|
// if (action_opt) |action| {
|
||||||
if (action != .print) {
|
// if (action != .print) {
|
||||||
log.info("action: {}", .{action});
|
// log.info("action: {}", .{action});
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
switch (action_opt orelse continue) {
|
switch (action_opt orelse continue) {
|
||||||
.print => |p| if (@hasDecl(T, "print")) try self.handler.print(p),
|
.print => |p| if (@hasDecl(T, "print")) try self.handler.print(p),
|
||||||
.execute => |code| try self.execute(code),
|
.execute => |code| try self.execute(code),
|
||||||
|
Reference in New Issue
Block a user