[vte] emulation: Improve the handling of Tab and CJK fragments



commit 4d6edfb7ecd9bc2743fe507794092ff096d50aac
Author: Egmont Koblinger <egmont gmail com>
Date:   Fri May 23 01:44:50 2014 +0200

    emulation: Improve the handling of Tab and CJK fragments
    
    Tab is converted to spaces when the content underneath is modified, instead of
    when the cursor is moved over. Only the left part is converted to spaces, the
    right part becomes a shorter tab.
    
    Double wide characters are replaced by spaces when split in two.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=691972
    https://bugzilla.gnome.org/show_bug.cgi?id=730343

 src/vte-private.h |    2 +-
 src/vte.c         |  157 ++++++++++++++++++++++++++++++++++++-----------------
 src/vteseq.c      |   20 +++++--
 3 files changed, 122 insertions(+), 57 deletions(-)
---
diff --git a/src/vte-private.h b/src/vte-private.h
index 89afddb..fc6a570 100644
--- a/src/vte-private.h
+++ b/src/vte-private.h
@@ -432,7 +432,7 @@ void _vte_terminal_clear_tabstop(VteTerminal *terminal, int column);
 gboolean _vte_terminal_get_tabstop(VteTerminal *terminal, int column);
 void _vte_terminal_set_tabstop(VteTerminal *terminal, int column);
 void _vte_terminal_update_insert_delta(VteTerminal *terminal);
-void _vte_terminal_cleanup_tab_fragments_at_cursor (VteTerminal *terminal);
+void _vte_terminal_cleanup_fragments(VteTerminal *terminal, long start, long end);
 void _vte_terminal_audible_beep(VteTerminal *terminal);
 void _vte_terminal_visible_beep(VteTerminal *terminal);
 void _vte_terminal_beep(VteTerminal *terminal);
diff --git a/src/vte.c b/src/vte.c
index 9a1b20a..26143e9 100644
--- a/src/vte.c
+++ b/src/vte.c
@@ -2901,36 +2901,109 @@ vte_terminal_set_default_colors(VteTerminal *terminal)
        _vte_terminal_set_colors(terminal, NULL, NULL, NULL, 0);
 }
 
-
-/* Cleanup smart-tabs.  See vte_sequence_handler_ta() */
+/*
+ * _vte_terminal_cleanup_fragments:
+ * @terminal: a #VteTerminal
+ * @start: the starting column, inclusive
+ * @end: the end column, exclusive
+ *
+ * Needs to be called before modifying the contents in the cursor's row,
+ * between the two given columns.  Cleans up TAB and CJK fragments to the
+ * left of @start and to the right of @end.  If a CJK is split in half,
+ * the remaining half is replaced by a space.  If a TAB at @start is split,
+ * it is replaced by spaces.  If a TAB at @end is split, it is replaced by
+ * a shorter TAB.  @start and @end can be equal if characters will be
+ * inserted at the location rather than overwritten.
+ *
+ * The area between @start and @end is not cleaned up, hence the whole row
+ * can be left in an inconsistent state.  It is expected that the caller
+ * will fill up that range afterwards, resulting in a consistent row again.
+ *
+ * Invalidates the cells that visually change outside of the range,
+ * because the caller can't reasonably be expected to take care of this.
+ */
 void
