[vte/wip/egmont/bidi: 10/79] vte-34-selection-big-rewrite-v1.patch



commit 1b7c03c7867f82349bb33c387f7d6a4e81f160bf
Author: Egmont Koblinger <egmont gmail com>
Date:   Wed Sep 19 10:26:48 2018 +0200

    vte-34-selection-big-rewrite-v1.patch

 src/vte.cc         | 1016 ++++++++++++++++++++++++++--------------------------
 src/vteaccess.cc   |    9 +-
 src/vtegtk.cc      |    2 +-
 src/vteinternal.hh |   30 +-
 src/vtetypes.cc    |    7 +
 src/vtetypes.hh    |    2 +
 6 files changed, 531 insertions(+), 535 deletions(-)
---
diff --git a/src/vte.cc b/src/vte.cc
index 9aba5e30..ba82f7a4 100644
--- a/src/vte.cc
+++ b/src/vte.cc
@@ -303,8 +303,33 @@ Terminal::invalidate_row(vte::grid::row_t row)
 void
 Terminal::invalidate(vte::grid::span const& s)
 {
-        invalidate_rows(s.start_row(),
-                        s.end_column() < 0 ? s.end_row() - 1 : s.end_row());
+        if (!s.empty())
+                invalidate_rows(s.start_row(), s.last_row());
+}
+
+/* Invalidates the XOR area of the two spans */
+void
+Terminal::invalidate_xor(vte::grid::span const& a, vte::grid::span const& b, bool block)
+{
+        if (a.empty() || b.empty() || a.start() > b.end() || b.start() > a.end()) {
+                /* One or both are empty (invalidate() will figure out which), or disjoint row intervals */
+                invalidate (a);
+                invalidate (b);
+                return;
+        }
+
+        if (block) {
+                /* We could optimize when the columns don't change, probably not worth it. */
+                invalidate_rows (MIN (a.start_row(), b.start_row()),
+                                 MAX (a.last_row(),  b.last_row()));
+        } else {
+                if (a.start() != b.start()) {
+                        invalidate_rows (a.start_row(), b.start_row());
+                }
+                if (a.end() != b.end()) {
+                        invalidate_rows (a.last_row(), b.last_row());
+                }
+        }
 }
 
 void
@@ -742,21 +767,17 @@ Terminal::emit_hyperlink_hover_uri_changed(const GdkRectangle *bbox)
 void
 Terminal::deselect_all()
 {
-       if (m_has_selection) {
-                gint sy, ey;
-
+        if (!m_selection_resolved.empty()) {
                _vte_debug_print(VTE_DEBUG_SELECTION,
                                "Deselecting all text.\n");
 
-               m_has_selection = FALSE;
+                m_selection_origin_half = m_selection_last_half = vte::grid::coords (-1, -1);
+                resolve_selection();
+
                /* Don't free the current selection, as we need to keep
                 * hold of it for async copying from the clipboard. */
 
                emit_selection_changed();
-
-               sy = m_selection_start.row;
-               ey = m_selection_end.row;
-                invalidate_rows(sy, ey);
        }
 }
 
@@ -1547,6 +1568,83 @@ Terminal::confine_grid_coords(vte::grid::coords const& rowcol) const
                                  CLAMP(rowcol.column(), 0, m_column_count - 1));
 }
 
+/*
+ * Track mouse click and drag positions (the "origin" and "last" coordinates) with half cell accuracy,
+ * that is, know whether the event occurred over the left or right half of the cell.
+ * This is required because some selection modes care about the cell over which the event occurred,
+ * while some care about the closest boundary between cells.
+ *
+ * Misuse vte::grid::coords for storing the result. Column k's left side is stored as 2k, right side as 2k+1.
+ * FIXMEegmont Maybe should we have a separate type?
+ *
+ * Left margin or anything further to the left is denoted by half-column -1,
+ * right margin or anything further to the right is denoted by half-column 2*m_column_count.
+ *
+ * Storing the actual view coordinates would become problematic when the font size changes, see bug 756058.
+ */
+vte::grid::coords
+Terminal::selection_grid_half_coords_from_view_coords(vte::view::coords const& pos) const
+{
+        vte::grid::row_t row = pixel_to_row(pos.y);
+        vte::grid::column_t colhalf;
+
+        if (pos.x < 0) {
+                colhalf = -1;
+        } else if (pos.x >= m_column_count * m_cell_width) {
+                colhalf = 2 * m_column_count;
+        } else {
+                colhalf = pos.x * 2 / m_cell_width;
+        }
+
+        return vte::grid::coords(row, colhalf);
+}
+
+/*
+ * Called on Shift+Click to continue (extend or shrink) the previous selection.
+ * Swaps the two endpoints of the selection if needed, so that m_selection_origin
+ * contains the new fixed point and m_selection_last is the newly dragged end.
+ * In block mode it might even switch to the other two corners.
+ * As per GTK+'s generic selection behavior, retains the origin and last
+ * endpoints if the Shift+click happened inside the selection.
+ */
+void
+Terminal::selection_maybe_swap_endpoints(vte::view::coords const& pos,
+                                         enum vte_selection_type type /* FIXME or use m_selection_type? */,
+                                         bool block /* FIXME or use m_selection_block_mode? */)
+{
+        if (m_selection_resolved.empty())
+                return;
+
+        vte::grid::coords current_half = selection_grid_half_coords_from_view_coords (pos);
+
+        if (block) {
+                if ((current_half.row() <= m_selection_origin_half.row() && m_selection_origin_half.row() < 
m_selection_last_half.row()) ||
+                    (current_half.row() >= m_selection_origin_half.row() && m_selection_origin_half.row() > 
m_selection_last_half.row())) {
+                        auto tmp = m_selection_origin_half.row();
+                        m_selection_origin_half.set_row(m_selection_last_half.row());
+                        m_selection_last_half.set_row(tmp);
+                }
+                if ((current_half.column() <= m_selection_origin_half.column() && 
m_selection_origin_half.column() < m_selection_last_half.column()) ||
+                    (current_half.column() >= m_selection_origin_half.column() && 
m_selection_origin_half.column() > m_selection_last_half.column())) {
+                        auto tmp = m_selection_origin_half.column();
+                        m_selection_origin_half.set_column(m_selection_last_half.column());
+                        m_selection_last_half.set_column(tmp);
+                }
+        } else {
+                if ((current_half <= m_selection_origin_half && m_selection_origin_half < 
m_selection_last_half) ||
+                    (current_half >= m_selection_origin_half && m_selection_origin_half > 
m_selection_last_half)) {
+                        auto tmp = m_selection_origin_half;
+                        m_selection_origin_half = m_selection_last_half;
+                        m_selection_last_half = tmp;
+                }
+        }
+
+        _vte_debug_print(VTE_DEBUG_SELECTION,
+                         "Selection maybe swap endpoints: origin=%s last=%s\n",
+                         m_selection_origin_half.to_string(),
+                         m_selection_last_half.to_string());
+}
+
 bool
 Terminal::rowcol_from_event(GdkEvent *event,
                                       long *column,
@@ -3642,7 +3740,7 @@ Terminal::process_incoming()
                }
                /* Deselect the current selection if its contents are changed
                 * by this insertion. */
