ollie/scripts/x/readmulti

104 lines
3.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""Multi-line input reader. C-j inserts newline, C-m (Enter) submits."""
import sys, tty, termios, os, signal
def readchar(fd):
"""Read a single UTF-8 character from fd in raw mode."""
b = os.read(fd, 1)
if not b:
return b
if b[0] < 0x80:
return b
# determine continuation bytes from leading byte
n = 1
if b[0] & 0xe0 == 0xc0: n = 1
elif b[0] & 0xf0 == 0xe0: n = 2
elif b[0] & 0xf8 == 0xf0: n = 3
rest = os.read(fd, n)
return b + rest
def main():
prompt = sys.argv[1] if len(sys.argv) > 1 else '> '
cont = sys.argv[2] if len(sys.argv) > 2 else '. '
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
lines = ['']
cur = 0 # current line index
def redraw_current():
p = prompt if cur == 0 else cont
sys.stderr.write('\r\033[K' + p + lines[cur])
sys.stderr.flush()
sys.stderr.write(prompt)
sys.stderr.flush()
try:
tty.setraw(fd)
while True:
ch = readchar(fd)
if not ch:
break
b = ch[0]
if b == 0x0d: # C-m / Enter -> submit
sys.stderr.write('\r\n')
sys.stderr.flush()
break
elif b == 0x0a: # C-j -> newline
sys.stderr.write('\r\n')
cur += 1
lines.insert(cur, '')
redraw_current()
elif b == 0x03: # C-c
sys.stderr.write('\r\n')
sys.stderr.flush()
termios.tcsetattr(fd, termios.TCSADRAIN, old)
os.kill(os.getpid(), signal.SIGINT)
elif b == 0x04: # C-d
if not any(lines):
sys.stderr.write('\r\n')
sys.stderr.flush()
termios.tcsetattr(fd, termios.TCSADRAIN, old)
sys.exit(1)
else:
sys.stderr.write('\r\n')
sys.stderr.flush()
break
elif b in (0x7f, 0x08): # backspace
if lines[cur]:
lines[cur] = lines[cur][:-1]
redraw_current()
elif cur > 0:
prev = lines[cur - 1]
lines.pop(cur)
cur -= 1
lines[cur] = prev
sys.stderr.write('\033[A')
redraw_current()
elif b == 0x15: # C-u: kill line
lines[cur] = ''
redraw_current()
elif b == 0x17: # C-w: kill word
l = lines[cur].rstrip()
i = l.rfind(' ')
lines[cur] = l[:i+1] if i >= 0 else ''
redraw_current()
elif b == 0x1b: # escape sequence - consume and ignore
os.read(fd, 2)
elif b >= 0x20:
s = ch.decode('utf-8', errors='replace')
lines[cur] += s
sys.stderr.write(s)
sys.stderr.flush()
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old)
text = '\n'.join(lines)
if not text:
sys.exit(1)
sys.stdout.write(text)
if __name__ == '__main__':
main()