Files
t/t.py
2018-05-11 17:10:22 +02:00

192 lines
5.1 KiB
Python
Executable File

#!/usr/bin/env python
import copy
import sys
import tty
import termios
NORMAL = 0
EDIT = 1
META = 2
UP = 1000
DOWN = 1001
LEFT = 1002
RIGHT = 1003
class Editor():
def __init__(self, f):
lines = [l.replace("\n", "") for l in f]
self.buffer = Buffer(lines)
self.cursor = Cursor(0, 0)
self.mode = NORMAL
self.history = []
def set_raw(self):
fd = sys.stdin.fileno()
self.old_settings = termios.tcgetattr(fd)
tty.setraw(fd)
def unset_raw(self):
fd = sys.stdin.fileno()
termios.tcsetattr(fd, termios.TCSADRAIN, self.old_settings)
def run(self):
self.running = True
self.set_raw()
while self.running:
self.render()
if self.mode == NORMAL: self.handle_input()
if self.mode == EDIT: self.handle_edit()
if self.mode == META: self.handle_meta()
self.unset_raw()
def render(self):
ANSI.clear_screen()
ANSI.move_cursor(0, 0)
self.buffer.render()
ANSI.move_cursor(self.cursor.row, self.cursor.col)
def handle_meta(self):
char = sys.stdin.read(1)
if char == 'q': self.running = False
def handle_edit(self):
char = self.read_input()
if char == chr(27): # ESC
self.mode = NORMAL
elif char == chr(127) and self.cursor.col: # BACK
self.save_snapshot()
self.buffer = self.buffer.delete(self.cursor)
self.cursor = self.cursor.left(self.buffer)
elif char == '\r':
self.save_snapshot()
self.buffer = self.buffer.split_line(self.cursor)
self.cursor = Cursor(self.cursor.row+1, 0)
elif char == UP: self.cursor = self.cursor.up(self.buffer)
elif char == DOWN: self.cursor = self.cursor.down(self.buffer)
elif char == LEFT: self.cursor = self.cursor.left(self.buffer)
elif char == RIGHT: self.cursor = self.cursor.right(self.buffer)
else:
self.save_snapshot()
self.buffer = self.buffer.insert(char, self.cursor)
self.cursor = self.cursor.right(self.buffer)
def read_input(self):
char = sys.stdin.read(1)
if char == '\x1b':
nchar = sys.stdin.read(1)
if nchar != '[': return char
char = sys.stdin.read(1)
if char == 'A': return UP
if char == 'B': return DOWN
if char == 'C': return RIGHT
if char == 'D': return LEFT
return char
def handle_input(self):
char = self.read_input()
if char == ':': self.mode = META
elif char == 'w' or char == UP:
self.cursor = self.cursor.up(self.buffer)
elif char == 's' or char == DOWN:
self.cursor = self.cursor.down(self.buffer)
elif char == 'a' or char == LEFT:
self.cursor = self.cursor.left(self.buffer)
elif char == 'd' or char == RIGHT:
self.cursor = self.cursor.right(self.buffer)
elif char == 'u': self.restore_snapshot()
elif char == 'e': self.mode = EDIT
def save_snapshot(self):
self.history.append([self.buffer, self.cursor])
def restore_snapshot(self):
if len(self.history): self.buffer, self.cursor = self.history.pop()
class ANSI():
@staticmethod
def ansi(s):
return "\x1b[" + s
@staticmethod
def clear_screen():
sys.stdout.write(ANSI.ansi("2J"))
@staticmethod
def move_cursor(x, y):
sys.stdout.write(ANSI.ansi("{};{}H".format(x+1, y+1)))
class Buffer():
def __init__(self, lines):
self.lines = lines
def render(self):
for line in self.lines:
sys.stdout.write(line + "\r\n")
def line_count(self):
return len(self.lines)
def line_length(self, x):
return len(self.lines[x])
def insert(self, char, cursor):
lines = copy.copy(self.lines)
line = lines[cursor.row]
lines[cursor.row] = line[:cursor.col] + char + line[cursor.col:]
return Buffer(lines)
def delete(self, cursor):
lines = copy.copy(self.lines)
line = lines[cursor.row]
lines[cursor.row] = line[:cursor.col-1] + line[cursor.col:]
return Buffer(lines)
def split_line(self, cursor):
lines = copy.copy(self.lines)
line = lines[cursor.row]
before, after = line[:cursor.col], line[cursor.col:]
lines[cursor.row] = before
lines.insert(cursor.row+1, after)
return Buffer(lines)
class Cursor():
def __init__(self, row=0, col=0):
self.row = row
self.col = col
def up(self, buf):
return Cursor(self.row-1, self.col).clamp(buf)
def down(self, buf):
return Cursor(self.row+1, self.col).clamp(buf)
def left(self, buf):
return Cursor(self.row, self.col-1).clamp(buf)
def right(self, buf):
return Cursor(self.row, self.col+1).clamp(buf)
def clamp(self, buf):
row = max(0, min(self.row, buf.line_count()-1))
col = max(0, min(self.col, buf.line_length(row)))
return Cursor(row, col)
with open(sys.argv[1]) as f:
Editor(f).run()