diff --git a/kittens/tui/loop.py b/kittens/tui/loop.py index f334ce919..599d0e91c 100644 --- a/kittens/tui/loop.py +++ b/kittens/tui/loop.py @@ -56,7 +56,7 @@ def sanitize_term(output_fd): class Loop: - def __init__(self, input_fd=None, output_fd=None): + def __init__(self, input_fd=None, output_fd=None, sanitize_bracketed_paste='[\x0e\x0f\r\x07\x7f\x8d\x8e\x8f\x90\x9b\x9d\x9e\x9f]'): self.input_fd = input_fd or sys.stdin.fileno() self.output_fd = output_fd or sys.stdout.fileno() self.wakeup_read_fd, self.wakeup_write_fd = os.pipe() @@ -76,6 +76,10 @@ class Loop: self.iov_limit = 255 self.parse_input_from_terminal = partial(parse_input_from_terminal, self.on_text, self.on_dcs, self.on_csi, self.on_osc, self.on_pm, self.on_apc) self.ebs_pat = re.compile('([\177\r])') + self.in_bracketed_paste = False + self.sanitize_bracketed_paste = bool(sanitize_bracketed_paste) + if self.sanitize_bracketed_paste: + self.sanitize_ibp_pat = re.compile(sanitize_bracketed_paste) def _read_ready(self, handler): if not self.read_allowed: @@ -89,24 +93,34 @@ class Loop: self.read_buf = data self.handler = handler try: - self.read_buf = self.parse_input_from_terminal(self.read_buf) + self.read_buf = self.parse_input_from_terminal(self.read_buf, self.in_bracketed_paste) finally: del self.handler def on_text(self, text): + if self.in_bracketed_paste and self.sanitize_bracketed_paste: + text = self.sanitize_ibp_pat.sub('', text) + for chunk in self.ebs_pat.split(text): if chunk == '\r': self.handler.on_key(enter_key) elif chunk == '\177': self.handler.on_key(backspace_key) else: - self.handler.on_text(chunk) + self.handler.on_text(chunk, self.in_bracketed_paste) def on_dcs(self, dcs): pass def on_csi(self, csi): - pass + q = csi[-1] + if q in 'mM': + pass + elif q == '~': + if csi == '200~': + self.in_bracketed_paste = True + elif csi == '201~': + self.in_bracketed_paste = False def on_pm(self, pm): pass diff --git a/kitty/kittens.c b/kitty/kittens.c index 91879bd1e..d30bd18f7 100644 --- a/kitty/kittens.c +++ b/kitty/kittens.c @@ -12,14 +12,24 @@ parse_input_from_terminal(PyObject *self UNUSED, PyObject *args) { enum State { NORMAL, ESC, CSI, ST, ESC_ST }; enum State state = NORMAL; PyObject *uo, *text_callback, *dcs_callback, *csi_callback, *osc_callback, *pm_callback, *apc_callback, *callback; - if (!PyArg_ParseTuple(args, "OOOOOOU", &text_callback, &dcs_callback, &csi_callback, &osc_callback, &pm_callback, &apc_callback, &uo)) return NULL; + int inbp = 0; + if (!PyArg_ParseTuple(args, "OOOOOOUp", &text_callback, &dcs_callback, &csi_callback, &osc_callback, &pm_callback, &apc_callback, &uo, &inbp)) return NULL; Py_ssize_t sz = PyUnicode_GET_LENGTH(uo), pos = 0, start = 0, count = 0, consumed = 0; callback = text_callback; int kind = PyUnicode_KIND(uo); void *data = PyUnicode_DATA(uo); -#define CALL(cb, s, num) {\ - if (num > 0) PyObject_CallFunction(cb, "N", PyUnicode_Substring(uo, s, s + num)); \ - consumed = s + num; \ + bool in_bracketed_paste_mode = inbp != 0; +#define CALL(cb, s_, num_) {\ + PyObject *fcb = cb; \ + Py_ssize_t s = s_, num = num_; \ + if (in_bracketed_paste_mode && fcb != text_callback) { \ + fcb = text_callback; num += 2; s -= 2; \ + } \ + if (num > 0) { \ + PyObject *ret = PyObject_CallFunction(fcb, "N", PyUnicode_Substring(uo, s, s + num)); \ + Py_XDECREF(ret); \ + } \ + consumed = s_ + num_; \ count = 0; \ } START_ALLOW_CASE_RANGE; @@ -30,6 +40,7 @@ parse_input_from_terminal(PyObject *self UNUSED, PyObject *args) { if (ch == 0x1b) { state = ESC; CALL(text_callback, start, count); + start = pos; } else count++; break; case ESC: @@ -55,7 +66,17 @@ parse_input_from_terminal(PyObject *self UNUSED, PyObject *args) { switch (ch) { case 'a' ... 'z': case 'A' ... 'Z': + case '@': + case '`': + case '{': + case '|': + case '}': + case '~': +#define IBP(w) ch == '~' && PyUnicode_READ(kind, data, start + 1) == '2' && PyUnicode_READ(kind, data, start + 2) == '0' && PyUnicode_READ(kind, data, start + 3) == w + if (IBP('1')) in_bracketed_paste_mode = false; CALL(callback, start + 1, count); + if (IBP('0')) in_bracketed_paste_mode = true; +#undef IBP state = NORMAL; start = pos + 1; break; diff --git a/kitty_tests/datatypes.py b/kitty_tests/datatypes.py index 029af455a..44a76111b 100644 --- a/kitty_tests/datatypes.py +++ b/kitty_tests/datatypes.py @@ -339,17 +339,28 @@ class TestDataTypes(BaseTest): self.ae(wcswidth('\u2716\u2716\ufe0f\U0001f337'), 5) self.ae(sanitize_title('a\0\01 \t\n\f\rb'), 'a b') - def tp(*data, leftover='', text='', csi='', apc=''): + def tp(*data, leftover='', text='', csi='', apc='', ibp=False): text_r, csi_r, apc_r, rest = [], [], [], [] left = '' + in_bp = ibp + + def on_csi(x): + nonlocal in_bp + if x == '200~': + in_bp = True + elif x == '201~': + in_bp = False + csi_r.append(x) + for d in data: - left = parse_input_from_terminal(text_r.append, rest.append, csi_r.append, rest.append, rest.append, apc_r.append, left + d) + left = parse_input_from_terminal(text_r.append, rest.append, on_csi, rest.append, rest.append, apc_r.append, left + d, in_bp) self.ae(left, leftover) self.ae(text, ' '.join(text_r)) self.ae(csi, ' '.join(csi_r)) self.ae(apc, ' '.join(apc_r)) self.assertFalse(rest) + tp('a\033[200~\033[32mxy\033[201~\033[33ma', text='a \033[32m xy a', csi='200~ 201~ 33m') tp('abc', text='abc') tp('a\033[38:5:12:32mb', text='a b', csi='38:5:12:32m') tp('a\033_x,;(\033\\b', text='a b', apc='x,;(')