[vte] emulation: Refactor tabstop handling



commit ef0a81b2cf08c5514772ee818316c094dda7d64e
Author: Christian Persch <chpe src gnome org>
Date:   Tue Mar 27 19:40:12 2018 +0200

    emulation: Refactor tabstop handling
    
    Use a bitmask to store the tabstops instead of a hash table,
    and add tests.

 src/Makefile.am      |   18 +++-
 src/parser-cmd.hh    |    5 +
 src/parser.cc        |   10 ++
 src/tabstops-test.cc |  234 ++++++++++++++++++++++++++++++++++++++
 src/tabstops.hh      |  222 ++++++++++++++++++++++++++++++++++++
 src/vte.cc           |   84 +++-----------
 src/vteinternal.hh   |   16 +--
 src/vteseq.cc        |  306 +++++++++++++++++++++++++++-----------------------
 8 files changed, 675 insertions(+), 220 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index f5f1434..2b2f6c5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -180,7 +180,7 @@ vteresources.cc: vte.gresource.xml Makefile $(shell $(GLIB_COMPILE_RESOURCES) --
 
 # Misc unit tests and utilities
 
-noinst_PROGRAMS += parser-cat slowcat test-modes test-parser
+noinst_PROGRAMS += parser-cat slowcat test-modes test-tabstops test-parser
 noinst_SCRIPTS = decset osc window
 EXTRA_DIST += $(noinst_SCRIPTS)
 
@@ -203,6 +203,7 @@ dist_check_SCRIPTS = \
 TESTS = \
        test-modes \
        test-parser \
+       test-tabstops \
        reaper \
        test-vtetypes \
        vteconv \
@@ -318,6 +319,21 @@ test_modes_LDADD = \
        $(GLIB_LIBS) \
        $(NULL)
 
+test_tabstops_SOURCES = \
+       tabstops-test.cc \
+       tabstops.hh \
+       $(NULL)
+test_tabstops_CPPFLAGS = \
+       -I$(builddir) \
+       -I$(srcdir) \
+       $(AM_CPPFLAGS)
+test_tabstops_CXXFLAGS = \
+       $(GLIB_CFLAGS) \
+       $(AM_CXXFLAGS)
+test_tabstops_LDADD = \
+       $(GLIB_LIBS) \
+       $(NULL)
+
 test_vtetypes_SOURCES = \
        vtetypes.cc \
        vtetypes.hh \
diff --git a/src/parser-cmd.hh b/src/parser-cmd.hh
index 60c301b..e57e2c7 100644
--- a/src/parser-cmd.hh
+++ b/src/parser-cmd.hh
@@ -191,7 +191,12 @@ _VTE_CMD(SS3) /* single-shift-3 */
 _VTE_CMD(ST) /* string-terminator */
 _VTE_CMD(SU) /* scroll-up */
 _VTE_CMD(SUB) /* substitute */
+_VTE_CMD(TAC) /* tabulation-aligned-centre */
+_VTE_CMD(TALE) /* tabulation-aligned-leading-edge */
+_VTE_CMD(TATE) /* tabulation-aligned-trailing-edge */
 _VTE_CMD(TBC) /* tab-clear */
+_VTE_CMD(TCC) /* tabulation-centred-on-character */
+_VTE_CMD(TSR) /* tabulation-stop-remove */
 _VTE_CMD(VPA) /* vertical-line-position-absolute */
 _VTE_CMD(VPR) /* vertical-line-position-relative */
 _VTE_CMD(VT) /* vertical-tab */
diff --git a/src/parser.cc b/src/parser.cc
index c9fd0c4..877947a 100644
--- a/src/parser.cc
+++ b/src/parser.cc
@@ -425,6 +425,8 @@ static unsigned int vte_parse_host_csi(const struct vte_seq *seq)
         case 'a':
                 if (flags == 0) /* HPR */
                         return VTE_CMD_HPR;
+                else if (flags == VTE_SEQ_FLAG_SPACE) /* TALE */
+                        return VTE_CMD_TALE;
                 break;
         case 'B':
                 if (flags == 0) /* CUD */
@@ -433,6 +435,8 @@ static unsigned int vte_parse_host_csi(const struct vte_seq *seq)
         case 'b':
                 if (flags == 0) /* REP */
                         return VTE_CMD_REP;
+                else if (flags == VTE_SEQ_FLAG_SPACE) /* TAC */
+                        return VTE_CMD_TAC;
                 break;
         case 'C':
                 if (flags == 0) /* CUF */
@@ -441,6 +445,8 @@ static unsigned int vte_parse_host_csi(const struct vte_seq *seq)
         case 'c':
                 if (flags == 0) /* DA1 */
                         return VTE_CMD_DA1;
+                else if (flags == VTE_SEQ_FLAG_SPACE) /* TCC */
+                        return VTE_CMD_TCC;
                 else if (flags == VTE_SEQ_FLAG_GT) /* DA2 */
                         return VTE_CMD_DA2;
                 else if (flags == VTE_SEQ_FLAG_EQUAL) /* DA3 */
@@ -453,6 +459,8 @@ static unsigned int vte_parse_host_csi(const struct vte_seq *seq)
         case 'd':
                 if (flags == 0) /* VPA */
                         return VTE_CMD_VPA;
+                else if (flags == VTE_SEQ_FLAG_SPACE) /* TSR */
+                        return VTE_CMD_TSR;
                 break;
         case 'E':
                 if (flags == 0) /* CNL */
@@ -796,6 +804,8 @@ static unsigned int vte_parse_host_csi(const struct vte_seq *seq)
         case '`':
                 if (flags == 0) /* HPA */
                         return VTE_CMD_HPA;
+                else if (flags == VTE_SEQ_FLAG_SPACE) /* TATE */
+                        return VTE_CMD_TATE;
                 break;
         case '{':
                 if (flags == VTE_SEQ_FLAG_CASH) /* DECSERA */
diff --git a/src/tabstops-test.cc b/src/tabstops-test.cc
new file mode 100644
index 0000000..a574814
--- /dev/null
+++ b/src/tabstops-test.cc
@@ -0,0 +1,234 @@
+/*
+ * Copyright © 2018 Christian Persch
+ *
+ * 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 3 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 General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <algorithm>
+
+#include <glib.h>
+
+#include "tabstops.hh"
+#include "vtedefines.hh"
+
+using namespace vte::terminal;
+
+static void
+tabstops_set(Tabstops& t,
+             std::initializer_list<Tabstops::position_t> l)
+{
+        for (auto i : l)
+                t.set(i);
+}
+
+static void
+assert_tabstops_default(Tabstops const& t,
+                        Tabstops::position_t start = 0,
+                        Tabstops::position_t end = Tabstops::position_t(-1),
+                        Tabstops::position_t tab_width = VTE_TAB_WIDTH)
+{
+        if (end == t.npos)
+                end = t.size();
+
+        for (auto i = start; i < end; i++) {
+                if (i % tab_width)
+                        g_assert_false(t.get(i));
+                else
+                        g_assert_true(t.get(i));
+        }
+}
+
+static void
+assert_tabstops_clear(Tabstops const& t,
+                      Tabstops::position_t start = 0,
+                      Tabstops::position_t end = Tabstops::position_t(-1))
+{
+        if (end == t.npos)
+                end = t.size();
+
+        for (auto i = start; i < end; i++)
+                g_assert_false(t.get(i));
+}
+
+static void
+assert_tabstops(Tabstops const& t,
+                std::initializer_list<Tabstops::position_t> l,
+                Tabstops::position_t start = 0,
+                Tabstops::position_t end = Tabstops::position_t(-1))
+{
+        if (end == t.npos)
+                end = t.size();
+
+        auto it = l.begin();
+        for (auto i = start; i < end; i++) {
+                if (it != l.end() && i == *it) {
+                        g_assert_true(t.get(i));
+                        ++it;
+                } else
+                        g_assert_false(t.get(i));
+
+        }
+        g_assert_true(it == l.end());
+}
+
+static void
+assert_tabstops_previous(Tabstops const& t,
+                         std::initializer_list<std::pair<Tabstops::position_t, Tabstops::position_t>> l,
+                         int count = 1)
+{
+        for (auto p : l) {
+                g_assert_cmpuint(t.get_previous(p.first, count), ==, p.second);
+        }
+}
+
+static void
+assert_tabstops_next(Tabstops const& t,
+                     std::initializer_list<std::pair<Tabstops::position_t, Tabstops::position_t>> l,
+                     int count = 1)
+{
+        for (auto p : l) {
+                g_assert_cmpuint(t.get_next(p.first, count), ==, p.second);
+        }
+}
+
+static void
+test_tabstops_default(void)
+{
+        Tabstops t{};
+        g_assert_cmpuint(t.size(), ==, VTE_COLUMNS);
+
+        assert_tabstops_default(t);
+}
+
+static void
+test_tabstops_get_set(void)
+{
+        Tabstops t{256, false};
+
+        tabstops_set(t, {42, 200});
+        assert_tabstops(t, {42, 200});
+}
+
+static void
+test_tabstops_clear(void)
+{
+        Tabstops t{128, true};
+        t.clear();
+        assert_tabstops_clear(t);
+}
+
+static void
+test_tabstops_reset(void)
+{
+        unsigned int const tab_width = 7;
+
+        Tabstops t{80, true, tab_width};
+        assert_tabstops_default(t, 0, t.npos, tab_width);
+
+        t.resize(80);
+        t.resize(160, false, tab_width);
+        assert_tabstops_default(t, 0, 80, tab_width);
+        assert_tabstops_clear(t, 80, t.npos);
+
+        t.resize(80);
+        t.clear();
+        t.resize(160, true, tab_width);
+        assert_tabstops_clear(t, 0, 80);
+        assert_tabstops_default(t, 80, t.npos, tab_width);
+
+        t.resize(256);
+        t.reset(tab_width);
+        assert_tabstops_default(t, 0, t.npos, tab_width);
+        t.resize(1024, true, tab_width);
+        assert_tabstops_default(t, 0, t.npos, tab_width);
+        t.resize(4096, true, tab_width);
+        assert_tabstops_default(t, 0, t.npos, tab_width);
+}
+
+static void
+test_tabstops_resize(void)
+{
+        Tabstops t;
+        t.resize(80);
+
+        t.reset();
+        assert_tabstops_default(t);
+        t.resize(161, false);
+        assert_tabstops_default(t, 0, 80);
+        assert_tabstops_clear(t, 80, t.npos);
+}
+
+static void
+test_tabstops_previous(void)
+{
+        Tabstops t{512, false};
+        tabstops_set(t, {0, 31, 32, 63, 64, 255, 256});
+        assert_tabstops_previous(t, {{511, 256}, {256, 255}, {255, 64}, {64, 63}, {63, 32}, {32, 31}, {31, 
0}});
+
+        assert_tabstops_previous(t, {{511, 255}, {257, 255}, {254, 63}, {64, 32}, {33, 31}, {32, 0}, {31, 
t.npos}, {0, t.npos}}, 2);
+
+        t.clear();
+        tabstops_set(t, {127, 256});
+        assert_tabstops_previous(t, {{511, 256}, {256, 127}, {127, t.npos}});
+        assert_tabstops_previous(t, {{384, 256}, {192, 127}, {92, t.npos}});
+
+        unsigned int const tab_width = 3;
+        t.reset(tab_width);
+
+        for (unsigned int p = 1 ; p < t.size(); ++p) {
+                g_assert_cmpuint(t.get_previous(p), ==, (p - 1) / tab_width * tab_width);
+        }
+        g_assert_cmpuint(t.get_previous(0), ==, t.npos);
+}
+
+static void
+test_tabstops_next(void)
+{
+        Tabstops t{512, false};
+        tabstops_set(t, {0, 31, 32, 63, 64, 255, 256});
+        assert_tabstops_next(t, {{0, 31}, {31, 32}, {32, 63}, {63, 64}, {64, 255}, {255, 256}, {256, 
t.npos}});
+        assert_tabstops_next(t, {{0, 32}, {2, 32}, {31, 63}, {48, 64}, {128, 256}, {255, t.npos}}, 2);
+
+        t.clear();
+        tabstops_set(t, {127, 256});
+        assert_tabstops_next(t, {{0, 127}, {127, 256}, {256, t.npos}});
+        assert_tabstops_next(t, {{1, 127}, {192, 256}, {384, t.npos}});
+
+        unsigned int const tab_width = 3;
+        t.reset(tab_width);
+
+        for (unsigned int p = 0; p < t.size() - tab_width; ++p) {
+                g_assert_cmpuint(t.get_next(p), ==, (p / tab_width + 1) * tab_width);
+        }
+        g_assert_cmpuint(t.get_next(t.size() - 1), ==, t.npos);
+}
+
+int
+main(int argc,
+     char* argv[])
+{
+        g_test_init(&argc, &argv, nullptr);
+
+        g_test_add_func("/vte/tabstops/default", test_tabstops_default);
+        g_test_add_func("/vte/tabstops/get-set", test_tabstops_get_set);
+        g_test_add_func("/vte/tabstops/clear", test_tabstops_clear);
+        g_test_add_func("/vte/tabstops/reset", test_tabstops_reset);
+        g_test_add_func("/vte/tabstops/resize", test_tabstops_resize);
+        g_test_add_func("/vte/tabstops/previous", test_tabstops_previous);
+        g_test_add_func("/vte/tabstops/next", test_tabstops_next);
+
+        return g_test_run();
+}
diff --git a/src/tabstops.hh b/src/tabstops.hh
new file mode 100644
index 0000000..0722c36
--- /dev/null
+++ b/src/tabstops.hh
@@ -0,0 +1,222 @@
+/*
+ * Copyright © 2018 Christian Persch
+ *
+ * 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 3 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 General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <assert.h>
+#include <string.h>
+
+#include <cstdint>
+#include <vector>
+
+#include "vtedefines.hh"
+
+namespace vte {
+
+namespace terminal {
+
+class Tabstops {
+public:
+        using position_t = unsigned int;
+        using signed_position_t = int;
+
+        static position_t const npos = position_t(signed_position_t(-1));
+
+private:
+        typedef unsigned long storage_t;
+
+        /* Number of bits used in storage */
+        position_t m_size{0};
+        /* Number of blocks in m_storage */
+        position_t m_capacity{0};
+        /* Bit storage */
+        storage_t* m_storage{nullptr};
+
+        inline position_t bits() const noexcept
+        {
+                return 8 * sizeof(storage_t);
+        }
+
+        inline position_t block(position_t position) const noexcept
+        {
+                return position / bits();
+        }
+
+        inline position_t block_first(position_t position) const noexcept
+        {
+                return position & ~(bits() - 1);
+        }
+
+        /* Mask with exactly the position's bit set */
+        inline storage_t mask(position_t position) const noexcept
+        {
+                return storage_t(1) << (position & (bits() - 1));
+        }
+
+        /* Mask with all bits set from position (excl.) up to the MSB */
+        inline storage_t mask_lower(position_t position) const noexcept
+        {
+                return ~(mask(position) | (mask(position) - 1));
+        }
+
+        /* Mask with all bits set from 0 to position (excl.) */
+        inline storage_t mask_upper(position_t position) const noexcept
+        {
+                return mask(position) - 1;
+        }
+
+        inline position_t next_position(position_t position) const noexcept
+        {
+                auto b = block(position);
+                auto v = m_storage[b] & mask_lower(position);
+                if (v != 0)
+                        return b * bits() + __builtin_ctzl(v);
+
+                while (++b < m_capacity) {
+                        v = m_storage[b];
+                        if (v != 0)
+                                return b * bits() + __builtin_ctzl(v);
+                }
+
+                return npos;
+        }
+
+        inline position_t previous_position(position_t position) const noexcept
+        {
+                auto b = block(position);
+                auto v = m_storage[b] & mask_upper(position);
+                if (v != 0)
+                        return (b + 1) * bits() - __builtin_clzl(v) - 1;
+
+                while (b > 0) {
+                        v = m_storage[--b];
+                        if (v != 0)
+                                return (b + 1) * bits() - __builtin_clzl(v) - 1;
+                }
+
+                return npos;
+        }
+
+public:
+        Tabstops(position_t size = VTE_COLUMNS,
+                 bool set_default = true,
+                 position_t tab_width = VTE_TAB_WIDTH) noexcept
+        {
+                resize(size, set_default, tab_width);
+        }
+
+        Tabstops(Tabstops const&) = delete;
+        Tabstops(Tabstops&&) = delete;
+
+        ~Tabstops()
+        {
+                free(m_storage);
+        };
+
+        Tabstops& operator=(Tabstops const&) = delete;
+        Tabstops& operator=(Tabstops&&) = delete;
+
+        inline position_t size() const noexcept
+        {
+                return m_size;
+        }
+
+        void resize(position_t size,
+                    bool set_default = true,
+                    position_t tab_width = VTE_TAB_WIDTH) noexcept
+        {
+                /* We want an even number of blocks */
+                auto const new_capacity = ((size + 8 * sizeof(storage_t) - 1) / (8 * sizeof(storage_t)) + 1) 
& ~1;
+                g_assert_cmpuint(new_capacity % 2, ==, 0);
+                g_assert_cmpuint(new_capacity * 8 * sizeof(storage_t), >=, size);
+
+                if (new_capacity > m_capacity) {
+                        auto const new_capacity_bytes = new_capacity * sizeof(storage_t);
+                        m_storage = reinterpret_cast<storage_t*>(realloc(m_storage, new_capacity_bytes));
+                }
+
+                if (size > m_size) {
+                        /* Clear storage */
+                        auto b = block(m_size);
+                        m_storage[b] &= mask_upper(m_size);
+                        while (++b < new_capacity)
+                                m_storage[b] = 0;
+                }
+
+                auto const old_size = m_size;
+                m_size = size;
+                m_capacity = new_capacity;
+
+                if (set_default) {
+                        auto const r = old_size % tab_width;
+                        position_t start =  r ? old_size + tab_width - r : old_size;
+                        for (position_t i = start; i < m_size; i += tab_width)
+                                set(i);
+                }
+        }
+
+        inline void clear() noexcept
+        {
+                memset(m_storage, 0, m_capacity * sizeof(m_storage[0]));
+        }
+
+        inline void reset(position_t tab_width = VTE_TAB_WIDTH) noexcept
+        {
+                clear();
+                for (position_t i = 0; i < m_size; i += tab_width)
+                        set(i);
+        }
+
+        inline void set(position_t position) noexcept
+        {
+                assert(position < m_size);
+                m_storage[block(position)] |= mask(position);
+        }
+
+        inline void unset(position_t position) noexcept
+        {
+                assert(position < m_size);
+                m_storage[block(position)] &= ~mask(position);
+        }
+
+        inline bool get(position_t position) const noexcept
+        {
+                return (m_storage[block(position)] & mask(position)) != 0;
+        }
+
+        inline position_t get_next(position_t position,
+                                   int count = 1,
+                                   position_t endpos = npos) const noexcept
+        {
+                while (count-- && position < m_size)
+                        position = next_position(position);
+                return position < m_size ? position : endpos;
+        }
+
+        inline position_t get_previous(position_t position,
+                                       int count = 1,
+                                       position_t endpos = npos) const noexcept
+        {
+                while (count-- && position != npos)
+                        position = previous_position(position);
+                return position != npos ? position : endpos;
+        }
+};
+
+} // namespace terminal
+
+} // namespace vte
diff --git a/src/vte.cc b/src/vte.cc
index 3e7ab4a..ea05d61 100644
--- a/src/vte.cc
+++ b/src/vte.cc
@@ -951,57 +951,6 @@ VteTerminalPrivate::deselect_all()
        }
 }
 
