summaryrefslogtreecommitdiff
path: root/src/pastify.cc
blob: a52e0e46ab86f973c1961c56807c24dcc45c4224 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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
102
103
104
105
106
107
108
/*
 * Copyright © 2015, 2019, Egmont Koblinger
 * Copyright © 2015, 2018, 2019, 2020, 2021 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 Lesser General Public License
 * along with this library.  If not, see <https://www.gnu.org/licenses/>.
 */

#include <config.h>

#include "pastify.hh"

namespace vte::terminal {

using namespace std::literals;

/*
 * pastify_string:
 * @str:
 * @insert_brackets:
 * @c1:
 *
 * Converts @str into a form safe for pasting to the child. Elide
 * C0 controls except NUL, HT, CR, LF, and C1 controls.
 * We also convert newlines to carriage returns, which more software
 * is able to cope with (cough, pico, cough).
 *
 * Also insert bracketed paste controls around the string if
 * @insert_brackets is true, using C1 CSI if @c1 is true or C0 controls
 * otherwise.
 */
std::string
pastify_string(std::string_view str,
               bool insert_brackets,
               bool c1)
{
        auto rv = std::string{};
        rv.reserve(str.size() + 1 + insert_brackets ? 12 : 0);

        if (insert_brackets) {
                if (c1)
                        rv.append("\xc2\x9b" "200~");
                else
                        rv.append("\e[200~");
        }

        /* C0 \ { NUL, HT, CR, LF } + { DEL } + { C1 control start byte } */
        auto const controls = "\x01\x02\x03\x04\x05\x06\x07\x08\x0a\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f\xc2"sv;

        auto next_char = [&str](size_t pos) constexpr noexcept -> unsigned char
        {
                return pos + 1 < str.size() ? str[pos + 1] : 0;
        };

        while (str.size() != 0) {
                auto run = str.find_first_of(controls, 0);

                rv.append(str, 0, run);
                if (run == str.npos)
                        break;

                switch (str[run]) {
                case '\x0a':
                        rv.push_back('\x0d');
                        ++run;
                        break;
                case '\xc2': {
                        auto const c = next_char(run);
                        if (c >= 0x80 && c <= 0x9f) {
                                /* Skip both bytes of a C1 */
                                run += 2;
                        } else {
                                /* Move along, nothing to see here */
                                rv.push_back('\xc2');
                                ++run;
                        }
                        break;
                }
                default:
                        /* Swallow this byte */
                        ++run;
                        break;
                }

                str = str.substr(run);
        }

        if (insert_brackets) {
                if (c1)
                        rv.append("\xc2\x9b" "201~");
                else
                        rv.append("\e[201~");
        }

        return rv;
}

} // namespace vte::terminal