mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-19 18:26:13 +03:00
selectWord goes across soft-wrapped lines
This commit is contained in:
@ -1562,7 +1562,16 @@ fn mouseButtonCallback(
|
|||||||
log.err("error scheduling render in mouseButtinCallback err={}", .{err});
|
log.err("error scheduling render in mouseButtinCallback err={}", .{err});
|
||||||
},
|
},
|
||||||
|
|
||||||
2 => log.info("DoublE CLICk", .{}),
|
// Double click, select the word under our mouse
|
||||||
|
2 => {
|
||||||
|
const sel_ = win.io.terminal.screen.selectWord(win.mouse.left_click_point);
|
||||||
|
if (sel_) |sel| {
|
||||||
|
win.io.terminal.selection = sel;
|
||||||
|
win.queueRender() catch |err|
|
||||||
|
log.err("error scheduling render in mouseButtinCallback err={}", .{err});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
3 => log.info("TRIPLE CLICK", .{}),
|
3 => log.info("TRIPLE CLICK", .{}),
|
||||||
|
|
||||||
// We should be bounded by 1 to 3
|
// We should be bounded by 1 to 3
|
||||||
|
@ -1098,7 +1098,10 @@ pub fn clearHistory(self: *Screen) void {
|
|||||||
|
|
||||||
/// Select the word under the given point. A word is any consecutive series
|
/// Select the word under the given point. A word is any consecutive series
|
||||||
/// of characters that are exclusively whitespace or exclusively non-whitespace.
|
/// of characters that are exclusively whitespace or exclusively non-whitespace.
|
||||||
/// A selection can span multiple physical lines.
|
/// 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, pt: point.ScreenPoint) ?Selection {
|
pub fn selectWord(self: *Screen, pt: point.ScreenPoint) ?Selection {
|
||||||
// Impossible to select anything outside of the area we've written.
|
// Impossible to select anything outside of the area we've written.
|
||||||
const y_max = self.rowsWritten() - 1;
|
const y_max = self.rowsWritten() - 1;
|
||||||
@ -1118,36 +1121,16 @@ pub fn selectWord(self: *Screen, pt: point.ScreenPoint) ?Selection {
|
|||||||
|
|
||||||
// Go forwards to find our end boundary
|
// Go forwards to find our end boundary
|
||||||
const end: point.ScreenPoint = boundary: {
|
const end: point.ScreenPoint = boundary: {
|
||||||
//var y: usize = pt.y;
|
|
||||||
var x: usize = pt.x;
|
|
||||||
var prev: point.ScreenPoint = pt;
|
var prev: point.ScreenPoint = pt;
|
||||||
while (x < self.cols) : (x += 1) {
|
|
||||||
const cell = row.getCell(x);
|
|
||||||
|
|
||||||
// If we reached an empty cell its always a boundary
|
|
||||||
if (cell.empty()) break :boundary prev;
|
|
||||||
|
|
||||||
// If we do not match our expected set, we hit a boundary
|
|
||||||
const this_whitespace = std.mem.indexOfAny(u32, whitespace, &[_]u32{cell.char}) != null;
|
|
||||||
if (this_whitespace != expect_whitespace) break :boundary prev;
|
|
||||||
|
|
||||||
// Increase our prev
|
|
||||||
prev.x = x;
|
|
||||||
}
|
|
||||||
|
|
||||||
break :boundary .{ .x = self.cols - 1, .y = y_max };
|
|
||||||
};
|
|
||||||
|
|
||||||
// Go backwards to find our start boundary
|
|
||||||
const start: point.ScreenPoint = boundary: {
|
|
||||||
var current_row = row;
|
|
||||||
var prev: point.ScreenPoint = pt;
|
|
||||||
|
|
||||||
var y: usize = pt.y;
|
var y: usize = pt.y;
|
||||||
while (true) {
|
var x: usize = pt.x;
|
||||||
var x: usize = pt.x;
|
while (y < y_max) : (y += 1) {
|
||||||
while (x > 0) : (x -= 1) {
|
const current_row = self.getRow(.{ .screen = y });
|
||||||
const cell = current_row.getCell(x - 1);
|
|
||||||
|
// Go through all the remainining cells on this row until
|
||||||
|
// we reach a boundary condition.
|
||||||
|
while (x < self.cols) : (x += 1) {
|
||||||
|
const cell = current_row.getCell(x);
|
||||||
|
|
||||||
// If we reached an empty cell its always a boundary
|
// If we reached an empty cell its always a boundary
|
||||||
if (cell.empty()) break :boundary prev;
|
if (cell.empty()) break :boundary prev;
|
||||||
@ -1160,8 +1143,43 @@ pub fn selectWord(self: *Screen, pt: point.ScreenPoint) ?Selection {
|
|||||||
) != null;
|
) != null;
|
||||||
if (this_whitespace != expect_whitespace) break :boundary prev;
|
if (this_whitespace != expect_whitespace) break :boundary prev;
|
||||||
|
|
||||||
|
// Increase our prev
|
||||||
|
prev.x = x;
|
||||||
|
prev.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we aren't wrapping, then we're done this is a boundary.
|
||||||
|
if (!current_row.header().flags.wrap) break :boundary prev;
|
||||||
|
|
||||||
|
// If we are wrapping, reset some values and search the next line.
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
break :boundary .{ .x = self.cols - 1, .y = y_max };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Go backwards to find our start boundary
|
||||||
|
const start: point.ScreenPoint = boundary: {
|
||||||
|
var current_row = row;
|
||||||
|
var prev: point.ScreenPoint = pt;
|
||||||
|
|
||||||
|
var y: usize = pt.y;
|
||||||
|
var x: usize = pt.x;
|
||||||
|
while (true) {
|
||||||
|
// Go through all the remainining cells on this row until
|
||||||
|
// we reach a boundary condition.
|
||||||
|
while (x > 0) : (x -= 1) {
|
||||||
|
const cell = current_row.getCell(x - 1);
|
||||||
|
const this_whitespace = std.mem.indexOfAny(
|
||||||
|
u32,
|
||||||
|
whitespace,
|
||||||
|
&[_]u32{cell.char},
|
||||||
|
) != null;
|
||||||
|
if (this_whitespace != expect_whitespace) break :boundary prev;
|
||||||
|
|
||||||
// Update our prev
|
// Update our prev
|
||||||
prev.x = x - 1;
|
prev.x = x - 1;
|
||||||
|
prev.y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're at the start, we need to check if the previous line wrapped.
|
// If we're at the start, we need to check if the previous line wrapped.
|
||||||
@ -1172,14 +1190,17 @@ pub fn selectWord(self: *Screen, pt: point.ScreenPoint) ?Selection {
|
|||||||
// If we're at the end, we're done!
|
// If we're at the end, we're done!
|
||||||
if (y == 0) break;
|
if (y == 0) break;
|
||||||
|
|
||||||
// Update our prev y
|
|
||||||
prev.y = y;
|
|
||||||
|
|
||||||
// If the previous row did not wrap, then we're done. Otherwise
|
// If the previous row did not wrap, then we're done. Otherwise
|
||||||
// we keep searching.
|
// we keep searching.
|
||||||
y -= 1;
|
y -= 1;
|
||||||
current_row = self.getRow(.{ .screen = y });
|
current_row = self.getRow(.{ .screen = y });
|
||||||
if (!current_row.header().flags.wrap) break :boundary prev;
|
if (!current_row.header().flags.wrap) break :boundary prev;
|
||||||
|
|
||||||
|
// Set x to start at the first non-empty cell
|
||||||
|
x = self.cols;
|
||||||
|
while (x > 0) : (x -= 1) {
|
||||||
|
if (!current_row.getCell(x - 1).empty()) break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break :boundary .{ .x = 0, .y = 0 };
|
break :boundary .{ .x = 0, .y = 0 };
|
||||||
@ -2542,6 +2563,7 @@ test "Screen: selectWord" {
|
|||||||
try s.testWriteString("ABC DEF\n 123\n456");
|
try s.testWriteString("ABC DEF\n 123\n456");
|
||||||
|
|
||||||
// Outside of active area
|
// Outside of active area
|
||||||
|
try testing.expect(s.selectWord(.{ .x = 9, .y = 0 }) == null);
|
||||||
try testing.expect(s.selectWord(.{ .x = 0, .y = 5 }) == null);
|
try testing.expect(s.selectWord(.{ .x = 0, .y = 5 }) == null);
|
||||||
|
|
||||||
// Going forward
|
// Going forward
|
||||||
@ -2589,7 +2611,86 @@ test "Screen: selectWord" {
|
|||||||
try testing.expectEqual(@as(usize, 1), sel.end.y);
|
try testing.expectEqual(@as(usize, 1), sel.end.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test going backwards up a line
|
// End of screen
|
||||||
|
{
|
||||||
|
const sel = s.selectWord(.{ .x = 1, .y = 2 }).?;
|
||||||
|
try testing.expectEqual(@as(usize, 0), sel.start.x);
|
||||||
|
try testing.expectEqual(@as(usize, 2), sel.start.y);
|
||||||
|
try testing.expectEqual(@as(usize, 2), sel.end.x);
|
||||||
|
try testing.expectEqual(@as(usize, 2), sel.end.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Screen: selectWord across soft-wrap" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, 10, 5, 0);
|
||||||
|
defer s.deinit();
|
||||||
|
try s.testWriteString(" 1234012\n 123");
|
||||||
|
|
||||||
|
// Going forward
|
||||||
|
{
|
||||||
|
const sel = s.selectWord(.{ .x = 1, .y = 0 }).?;
|
||||||
|
try testing.expectEqual(@as(usize, 1), sel.start.x);
|
||||||
|
try testing.expectEqual(@as(usize, 0), sel.start.y);
|
||||||
|
try testing.expectEqual(@as(usize, 2), sel.end.x);
|
||||||
|
try testing.expectEqual(@as(usize, 1), sel.end.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Going backward
|
||||||
|
{
|
||||||
|
const sel = s.selectWord(.{ .x = 1, .y = 1 }).?;
|
||||||
|
try testing.expectEqual(@as(usize, 1), sel.start.x);
|
||||||
|
try testing.expectEqual(@as(usize, 0), sel.start.y);
|
||||||
|
try testing.expectEqual(@as(usize, 2), sel.end.x);
|
||||||
|
try testing.expectEqual(@as(usize, 1), sel.end.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Going forward and backward
|
||||||
|
{
|
||||||
|
const sel = s.selectWord(.{ .x = 3, .y = 0 }).?;
|
||||||
|
try testing.expectEqual(@as(usize, 1), sel.start.x);
|
||||||
|
try testing.expectEqual(@as(usize, 0), sel.start.y);
|
||||||
|
try testing.expectEqual(@as(usize, 2), sel.end.x);
|
||||||
|
try testing.expectEqual(@as(usize, 1), sel.end.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Screen: selectWord whitespace across soft-wrap" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, 10, 5, 0);
|
||||||
|
defer s.deinit();
|
||||||
|
try s.testWriteString("1 1\n 123");
|
||||||
|
|
||||||
|
// Going forward
|
||||||
|
{
|
||||||
|
const sel = s.selectWord(.{ .x = 1, .y = 0 }).?;
|
||||||
|
try testing.expectEqual(@as(usize, 1), sel.start.x);
|
||||||
|
try testing.expectEqual(@as(usize, 0), sel.start.y);
|
||||||
|
try testing.expectEqual(@as(usize, 2), sel.end.x);
|
||||||
|
try testing.expectEqual(@as(usize, 1), sel.end.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Going backward
|
||||||
|
{
|
||||||
|
const sel = s.selectWord(.{ .x = 1, .y = 1 }).?;
|
||||||
|
try testing.expectEqual(@as(usize, 1), sel.start.x);
|
||||||
|
try testing.expectEqual(@as(usize, 0), sel.start.y);
|
||||||
|
try testing.expectEqual(@as(usize, 2), sel.end.x);
|
||||||
|
try testing.expectEqual(@as(usize, 1), sel.end.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Going forward and backward
|
||||||
|
{
|
||||||
|
const sel = s.selectWord(.{ .x = 3, .y = 0 }).?;
|
||||||
|
try testing.expectEqual(@as(usize, 1), sel.start.x);
|
||||||
|
try testing.expectEqual(@as(usize, 0), sel.start.y);
|
||||||
|
try testing.expectEqual(@as(usize, 2), sel.end.x);
|
||||||
|
try testing.expectEqual(@as(usize, 1), sel.end.y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "Screen: scrollRegionUp single" {
|
test "Screen: scrollRegionUp single" {
|
||||||
|
Reference in New Issue
Block a user