-// FIXMEchpe make m_tabstops a hashset
-
-/* Remove a tabstop. */
-void
-VteTerminalPrivate::clear_tabstop(int column)
-{
-       if (m_tabstops) {
-               /* Remove a tab stop from the hash table. */
-               g_hash_table_remove(m_tabstops,
-                                   GINT_TO_POINTER(2 * column + 1));
-       }
-}
-
-/* Check if we have a tabstop at a given position. */
-bool
-VteTerminalPrivate::get_tabstop(int column)
-{
-       if (m_tabstops != NULL) {
-               auto hash = g_hash_table_lookup(m_tabstops,
-                                          GINT_TO_POINTER(2 * column + 1));
-               return hash != nullptr;
-       }
-
-        return false;
-}
-
-/* Reset the set of tab stops to the default. */
-void
-VteTerminalPrivate::set_tabstop(int column)
-{
-       if (m_tabstops != NULL) {
-               /* Just set a non-NULL pointer for this column number. */
-               g_hash_table_insert(m_tabstops,
-                                   GINT_TO_POINTER(2 * column + 1),
-                                   m_terminal);
-       }
-}
-
-/* Reset the set of tab stops to the default. */
-void
-VteTerminalPrivate::set_default_tabstops()
-{
-       if (m_tabstops) {
-               g_hash_table_destroy(m_tabstops);
-       }
-       m_tabstops = g_hash_table_new(nullptr, nullptr);
-        for (int i = 0; i <= VTE_TAB_MAX; i += VTE_TAB_WIDTH) {
-               set_tabstop(i);
-       }
-}
-
 /* Clear the cache of the screen contents we keep. */
 void
 VteTerminalPrivate::match_contents_clear()
