Kittens learn about bracketed paste

This commit is contained in:
Kovid Goyal 2018-02-07 10:39:05 +05:30
parent 13c034c613
commit f4f0b8fd5f
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 56 additions and 10 deletions

View File

@ -56,7 +56,7 @@ def sanitize_term(output_fd):
class Loop: 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.input_fd = input_fd or sys.stdin.fileno()
self.output_fd = output_fd or sys.stdout.fileno() self.output_fd = output_fd or sys.stdout.fileno()
self.wakeup_read_fd, self.wakeup_write_fd = os.pipe() self.wakeup_read_fd, self.wakeup_write_fd = os.pipe()
@ -76,6 +76,10 @@ class Loop:
self.iov_limit = 255 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.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.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): def _read_ready(self, handler):
if not self.read_allowed: if not self.read_allowed:
@ -89,24 +93,34 @@ class Loop:
self.read_buf = data self.read_buf = data
self.handler = handler self.handler = handler
try: 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: finally:
del self.handler del self.handler
def on_text(self, text): 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): for chunk in self.ebs_pat.split(text):
if chunk == '\r': if chunk == '\r':
self.handler.on_key(enter_key) self.handler.on_key(enter_key)
elif chunk == '\177': elif chunk == '\177':
self.handler.on_key(backspace_key) self.handler.on_key(backspace_key)
else: else:
self.handler.on_text(chunk) self.handler.on_text(chunk, self.in_bracketed_paste)
def on_dcs(self, dcs): def on_dcs(self, dcs):
pass pass
def on_csi(self, csi): 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): def on_pm(self, pm):
pass pass

View File

@ -12,14 +12,24 @@ parse_input_from_terminal(PyObject *self UNUSED, PyObject *args) {
enum State { NORMAL, ESC, CSI, ST, ESC_ST }; enum State { NORMAL, ESC, CSI, ST, ESC_ST };
enum State state = NORMAL; enum State state = NORMAL;
PyObject *uo, *text_callback, *dcs_callback, *csi_callback, *osc_callback, *pm_callback, *apc_callback, *callback; 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; Py_ssize_t sz = PyUnicode_GET_LENGTH(uo), pos = 0, start = 0, count = 0, consumed = 0;
callback = text_callback; callback = text_callback;
int kind = PyUnicode_KIND(uo); int kind = PyUnicode_KIND(uo);
void *data = PyUnicode_DATA(uo); void *data = PyUnicode_DATA(uo);
#define CALL(cb, s, num) {\ bool in_bracketed_paste_mode = inbp != 0;
if (num > 0) PyObject_CallFunction(cb, "N", PyUnicode_Substring(uo, s, s + num)); \ #define CALL(cb, s_, num_) {\
consumed = 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; \ count = 0; \
} }
START_ALLOW_CASE_RANGE; START_ALLOW_CASE_RANGE;
@ -30,6 +40,7 @@ parse_input_from_terminal(PyObject *self UNUSED, PyObject *args) {
if (ch == 0x1b) { if (ch == 0x1b) {
state = ESC; state = ESC;
CALL(text_callback, start, count); CALL(text_callback, start, count);
start = pos;
} else count++; } else count++;
break; break;
case ESC: case ESC:
@ -55,7 +66,17 @@ parse_input_from_terminal(PyObject *self UNUSED, PyObject *args) {
switch (ch) { switch (ch) {
case 'a' ... 'z': case 'a' ... 'z':
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); CALL(callback, start + 1, count);
if (IBP('0')) in_bracketed_paste_mode = true;
#undef IBP
state = NORMAL; state = NORMAL;
start = pos + 1; start = pos + 1;
break; break;

View File

@ -339,17 +339,28 @@ class TestDataTypes(BaseTest):
self.ae(wcswidth('\u2716\u2716\ufe0f\U0001f337'), 5) self.ae(wcswidth('\u2716\u2716\ufe0f\U0001f337'), 5)
self.ae(sanitize_title('a\0\01 \t\n\f\rb'), 'a b') 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 = [], [], [], [] text_r, csi_r, apc_r, rest = [], [], [], []
left = '' 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: 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(left, leftover)
self.ae(text, ' '.join(text_r)) self.ae(text, ' '.join(text_r))
self.ae(csi, ' '.join(csi_r)) self.ae(csi, ' '.join(csi_r))
self.ae(apc, ' '.join(apc_r)) self.ae(apc, ' '.join(apc_r))
self.assertFalse(rest) 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('abc', text='abc')
tp('a\033[38:5:12:32mb', text='a b', csi='38:5:12:32m') 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,;(') tp('a\033_x,;(\033\\b', text='a b', apc='x,;(')