[vte/wip/egmont/bidi: 4/76] vte-34-selection-big-rewrite-v3.patch
- From: Egmont Koblinger <egmontkob src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [vte/wip/egmont/bidi: 4/76] vte-34-selection-big-rewrite-v3.patch
- Date: Sun, 7 Oct 2018 13:03:21 +0000 (UTC)
commit 54555ff921d344ae9655f6bcf8b12798ae81a3f5
Author: Egmont Koblinger <egmont gmail com>
Date: Sun Oct 7 14:15:55 2018 +0200
vte-34-selection-big-rewrite-v3.patch
src/vte.cc | 1016 ++++++++++++++++++++++++++--------------------------
src/vteaccess.cc | 9 +-
src/vtegtk.cc | 2 +-
src/vteinternal.hh | 31 +-
src/vtetypes.cc | 73 +++-
src/vtetypes.hh | 57 ++-
6 files changed, 631 insertions(+), 557 deletions(-)
---
diff --git a/src/vte.cc b/src/vte.cc
index f73594bd..bd27c5a6 100644
--- a/src/vte.cc
+++ b/src/vte.cc
@@ -27,7 +27,6 @@
#include <sys/param.h> /* howmany() */
#include <errno.h>
#include <fcntl.h>
-#include <math.h>
#ifdef HAVE_SYS_TERMIOS_H
#include <sys/termios.h>
#endif
@@ -305,8 +304,35 @@ 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 intervals. */
+ invalidate (a);
+ invalidate (b);
+ return;
+ }
+
+ if (block) {
+ /* We could optimize when the columns don't change, probably not worth it. */
+ invalidate_rows (std::min (a.start_row(), b.start_row()),
+ std::max (a.last_row(), b.last_row()));
+ } else {
+ if (a.start() != b.start()) {
+ invalidate_rows (std::min (a.start_row(), b.start_row()),
+ std::max (a.start_row(), b.start_row()));
+ }
+ if (a.end() != b.end()) {
+ invalidate_rows (std::min (a.last_row(), b.last_row()),
+ std::max (a.last_row(), b.last_row()));
+ }
+ }
}
void
@@ -725,21 +751,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 = m_selection_last = vte::grid::halfcoords (-1, -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);
}
}
@@ -1530,6 +1552,84 @@ 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.
+ *
+ * Storing the actual view coordinates would become problematic when the font size changes (bug 756058),
+ * and would cause too much work when the mouse moves within the half cell.
+ *
+ * Left margin or anything further to the left is denoted by column -1's right half,
+ * right margin or anything further to the right is denoted by column m_column_count's left half.
+ */
+vte::grid::halfcoords
+Terminal::selection_grid_halfcoords_from_view_coords(vte::view::coords const& pos) const
+{
+ vte::grid::row_t row = pixel_to_row(pos.y);
+ vte::grid::halfcolumn_t halfcolumn;
+
+ if (pos.x < 0) {
+ halfcolumn.set_column(-1);
+ halfcolumn.set_half(1);
+ } else if (pos.x >= m_column_count * m_cell_width) {
+ halfcolumn.set_column(m_column_count);
+ halfcolumn.set_half(0);
+ } else {
+ halfcolumn.set_column(pos.x / m_cell_width);
+ halfcolumn.set_half((pos.x * 2 / m_cell_width) % 2);
+ }
+
+ return vte::grid::halfcoords(row, halfcolumn);
+}
+
+/*
+ * 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::halfcoords current = selection_grid_halfcoords_from_view_coords (pos);
+
+ if (block) {
+ if ((current.row() <= m_selection_origin.row() && m_selection_origin.row() <
m_selection_last.row()) ||
+ (current.row() >= m_selection_origin.row() && m_selection_origin.row() >
m_selection_last.row())) {
+ auto tmp = m_selection_origin.row();
+ m_selection_origin.set_row(m_selection_last.row());
+ m_selection_last.set_row(tmp);
+ }
+ if ((current.halfcolumn() <= m_selection_origin.halfcolumn() &&
m_selection_origin.halfcolumn() < m_selection_last.halfcolumn()) ||
+ (current.halfcolumn() >= m_selection_origin.halfcolumn() &&
m_selection_origin.halfcolumn() > m_selection_last.halfcolumn())) {
+ auto tmp = m_selection_origin.halfcolumn();
+ m_selection_origin.set_halfcolumn(m_selection_last.halfcolumn());
+ m_selection_last.set_halfcolumn(tmp);
+ }
+ } else {
+ if ((current <= m_selection_origin && m_selection_origin < m_selection_last) ||
+ (current >= m_selection_origin && m_selection_origin > m_selection_last)) {
+ auto tmp = m_selection_origin;
+ m_selection_origin = m_selection_last;
+ m_selection_last = tmp;
+ }
+ }
+
+ _vte_debug_print(VTE_DEBUG_SELECTION,
+ "Selection maybe swap endpoints: origin=%s last=%s\n",
+ m_selection_origin.to_string(),
+ m_selection_last.to_string());
+}
+
bool
Terminal::rowcol_from_event(GdkEvent *event,
long *column,
@@ -3625,7 +3725,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) ||
@@ -4923,6 +5023,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) */
@@ -4950,31 +5058,328 @@ 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, taking multi-cell characters
(CJKs, TABs) 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), but over no other character, selects this entire word. By dragging the
mouse it's not possible to
+ * select nothing, the word (or non-word character) 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 (or non-word character) 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::halfcoords rowcolhalf, bool after) const
+{
+ vte::grid::row_t row = rowcolhalf.row();
+ /* col points to an actual cell now. At the end of this method it'll point to a boundary. */
+ vte::grid::column_t col = rowcolhalf.halfcolumn().column();
+ vte::grid::half_t half = rowcolhalf.halfcolumn().half(); /* 0 for left half, 1 for right half of
the cell */
+ 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 += half;
+ 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 (col < 0) {
+ col = 0;
+ } else if (col >= 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 && col < rowdata->len) {
+ /* Clicked over a used cell. Check for multi-cell characters. */
+ char_begin = col;
+ 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 = col;
+ char_end = char_begin + 1;
+ }
+ /* Which boundary is closer? */
+ if (col * 2 + half < 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:
+ /* Initialization for the cumbersome cases where the click didn't occur over an
actual used cell. */
+ rowdata = find_row_data(row);
+ if (col < 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 (col >= (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 next 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.row() < 0 || m_selection_last.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 = std::min (m_selection_origin.row(), m_selection_last.row());
+ vte::grid::row_t bottom = std::max (m_selection_origin.row(), m_selection_last.row());
+ vte::grid::halfcolumn_t left = std::min (m_selection_origin.halfcolumn(),
m_selection_last.halfcolumn());
+ vte::grid::halfcolumn_t right = std::max (m_selection_origin.halfcolumn(),
m_selection_last.halfcolumn());
+
+ vte::grid::coords topleft = resolve_selection_endpoint (vte::grid::halfcoords (top,
left), false);
+ vte::grid::coords bottomright = resolve_selection_endpoint (vte::grid::halfcoords (bottom,
right), true);
+
+ if (topleft.column() == bottomright.column()) {
+ m_selection_resolved.clear();
+ } else {
+ m_selection_resolved.set (topleft, bottomright);
+ }
+ } else {
+ vte::grid::halfcoords a = std::min (m_selection_origin, m_selection_last);
+ vte::grid::halfcoords b = std::max (m_selection_origin, m_selection_last);
+
+ m_selection_resolved.set (resolve_selection_endpoint (a, false),
+ resolve_selection_endpoint (b, 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::halfcoords current = selection_grid_halfcoords_from_view_coords (pos);
+
+ if (current == m_selection_last)
+ return;
+
+ _vte_debug_print(VTE_DEBUG_SELECTION,
+ "Selection dragged to %s.\n",
+ current.to_string());
+
+ m_selection_last = current;
+ 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));
}
}
@@ -5509,7 +5914,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();
}
@@ -5656,7 +6061,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;
@@ -5736,7 +6141,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);
}
@@ -5749,7 +6154,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);
}
@@ -5757,10 +6162,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);
@@ -6043,7 +6448,6 @@ Terminal::widget_copy(VteSelection sel,
if (selection == nullptr) {
g_array_free(attributes, TRUE);
- m_has_selection = FALSE;
m_selection_owned[sel] = false;
return;
}
@@ -6057,8 +6461,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,
@@ -6097,12 +6501,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,
@@ -6135,54 +6533,42 @@ 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);
+ m_selection_origin = m_selection_last = selection_grid_halfcoords_from_view_coords(pos);
- /* 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;
-
- /* 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 %s.\n",
+ m_selection_origin.to_string());
/* 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();
@@ -6193,8 +6579,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();
@@ -6207,395 +6592,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(std::min(sc->row, so->row), std::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(std::min(sc->row, so->row),
- std::max(sc->row, so->row));
- /* The after band */
- if (ec->row != eo->row || ec->col != eo->col)
- invalidate_rows(std::min(ec->row, eo->row),
- std::max(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:
*
@@ -6606,14 +6608,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");
@@ -6678,7 +6676,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;
@@ -6729,22 +6727,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()) {
@@ -6815,10 +6812,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;
@@ -6826,16 +6820,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;
@@ -6877,13 +6872,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;
}
@@ -6902,7 +6897,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;
}
@@ -6973,7 +6968,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();
@@ -7373,6 +7368,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);
@@ -7407,11 +7403,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;
@@ -7439,9 +7437,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 */
@@ -9858,18 +9856,10 @@ 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));
+ m_selection_origin = m_selection_last = vte::grid::halfcoords(-1, -1, 1);
+ m_selection_resolved.clear();
}
/* Reset mouse motion events. */
@@ -9989,10 +9979,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();
@@ -10003,7 +9991,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
@@ -10547,7 +10535,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);
@@ -10598,7 +10586,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);
@@ -10608,7 +10596,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);
@@ -10691,9 +10679,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;
@@ -10711,9 +10699,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);
}
@@ -10726,11 +10714,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..817141e2 100644
--- a/src/vteinternal.hh
+++ b/src/vteinternal.hh
@@ -423,22 +423,19 @@ 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::halfcoords m_selection_origin, m_selection_last;
+ 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 +672,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 +723,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::halfcoords selection_grid_halfcoords_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 +943,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::halfcoords 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 f5c30b15..c657cc25 100644
--- a/src/vtetypes.cc
+++ b/src/vtetypes.cc
@@ -23,10 +23,8 @@
#include <type_traits>
-static_assert(std::is_pod<vte::grid::coords>::value, "vte::grid::coords not POD");
static_assert(sizeof(vte::grid::coords) == 2 * sizeof(long), "vte::grid::coords size wrong");
-static_assert(std::is_pod<vte::grid::span>::value, "vte::grid::span not POD");
static_assert(sizeof(vte::grid::span) == 4 * sizeof(long), "vte::grid::span size wrong");
static_assert(std::is_pod<vte::view::coords>::value, "vte::view::coords not POD");
@@ -104,6 +102,14 @@ vte::grid::coords::to_string() const
return buf;
}
+char const*
+vte::grid::halfcoords::to_string() const
+{
+ char *buf = debug_get_buf();
+ g_snprintf(buf, DEBUG_STRING_SIZE, "halfgrid[%ld,%ld%c]", row(), halfcolumn().column(),
halfcolumn().half() ? 'R' : 'L');
+ return buf;
+}
+
char const*
vte::grid::span::to_string() const
{
@@ -203,6 +209,61 @@ test_grid_coords (void)
#endif
}
+static void
+test_grid_halfcoords (void)
+{
+ /* Default constructor */
+ halfcoords p1;
+
+ /* Construction and assignment */
+
+ halfcoords p2(16, halfcolumn_t(32, 1));
+ g_assert_cmpint(p2.row(), ==, 16);
+ g_assert_cmpint(p2.halfcolumn().column(), ==, 32);
+ g_assert_cmpint(p2.halfcolumn().half(), ==, 1);
+
+ /* Comparision operators */
+
+ halfcoords a (10, halfcolumn_t(20, 1));
+ halfcoords a2(10, halfcolumn_t(20, 1));
+ halfcoords b (10, halfcolumn_t(21, 0));
+ halfcoords c (10, halfcolumn_t(21, 1));
+ halfcoords d (10, halfcolumn_t(22, 0));
+ halfcoords e (11, halfcolumn_t( 5, 0));
+
+ g_assert_true (a <= a2);
+ g_assert_false(a < a2);
+ g_assert_false(a > a2);
+ g_assert_true (a >= a2);
+
+ g_assert_true (a <= b);
+ g_assert_true (a < b);
+ g_assert_false(a > b);
+ g_assert_false(a >= b);
+
+ g_assert_true (b <= c);
+ g_assert_true (b < c);
+ g_assert_false(b > c);
+ g_assert_false(b >= c);
+
+ g_assert_true (c <= d);
+ g_assert_true (c < d);
+ g_assert_false(c > d);
+ g_assert_false(c >= d);
+
+ g_assert_true (d <= e);
+ g_assert_true (d < e);
+ g_assert_false(d > e);
+ g_assert_false(d >= e);
+
+#ifdef VTE_DEBUG
+ /* to_string() */
+ g_assert_cmpstr(halfcoords(16, 32, 0).to_string(), ==, "halfgrid[16,32L]");
+ g_assert_cmpstr(halfcoords(16, 32, 1).to_string(), ==, "halfgrid[16,32R]");
+#endif
+}
+
+
static void
test_grid_span (void)
{
@@ -294,6 +355,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))");
@@ -376,6 +444,7 @@ main(int argc, char *argv[])
g_test_init (&argc, &argv, nullptr);
g_test_add_func("/vte/c++/grid/coords", test_grid_coords);
+ g_test_add_func("/vte/c++/grid/halfcoords", test_grid_halfcoords);
g_test_add_func("/vte/c++/grid/span", test_grid_span);
g_test_add_func("/vte/c++/color/rgb", test_color_rgb);
g_test_add_func("/vte/c++/view/coords", test_view_coords);
diff --git a/src/vtetypes.hh b/src/vtetypes.hh
index e1bb775d..4092e43f 100644
--- a/src/vtetypes.hh
+++ b/src/vtetypes.hh
@@ -36,31 +36,53 @@ namespace grid {
typedef long row_t;
typedef long column_t;
+ typedef int half_t;
- struct coords {
+ struct coords : public std::pair<row_t, column_t> {
public:
+ using base_type = std::pair<row_t, column_t>;
+
coords() = default;
- coords(row_t r, column_t c) : m_row(r), m_column(c) { }
+ coords(row_t r, column_t c) : base_type{r, c} { }
+
+ inline void set_row(row_t r) { first = r; }
+ inline void set_column(column_t c) { second = c; }
- inline void set_row(row_t r) { m_row = r; }
- inline void set_column(column_t c) { m_column = c; }
+ inline row_t row() const { return first; }
+ inline column_t column() const { return second; }
- inline row_t row() const { return m_row; }
- inline column_t column() const { return m_column; }
+ IFDEF_DEBUG(char const* to_string() const);
+ };
- inline bool operator == (coords const& rhs) const { return m_row == rhs.m_row && m_column ==
rhs.m_column; }
- inline bool operator != (coords const& rhs) const { return m_row != rhs.m_row || m_column !=
rhs.m_column; }
+ struct halfcolumn_t : public std::pair<column_t, half_t> {
+ public:
+ using base_type = std::pair<column_t, half_t>;
- inline bool operator < (coords const& rhs) const { return m_row < rhs.m_row || (m_row ==
rhs.m_row && m_column < rhs.m_column); }
- inline bool operator <= (coords const& rhs) const { return m_row < rhs.m_row || (m_row ==
rhs.m_row && m_column <= rhs.m_column); }
- inline bool operator > (coords const& rhs) const { return m_row > rhs.m_row || (m_row ==
rhs.m_row && m_column > rhs.m_column); }
- inline bool operator >= (coords const& rhs) const { return m_row > rhs.m_row || (m_row ==
rhs.m_row && m_column >= rhs.m_column); }
+ halfcolumn_t() = default;
+ halfcolumn_t(column_t c, half_t h) : base_type{c, h} { }
- IFDEF_DEBUG(char const* to_string() const);
+ inline void set_column(column_t c) { first = c; }
+ inline void set_half(half_t h) { second = h; }
- private:
- row_t m_row;
- column_t m_column;
+ inline column_t column() const { return first; }
+ inline half_t half() const { return second; }
+ };
+
+ struct halfcoords : public std::pair<row_t, halfcolumn_t> {
+ public:
+ using base_type = std::pair<row_t, halfcolumn_t>;
+
+ halfcoords() = default;
+ halfcoords(row_t r, halfcolumn_t hc) : base_type{r, hc} { }
+ halfcoords(row_t r, column_t c, half_t h) : base_type{r, halfcolumn_t(c, h)} { }
+
+ inline void set_row(row_t r) { first = r; }
+ inline void set_halfcolumn(halfcolumn_t hc) { second = hc; }
+
+ inline row_t row() const { return first; }
+ inline halfcolumn_t halfcolumn() const { return second; }
+
+ IFDEF_DEBUG(char const* to_string() const);
};
/* end is exclusive (or: start and end point to boundaries between cells) */
@@ -81,6 +103,8 @@ 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(); }
+ /* Get the last row that actually contains characters belonging to this span. */
+ 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 +113,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]