@@ -7715,14 +7664,19 @@ VteTerminalPrivate::refresh_size()
                 return;
 
        int rows, columns;
-        if (vte_pty_get_size(m_pty, &rows, &columns, NULL)) {
-                m_row_count = rows;
-                m_column_count = columns;
-        } else {
+        if (!vte_pty_get_size(m_pty, &rows, &columns, nullptr)) {
                 /* Error reading PTY size, use defaults */
-                m_row_count = VTE_ROWS;
-                m_column_count = VTE_COLUMNS;
+                rows = VTE_ROWS;
+                columns = VTE_COLUMNS;
        }
+
+        if (m_row_count == rows &&
+            m_column_count == columns)
+                return;
+
+        m_row_count = rows;
+        m_column_count = columns;
+        m_tabstops.resize(columns);
 }
 
 /* Resize the given screen (normal or alternate) of the terminal. */
@@ -7889,6 +7843,7 @@ VteTerminalPrivate::set_size(long columns,
        } else {
                m_row_count = rows;
                m_column_count = columns;
+                m_tabstops.resize(columns);
        }
        if (old_rows != m_row_count || old_columns != m_column_count) {
                 m_scrolling_restricted = FALSE;
@@ -8121,7 +8076,6 @@ VteTerminalPrivate::VteTerminalPrivate(VteTerminal *t) :
        m_allow_bold = TRUE;
         m_bold_is_bright = TRUE;
         m_rewrap_on_resize = TRUE;
-       set_default_tabstops();
 
         m_input_enabled = TRUE;
 
@@ -8526,11 +8480,6 @@ VteTerminalPrivate::~VteTerminalPrivate()
        /* Cancel pending adjustment change notifications. */
        m_adjustment_changed_pending = FALSE;
 
-       /* Tabstop information. */
-       if (m_tabstops) {
-               g_hash_table_destroy(m_tabstops);
-       }
-
        /* Free any selected text, but if we currently own the selection,
         * throw the text onto the clipboard without an owner so that it
         * doesn't just disappear. */
@@ -10541,6 +10490,11 @@ VteTerminalPrivate::reset(bool clear_tabstops,
         m_modes_private.clear_saved();
         m_modes_private.reset();
 
+        /* Reset tabstops */
+        if (clear_tabstops) {
+                m_tabstops.reset();
+        }
+
         /* Window title stack */
         if (clear_history) {
                 m_window_title_stack.clear();
@@ -10578,10 +10532,6 @@ VteTerminalPrivate::reset(bool clear_tabstops,
        }
         /* DECSCUSR cursor style */
         set_cursor_style(VTE_CURSOR_STYLE_TERMINAL_DEFAULT);
-       /* Do more stuff we refer to as a "full" reset. */
-       if (clear_tabstops) {
-               set_default_tabstops();
-       }
        /* Reset restricted scrolling regions, leave insert mode, make
         * the cursor visible again. */
         m_scrolling_restricted = FALSE;
diff --git a/src/vteinternal.hh b/src/vteinternal.hh
index 361187c..781cc19 100644
--- a/src/vteinternal.hh
+++ b/src/vteinternal.hh
@@ -29,6 +29,7 @@
 #include "parser.hh"
 #include "parser-glue.hh"
 #include "modes.hh"
+#include "tabstops.hh"
 
 #include "vtepcre2.h"
 #include "vteregexinternal.hh"
@@ -289,8 +290,10 @@ public:
         GdkWindow *m_event_window;
 
         /* Metric and sizing data: dimensions of the window */
-        vte::grid::row_t m_row_count;
-        vte::grid::column_t m_column_count;
+        vte::grid::row_t m_row_count{VTE_ROWS};
+        vte::grid::column_t m_column_count{VTE_COLUMNS};
+
+        vte::terminal::Tabstops m_tabstops{};
 
        /* Emulation setup data. */
         struct vte_parser* m_parser; /* control sequence state machine */
@@ -383,7 +386,6 @@ public:
         gboolean m_audible_bell;
         gboolean m_allow_bold;
         gboolean m_bold_is_bright;
-        GHashTable *m_tabstops;
         gboolean m_text_modified_flag;
         gboolean m_text_inserted_flag;
         gboolean m_text_deleted_flag;
@@ -991,11 +993,6 @@ public:
         void emit_paste_clipboard();
         void emit_hyperlink_hover_uri_changed(const GdkRectangle *bbox);
 
-        void clear_tabstop(int column); // FIXMEchpe vte::grid::column_t ?
-        bool get_tabstop(int column);
-        void set_tabstop(int column);
-        void set_default_tabstops();
-
         void hyperlink_invalidate_and_get_bbox(hyperlink_idx_t idx, GdkRectangle *bbox);
         void hyperlink_hilite_update();
 
@@ -1210,7 +1207,8 @@ public:
 
         inline void move_cursor_backward(vte::grid::column_t columns);
         inline void move_cursor_forward(vte::grid::column_t columns);
-        inline void move_cursor_tab();
+        inline void move_cursor_tab_backward(int count = 1);
+        inline void move_cursor_tab_forward(int count = 1);
         inline void line_feed();
         inline void erase_in_display(vte::parser::Sequence const& seq);
         inline void erase_in_line(vte::parser::Sequence const& seq);
diff --git a/src/vteseq.cc b/src/vteseq.cc
index e834f8a..371bbb1 100644
--- a/src/vteseq.cc
+++ b/src/vteseq.cc
@@ -780,6 +780,8 @@ VteTerminalPrivate::clear_to_eol()
 void
 VteTerminalPrivate::set_cursor_column(vte::grid::column_t col)
 {
+       _vte_debug_print(VTE_DEBUG_PARSER,
+                         "Moving cursor to column %ld.\n", col);
         m_screen->cursor.col = CLAMP(col, 0, m_column_count - 1);
 }
 
@@ -1009,75 +1011,73 @@ VteTerminalPrivate::line_feed()
 }
 
 void
-VteTerminalPrivate::move_cursor_tab()
+VteTerminalPrivate::move_cursor_tab_backward(int count)
 {
-        long old_len;
-        vte::grid::column_t newcol, col;
+        if (count == 0)
+                return;
+
+        auto const newcol = m_tabstops.get_previous(m_screen->cursor.col, count, 0);
+        set_cursor_column(newcol);
+}
 
-       /* Calculate which column is the next tab stop. */
-        newcol = col = m_screen->cursor.col;
+void
+VteTerminalPrivate::move_cursor_tab_forward(int count)
+{
+        if (count == 0)
+                return;
 
+        auto const col = m_screen->cursor.col;
        g_assert (col >= 0);
 
-       if (m_tabstops != NULL) {
-               /* Find the next tabstop. */
-               for (newcol++; newcol < VTE_TAB_MAX; newcol++) {
-                       if (get_tabstop(newcol)) {
-                               break;
-                       }
-               }
-       }
+       /* Find the next tabstop, but don't go beyond the end of the line */
+        auto const newcol = m_tabstops.get_next(col, count, m_column_count - 1);
 
-       /* If we have no tab stops or went past the end of the line, stop
-        * at the right-most column. */
-       if (newcol >= m_column_count) {
-               newcol = m_column_count - 1;
-       }
+       /* Make sure we don't move cursor back (see bug #340631) */
+        // FIXMEchpe how could this happen!?
+       if (col >= newcol)
+                return;
 
-       /* but make sure we don't move cursor back (bug #340631) */
-       if (col < newcol) {
-               VteRowData *rowdata = ensure_row();
-
-               /* Smart tab handling: bug 353610
-                *
-                * If we currently don't have any cells in the space this
-                * tab creates, we try to make the tab character copyable,
-                * by appending a single tab char with lots of fragment
-                * cells following it.
-                *
-                * Otherwise, just append empty cells that will show up
-                * as a space each.
-                */
-
-               old_len = _vte_row_data_length (rowdata);
-                _vte_row_data_fill (rowdata, &basic_cell, newcol);
-
-               /* Insert smart tab if there's nothing in the line after
-                * us, not even empty cells (with non-default background
-                * color for example).
-                *
-                * Notable bugs here: 545924, 597242, 764330 */
-               if (col >= old_len && newcol - col <= VTE_TAB_WIDTH_MAX) {
-                       glong i;
-                       VteCell *cell = _vte_row_data_get_writable (rowdata, col);
-                       VteCell tab = *cell;
-                       tab.attr.set_columns(newcol - col);
-                       tab.c = '\t';
-                       /* Save tab char */
-                       *cell = tab;
-                       /* And adjust the fragments */
-                       for (i = col + 1; i < newcol; i++) {
-                               cell = _vte_row_data_get_writable (rowdata, i);
-                               cell->c = '\t';
-                               cell->attr.set_columns(1);
-                               cell->attr.set_fragment(true);
-                       }
-               }
+        /* Smart tab handling: bug 353610
+         *
+         * If we currently don't have any cells in the space this
+         * tab creates, we try to make the tab character copyable,
+         * by appending a single tab char with lots of fragment
+         * cells following it.
+         *
+         * Otherwise, just append empty cells that will show up
+         * as a space each.
+         */
+
+        VteRowData *rowdata = ensure_row();
+        auto const old_len = _vte_row_data_length (rowdata);
+        _vte_row_data_fill (rowdata, &basic_cell, newcol);
+
+        /* Insert smart tab if there's nothing in the line after
+         * us, not even empty cells (with non-default background
+         * color for example).
+         *
+         * Notable bugs here: 545924, 597242, 764330
+         */
+        if (col >= old_len && (newcol - col) <= VTE_TAB_WIDTH_MAX) {
+                glong i;
+                VteCell *cell = _vte_row_data_get_writable (rowdata, col);
+                VteCell tab = *cell;
+                tab.attr.set_columns(newcol - col);
+                tab.c = '\t';
+                /* Save tab char */
+                *cell = tab;
+                /* And adjust the fragments */
+                for (i = col + 1; i < newcol; i++) {
+                        cell = _vte_row_data_get_writable (rowdata, i);
+                        cell->c = '\t';
+                        cell->attr.set_columns(1);
+                        cell->attr.set_fragment(true);
+                }
+        }
 
-               invalidate_cells(m_screen->cursor.col, newcol - m_screen->cursor.col,
-                                 m_screen->cursor.row, 1);
-                m_screen->cursor.col = newcol;
-       }
+        invalidate_cells(m_screen->cursor.col, newcol - m_screen->cursor.col,
+                         m_screen->cursor.row, 1);
+        m_screen->cursor.col = newcol;
 }
 
 void
@@ -1733,35 +1733,10 @@ VteTerminalPrivate::CBT(vte::parser::Sequence const& seq)
          * References: ECMA-48 § 8.3.7
          */
 #if 0
-
-        unsigned int num = 1;
-
-        if (seq->args[0] > 0)
-                num = seq->args[0];
-
         screen_cursor_clear_wrap(screen);
-        screen_cursor_left_tab(screen, num);
 #endif
 
-        // FIXMEchpe! need to support the parameter!!!
-
-       /* Calculate which column is the previous tab stop. */
-        auto newcol = m_screen->cursor.col;
-
-       if (m_tabstops) {
-               /* Find the next tabstop. */
-               while (newcol > 0) {
-                       newcol--;
-                        if (get_tabstop(newcol % m_column_count)) {
-                               break;
-                       }
-               }
-       }
-
-       /* Warp the cursor. */
-       _vte_debug_print(VTE_DEBUG_PARSER,
-                       "Moving cursor to column %ld.\n", (long)newcol);
-        set_cursor_column(newcol);
+        move_cursor_tab_backward(seq.collect1(0, 1));
 }
 
 void
@@ -1811,19 +1786,10 @@ VteTerminalPrivate::CHT(vte::parser::Sequence const& seq)
          * References: ECMA-48 § 8.3.10
          */
 #if 0
-        unsigned int num = 1;
-
-        if (seq->args[0] > 0)
-                num = seq->args[0];
-
         screen_cursor_clear_wrap(screen);
-        screen_cursor_right_tab(screen, num);
 #endif
 
-        auto const val = seq.collect1(0, 1, 1, int(m_column_count - m_screen->cursor.col));
-        // FIXMEchpe stop when cursor.col reaches m_column_count!
-        for (auto i = 0; i < val; i++)
-                move_cursor_tab();
+        move_cursor_tab_forward(seq.collect1(0, 1));
 }
 
 void
