[vte] parser: Implement OSC parsing



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

    parser: Implement OSC parsing

 src/Makefile.am      |    6 +
 src/parser-arg.hh    |    2 +-
 src/parser-cat.cc    |   24 ++-
 src/parser-cmd.hh    |    1 +
 src/parser-glue.hh   |  324 ++++++++++++++++-----
 src/parser-osc.hh    |  100 +++++++
 src/parser-string.hh |  138 +++++++++
 src/parser-test.cc   |  393 +++++++++++++++++++++++--
 src/parser.cc        |   58 +++--
 src/parser.hh        |   12 +-
 src/vte.cc           |   53 ++--
 src/vtegtk.cc        |   10 +-
 src/vteinternal.hh   |   63 +++--
 src/vteseq.cc        |  808 +++++++++++++++++++++++++-------------------------
 14 files changed, 1399 insertions(+), 593 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index a17d441..e93f6a5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -65,6 +65,8 @@ libvte_@VTE_API_MAJOR_VERSION@_@VTE_API_MINOR_VERSION@_la_SOURCES = \
        parser-charset-tables.hh \
        parser-cmd.hh \
        parser-glue.hh \
+       parser-osc.hh \
+       parser-string.hh \
        pty.cc \
        reaper.cc \
        reaper.hh \
@@ -248,6 +250,8 @@ parser_cat_SOURCES = \
        parser-charset-tables.hh \
        parser-cmd.hh \
        parser-glue.hh \
+       parser-osc.hh \
+       parser-string.hh \
        parser-cat.cc \
        vteconv.cc \
        vteconv.h \
@@ -281,6 +285,8 @@ test_parser_SOURCES = \
        parser-charset.hh \
        parser-charset-tables.hh \
        parser-glue.hh \
+       parser-osc.hh \
+       parser-string.hh \
        $(NULL)
 test_parser_CPPFLAGS = \
        -I$(builddir) \
diff --git a/src/parser-arg.hh b/src/parser-arg.hh
index 88a31b2..0f94832 100644
--- a/src/parser-arg.hh
+++ b/src/parser-arg.hh
@@ -103,7 +103,7 @@ static inline void vte_seq_arg_push(vte_seq_arg_t* arg,
  * @arg:
  * @finalise:
  *
- * Finished @arg; after this no more vte_seq_arg_push() calls
+ * Finishes @arg; after this no more vte_seq_arg_push() calls
  * are allowed.
  *
  * If @nonfinal is %true, marks @arg as a nonfinal parameter, is,
diff --git a/src/parser-cat.cc b/src/parser-cat.cc
index 88319a6..6953097 100644
--- a/src/parser-cat.cc
+++ b/src/parser-cat.cc
@@ -169,6 +169,19 @@ print_intermediates(GString* str,
 }
 
 static void
+print_string(GString* str,
+             struct vte_seq const* seq)
+{
+        size_t len;
+        auto buf = vte_seq_string_get(&seq->arg_str, &len);
+
+        g_string_append_c(str, '\"');
+        for (size_t i = 0; i < len; ++i)
+                g_string_append_unichar(str, buf[i]);
+        g_string_append_c(str, '\"');
+}
+
+static void
 print_seq_and_params(GString* str,
                      const struct vte_seq *seq,
                      bool plain)
@@ -238,12 +251,19 @@ print_seq(GString* str,
         }
 
         case VTE_SEQ_CSI:
-        case VTE_SEQ_DCS:
-        case VTE_SEQ_OSC: {
+        case VTE_SEQ_DCS: {
                 print_seq_and_params(str, seq, plain);
                 break;
         }
 
+        case VTE_SEQ_OSC: {
+                printer p(str, plain, SEQ_START, SEQ_END);
+                g_string_append(str, "{OSC ");
+                print_string(str, seq);
+                g_string_append_c(str, '}');
+                break;
+        }
+
         default:
                 assert(false);
         }
diff --git a/src/parser-cmd.hh b/src/parser-cmd.hh
index a94a308..ea2dbcb 100644
--- a/src/parser-cmd.hh
+++ b/src/parser-cmd.hh
@@ -152,6 +152,7 @@ _VTE_CMD(MC_DEC) /* media-copy-dec */
 _VTE_CMD(NEL) /* next-line */
 _VTE_CMD(NP) /* next-page */
 _VTE_CMD(NUL) /* nul */
+_VTE_CMD(OSC) /* operating-system-command */
 _VTE_CMD(PP) /* preceding-page */
 _VTE_CMD(PPA) /* page-position-absolute */
 _VTE_CMD(PPB) /* page-position-backward */
diff --git a/src/parser-glue.hh b/src/parser-glue.hh
index 98ef424..5e4303e 100644
--- a/src/parser-glue.hh
+++ b/src/parser-glue.hh
@@ -19,6 +19,7 @@
 
 #include <cstdint>
 #include <algorithm>
+#include <string>
 
 #include "parser.hh"
 
@@ -31,7 +32,8 @@ public:
 
         typedef int number;
 
-        char* ucs4_to_utf8(gunichar const* str) const noexcept;
+        char* ucs4_to_utf8(gunichar const* str,
+                           ssize_t len = -1) const noexcept;
 
         void print() const noexcept;
 
@@ -91,6 +93,53 @@ public:
                 return m_seq->terminator;
         }
 
+        // FIXMEchpe: upgrade to C++17 and use the u32string_view version below, instead
+        /*
+         * string:
+         *
+         * This is the string argument of a DCS or OSC sequence.
+         *
+         * Returns: the string argument
+         */
+        inline std::u32string string() const noexcept
+        {
+                size_t len;
+                auto buf = vte_seq_string_get(&m_seq->arg_str, &len);
+                return std::u32string(reinterpret_cast<char32_t*>(buf), len);
+        }
+
+        #if 0
+        /*
+         * string:
+         *
+         * This is the string argument of a DCS or OSC sequence.
+         *
+         * Returns: the string argument
+         */
+        inline constexpr std::u32string_view string() const noexcept
+        {
+                size_t len = 0;
+                auto buf = vte_seq_string_get(&m_seq->arg_str, &len);
+                return std::u32string_view(buf, len);
+        }
+        #endif
+
+        /*
+         * string:
+         *
+         * This is the string argument of a DCS or OSC sequence.
+         *
+         * Returns: the string argument
+         */
+        std::string string_utf8() const noexcept;
+
+        inline char* string_param() const noexcept
+        {
+                size_t len = 0;
+                auto buf = vte_seq_string_get(&m_seq->arg_str, &len);
+                return ucs4_to_utf8(buf, len);
+        }
+
         /* size:
          *
          * Returns: the number of parameters
@@ -274,105 +323,232 @@ public:
                 return idx <= next(start_idx);
         }
 
-        //FIMXE remove this one
-        inline constexpr int operator[](int position) const
-        {
-                return __builtin_expect(position < (int)size(), 1) ? 
vte_seq_arg_value(m_seq->args[position]) : -1;
-        }
+        struct vte_seq** seq_ptr() { return &m_seq; }
+
+        inline explicit operator bool() const { return m_seq != nullptr; }
+
+private:
+        struct vte_seq *m_seq{nullptr};
 
-        inline bool has_number_at_unchecked(unsigned int position) const
+        char const* type_string() const;
+        char const* command_string() const;
+};
+
+class StringTokeniser {
+public:
+        using string_type = std::string;
+        using char_type = std::string::value_type;
+
+private:
+        string_type const& m_string;
+        char_type m_separator{';'};
+
+public:
+        StringTokeniser(string_type const& s,
+                        char_type separator = ';')
+                : m_string{s},
+                  m_separator{separator}
         {
-                return true;
         }
 
-        inline bool number_at_unchecked(unsigned int position, number& v) const
+        StringTokeniser(string_type&& s,
+                        char_type separator = ';')
+                : m_string{s},
+                  m_separator{separator}
         {
-                v = vte_seq_arg_value(m_seq->args[position]);
-                return true;
         }
 
-        inline bool number_at(unsigned int position, number& v) const
-        {
-                if (G_UNLIKELY(position >= size()))
-                        return false;
+        StringTokeniser(StringTokeniser const&) = delete;
+        StringTokeniser(StringTokeniser&&) = delete;
+        ~StringTokeniser() = default;
 
-                return number_at_unchecked(position, v);
-        }
+        StringTokeniser& operator=(StringTokeniser const&) = delete;
+        StringTokeniser& operator=(StringTokeniser&&) = delete;
 
-        inline number number_or_default_at_unchecked(unsigned int position, number default_v = 0) const
-        {
-                number v;
-                if (G_UNLIKELY(!number_at_unchecked(position, v)))
-                        v = default_v;
-                return v;
-        }
+        /*
+         * const_iterator:
+         *
+         * InputIterator for string tokens.
+         */
+        class const_iterator {
+        public:
+                using difference_type = ptrdiff_t;
+                using value_type = string_type;
+                using pointer = string_type;
+                using reference = string_type;
+                using iterator_category = std::input_iterator_tag;
+                using size_type = string_type::size_type;
+
+        private:
+                string_type const* m_string;
+                char_type m_separator{';'};
+                string_type::size_type m_position;
+                string_type::size_type m_next_separator;
+
+        public:
+                const_iterator(string_type const* str,
+                               char_type separator,
+                               size_type position)
+                        : m_string{str},
+                          m_separator{separator},
+                          m_position{position},
+                          m_next_separator{m_string->find(m_separator, m_position)}
+                {
+                }
 
+                const_iterator(string_type const* str,
+                               char_type separator)
+                        : m_string{str},
+                          m_separator{separator},
+                          m_position{string_type::npos},
+                          m_next_separator{string_type::npos}
+                {
+                }
 
-        inline number number_or_default_at(unsigned int position, number default_v = 0) const
-        {
-                number v;
-                if (G_UNLIKELY(!number_at(position, v)))
-                        v = default_v;
-                return v;
-        }
+                const_iterator(const_iterator const&) = default;
+                const_iterator(const_iterator&& o)
+                        : m_string{o.m_string},
+                          m_separator{o.m_separator},
+                          m_position{o.m_position},
+                          m_next_separator{o.m_next_separator}
+                {
+                }
 
-        inline bool string_at_unchecked(unsigned int position, char*& str) const
-        {
-#if 0
-                auto value = value_at_unchecked(position);
-                if (G_LIKELY(G_VALUE_HOLDS_POINTER(value))) {
-                        str = ucs4_to_utf8((gunichar const*)g_value_get_pointer (value));
-                        return str != nullptr;
+                ~const_iterator() = default;
+
+                const_iterator& operator=(const_iterator const& o)
+                {
+                        m_string = o.m_string;
+                        m_separator = o.m_separator;
+                        m_position = o.m_position;
+                        m_next_separator = o.m_next_separator;
+                        return *this;
                 }
-                if (G_VALUE_HOLDS_STRING(value)) {
-                        /* Copy the string into the buffer. */
-                        str = g_value_dup_string(value);
-                        return str != nullptr;
+
+                const_iterator& operator=(const_iterator&& o)
+                {
+                        m_string = std::move(o.m_string);
+                        m_separator = o.m_separator;
+                        m_position = o.m_position;
+                        m_next_separator = o.m_next_separator;
+                        return *this;
                 }
-                if (G_VALUE_HOLDS_LONG(value)) {
-                        /* Convert the long to a string. */
-                        str = g_strdup_printf("%ld", g_value_get_long(value));
-                        return true;
+
+                inline bool operator==(const_iterator const& o) const noexcept
+                {
+                        return m_position == o.m_position;
+                }
+
+                inline bool operator!=(const_iterator const& o) const noexcept
+                {
+                        return m_position != o.m_position;
+                }
+
+                inline const_iterator& operator++() noexcept
+                {
+                        if (m_next_separator != string_type::npos) {
+                                m_position = ++m_next_separator;
+                                m_next_separator = m_string->find(m_separator, m_position);
+                        } else
+                                m_position = string_type::npos;
+
+                        return *this;
+                }
+
+                /*
+                 * number:
+                 *
+                 * Returns the value of the iterator as a number, or -1
+                 *   if the string could not be parsed as a number, or
+                 *   the parsed values exceeds the uint16_t range.
+                 *
+                 * Returns: true if a number was parsed
+                 */
+                bool number(int& v) const noexcept
+                {
+                        auto const s = size();
+                        if (s == 0) {
+                                v = -1;
+                                return true;
+                        }
+
+                        v = 0;
+                        size_type i;
+                        for (i = 0; i < s; ++i) {
+                                char_type c = (*m_string)[m_position + i];
+                                if (c < '0' || c > '9')
+                                        return false;
+
+                                v = v * 10 + (c - '0');
+                                if (v > 0xffff)
+                                        return false;
+                        }
+
+                        /* All consumed? */
+                        return i == s;
+                }
+
+                inline size_type size() const noexcept
+                {
+                        if (m_next_separator != string_type::npos)
+                                return m_next_separator - m_position;
+                        else
+                                return m_string->size() - m_position;
+                }
+
+                inline size_type size_remaining() const noexcept
+                {
+                        return m_string->size() - m_position;
                 }
-#endif
-                str = nullptr;
-                return false;
-        }
 
-        inline bool string_at(unsigned int position, char*& str) const
+                inline string_type operator*() const noexcept
+                {
+                        return m_string->substr(m_position, size());
+                }
+
+                /*
+                 * string_remaining:
+                 *
+                 * Returns the whole string left, including possibly more separators.
+                 */
+                inline string_type string_remaining() const noexcept
+                {
+                        return m_string->substr(m_position);
+                }
+
+                inline void append(string_type& str) const noexcept
+                {
+                        str.append(m_string->substr(m_position, size()));
+                }
+
+                inline void append_remaining(string_type& str) const noexcept
+                {
+                        str.append(m_string->substr(m_position));
+                }
+
+        }; // class const_iterator
+
+        inline const_iterator cbegin(char_type c = ';') const noexcept
         {
-#if 0
-                if (G_UNLIKELY(position >= size()))
-                        return false;
-
-                return string_at_unchecked(position, str);
-#endif
-                str = nullptr;
-                return false;
+                return const_iterator(&m_string, m_separator, 0);
         }
 
-        inline bool has_subparams_at_unchecked(unsigned int position) const
+        inline const_iterator cend() const noexcept
         {
-                return false;
+                return const_iterator(&m_string, m_separator);
         }
 
-        inline Sequence subparams_at_unchecked(unsigned int position) const
+        inline const_iterator begin(char_type c = ';') const noexcept
         {
-                return Sequence{};
+                return cbegin();
         }
 
-        struct vte_seq** seq_ptr() { return &m_seq; }
-
-        inline explicit operator bool() const { return m_seq != nullptr; }
-
-private:
-        struct vte_seq *m_seq{nullptr};
-
-        char const* type_string() const;
-        char const* command_string() const;
-};
+        inline const_iterator end() const noexcept
+        {
+                return cend();
+        }
 
-typedef Sequence Params;
+}; // class StringTokeniser
 
 } // namespace parser
 
