Kittens learn about bracketed paste
This commit is contained in:
parent
13c034c613
commit
f4f0b8fd5f
@ -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):
|
||||
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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,;(')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user