@@ -3458,13 +3424,15 @@ VteTerminalPrivate::DECST8C(vte::parser::Sequence const& seq)
          * Clear the tab-ruler and reset it to a tab at every 8th column,
          * starting at 9 (though, setting a tab at 1 is fine as it has no
          * effect).
+         *
+         * References: VT525
          */
-#if 0
-        unsigned int i;
 
-        for (i = 0; i < screen->page->width; i += 8)
-                screen->tabs[i / 8] = 0x1;
-#endif
+        if (seq.collect1(0) != 5)
+                return;
+
+        m_tabstops.reset(8);
+        m_tabstops.unset(0);
 }
 
 void
@@ -4160,10 +4128,9 @@ VteTerminalPrivate::HT(vte::parser::Sequence const& seq)
          */
 #if 0
         screen_cursor_clear_wrap(screen);
-        screen_cursor_right_tab(screen, 1);
 #endif
 
-        move_cursor_tab();
+        move_cursor_tab_forward();
 }
 
 void
@@ -4178,18 +4145,8 @@ VteTerminalPrivate::HTS(vte::parser::Sequence const& seq)
          *
          * References: ECMA-48 § 8.3.62
          */
-#if 0
-        unsigned int pos;
-
-        pos = screen->state.cursor_x;
-        if (screen->page->width > 0)
-                screen->tabs[pos / 8] |= 1U << (pos % 8);
-#endif
 