diff --git a/src/parser-osc.hh b/src/parser-osc.hh
new file mode 100644
index 0000000..ca2cdc8
--- /dev/null
+++ b/src/parser-osc.hh
@@ -0,0 +1,100 @@
+/*
+ * 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/>.
+ */
+
+#if !defined(_VTE_OSC)
+#error "Must define _VTE_OSC before including this file"
+#endif
+
+/* _VTE_OSC(DTTERM_CWD, 3) Conflicts with XTERM_SET_XPROPERTY */
+
+_VTE_OSC(EMACS_51, 51)
+
+_VTE_OSC(ITERM2_GROWL, 9)
+_VTE_OSC(ITERM2_133, 133)
+_VTE_OSC(ITERM2_1337, 1337)
+
+_VTE_OSC(KONSOLE_30, 30)
+_VTE_OSC(KONSOLE_31, 31)
+
+/* _VTE_OSC(MINTTY_CLIPBOARD_COPY_WINDOW_TITLE,    7721) */
+/* _VTE_OSC(MINTTY_CHANGE_FONT_SIZE,               7770) */
+/* _VTE_OSC(MINTTY_QUERY_FONT_SUPPORTS_CHARACTERS, 7771) */
+/* _VTE_OSC(MINTTY_CHANGE_FONT_AND_WINDOW_SIZE,    7777) */
+/* _VTE_OSC(MINTTY_INDIC_WIDE, 77119) out of range */
+
+_VTE_OSC(RLOGIN_SET_KANJI_MODE, 800)
+_VTE_OSC(RLOGIN_SPEECH, 801)
+
+/* _VTE_OSC(RXVT_MENU, 10) * Conflics with XTERM_SET_COLOR_TEXT_FG */
+_VTE_OSC(RXVT_SET_BACKGROUND_PIXMAP, 20)
+_VTE_OSC(RXVT_SET_COLOR_FG, 39)
+_VTE_OSC(RXVT_SET_COLOR_BG, 49)
+_VTE_OSC(RXVT_DUMP_SCREEN, 55)
+
+_VTE_OSC(URXVT_SET_LOCALE, 701)
+_VTE_OSC(URXVT_VERSION, 702)
+_VTE_OSC(URXVT_SET_COLOR_TEXT_ITALIC, 704)
+_VTE_OSC(URXVT_SET_COLOR_TEXT_BOLD, 706)
+_VTE_OSC(URXVT_SET_COLOR_UNDERLINE, 707)
+_VTE_OSC(URXVT_SET_COLOR_BORDER, 708)
+_VTE_OSC(URXVT_SET_FONT, 710)
+_VTE_OSC(URXVT_SET_FONT_BOLD, 711)
+_VTE_OSC(URXVT_SET_FONT_ITALIC, 712)
+_VTE_OSC(URXVT_SET_FONT_BOLD_ITALIC, 713)
+_VTE_OSC(URXVT_VIEW_UP, 720)
+_VTE_OSC(URXVT_VIEW_DOWN, 721)
+_VTE_OSC(URXVT_EXTENSION, 777)
+
+_VTE_OSC(VTECWF, 6)
+_VTE_OSC(VTECWD, 7)
+_VTE_OSC(VTEHYPER, 8)
+
+_VTE_OSC(XTERM_SET_WINDOW_AND_ICON_TITLE, 0)
+_VTE_OSC(XTERM_SET_ICON_TITLE, 1)
+_VTE_OSC(XTERM_SET_WINDOW_TITLE, 2)
+_VTE_OSC(XTERM_SET_XPROPERTY, 3)
+_VTE_OSC(XTERM_SET_COLOR, 4)
+_VTE_OSC(XTERM_SET_COLOR_SPECIAL, 5)
+/* _VTE_OSC(XTERM_SET_COLOR_MODE, 6) Conflict with our own OSC 6 VTECWF; so use 106 */
+_VTE_OSC(XTERM_SET_COLOR_TEXT_FG, 10)
+_VTE_OSC(XTERM_SET_COLOR_TEXT_BG, 11)
+_VTE_OSC(XTERM_SET_COLOR_CURSOR_BG, 12)
+_VTE_OSC(XTERM_SET_COLOR_MOUSE_CURSOR_FG, 13)
+_VTE_OSC(XTERM_SET_COLOR_MOUSE_CURSOR_BG, 14)
+_VTE_OSC(XTERM_SET_COLOR_TEK_FG, 15)
+_VTE_OSC(XTERM_SET_COLOR_TEK_BG, 16)
+_VTE_OSC(XTERM_SET_COLOR_HIGHLIGHT_BG, 17)
+_VTE_OSC(XTERM_SET_COLOR_TEK_CURSOR, 18)
+_VTE_OSC(XTERM_SET_COLOR_HIGHLIGHT_FG, 19)
+_VTE_OSC(XTERM_LOGFILE, 46)
+_VTE_OSC(XTERM_SET_FONT, 50)
+_VTE_OSC(XTERM_SET_XSELECTION, 52)
+_VTE_OSC(XTERM_RESET_COLOR, 104)
+_VTE_OSC(XTERM_RESET_COLOR_SPECIAL, 105)
+_VTE_OSC(XTERM_SET_COLOR_MODE, 106)
+_VTE_OSC(XTERM_RESET_COLOR_TEXT_FG, 110)
+_VTE_OSC(XTERM_RESET_COLOR_TEXT_BG, 111)
+_VTE_OSC(XTERM_RESET_COLOR_CURSOR_BG, 112)
+_VTE_OSC(XTERM_RESET_COLOR_MOUSE_CURSOR_FG, 113)
+_VTE_OSC(XTERM_RESET_COLOR_MOUSE_CURSOR_BG, 114)
+_VTE_OSC(XTERM_RESET_COLOR_TEK_FG, 115)
+_VTE_OSC(XTERM_RESET_COLOR_TEK_BG, 116)
+_VTE_OSC(XTERM_RESET_COLOR_HIGHLIGHT_BG, 117)
+_VTE_OSC(XTERM_RESET_COLOR_TEK_CURSOR, 118)
+_VTE_OSC(XTERM_RESET_COLOR_HIGHLIGHT_FG, 119)
+
+_VTE_OSC(YF_RQGWR, 8900)
diff --git a/src/parser-string.hh b/src/parser-string.hh
new file mode 100644
index 0000000..7de37e4
--- /dev/null
+++ b/src/parser-string.hh
@@ -0,0 +1,138 @@
+/*
+ * 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 <glib.h>
+#include <assert.h>
+#include <cstdint>
+#include <cstring>
+
+/*
+ * vte_seq_string_t:
+ *
+ * A type to hold the argument string of a DSC or OSC sequence.
+ */
+typedef struct vte_seq_string_t {
+        uint32_t capacity;
+        uint32_t len;
+        uint32_t* buf;
+} vte_seq_string_t;
+
+#define VTE_SEQ_STRING_DEFAULT_CAPACITY (1 << 7) /* must be power of two */
+#define VTE_SEQ_STRING_MAX_CAPACITY     (1 << 12)
+
+/*
+ * vte_seq_string_init:
+ *
+ * Returns: a new #vte_seq_string_t
+ */
+static inline void vte_seq_string_init(vte_seq_string_t* str) noexcept
+{
+        str->capacity = VTE_SEQ_STRING_DEFAULT_CAPACITY;
+        str->len = 0;
+        str->buf = (uint32_t*)g_malloc0_n(str->capacity, sizeof(uint32_t));
+}
+
+/*
+ * vte_seq_string_free:
+ * @string:
+ *
+ * Frees @string's storage and itself.
+ */
+static inline void vte_seq_string_free(vte_seq_string_t* str) noexcept
+{
+        g_free(str->buf);
+}
+
+/*
+ * vte_seq_string_ensure_capacity:
+ * @string:
+ *
+ * If @string's length is at capacity, and capacity is not maximal,
+ * expands the string's capacity.
+ *
+ * Returns: %true if the string has capacity for at least one more character
+ */
+static inline bool vte_seq_string_ensure_capacity(vte_seq_string_t* str) noexcept
+{
+        if (str->len < str->capacity)
+                return true;
+        if (str->capacity >= VTE_SEQ_STRING_MAX_CAPACITY)
+                return false;
+
+        str->capacity *= 2;
+        str->buf = (uint32_t*)g_realloc_n(str->buf, str->capacity, sizeof(uint32_t));
+        return true;
+}
+
+/*
+ * vte_seq_string_push:
+ * @string:
+ * @c: a character
+ *
+ * Appends @c to @str, or iff @str already has maximum length, does nothing.
+ *
+ * Returns: %true if the character was appended
+ */
+static inline bool vte_seq_string_push(vte_seq_string_t* str,
+                                       uint32_t c) noexcept
+{
+        if (!vte_seq_string_ensure_capacity(str))
+                return false;
+
+        str->buf[str->len++] = c;
+        return true;
+}
+
+/*
+ * vte_seq_string_finish:
+ * @string:
+ *
+ * Finishes @string; after this no more vte_seq_string_push() calls
+ * are allowed until the string is reset with vte_seq_string_reset().
+ */
+static inline void vte_seq_string_finish(vte_seq_string_t* str)
+{
+}
+
+/*
+ * vte_seq_string_reset:
+ * @string:
+ *
+ * Resets @string.
+ */
+static inline void vte_seq_string_reset(vte_seq_string_t* str) noexcept
+{
+        /* Zero length. However, don't clear the buffer, nor shrink the capacity. */
+        str->len = 0;
+}
+
+/*
+ * vte_seq_string_get:
+ * @string:
+ * @len: location to store the buffer length in code units
+ *
+ * Returns: the string's buffer as an array of uint32_t code units
+ */
+static constexpr inline uint32_t* vte_seq_string_get(vte_seq_string_t const* str,
+                                                     size_t* len) noexcept
+{
+        assert(len != nullptr);
+        *len = str->len;
+        return str->buf;
+}
diff --git a/src/parser-test.cc b/src/parser-test.cc
index e050870..113719d 100644
--- a/src/parser-test.cc
+++ b/src/parser-test.cc
@@ -25,6 +25,8 @@
 #include <string>
 #include <vector>
 
