[vte/wip/egmont/bidi: 3/8] ringview: Add RingView infrastructure
- From: Egmont Koblinger <egmontkob src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [vte/wip/egmont/bidi: 3/8] ringview: Add RingView infrastructure
- Date: Mon, 3 Jun 2019 12:22:13 +0000 (UTC)
commit e8f5c29e9b2c8298984ea83143c3ed3809e014ba
Author: Egmont Koblinger <egmont gmail com>
Date: Sat Jun 1 15:12:32 2019 +0200
ringview: Add RingView infrastructure
The RingView extracts the contents of the currently visible area plus
context lines up to the next hard newline (or a safety limit).
This can be used to perform display related tweaks, such as BiDi.
src/debug.cc | 1 +
src/debug.h | 3 +-
src/meson.build | 2 +
src/ring.cc | 9 +++
src/ring.hh | 1 +
src/ringview.cc | 219 +++++++++++++++++++++++++++++++++++++++++++++++++++++
src/ringview.hh | 96 +++++++++++++++++++++++
src/vte.cc | 55 +++++++++++++-
src/vteinternal.hh | 5 ++
src/vterowdata.cc | 9 +++
src/vterowdata.hh | 1 +
src/widget.cc | 2 +
12 files changed, 399 insertions(+), 4 deletions(-)
---
diff --git a/src/debug.cc b/src/debug.cc
index afbd7413..e8dd8d08 100644
--- a/src/debug.cc
+++ b/src/debug.cc
@@ -57,6 +57,7 @@ _vte_debug_init(void)
{ "hyperlink", VTE_DEBUG_HYPERLINK },
{ "modes", VTE_DEBUG_MODES },
{ "emulation", VTE_DEBUG_EMULATION },
+ { "ringview", VTE_DEBUG_RINGVIEW },
{ "bidi", VTE_DEBUG_BIDI },
};
diff --git a/src/debug.h b/src/debug.h
index 92d9d0f9..8601dad0 100644
--- a/src/debug.h
+++ b/src/debug.h
@@ -64,7 +64,8 @@ typedef enum {
VTE_DEBUG_HYPERLINK = 1 << 24,
VTE_DEBUG_MODES = 1 << 25,
VTE_DEBUG_EMULATION = 1 << 26,
- VTE_DEBUG_BIDI = 1 << 27,
+ VTE_DEBUG_RINGVIEW = 1 << 27,
+ VTE_DEBUG_BIDI = 1 << 28,
} VteDebugFlags;
void _vte_debug_init(void);
diff --git a/src/meson.build b/src/meson.build
index 29046b77..6b97aaec 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -70,6 +70,8 @@ libvte_common_sources = debug_sources + modes_sources + parser_sources + utf8_so
'refptr.hh',
'ring.cc',
'ring.hh',
+ 'ringview.cc',
+ 'ringview.hh',
'utf8.cc',
'utf8.hh',
'vte.cc',
diff --git a/src/ring.cc b/src/ring.cc
index f61c78ce..384cd75e 100644
--- a/src/ring.cc
+++ b/src/ring.cc
@@ -609,6 +609,15 @@ Ring::index(row_t position)
return &m_cached_row;
}
+VteRowData const*
+Ring::index_safe(row_t position)
+{
+ if (G_UNLIKELY (position < m_start || position >= m_end))
+ return nullptr;
+
+ return index(position);
+}
+
bool
Ring::is_soft_wrapped(row_t position)
{
diff --git a/src/ring.hh b/src/ring.hh
index 87b73645..6f74396e 100644
--- a/src/ring.hh
+++ b/src/ring.hh
@@ -73,6 +73,7 @@ public:
//FIXMEchpe rename this to at()
//FIXMEchpe use references not pointers
VteRowData const* index(row_t position); /* const? */
+ VteRowData const* index_safe(row_t position);
VteRowData* index_writable(row_t position);
bool is_soft_wrapped(row_t position);
diff --git a/src/ringview.cc b/src/ringview.cc
new file mode 100644
index 00000000..dfb1e2f5
--- /dev/null
+++ b/src/ringview.cc
@@ -0,0 +1,219 @@
+/*
+ * Copyright © 2018–2019 Egmont Koblinger
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include "debug.h"
+#include "vtedefines.hh"
+#include "vteinternal.hh"
+
+using namespace vte::base;
+
+RingView::RingView()
+{
+ m_ring = nullptr;
+ m_start = m_len = m_width = 0;
+
+ m_invalid = true;
+ m_paused = true;
+}
+
+RingView::~RingView()
+{
+ pause();
+}
+
+/* Pausing a RingView frees up pretty much all of its memory.
+ *
+ * This is to be used when the terminal is unlikely to be painted or interacted with
+ * in the near future, e.g. the widget is unmapped. Not to be called too frequently,
+ * in order to avoid memory fragmentation.
+ *
+ * The RingView is resumed automatically on demand.
+ */
+void RingView::pause()
+{
+ int i;
+
+ if (m_paused)
+ return;
+
+ _vte_debug_print (VTE_DEBUG_RINGVIEW, "Ringview: pause, freeing %d rows.\n",
+ m_rows_alloc_len);
+
+ for (i = 0; i < m_rows_alloc_len; i++) {
+ _vte_row_data_fini(m_rows[i]);
+ g_free (m_rows[i]);
+ }
+ g_free (m_rows);
+ m_rows_alloc_len = 0;
+
+ m_invalid = true;
+ m_paused = true;
+}
+
+/* Allocate (again) the required memory. */
+void RingView::resume()
+{
+ g_assert_cmpint (m_len, >=, 1);
+
+ /* +16: A bit of arbitrary heuristics to likely prevent a quickly following
+ * realloc for the required context lines. */
+ m_rows_alloc_len = m_len + 16;
+ m_rows = (VteRowData **) g_malloc (sizeof (VteRowData *) * m_rows_alloc_len);
+ for (int i = 0; i < m_rows_alloc_len; i++) {
+ m_rows[i] = (VteRowData *) g_malloc (sizeof (VteRowData));
+ _vte_row_data_init (m_rows[i]);
+ }
+
+ _vte_debug_print (VTE_DEBUG_RINGVIEW, "Ringview: resume, allocating %d rows\n",
+ m_rows_alloc_len);
+
+ m_paused = false;
+}
+
+void RingView::set_ring(Ring *ring)
+{
+ if (G_LIKELY (ring == m_ring))
+ return;
+
+ m_ring = ring;
+ m_invalid = true;
+}
+
+void RingView::set_width(vte::grid::column_t width)
+{
+ if (G_LIKELY (width == m_width))
+ return;
+
+ m_width = width;
+ m_invalid = true;
+}
+
+void RingView::set_rows(vte::grid::row_t start, vte::grid::row_t len)
+{
+ if (start == m_start && len == m_len)
+ return;
+
+ /* With per-pixel scrolling, the desired viewport often shrinks by
+ * one row at one end, and remains the same at the other end.
+ * Save work by just keeping the current valid data in this case. */
+ if (!m_invalid && start >= m_start && start + len <= m_start + m_len)
+ return;
+
+ g_assert_cmpint (len, >=, 1);
+
+ /* m_rows is expanded on demand in update() */
+
+ m_start = start;
+ m_len = len;
+ m_invalid = true;
+}
+
+VteRowData *RingView::get_row(vte::grid::row_t row) {
+ g_assert_cmpint(row, >=, m_top);
+ g_assert_cmpint(row, <, m_top + m_rows_len);
+
+ return m_rows[row - m_top];
+}
+
+void RingView::update()
+{
+ if (!m_invalid)
+ return;
+ if (m_paused)
+ resume();
+
+ /* Find the beginning of the topmost paragraph.
+ *
+ * Extract at most VTE_RINGVIEW_PARAGRAPH_LENGTH_MAX context rows.
+ * If this safety limit is reached then together with the first
+ * non-context row this paragraph fragment is already longer
+ * than VTE_RINGVIEW_PARAGRAPH_LENGTH_MAX lines, and thus the
+ * BiDi code will skip it. */
+ vte::grid::row_t row = m_start;
+ const VteRowData *row_data;
+
+ _vte_debug_print (VTE_DEBUG_RINGVIEW, "Ringview: updating for [%ld..%ld] (%ld rows).\n",
+ m_start, m_start + m_len - 1, m_len);
+
+ int i = VTE_RINGVIEW_PARAGRAPH_LENGTH_MAX;
+ while (i--) {
+ if (!m_ring->is_soft_wrapped(row - 1))
+ break;
+ row--;
+ }
+
+ /* Extract the data beginning at the found row.
+ *
+ * Extract at most VTE_RINGVIEW_PARAGRAPH_LENGTH_MAX rows
+ * beyond the end of the specified area. Again, if this safety
+ * limit is reached then together with the last non-context row
+ * this paragraph fragment is already longer than
+ * VTE_RINGVIEW_PARAGRAPH_LENGTH_MAX lines, and thus the
+ * BiDi code will skip it. */
+ m_top = row;
+ m_rows_len = 0;
+ while (row < m_start + m_len + VTE_RINGVIEW_PARAGRAPH_LENGTH_MAX) {
+ if (m_rows_len == m_rows_alloc_len) {
+ /* Don't realloc too aggressively. */
+ m_rows_alloc_len = MAX (m_rows_alloc_len + 1, m_rows_alloc_len * 1.25 /* whatever
*/);
+ _vte_debug_print (VTE_DEBUG_RINGVIEW, "Ringview: reallocate to %d rows\n",
+ m_rows_alloc_len);
+ m_rows = (VteRowData **) g_realloc (m_rows, sizeof (VteRowData *) *
m_rows_alloc_len);
+ for (int j = m_rows_len; j < m_rows_alloc_len; j++) {
+ m_rows[j] = (VteRowData *) g_malloc (sizeof (VteRowData));
+ _vte_row_data_init (m_rows[j]);
+ }
+ }
+
+ row_data = m_ring->index_safe(row);
+ if (row_data != nullptr) {
+ _vte_row_data_copy (row_data, m_rows[m_rows_len]);
+ } else {
+ _vte_row_data_clear (m_rows[m_rows_len]);
+ }
+ m_rows_len++;
+ row++;
+
+ /* Once the bottom of the specified area is reached, stop at a hard newline. */
+ if (row >= m_start + m_len && (!row_data || !row_data->attr.soft_wrapped))
+ break;
+ }
+
+ _vte_debug_print (VTE_DEBUG_RINGVIEW, "Ringview: extracted %ld+%ld context lines: [%ld..%ld] (%d
rows).\n",
+ m_start - m_top, (m_top + m_rows_len) - (m_start + m_len),
+ m_top, m_top + m_rows_len - 1, m_rows_len);
+
+ /* Loop through paragraphs of the extracted text, and do whatever we need to do on each paragraph. */
+ auto top = m_top;
+ row = top;
+ while (row < m_top + m_rows_len) {
+ row_data = m_rows[row - m_top];
+ if (!row_data->attr.soft_wrapped || row == m_top + m_rows_len - 1) {
+ /* Found a paragraph from @top to @row, inclusive. */
+
+ /* Doing BiDi, syntax highlighting etc. come here in the future. */
+
+ top = row + 1;
+ }
+ row++;
+ }
+
+ m_invalid = false;
+}
diff --git a/src/ringview.hh b/src/ringview.hh
new file mode 100644
index 00000000..fe54ea2d
--- /dev/null
+++ b/src/ringview.hh
@@ -0,0 +1,96 @@
+/*
+ * Copyright © 2018–2019 Egmont Koblinger
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include <glib.h>
+
+#include "ring.hh"
+#include "vterowdata.hh"
+#include "vtetypes.hh"
+#include "vteunistr.h"
+
+namespace vte {
+
+namespace base { // FIXME ???
+
+/*
+ * RingView provides a "view" to a continuous segment of the Ring (or stream),
+ * typically the user visible area.
+ *
+ * It computes additional data that are needed to display the contents (or handle
+ * user events such as mouse click), but not needed for the terminal emulation logic.
+ * In order to save tons of resources, these data are computed when the Ring's
+ * contents are about to be displayed, rather than whenever they change.
+ *
+ * For computing these data, context lines (outside of the specified region of the
+ * Ring) are also taken into account up to the next hard newline or a safety limit.
+ *
+ * Currently RingView is used for BiDi: to figure out which logical character is
+ * mapped to which visual position.
+ *
+ * Future possible uses include "highlight all" for the search match, and
+ * syntax highlighting. URL autodetection might also be ported to this
+ * infrastructure one day.
+ */
+class RingView {
+public:
+ RingView();
+ ~RingView();
+
+ // prevent accidents
+ RingView(RingView& o) = delete;
+ RingView(RingView const& o) = delete;
+ RingView(RingView&& o) = delete;
+ RingView& operator= (RingView& o) = delete;
+ RingView& operator= (RingView const& o) = delete;
+ RingView& operator= (RingView&& o) = delete;
+
+ void set_ring(Ring *ring);
+ void set_rows(vte::grid::row_t start, vte::grid::row_t len);
+ void set_width(vte::grid::column_t width);
+ vte::grid::column_t get_width() { return m_width; }
+
+ inline void invalidate() { m_invalid = true; }
+ void update();
+ void pause();
+
+ VteRowData *get_row(vte::grid::row_t row);
+
+private:
+ Ring *m_ring;
+
+ VteRowData **m_rows;
+ int m_rows_len;
+ int m_rows_alloc_len;
+
+ vte::grid::row_t m_top; /* the row of the Ring corresponding to m_rows[0] */
+
+ vte::grid::row_t m_start;
+ vte::grid::row_t m_len;
+ vte::grid::column_t m_width;
+
+ bool m_invalid;
+ bool m_paused;
+
+ void resume();
+};
+
+}; /* namespace base */
+
+}; /* namespace vte */
diff --git a/src/vte.cc b/src/vte.cc
index 3645ac87..2f74b869 100644
--- a/src/vte.cc
+++ b/src/vte.cc
@@ -41,6 +41,7 @@
#include "vtedraw.hh"
#include "reaper.hh"
#include "ring.hh"
+#include "ringview.hh"
#include "caps.hh"
#include "widget.hh"
@@ -446,7 +447,7 @@ Terminal::invalidate_all()
/* Find the row in the given position in the backscroll buffer.
* Note that calling this method may invalidate the return value of
* a previous find_row_data() call. */
-// FIXMEchpe replace this with a method on VteRing
+// FIXMEchpe replace this with a method on VteRing (index_safe())
VteRowData const*
Terminal::find_row_data(vte::grid::row_t row) const
{
@@ -1730,6 +1731,8 @@ Terminal::selection_maybe_swap_endpoints(vte::view::coords const& pos)
if (m_selection_resolved.empty())
return;
+ ringview_update();
+
auto current = selection_grid_halfcoords_from_view_coords (pos);
if (m_selection_block_mode) {
@@ -3940,6 +3943,11 @@ Terminal::process_incoming()
queue_contents_changed();
}
+ /* BiDi properties might have changed, even when !modified.
+ * emit_pending_signals() requires the ringview to be updated. */
+ m_ringview.invalidate();
+ ringview_update();
+
emit_pending_signals();
if (invalidated_text) {
@@ -5581,6 +5589,8 @@ Terminal::modify_selection (vte::view::coords const& pos)
{
g_assert (m_selecting);
+ ringview_update();
+
auto current = selection_grid_halfcoords_from_view_coords (pos);
if (current == m_selection_last)
@@ -6975,6 +6985,8 @@ Terminal::widget_motion_notify(GdkEventMotion *event)
{
bool handled = false;
+ ringview_update();
+
GdkEvent* base_event = reinterpret_cast<GdkEvent*>(event);
auto pos = view_coords_from_event(base_event);
auto rowcol = grid_coords_from_view_coords(pos);
@@ -7040,6 +7052,8 @@ Terminal::widget_button_press(GdkEventButton *event)
bool handled = false;
gboolean start_selecting = FALSE, extend_selecting = FALSE;
+ ringview_update();
+
GdkEvent* base_event = reinterpret_cast<GdkEvent*>(event);
auto pos = view_coords_from_event(base_event);
auto rowcol = grid_coords_from_view_coords(pos);
@@ -7189,6 +7203,8 @@ Terminal::widget_button_release(GdkEventButton *event)
{
bool handled = false;
+ ringview_update();
+
GdkEvent* base_event = reinterpret_cast<GdkEvent*>(event);
auto pos = view_coords_from_event(base_event);
auto rowcol = grid_coords_from_view_coords(pos);
@@ -7306,6 +7322,8 @@ Terminal::widget_focus_out(GdkEventFocus *event)
void
Terminal::widget_enter(GdkEventCrossing *event)
{
+ ringview_update();
+
GdkEvent* base_event = reinterpret_cast<GdkEvent*>(event);
auto pos = view_coords_from_event(base_event);
@@ -7323,6 +7341,8 @@ Terminal::widget_enter(GdkEventCrossing *event)
void
Terminal::widget_leave(GdkEventCrossing *event)
{
+ ringview_update();
+
GdkEvent* base_event = reinterpret_cast<GdkEvent*>(event);
auto pos = view_coords_from_event(base_event);
@@ -8202,6 +8222,12 @@ Terminal::widget_size_allocate(GtkAllocation *allocation)
}
}
+void
+Terminal::widget_unmap()
+{
+ m_ringview.pause();
+}
+
void
Terminal::widget_unrealize()
{
@@ -8994,6 +9020,19 @@ Terminal::draw_cells_with_attributes(struct _vte_draw_text_request *items,
g_free(cells);
}
+void
+Terminal::ringview_update()
+{
+ auto first_row = first_displayed_row();
+ auto last_row = last_displayed_row();
+ if (cursor_is_onscreen())
+ last_row = MAX(last_row, m_screen->cursor.row);
+
+ m_ringview.set_ring (m_screen->row_data);
+ m_ringview.set_rows (first_row, last_row - first_row + 1);
+ m_ringview.set_width (m_column_count);
+ m_ringview.update ();
+}
/* Paint the contents of a given row at the given location. Take advantage
* of multiple-draw APIs by finding runs of characters with identical
@@ -9469,6 +9508,8 @@ Terminal::widget_draw(cairo_t *cr)
if (region == NULL)
return;
+ ringview_update();
+
allocated_width = get_allocated_width();
allocated_height = get_allocated_height();
@@ -9601,7 +9642,8 @@ Terminal::widget_scroll(GdkEventScroll *event)
int button;
GdkEvent *base_event = reinterpret_cast<GdkEvent*>(event);
- auto rowcol = confined_grid_coords_from_event(base_event);
+
+// ringview_update(); // needed?
read_modifiers(base_event);
@@ -9636,6 +9678,8 @@ Terminal::widget_scroll(GdkEventScroll *event)
"Scroll application by %d lines, smooth scroll delta set back to %f\n",
cnt, m_mouse_smooth_scroll_delta);
+ auto rowcol = confined_grid_coords_from_event(base_event);
+
button = cnt > 0 ? 5 : 4;
if (cnt < 0)
cnt = -cnt;
@@ -10480,8 +10524,13 @@ Terminal::process(bool emit_adj_changed)
process_incoming();
}
m_input_bytes = 0;
- } else
+ } else {
+ // FIXMEegmont why is it needed here, and is this the best place?
+ // Without this, sudden two-finger kinetic scrolls result in crash.
+ ringview_update();
+
emit_pending_signals();
+ }
return is_active;
}
diff --git a/src/vteinternal.hh b/src/vteinternal.hh
index 27d503dd..a8e267e4 100644
--- a/src/vteinternal.hh
+++ b/src/vteinternal.hh
@@ -25,6 +25,7 @@
#include "vtedraw.hh"
#include "reaper.hh"
#include "ring.hh"
+#include "ringview.hh"
#include "buffer.h"
#include "parser.hh"
#include "parser-glue.hh"
@@ -618,6 +619,7 @@ public:
/* BiDi parameters outside of ECMA and DEC private modes */
guint m_bidi_rtl : 1;
+ vte::base::RingView m_ringview;
public:
@@ -752,6 +754,7 @@ public:
void widget_constructed();
void widget_realize();
void widget_unrealize();
+ void widget_unmap();
void widget_style_updated();
void widget_focus_in(GdkEventFocus *event);
void widget_focus_out(GdkEventFocus *event);
@@ -1330,6 +1333,8 @@ public:
vte::parser::StringTokeniser::const_iterator& token,
vte::parser::StringTokeniser::const_iterator const& endtoken) noexcept;
+ void ringview_update();
+
/* Sequence handlers */
bool m_line_wrapped; // signals line wrapped from character insertion
// Note: inlining the handlers seems to worsen the performance, so we don't do that
diff --git a/src/vterowdata.cc b/src/vterowdata.cc
index 09a75976..217a8b2f 100644
--- a/src/vterowdata.cc
+++ b/src/vterowdata.cc
@@ -175,6 +175,15 @@ void _vte_row_data_shrink (VteRowData *row, gulong max_len)
row->len = max_len;
}
+// FIXME swap the two params a la memcpy?
+void _vte_row_data_copy (const VteRowData *src, VteRowData *dst)
+{
+ _vte_row_data_ensure (dst, src->len);
+ dst->len = src->len;
+ dst->attr = src->attr;
+ memcpy(dst->cells, src->cells, src->len * sizeof (src->cells[0]));
+}
+
/* Get the length, ignoring trailing empty cells (with a custom background color). */
guint16 _vte_row_data_nonempty_length (const VteRowData *row)
{
diff --git a/src/vterowdata.hh b/src/vterowdata.hh
index 12cbf311..10b76b6a 100644
--- a/src/vterowdata.hh
+++ b/src/vterowdata.hh
@@ -80,6 +80,7 @@ void _vte_row_data_append (VteRowData *row, const VteCell *cell);
void _vte_row_data_remove (VteRowData *row, gulong col);
void _vte_row_data_fill (VteRowData *row, const VteCell *cell, gulong len);
void _vte_row_data_shrink (VteRowData *row, gulong max_len);
+void _vte_row_data_copy (const VteRowData *src, VteRowData *dst);
guint16 _vte_row_data_nonempty_length (const VteRowData *row);
G_END_DECLS
diff --git a/src/widget.cc b/src/widget.cc
index 331ddba9..5af5d47c 100644
--- a/src/widget.cc
+++ b/src/widget.cc
@@ -334,6 +334,8 @@ Widget::size_allocate(GtkAllocation* allocation) noexcept
void
Widget::unmap() noexcept
{
+ m_terminal->widget_unmap();
+
if (m_event_window)
gdk_window_hide(m_event_window);
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]