From aeaf5ea467781330292a52ef6f8d1d1be2d6ca6e Mon Sep 17 00:00:00 2001 From: Chris Marchesi Date: Wed, 13 Dec 2023 10:51:17 -0800 Subject: [PATCH] Surface: fix some rectangle select behaviors This fixes a couple of subtle rectangle select behaviors: * Corrects how selection rolls over when crossing the x-boundary; this was mentioned in #1021, this properly corrects it so both sides of the x-boundary do not share characters. * Corrects a minor quirk in the selection of initial cells in a selection - this can be more readily observed when selecting a single line with rectangle select. To correct this, we only use the x axis when calculating this instead of both x and y. --- src/Surface.zig | 69 ++++++++++++++++++++++++++++++++------ src/terminal/Selection.zig | 2 +- 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index e6c906310..31886a908 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -2348,14 +2348,7 @@ fn dragLeftClickSingle( // If we were selecting, and we switched directions, then we restart // calculations because it forces us to reconsider if the first cell is // selected. - if (self.io.terminal.screen.selection) |sel| { - const reset: bool = if (sel.end.before(sel.start)) - sel.start.before(screen_point) - else - screen_point.before(sel.start); - - if (reset) self.setSelection(null); - } + self.checkResetSelSwitch(screen_point); // Our logic for determining if the starting cell is selected: // @@ -2406,8 +2399,12 @@ fn dragLeftClickSingle( // we start selection of the prior cell. // - Inverse logic for a point after the start. const click_point = self.mouse.left_click_point; - const start: terminal.point.ScreenPoint = if (screen_point.before(click_point)) start: { - if ((ctrlOrSuper(self.mouse.mods) and self.mouse.mods.alt) or cell_start_xpos >= cell_xboundary) { + const start: terminal.point.ScreenPoint = if (dragLeftClickBefore( + screen_point, + click_point, + self.mouse.mods, + )) start: { + if (cell_start_xpos >= cell_xboundary) { break :start click_point; } else { break :start if (click_point.x > 0) terminal.point.ScreenPoint{ @@ -2419,7 +2416,7 @@ fn dragLeftClickSingle( }; } } else start: { - if ((ctrlOrSuper(self.mouse.mods) and self.mouse.mods.alt) or cell_start_xpos < cell_xboundary) { + if (cell_start_xpos < cell_xboundary) { break :start click_point; } else { break :start if (click_point.x < self.io.terminal.screen.cols - 1) terminal.point.ScreenPoint{ @@ -2451,6 +2448,56 @@ fn dragLeftClickSingle( self.setSelection(sel); } +// Resets the selection if we switched directions, depending on the select +// mode. See dragLeftClickSingle for more details. +fn checkResetSelSwitch(self: *Surface, screen_point: terminal.point.ScreenPoint) void { + var reset: bool = undefined; + if (self.io.terminal.screen.selection) |sel| { + if (sel.rectangle) { + // When we're in rectangle mode, we reset the selection relative to + // the click point depending on the selection mode we're in, with + // the exception of single-column selections, which we always reset + // on if we drift. + if (sel.start.x == sel.end.x) { + reset = screen_point.x != sel.start.x; + } else { + reset = switch (sel.order()) { + .forward => screen_point.x < sel.start.x or screen_point.y < sel.start.y, + .reverse => screen_point.x > sel.start.x or screen_point.y > sel.start.y, + .mirrored_forward => screen_point.x > sel.start.x or screen_point.y < sel.start.y, + .mirrored_reverse => screen_point.x < sel.start.x or screen_point.y > sel.start.y, + }; + } + } else { + // Normal select uses simpler logic that is just based on the + // selection start/end. + reset = if (sel.end.before(sel.start)) + sel.start.before(screen_point) + else + screen_point.before(sel.start); + } + } + + if (reset) + self.setSelection(null); +} + +// Handles how whether or not the drag screen point is before the click point. +// When we are in rectangle select, we only interpret the x axis to determine +// where to start the selection (before or after the click point). See +// dragLeftClickSingle for more details. +fn dragLeftClickBefore( + screen_point: terminal.point.ScreenPoint, + click_point: terminal.point.ScreenPoint, + mods: input.Mods, +) bool { + if (ctrlOrSuper(mods) and mods.alt) { + return screen_point.x < click_point.x; + } + + return screen_point.before(click_point); +} + fn posToViewport(self: Surface, xpos: f64, ypos: f64) terminal.point.Viewport { // xpos/ypos need to be adjusted for window padding // (i.e. "window-padding-*" settings. diff --git a/src/terminal/Selection.zig b/src/terminal/Selection.zig index ea507b571..b1053afc8 100644 --- a/src/terminal/Selection.zig +++ b/src/terminal/Selection.zig @@ -200,7 +200,7 @@ pub fn ordered(self: Selection, desired: Order) Selection { /// pub const Order = enum { forward, reverse, mirrored_forward, mirrored_reverse }; -fn order(self: Selection) Order { +pub fn order(self: Selection) Order { if (self.rectangle) { // Reverse (also handles single-column) if (self.start.y > self.end.y and self.start.x >= self.end.x) return .reverse;