+using namespace std::literals;
+
 #include <glib.h>
 
 #include "parser.hh"
@@ -155,6 +157,15 @@ public:
                 set_final(f);
         }
 
+        vte_seq_builder(unsigned int type,
+                        std::u32string const& str)
+                : m_arg_str(str)
+        {
+                memset(&m_seq, 0, sizeof(m_seq));
+                m_seq.type = type;
+                set_final(0);
+        }
+
         ~vte_seq_builder() = default;
 
         void set_final(uint32_t raw) { m_seq.terminator = raw; }
@@ -189,8 +200,23 @@ public:
                 }
         }
 
+        void set_string(std::u32string const& str)
+        {
+                m_arg_str = str;
+        }
+
+        enum VariantST {
+                ST_NONE,
+                ST_DEFAULT,
+                ST_C0,
+                ST_C1,
+                ST_BEL
+        };
+
         void to_string(std::u32string& s,
-                       bool c1 = false);
+                       bool c1 = false,
+                       ssize_t max_arg_str_len = -1,
+                       VariantST st = ST_DEFAULT);
 
         void assert_equal(struct vte_seq* seq);
         void assert_equal_full(struct vte_seq* seq);
@@ -201,25 +227,40 @@ private:
         uint32_t m_i[4]{0, 0, 0, 0};
         uint32_t m_p;
         unsigned int m_ni{0};
+        std::u32string m_arg_str;
         struct vte_seq m_seq;
