vteseq.cc 238 KB
Newer Older
1
/*
2 3 4
 * Copyright © 2001-2004 Red Hat, Inc.
 * Copyright © 2015 David Herrmann <dh.herrmann@gmail.com>
 * Copyright © 2008-2018 Christian Persch
5
 *
6 7 8
 * 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
9
 * version 3 of the License, or (at your option) any later version.
10
 *
11 12
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
 * Lesser General Public License for more details.
15
 *
16 17 18
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19 20
 */

21
#include "config.h"
22

23 24
#include <search.h>
#include <stdlib.h>
25
#include <string.h>
26
#include <limits.h>
27 28 29 30
#ifdef HAVE_SYS_SYSLIMITS_H
#include <sys/syslimits.h>
#endif

31 32
#include <glib.h>

33
#include <vte/vte.h>
34 35
#include "vteinternal.hh"
#include "vtegtk.hh"
36
#include "caps.hh"
37
#include "debug.h"
38

39 40
#define BEL_C0 "\007"
#define ST_C0 _VTE_CAP_ST
41

42
#include <algorithm>
43

44 45
using namespace std::literals;

46
void
47
vte::parser::Sequence::print() const noexcept
48 49
{
#ifdef VTE_DEBUG
50 51 52 53 54 55 56 57 58 59
        auto c = m_seq != nullptr ? terminator() : 0;
        char c_buf[7];
        g_snprintf(c_buf, sizeof(c_buf), "%lc", c);
        g_printerr("%s:%s [%s]", type_string(), command_string(),
                   g_unichar_isprint(c) ? c_buf : _vte_debug_sequence_to_string(c_buf, -1));
        if (m_seq != nullptr && m_seq->n_args > 0) {
                g_printerr("[ ");
                for (unsigned int i = 0; i < m_seq->n_args; i++) {
                        if (i > 0)
                                g_print(", ");
60
                        g_printerr("%d", vte_seq_arg_value(m_seq->args[i]));
61
                }
62 63
                g_printerr(" ]");
        }
64 65 66 67 68
        if (m_seq->type == VTE_SEQ_OSC) {
                char* str = string_param();
                g_printerr(" \"%s\"", str);
                g_free(str);
        }
69
        g_printerr("\n");
70 71 72
#endif
}

73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
char const*
vte::parser::Sequence::type_string() const
{
        if (G_UNLIKELY(m_seq == nullptr))
                return "(nil)";

        switch (type()) {
        case VTE_SEQ_NONE:    return "NONE";
        case VTE_SEQ_IGNORE:  return "IGNORE";
        case VTE_SEQ_GRAPHIC: return "GRAPHIC";
        case VTE_SEQ_CONTROL: return "CONTROL";
        case VTE_SEQ_ESCAPE:  return "ESCAPE";
        case VTE_SEQ_CSI:     return "CSI";
        case VTE_SEQ_DCS:     return "DCS";
        case VTE_SEQ_OSC:     return "OSC";
        default:
                g_assert(false);
                return nullptr;
        }
}

char const*
vte::parser::Sequence::command_string() const
{
        if (G_UNLIKELY(m_seq == nullptr))
                return "(nil)";

        switch (command()) {
#define _VTE_CMD(cmd) case VTE_CMD_##cmd: return #cmd;
102
#define _VTE_NOP(cmd)
103 104
#include "parser-cmd.hh"
#undef _VTE_CMD
105
#undef _VTE_NOP
106 107
        default:
                static char buf[32];
108
                snprintf(buf, sizeof(buf), "NOP OR UNKOWN(%u)", command());
109 110 111 112
                return buf;
        }
}

113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
// 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;
}

131 132
/* A couple are duplicated from vte.c, to keep them static... */

133
/* Check how long a string of unichars is.  Slow version. */
134 135
static gsize
vte_unichar_strlen(gunichar const* c)
136
{
137
	gsize i;
138 139 140 141
	for (i = 0; c[i] != 0; i++) ;
	return i;
}

142
/* Convert a wide character string to a multibyte string */
143 144 145
/* Simplified from glib's g_ucs4_to_utf8() to simply allocate the maximum
 * length instead of walking the input twice.
 */
146
char*
147
vte::parser::Sequence::ucs4_to_utf8(gunichar const* str,
148
                                    ssize_t len) const noexcept
149
{
150 151
        if (len < 0)
                len = vte_unichar_strlen(str);
152
        auto outlen = (len * VTE_UTF8_BPC) + 1;
153

154 155 156
        auto result = (char*)g_try_malloc(outlen);
        if (result == nullptr)
                return nullptr;
157

158 159 160 161 162
        auto end = str + len;
        auto p = result;
        for (auto i = str; i < end; i++)
                p += g_unichar_to_utf8(*i, p);
        *p = '\0';
163

164
        return result;
165 166
}

