[vte/wip/egmont/bidi: 5/73] vte-34-selection-big-rewrite-v1.patch
- From: Egmont Koblinger <egmontkob src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [vte/wip/egmont/bidi: 5/73] vte-34-selection-big-rewrite-v1.patch
- Date: Sun, 23 Sep 2018 11:00:22 +0000 (UTC)
commit 9ef9941ff46798f7925c6f181ea13a8431ce0d65
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 4603238c..e200f1e4 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]