-
 };
 
 void
 vte_seq_builder::to_string(std::u32string& s,
-                           bool c1)
+                           bool c1,
+                           ssize_t max_arg_str_len,
+                           VariantST st)
 {
-        switch (m_seq.type) {
-        case VTE_SEQ_ESCAPE:
+        /* Introducer */
+        if (c1) {
+                switch (m_seq.type) {
+                case VTE_SEQ_ESCAPE: s.push_back(0x1B); break; // ESC
+                case VTE_SEQ_CSI:    s.push_back(0x9B); break; // CSI
+                case VTE_SEQ_DCS:    s.push_back(0x90); break; // DCS
+                case VTE_SEQ_OSC:    s.push_back(0x9D); break; // OSC
+                default: return;
+                }
+        } else {
                 s.push_back(0x1B); // ESC
-                break;
-        case VTE_SEQ_CSI: {
-                if (c1) {
-                        s.push_back(0x9B); // CSI
-                } else {
-                        s.push_back(0x1B); // ESC
-                        s.push_back(0x5B); // [
+                switch (m_seq.type) {
+                case VTE_SEQ_ESCAPE:                    break; // nothing more
+                case VTE_SEQ_CSI:    s.push_back(0x5B); break; // [
+                case VTE_SEQ_DCS:    s.push_back(0x50); break; // P
+                case VTE_SEQ_OSC:    s.push_back(0x5D); break; // ]
+                default: return;
                 }
+        }
+
+        /* Parameters */
+        switch (m_seq.type) {
+        case VTE_SEQ_CSI:
+        case VTE_SEQ_DCS: {
 
                 if (m_p != 0)
                         s.push_back(m_p);
@@ -238,13 +279,59 @@ vte_seq_builder::to_string(std::u32string& s,
                 break;
         }
         default:
-                return;
+                break;
+        }
+
+        /* Intermediates and Final */
+        switch (m_seq.type) {
+        case VTE_SEQ_ESCAPE:
+        case VTE_SEQ_CSI:
+        case VTE_SEQ_DCS:
+                for (unsigned int n = 0; n < m_ni; n++)
+                        s.push_back(m_i[n]);
+
+                s.push_back(m_seq.terminator);
+                break;
+        default:
+                break;
         }
 
-        for (unsigned int n = 0; n < m_ni; n++)
-                s.push_back(m_i[n]);
+        /* String and ST */
+        switch (m_seq.type) {
+        case VTE_SEQ_DCS:
+        case VTE_SEQ_OSC:
+
+                if (max_arg_str_len < 0)
+                        s.append(m_arg_str, 0, max_arg_str_len);
+                else
+                        s.append(m_arg_str);
 
-        s.push_back(m_seq.terminator);
+                switch (st) {
+                case ST_NONE:
+                        // omit ST
+                        break;
+                case ST_DEFAULT:
+                        if (c1) {
+                                s.push_back(0x9C); // ST
+                        } else {
+                                s.push_back(0x1B); // ESC
+                                s.push_back(0x5C); // BACKSLASH
+                        }
+                        break;
+                case ST_C0:
+                        s.push_back(0x1B); // ESC
+                        s.push_back(0x5C); // BACKSLASH
+                        break;
+                case ST_C1:
+                        s.push_back(0x9C); // ST
+                        break;
+                case ST_BEL:
+                        s.push_back(0x7); // BEL
+                        break;
+                default:
+                        break;
+                }
+        }
 }
 
 void
@@ -259,7 +346,7 @@ void
 vte_seq_builder::assert_equal(struct vte_seq* seq)
 {
         g_assert_cmpuint(m_seq.type, ==, seq->type);
-        g_assert_cmpuint(m_seq.terminator, ==, seq->terminator);
+        g_assert_cmphex(m_seq.terminator, ==, seq->terminator);
 }
 
 void
@@ -330,6 +417,39 @@ test_seq_arg(void)
 }
 
 static void
+test_seq_string(void)
+{
+        vte_seq_string_t str;
+        vte_seq_string_init(&str);
+
+        size_t len;
+        auto buf = vte_seq_string_get(&str, &len);
+        g_assert_cmpuint(len, ==, 0);
+
+        for (unsigned int i = 0; i < VTE_SEQ_STRING_MAX_CAPACITY; ++i) {
+                auto rv = vte_seq_string_push(&str, 0xfffdU);
+                g_assert_true(rv);
+
+                buf = vte_seq_string_get(&str, &len);
+                g_assert_cmpuint(len, ==, i + 1);
+        }
+
+        /* Try one more */
+        auto rv = vte_seq_string_push(&str, 0xfffdU);
+        g_assert_false(rv);
+
+        buf = vte_seq_string_get(&str, &len);
+        for (unsigned int i = 0; i < len; i++)
+                g_assert_cmpuint(buf[i], ==, 0xfffdU);
+
+        vte_seq_string_reset(&str);
+        buf = vte_seq_string_get(&str, &len);
+        g_assert_cmpuint(len, ==, 0);
+
+        vte_seq_string_free(&str);
+}
+
+static void
 test_seq_control(void)
 {
         static struct {
@@ -865,10 +985,10 @@ test_seq_csi_param(void)
 }
 
 static void
-test_seq_glue(char const* str,
-              unsigned int n_args,
-              unsigned int n_final_args,
-              vte::parser::Sequence& seq)
+test_seq_glue_arg(char const* str,
+                  unsigned int n_args,
+                  unsigned int n_final_args,
+                  vte::parser::Sequence& seq)
 {
         test_seq_parse(str, seq.seq_ptr());
 
@@ -888,11 +1008,11 @@ test_seq_glue(char const* str,
 }
 
 static void
-test_seq_glue(void)
+test_seq_glue_arg(void)
 {
         vte::parser::Sequence seq{};
 
-        test_seq_glue(":0:1000;2;3;4;:;", 9, 6, seq);
+        test_seq_glue_arg(":0:1000;2;3;4;:;", 9, 6, seq);
         g_assert_cmpuint(seq.cbegin(), ==, 0);
         g_assert_cmpuint(seq.cend(), ==, 9);
 
@@ -992,6 +1112,227 @@ test_seq_glue(void)
         g_assert_cmpint(seq.collect1(it, 42, 100, 200), ==, 100);
 }
 
+static int
+feed_parser_st(vte_seq_builder& b,
+               struct vte_seq** seq,
+               bool c1 = false,
+               ssize_t max_arg_str_len = -1,
+               vte_seq_builder::VariantST st = vte_seq_builder::ST_DEFAULT)
+{
+        std::u32string s;
+        b.to_string(s, c1, max_arg_str_len, st);
+
+        auto rv = feed_parser(s, seq);
+        if (rv == VTE_SEQ_NONE)
+                return rv;
+
+        #if 0
+        switch (st) {
+        case ST_NONE:
+                g_assert_cmpuint(seq.terminator(), ==, 0);
+                break;
+        case ST_DEFAULT:
+                g_assert_cmpuint(seq.terminator(), ==, c1 ? 0x9C /* ST */ : 0x50 /* BACKSLASH */);
+                break;
+        case ST_C0:
+                g_assert_cmpuint(seq.terminator(), ==, 0x50 /* BACKSLASH */);
+                break;
+        case ST_C1:
+                g_assert_cmpuint(seq.terminator(), ==, 0x9C /* ST */);
+                break;
+        case ST_BEL:
+                g_assert_cmpuint(seq.terminator(), ==, 0x7 /* BEL */);
+                break;
+        }
+        #endif
+
+        return rv;
+}
+
+static void
+test_seq_osc(std::u32string const& str,
+             struct vte_seq** seq,
+             int expected_rv = VTE_SEQ_OSC,
+             bool c1 = true,
+             ssize_t max_arg_str_len = -1,
+             vte_seq_builder::VariantST st = vte_seq_builder::ST_DEFAULT)
+{
+        vte_seq_builder b{VTE_SEQ_OSC, str};
+
+        vte_parser_reset(parser);
+        auto rv = feed_parser_st(b, seq, c1, max_arg_str_len, st);
+        g_assert_cmpint(rv, ==, expected_rv);
+        #if 0
+        if (rv != VTE_SEQ_NONE)
+                b.assert_equal(*seq);
+        #endif
+}
+
+static void
+test_seq_osc(std::u32string const& str,
+             vte::parser::Sequence& seq,
+             int expected_rv = VTE_SEQ_OSC,
+             bool c1 = true,
+             ssize_t max_arg_str_len = -1,
+             vte_seq_builder::VariantST st = vte_seq_builder::ST_DEFAULT)
+{
+        test_seq_osc(str, seq.seq_ptr(), expected_rv, c1, max_arg_str_len, st);
+        if (expected_rv != VTE_SEQ_OSC)
+                return;
+
+        if (max_arg_str_len < 0 || size_t(max_arg_str_len) == str.size())
+                g_assert_true(seq.string() == str);
+        else
+                g_assert_true(seq.string() == str.substr(0, max_arg_str_len));
+}
+
+static void
+test_seq_osc(void)
+{
+        vte::parser::Sequence seq{};
+
+        /* Simple */
+        test_seq_osc(U""s, seq);
+        test_seq_osc(U"TEST"s, seq);
+
+        /* String of any supported length */
+        for (unsigned int len = 0; len < VTE_SEQ_STRING_MAX_CAPACITY; ++len)
+                test_seq_osc(std::u32string(len, 0x100000), seq);
+
+        /* Length exceeded */
+        //        test_seq_osc(std::u32string(VTE_SEQ_STRING_MAX_CAPACITY + 1, 0x100000), seq, VTE_SEQ_NONE);
+
+        /* Test all introducer/ST combinations */
+        //        test_seq_osc(U"TEST"s, seq, VTE_SEQ_NONE, false, -1, vte_seq_builder::ST_NONE);
+        //        test_seq_osc(U"TEST"s, seq, VTE_SEQ_NONE, true, -1, vte_seq_builder::ST_NONE);
+        //        test_seq_osc(U"TEST"s, seq, VTE_SEQ_OSC, false, -1, vte_seq_builder::ST_DEFAULT);
+        //        test_seq_osc(U"TEST"s, seq, VTE_SEQ_OSC, true, -1, vte_seq_builder::ST_DEFAULT);
+        //        test_seq_osc(U"TEST"s, seq, VTE_SEQ_OSC, false, -1, vte_seq_builder::ST_C0);
+        //        test_seq_osc(U"TEST"s, seq, VTE_SEQ_OSC, true, -1, vte_seq_builder::ST_C0);
+        test_seq_osc(U"TEST"s, seq, VTE_SEQ_OSC, false, -1, vte_seq_builder::ST_C1);
+        test_seq_osc(U"TEST"s, seq, VTE_SEQ_OSC, true, -1, vte_seq_builder::ST_C1);
+        test_seq_osc(U"TEST"s, seq, VTE_SEQ_OSC, false, -1, vte_seq_builder::ST_BEL);
+        test_seq_osc(U"TEST"s, seq, VTE_SEQ_OSC, true, -1, vte_seq_builder::ST_BEL);
+}
+
+static void
+test_seq_glue_string(void)
+{
+        vte::parser::Sequence seq{};
+
+        std::u32string str{U"TEST"s};
+        test_seq_osc(str, seq);
+
+        g_assert_true(seq.string() == str);
+}
+
+static void
+test_seq_glue_string_tokeniser(void)
+{
+        std::string str{"a;1b:17:test::b:;3;5;def;17 a;ghi;"s};
+
+        vte::parser::StringTokeniser tokeniser{str, ';'};
+
+        auto start = tokeniser.cbegin();
+        auto end = tokeniser.cend();
+
+        auto pit = start;
+        for (auto it : {"a"s, "1b:17:test::b:"s, "3"s, "5"s, "def"s, "17 a"s, "ghi"s, ""s}) {
+                g_assert_true(it == *pit);
+
+                /* Use std::find to see if the InputIterator implementation
+                 * is complete and correct.
+                 */
+                auto fit = std::find(start, end, it);
+                g_assert_true(fit == pit);
+
+                ++pit;
+        }
+        g_assert_true(pit == end);
+
+        auto len = str.size();
+        size_t pos = 0;
+        pit = start;
+        for (auto it : {1, 14, 1, 1, 3, 4, 3, 0}) {
+                g_assert_cmpuint(it, ==, pit.size());
+                g_assert_cmpuint(len, ==, pit.size_remaining());
+
+                g_assert_true(pit.string_remaining() == str.substr(pos, std::string::npos));
+
+                len -= it + 1;
+                pos += it + 1;
+
+                ++pit;
+        }
+        g_assert_cmpuint(len + 1, ==, 0);
+        g_assert_cmpuint(pos, ==, str.size() + 1);
+
+        pit = start;
+        for (auto it : {-2, -2, 3, 5, -2, -2, -2, -1}) {
+                int num;
+                bool v = pit.number(num);
+                if (it == -2)
+                        g_assert_false(v);
+                else
+                        g_assert_cmpint(it, ==, num);
+
+                ++pit;
+        }
+
+        /* Test range for */
+        for (auto it : tokeniser)
+                ;
+
+        /* Test different separator */
+        pit = start;
+        ++pit;
+
+        auto substr = *pit;
+        vte::parser::StringTokeniser subtokeniser{substr, ':'};
+
+        auto subpit = subtokeniser.cbegin();
+        for (auto it : {"1b"s, "17"s, "test"s, ""s, "b"s, ""s}) {
+                g_assert_true(it == *subpit);
+
+                ++subpit;
+        }
+        g_assert_true(subpit == subtokeniser.cend());
+
+        /* Test another string, one that doesn't end with an empty token */
+        std::string str2{"abc;defghi"s};
+        vte::parser::StringTokeniser tokeniser2{str2, ';'};
+
+        g_assert_cmpint(std::distance(tokeniser2.cbegin(), tokeniser2.cend()), ==, 2);
+        auto pit2 = tokeniser2.cbegin();
+        g_assert_true(*pit2 == "abc"s);
+        ++pit2;
+        g_assert_true(*pit2 == "defghi"s);
+        ++pit2;
+        g_assert_true(pit2 == tokeniser2.cend());
+
+        /* Test another string, one that starts with an empty token */
+        std::string str3{";abc"s};
+        vte::parser::StringTokeniser tokeniser3{str3, ';'};
+
+        g_assert_cmpint(std::distance(tokeniser3.cbegin(), tokeniser3.cend()), ==, 2);
+        auto pit3 = tokeniser3.cbegin();
+        g_assert_true(*pit3 == ""s);
+        ++pit3;
+        g_assert_true(*pit3 == "abc"s);
+        ++pit3;
+        g_assert_true(pit3 == tokeniser3.cend());
+
+        /* And try an empty string, which should split into one empty token */
+        std::string str4{""s};
+        vte::parser::StringTokeniser tokeniser4{str4, ';'};
+
+        g_assert_cmpint(std::distance(tokeniser4.cbegin(), tokeniser4.cend()), ==, 1);
+        auto pit4 = tokeniser4.cbegin();
+        g_assert_true(*pit4 == ""s);
+        ++pit4;
+        g_assert_true(pit4 == tokeniser4.cend());
+}
+
 int
 main(int argc,
      char* argv[])
@@ -1002,7 +1343,10 @@ main(int argc,
                 return 1;
 
         g_test_add_func("/vte/parser/sequences/arg", test_seq_arg);
-        g_test_add_func("/vte/parser/sequences/glue", test_seq_glue);
+        g_test_add_func("/vte/parser/sequences/string", test_seq_string);
+        g_test_add_func("/vte/parser/sequences/glue/arg", test_seq_glue_arg);
+        g_test_add_func("/vte/parser/sequences/glue/string", test_seq_glue_string);
+        g_test_add_func("/vte/parser/sequences/glue/string-tokeniser", test_seq_glue_string_tokeniser);
         g_test_add_func("/vte/parser/sequences/control", test_seq_control);
         g_test_add_func("/vte/parser/sequences/escape/invalid", test_seq_esc_invalid);
         g_test_add_func("/vte/parser/sequences/escape/charset/94", test_seq_esc_charset_94);
@@ -1015,6 +1359,7 @@ main(int argc,
         g_test_add_func("/vte/parser/sequences/escape/F[pes]", test_seq_esc_Fpes);
         g_test_add_func("/vte/parser/sequences/csi", test_seq_csi);
         g_test_add_func("/vte/parser/sequences/csi/parameters", test_seq_csi_param);
+        g_test_add_func("/vte/parser/sequences/osc", test_seq_osc);
 
         auto rv = g_test_run();
 
diff --git a/src/parser.cc b/src/parser.cc
index 6ddc1c4..e7cb741 100644
--- a/src/parser.cc
+++ b/src/parser.cc
@@ -43,11 +43,8 @@
  * detected sequences.
  */
 
-#define VTE_PARSER_ST_MAX (4096)
-
 struct vte_parser {
         struct vte_seq seq;
-        size_t st_alloc;
         unsigned int state;
 };
 
@@ -876,7 +873,7 @@ enum parser_action {
         ACTION_DCS_COLLECT,     /* collect DCS data */
         ACTION_DCS_CONSUME,     /* consume DCS terminator */
         ACTION_DCS_DISPATCH,    /* dispatch DCS sequence */
-        ACTION_OSC_START,       /* start of OSC data */
+        ACTION_OSC_START,        /* clear and clear string data */
         ACTION_OSC_COLLECT,     /* collect OSC data */
         ACTION_OSC_CONSUME,     /* consume OSC terminator */
         ACTION_OSC_DISPATCH,    /* dispatch OSC sequence */
@@ -897,12 +894,7 @@ int vte_parser_new(struct vte_parser **out)
         if (!parser)
                 return -ENOMEM;
 
-        parser->st_alloc = 64;
-        parser->seq.st = (char*)kzalloc(parser->st_alloc + 1, GFP_KERNEL);
-        if (!parser->seq.st) {
-                kfree(parser);
-                return -ENOMEM;
-        }
+        vte_seq_string_init(&parser->seq.arg_str);
 
         *out = parser;
         return 0;
@@ -919,7 +911,7 @@ struct vte_parser *vte_parser_free(struct vte_parser *parser)
         if (!parser)
                 return NULL;
 
-        kfree(parser->seq.st);
+        vte_seq_string_free(&parser->seq.arg_str);
         kfree(parser);
         return NULL;
 }
@@ -935,11 +927,12 @@ static inline void parser_clear(struct vte_parser *parser)
         parser->seq.n_args = 0;
         parser->seq.n_final_args = 0;
         /* FIXME: we only really need to clear 0..n_args+1 since all others have not been touched */
+        // FIXMEchpe: now that DEFAULT is all-zero, use memset here
         for (i = 0; i < VTE_PARSER_ARG_MAX; ++i)
                 parser->seq.args[i] = VTE_SEQ_ARG_INIT_DEFAULT;
 
-        parser->seq.n_st = 0;
-        parser->seq.st[0] = 0;
+        // FIXMEchpe not really needed here, just in parser_st_start
+        vte_seq_string_reset(&parser->seq.arg_str);
 }
 
 static int parser_ignore(struct vte_parser *parser, uint32_t raw)
@@ -1018,6 +1011,21 @@ static void parser_param(struct vte_parser *parser, uint32_t raw)
         }
 }
 
+static inline void parser_osc_start(struct vte_parser *parser)
+{
+        parser_clear(parser);
+}
+
+static void parser_collect_string(struct vte_parser *parser, uint32_t raw)
+{
+        /*
+         * Only characters from 0x20..0x7e and 0xa0..0x10ffff are allowed here.
+         * Our state-machine already verifies those restrictions.
+         */
+
+        vte_seq_string_push(&parser->seq.arg_str, raw);
+}
+
 static int parser_esc(struct vte_parser *parser, uint32_t raw)
 {
         parser->seq.type = VTE_SEQ_ESCAPE;
@@ -1053,6 +1061,21 @@ static int parser_csi(struct vte_parser *parser, uint32_t raw)
         return parser->seq.type;
 }
 
+static int parser_osc(struct vte_parser *parser, uint32_t raw)
+{
+        /* parser->seq is cleared during OSC_START state, thus there's no need
+         * to clear invalid fields here. */
+
+        vte_seq_string_finish(&parser->seq.arg_str);
+
+        parser->seq.type = VTE_SEQ_OSC;
+        parser->seq.command = VTE_CMD_OSC;
+        parser->seq.terminator = raw;
+        parser->seq.charset = VTE_CHARSET_NONE;
+
+        return parser->seq.type;
+}
+
 /* perform state transition and dispatch related actions */
 static int parser_transition(struct vte_parser *parser,
                              uint32_t raw,
@@ -1097,17 +1120,16 @@ static int parser_transition(struct vte_parser *parser,
                 /* not implemented */
                 return VTE_SEQ_NONE;
         case ACTION_OSC_START:
-                /* not implemented */
+                parser_osc_start(parser);
                 return VTE_SEQ_NONE;
         case ACTION_OSC_COLLECT:
-                /* not implemented */
+                parser_collect_string(parser, raw);
                 return VTE_SEQ_NONE;
         case ACTION_OSC_CONSUME:
                 /* not implemented */
                 return VTE_SEQ_NONE;
         case ACTION_OSC_DISPATCH:
-                /* not implemented */
-                return VTE_SEQ_NONE;
+                return parser_osc(parser, raw);
         default:
                 WARN(1, "invalid vte-parser action");
                 return VTE_SEQ_NONE;
@@ -1485,7 +1507,7 @@ int vte_parser_feed(struct vte_parser *parser,
                 break;
         case 0x9d:                /* OSC */
                 ret = parser_transition(parser, raw,
-                                        STATE_OSC_STRING, ACTION_CLEAR);
+                                        STATE_OSC_STRING, ACTION_OSC_START);
                 break;
         case 0x9b:                /* CSI */
                 ret = parser_transition(parser, raw,
diff --git a/src/parser.hh b/src/parser.hh
index 1a723af..751ee11 100644
--- a/src/parser.hh
+++ b/src/parser.hh
@@ -21,6 +21,7 @@
 #include <cstdio>
 
 #include "parser-arg.hh"
+#include "parser-string.hh"
 
 struct vte_parser;
 struct vte_seq;
@@ -133,6 +134,14 @@ enum {
 #undef _VTE_CHARSET_ALIAS
 };
 
+enum {
+#define _VTE_OSC(osc,value) VTE_OSC_##osc = value,
+#include "parser-osc.hh"
+#undef _VTE_OSC
+
+        VTE_OSC_N
+};
+
 struct vte_seq {
         unsigned int type;
         unsigned int command;
@@ -142,8 +151,7 @@ struct vte_seq {
         unsigned int n_args;
         unsigned int n_final_args;
         vte_seq_arg_t args[VTE_PARSER_ARG_MAX];
-        unsigned int n_st;
-        char *st;
+        vte_seq_string_t arg_str;
 };
 
 int vte_parser_new(struct vte_parser **out);
diff --git a/src/vte.cc b/src/vte.cc
index cf1c55c..e312ae3 100644
--- a/src/vte.cc
+++ b/src/vte.cc
@@ -3693,9 +3693,11 @@ skip_chunk:
 
                         break;
                 }
+
                 case VTE_SEQ_NONE:
                 case VTE_SEQ_IGNORE:
                         break;
+
                 default: {
                         switch (seq.command()) {
 #define _VTE_CMD(cmd)   case VTE_CMD_##cmd: cmd(seq); break;
@@ -8536,21 +8538,11 @@ VteTerminalPrivate::~VteTerminalPrivate()
 
        remove_update_timeout(this);
 
-       /* discard title updates */
-        g_free(m_window_title);
-        g_free(m_window_title_changed);
-       g_free(m_icon_title_changed);
-        g_free(m_current_directory_uri_changed);
-        g_free(m_current_directory_uri);
-        g_free(m_current_file_uri_changed);
-        g_free(m_current_file_uri);
-
         /* Word char exceptions */
         g_free(m_word_char_exceptions_string);
         g_free(m_word_char_exceptions);
 
        /* Free public-facing data. */
-       g_free(m_icon_title);
        if (m_vadjustment != NULL) {
                /* Disconnect our signal handlers from this object. */
                g_signal_handlers_disconnect_by_func(m_vadjustment,
@@ -10775,64 +10767,63 @@ VteTerminalPrivate::emit_pending_signals()
 {
        GObject *object = G_OBJECT(m_terminal);
         g_object_freeze_notify(object);
-        gboolean really_changed;
 
        emit_adjustment_changed();
 
        if (m_window_title_changed) {
-                really_changed = (g_strcmp0(m_window_title, m_window_title_changed) != 0);
-               g_free (m_window_title);
-               m_window_title = m_window_title_changed;
-               m_window_title_changed = NULL;
+                if (m_window_title != m_window_title_pending) {
+                        m_window_title.swap(m_window_title_pending);
 
-                if (really_changed) {
                         _vte_debug_print(VTE_DEBUG_SIGNALS,
                                          "Emitting `window-title-changed'.\n");
                         g_signal_emit(object, signals[SIGNAL_WINDOW_TITLE_CHANGED], 0);
                         g_object_notify_by_pspec(object, pspecs[PROP_WINDOW_TITLE]);
                 }
+
+                m_window_title_pending.clear();
+                m_window_title_changed = false;
        }
 
        if (m_icon_title_changed) {
-                really_changed = (g_strcmp0(m_icon_title, m_icon_title_changed) != 0);
-               g_free (m_icon_title);
-               m_icon_title = m_icon_title_changed;
-               m_icon_title_changed = NULL;
+                if (m_icon_title != m_icon_title_pending) {
+                        m_icon_title.swap(m_icon_title_pending);
 
-                if (really_changed) {
                         _vte_debug_print(VTE_DEBUG_SIGNALS,
                                          "Emitting `icon-title-changed'.\n");
                         g_signal_emit(object, signals[SIGNAL_ICON_TITLE_CHANGED], 0);
                         g_object_notify_by_pspec(object, pspecs[PROP_ICON_TITLE]);
                 }
+
+               m_icon_title_pending.clear();
+               m_icon_title_changed = false;
        }
 
        if (m_current_directory_uri_changed) {
-                really_changed = (g_strcmp0(m_current_directory_uri, m_current_directory_uri_changed) != 0);
-                g_free (m_current_directory_uri);
-                m_current_directory_uri = m_current_directory_uri_changed;
-                m_current_directory_uri_changed = NULL;
+                if (m_current_directory_uri != m_current_directory_uri_pending) {
+                        m_current_directory_uri.swap(m_current_directory_uri_pending);
 
-                if (really_changed) {
                         _vte_debug_print(VTE_DEBUG_SIGNALS,
                                          "Emitting `current-directory-uri-changed'.\n");
                         g_signal_emit(object, signals[SIGNAL_CURRENT_DIRECTORY_URI_CHANGED], 0);
                         g_object_notify_by_pspec(object, pspecs[PROP_CURRENT_DIRECTORY_URI]);
                 }
+
+                m_current_directory_uri_pending.clear();
+                m_current_directory_uri_changed = false;
         }
 
         if (m_current_file_uri_changed) {
-                really_changed = (g_strcmp0(m_current_file_uri, m_current_file_uri_changed) != 0);
-                g_free (m_current_file_uri);
-                m_current_file_uri = m_current_file_uri_changed;
-                m_current_file_uri_changed = NULL;
+                if (m_current_file_uri != m_current_file_uri_pending) {
+                        m_current_file_uri.swap(m_current_file_uri_pending);
 
-                if (really_changed) {
                         _vte_debug_print(VTE_DEBUG_SIGNALS,
                                          "Emitting `current-file-uri-changed'.\n");
                         g_signal_emit(object, signals[SIGNAL_CURRENT_FILE_URI_CHANGED], 0);
                         g_object_notify_by_pspec(object, pspecs[PROP_CURRENT_FILE_URI]);
                 }
+
+                m_current_file_uri_pending.clear();
+                m_current_file_uri_changed = false;
         }
 
        /* Flush any pending "inserted" signals. */
diff --git a/src/vtegtk.cc b/src/vtegtk.cc
index 516e042..57e259d 100644
--- a/src/vtegtk.cc
+++ b/src/vtegtk.cc
@@ -3456,7 +3456,8 @@ const char *
 vte_terminal_get_current_directory_uri(VteTerminal *terminal)
 {
         g_return_val_if_fail(VTE_IS_TERMINAL(terminal), NULL);
-        return IMPL(terminal)->m_current_directory_uri;
+        auto impl = IMPL(terminal);
+        return impl->m_current_directory_uri.size() ? impl->m_current_directory_uri.data() : nullptr;
 }
 
 /**
@@ -3471,7 +3472,8 @@ const char *
 vte_terminal_get_current_file_uri(VteTerminal *terminal)
 {
         g_return_val_if_fail(VTE_IS_TERMINAL(terminal), NULL);
-        return IMPL(terminal)->m_current_file_uri;
+        auto impl = IMPL(terminal);
+        return impl->m_current_file_uri.size() ? impl->m_current_file_uri.data() : nullptr;
 }
 
 /**
@@ -3886,7 +3888,7 @@ const char *
 vte_terminal_get_icon_title(VteTerminal *terminal)
 {
        g_return_val_if_fail(VTE_IS_TERMINAL(terminal), "");
-       return IMPL(terminal)->m_icon_title;
+       return IMPL(terminal)->m_icon_title.data();
 }
 
 /**
@@ -4172,7 +4174,7 @@ const char *
 vte_terminal_get_window_title(VteTerminal *terminal)
 {
        g_return_val_if_fail(VTE_IS_TERMINAL(terminal), "");
-       return IMPL(terminal)->m_window_title;
+       return IMPL(terminal)->m_window_title.data();
 }
 
 /**
diff --git a/src/vteinternal.hh b/src/vteinternal.hh
index bf38fbd..ea53321 100644
--- a/src/vteinternal.hh
+++ b/src/vteinternal.hh
@@ -493,15 +493,18 @@ public:
         gboolean m_cursor_moved_pending;
         gboolean m_contents_changed_pending;
 
-       /* window name changes */
-        char* m_window_title;
-        char* m_window_title_changed;
-        char* m_icon_title;
-        char* m_icon_title_changed;
-        char* m_current_directory_uri;
-        char* m_current_directory_uri_changed;
-        char* m_current_file_uri;
-        char* m_current_file_uri_changed;
+        std::string m_window_title{};
+        std::string m_icon_title{};
+        std::string m_current_directory_uri{};
+        std::string m_current_file_uri{};
+        std::string m_window_title_pending{};
+        std::string m_icon_title_pending{};
+        std::string m_current_directory_uri_pending{};
+        std::string m_current_file_uri_pending{};
+        bool m_icon_title_changed{false};
+        bool m_window_title_changed{false};
+        bool m_current_directory_uri_changed{false};
+        bool m_current_file_uri_changed{false};
 
        /* Background */
         double m_background_alpha;
@@ -1132,10 +1135,6 @@ public:
                                   GCancellable *cancellable,
                                   GError **error);
 
-        /* Sequence handlers and their helper functions */
-        void handle_sequence(char const* match,
-                             vte::parser::Params const& params);
-
         inline void ensure_cursor_is_onscreen();
         inline void home_cursor();
         inline void clear_screen();
@@ -1147,9 +1146,6 @@ public:
         inline void switch_alternate_screen();
         inline void save_cursor();
         inline void restore_cursor();
-        void set_title_internal(vte::parser::Params const& params,
-                                bool icon_title,
-                                bool window_title);
 
         inline void set_mode_ecma(vte::parser::Sequence const& seq,
                                   bool set) noexcept;
@@ -1191,19 +1187,11 @@ 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 change_color(vte::parser::Params const& params,
-                                 char const* terminator);
         inline void line_feed();
-        inline void set_current_hyperlink(char* hyperlink_params /* adopted */, char* uri /* adopted */);
         inline void erase_in_display(vte::parser::Sequence const& seq);
         inline void erase_in_line(vte::parser::Sequence const& seq);
         inline void insert_lines(vte::grid::row_t param);
         inline void delete_lines(vte::grid::row_t param);
-        inline void change_special_color(vte::parser::Params const& params,
-                                         int index,
-                                         int index_fallback,
-                                         int osc,
-                                         char const *terminator);
 
         void subscribe_accessible_events();
         void select_text(vte::grid::column_t start_col,
@@ -1213,11 +1201,34 @@ public:
         void select_empty(vte::grid::column_t col,
                           vte::grid::row_t row);
 
+        /* OSC handlers */
+        void set_color(vte::parser::Sequence const& seq,
+                       vte::parser::StringTokeniser::const_iterator& token,
+                       vte::parser::StringTokeniser::const_iterator const& endtoken) noexcept;
+        void set_special_color(vte::parser::Sequence const& seq,
+                               vte::parser::StringTokeniser::const_iterator& token,
+                               vte::parser::StringTokeniser::const_iterator const& endtoken,
+                               int index,
+                               int index_fallback,
+                               int osc) noexcept;
+        void reset_color(vte::parser::Sequence const& seq,
+                         vte::parser::StringTokeniser::const_iterator& token,
+                         vte::parser::StringTokeniser::const_iterator const& endtoken) noexcept;
+        void set_current_directory_uri(vte::parser::Sequence const& seq,
+                                       vte::parser::StringTokeniser::const_iterator& token,
+                                       vte::parser::StringTokeniser::const_iterator const& endtoken) 
noexcept;
+        void set_current_file_uri(vte::parser::Sequence const& seq,
+                                  vte::parser::StringTokeniser::const_iterator& token,
+                                  vte::parser::StringTokeniser::const_iterator const& endtoken) noexcept;
+        void set_current_hyperlink(vte::parser::Sequence const& seq,
+                                   vte::parser::StringTokeniser::const_iterator& token,
+                                   vte::parser::StringTokeniser::const_iterator const& endtoken) noexcept;
+
         /* Sequence handlers */
 
         /* old style */
-#define SEQUENCE_HANDLER(name)                                          \
-      inline void seq_ ## name (vte::parser::Params const& params);
+#define SEQUENCE_HANDLER(name) \
+        inline void seq_ ## name (vte::parser::Sequence const& seq);
 #include "vteseq-list.hh"
 #undef SEQUENCE_HANDLER
 
diff --git a/src/vteseq.cc b/src/vteseq.cc
index 692f560..8299974 100644
--- a/src/vteseq.cc
+++ b/src/vteseq.cc
@@ -33,7 +33,6 @@
 #include <vte/vte.h>
 #include "vteinternal.hh"
 #include "vtegtk.hh"
-#include "vteutils.h"  /* for strchrnul on non-GNU systems */
 #include "caps.hh"
 #include "debug.h"
 
@@ -42,6 +41,8 @@
 
 #include <algorithm>
 
+using namespace std::literals;
+
 void
 vte::parser::Sequence::print() const
 {
@@ -60,6 +61,11 @@ vte::parser::Sequence::print() const
                 }
                 g_printerr(" ]");
         }
+        if (m_seq->type == VTE_SEQ_OSC) {
+                char* str = string_param();
+                g_printerr(" \"%s\"", str);
+                g_free(str);
+        }
         g_printerr("\n");
 #endif
 }
@@ -102,6 +108,24 @@ vte::parser::Sequence::command_string() const
         }
 }
 
+// FIXMEchpe optimise this
+std::string
+vte::parser::Sequence::string_utf8() const noexcept
+{
+        std::string str;
+
+        size_t len;
+        auto buf = vte_seq_string_get(&m_seq->arg_str, &len);
+
+        char u[6];
+        for (size_t i = 0; i < len; ++i) {
+                auto ulen = g_unichar_to_utf8(buf[i], u);
+                str.append((char const*)u, ulen);
+        }
+
+        return str;
+}
+
 /* A couple are duplicated from vte.c, to keep them static... */
 
 /* Check how long a string of unichars is.  Slow version. */
@@ -118,9 +142,11 @@ vte_unichar_strlen(gunichar const* c)
  * length instead of walking the input twice.
  */
 char*
-vte::parser::Sequence::ucs4_to_utf8(gunichar const* str) const
+vte::parser::Sequence::ucs4_to_utf8(gunichar const* str,
+                                    ssize_t len) const
 {
-        auto len = vte_unichar_strlen(str);
+        if (len < 0)
+                len = vte_unichar_strlen(str);
         auto outlen = (len * VTE_UTF8_BPC) + 1;
 
         auto result = (char*)g_try_malloc(outlen);
@@ -410,51 +436,6 @@ VteTerminalPrivate::switch_alternate_screen()
         switch_screen(&m_alternate_screen);
 }
 
-/* Set icon/window titles. */
-void
-VteTerminalPrivate::set_title_internal(vte::parser::Params const& params,
-                                       bool change_icon_title,
-                                       bool change_window_title)
-{
-        if (change_icon_title == FALSE && change_window_title == FALSE)
-               return;
-
-       /* Get the string parameter's value. */
-        char* title;
-        if (!params.string_at(0, title))
-                return;
-
-                       char *p, *validated;
-                       const char *end;
-
-                        //FIXMEchpe why? it's guaranteed UTF-8 already
-                       /* Validate the text. */
-                       g_utf8_validate(title, strlen(title), &end);
-                       validated = g_strndup(title, end - title);
-
-                       /* No control characters allowed. */
-                       for (p = validated; *p != '\0'; p++) {
-                               if ((*p & 0x1f) == *p) {
-                                       *p = ' ';
-                               }
-                       }
-
-                       /* Emit the signal */
-                        if (change_window_title) {
-                                g_free(m_window_title_changed);
-                                m_window_title_changed = g_strdup(validated);
-                       }
-
-                        if (change_icon_title) {
-                                g_free(m_icon_title_changed);
-                                m_icon_title_changed = g_strdup(validated);
-                       }
-
-                       g_free (validated);
-
-        g_free(title);
-}
-
 void
 VteTerminalPrivate::set_mode_ecma(vte::parser::Sequence const& seq,
                                   bool set) noexcept
@@ -1020,90 +1001,6 @@ VteTerminalPrivate::move_cursor_forward(vte::grid::column_t columns)
        }
 }
 
-/* Internal helper for changing color in the palette */
-void
-VteTerminalPrivate::change_color(vte::parser::Params const& params,
-                                 const char *terminator)
-{
-        char **pairs;
-        {
-                char* str;
-                if (!params.string_at(0, str))
-                        return;
-
-               pairs = g_strsplit (str, ";", 0);
-                g_free(str);
-        }
-
-        if (!pairs)
-                return;
-
-        vte::color::rgb color;
-        guint idx, i;
-
-               for (i = 0; pairs[i] && pairs[i + 1]; i += 2) {
-                       idx = strtoul (pairs[i], (char **) NULL, 10);
-
-                       if (idx >= VTE_DEFAULT_FG)
-                               continue;
-
-                       if (color.parse(pairs[i + 1])) {
-                                set_color(idx, VTE_COLOR_SOURCE_ESCAPE, color);
-                       } else if (strcmp (pairs[i + 1], "?") == 0) {
-                               gchar buf[128];
-                               auto c = get_color(idx);
-                               g_assert(c != NULL);
-                               g_snprintf (buf, sizeof (buf),
-                                           _VTE_CAP_OSC "4;%u;rgb:%04x/%04x/%04x%s",
-                                           idx, c->red, c->green, c->blue, terminator);
-                               feed_child(buf, -1);
-                       }
-               }
-
-               g_strfreev (pairs);
-
-               /* emit the refresh as the palette has changed and previous
-                * renders need to be updated. */
-               emit_refresh_window();
-}
-
-/* Change color in the palette, BEL terminated */
-void
-VteTerminalPrivate::seq_change_color_bel(vte::parser::Params const& params)
-{
-       change_color(params, BEL_C0);
-}
-
-/* Change color in the palette, ST_C0 terminated */
-void
-VteTerminalPrivate::seq_change_color_st(vte::parser::Params const& params)
-{
-       change_color(params, ST_C0);
-}
-
-/* Reset color in the palette */
-void
-VteTerminalPrivate::seq_reset_color(vte::parser::Params const& params)
-{
-        auto n_params = params.size();
-        if (n_params) {
-                for (unsigned int i = 0; i < n_params; i++) {
-                        int value;
-                        if (!params.number_at_unchecked(i, value))
-                                continue;
-
-                        if (value < 0 || value >= VTE_DEFAULT_FG)
-                                continue;
-
-                        reset_color(value, VTE_COLOR_SOURCE_ESCAPE);
-                }
-       } else {
-               for (unsigned int idx = 0; idx < VTE_DEFAULT_FG; idx++) {
-                       reset_color(idx, VTE_COLOR_SOURCE_ESCAPE);
-               }
-       }
-}
-
 void
 VteTerminalPrivate::line_feed()
 {
@@ -1305,161 +1202,6 @@ VteTerminalPrivate::seq_parse_sgr_color(vte::parser::Sequence const& seq,
         return false;
 }
 
-/* Set one or the other. */
-void
-VteTerminalPrivate::seq_set_icon_title(vte::parser::Params const& params)
-{
-       set_title_internal(params, true, false);
-}
-
-void
-VteTerminalPrivate::seq_set_window_title(vte::parser::Params const& params)
-{
-       set_title_internal(params, false, true);
-}
-
-/* Set both the window and icon titles to the same string. */
-void
-VteTerminalPrivate::seq_set_icon_and_window_title(vte::parser::Params const& params)
-{
-       set_title_internal(params, true, true);
-}
-
-void
-VteTerminalPrivate::seq_set_current_directory_uri(vte::parser::Params const& params)
-{
-        char* uri = nullptr;
-        if (params.string_at(0, uri)) {
-                /* Validate URI */
-                if (uri[0]) {
-                        auto filename = g_filename_from_uri (uri, nullptr, nullptr);
-                        if (filename == nullptr) {
-                                /* invalid URI */
-                                g_free (uri);
-                                uri = nullptr;
-                        } else {
-                                g_free (filename);
-                        }
-                } else {
-                        g_free(uri);
-                        uri = nullptr;
-                }
-        }
-
-        g_free(m_current_directory_uri_changed);
-        m_current_directory_uri_changed = uri /* adopt */;
-}
-
-void
-VteTerminalPrivate::seq_set_current_file_uri(vte::parser::Params const& params)
-{
-        char* uri = nullptr;
-        if (params.string_at(0, uri)) {
-                /* Validate URI */
-                if (uri[0]) {
-                        auto filename = g_filename_from_uri (uri, nullptr, nullptr);
-                        if (filename == nullptr) {
-                                /* invalid URI */
-                                g_free (uri);
-                                uri = nullptr;
-                        } else {
-                                g_free (filename);
-                        }
-                } else {
-                        g_free(uri);
-                        uri = nullptr;
-                }
-        }
-
-        g_free(m_current_file_uri_changed);
-        m_current_file_uri_changed = uri /* adopt */;
-}
-
-/* Handle OSC 8 hyperlinks.
- * See bug 779734 and https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda. */
-void
-VteTerminalPrivate::seq_set_current_hyperlink(vte::parser::Params const& params)
-{
-
-        char* hyperlink_params = nullptr;
-        char* uri = nullptr;
-        if (params.size() >= 2) {
-                params.string_at_unchecked(0, hyperlink_params);
-                params.string_at_unchecked(1, uri);
-        }
-
-        set_current_hyperlink(hyperlink_params, uri);
-}
-
-void
-VteTerminalPrivate::set_current_hyperlink(char *hyperlink_params /* adopted */,
-                                          char* uri /* adopted */)
-{
-        guint idx;
-        char *id = NULL;
-        char idbuf[24];
-
-        if (!m_allow_hyperlink)
-                return;
-
-        /* Get the "id" parameter */
-        if (hyperlink_params) {
-                if (strncmp(hyperlink_params, "id=", 3) == 0) {
-                        id = hyperlink_params + 3;
-                } else {
-                        id = strstr(hyperlink_params, ":id=");
-                        if (id)
-                                id += 4;
-                }
-        }
-        if (id) {
-                *strchrnul(id, ':') = '\0';
-        }
-        _vte_debug_print (VTE_DEBUG_HYPERLINK,
-                          "OSC 8: id=\"%s\" uri=\"%s\"\n",
-                          id, uri);
-
-        if (uri && strlen(uri) > VTE_HYPERLINK_URI_LENGTH_MAX) {
-                _vte_debug_print (VTE_DEBUG_HYPERLINK,
-                                  "Overlong URI ignored: \"%s\"\n",
-                                  uri);
-                uri[0] = '\0';
-        }
-
-        if (id && strlen(id) > VTE_HYPERLINK_ID_LENGTH_MAX) {
-                _vte_debug_print (VTE_DEBUG_HYPERLINK,
-                                  "Overlong \"id\" ignored: \"%s\"\n",
-                                  id);
-                id[0] = '\0';
-        }
-
-        if (uri && uri[0]) {
-                /* The hyperlink, as we carry around and store in the streams, is "id;uri" */
-                char *hyperlink;
-
-                if (!id || !id[0]) {
-                        /* Automatically generate a unique ID string. The colon makes sure
-                         * it cannot conflict with an explicitly specified one. */
-                        sprintf(idbuf, ":%ld", m_hyperlink_auto_id++);
-                        id = idbuf;
-                        _vte_debug_print (VTE_DEBUG_HYPERLINK,
-                                          "Autogenerated id=\"%s\"\n",
-                                          id);
-                }
-                hyperlink = g_strdup_printf("%s;%s", id, uri);
-                idx = _vte_ring_get_hyperlink_idx(m_screen->row_data, hyperlink);
-                g_free (hyperlink);
-        } else {
-                /* idx = 0; also remove the previous current_idx so that it can be GC'd now. */
-                idx = _vte_ring_get_hyperlink_idx(m_screen->row_data, NULL);
-        }
-
-        m_defaults.attr.hyperlink_idx = idx;
-
-        g_free(hyperlink_params);
-        g_free(uri);
-}
-
 void
 VteTerminalPrivate::erase_in_display(vte::parser::Sequence const& seq)
 {
@@ -1467,7 +1209,7 @@ VteTerminalPrivate::erase_in_display(vte::parser::Sequence const& seq)
          * bool selective = (seq.command() == VTE_CMD_DECSED);
          */
 
-       switch (seq[0]) {
+        switch (seq.collect1(0)) {
         case -1: /* default */
        case 0:
                /* Clear below the current line. */
@@ -1502,7 +1244,7 @@ VteTerminalPrivate::erase_in_line(vte::parser::Sequence const& seq)
          * bool selective = (seq.command() == VTE_CMD_DECSEL);
          */
 
-       switch (seq[0]) {
+        switch (seq.collect1(0)) {
         case -1: /* default */
        case 0:
                /* Clear to end of the line. */
@@ -1592,166 +1334,244 @@ VteTerminalPrivate::delete_lines(vte::grid::row_t param)
         m_text_deleted_flag = TRUE;
 }
 
-/* Internal helper for setting/querying special colors */
 void
-VteTerminalPrivate::change_special_color(vte::parser::Params const& params,
-                                         int index,
-                                         int index_fallback,
-                                         int osc,
-                                         const char *terminator)
+VteTerminalPrivate::set_color(vte::parser::Sequence const& seq,
+                              vte::parser::StringTokeniser::const_iterator& token,
+                              vte::parser::StringTokeniser::const_iterator const& endtoken) noexcept
 {
-        char* name;
-        if (!params.string_at(0, name))
-                return;
+        bool any_changed = false;
 
-        vte::color::rgb color;
-
-               if (color.parse(name))
-                       set_color(index, VTE_COLOR_SOURCE_ESCAPE, color);
-               else if (strcmp (name, "?") == 0) {
-                       gchar buf[128];
-                       auto c = get_color(index);
-                       if (c == NULL && index_fallback != -1)
-                               c = get_color(index_fallback);
-                       g_assert(c != NULL);
-                       g_snprintf (buf, sizeof (buf),
-                                   _VTE_CAP_OSC "%d;rgb:%04x/%04x/%04x%s",
-                                   osc, c->red, c->green, c->blue, terminator);
-                       feed_child(buf, -1);
-               }
-}
+        while (token != endtoken) {
+                int value;
+                bool has_value = token.number(value);
 
-/* Change the default foreground cursor, BEL terminated */
-void
-VteTerminalPrivate::seq_change_foreground_color_bel(vte::parser::Params const& params)
-{
-        change_special_color(params, VTE_DEFAULT_FG, -1, 10, BEL_C0);
-}
+                if (++token == endtoken)
+                        break;
 
-/* Change the default foreground cursor, ST_C0 terminated */
-void
-VteTerminalPrivate::seq_change_foreground_color_st(vte::parser::Params const& params)
-{
-        change_special_color(params, VTE_DEFAULT_FG, -1, 10, ST_C0);
-}
+                if (!has_value ||
+                    value < 0 ||
+                    value >= VTE_DEFAULT_FG) {
+                        ++token;
+                        continue;
+                }
 
-/* Reset the default foreground color */
-void
-VteTerminalPrivate::seq_reset_foreground_color(vte::parser::Params const& params)
-{
-        reset_color(VTE_DEFAULT_FG, VTE_COLOR_SOURCE_ESCAPE);
-}
+                auto const str = *token;
+
+                if (str == "?"s) {
+                        gchar buf[128];
+                        auto c = get_color(value);
+                        g_assert_nonnull(c);
+                        g_snprintf (buf, sizeof (buf),
+                                    _VTE_CAP_OSC "4;%u;rgb:%04x/%04x/%04x%s",
+                                    value, c->red, c->green, c->blue,
+                                    seq.terminator() == 0x7 ? BEL_C0 : ST_C0);
+                        feed_child(buf, -1);
+                } else {
+                        vte::color::rgb color;
+                        if (color.parse(str.data())) {
+                                set_color(value, VTE_COLOR_SOURCE_ESCAPE, color);
+                                any_changed = true;
+                        }
+                }
 
-/* Change the default background cursor, BEL terminated */
-void
-VteTerminalPrivate::seq_change_background_color_bel(vte::parser::Params const& params)
-{
-        change_special_color(params, VTE_DEFAULT_BG, -1, 11, BEL_C0);
-}
+                ++token;
+        }
 
-/* Change the default background cursor, ST_C0 terminated */
-void
-VteTerminalPrivate::seq_change_background_color_st(vte::parser::Params const& params)
-{
-        change_special_color(params, VTE_DEFAULT_BG, -1, 11, ST_C0);
+        /* emit the refresh as the palette has changed and previous
+         * renders need to be updated. */
+        if (any_changed)
+                emit_refresh_window();
 }
 
-/* Reset the default background color */
 void
-VteTerminalPrivate::seq_reset_background_color(vte::parser::Params const& params)
+VteTerminalPrivate::set_special_color(vte::parser::Sequence const& seq,
+                                      vte::parser::StringTokeniser::const_iterator& token,
+                                      vte::parser::StringTokeniser::const_iterator const& endtoken,
+                                      int index,
+                                      int index_fallback,
+                                      int osc)
 {
-        reset_color(VTE_DEFAULT_BG, VTE_COLOR_SOURCE_ESCAPE);
-}
+        if (token == endtoken)
+                return;
 
-/* Change the color of the cursor background, BEL terminated */
-void
-VteTerminalPrivate::seq_change_cursor_background_color_bel(vte::parser::Params const& params)
-{
-        change_special_color(params, VTE_CURSOR_BG, VTE_DEFAULT_FG, 12, BEL_C0);
-}
+        auto const str = *token;
+        if (str == "?"s) {
+                gchar buf[128];
+                auto c = get_color(index);
+                if (c == nullptr && index_fallback != -1)
+                        c = get_color(index_fallback);
+                g_assert_nonnull(c);
+                auto len = g_snprintf (buf, sizeof (buf),
+                                       _VTE_CAP_OSC "%d;rgb:%04x/%04x/%04x%s",
+                                       osc,
+                                       c->red, c->green, c->blue,
+                                       ST_C0);//seq.terminator() == 7 ? BEL_C0 : ST_C0);
+                feed_child(buf, len);
+        } else {
+                vte::color::rgb color;
+                if (color.parse(str.data())) {
+                        set_color(index, VTE_COLOR_SOURCE_ESCAPE, color);
 
-/* Change the color of the cursor background, ST_C0 terminated */
-void
-VteTerminalPrivate::seq_change_cursor_background_color_st(vte::parser::Params const& params)
-{
-        change_special_color(params, VTE_CURSOR_BG, VTE_DEFAULT_FG, 12, ST_C0);
+                        /* emit the refresh as the palette has changed and previous
+                         * renders need to be updated. */
+                        emit_refresh_window();
+                }
+        }
 }
 
-/* Reset the color of the cursor */
 void
-VteTerminalPrivate::seq_reset_cursor_background_color(vte::parser::Params const& params)
+VteTerminalPrivate::reset_color(vte::parser::Sequence const& seq,
+                                vte::parser::StringTokeniser::const_iterator& token,
+                                vte::parser::StringTokeniser::const_iterator const& endtoken) noexcept
 {
-        reset_color(VTE_CURSOR_BG, VTE_COLOR_SOURCE_ESCAPE);
-}
+        /* Empty param? Reset all */
+        if (token == endtoken ||
+            token.size_remaining() == 0) {
+                for (unsigned int idx = 0; idx < VTE_DEFAULT_FG; idx++)
+                        reset_color(idx, VTE_COLOR_SOURCE_ESCAPE);
 
-/* Change the highlight background color, BEL terminated */
-void
-VteTerminalPrivate::seq_change_highlight_background_color_bel(vte::parser::Params const& params)
-{
-        change_special_color(params, VTE_HIGHLIGHT_BG, VTE_DEFAULT_FG, 17, BEL_C0);
-}
+                /* emit the refresh as the palette has changed and previous
+                 * renders need to be updated. */
+                emit_refresh_window();
+                return;
+        }
 
-/* Change the highlight background color, ST_C0 terminated */
-void
-VteTerminalPrivate::seq_change_highlight_background_color_st(vte::parser::Params const& params)
-{
-        change_special_color(params, VTE_HIGHLIGHT_BG, VTE_DEFAULT_FG, 17, ST_C0);
-}
+        bool any_changed = false;
 
-/* Reset the highlight background color */
-void
-VteTerminalPrivate::seq_reset_highlight_background_color(vte::parser::Params const& params)
-{
-        reset_color(VTE_HIGHLIGHT_BG, VTE_COLOR_SOURCE_ESCAPE);
-}
+        while (token != endtoken) {
+                int value;
+                if (!token.number(value))
+                        continue;
 
-/* Change the highlight foreground color, BEL terminated */
-void
-VteTerminalPrivate::seq_change_highlight_foreground_color_bel(vte::parser::Params const& params)
-{
-        change_special_color(params, VTE_HIGHLIGHT_FG, VTE_DEFAULT_BG, 19, BEL_C0);
-}
+                if (0 <= value && value < VTE_DEFAULT_FG) {
+                        reset_color(value, VTE_COLOR_SOURCE_ESCAPE);
+                        any_changed = true;
+                }
 
-/* Change the highlight foreground color, ST_C0 terminated */
-void
-VteTerminalPrivate::seq_change_highlight_foreground_color_st(vte::parser::Params const& params)
-{
-        change_special_color(params, VTE_HIGHLIGHT_FG, VTE_DEFAULT_BG, 19, ST_C0);
+                ++token;
+        }
+
+        /* emit the refresh as the palette has changed and previous
+         * renders need to be updated. */
+        if (any_changed)
+                emit_refresh_window();
 }
 
-/* Reset the highlight foreground color */
 void
-VteTerminalPrivate::seq_reset_highlight_foreground_color(vte::parser::Params const& params)
+VteTerminalPrivate::set_current_directory_uri(vte::parser::Sequence const& seq,
+                                              vte::parser::StringTokeniser::const_iterator& token,
+                                              vte::parser::StringTokeniser::const_iterator const& endtoken) 
noexcept
 {
-        reset_color(VTE_HIGHLIGHT_FG, VTE_COLOR_SOURCE_ESCAPE);
-}
+        std::string uri;
+        if (token != endtoken && token.size_remaining() > 0) {
+                uri = token.string_remaining();
 
-/* URXVT generic OSC 777 */
+                auto filename = g_filename_from_uri(uri.data(), nullptr, nullptr);
+                if (filename != nullptr) {
+                        g_free(filename);
+                } else {
+                        /* invalid URI */
+                        uri.clear();
+                }
+        }
 
-void
-VteTerminalPrivate::seq_urxvt_777(vte::parser::Params const& params)
-{
-        /* Accept but ignore this for compatibility with downstream-patched vte (bug #711059)*/
+        m_current_directory_uri_pending.swap(uri);
+        m_current_directory_uri_changed = true;
 }
 
-/* iterm2 OSC 133 & 1337 */
-
 void
-VteTerminalPrivate::seq_iterm2_133(vte::parser::Params const& params)
+VteTerminalPrivate::set_current_file_uri(vte::parser::Sequence const& seq,
+                                         vte::parser::StringTokeniser::const_iterator& token,
+                                         vte::parser::StringTokeniser::const_iterator const& endtoken) 
noexcept
+
 {
-        /* Accept but ignore this for compatibility when sshing to an osx host
-         * where the iterm2 integration is loaded even when not actually using
-         * iterm2.
-         */
+        std::string uri;
+        if (token != endtoken && token.size_remaining() > 0) {
+                uri = token.string_remaining();
+
+                auto filename = g_filename_from_uri(uri.data(), nullptr, nullptr);
+                if (filename != nullptr) {
+                        g_free(filename);
+                } else {
+                        /* invalid URI */
+                        uri.clear();
+                }
+        }
+
+        m_current_file_uri_pending.swap(uri);
+        m_current_file_uri_changed = true;
 }
 
 void
-VteTerminalPrivate::seq_iterm2_1337(vte::parser::Params const& params)
+VteTerminalPrivate::set_current_hyperlink(vte::parser::Sequence const& seq,
+                                          vte::parser::StringTokeniser::const_iterator& token,
+                                          vte::parser::StringTokeniser::const_iterator const& endtoken) 
noexcept
 {
-        /* Accept but ignore this for compatibility when sshing to an osx host
-         * where the iterm2 integration is loaded even when not actually using
-         * iterm2.
+        if (token == endtoken)
+                return; // FIXMEchpe or should we treat this as a reset?
+
+        /* Handle OSC 8 hyperlinks.
+         * See bug 779734 and https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
          */
+
+        if (!m_allow_hyperlink)
+                return;
+
+        /* The hyperlink, as we carry around and store in the streams, is "id;uri" */
+        std::string hyperlink;
+
+        /* First, find the ID */
+        vte::parser::StringTokeniser subtokeniser{*token, ':'};
+        for (auto subtoken : subtokeniser) {
+                auto const len = subtoken.size();
+                if (len < 3)
+                        continue;
+
+                if (subtoken[0] != 'i' || subtoken[1] != 'd' || subtoken[2] != '=')
+                        continue;
+
+                if (len > 3 + VTE_HYPERLINK_ID_LENGTH_MAX) {
+                        _vte_debug_print (VTE_DEBUG_HYPERLINK, "Overlong \"id\" ignored: \"%s\"\n",
+                                          subtoken.data());
+                        break;
+                }
+
+                hyperlink = subtoken.substr(3);
+                break;
+        }
+
+        if (hyperlink.size() == 0) {
+                /* Automatically generate a unique ID string. The colon makes sure
+                 * it cannot conflict with an explicitly specified one.
+                 */
+                char idbuf[24];
+                auto len = g_snprintf(idbuf, sizeof(idbuf), ":%ld", m_hyperlink_auto_id++);
+                hyperlink.append(idbuf, len);
+                _vte_debug_print (VTE_DEBUG_HYPERLINK, "Autogenerated id=\"%s\"\n", hyperlink.data());
+        }
+
+        /* Now get the URI */
+        if (++token == endtoken)
+                return; // FIXMEchpe or should we treat this the same as 0-length URI ?
+
+        hyperlink.push_back(';');
+        guint idx;
+        auto const len = token.size_remaining();
+        if (len > 0 && len <= VTE_HYPERLINK_URI_LENGTH_MAX) {
+                token.append_remaining(hyperlink);
+
+                _vte_debug_print (VTE_DEBUG_HYPERLINK, "OSC 8: id;uri=\"%s\"\n", hyperlink.data());
+
+                idx = _vte_ring_get_hyperlink_idx(m_screen->row_data, hyperlink.data());
+        } else {
+                if (G_UNLIKELY(len > VTE_HYPERLINK_URI_LENGTH_MAX))
+                        _vte_debug_print (VTE_DEBUG_HYPERLINK, "Overlong URI ignored (len %" G_GSIZE_FORMAT 
")\n", len);
+
+                /* idx = 0; also remove the previous current_idx so that it can be GC'd now. */
+                idx = _vte_ring_get_hyperlink_idx(m_screen->row_data, nullptr);
+        }
+
+        m_defaults.attr.hyperlink_idx = idx;
 }
 
 /*
@@ -4353,6 +4173,172 @@ VteTerminalPrivate::NUL(vte::parser::Sequence const& seq)
 }
 
 void
+VteTerminalPrivate::OSC(vte::parser::Sequence const& seq)
+{
+        /*
+         * OSC - operating system command
+         *
+         * References: ECMA-48 § 8.3.89
+         *             XTERM
+         */
+
+        /* Our OSC have the format
+         *   OSC number ; rest of string ST
+         * where the rest of the string may or may not contain more semicolons.
+         *
+         * First, extract the number.
+         */
+
+        auto const str = seq.string_utf8();
+        vte::parser::StringTokeniser tokeniser{str, ';'};
+        auto it = tokeniser.cbegin();
+        int osc;
+        if (!it.number(osc))
+                return;
+
+        auto const cend = tokeniser.cend();
+        ++it; /* could now be cend */
+
+        switch (osc) {
+        case VTE_OSC_VTECWF:
+                set_current_file_uri(seq, it, cend);
+                break;
+
+        case VTE_OSC_VTECWD:
+                set_current_directory_uri(seq, it, cend);
+                break;
+
+        case VTE_OSC_VTEHYPER:
+                set_current_hyperlink(seq, it, cend);
+                break;
+
+        case -1: /* default */
+        case VTE_OSC_XTERM_SET_WINDOW_AND_ICON_TITLE: {
+                std::string title;
+                if (it != cend)
+                        title = it.string_remaining();
+                m_icon_title_pending = title;
+                m_window_title_pending.swap(title);
+                m_icon_title_changed = true;
+                m_window_title_changed = true;
+                break;
+        }
+
+        case VTE_OSC_XTERM_SET_ICON_TITLE: {
+                std::string title;
+                if (it != cend)
+                        title = it.string_remaining();
+                m_icon_title_pending.swap(title);
+                m_icon_title_changed = true;
+                break;
+        }
+
+        case VTE_OSC_XTERM_SET_WINDOW_TITLE: {
+                std::string title;
+                if (it != cend)
+                        title = it.string_remaining();
+                m_window_title_pending.swap(title);
+                m_window_title_changed = true;
+                break;
+        }
+
+        case VTE_OSC_XTERM_SET_COLOR:
+                set_color(seq, it, cend);
+                break;
+
+        case VTE_OSC_XTERM_SET_COLOR_TEXT_FG:
+                set_special_color(seq, it, cend, VTE_DEFAULT_FG, -1, osc);
+                break;
+
+        case VTE_OSC_XTERM_SET_COLOR_TEXT_BG:
+                set_special_color(seq, it, cend, VTE_DEFAULT_BG, -1, osc);
+                break;
+
+        case VTE_OSC_XTERM_SET_COLOR_CURSOR_BG:
+                set_special_color(seq, it, cend, VTE_CURSOR_BG, VTE_DEFAULT_FG, osc);
+                break;
+
+        case VTE_OSC_XTERM_SET_COLOR_HIGHLIGHT_BG:
+                set_special_color(seq, it, cend, VTE_HIGHLIGHT_BG, VTE_DEFAULT_FG, osc);
+                break;
+
+        case VTE_OSC_XTERM_SET_COLOR_HIGHLIGHT_FG:
+                set_special_color(seq, it, cend, VTE_HIGHLIGHT_FG, VTE_DEFAULT_BG, osc);
+                break;
+
+        case VTE_OSC_XTERM_RESET_COLOR:
+                reset_color(seq, it, cend);
+                break;
+
+        case VTE_OSC_XTERM_RESET_COLOR_TEXT_FG:
+                reset_color(VTE_DEFAULT_FG, VTE_COLOR_SOURCE_ESCAPE);
+                break;
+
+        case VTE_OSC_XTERM_RESET_COLOR_TEXT_BG:
+                reset_color(VTE_DEFAULT_BG, VTE_COLOR_SOURCE_ESCAPE);
+                break;
+
+        case VTE_OSC_XTERM_RESET_COLOR_CURSOR_BG:
+                reset_color(VTE_CURSOR_BG, VTE_COLOR_SOURCE_ESCAPE);
+                break;
+
+        case VTE_OSC_XTERM_RESET_COLOR_HIGHLIGHT_BG:
+                reset_color(VTE_HIGHLIGHT_BG, VTE_COLOR_SOURCE_ESCAPE);
+                break;
+
+        case VTE_OSC_XTERM_RESET_COLOR_HIGHLIGHT_FG:
+                reset_color(VTE_HIGHLIGHT_FG, VTE_COLOR_SOURCE_ESCAPE);
+                break;
+
+        case VTE_OSC_XTERM_SET_XPROPERTY:
+        case VTE_OSC_XTERM_SET_COLOR_SPECIAL:
+        case VTE_OSC_XTERM_SET_COLOR_MOUSE_CURSOR_FG:
+        case VTE_OSC_XTERM_SET_COLOR_MOUSE_CURSOR_BG:
+        case VTE_OSC_XTERM_SET_COLOR_TEK_FG:
+        case VTE_OSC_XTERM_SET_COLOR_TEK_BG:
+        case VTE_OSC_XTERM_SET_COLOR_TEK_CURSOR:
+        case VTE_OSC_XTERM_LOGFILE:
+        case VTE_OSC_XTERM_SET_FONT:
+        case VTE_OSC_XTERM_SET_XSELECTION:
+        case VTE_OSC_XTERM_RESET_COLOR_SPECIAL:
+        case VTE_OSC_XTERM_SET_COLOR_MODE:
+        case VTE_OSC_XTERM_RESET_COLOR_MOUSE_CURSOR_FG:
+        case VTE_OSC_XTERM_RESET_COLOR_MOUSE_CURSOR_BG:
+        case VTE_OSC_XTERM_RESET_COLOR_TEK_FG:
+        case VTE_OSC_XTERM_RESET_COLOR_TEK_BG:
+        case VTE_OSC_XTERM_RESET_COLOR_TEK_CURSOR:
+        case VTE_OSC_EMACS_51:
+        case VTE_OSC_ITERM2_133:
+        case VTE_OSC_ITERM2_1337:
+        case VTE_OSC_ITERM2_GROWL:
+        case VTE_OSC_KONSOLE_30:
+        case VTE_OSC_KONSOLE_31:
+        case VTE_OSC_RLOGIN_SET_KANJI_MODE:
+        case VTE_OSC_RLOGIN_SPEECH:
+        case VTE_OSC_RXVT_SET_BACKGROUND_PIXMAP:
+        case VTE_OSC_RXVT_SET_COLOR_FG:
+        case VTE_OSC_RXVT_SET_COLOR_BG:
+        case VTE_OSC_RXVT_DUMP_SCREEN:
+        case VTE_OSC_URXVT_SET_LOCALE:
+        case VTE_OSC_URXVT_VERSION:
+        case VTE_OSC_URXVT_SET_COLOR_TEXT_ITALIC:
+        case VTE_OSC_URXVT_SET_COLOR_TEXT_BOLD:
+        case VTE_OSC_URXVT_SET_COLOR_UNDERLINE:
+        case VTE_OSC_URXVT_SET_COLOR_BORDER:
+        case VTE_OSC_URXVT_SET_FONT:
+        case VTE_OSC_URXVT_SET_FONT_BOLD:
+        case VTE_OSC_URXVT_SET_FONT_ITALIC:
+        case VTE_OSC_URXVT_SET_FONT_BOLD_ITALIC:
+        case VTE_OSC_URXVT_VIEW_UP:
+        case VTE_OSC_URXVT_VIEW_DOWN:
+        case VTE_OSC_URXVT_EXTENSION:
+        case VTE_OSC_YF_RQGWR:
+        default:
+                break;
+        }
+}
+
+void
 VteTerminalPrivate::PP(vte::parser::Sequence const& seq)
 {
         /*



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