import sys import tty import termios def syntax_highlight(text, sel_start=None, sel_end=None): if sel_start is not None and sel_end is not None and sel_start != sel_end: s = min(sel_start, sel_end) e = max(sel_start, sel_end) result = text[:s] + u"\u001b[7m" + text[s:e] + u"\u001b[0m" + text[e:] stripped = result return stripped stripped = text.rstrip() trailing = len(text) - len(stripped) if trailing > 0: return stripped + u"\u001b[41m" + " " * trailing + u"\u001b[0m" return text def word_boundary_left(text, index): i = index while i > 0 and text[i - 1] == " ": i -= 1 while i > 0 and text[i - 1] != " ": i -= 1 return i def word_boundary_right(text, index): i = index while i < len(text) and text[i] == " ": i += 1 while i < len(text) and text[i] != " ": i += 1 return i def render(prompt, prompt_len, text, index, sel_anchor): sys.stdout.write(u"\u001b[1000D") sys.stdout.write(u"\u001b[0K") sys.stdout.write(prompt) sys.stdout.write(syntax_highlight(text, sel_anchor, index)) sys.stdout.write(u"\u001b[1000D") target = prompt_len + index if target > 0: sys.stdout.write(u"\u001b[" + str(target) + "C") sys.stdout.flush() def read_char(): return ord(sys.stdin.read(1)) def command_line(): fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) prompt = u"\u001b[32;1m>>> \u001b[0m" prompt_len = 4 try: tty.setraw(fd) while True: text = "" index = 0 sel_anchor = None sys.stdout.write(prompt) sys.stdout.flush() while True: char = read_char() if char == 3: sys.stdout.write(u"\r\n") sys.stdout.flush() return elif char == 4: if not text: sys.stdout.write(u"\r\n") sys.stdout.flush() return elif char == 1: index = 0 sel_anchor = None elif char == 5: index = len(text) sel_anchor = None elif char == 11: text = text[:index] sel_anchor = None elif char == 21: text = text[index:] index = 0 sel_anchor = None elif char == 23: new_index = word_boundary_left(text, index) text = text[:new_index] + text[index:] index = new_index sel_anchor = None elif 32 <= char <= 126: if sel_anchor is not None: s = min(sel_anchor, index) e = max(sel_anchor, index) text = text[:s] + chr(char) + text[e:] index = s + 1 sel_anchor = None else: text = text[:index] + chr(char) + text[index:] index += 1 elif char in {10, 13}: sys.stdout.write(u"\u001b[1000D") sys.stdout.write(u"\u001b[0K") sys.stdout.write(prompt + text + u"\r\n") sys.stdout.write(u"\u001b[36m=>\u001b[0m " + text + u"\r\n") sys.stdout.flush() sel_anchor = None break elif char == 127: if sel_anchor is not None: s = min(sel_anchor, index) e = max(sel_anchor, index) text = text[:s] + text[e:] index = s sel_anchor = None elif index > 0: text = text[:index - 1] + text[index:] index -= 1 elif char == 27: next1 = read_char() if next1 == 91: next2 = read_char() if next2 == 68: index = max(0, index - 1) sel_anchor = None elif next2 == 67: index = min(len(text), index + 1) sel_anchor = None elif next2 == 65: pass elif next2 == 66: pass elif next2 == 72: index = 0 sel_anchor = None elif next2 == 70: index = len(text) sel_anchor = None elif next2 == 51: next3 = read_char() if next3 == 126: if sel_anchor is not None: s = min(sel_anchor, index) e = max(sel_anchor, index) text = text[:s] + text[e:] index = s sel_anchor = None elif index < len(text): text = text[:index] + text[index + 1:] elif next2 == 52: next3 = read_char() if next3 == 126: index = len(text) sel_anchor = None elif next2 == 49: next3 = read_char() if next3 == 126: index = 0 sel_anchor = None elif next3 == 59: next4 = read_char() next5 = read_char() if next4 == 50: if next5 == 68: if sel_anchor is None: sel_anchor = index index = max(0, index - 1) elif next5 == 67: if sel_anchor is None: sel_anchor = index index = min(len(text), index + 1) elif next5 == 72: if sel_anchor is None: sel_anchor = index index = 0 elif next5 == 70: if sel_anchor is None: sel_anchor = index index = len(text) elif next4 == 53: if next5 == 68: index = word_boundary_left(text, index) sel_anchor = None elif next5 == 67: index = word_boundary_right(text, index) sel_anchor = None elif next1 == 98: index = word_boundary_left(text, index) sel_anchor = None elif next1 == 102: index = word_boundary_right(text, index) sel_anchor = None elif next1 == 100: new_index = word_boundary_right(text, index) text = text[:index] + text[new_index:] sel_anchor = None elif next1 == 127: new_index = word_boundary_left(text, index) text = text[:new_index] + text[index:] index = new_index sel_anchor = None elif next1 == 98: index = word_boundary_left(text, index) render(prompt, prompt_len, text, index, sel_anchor) finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) sys.stdout.write(u"\u001b[0m\r\n") sys.stdout.flush() command_line()