-_vte_terminal_cleanup_tab_fragments_at_cursor (VteTerminal *terminal)
-{
-       VteRowData *row = _vte_terminal_ensure_row (terminal);
-       VteScreen *screen = terminal->pvt->screen;
-       long col = screen->cursor_current.col;
-       const VteCell *pcell = _vte_row_data_get (row, col);
-
-       if (G_UNLIKELY (pcell != NULL && pcell->c == '\t')) {
-               long i, num_columns;
-               VteCell *cell = _vte_row_data_get_writable (row, col);
-               
-               _vte_debug_print(VTE_DEBUG_MISC,
-                                "Cleaning tab fragments at %ld",
-                                col);
-
-               /* go back to the beginning of the tab */
-               while (cell->attr.fragment && col > 0)
-                       cell = _vte_row_data_get_writable (row, --col);
+_vte_terminal_cleanup_fragments(VteTerminal *terminal,
+                                long start, long end)
+{
+        VteRowData *row = _vte_terminal_ensure_row (terminal);
+        const VteCell *cell_start;
+        VteCell *cell_end, *cell_col;
+        gboolean cell_start_is_fragment;
+        long col;
+
+        g_assert(end >= start);
+
+        /* Remember whether the cell at start is a fragment.  We'll need to know it when
+         * handling the left hand side, but handling the right hand side first might
+         * overwrite it if start == end (inserting to the middle of a character). */
+        cell_start = _vte_row_data_get (row, start);
+        cell_start_is_fragment = cell_start != NULL && cell_start->attr.fragment;
+
+        /* On the right hand side, try to replace a TAB by a shorter TAB if we can.
+         * This requires that the TAB on the left (which might be the same TAB) is
+         * not yet converted to spaces, so start on the right hand side. */
+        cell_end = _vte_row_data_get_writable (row, end);
+        if (G_UNLIKELY (cell_end != NULL && cell_end->attr.fragment)) {
+                col = end;
+                do {
+                        col--;
+                        g_assert(col >= 0);  /* The first cell can't be a fragment. */
+                        cell_col = _vte_row_data_get_writable (row, col);
+                } while (cell_col->attr.fragment);
+                if (cell_col->c == '\t') {
+                        _vte_debug_print(VTE_DEBUG_MISC,
+                                         "Replacing right part of TAB with a shorter one at %ld (%d cells) 
=> %ld (%ld cells)\n",
+                                         col, cell_col->attr.columns, end, cell_col->attr.columns - (end - 
col));
+                        cell_end->c = '\t';
+                        cell_end->attr.fragment = 0;
+                        g_assert(cell_col->attr.columns > end - col);
+                        cell_end->attr.columns = cell_col->attr.columns - (end - col);
+                } else {
+                        _vte_debug_print(VTE_DEBUG_MISC,
+                                         "Cleaning CJK right half at %ld\n",
+                                         end);
+                        g_assert(end - col == 1 && cell_col->attr.columns == 2);
+                        cell_end->c = ' ';
+                        cell_end->attr.fragment = 0;
+                        cell_end->attr.columns = 1;
+                        _vte_invalidate_cells(terminal,
+                                              end, 1,
+                                              terminal->pvt->screen->cursor_current.row, 1);
+                }
+        }
 
-               num_columns = cell->attr.columns;
-               for (i = 0; i < num_columns; i++) {
-                       cell = _vte_row_data_get_writable (row, col++);
-                       if (G_UNLIKELY (!cell))
-                         break;
-                       *cell = screen->fill_defaults;
-               }
-       }
+        /* Handle the left hand side.  Converting longer TABs to shorter ones probably
+         * wouldn't make that much sense here, so instead convert to spaces. */
+        if (G_UNLIKELY (cell_start_is_fragment)) {
+                gboolean keep_going = TRUE;
+                col = start;
+                do {
+                        col--;
+                        g_assert(col >= 0);  /* The first cell can't be a fragment. */
+                        cell_col = _vte_row_data_get_writable (row, col);
+                        if (!cell_col->attr.fragment) {
+                                if (cell_col->c == '\t') {
+                                        _vte_debug_print(VTE_DEBUG_MISC,
+                                                         "Replacing left part of TAB with spaces at %ld (%d 
=> %ld cells)\n",
+                                                         col, cell_col->attr.columns, start - col);
+                                        /* nothing to do here */
+                                } else {
+                                        _vte_debug_print(VTE_DEBUG_MISC,
+                                                         "Cleaning CJK left half at %ld\n",
+                                                         col);
+                                        g_assert(start - col == 1);
+                                        _vte_invalidate_cells(terminal,
+                                                              col, 1,
+                                                              terminal->pvt->screen->cursor_current.row, 1);
+                                }
+                                keep_going = FALSE;
+                        }
+                        cell_col->c = ' ';
+                        cell_col->attr.fragment = 0;
+                        cell_col->attr.columns = 1;
+                } while (keep_going);
+        }
 }
 
 /* Cursor down, with scrolling. */
