mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-20 02:36:22 +03:00
terminal2: selectWord starts
This commit is contained in:
@ -4672,6 +4672,7 @@ test "Screen: selectLine with scrollback" {
|
||||
}
|
||||
}
|
||||
|
||||
// X
|
||||
test "Screen: selectWord" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
@ -1017,6 +1017,114 @@ pub fn selectAll(self: *Screen) ?Selection {
|
||||
return Selection.init(start, end, false);
|
||||
}
|
||||
|
||||
/// Select the word under the given point. A word is any consecutive series
|
||||
/// of characters that are exclusively whitespace or exclusively non-whitespace.
|
||||
/// A selection can span multiple physical lines if they are soft-wrapped.
|
||||
///
|
||||
/// This will return null if a selection is impossible. The only scenario
|
||||
/// this happens is if the point pt is outside of the written screen space.
|
||||
pub fn selectWord(self: *Screen, pin: Pin) ?Selection {
|
||||
_ = self;
|
||||
|
||||
// Boundary characters for selection purposes
|
||||
const boundary = &[_]u32{
|
||||
0,
|
||||
' ',
|
||||
'\t',
|
||||
'\'',
|
||||
'"',
|
||||
'│',
|
||||
'`',
|
||||
'|',
|
||||
':',
|
||||
',',
|
||||
'(',
|
||||
')',
|
||||
'[',
|
||||
']',
|
||||
'{',
|
||||
'}',
|
||||
'<',
|
||||
'>',
|
||||
};
|
||||
|
||||
// If our cell is empty we can't select a word, because we can't select
|
||||
// areas where the screen is not yet written.
|
||||
const start_cell = pin.rowAndCell().cell;
|
||||
if (!start_cell.hasText()) return null;
|
||||
|
||||
// Determine if we are a boundary or not to determine what our boundary is.
|
||||
const expect_boundary = std.mem.indexOfAny(
|
||||
u32,
|
||||
boundary,
|
||||
&[_]u32{start_cell.content.codepoint},
|
||||
) != null;
|
||||
|
||||
// Go forwards to find our end boundary
|
||||
const end: Pin = end: {
|
||||
var it = pin.cellIterator(.right_down, null);
|
||||
var prev = it.next().?; // Consume one, our start
|
||||
while (it.next()) |p| {
|
||||
const rac = p.rowAndCell();
|
||||
const cell = rac.cell;
|
||||
|
||||
// If we are going to the next row and it isn't wrapped, we
|
||||
// return the previous.
|
||||
if (p.x == 0 and !rac.row.wrap) {
|
||||
break :end prev;
|
||||
}
|
||||
|
||||
// If we reached an empty cell its always a boundary
|
||||
if (!cell.hasText()) break :end prev;
|
||||
|
||||
// If we do not match our expected set, we hit a boundary
|
||||
const this_boundary = std.mem.indexOfAny(
|
||||
u32,
|
||||
boundary,
|
||||
&[_]u32{cell.content.codepoint},
|
||||
) != null;
|
||||
if (this_boundary != expect_boundary) break :end prev;
|
||||
|
||||
prev = p;
|
||||
}
|
||||
|
||||
break :end prev;
|
||||
};
|
||||
|
||||
// Go backwards to find our start boundary
|
||||
const start: Pin = start: {
|
||||
var it = pin.cellIterator(.left_up, null);
|
||||
var prev = it.next().?; // Consume one, our start
|
||||
while (it.next()) |p| {
|
||||
const rac = p.rowAndCell();
|
||||
const cell = rac.cell;
|
||||
|
||||
// If we are going to the next row and it isn't wrapped, we
|
||||
// return the previous.
|
||||
if (p.x == p.page.data.size.cols - 1 and !rac.row.wrap) {
|
||||
break :start prev;
|
||||
}
|
||||
|
||||
// If we reached an empty cell its always a boundary
|
||||
if (!cell.hasText()) break :start prev;
|
||||
|
||||
// If we do not match our expected set, we hit a boundary
|
||||
const this_boundary = std.mem.indexOfAny(
|
||||
u32,
|
||||
boundary,
|
||||
&[_]u32{cell.content.codepoint},
|
||||
) != null;
|
||||
if (this_boundary != expect_boundary) break :start prev;
|
||||
|
||||
prev = p;
|
||||
}
|
||||
|
||||
break :start prev;
|
||||
};
|
||||
|
||||
return Selection.init(start, end, false);
|
||||
}
|
||||
|
||||
/// Dump the screen to a string. The writer given should be buffered;
|
||||
/// this function does not attempt to efficiently write and generally writes
|
||||
/// one byte at a time.
|
||||
@ -3922,3 +4030,118 @@ test "Screen: selectLine semantic prompt boundary" {
|
||||
} }, s.pages.pointFromPin(.active, sel.end().*).?);
|
||||
}
|
||||
}
|
||||
|
||||
test "Screen: selectWord" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var s = try init(alloc, 10, 10, 0);
|
||||
defer s.deinit();
|
||||
try s.testWriteString("ABC DEF\n 123\n456");
|
||||
|
||||
// Outside of active area
|
||||
// try testing.expect(s.selectWord(.{ .x = 9, .y = 0 }) == null);
|
||||
// try testing.expect(s.selectWord(.{ .x = 0, .y = 5 }) == null);
|
||||
|
||||
// Going forward
|
||||
{
|
||||
var sel = s.selectWord(s.pages.pin(.{ .active = .{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
} }).?).?;
|
||||
defer sel.deinit(&s);
|
||||
try testing.expectEqual(point.Point{ .screen = .{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
} }, s.pages.pointFromPin(.screen, sel.start().*).?);
|
||||
try testing.expectEqual(point.Point{ .screen = .{
|
||||
.x = 2,
|
||||
.y = 0,
|
||||
} }, s.pages.pointFromPin(.screen, sel.end().*).?);
|
||||
}
|
||||
|
||||
// Going backward
|
||||
{
|
||||
var sel = s.selectWord(s.pages.pin(.{ .active = .{
|
||||
.x = 2,
|
||||
.y = 0,
|
||||
} }).?).?;
|
||||
defer sel.deinit(&s);
|
||||
try testing.expectEqual(point.Point{ .screen = .{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
} }, s.pages.pointFromPin(.screen, sel.start().*).?);
|
||||
try testing.expectEqual(point.Point{ .screen = .{
|
||||
.x = 2,
|
||||
.y = 0,
|
||||
} }, s.pages.pointFromPin(.screen, sel.end().*).?);
|
||||
}
|
||||
|
||||
// Going forward and backward
|
||||
{
|
||||
var sel = s.selectWord(s.pages.pin(.{ .active = .{
|
||||
.x = 1,
|
||||
.y = 0,
|
||||
} }).?).?;
|
||||
defer sel.deinit(&s);
|
||||
try testing.expectEqual(point.Point{ .screen = .{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
} }, s.pages.pointFromPin(.screen, sel.start().*).?);
|
||||
try testing.expectEqual(point.Point{ .screen = .{
|
||||
.x = 2,
|
||||
.y = 0,
|
||||
} }, s.pages.pointFromPin(.screen, sel.end().*).?);
|
||||
}
|
||||
|
||||
// Whitespace
|
||||
{
|
||||
var sel = s.selectWord(s.pages.pin(.{ .active = .{
|
||||
.x = 3,
|
||||
.y = 0,
|
||||
} }).?).?;
|
||||
defer sel.deinit(&s);
|
||||
try testing.expectEqual(point.Point{ .screen = .{
|
||||
.x = 3,
|
||||
.y = 0,
|
||||
} }, s.pages.pointFromPin(.screen, sel.start().*).?);
|
||||
try testing.expectEqual(point.Point{ .screen = .{
|
||||
.x = 4,
|
||||
.y = 0,
|
||||
} }, s.pages.pointFromPin(.screen, sel.end().*).?);
|
||||
}
|
||||
|
||||
// Whitespace single char
|
||||
{
|
||||
var sel = s.selectWord(s.pages.pin(.{ .active = .{
|
||||
.x = 0,
|
||||
.y = 1,
|
||||
} }).?).?;
|
||||
defer sel.deinit(&s);
|
||||
try testing.expectEqual(point.Point{ .screen = .{
|
||||
.x = 0,
|
||||
.y = 1,
|
||||
} }, s.pages.pointFromPin(.screen, sel.start().*).?);
|
||||
try testing.expectEqual(point.Point{ .screen = .{
|
||||
.x = 0,
|
||||
.y = 1,
|
||||
} }, s.pages.pointFromPin(.screen, sel.end().*).?);
|
||||
}
|
||||
|
||||
// End of screen
|
||||
{
|
||||
var sel = s.selectWord(s.pages.pin(.{ .active = .{
|
||||
.x = 1,
|
||||
.y = 2,
|
||||
} }).?).?;
|
||||
defer sel.deinit(&s);
|
||||
try testing.expectEqual(point.Point{ .screen = .{
|
||||
.x = 0,
|
||||
.y = 2,
|
||||
} }, s.pages.pointFromPin(.screen, sel.start().*).?);
|
||||
try testing.expectEqual(point.Point{ .screen = .{
|
||||
.x = 2,
|
||||
.y = 2,
|
||||
} }, s.pages.pointFromPin(.screen, sel.end().*).?);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user