-               if (m_has_selection) {
+                if (!m_selection_resolved.empty()) {
                         //FIXMEchpe: this is atrocious
                        auto selection = get_selected_text();
                        if ((selection == nullptr) ||
@@ -4942,6 +5040,14 @@ Terminal::is_same_class(vte::grid::column_t acol,
        VteCell const* pcell = nullptr;
        bool word_char;
        if ((pcell = find_charcell(acol, arow)) != nullptr && pcell->c != 0) {
+                /* Group together if they're fragments of the very same character (not just character value) 
*/
+                if (arow == brow) {
+                        vte::grid::column_t a2 = acol, b2 = bcol;
+                        while (a2 > 0 && find_charcell(a2, arow)->attr.fragment()) a2--;
+                        while (b2 > 0 && find_charcell(b2, brow)->attr.fragment()) b2--;
+                        if (a2 == b2) return true;
+                }
+
                word_char = is_word_char(_vte_unistr_get_base(pcell->c));
 
                /* Lets not group non-wordchars together (bug #25290) */
@@ -4969,31 +5075,330 @@ Terminal::line_is_wrappable(vte::grid::row_t row) const
        return rowdata && rowdata->attr.soft_wrapped;
 }
 
+/*
+ * Convert the mouse click or drag location (left or right half of a cell) into a selection endpoint (a 
boundary between
+ * characters), extending the selection according to the current mode, in the direction given in the @after 
parameter.
+ *
+ * All four selection modes require different strategies.
+ *
+ * In char mode, what matters is which vertical character boundary is closer, ideally taking multi-cell 
characters (CJKs, TABs)
+ * properly into account. Given the string "abcdef", if the user clicks on the boundary between "a" and "b" 
(perhaps on
+ * the right half of "a", perhaps on the left half of "b"), and moves the mouse to the boundary between "e" 
and "f"
+ * (perhaps a bit over "e", perhaps a bit over "f"), the selection should be "bcde". By dragging the mouse 
back to approximately
+ * the click location, it is possible to select the empty string. This is the common sense behavior 
impemented by basically every
+ * graphical toolkit (unfortunately not by many terminal emulators), and also the one we go for.
+ *
+ * Word mode is the trickiest one. Many implementations have weird corner case bugs (e.g. don't highlight a 
word if you
+ * double click on the second half of its last letter, or even highlight it if you click on the first half 
of the following space).
+ * I think it is expected that double-clicking anywhere over a word (including the first half of its first 
letter, or the
+ * last half of its last letter), over no other character, selects this entire word. By dragging the mouse 
it's not possible to
+ * select nothing, the word initially clicked on is always part of the selection. (An exception is when 
clicking occurred over the
+ * margin, or an unused cell in a soft-wrapped row (due to CJK wrapping).) Also, for symmetry reasons, the 
word under the current
+ * mouse location is also always selected.
+ *
+ * Line (paragraph) mode is conceptually quite similar to word mode (the cell, thus the entire row under the 
click's location
+ * is always included), but is much easier to implement.
+ *
+ * In block mode, similarly to char mode, we care about vertical character boundary. (This is somewhat 
debatable, as results in
+ * asymmetrical behavior along the two axes: a rectangle can disappear by becoming zero wide, but not zero 
high.)
+ * We cannot take care of CJKs at the endpoints now because CJKs can cross the boundary in any included row. 
Taking care of them
+ * needs to go to cell_is_selected(). We don't care about used vs. unused cells either. The event coordinate 
is simply rounded
+ * to the nearest vertical cell boundary.
+ */
+vte::grid::coords
+Terminal::resolve_selection_endpoint(vte::grid::coords rowcolhalf, bool after) const
+{
+        vte::grid::row_t row = rowcolhalf.row();
+        vte::grid::column_t colhalf = rowcolhalf.column();
+        vte::grid::column_t col = 0;  /* just to silence gcc */
+        VteRowData const* rowdata;
+        VteCell const* cell;
+        int len;
+
+        if (m_selection_block_mode) {
+                /* Just find the nearest cell boundary within the line, not caring about CJKs, unused cells, 
or wrapping at EOL.
+                 * The @after parameter is unused in this mode. */
+                col = (rowcolhalf.column() + 1) / 2;
+                col = CLAMP (col, 0, m_column_count);
+        } else {
+                switch (m_selection_type) {
+                case selection_type_char:
+                        /* Find the nearest actual character boundary, taking CJKs and TABs into account.
+                         * If at least halfway through the first unused cell, or over the right margin
+                         * then wrap to the beginning of the next line.
+                         * The @after parameter is unused in this mode. */
+                        if (colhalf < 0) {
+                                col = 0;
+                        } else if (colhalf >= 2 * m_column_count) {
+                                /* If on the right padding, select the entire line including a possible 
newline character.
+                                 * This way if a line is fully filled and ends in a newline, there's only a 
half cell width
+                                 * for which the line is selected without the newline, but at least there's 
a way to include
+                                 * the newline by moving the mouse to the right (bug 724253). */
+                                col = 0;
+                                row++;
+                        } else {
+                                vte::grid::column_t char_begin, char_end;  /* cell boundaries */
+                                rowdata = find_row_data(row);
+                                if (rowdata && colhalf < rowdata->len * 2) {
+                                        /* Clicked over a used cell. Check for multi-cell characters. */
+                                        char_begin = colhalf / 2;
+                                        while (char_begin > 0) {
+                                                cell = _vte_row_data_get (rowdata, char_begin);
+                                                if (!cell->attr.fragment()) break;
+                                                char_begin--;
+                                        }
+                                        cell = _vte_row_data_get (rowdata, char_begin);
+                                        char_end = char_begin + cell->attr.columns();
+                                } else {
+                                        /* Clicked over unused area. Just go with cell boundaries. */
+                                        char_begin = colhalf / 2;
+                                        char_end = char_begin + 1;
+                                }
+                                /* Which boundary is closer? */
+                                if (colhalf < char_begin + char_end)
+                                        col = char_begin;
+                                else
+                                        col = char_end;
+
+                                /* Maybe wrap to the beginning of the next line. */
+                                if (col > (rowdata ? rowdata->len : 0)) {
+                                        col = 0;
+                                        row++;
+                                }
+                        }
+                        break;
+
+                case selection_type_word:
+                        /* col points to an actual cell throughout this logic, except at its final 
assignment when it points to a boundary. */
+                        col = colhalf / 2;
+
+                        /* Initialization for the cumbersome cases where the click didn't occur over an 
actual used cell. */
+                        rowdata = find_row_data(row);
+                        if (colhalf < 0) {
+                                /* Clicked over the left margin.
+                                 * If within a word (that is, the first letter in this row, and the last 
letter of the previous row
+                                 * belong to the same word) then select the letter according to the 
direction and continue expanding.
+                                 * Otherwise stop, the boundary is here before the first letter. */
+                                if ((rowdata = find_row_data(row - 1)) != nullptr &&
+                                    rowdata->attr.soft_wrapped &&
+                                    (len = rowdata->len) > 0 &&
+                                    is_same_class(len - 1, row - 1, 0, row) /* invalidates rowdata! */) {
+                                        if (!after) {
+                                                col = len - 1;
+                                                row--;
+                                        } else {
+                                                col = 0;
+                                        }
+                                } else {
+                                        col = 0;  /* end-exclusive */
+                                        break;
+                                }
+                        } else if (colhalf >= 2 * (rowdata ? rowdata->len : 0)) {
+                                /* Clicked over the right margin, or right unused area.
+                                 * If within a word (that is, the last letter in this row, and the first 
letter of the previous row
+                                 * belong to the same word) then select the letter according to the 
direction and continue expanding.
+                                 * Otherwise, if the row is soft-wrapped and we're over the unused area 
(which can happen if a CJK wrapped)
+                                 * or over the right margin, then stop, the boundary is wrapped to the 
beginning of the next row.
+                                 * Otherwise select the newline only and stop. */
+                                if ((rowdata = find_row_data(row)) != nullptr &&
+                                    rowdata->attr.soft_wrapped &&
+                                    (len = rowdata->len) > 0 &&
+                                    is_same_class(len - 1, row, 0, row + 1) /* invalidates rowdata! */) {
+                                        if (!after) {
+                                                col = len - 1;
+                                        } else {
+                                                col = 0;
+                                                row++;
+                                        }
+                                } else if ((rowdata = find_row_data(row)) != nullptr &&
+                                           rowdata->attr.soft_wrapped) {
+                                        col = 0;  /* end-exclusive */
+                                        row++;
+                                        break;
+                                } else {
+                                        if (!after) {
+                                                col = rowdata ? rowdata->len : 0;  /* end-exclusive */
+                                        } else {
+                                                col = 0;  /* end-exclusive */
+                                                row++;
+                                        }
+                                        break;
+                                }
+                        }
+
+                        /* Expand in the given direction. */
+                        if (!after) {
+                                /* Keep selecting to the left (and then up) as long as the next character we
+                                 * look at is of the same class as the current start point. */
+                                while (true) {
+                                        /* Back up within the row. */
+                                        for (; col > 0; col--) {
+                                                if (!is_same_class(col - 1, row, col, row)) {
+                                                        break;
+                                                }
+                                        }
+                                        if (col > 0) {
+                                                /* We hit a stopping point, so stop. */
+                                                break;
+                                        }
+                                        rowdata = find_row_data(row - 1);
+                                        if (!rowdata || !rowdata->attr.soft_wrapped) {
+                                                /* Reached a hard newline. */
+                                                break;
+                                        }
+                                        len = rowdata->len;
+                                        /* len might be smaller than m_column_count if a CJK wrapped */
+                                        if (!is_same_class(len - 1, row - 1, col, row) /* invalidates 
rowdata! */) {
+                                                break;
+                                        }
+                                        /* Move on to the previous line. */
+                                        col = len - 1;
+                                        row--;
+                                }
+                        } else {
+                                /* Keep selecting to the right (and then down) as long as the next character 
we
+                                 * look at is of the same class as the current end point. */
+                                while (true) {
+                                        rowdata = find_row_data(row);
+                                        if (!rowdata) {
+                                                break;
+                                        }
+                                        len = rowdata->len;
+                                        bool soft_wrapped = rowdata->attr.soft_wrapped;
+                                        /* Move forward within the row. */
+                                        for (; col < len - 1; col++) {
+                                                if (!is_same_class(col, row, col + 1, row) /* invalidates 
rowdata! */) {
+                                                        break;
+                                                }
+                                        }
+                                        if (col < len - 1) {
+                                                /* We hit a stopping point, so stop. */
+                                                break;
+                                        }
+                                        if (!soft_wrapped) {
+                                                /* Reached a hard newline. */
+                                                break;
+                                        }
+                                        if (!is_same_class(col, row, 0, row + 1)) {
+                                                break;
+                                        }
+                                        /* Move on to the next line. */
+                                        col = 0;
+                                        row++;
+                                }
+                                col++;  /* col points to an actual cell, we need end-exclusive instead. */
+                        }
+                        break;
+
+                case selection_type_line:
+                        if (!after) {
+                                /* Back up as far as we can go. */
+                                while (_vte_ring_contains (m_screen->row_data, row - 1) &&
+                                       line_is_wrappable(row - 1)) {
+                                        row--;
+                                }
+                        } else {
+                                /* Move forward as far as we can go. */
+                                while (_vte_ring_contains (m_screen->row_data, row) &&
+                                       line_is_wrappable(row)) {
+                                        row++;
+                                }
+                                row++;  /* One more row, since the column is 0. */
+                        }
+                        col = 0;
+                        break;
+                }
+        }
+
+        return vte::grid::coords (row, col);
+}
+
+/*
+ * Creates the selection's span from the origin and last coordinates.
+ * Takes the endpoints in swapped order if necessary. In block mode it might even switch to the other two 
corners.
+ * In word & line (paragraph) modes it extends the selection accordingly.
+ * Also makes sure to invalidate the regions that changed, and update m_selecting_had_delta.
+ *
+ * FIXMEegmont it always resolves both endpoints. With a bit of extra bookkeeping it could usually
+ * just resolve the moving one.
+ */
+void
+Terminal::resolve_selection()
+{
+        if (m_selection_origin_half.row() < 0 || m_selection_last_half.row() < 0) {
+                invalidate (m_selection_resolved);
+                m_selection_resolved.clear();
+                _vte_debug_print(VTE_DEBUG_SELECTION, "Selection resolved to %s.\n", 
m_selection_resolved.to_string());
+                return;
+        }
+
+        auto m_selection_resolved_old = m_selection_resolved;
+
+        if (m_selection_block_mode) {
+                vte::grid::row_t top    = MIN (m_selection_origin_half.row(), m_selection_last_half.row());
+                vte::grid::row_t bottom = MAX (m_selection_origin_half.row(), m_selection_last_half.row());
+                vte::grid::column_t left_half  = MIN (m_selection_origin_half.column(), 
m_selection_last_half.column());
+                vte::grid::column_t right_half = MAX (m_selection_origin_half.column(), 
m_selection_last_half.column());
+
+                vte::grid::coords topleft     = resolve_selection_endpoint (vte::grid::coords (top,    
left_half),  false);
+                vte::grid::coords bottomright = resolve_selection_endpoint (vte::grid::coords (bottom, 
right_half), true);
+
+                if (topleft.column() == bottomright.column()) {
+                        m_selection_resolved.clear();
+                } else {
+                        m_selection_resolved.set (topleft, bottomright);
+                }
+        } else {
+                vte::grid::coords a_half = MIN (m_selection_origin_half, m_selection_last_half);
+                vte::grid::coords b_half = MAX (m_selection_origin_half, m_selection_last_half);
+
+                m_selection_resolved.set (resolve_selection_endpoint (a_half, false),
+                                          resolve_selection_endpoint (b_half, true));
+        }
+
+        if (!m_selection_resolved.empty())
+                m_selecting_had_delta = true;
+
+        _vte_debug_print(VTE_DEBUG_SELECTION, "Selection resolved to %s.\n", 
m_selection_resolved.to_string());
+
+        invalidate_xor (m_selection_resolved_old, m_selection_resolved, m_selection_block_mode);
+}
+
+void
+Terminal::modify_selection (vte::view::coords pos)
+{
+        g_assert (m_selecting);
+
+        vte::grid::coords current_half = selection_grid_half_coords_from_view_coords (pos);
+
+        if (current_half == m_selection_last_half)
+                return;
+
+        _vte_debug_print(VTE_DEBUG_SELECTION,
+                         "Selection dragged to (%ld, %.1f).\n",
+                         current_half.row(), current_half.column() / 2.0);
+
+        m_selection_last_half = current_half;
+        resolve_selection();
+}
+
 /* Check if a cell is selected or not. */
-// FIXMEchpe: replace this by just using vte::grid::span for selection and then this simply becomes 
.contains()
 bool
 Terminal::cell_is_selected(vte::grid::column_t col,
                                      vte::grid::row_t row) const
 {
-       /* If there's nothing selected, it's an easy question to answer. */
-       if (!m_has_selection)
-               return false;
-
-       /* If the selection is obviously bogus, then it's also very easy. */
-       auto const& ss = m_selection_start;
-       auto const& se = m_selection_end;
-       if ((ss.row < 0) || (se.row < 0)) {
-               return false;
-       }
-
-       /* Now it boils down to whether or not the point is between the
-        * begin and endpoint of the selection. */
         if (m_selection_block_mode) {
-                return (row >= ss.row && row <= se.row &&
-                        col >= ss.col && col <= se.col);
+                /* In block mode, make sure CJKs and TABs aren't cut in half. */
+                while (col > 0) {
+                        VteCell const* cell = find_charcell(col, row);
+                        if (!cell || !cell->attr.fragment())
+                                break;
+                        col--;
+                }
+                return m_selection_resolved.box_contains (vte::grid::coords (row, col));
         } else {
-                return ((row > ss.row || (row == ss.row && col >= ss.col)) &&
-                        (row < se.row || (row == se.row && col <= se.col)));
+                /* In normal modes, resolve_selection() made sure to generate such boundaries for 
m_selection_resolved. */
+                return m_selection_resolved.contains (vte::grid::coords (row, col));
         }
 }
 
@@ -5528,7 +5933,7 @@ Terminal::widget_clipboard_cleared(GtkClipboard *clipboard_)
 
        if (clipboard_ == m_clipboard[VTE_SELECTION_PRIMARY]) {
                if (m_selection_owned[VTE_SELECTION_PRIMARY] &&
-                    m_has_selection) {
+                    !m_selection_resolved.empty()) {
                        _vte_debug_print(VTE_DEBUG_SELECTION, "Lost selection.\n");
                        deselect_all();
                }
@@ -5675,7 +6080,7 @@ Terminal::get_text(vte::grid::row_t start_row,
                attr.column = col;
                pcell = NULL;
                if (row_data != NULL) {
-                        while (col <= line_last_column &&
+                        while (col < line_last_column &&
                                (pcell = _vte_row_data_get (row_data, col))) {
 
                                attr.column = col;
@@ -5755,7 +6160,7 @@ Terminal::get_text_displayed(bool wrap,
                                        GArray *attributes)
 {
         return get_text(first_displayed_row(), 0,
-                        last_displayed_row() + 1, -1,
+                        last_displayed_row() + 1, 0,
                         false /* block */, wrap,
                         attributes);
 }
@@ -5768,7 +6173,7 @@ Terminal::get_text_displayed_a11y(bool wrap,
                                             GArray *attributes)
 {
         return get_text(m_screen->scroll_delta, 0,
-                        m_screen->scroll_delta + m_row_count - 1 + 1, -1,
+                        m_screen->scroll_delta + m_row_count - 1 + 1, 0,
                         false /* block */, wrap,
                         attributes);
 }
@@ -5776,10 +6181,10 @@ Terminal::get_text_displayed_a11y(bool wrap,
 GString*
 Terminal::get_selected_text(GArray *attributes)
 {
-       return get_text(m_selection_start.row,
-                        m_selection_start.col,
-                        m_selection_end.row,
-                        m_selection_end.col,
+        return get_text(m_selection_resolved.start_row(),
+                        m_selection_resolved.start_column(),
+                        m_selection_resolved.end_row(),
+                        m_selection_resolved.end_column(),
                         m_selection_block_mode,
                         true /* wrap */,
                         attributes);
@@ -6062,7 +6467,6 @@ Terminal::widget_copy(VteSelection sel,
 
         if (selection == nullptr) {
                 g_array_free(attributes, TRUE);
-                m_has_selection = FALSE;
                 m_selection_owned[sel] = false;
                 return;
         }
@@ -6076,8 +6480,8 @@ Terminal::widget_copy(VteSelection sel,
 
        g_array_free (attributes, TRUE);
 
-       if (sel == VTE_SELECTION_PRIMARY)
-               m_has_selection = TRUE;
+//     if (sel == VTE_SELECTION_PRIMARY)
+//             m_has_selection = TRUE;  // FIXME
 
        /* Place the text on the clipboard. */
         _vte_debug_print(VTE_DEBUG_SELECTION,
@@ -6116,12 +6520,6 @@ Terminal::widget_paste(GdkAtom board)
         m_paste_request.request_text(clip, &Terminal::widget_paste_received, this);
 }
 
-void
-Terminal::invalidate_selection()
-{
-        invalidate_rows(m_selection_start.row, m_selection_end.row);
-}
-
 /* Confine coordinates into the visible area. Padding is already subtracted. */
 void
 Terminal::confine_coordinates(long *xp,
@@ -6154,54 +6552,43 @@ Terminal::confine_coordinates(long *xp,
        *yp = y;
 }
 
-/* Start selection at the location of the event. */
+/* Start selection at the location of the event.
+ * In case of regular selection, this is called with the original click's location once the mouse has moved 
by the gtk drag threshold.
+ */
 void
-Terminal::start_selection(long x,
-                                    long y,
-                                    enum vte_selection_type type)
+Terminal::start_selection (vte::view::coords pos,
+                           enum vte_selection_type type)
 {
        if (m_selection_block_mode)
                type = selection_type_char;
 
-       /* Confine coordinates into the visible area. (#563024, #722635c7) */
-       confine_coordinates(&x, &y);
-
-       /* Record that we have the selection, and where it started. */
-       m_has_selection = TRUE;
-       m_selection_last.x = x;
-       m_selection_last.y = scroll_delta_pixel() + y;
+        m_selection_origin_half = m_selection_last_half = selection_grid_half_coords_from_view_coords(pos);
 
-       /* Decide whether or not to restart on the next drag. */
+       /* Decide whether or not to restart on the next drag. FIXME this description is bad */
        switch (type) {
-       case selection_type_char:
+       case selection_type_char:  // FIXME this should only go for non-block mode
                /* Restart selection once we register a drag. */
-               m_selecting_restart = TRUE;
-               m_has_selection = FALSE;
                m_selecting_had_delta = FALSE;
-
-               m_selection_origin = m_selection_last;
                break;
        case selection_type_word:
        case selection_type_line:
                /* Mark the newly-selected areas now. */
-               m_selecting_restart = FALSE;
-               m_has_selection = FALSE;
-               m_selecting_had_delta = FALSE;
+               m_selecting_had_delta = FALSE;  // FIXME why not TRUE?
                break;
        }
 
        /* Record the selection type. */
        m_selection_type = type;
        m_selecting = TRUE;
-       m_selecting_after_threshold = FALSE;
+        m_will_select_after_threshold = FALSE;
 
        _vte_debug_print(VTE_DEBUG_SELECTION,
-                         "Selection started at (%ld,%ld).\n",
-                         m_selection_start.col,
-                         m_selection_start.row);
+                         "Selection started at (%ld, %.1f).\n",
+                         m_selection_origin_half.row(),
+                         m_selection_origin_half.column() / 2.);
 
         /* Take care of updating the display. */
-        extend_selection(x, y, false, true);
+        resolve_selection();
 
        /* Temporarily stop caring about input from the child. */
        disconnect_pty_read();
@@ -6212,8 +6599,7 @@ Terminal::maybe_end_selection()
 {
        if (m_selecting) {
                /* Copy only if something was selected. */
-               if (m_has_selection &&
-                   !m_selecting_restart &&
+                if (!m_selection_resolved.empty() &&
                    m_selecting_had_delta) {
                         widget_copy(VTE_SELECTION_PRIMARY, VTE_FORMAT_TEXT);
                        emit_selection_changed();
@@ -6226,393 +6612,12 @@ Terminal::maybe_end_selection()
                return true;
        }
 
-        if (m_selecting_after_threshold)
+        if (m_will_select_after_threshold)
                 return true;
 
         return false;
 }
 
-static long
-math_div (long a, long b)
-{
-       if (G_LIKELY (a >= 0))
-               return a / b;
-       else
-               return (a / b) - 1;
-}
-
-/* Helper */
-void
-Terminal::extend_selection_expand()
-{
-       long i, j;
-       const VteCell *cell;
-       VteVisualPosition *sc, *ec;
-
-       if (m_selection_block_mode)
-               return;
-
-       sc = &m_selection_start;
-       ec = &m_selection_end;
-
-       /* Extend the selection to handle end-of-line cases, word, and line
-        * selection.  We do this here because calculating it once is cheaper
-        * than recalculating for each cell as we render it. */
-
-       /* Handle end-of-line at the start-cell. */
-       VteRowData const* rowdata = find_row_data(sc->row);
-       if (rowdata != NULL) {
-               /* Find the last non-empty character on the first line. */
-               for (i = _vte_row_data_length (rowdata); i > 0; i--) {
-                       cell = _vte_row_data_get (rowdata, i - 1);
-                       if (cell->attr.fragment() || cell->c != 0)
-                               break;
-               }
-       } else {
-                i = 0;
-       }
-        if (sc->col > i) {
-                if (m_selection_type == selection_type_char) {
-                        /* If the start point is neither over the used cells, nor over the first
-                         * unused one, then move it to the next line. This way you can still start
-                         * selecting at the newline character by clicking over the first unused cell.
-                         * See bug 725909. */
-                        sc->col = -1;
-                        sc->row++;
-                } else if (m_selection_type == selection_type_word) {
-                        sc->col = i;
-                }
-        }
-        sc->col = find_start_column(sc->col, sc->row);
-
-       /* Handle end-of-line at the end-cell. */
-       rowdata = find_row_data(ec->row);
-       if (rowdata != NULL) {
-               /* Find the last non-empty character on the last line. */
-               for (i = _vte_row_data_length (rowdata); i > 0; i--) {
-                       cell = _vte_row_data_get (rowdata, i - 1);
-                       if (cell->attr.fragment() || cell->c != 0)
-                               break;
-               }
-               /* If the end point is to its right, then extend the
-                * endpoint to the beginning of the next row. */
-               if (ec->col >= i) {
-                       ec->col = -1;
-                       ec->row++;
-               }
-       } else {
-               /* Snap to the beginning of the next line, only if
-                * selecting anything of this row. */
-               if (ec->col >= 0) {
-                       ec->col = -1;
-                       ec->row++;
-               }
-       }
-       ec->col = find_end_column(ec->col, ec->row);
-
-       /* Now extend again based on selection type. */
-       switch (m_selection_type) {
-       case selection_type_char:
-               /* Nothing more to do. */
-               break;
-       case selection_type_word:
-               /* Keep selecting to the left as long as the next character we
-                * look at is of the same class as the current start point. */
-               i = sc->col;
-               j = sc->row;
-               while (_vte_ring_contains (m_screen->row_data, j)) {
-                       /* Get the data for the row we're looking at. */
-                       rowdata = _vte_ring_index(m_screen->row_data, j);
-                       if (rowdata == NULL) {
-                               break;
-                       }
-                       /* Back up. */
-                       for (i = (j == sc->row) ?
-                                sc->col :
-                                m_column_count;
-                            i > 0;
-                            i--) {
-                               if (is_same_class(
-                                                  i - 1,
-                                                  j,
-                                                  i,
-                                                  j)) {
-                                       sc->col = i - 1;
-                                       sc->row = j;
-                               } else {
-                                       break;
-                               }
-                       }
-                       if (i > 0) {
-                               /* We hit a stopping point, so stop. */
-                               break;
-                       } else {
-                               if (line_is_wrappable(j - 1) &&
-                                   is_same_class(
-                                                  m_column_count - 1,
-                                                  j - 1,
-                                                  0,
-                                                  j)) {
-                                       /* Move on to the previous line. */
-                                       j--;
-                                       sc->col = m_column_count - 1;
-                                       sc->row = j;
-                               } else {
-                                       break;
-                               }
-                       }
-               }
-               /* Keep selecting to the right as long as the next character we
-                * look at is of the same class as the current end point. */
-               i = ec->col;
-               j = ec->row;
-               while (_vte_ring_contains (m_screen->row_data, j)) {
-                       /* Get the data for the row we're looking at. */
-                       rowdata = _vte_ring_index(m_screen->row_data, j);
-                       if (rowdata == NULL) {
-                               break;
-                       }
-                       /* Move forward. */
-                       for (i = (j == ec->row) ?
-                                ec->col :
-                                0;
-                            i < m_column_count - 1;
-                            i++) {
-                               if (is_same_class(
-                                                  i,
-                                                  j,
-                                                  i + 1,
-                                                  j)) {
-                                       ec->col = i + 1;
-                                       ec->row = j;
-                               } else {
-                                       break;
-                               }
-                       }
-                       if (i < m_column_count - 1) {
-                               /* We hit a stopping point, so stop. */
-                               break;
-                       } else {
-                               if (line_is_wrappable(j) &&
-                                   is_same_class(
-                                                  m_column_count - 1,
-                                                  j,
-                                                  0,
-                                                  j + 1)) {
-                                       /* Move on to the next line. */
-                                       j++;
-                                       ec->col = 0;
-                                       ec->row = j;
-                               } else {
-                                       break;
-                               }
-                       }
-               }
-               break;
-       case selection_type_line:
-               /* Extend the selection to the beginning of the start line. */
-               sc->col = 0;
-               /* Now back up as far as we can go. */
-               j = sc->row;
-               while (_vte_ring_contains (m_screen->row_data, j - 1) &&
-                      line_is_wrappable(j - 1)) {
-                       j--;
-                       sc->row = j;
-               }
-               /* And move forward as far as we can go. */
-                if (ec->col < 0) {
-                        /* If triple clicking on an unused area, ec already points
-                         * to the beginning of the next line after the second click.
-                         * Go back to the actual row we're at. See bug 725909. */
-                        ec->row--;
-                }
-               j = ec->row;
-               while (_vte_ring_contains (m_screen->row_data, j) &&
-                      line_is_wrappable(j)) {
-                       j++;
-                       ec->row = j;
-               }
-               /* Make sure we include all of the last line by extending
-                * to the beginning of the next line. */
-               ec->row++;
-               ec->col = -1;
-               break;
-       }
-}
-
-/* Extend selection to include the given event coordinates. */
-void
-Terminal::extend_selection(long x,
-                                     long y,
-                                     bool always_grow,
-                                     bool force)
-{
-       long residual;
-       long row;
-        vte::view::coords *origin, *last, *start, *end;
-       VteVisualPosition old_start, old_end, *sc, *ec, *so, *eo;
-       gboolean invalidate_selected = FALSE;
-       gboolean had_selection;
-
-       /* Confine coordinates into the visible area. (#563024, #722635c7) */
-       confine_coordinates(&x, &y);
-
-       old_start = m_selection_start;
-       old_end = m_selection_end;
-       so = &old_start;
-       eo = &old_end;
-
-       /* If we're restarting on a drag, then mark this as the start of
-        * the selected block. */
-       if (m_selecting_restart) {
-               deselect_all();
-               invalidate_selected = TRUE;
-               _vte_debug_print(VTE_DEBUG_SELECTION,
-                               "Selection delayed start at (%ld,%ld).\n",
-                               m_selection_origin.x / m_cell_width,
-                               m_selection_origin.y / m_cell_height);
-       }
-
-       /* Recognize that we've got a selected block. */
-       had_selection = m_has_selection;
-       m_has_selection = TRUE;
-       m_selecting_had_delta = TRUE;
-       m_selecting_restart = FALSE;
-
-       /* If we're not in always-grow mode, update the last location of
-        * the selection. */
-       last = &m_selection_last;
-
-       /* Map the origin and last selected points to a start and end. */
-       origin = &m_selection_origin;
-       if (m_selection_block_mode) {
-               last->x = x;
-               last->y = scroll_delta_pixel() + y;
-
-               /* We don't support always_grow in block mode */
-               if (always_grow)
-                       invalidate_selection();
-
-               if (origin->y <= last->y) {
-                       /* The origin point is "before" the last point. */
-                       start = origin;
-                       end = last;
-               } else {
-                       /* The last point is "before" the origin point. */
-                       start = last;
-                       end = origin;
-               }
-       } else {
-               if (!always_grow) {
-                       last->x = x;
-                       last->y = scroll_delta_pixel() + y;
-               }
-
-               if ((origin->y / m_cell_height < last->y / m_cell_height) ||
-                   ((origin->y / m_cell_height == last->y / m_cell_height) &&
-                    (origin->x / m_cell_width < last->x / m_cell_width ))) {
-                       /* The origin point is "before" the last point. */
-                       start = origin;
-                       end = last;
-               } else {
-                       /* The last point is "before" the origin point. */
-                       start = last;
-                       end = origin;
-               }
-
-               /* Extend the selection by moving whichever end of the selection is
-                * closer to the new point. */
-               if (always_grow) {
-                       /* New endpoint is before existing selection. */
-                        row = pixel_to_row(y);
-                       if ((row < start->y / m_cell_height) ||
-                           ((row == start->y / m_cell_height) &&
-                            (x / m_cell_width < start->x / m_cell_width))) {
-                               start->x = x;
-                               start->y = scroll_delta_pixel() + y;
-                       } else {
-                               /* New endpoint is after existing selection. */
-                               end->x = x;
-                               end->y = scroll_delta_pixel() + y;
-                       }
-               }
-       }
-
-#if 0
-       _vte_debug_print(VTE_DEBUG_SELECTION,
-                       "Selection is (%ld,%ld) to (%ld,%ld).\n",
-                       start->x, start->y, end->x, end->y);
-#endif
-
-       /* Recalculate the selection area in terms of cell positions. */
-
-       sc = &m_selection_start;
-       ec = &m_selection_end;
-
-       sc->row = MAX (0, start->y / m_cell_height);
-       ec->row = MAX (0, end->y   / m_cell_height);
-
-       /* Sort x using row cell coordinates */
-       if ((m_selection_block_mode || sc->row == ec->row) && (start->x > end->x)) {
-                vte::view::coords *tmp;
-               tmp = start;
-               start = end;
-               end = tmp;
-       }
-
-       /* We want to be more lenient on the user with their column selection.
-        * We round to the closest logical position (positions are located between
-        * cells).  But we don't want to fully round.  So we divide the cell
-        * width into three parts.  The side parts round to their nearest
-        * position.  The middle part is always inclusive in the selection.
-        *
-        * math_div and no MAX, to allow selecting no cells in the line,
-        * ie. ec->col = -1, which is essentially equal to copying the
-        * newline from previous line but no chars from current line. */
-       residual = (m_cell_width + 1) / 3;
-       sc->col = math_div (start->x + residual, m_cell_width);
-       ec->col = math_div (end->x - residual, m_cell_width);
-
-       extend_selection_expand();
-
-       if (!invalidate_selected && !force &&
-           0 == memcmp (sc, so, sizeof (*sc)) &&
-           0 == memcmp (ec, eo, sizeof (*ec)))
-               /* No change */
-               return;
-
-       /* Invalidate */
-
-       if (had_selection) {
-
-               if (m_selection_block_mode) {
-                        /* Update the selection area diff in block mode.
-                         * We could optimize when the columns don't change, probably not worth it. */
-                        invalidate_rows(MIN(sc->row, so->row), MAX(ec->row, eo->row));
-               } else {
-                       /* Update the selection area diff in non-block mode. */
-
-                       /* The before band */
-                        // FIXMEegmont simplify these conditions when sc becomes a grid:coords.
-                        if (sc->row != so->row || sc->col != so->col)
-                                invalidate_rows(sc->row, so->row);
-                       /* The after band */
-                        if (ec->row != eo->row || ec->col != eo->col)
-                                invalidate_rows(ec->row, eo->row);
-               }
-       }
-
-       if (invalidate_selected || !had_selection) {
-               _vte_debug_print(VTE_DEBUG_SELECTION, "Invalidating selection.");
-               invalidate_selection();
-       }
-
-       _vte_debug_print(VTE_DEBUG_SELECTION,
-                       "Selection changed to "
-                       "(%ld,%ld) to (%ld,%ld).\n",
-                       sc->col, sc->row, ec->col, ec->row);
-}
-
 /*
  * Terminal::select_all:
  *
@@ -6623,14 +6628,10 @@ Terminal::select_all()
 {
        deselect_all();
 
-       m_has_selection = TRUE;
        m_selecting_had_delta = TRUE;
-       m_selecting_restart = FALSE;
 
-       m_selection_start.row = _vte_ring_delta (m_screen->row_data);
-       m_selection_start.col = 0;
-       m_selection_end.row = _vte_ring_next (m_screen->row_data);
-       m_selection_end.col = -1;
+        m_selection_resolved.set (vte::grid::coords (_vte_ring_delta (m_screen->row_data), 0),
+                                  vte::grid::coords (_vte_ring_next (m_screen->row_data), 0));
 
        _vte_debug_print(VTE_DEBUG_SELECTION, "Selecting *all* text.\n");
 
@@ -6695,7 +6696,7 @@ Terminal::autoscroll()
                        x = m_column_count * m_cell_width;
                }
                /* Extend selection to cover the newly-scrolled area. */
-                extend_selection(x, y, false, true);
+                modify_selection(vte::view::coords(x, y));
        } else {
                /* Stop autoscrolling. */
                m_mouse_autoscroll_tag = 0;
@@ -6746,22 +6747,21 @@ Terminal::widget_motion_notify(GdkEventMotion *event)
 
        switch (event->type) {
        case GDK_MOTION_NOTIFY:
-               if (m_selecting_after_threshold) {
+                if (m_will_select_after_threshold) {
                        if (!gtk_drag_check_threshold (m_widget,
                                                       m_mouse_last_position.x,
                                                       m_mouse_last_position.y,
                                                       pos.x, pos.y))
                                return true;
 
-                       start_selection(m_mouse_last_position.x,
-                                        m_mouse_last_position.y,
+                        start_selection(vte::view::coords(m_mouse_last_position.x, m_mouse_last_position.y),
                                         selection_type_char);
                }
 
                if (m_selecting &&
                     (m_mouse_handled_buttons & 1) != 0) {
                        _vte_debug_print(VTE_DEBUG_EVENTS, "Mousing drag 1.\n");
-                       extend_selection(pos.x, pos.y, false, false);
+                        modify_selection(pos);
 
                        /* Start scrolling if we need to. */
                        if (pos.y < 0 || pos.y >= m_view_usable_extents.height()) {
@@ -6832,10 +6832,7 @@ Terminal::widget_button_press(GdkEventButton *event)
                                /* If the user hit shift, then extend the
                                 * selection instead. */
                                if ((m_modifiers & GDK_SHIFT_MASK) &&
-                                   (m_has_selection ||
-                                    m_selecting_restart) &&
-                                   !cell_is_selected(rowcol.column(),
-                                                      rowcol.row())) {
+                                    !m_selection_resolved.empty()) {
                                        extend_selecting = TRUE;
                                } else {
                                        start_selecting = TRUE;
@@ -6843,16 +6840,17 @@ Terminal::widget_button_press(GdkEventButton *event)
                        }
                        if (start_selecting) {
                                deselect_all();
-                               m_selecting_after_threshold = TRUE;
+                                m_will_select_after_threshold = TRUE;
                                 m_selection_block_mode = !!(m_modifiers & GDK_CONTROL_MASK);
                                handled = true;
                        }
                        if (extend_selecting) {
-                               extend_selection(pos.x, pos.y, !m_selecting_restart, true);
                                /* The whole selection code needs to be
                                 * rewritten.  For now, put this here to
                                 * fix bug 614658 */
                                m_selecting = TRUE;
+                                selection_maybe_swap_endpoints (pos, m_selection_type, 
m_selection_block_mode);
+                                modify_selection(pos);
                                handled = true;
                        }
                        break;
@@ -6894,13 +6892,13 @@ Terminal::widget_button_press(GdkEventButton *event)
                                  rowcol.to_string());
                switch (event->button) {
                case 1:
-                       if (m_selecting_after_threshold) {
-                               start_selection(pos.x, pos.y,
+                        if (m_will_select_after_threshold) {
+                                start_selection(pos,
                                                 selection_type_char);
                                handled = true;
                        }
                         if ((m_mouse_handled_buttons & 1) != 0) {
-                               start_selection(pos.x, pos.y,
+                                start_selection(pos,
                                                 selection_type_word);
                                handled = true;
                        }
@@ -6919,7 +6917,7 @@ Terminal::widget_button_press(GdkEventButton *event)
                switch (event->button) {
                case 1:
                         if ((m_mouse_handled_buttons & 1) != 0) {
-                               start_selection(pos.x, pos.y,
+                                start_selection(pos,
                                                 selection_type_line);
                                handled = true;
                        }
@@ -6990,7 +6988,7 @@ Terminal::widget_button_release(GdkEventButton *event)
                 m_mouse_pressed_buttons &= ~(1 << (event->button - 1));
 
        m_mouse_last_position = pos;
-       m_selecting_after_threshold = false;
+        m_will_select_after_threshold = false;
 
         set_pointer_autohidden(false);
         hyperlink_hilite_update();
@@ -7390,6 +7388,7 @@ Terminal::screen_set_size(VteScreen *screen_,
        VteVisualPosition cursor_saved_absolute;
        VteVisualPosition below_viewport;
        VteVisualPosition below_current_paragraph;
+        VteVisualPosition selection_start, selection_end;
        VteVisualPosition *markers[7];
         gboolean was_scrolled_to_top = ((long) ceil(screen_->scroll_delta) == _vte_ring_delta(ring));
         gboolean was_scrolled_to_bottom = ((long) screen_->scroll_delta == screen_->insert_delta);
@@ -7424,11 +7423,13 @@ Terminal::screen_set_size(VteScreen *screen_,
         markers[1] = &below_viewport;
         markers[2] = &below_current_paragraph;
         markers[3] = &screen_->cursor;
-        if (m_has_selection) {
-                /* selection_end is inclusive, make it non-inclusive, see bug 722635. */
-                m_selection_end.col++;
-                markers[4] = &m_selection_start;
-                markers[5] = &m_selection_end;
+        if (!m_selection_resolved.empty()) {
+                selection_start.row = m_selection_resolved.start_row();
+                selection_start.col = m_selection_resolved.start_column();
+                selection_end.row = m_selection_resolved.end_row();
+                selection_end.col = m_selection_resolved.end_column();
+                markers[4] = &selection_start;
+                markers[5] = &selection_end;
        }
 
        old_top_lines = below_current_paragraph.row - screen_->insert_delta;
@@ -7456,9 +7457,9 @@ Terminal::screen_set_size(VteScreen *screen_,
                }
        }
 
-       if (m_has_selection) {
-               /* Make selection_end inclusive again, see above. */
-               m_selection_end.col--;
+        if (!m_selection_resolved.empty()) {
+                m_selection_resolved.set (vte::grid::coords (selection_start.row, selection_start.col),
+                                          vte::grid::coords (selection_end.row, selection_end.col));
        }
 
        /* Figure out new insert and scroll deltas */
@@ -9868,18 +9869,13 @@ Terminal::reset(bool clear_tabstops,
         /* Reset the visual bits of selection on hard reset, see bug 789954. */
         if (clear_history) {
                 deselect_all();
-                m_has_selection = FALSE;
                 m_selecting = FALSE;
-                m_selecting_restart = FALSE;
                 m_selecting_had_delta = FALSE;
-                memset(&m_selection_origin, 0,
-                       sizeof(m_selection_origin));
-                memset(&m_selection_last, 0,
-                       sizeof(m_selection_last));
-                memset(&m_selection_start, 0,
-                       sizeof(m_selection_start));
-                memset(&m_selection_end, 0,
-                       sizeof(m_selection_end));
+                memset(&m_selection_origin_half, 0,
+                       sizeof(m_selection_origin_half));
+                memset(&m_selection_last_half, 0,
+                       sizeof(m_selection_last_half));
+                m_selection_resolved.clear();
         }
 
        /* Reset mouse motion events. */
@@ -9999,10 +9995,8 @@ Terminal::select_text(vte::grid::column_t start_col,
 
        m_selection_type = selection_type_char;
        m_selecting_had_delta = true;
-       m_selection_start.col = start_col;
-       m_selection_start.row = start_row;
-       m_selection_end.col = end_col;
-       m_selection_end.row = end_row;
+        m_selection_resolved.set (vte::grid::coords (start_row, start_col),
+                                  vte::grid::coords (end_row, end_col));
         widget_copy(VTE_SELECTION_PRIMARY, VTE_FORMAT_TEXT);
        emit_selection_changed();
 
@@ -10013,7 +10007,7 @@ void
 Terminal::select_empty(vte::grid::column_t col,
                                  vte::grid::row_t row)
 {
-        select_text(col, row, col - 1, row);
+        select_text(col, row, col, row);
 }
 
 static void
@@ -10557,7 +10551,7 @@ Terminal::search_rows(pcre2_match_context_8 *match_context,
        gdouble value, page_size;
 
        auto row_text = get_text(start_row, 0,
-                                 end_row, -1,
+                                 end_row, 0,
                                  false /* block */,
                                  true /* wrap */,
                                  nullptr);
@@ -10608,7 +10602,7 @@ Terminal::search_rows(pcre2_match_context_8 *match_context,
                m_search_attrs = g_array_new (FALSE, TRUE, sizeof (VteCharAttributes));
        attrs = m_search_attrs;
        row_text = get_text(start_row, 0,
-                            end_row, -1,
+                            end_row, 0,
                             false /* block */,
                             true /* wrap */,
                             attrs);
@@ -10618,7 +10612,7 @@ Terminal::search_rows(pcre2_match_context_8 *match_context,
        start_col = ca->column;
        ca = &g_array_index (attrs, VteCharAttributes, end - 1);
        end_row = ca->row;
-       end_col = ca->column + ca->columns - 1 /* select_text is end-inclusive */;
+        end_col = ca->column + ca->columns;
 
        g_string_free (row_text, TRUE);
 
@@ -10701,9 +10695,9 @@ Terminal::search_find (bool backward)
        buffer_start_row = _vte_ring_delta (m_screen->row_data);
        buffer_end_row = _vte_ring_next (m_screen->row_data);
 
-       if (m_has_selection) {
-               last_start_row = m_selection_start.row;
-               last_end_row = m_selection_end.row + 1;
+        if (!m_selection_resolved.empty()) {
+                last_start_row = m_selection_resolved.start_row();
+                last_end_row = m_selection_resolved.end_row() + 1;
        } else {
                last_start_row = m_screen->scroll_delta + m_row_count;
                last_end_row = m_screen->scroll_delta;
@@ -10721,9 +10715,9 @@ Terminal::search_find (bool backward)
                    search_rows_iter (match_context, match_data,
                                       last_end_row, buffer_end_row, backward))
                        goto found;
-               if (m_has_selection) {
+                if (!m_selection_resolved.empty()) {
                        if (m_search_wrap_around)
-                           select_empty(m_selection_start.col, m_selection_start.row);
+                            select_empty(m_selection_resolved.start_column(), 
m_selection_resolved.start_row());
                        else
                            select_empty(-1, buffer_start_row - 1);
                }
@@ -10736,11 +10730,11 @@ Terminal::search_find (bool backward)
                    search_rows_iter (match_context, match_data,
                                       buffer_start_row, last_start_row, backward))
                        goto found;
-               if (m_has_selection) {
+                if (!m_selection_resolved.empty()) {
                        if (m_search_wrap_around)
-                                select_empty(m_selection_end.col + 1, m_selection_end.row);
+                                select_empty(m_selection_resolved.end_column() + 1 /* ? */, 
m_selection_resolved.end_row());
                        else
-                                select_empty(-1, buffer_end_row);
+                                select_empty(-1 /* or 0 ? */, buffer_end_row);
                }
                 match_found = false;
        }
diff --git a/src/vteaccess.cc b/src/vteaccess.cc
index b6155af1..1851e077 100644
--- a/src/vteaccess.cc
+++ b/src/vteaccess.cc
@@ -1421,14 +1421,11 @@ vte_terminal_accessible_get_selection(AtkText *text, gint selection_number,
 
         auto impl = IMPL_FROM_WIDGET(widget);
 
-       if (!impl->m_has_selection || impl->m_selection[VTE_SELECTION_PRIMARY] == nullptr)
+        if (impl->m_selection_resolved.empty() || impl->m_selection[VTE_SELECTION_PRIMARY] == nullptr)
                return NULL;
 
-        auto start_sel = impl->m_selection_start;
-        auto end_sel = impl->m_selection_end;
-
-       *start_offset = offset_from_xy (priv, start_sel.col, start_sel.row);
-       *end_offset = offset_from_xy (priv, end_sel.col, end_sel.row);
+        *start_offset = offset_from_xy (priv, impl->m_selection_resolved.start_column(), 
impl->m_selection_resolved.start_row());
+        *end_offset = offset_from_xy (priv, impl->m_selection_resolved.end_column(), 
impl->m_selection_resolved.end_row());
 
        return g_strdup(impl->m_selection[VTE_SELECTION_PRIMARY]->str);
 }
diff --git a/src/vtegtk.cc b/src/vtegtk.cc
index 194837f4..ea8b3208 100644
--- a/src/vtegtk.cc
+++ b/src/vtegtk.cc
@@ -3918,7 +3918,7 @@ gboolean
 vte_terminal_get_has_selection(VteTerminal *terminal)
 {
        g_return_val_if_fail(VTE_IS_TERMINAL(terminal), FALSE);
-       return IMPL(terminal)->m_has_selection;
+        return !IMPL(terminal)->m_selection_resolved.empty();
 }
 
 /**
diff --git a/src/vteinternal.hh b/src/vteinternal.hh
index 6bb39548..77d54276 100644
--- a/src/vteinternal.hh
+++ b/src/vteinternal.hh
@@ -423,22 +423,20 @@ public:
         std::u32string m_word_char_exceptions;
 
        /* Selection information. */
-        gboolean m_has_selection;
         gboolean m_selecting;
-        gboolean m_selecting_after_threshold;
-        gboolean m_selecting_restart;
+        gboolean m_will_select_after_threshold;
         gboolean m_selecting_had_delta;
-        gboolean m_selection_block_mode;
+        gboolean m_selection_block_mode;  // FIXMEegmont move it into a 4th value in vte_selection_type?
         enum vte_selection_type m_selection_type;
-        vte::view::coords m_selection_origin, m_selection_last;
-        VteVisualPosition m_selection_start, m_selection_end;
+        vte::grid::coords m_selection_origin_half, m_selection_last_half;
+        vte::grid::span m_selection_resolved;
 
        /* Clipboard data information. */
         // FIXMEchpe check if this can make m_has_selection obsolete!
         bool m_selection_owned[LAST_VTE_SELECTION];
         VteFormat m_selection_format[LAST_VTE_SELECTION];
         bool m_changing_selection;
-        GString *m_selection[LAST_VTE_SELECTION];
+        GString *m_selection[LAST_VTE_SELECTION];  // FIXMEegmont rename this so that m_selection_resolved 
can become m_selection?
         GtkClipboard *m_clipboard[LAST_VTE_SELECTION];
 
         ClipboardTextRequestGtk<Terminal> m_paste_request;
@@ -675,8 +673,8 @@ public:
         void invalidate_rows(vte::grid::row_t row_start,
                              vte::grid::row_t row_end /* inclusive */);
         void invalidate(vte::grid::span const& s);
+        void invalidate_xor(vte::grid::span const& a, vte::grid::span const& b, bool block);
         void invalidate_match_span();
-        void invalidate_selection();
         void invalidate_all();
 
         void reset_update_rects();
@@ -726,6 +724,8 @@ public:
         vte::view::coords view_coords_from_grid_coords(vte::grid::coords const& rowcol) const;
         vte::grid::coords grid_coords_from_view_coords(vte::view::coords const& pos) const;
 
+        vte::grid::coords selection_grid_half_coords_from_view_coords(vte::view::coords const& pos) const;
+        void selection_maybe_swap_endpoints(vte::view::coords const& pos, enum vte_selection_type type, bool 
block);  // FIXME move elsewhere?
         bool view_coords_visible(vte::view::coords const& pos) const;
         bool grid_coords_visible(vte::grid::coords const& rowcol) const;
 
@@ -944,20 +944,16 @@ public:
         GString* attributes_to_html(GString* text_string,
                                     GArray* attrs);
 
-        void start_selection(long x,
-                             long y,
-                             enum vte_selection_type selection_type);
+        void start_selection(vte::view::coords pos,
+                             enum vte_selection_type type);
         bool maybe_end_selection();
 
-        void extend_selection_expand();
-        void extend_selection(long x,
-                              long y,
-                              bool always_grow,
-                              bool force);
-
         void select_all();
         void deselect_all();
 
+        vte::grid::coords resolve_selection_endpoint(vte::grid::coords rowcolhalf, bool after) const;
+        void resolve_selection();
+        void modify_selection(vte::view::coords pos);
         bool cell_is_selected(vte::grid::column_t col,
                               vte::grid::row_t) const;
 
diff --git a/src/vtetypes.cc b/src/vtetypes.cc
index 367e5290..91c35891 100644
--- a/src/vtetypes.cc
+++ b/src/vtetypes.cc
@@ -294,6 +294,13 @@ test_grid_span (void)
         g_assert_false(s8.box_contains(coords(33, 24)));
         g_assert_false(s8.box_contains(coords(33, 42)));
 
+        /* last_row */
+        span s9(16, 16, 32, 0);
+        g_assert_cmpint(s9.last_row(), ==, 31);
+
+        span s10(16, 16, 32, 1);
+        g_assert_cmpint(s10.last_row(), ==, 32);
+
 #ifdef VTE_DEBUG
         /* to_string() */
         g_assert_cmpstr(vte::grid::span(17, 42, 18, 3).to_string(), ==, "grid[17,42 .. 18,3)");
diff --git a/src/vtetypes.hh b/src/vtetypes.hh
index e1bb775d..8a4ce2a8 100644
--- a/src/vtetypes.hh
+++ b/src/vtetypes.hh
@@ -81,6 +81,7 @@ namespace grid {
                 inline coords const& end()   const { return m_end; }
                 inline row_t start_row()       const { return m_start.row(); }
                 inline row_t end_row()         const { return m_end.row(); }
+                inline row_t last_row()        const { return m_end.column() > 0 ? m_end.row() : m_end.row() 
- 1; }
                 inline column_t start_column() const { return m_start.column(); }
                 inline column_t end_column()   const { return m_end.column(); }
 
@@ -89,6 +90,7 @@ namespace grid {
                 inline explicit operator bool() const { return !empty(); }
 
                 inline bool contains(coords const& p) const { return m_start <= p && p < m_end; }
+                // FIXME make "block" a member of the span? Or subclasses for regular and block spans?
                 inline bool box_contains(coords const& p) const { return m_start.row() <= p.row() && p.row() 
<= m_end.row() &&
                                                                          m_start.column() <= p.column() && 
p.column() < m_end.column(); }
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]