167 168 169
namespace vte {
namespace terminal {

170 171
/* Emit a "bell" signal. */
void
172
Terminal::emit_bell()
173 174 175 176 177
{
        _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `bell'.\n");
        g_signal_emit(m_terminal, signals[SIGNAL_BELL], 0);
}

178
/* Emit a "deiconify-window" signal. */
179
void
180
Terminal::emit_deiconify_window()
181
{
182 183
        _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `deiconify-window'.\n");
        g_signal_emit(m_terminal, signals[SIGNAL_DEICONIFY_WINDOW], 0);
184 185 186
}

/* Emit a "iconify-window" signal. */
187
void
188
Terminal::emit_iconify_window()
189
{
190 191
        _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `iconify-window'.\n");
        g_signal_emit(m_terminal, signals[SIGNAL_ICONIFY_WINDOW], 0);
192 193 194
}

/* Emit a "raise-window" signal. */
195
void
196
Terminal::emit_raise_window()
197
{
198 199
        _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `raise-window'.\n");
        g_signal_emit(m_terminal, signals[SIGNAL_RAISE_WINDOW], 0);
200 201 202
}

/* Emit a "lower-window" signal. */
203
void
204
Terminal::emit_lower_window()
205
{
206 207
        _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `lower-window'.\n");
        g_signal_emit(m_terminal, signals[SIGNAL_LOWER_WINDOW], 0);
208 209 210
}

/* Emit a "maximize-window" signal. */
211
void
212
Terminal::emit_maximize_window()
213
{
214 215
        _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `maximize-window'.\n");
        g_signal_emit(m_terminal, signals[SIGNAL_MAXIMIZE_WINDOW], 0);
216 217 218
}

/* Emit a "refresh-window" signal. */
219
void
220
Terminal::emit_refresh_window()
221
{
222 223
        _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `refresh-window'.\n");
        g_signal_emit(m_terminal, signals[SIGNAL_REFRESH_WINDOW], 0);
224 225 226
}

/* Emit a "restore-window" signal. */
227
void
228
Terminal::emit_restore_window()
229
{
230 231
        _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `restore-window'.\n");
        g_signal_emit(m_terminal, signals[SIGNAL_RESTORE_WINDOW], 0);
232 233 234
}

/* Emit a "move-window" signal.  (Pixels.) */
235
void
236
Terminal::emit_move_window(guint x,
237
                                     guint y)
238
{
239 240
        _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `move-window'.\n");
        g_signal_emit(m_terminal, signals[SIGNAL_MOVE_WINDOW], 0, x, y);
241 242
}

243
/* Emit a "resize-window" signal.  (Grid size.) */
244
void
245
Terminal::emit_resize_window(guint columns,
246
                                       guint rows)
247
{
248 249
        _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `resize-window'.\n");
        g_signal_emit(m_terminal, signals[SIGNAL_RESIZE_WINDOW], 0, columns, rows);
250 251
}

252 253
/* Some common functions */

254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
/* In Xterm, upon printing a character in the last column the cursor doesn't
 * advance.  It's special cased that printing the following letter will first
 * wrap to the next row.
 *
 * As a rule of thumb, escape sequences that move the cursor (e.g. cursor up)
 * or immediately update the visible contents (e.g. clear in line) disable
 * this special mode, whereas escape sequences with no immediate visible
 * effect (e.g. color change) leave this special mode on.  There are
 * exceptions of course (e.g. scroll up).
 *
 * In VTE, a different technical approach is used.  The cursor is advanced to
 * the invisible column on the right, but it's set back to the visible
 * rightmost column whenever necessary (that is, before handling any of the
 * sequences that disable the special cased mode in xterm).  (Bug 731155.)
 */
269
void
270
Terminal::ensure_cursor_is_onscreen()
271
{
272 273
        if (G_UNLIKELY (m_screen->cursor.col >= m_column_count))
                m_screen->cursor.col = m_column_count - 1;
274 275
}

276
void
277
Terminal::home_cursor()
278
{
279
        set_cursor_coords(0, 0);
280 281
}

282
void
283
Terminal::clear_screen()
284
{
285
        auto row = m_screen->cursor.row - m_screen->insert_delta;
286
        auto initial = _vte_ring_next(m_screen->row_data);
287
	/* Add a new screen's worth of rows. */
288
        for (auto i = 0; i < m_row_count; i++)
289
                ring_append(true);
290 291
	/* Move the cursor and insertion delta to the first line in the
	 * newly-cleared area and scroll if need be. */
292
        m_screen->insert_delta = initial;
293
        m_screen->cursor.row = row + m_screen->insert_delta;
294
        adjust_adjustments();
295
	/* Redraw everything. */
296
        invalidate_all();
297
	/* We've modified the display.  Make a note of it. */
298
        m_text_deleted_flag = TRUE;
299 300 301
}

/* Clear the current line. */
302
void
303
Terminal::clear_current_line()
304 305 306 307 308
{
	VteRowData *rowdata;

	/* If the cursor is actually on the screen, clear data in the row
	 * which corresponds to the cursor. */
309
        if (_vte_ring_next(m_screen->row_data) > m_screen->cursor.row) {
310
		/* Get the data for the row which the cursor points to. */
311
                rowdata = _vte_ring_index_writable(m_screen->row_data, m_screen->cursor.row);
312 313
		g_assert(rowdata != NULL);
		/* Remove it. */
314 315
		_vte_row_data_shrink (rowdata, 0);
		/* Add enough cells to the end of the line to fill out the row. */
316
                _vte_row_data_fill (rowdata, &m_fill_defaults, m_column_count);
317
		rowdata->attr.soft_wrapped = 0;
318
		/* Repaint this row. */
319
		invalidate_cells(0, m_column_count,
320
                                 m_screen->cursor.row, 1);
321 322 323
	}

	/* We've modified the display.  Make a note of it. */
324
        m_text_deleted_flag = TRUE;
325 326 327
}

/* Clear above the current line. */
328
void
329
Terminal::clear_above_current()
330 331 332
{
	/* If the cursor is actually on the screen, clear data in the row
	 * which corresponds to the cursor. */
333
        for (auto i = m_screen->insert_delta; i < m_screen->cursor.row; i++) {
334
                if (_vte_ring_next(m_screen->row_data) > i) {
335
			/* Get the data for the row we're erasing. */
336
                        auto rowdata = _vte_ring_index_writable(m_screen->row_data, i);
337 338
			g_assert(rowdata != NULL);
			/* Remove it. */
339
			_vte_row_data_shrink (rowdata, 0);
340
			/* Add new cells until we fill the row. */
341
                        _vte_row_data_fill (rowdata, &m_fill_defaults, m_column_count);
342
			rowdata->attr.soft_wrapped = 0;
343
			/* Repaint the row. */
344
			invalidate_cells(0, m_column_count, i, 1);
345 346 347
		}
	}
	/* We've modified the display.  Make a note of it. */
348
        m_text_deleted_flag = TRUE;
349 350
}

Behdad Esfahbod's avatar
Behdad Esfahbod committed
351
/* Scroll the text, but don't move the cursor.  Negative = up, positive = down. */
352
void
353
Terminal::scroll_text(vte::grid::row_t scroll_amount)
Behdad Esfahbod's avatar
Behdad Esfahbod committed
354
{
355 356 357 358
        vte::grid::row_t start, end;
        if (m_scrolling_restricted) {
                start = m_screen->insert_delta + m_scrolling_region.start;
                end = m_screen->insert_delta + m_scrolling_region.end;
Behdad Esfahbod's avatar
Behdad Esfahbod committed
359
	} else {
360 361
                start = m_screen->insert_delta;
                end = start + m_row_count - 1;
Behdad Esfahbod's avatar
Behdad Esfahbod committed
362 363
	}

364
        while (_vte_ring_next(m_screen->row_data) <= end)
365
                ring_append(false);
366

Behdad Esfahbod's avatar
Behdad Esfahbod committed
367
	if (scroll_amount > 0) {
368
		for (auto i = 0; i < scroll_amount; i++) {
369 370
                        ring_remove(end);
                        ring_insert(start, true);
Behdad Esfahbod's avatar
Behdad Esfahbod committed
371 372
		}
	} else {
373
		for (auto i = 0; i < -scroll_amount; i++) {
374 375
                        ring_remove(start);
                        ring_insert(end, true);
Behdad Esfahbod's avatar
Behdad Esfahbod committed
376 377 378 379
		}
	}

	/* Update the display. */
380
        scroll_region(start, end - start + 1, scroll_amount);
Behdad Esfahbod's avatar
Behdad Esfahbod committed
381 382

	/* Adjust the scrollbars if necessary. */
383
        adjust_adjustments();
Behdad Esfahbod's avatar
Behdad Esfahbod committed
384 385

	/* We've modified the display.  Make a note of it. */
386 387
        m_text_inserted_flag = TRUE;
        m_text_deleted_flag = TRUE;
Behdad Esfahbod's avatar
Behdad Esfahbod committed
388 389
}

390
void
391
Terminal::restore_cursor()
392 393 394
{
        restore_cursor(m_screen);
        ensure_cursor_is_onscreen();
395 396
}

397
void
398
Terminal::save_cursor()
399 400
{
        save_cursor(m_screen);
401 402 403
}

/* Switch to normal screen. */
404
void
405
Terminal::switch_normal_screen()
406
{
407
        switch_screen(&m_normal_screen);
408 409 410
}

void
411
Terminal::switch_screen(VteScreen *new_screen)
412 413 414
{
        /* if (new_screen == m_screen) return; ? */

415 416 417 418 419 420 421 422 423 424 425
        /* The two screens use different hyperlink pools, so carrying on the idx
         * wouldn't make sense and could lead to crashes.
         * Ideally we'd carry the target URI itself, but I'm just lazy.
         * Also, run a GC before we switch away from that screen. */
        m_hyperlink_hover_idx = _vte_ring_get_hyperlink_at_position(m_screen->row_data, -1, -1, true, NULL);
        g_assert (m_hyperlink_hover_idx == 0);
        m_hyperlink_hover_uri = NULL;
        emit_hyperlink_hover_uri_changed(NULL);  /* FIXME only emit if really changed */
        m_defaults.attr.hyperlink_idx = _vte_ring_get_hyperlink_idx(m_screen->row_data, NULL);
        g_assert (m_defaults.attr.hyperlink_idx == 0);

426
        /* cursor.row includes insert_delta, adjust accordingly */
427
        auto cr = m_screen->cursor.row - m_screen->insert_delta;
428
        m_screen = new_screen;
429
        m_screen->cursor.row = cr + m_screen->insert_delta;
430 431

        /* Make sure the ring is large enough */
432
        ensure_row();
433 434 435
}

/* Switch to alternate screen. */
436
void
437
Terminal::switch_alternate_screen()
438
{
439
        switch_screen(&m_alternate_screen);
440 441
}

442
void
443
Terminal::set_mode_ecma(vte::parser::Sequence const& seq,
444
                                  bool set) noexcept
Behdad Esfahbod's avatar
Behdad Esfahbod committed
445
{
446 447 448 449 450 451 452 453 454
        auto const n_params = seq.size();
        for (unsigned int i = 0; i < n_params; i = seq.next(i)) {
                auto const param = seq.collect1(i);
                auto const mode = m_modes_ecma.mode_from_param(param);

                _vte_debug_print(VTE_DEBUG_MODES,
                                 "Mode %d (%s) %s\n",
                                 param, m_modes_ecma.mode_to_cstring(mode),
                                 set ? "set" : "reset");
455

456
                if (mode < 0)
457
                        continue;
458

459
                m_modes_ecma.set(mode, set);
460
        }
461 462 463
}

void
464
Terminal::update_mouse_protocol() noexcept
465
{
466 467 468 469 470 471 472 473 474 475 476 477
        if (m_modes_private.XTERM_MOUSE_ANY_EVENT())
                m_mouse_tracking_mode = MOUSE_TRACKING_ALL_MOTION_TRACKING;
        else if (m_modes_private.XTERM_MOUSE_BUTTON_EVENT())
                m_mouse_tracking_mode = MOUSE_TRACKING_CELL_MOTION_TRACKING;
        else if (m_modes_private.XTERM_MOUSE_VT220_HIGHLIGHT())
                m_mouse_tracking_mode = MOUSE_TRACKING_HILITE_TRACKING;
        else if (m_modes_private.XTERM_MOUSE_VT220())
                m_mouse_tracking_mode = MOUSE_TRACKING_SEND_XY_ON_BUTTON;
        else if (m_modes_private.XTERM_MOUSE_X10())
                m_mouse_tracking_mode = MOUSE_TRACKING_SEND_XY_ON_CLICK;
        else
                m_mouse_tracking_mode = MOUSE_TRACKING_NONE;
478

479
        m_mouse_smooth_scroll_delta = 0.0;
480

481 482
        /* Mouse pointer might change */
        apply_mouse_cursor();
483

484 485 486 487 488
        _vte_debug_print(VTE_DEBUG_MODES,
                         "Mouse protocol is now %d\n", m_mouse_tracking_mode);
}

void
489
Terminal::set_mode_private(int mode,
490
                                     bool set) noexcept
491
{
492 493 494 495 496 497 498 499 500 501 502 503 504
        /* Pre actions */
        switch (mode) {
        default:
                break;
        }

        m_modes_private.set(mode, set);

        /* Post actions */
        switch (mode) {
        case vte::terminal::modes::Private::eDEC_132_COLUMN:
                /* DECCOLM: set/reset to 132/80 columns mode, clear screen and cursor home */
                // FIXMEchpe don't do clear screen if DECNCSM is set
505 506 507 508 509
                /* FIXMEchpe!!!
                 * Changing this mode resets the top, bottom, left, right margins;
                 * clears the screen (unless DECNCSM is set); resets DECLRMM; and clears
                 * the status line if host-writable.
                 */
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563
                if (m_modes_private.XTERM_DECCOLM()) {
                        emit_resize_window(set ? 132 : 80, m_row_count);
                        clear_screen();
                        home_cursor();
                }
                break;

        case vte::terminal::modes::Private::eDEC_REVERSE_IMAGE:
                invalidate_all();
                break;

        case vte::terminal::modes::Private::eDEC_ORIGIN:
                /* Reposition the cursor in its new home position. */
                home_cursor();
                break;

        case vte::terminal::modes::Private::eDEC_TEXT_CURSOR:
                /* No need to invalidate the cursor here, this is done
                 * in process_incoming().
                 */
                break;

        case vte::terminal::modes::Private::eXTERM_ALTBUF:
                /* [[fallthrough]]; */
        case vte::terminal::modes::Private::eXTERM_OPT_ALTBUF:
                /* [[fallthrough]]; */
        case vte::terminal::modes::Private::eXTERM_OPT_ALTBUF_SAVE_CURSOR:
                if (set) {
                        if (mode == vte::terminal::modes::Private::eXTERM_OPT_ALTBUF_SAVE_CURSOR)
                                save_cursor();

                        switch_alternate_screen();

                        /* Clear the alternate screen */
                        if (mode == vte::terminal::modes::Private::eXTERM_OPT_ALTBUF_SAVE_CURSOR)
                                clear_screen();
                } else {
                        if (mode == vte::terminal::modes::Private::eXTERM_OPT_ALTBUF &&
                            m_screen == &m_alternate_screen)
                                clear_screen();

                        switch_normal_screen();

                        if (mode == vte::terminal::modes::Private::eXTERM_OPT_ALTBUF_SAVE_CURSOR)
                                restore_cursor();
                }

                /* Reset scrollbars and repaint everything. */
                gtk_adjustment_set_value(m_vadjustment,
                                         m_screen->scroll_delta);
                set_scrollback_lines(m_scrollback_lines);
                queue_contents_changed();
                invalidate_all();
                break;
564

565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590
        case vte::terminal::modes::Private::eXTERM_SAVE_CURSOR:
                if (set)
                        save_cursor();
                else
                        restore_cursor();
                break;

        case vte::terminal::modes::Private::eXTERM_MOUSE_X10:
        case vte::terminal::modes::Private::eXTERM_MOUSE_VT220:
        case vte::terminal::modes::Private::eXTERM_MOUSE_VT220_HIGHLIGHT:
        case vte::terminal::modes::Private::eXTERM_MOUSE_BUTTON_EVENT:
        case vte::terminal::modes::Private::eXTERM_MOUSE_ANY_EVENT:
        case vte::terminal::modes::Private::eXTERM_MOUSE_EXT:
        case vte::terminal::modes::Private::eXTERM_MOUSE_EXT_SGR:
        case vte::terminal::modes::Private::eURXVT_MOUSE_EXT:
                update_mouse_protocol();
                break;

        case vte::terminal::modes::Private::eXTERM_FOCUS:
                if (set)
                        feed_focus_event_initial();
                break;

        default:
                break;
        }
591 592
}

593
void
594
Terminal::set_mode_private(vte::parser::Sequence const& seq,
595
                                     bool set) noexcept
596
{
597 598 599 600
        auto const n_params = seq.size();
        for (unsigned int i = 0; i < n_params; i = seq.next(i)) {
                auto const param = seq.collect1(i);
                auto const mode = m_modes_private.mode_from_param(param);
601

602 603 604 605
                _vte_debug_print(VTE_DEBUG_MODES,
                                 "Private mode %d (%s) %s\n",
                                 param, m_modes_private.mode_to_cstring(mode),
                                 set ? "set" : "reset");
606

607
                if (mode < 0)
608 609
                        continue;

610 611
                set_mode_private(mode, set);
        }
612 613 614
}

void
615
Terminal::save_mode_private(vte::parser::Sequence const& seq,
616 617 618 619 620 621
                                      bool save) noexcept
{
        auto const n_params = seq.size();
        for (unsigned int i = 0; i < n_params; i = seq.next(i)) {
                auto const param = seq.collect1(i);
                auto const mode = m_modes_private.mode_from_param(param);
622

623 624 625 626 627
                if (mode < 0) {
                        _vte_debug_print(VTE_DEBUG_MODES,
                                         "Saving private mode %d (%s)\n",
                                         param, m_modes_private.mode_to_cstring(mode));
                        continue;
628
                }
629

630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645
                if (save) {
                        _vte_debug_print(VTE_DEBUG_MODES,
                                         "Saving private mode %d (%s) is %s\n",
                                         param, m_modes_private.mode_to_cstring(mode),
                                         m_modes_private.get(mode) ? "set" : "reset");

                        m_modes_private.push_saved(mode);
                } else {
                        bool const set = m_modes_private.pop_saved(mode);

                        _vte_debug_print(VTE_DEBUG_MODES,
                                         "Restoring private mode %d (%s) to %s\n",
                                         param, m_modes_private.mode_to_cstring(mode),
                                         set ? "set" : "reset");

                        set_mode_private(mode, set);
646
                }
647
        }
648 649
}

650
void
651
Terminal::set_character_replacement(unsigned slot)
652 653 654
{
        g_assert(slot < G_N_ELEMENTS(m_character_replacements));
        m_character_replacement = &m_character_replacements[slot];
655 656
}

657
/* Clear from the cursor position (inclusive!) to the beginning of the line. */
658
void
659
Terminal::clear_to_bol()
660
{
661
        ensure_cursor_is_onscreen();
662

663
	/* Get the data for the row which the cursor points to. */
664
	auto rowdata = ensure_row();
665
        /* Clean up Tab/CJK fragments. */
666
        cleanup_fragments(0, m_screen->cursor.col + 1);
667 668 669
	/* Clear the data up to the current column with the default
	 * attributes.  If there is no such character cell, we need
	 * to add one. */
670
        vte::grid::column_t i;
671
        for (i = 0; i <= m_screen->cursor.col; i++) {
672
                if (i < (glong) _vte_row_data_length (rowdata)) {
673
			/* Muck with the cell in this location. */
674 675
                        auto pcell = _vte_row_data_get_writable(rowdata, i);
                        *pcell = m_color_defaults;
676 677
		} else {
			/* Add new cells until we have one here. */
678
                        _vte_row_data_append (rowdata, &m_color_defaults);
679 680 681
		}
	}
	/* Repaint this row. */
682 683
        invalidate_cells(0, m_screen->cursor.col+1,
                         m_screen->cursor.row, 1);
684 685

	/* We've modified the display.  Make a note of it. */
686
        m_text_deleted_flag = TRUE;
687 688 689
}

/* Clear to the right of the cursor and below the current line. */
690
void
691
Terminal::clear_below_current()
692
{
693
        ensure_cursor_is_onscreen();
694

695 696
	/* If the cursor is actually on the screen, clear the rest of the
	 * row the cursor is on and all of the rows below the cursor. */
697
        VteRowData *rowdata;
698
        auto i = m_screen->cursor.row;
699
	if (i < _vte_ring_next(m_screen->row_data)) {
700
		/* Get the data for the row we're clipping. */
701
                rowdata = _vte_ring_index_writable(m_screen->row_data, i);
702
                /* Clean up Tab/CJK fragments. */
703 704
                if ((glong) _vte_row_data_length(rowdata) > m_screen->cursor.col)
                        cleanup_fragments(m_screen->cursor.col, _vte_row_data_length(rowdata));
705
		/* Clear everything to the right of the cursor. */
706
		if (rowdata)
707
                        _vte_row_data_shrink(rowdata, m_screen->cursor.col);
708 709
	}
	/* Now for the rest of the lines. */
710
        for (i = m_screen->cursor.row + 1;
711
	     i < _vte_ring_next(m_screen->row_data);
712 713
	     i++) {
		/* Get the data for the row we're removing. */
714
		rowdata = _vte_ring_index_writable(m_screen->row_data, i);
715
		/* Remove it. */
716 717
		if (rowdata)
			_vte_row_data_shrink (rowdata, 0);
718 719
	}
	/* Now fill the cleared areas. */
720 721
        bool const not_default_bg = (m_fill_defaults.attr.back() != VTE_DEFAULT_BG);

722
        for (i = m_screen->cursor.row;
723
	     i < m_screen->insert_delta + m_row_count;
724 725
	     i++) {
		/* Retrieve the row's data, creating it if necessary. */
726 727
		if (_vte_ring_contains(m_screen->row_data, i)) {
			rowdata = _vte_ring_index_writable (m_screen->row_data, i);
728 729
			g_assert(rowdata != NULL);
		} else {
730
			rowdata = ring_append(false);
731 732
		}
		/* Pad out the row. */
733
                if (not_default_bg) {
734
                        _vte_row_data_fill(rowdata, &m_fill_defaults, m_column_count);
735
		}
736
		rowdata->attr.soft_wrapped = 0;
737
		/* Repaint this row. */
738 739
		invalidate_cells(0, m_column_count,
                                 i, 1);
740 741 742
	}

	/* We've modified the display.  Make a note of it. */
743
	m_text_deleted_flag = TRUE;
744 745 746
}

/* Clear from the cursor position to the end of the line. */
747
void
748
Terminal::clear_to_eol()
749
{
750 751 752 753 754 755
	/* If we were to strictly emulate xterm, we'd ensure the cursor is onscreen.
	 * But due to https://bugzilla.gnome.org/show_bug.cgi?id=740789 we intentionally
	 * deviate and do instead what konsole does. This way emitting a \e[K doesn't
	 * influence the text flow, and serves as a perfect workaround against a new line
	 * getting painted with the active background color (except for a possible flicker).
	 */
756
	/* ensure_cursor_is_onscreen(); */
757

758
	/* Get the data for the row which the cursor points to. */
759
        auto rowdata = ensure_row();
760
	g_assert(rowdata != NULL);
761
        if ((glong) _vte_row_data_length(rowdata) > m_screen->cursor.col) {
762
                /* Clean up Tab/CJK fragments. */
763
                cleanup_fragments(m_screen->cursor.col, _vte_row_data_length(rowdata));
764 765
                /* Remove the data at the end of the array until the current column
                 * is the end of the array. */
766
                _vte_row_data_shrink(rowdata, m_screen->cursor.col);
767
		/* We've modified the display.  Make a note of it. */
768
		m_text_deleted_flag = TRUE;
769
	}
770 771 772
        bool const not_default_bg = (m_fill_defaults.attr.back() != VTE_DEFAULT_BG);

        if (not_default_bg) {
773
		/* Add enough cells to fill out the row. */
774
                _vte_row_data_fill(rowdata, &m_fill_defaults, m_column_count);
775
	}
776
	rowdata->attr.soft_wrapped = 0;
777
	/* Repaint this row. */
778 779
	invalidate_cells(m_screen->cursor.col, m_column_count - m_screen->cursor.col,
                         m_screen->cursor.row, 1);
780 781
}

782
/*
783
 * Terminal::set_cursor_column:
784 785 786 787
 * @col: the column. 0-based from 0 to m_column_count - 1
 *
 * Sets the cursor column to @col, clamped to the range 0..m_column_count-1.
 */
788
void
789
Terminal::set_cursor_column(vte::grid::column_t col)
790
{
791 792
	_vte_debug_print(VTE_DEBUG_PARSER,
                         "Moving cursor to column %ld.\n", col);
793
        m_screen->cursor.col = CLAMP(col, 0, m_column_count - 1);
794 795
}

796
void
797
Terminal::set_cursor_column1(vte::grid::column_t col)
798 799 800 801
{
        set_cursor_column(col - 1);
}

802
/*
803
 * Terminal::set_cursor_row:
804 805 806 807 808
 * @row: the row. 0-based and relative to the scrolling region
 *
 * Sets the cursor row to @row. @row is relative to the scrolling region
 * (0 if restricted scrolling is off).
 */
809
void
810
Terminal::set_cursor_row(vte::grid::row_t row)
811
{
812
        vte::grid::row_t start_row, end_row;
813
        if (m_modes_private.DEC_ORIGIN() &&
814 815 816 817 818 819 820 821 822 823
            m_scrolling_restricted) {
                start_row = m_scrolling_region.start;
                end_row = m_scrolling_region.end;
        } else {
                start_row = 0;
                end_row = m_row_count - 1;
        }
        row += start_row;
        row = CLAMP(row, start_row, end_row);

824
        m_screen->cursor.row = row + m_screen->insert_delta;
825 826
}

827
void
828
Terminal::set_cursor_row1(vte::grid::row_t row)
829 830 831 832
{
        set_cursor_row(row - 1);
}

833
/*
834
 * Terminal::get_cursor_row:
835 836 837 838 839
 *
 * Returns: the relative cursor row, 0-based and relative to the scrolling region
 * if set (regardless of origin mode).
 */
vte::grid::row_t
840
Terminal::get_cursor_row_unclamped() const
841
{
842
        auto row = m_screen->cursor.row - m_screen->insert_delta;
843
        /* Note that we do NOT check DEC_ORIGIN mode here! */
844 845 846 847 848 849
        if (m_scrolling_restricted) {
                row -= m_scrolling_region.start;
        }
        return row;
}

850
vte::grid::column_t
851
Terminal::get_cursor_column_unclamped() const
852
{
853
        return m_screen->cursor.col;
854 855
}

856
/*
857
 * Terminal::set_cursor_coords:
858 859 860 861 862 863 864 865 866
 * @row: the row. 0-based and relative to the scrolling region
 * @col: the column. 0-based from 0 to m_column_count - 1
 *
 * Sets the cursor row to @row. @row is relative to the scrolling region
 * (0 if restricted scrolling is off).
 *
 * Sets the cursor column to @col, clamped to the range 0..m_column_count-1.
 */
void
867
Terminal::set_cursor_coords(vte::grid::row_t row,
868 869 870 871
                                      vte::grid::column_t column)
{
        set_cursor_column(column);
        set_cursor_row(row);
872 873
}

874
void
875
Terminal::set_cursor_coords1(vte::grid::row_t row,
876
                                      vte::grid::column_t column)
877
{
878 879
        set_cursor_column1(column);
        set_cursor_row1(row);
880 881
}

882
/* Delete a character at the current cursor position. */
883
void
884
Terminal::delete_character()
885
{
886 887 888
	VteRowData *rowdata;
	long col;

889
        ensure_cursor_is_onscreen();
890

891
        if (_vte_ring_next(m_screen->row_data) > m_screen->cursor.row) {
892
		long len;
893
		/* Get the data for the row which the cursor points to. */
894
                rowdata = _vte_ring_index_writable(m_screen->row_data, m_screen->cursor.row);
895
		g_assert(rowdata != NULL);
896
                col = m_screen->cursor.col;
897
		len = _vte_row_data_length (rowdata);
898
		/* Remove the column. */
899
		if (col < len) {
900
                        /* Clean up Tab/CJK fragments. */
901
                        cleanup_fragments(col, col + 1);
902
			_vte_row_data_remove (rowdata, col);
903 904 905
                        bool const not_default_bg = (m_fill_defaults.attr.back() != VTE_DEFAULT_BG);

                        if (not_default_bg) {
906 907
                                _vte_row_data_fill(rowdata, &m_fill_defaults, m_column_count);
                                len = m_column_count;
908
			}
909
                        rowdata->attr.soft_wrapped = 0;
910
			/* Repaint this row. */
911
                        invalidate_cells(col, len - col,
912
                                         m_screen->cursor.row, 1);
913 914 915 916
		}
	}

	/* We've modified the display.  Make a note of it. */
917
        m_text_deleted_flag = TRUE;
918 919
}

920
void
921
Terminal::move_cursor_down(vte::grid::row_t rows)
922 923
{
        rows = CLAMP(rows, 1, m_row_count);
924

925 926 927 928
        // FIXMEchpe why not do this afterwards?
        ensure_cursor_is_onscreen();

        vte::grid::row_t end;
929
        // FIXMEchpe why not check DEC_ORIGIN here?
930 931
        if (m_scrolling_restricted) {
                end = m_screen->insert_delta + m_scrolling_region.end;
932
	} else {
933
                end = m_screen->insert_delta + m_row_count - 1;
934 935
	}

936
        m_screen->cursor.row = MIN(m_screen->cursor.row + rows, end);
937 938
}

939
void
940
Terminal::erase_characters(long count)
941 942 943 944 945 946
{
	VteCell *cell;
	long col, i;

        ensure_cursor_is_onscreen();

947
	/* Clear out the given number of characters. */
948
	auto rowdata = ensure_row();
949
        if (_vte_ring_next(m_screen->row_data) > m_screen->cursor.row) {
950
		g_assert(rowdata != NULL);
951
                /* Clean up Tab/CJK fragments. */
952
                cleanup_fragments(m_screen->cursor.col, m_screen->cursor.col + count);
953 954 955
		/* Write over the characters.  (If there aren't enough, we'll
		 * need to create them.) */
		for (i = 0; i < count; i++) {
956
                        col = m_screen->cursor.col + i;
957
			if (col >= 0) {
958
				if (col < (glong) _vte_row_data_length (rowdata)) {
959 960
					/* Replace this cell with the current
					 * defaults. */
961
					cell = _vte_row_data_get_writable (rowdata, col);
962
                                        *cell = m_color_defaults;
963 964
				} else {
					/* Add new cells until we have one here. */
965
                                        _vte_row_data_fill (rowdata, &m_color_defaults, col + 1);
966 967 968 969
				}
			}
		}
		/* Repaint this row. */
970 971
                invalidate_cells(m_screen->cursor.col, count,
                                 m_screen->cursor.row, 1);
972 973 974
	}

	/* We've modified the display.  Make a note of it. */
975
        m_text_deleted_flag = TRUE;
976 977
}

978
/* Insert a blank character. */
979
void
980
Terminal::insert_blank_character()
981 982
{
        ensure_cursor_is_onscreen();
983

984
        auto save = m_screen->cursor;
985
        insert_char(' ', true, true);
986
        m_screen->cursor = save;
987 988
}

989
void
990
Terminal::move_cursor_backward(vte::grid::column_t columns)
991 992 993
{
        ensure_cursor_is_onscreen();

994
        auto col = get_cursor_column_unclamped();
995 996
        columns = CLAMP(columns, 1, col);
        set_cursor_column(col - columns);
997 998
}

999
void
1000
Terminal::move_cursor_forward(vte::grid::column_t columns)
1001 1002 1003 1004 1005
{
        columns = CLAMP(columns, 1, m_column_count);

        ensure_cursor_is_onscreen();

1006
        /* The cursor can be further to the right, don't move in that case. */
1007
        auto col = get_cursor_column_unclamped();
1008
        if (col < m_column_count) {
1009
		/* There's room to move right. */
1010
                set_cursor_column(col + columns);
1011 1012 1013
	}
}

1014
void
1015
Terminal::line_feed()
1016
{
1017 1018
        ensure_cursor_is_onscreen();
        cursor_down(true);
1019
}
1020

1021
void
1022
Terminal::move_cursor_tab_backward(int count)
1023
{
1024 1025 1026
        if (count == 0)
                return;

1027
        auto const newcol = m_tabstops.get_previous(get_cursor_column(), count, 0);
1028 1029
        set_cursor_column(newcol);
}
1030

1031
void
1032
Terminal::move_cursor_tab_forward(int count)
1033 1034 1035
{
        if (count == 0)
                return;
1036

1037
        auto const col = get_cursor_column();
1038

1039
	/* Find the next tabstop, but don't go beyond the end of the line */
1040
        int const newcol = m_tabstops.get_next(col, count, m_column_count - 1);
1041

1042 1043 1044 1045
	/* Make sure we don't move cursor back (see bug #340631) */
        // FIXMEchpe how could this happen!?
	if (col >= newcol)
                return;
1046

1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083
        /* Smart tab handling: bug 353610
         *
         * If we currently don't have any cells in the space this
         * tab creates, we try to make the tab character copyable,
         * by appending a single tab char with lots of fragment
         * cells following it.
         *
         * Otherwise, just append empty cells that will show up
         * as a space each.
         */

        VteRowData *rowdata = ensure_row();
        auto const old_len = _vte_row_data_length (rowdata);
        _vte_row_data_fill (rowdata, &basic_cell, newcol);

        /* Insert smart tab if there's nothing in the line after
         * us, not even empty cells (with non-default background
         * color for example).
         *
         * Notable bugs here: 545924, 597242, 764330
         */
        if (col >= old_len && (newcol - col) <= VTE_TAB_WIDTH_MAX) {
                glong i;
                VteCell *cell = _vte_row_data_get_writable (rowdata, col);
                VteCell tab = *cell;
                tab.attr.set_columns(newcol - col);
                tab.c = '\t';
                /* Save tab char */
                *cell = tab;
                /* And adjust the fragments */
                for (i = col + 1; i < newcol; i++) {
                        cell = _vte_row_data_get_writable (rowdata, i);
                        cell->c = '\t';
                        cell->attr.set_columns(1);
                        cell->attr.set_fragment(true);
                }
        }
1084

1085 1086 1087
        invalidate_cells(m_screen->cursor.col, newcol - m_screen->cursor.col,
                         m_screen->cursor.row, 1);
        m_screen->cursor.col = newcol;
1088 1089
}

1090
void
1091
Terminal::move_cursor_up(vte::grid::row_t rows)
1092
{
1093
        // FIXMEchpe allow 0 as no-op?
1094
        rows = CLAMP(rows, 1, m_row_count);
1095

1096 1097 1098 1099
        //FIXMEchpe why not do this afterward?
        ensure_cursor_is_onscreen();

        vte::grid::row_t start;
1100
        //FIXMEchpe why not check DEC_ORIGIN mode here?
1101 1102
        if (m_scrolling_restricted) {
                start = m_screen->insert_delta + m_scrolling_region.start;
1103
	} else {
1104
		start = m_screen->insert_delta;
1105 1106
	}

1107
        m_screen->cursor.row = MAX(m_screen->cursor.row - rows, start);
1108 1109
}

1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125
/*
 * Parse parameters of SGR 38, 48 or 58, starting at @index within @seq.
 * Returns %true if @seq contained colour parameters at @index, or %false otherwise.
 * In each case, @idx is set to last consumed parameter,
 * and the colour is returned in @color.
 *
 * The format looks like:
 * - 256 color indexed palette:
 *   - ^[[38:5:INDEXm  (de jure standard: ITU-T T.416 / ISO/IEC 8613-6; we also allow and ignore further parameters)
 *   - ^[[38;5;INDEXm  (de facto standard, understood by probably all terminal emulators that support 256 colors)
 * - true colors:
 *   - ^[[38:2:[id]:RED:GREEN:BLUE[:...]m  (de jure standard: ITU-T T.416 / ISO/IEC 8613-6)
 *   - ^[[38:2:RED:GREEN:BLUEm             (common misinterpretation of the standard, FIXME: stop supporting it at some point)
 *   - ^[[38;2;RED;GREEN;BLUEm             (de facto standard, understood by probably all terminal emulators that support true colors)
 * See bugs 685759 and 791456 for details.
 */
1126
template<unsigned int redbits, unsigned int greenbits, unsigned int bluebits>
1127
bool
1128
Terminal::seq_parse_sgr_color(vte::parser::Sequence const& seq,
1129 1130
                                        unsigned int &idx,
                                        uint32_t& color) const noexcept
1131
{
1132 1133 1134
        /* Note that we don't have to check if the index is after the end of
         * the parameters list, since dereferencing is safe and returns -1.
         */
1135

1136 1137 1138
        if (seq.param_nonfinal(idx)) {
                /* Colon version */
                switch (seq.param(++idx)) {
1139
                case VTE_SGR_COLOR_SPEC_RGB: {
1140 1141 1142 1143 1144 1145 1146