diff --git a/src/vte-private.h b/src/vte-private.h
index cc19614..38d6704 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 0890a6d..44f9a5d 100644
--- a/src/vte.c
+++ b/src/vte.c
@@ -2901,36 +2901,102 @@ 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);
+        VteCell *cell_start, *cell_end, *cell_col;
+        long col;
+
+        g_assert(end >= start);
+
+        /* 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. */
+        cell_start = _vte_row_data_get_writable (row, start);
+        if (G_UNLIKELY (cell_start != NULL && cell_start->attr.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 +3201,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;
diff --git a/src/vteseq.c b/src/vteseq.c
index 144a7a4..2eb4462 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);
 	}
 }
 
