mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-20 10:46:07 +03:00
terminal: selectWord stops at single/double quote boundaries
This commit is contained in:
@ -65,9 +65,6 @@ const fastmem = @import("../fastmem.zig");
|
|||||||
|
|
||||||
const log = std.log.scoped(.screen);
|
const log = std.log.scoped(.screen);
|
||||||
|
|
||||||
/// Whitespace characters for selection purposes
|
|
||||||
const whitespace = &[_]u32{ 0, ' ', '\t' };
|
|
||||||
|
|
||||||
/// Cursor represents the cursor state.
|
/// Cursor represents the cursor state.
|
||||||
pub const Cursor = struct {
|
pub const Cursor = struct {
|
||||||
// x, y where the cursor currently exists (0-indexed). This x/y is
|
// x, y where the cursor currently exists (0-indexed). This x/y is
|
||||||
@ -1157,6 +1154,9 @@ pub fn clearHistory(self: *Screen) void {
|
|||||||
/// over whitespace but the line has non-whitespace characters elsewhere, the
|
/// over whitespace but the line has non-whitespace characters elsewhere, the
|
||||||
/// line will be selected.
|
/// line will be selected.
|
||||||
pub fn selectLine(self: *Screen, pt: point.ScreenPoint) ?Selection {
|
pub fn selectLine(self: *Screen, pt: point.ScreenPoint) ?Selection {
|
||||||
|
// Whitespace characters for selection purposes
|
||||||
|
const whitespace = &[_]u32{ 0, ' ', '\t' };
|
||||||
|
|
||||||
// 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;
|
||||||
if (pt.y > y_max or pt.x >= self.cols) return null;
|
if (pt.y > y_max or pt.x >= self.cols) return null;
|
||||||
@ -1258,6 +1258,9 @@ pub fn selectLine(self: *Screen, pt: point.ScreenPoint) ?Selection {
|
|||||||
/// This will return null if a selection is impossible. The only scenario
|
/// 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.
|
/// 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 {
|
||||||
|
// Boundary characters for selection purposes
|
||||||
|
const boundary = &[_]u32{ 0, ' ', '\t', '\'', '"' };
|
||||||
|
|
||||||
// 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;
|
||||||
if (pt.y > y_max) return null;
|
if (pt.y > y_max) return null;
|
||||||
@ -1270,8 +1273,8 @@ pub fn selectWord(self: *Screen, pt: point.ScreenPoint) ?Selection {
|
|||||||
// areas where the screen is not yet written.
|
// areas where the screen is not yet written.
|
||||||
if (start_cell.empty()) return null;
|
if (start_cell.empty()) return null;
|
||||||
|
|
||||||
// Determine if we are whitespace or not to determine what our boundary is.
|
// Determine if we are a boundary or not to determine what our boundary is.
|
||||||
const expect_whitespace = std.mem.indexOfAny(u32, whitespace, &[_]u32{start_cell.char}) != null;
|
const expect_boundary = std.mem.indexOfAny(u32, boundary, &[_]u32{start_cell.char}) != null;
|
||||||
|
|
||||||
// Go forwards to find our end boundary
|
// Go forwards to find our end boundary
|
||||||
const end: point.ScreenPoint = boundary: {
|
const end: point.ScreenPoint = boundary: {
|
||||||
@ -1290,12 +1293,12 @@ pub fn selectWord(self: *Screen, pt: point.ScreenPoint) ?Selection {
|
|||||||
if (cell.empty()) break :boundary prev;
|
if (cell.empty()) break :boundary prev;
|
||||||
|
|
||||||
// If we do not match our expected set, we hit a boundary
|
// If we do not match our expected set, we hit a boundary
|
||||||
const this_whitespace = std.mem.indexOfAny(
|
const this_boundary = std.mem.indexOfAny(
|
||||||
u32,
|
u32,
|
||||||
whitespace,
|
boundary,
|
||||||
&[_]u32{cell.char},
|
&[_]u32{cell.char},
|
||||||
) != null;
|
) != null;
|
||||||
if (this_whitespace != expect_whitespace) break :boundary prev;
|
if (this_boundary != expect_boundary) break :boundary prev;
|
||||||
|
|
||||||
// Increase our prev
|
// Increase our prev
|
||||||
prev.x = x;
|
prev.x = x;
|
||||||
@ -1324,12 +1327,12 @@ pub fn selectWord(self: *Screen, pt: point.ScreenPoint) ?Selection {
|
|||||||
// we reach a boundary condition.
|
// we reach a boundary condition.
|
||||||
while (x > 0) : (x -= 1) {
|
while (x > 0) : (x -= 1) {
|
||||||
const cell = current_row.getCell(x - 1);
|
const cell = current_row.getCell(x - 1);
|
||||||
const this_whitespace = std.mem.indexOfAny(
|
const this_boundary = std.mem.indexOfAny(
|
||||||
u32,
|
u32,
|
||||||
whitespace,
|
boundary,
|
||||||
&[_]u32{cell.char},
|
&[_]u32{cell.char},
|
||||||
) != null;
|
) != null;
|
||||||
if (this_whitespace != expect_whitespace) break :boundary prev;
|
if (this_boundary != expect_boundary) break :boundary prev;
|
||||||
|
|
||||||
// Update our prev
|
// Update our prev
|
||||||
prev.x = x - 1;
|
prev.x = x - 1;
|
||||||
@ -3368,6 +3371,53 @@ test "Screen: selectWord whitespace across soft-wrap" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "Screen: selectWord with single quote boundary" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, 10, 20, 0);
|
||||||
|
defer s.deinit();
|
||||||
|
try s.testWriteString(" 'abc' \n123");
|
||||||
|
|
||||||
|
// Inside quotes forward
|
||||||
|
{
|
||||||
|
const sel = s.selectWord(.{ .x = 2, .y = 0 }).?;
|
||||||
|
try testing.expectEqual(@as(usize, 2), sel.start.x);
|
||||||
|
try testing.expectEqual(@as(usize, 0), sel.start.y);
|
||||||
|
try testing.expectEqual(@as(usize, 4), sel.end.x);
|
||||||
|
try testing.expectEqual(@as(usize, 0), sel.end.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inside quotes backward
|
||||||
|
{
|
||||||
|
const sel = s.selectWord(.{ .x = 4, .y = 0 }).?;
|
||||||
|
try testing.expectEqual(@as(usize, 2), sel.start.x);
|
||||||
|
try testing.expectEqual(@as(usize, 0), sel.start.y);
|
||||||
|
try testing.expectEqual(@as(usize, 4), sel.end.x);
|
||||||
|
try testing.expectEqual(@as(usize, 0), sel.end.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inside quotes bidirectional
|
||||||
|
{
|
||||||
|
const sel = s.selectWord(.{ .x = 3, .y = 0 }).?;
|
||||||
|
try testing.expectEqual(@as(usize, 2), sel.start.x);
|
||||||
|
try testing.expectEqual(@as(usize, 0), sel.start.y);
|
||||||
|
try testing.expectEqual(@as(usize, 4), sel.end.x);
|
||||||
|
try testing.expectEqual(@as(usize, 0), sel.end.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// On quote
|
||||||
|
// NOTE: this behavior is not ideal, so we can change this one day,
|
||||||
|
// but I think its also not that important compared to the above.
|
||||||
|
{
|
||||||
|
const sel = s.selectWord(.{ .x = 1, .y = 0 }).?;
|
||||||
|
try testing.expectEqual(@as(usize, 0), sel.start.x);
|
||||||
|
try testing.expectEqual(@as(usize, 0), sel.start.y);
|
||||||
|
try testing.expectEqual(@as(usize, 1), sel.end.x);
|
||||||
|
try testing.expectEqual(@as(usize, 0), sel.end.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "Screen: scrollRegionUp single" {
|
test "Screen: scrollRegionUp single" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
Reference in New Issue
Block a user