@@ -3135,34 +3208,15 @@ _vte_terminal_insert_char(VteTerminal *terminal, gunichar c,
        row = vte_terminal_ensure_cursor (terminal);
        g_assert(row != NULL);
 
-       _vte_terminal_cleanup_tab_fragments_at_cursor (terminal);
-
        if (insert) {
+                _vte_terminal_cleanup_fragments (terminal, col, col);
                for (i = 0; i < columns; i++)
                        _vte_row_data_insert (row, col + i, &screen->color_defaults);
        } else {
+                _vte_terminal_cleanup_fragments (terminal, col, col + columns);
                _vte_row_data_fill (row, &basic_cell.cell, col + columns);
        }
 
-       /* Convert any wide characters we may have broken into single
-        * cells. (#514632) */
-       if (G_LIKELY (col > 0)) {
-               glong col2 = col - 1;
-               VteCell *cell = _vte_row_data_get_writable (row, col2);
-               while (col2 > 0 && cell != NULL && cell->attr.fragment)
-                       cell = _vte_row_data_get_writable (row, --col2);
-               cell->attr.columns = col - col2;
-       }
-       {
-               glong col2 = col + columns;
-               VteCell *cell = _vte_row_data_get_writable (row, col2);
-               while (cell != NULL && cell->attr.fragment) {
-                       cell->attr.columns = 1;
-                       cell->c = 0;
-                       cell = _vte_row_data_get_writable (row, ++col2);
-               }
-       }
-
        attr = screen->defaults.attr;
        attr.fore = screen->color_defaults.attr.fore;
        attr.back = screen->color_defaults.attr.back;
@@ -10048,9 +10102,10 @@ vte_terminal_paint_cursor(VteTerminal *terminal)
        if (focus && !blink)
                return;
 
-       /* Find the character "under" the cursor. */
+        /* Find the first cell of the character "under" the cursor.
+         * This is for CJK.  For TAB, paint the cursor where it really is. */
        cell = vte_terminal_find_charcell(terminal, col, drow);
-       while ((cell != NULL) && (cell->attr.fragment) && (col > 0)) {
+        while (cell != NULL && cell->attr.fragment && cell->c != '\t' && col > 0) {
                col--;
                cell = vte_terminal_find_charcell(terminal, col, drow);
        }
diff --git a/src/vteseq.c b/src/vteseq.c
index c4b145c..8371ebb 100644
--- a/src/vteseq.c
+++ b/src/vteseq.c
@@ -921,6 +921,8 @@ _vte_sequence_handler_cb (VteTerminal *terminal, GValueArray *params)
 
        /* Get the data for the row which the cursor points to. */
        rowdata = _vte_terminal_ensure_row(terminal);
+        /* Clean up Tab/CJK fragments. */
+        _vte_terminal_cleanup_fragments (terminal, 0, screen->cursor_current.col);
        /* Clear the data up to the current column with the default
         * attributes.  If there is no such character cell, we need
         * to add one. */
@@ -958,6 +960,9 @@ _vte_sequence_handler_cd (VteTerminal *terminal, GValueArray *params)
        if (i < _vte_ring_next(screen->row_data)) {
                /* Get the data for the row we're clipping. */
                rowdata = _vte_ring_index_writable (screen->row_data, i);
+                /* Clean up Tab/CJK fragments. */
+                if ((glong) _vte_row_data_length (rowdata) > screen->cursor_current.col)
+                        _vte_terminal_cleanup_fragments (terminal, screen->cursor_current.col, 
_vte_row_data_length (rowdata));
                /* Clear everything to the right of the cursor. */
                if (rowdata)
                        _vte_row_data_shrink (rowdata, screen->cursor_current.col);
@@ -1009,9 +1014,11 @@ _vte_sequence_handler_ce (VteTerminal *terminal, GValueArray *params)
        /* Get the data for the row which the cursor points to. */
        rowdata = _vte_terminal_ensure_row(terminal);
        g_assert(rowdata != NULL);
-       /* Remove the data at the end of the array until the current column
-        * is the end of the array. */
        if ((glong) _vte_row_data_length (rowdata) > screen->cursor_current.col) {
+                /* Clean up Tab/CJK fragments. */
+                _vte_terminal_cleanup_fragments (terminal, screen->cursor_current.col, _vte_row_data_length 
(rowdata));
+                /* Remove the data at the end of the array until the current column
+                 * is the end of the array. */
                _vte_row_data_shrink (rowdata, screen->cursor_current.col);
                /* We've modified the display.  Make a note of it. */
                terminal->pvt->text_deleted_flag = TRUE;
@@ -1050,7 +1057,6 @@ vte_sequence_handler_cursor_character_absolute (VteTerminal *terminal, GValueArr
        }
 
         screen->cursor_current.col = val;
-        _vte_terminal_cleanup_tab_fragments_at_cursor (terminal);
 }
 
 /* Move the cursor to the given position, 1-based. */
@@ -1088,7 +1094,6 @@ vte_sequence_handler_cursor_position (VteTerminal *terminal, GValueArray *params
        }
        screen->cursor_current.row = rowval + screen->insert_delta;
        screen->cursor_current.col = colval;
-       _vte_terminal_cleanup_tab_fragments_at_cursor (terminal);
 }
 
 /* Carriage return. */
@@ -1216,6 +1221,8 @@ _vte_sequence_handler_dc (VteTerminal *terminal, GValueArray *params)
                len = _vte_row_data_length (rowdata);
                /* Remove the column. */
                if (col < len) {
+                        /* Clean up Tab/CJK fragments. */
+                        _vte_terminal_cleanup_fragments (terminal, col, col + 1);
                        _vte_row_data_remove (rowdata, col);
                        if (screen->fill_defaults.attr.back != VTE_DEFAULT_BG) {
                                _vte_row_data_fill (rowdata, &screen->fill_defaults, 
terminal->pvt->column_count);
@@ -1294,6 +1301,8 @@ vte_sequence_handler_erase_characters (VteTerminal *terminal, GValueArray *param
        rowdata = _vte_terminal_ensure_row(terminal);
        if (_vte_ring_next(screen->row_data) > screen->cursor_current.row) {
                g_assert(rowdata != NULL);
+                /* Clean up Tab/CJK fragments. */
+                _vte_terminal_cleanup_fragments (terminal, screen->cursor_current.col, 
screen->cursor_current.col + count);
                /* Write over the characters.  (If there aren't enough, we'll
                 * need to create them.) */
                for (i = 0; i < count; i++) {
@@ -1338,12 +1347,14 @@ _vte_sequence_handler_insert_character (VteTerminal *terminal, GValueArray *para
 
        save = screen->cursor_current;
 
+        _vte_terminal_cleanup_fragments(terminal, screen->cursor_current.col, screen->cursor_current.col);
        _vte_terminal_insert_char(terminal, ' ', TRUE, TRUE);
 
        screen->cursor_current = save;
 }
 
 /* Insert N blank characters. */
+/* TODOegmont: Insert them in a single run, so that we call _vte_terminal_cleanup_fragments only once. */
 static void
 vte_sequence_handler_insert_blank_characters (VteTerminal *terminal, GValueArray *params)
 {
@@ -1367,7 +1378,6 @@ vte_sequence_handler_backspace (VteTerminal *terminal, GValueArray *params)
        if (screen->cursor_current.col > 0) {
                /* There's room to move left, so do so. */
                screen->cursor_current.col--;
-               _vte_terminal_cleanup_tab_fragments_at_cursor (terminal);
        }
 }
 


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