-       if (m_tabstops == nullptr) {
-               m_tabstops = g_hash_table_new(nullptr, nullptr);
-       }
-       set_tabstop(m_screen->cursor.col);
+        m_tabstops.set(m_screen->cursor.col);
 }
 
 void
@@ -5108,6 +5065,45 @@ VteTerminalPrivate::SUB(vte::parser::Sequence const& seq)
 }
 
 void
+VteTerminalPrivate::TAC(vte::parser::Sequence const& seq)
+{
+        /*
+         * TAC - tabulation aligned centre
+         *
+         * Defaults:
+         *   args[0]: no default
+         *
+         * References: ECMA-48 § 8.3.151
+         */
+}
+
+void
+VteTerminalPrivate::TALE(vte::parser::Sequence const& seq)
+{
+        /*
+         * TALE - tabulation aligned leading edge
+         *
+         * Defaults:
+         *   args[0]: no default
+         *
+         * References: ECMA-48 § 8.3.152
+         */
+}
+
+void
+VteTerminalPrivate::TATE(vte::parser::Sequence const& seq)
+{
+        /*
+         * TATE - tabulation aligned trailing edge
+         *
+         * Defaults:
+         *   args[0]: no default
+         *
+         * References: ECMA-48 § 8.3.153
+         */
+}
+
+void
 VteTerminalPrivate::TBC(vte::parser::Sequence const& seq)
 {
         /*
@@ -5122,43 +5118,32 @@ VteTerminalPrivate::TBC(vte::parser::Sequence const& seq)
          *
          * References: ECMA-48 § 8.3.154
          */
-#if 0
-        unsigned int mode = 0, pos;
-
-        if (seq->args[0] > 0)
-                mode = seq->args[0];
-
-        switch (mode) {
-        case 0:
-                pos = screen->state.cursor_x;
-                if (screen->page->width > 0)
-                        screen->tabs[pos / 8] &= ~(1U << (pos % 8));
-                break;
-        case 3:
-                if (screen->page->width > 0)
-                        memset(screen->tabs, 0, (screen->page->width + 7) / 8);
-                break;
-        }
-#endif
 
-        auto const param = seq.collect1(0, 0);
+        auto const param = seq.collect1(0);
         switch (param) {
+        case -1:
         case 0:
-               clear_tabstop(m_screen->cursor.col);
+                /* Clear character tabstop at the current presentation position */
+                m_tabstops.unset(m_screen->cursor.col);
                 break;
-        case 1: // FIXME implement
+        case 1:
+                /* Clear line tabstop at the current line */
                 break;
-        case 2: // FIXME implement
+        case 2:
+                /* Clear all character tabstops in the current line */
+                /* NOTE: vttest issues this but claims it's a 'no-op' */
+                m_tabstops.clear();
                 break;
         case 3:
-               if (m_tabstops != nullptr) {
-                       g_hash_table_destroy(m_tabstops);
-                       m_tabstops = nullptr;
-               }
+                /* Clear all character tabstops */
+                m_tabstops.clear();
                 break;
-        case 4: // FIXME implement
+        case 4:
+                /* Clear all line tabstops */
                 break;
-        case 5: // FIXME implement
+        case 5:
+                /* Clear all (character and line) tabstops */
+                m_tabstops.clear();
                 break;
         default:
                 break;
@@ -5166,6 +5151,41 @@ VteTerminalPrivate::TBC(vte::parser::Sequence const& seq)
 }
 
 void
+VteTerminalPrivate::TCC(vte::parser::Sequence const& seq)
+{
+        /*
+         * TCC - tabulation centred on character
+         *
+         * Defaults:
+         *   args[0]: no default
+         *   args[1]: 32 (SPACE)
+         *
+         * References: ECMA-48 § 8.3.155
+         */
+}
+
+void
+VteTerminalPrivate::TSR(vte::parser::Sequence const& seq)
+{
+        /*
+         * TSR - tabulation stop remove
+         * This clears a tab stop at position @arg[0] in the active line (presentation),
+         * and on any lines below it.
+         *
+         * Defaults:
+         *   args[0]: no default
+         *
+         * References: ECMA-48 § 8.3.156
+         */
+
+        auto const pos = seq.collect1(0);
+        if (pos < 0 || pos >= m_column_count)
+                return;
+
+        m_tabstops.unset(pos);
+}
+
+void
 VteTerminalPrivate::VPA(vte::parser::Sequence const& seq)
 {
         /*


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