diff-edit/diff_edit/editor.py

664 lines
24 KiB
Python
Raw Normal View History

2022-01-01 17:37:20 +10:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import asyncio
import contextlib
import functools
import os
2022-01-01 17:37:20 +10:00
import string
import sys
import fill3
import fill3.terminal as terminal
import pygments
import pygments.lexers
import pygments.styles
import termstr
2022-01-08 15:05:40 +10:00
2022-01-01 17:37:20 +10:00
@functools.lru_cache(maxsize=100)
def highlight_str(line, bg_color, transparency=0.6):
def blend_style(style):
return termstr.CharStyle(termstr.blend_color(style.fg_color, bg_color, transparency),
termstr.blend_color(style.bg_color, bg_color, transparency),
is_bold=style.is_bold, is_italic=style.is_italic,
is_underlined=style.is_underlined)
2022-01-01 17:37:20 +10:00
return termstr.TermStr(line).transform_style(blend_style)
PYTHON_LEXER = pygments.lexers.get_lexer_by_name("python")
NATIVE_STYLE = pygments.styles.get_style_by_name("paraiso-dark")
def _syntax_highlight(text, lexer, style):
@functools.lru_cache(maxsize=500)
def _parse_rgb(hex_rgb):
if hex_rgb.startswith("#"):
hex_rgb = hex_rgb[1:]
return tuple(int("0x" + hex_rgb[index:index+2], base=16) for index in [0, 2, 4])
@functools.lru_cache(maxsize=500)
def _char_style_for_token_type(token_type, default_bg_color, default_style):
try:
token_style = style.style_for_token(token_type)
except KeyError:
return default_style
fg_color = (termstr.Color.black if token_style["color"] is None
else _parse_rgb(token_style["color"]))
bg_color = (default_bg_color if token_style["bgcolor"] is None
else _parse_rgb(token_style["bgcolor"]))
return termstr.CharStyle(fg_color, bg_color, token_style["bold"], token_style["italic"],
token_style["underline"])
default_bg_color = _parse_rgb(style.background_color)
default_style = termstr.CharStyle(bg_color=default_bg_color)
text = fill3.join("", [termstr.TermStr(
text, _char_style_for_token_type(token_type, default_bg_color, default_style))
2022-01-01 17:37:20 +10:00
for token_type, text in pygments.lex(text, lexer)])
text_widget = fill3.Text(text, pad_char=termstr.TermStr(" ").bg_color(default_bg_color))
return fill3.join("\n", text_widget.text)
class Text:
def __init__(self, text, padding_char=" "):
self.padding_char = padding_char
2022-01-01 17:37:20 +10:00
self.text, self.actual_text, self.max_line_length = [], [], 0
lines = [""] if text == "" else text.splitlines()
if text.endswith("\n"):
lines.append("")
self[:] = lines
def __len__(self):
return len(self.text)
def __getitem__(self, line_index):
return self.actual_text[line_index]
def _convert_line(self, line, max_line_length):
return line.ljust(max_line_length)
def __setitem__(self, key, value):
if type(key) == int:
self._replace_lines(slice(key, key + 1), [value])
else: # slice
self._replace_lines(key, value)
def _replace_lines(self, slice_, new_lines):
max_new_lengths = max(len(line) for line in new_lines)
2022-01-01 17:37:20 +10:00
if max_new_lengths > self.max_line_length:
padding = self.padding_char * (max_new_lengths - self.max_line_length)
2022-01-01 17:37:20 +10:00
self.text = [line + padding for line in self.text]
self.max_line_length = max_new_lengths
converted_lines = [self._convert_line(line, self.max_line_length) for line in new_lines]
self.text[slice_], self.actual_text[slice_] = converted_lines, new_lines
2022-01-01 17:37:20 +10:00
new_max_line_length = max(len(line) for line in self.actual_text)
if new_max_line_length < self.max_line_length:
clip_width = self.max_line_length - new_max_line_length
self.text = [line[:-clip_width] for line in self.text]
self.max_line_length = new_max_line_length
def insert(self, index, line):
self._replace_lines(slice(index, index), [line])
def append(self, line):
self.insert(len(self.text), line)
def get_text(self):
return "\n".join(self)
def appearance(self):
2022-01-01 17:37:20 +10:00
return self.text
def appearance_for(self, dimensions):
return fill3.appearance_resize(self.appearance(), dimensions)
2022-01-01 17:37:20 +10:00
class Code(Text):
def __init__(self, text, path, theme=NATIVE_STYLE):
self.lexer = pygments.lexers.get_lexer_for_filename(path, text, stripnl=False)
2022-01-01 17:37:20 +10:00
self.theme = theme
padding_char = _syntax_highlight(" ", self.lexer, theme)
Text.__init__(self, text, padding_char)
2022-01-01 17:37:20 +10:00
def _convert_line(self, line, max_line_length):
return (termstr.TermStr(line.ljust(max_line_length)) if self.theme is None
else _syntax_highlight(line.ljust(max_line_length), self.lexer, self.theme))
2022-01-01 17:37:20 +10:00
def syntax_highlight_all(self):
if self.theme is None:
self.text = [termstr.TermStr(line.ljust(self.max_line_length))
for line in self.get_text().splitlines()]
else:
self.padding_char = _syntax_highlight(" ", self.lexer, self.theme)
highlighted = _syntax_highlight(self.get_text(), self.lexer, self.theme)
self.text = [line.ljust(self.max_line_length) for line in highlighted.splitlines()]
class Decor:
def __init__(self, widget, decorator):
self.widget = widget
self.decorator = decorator
def appearance_for(self, dimensions):
return self.decorator(self.widget.appearance_for(dimensions))
2022-01-01 17:37:20 +10:00
def appearance(self):
return self.decorator(self.widget.appearance())
2022-01-01 17:37:20 +10:00
def highlight_part(line, start, end):
return (line[:start] + highlight_str(line[start:end], termstr.Color.white, transparency=0.7) +
line[end:])
2022-01-01 17:37:20 +10:00
def add_highlights(self, appearance):
result = appearance.copy()
if not self.is_editing:
return result
if self.mark is None:
result[self.cursor_y] = highlight_str(result[self.cursor_y], termstr.Color.white, 0.8)
2022-01-01 17:37:20 +10:00
else:
(start_x, start_y), (end_x, end_y) = self.get_selection_interval()
if start_y == end_y:
result[start_y] = highlight_part(result[start_y], start_x, end_x)
else:
result[start_y] = highlight_part(result[start_y], start_x, len(result[start_y]))
2022-01-01 17:37:20 +10:00
view_x, view_y = self.view_widget.position
for line_num in range(max(start_y+1, view_y), min(end_y, view_y + self.last_height)):
result[line_num] = highlight_part(result[line_num], 0, len(result[line_num]))
2022-01-01 17:37:20 +10:00
result[end_y] = highlight_part(result[end_y], 0, end_x)
if self.cursor_x >= len(result[0]):
result = fill3.appearance_resize(result, (self.cursor_x+1, len(result)))
2022-01-01 17:37:20 +10:00
cursor_line = result[self.cursor_y]
result[self.cursor_y] = (cursor_line[:self.cursor_x] +
termstr.TermStr(cursor_line[self.cursor_x]).invert() +
cursor_line[self.cursor_x+1:])
2022-01-01 17:37:20 +10:00
return result
class Editor:
THEMES = [pygments.styles.get_style_by_name(style)
for style in ["monokai", "fruity", "native"]] + [None]
def __init__(self, text="", path="Untitled", is_left_aligned=True):
self.path = os.path.normpath(path)
self.is_left_aligned = is_left_aligned
self.set_text(text)
2022-01-01 17:37:20 +10:00
self.mark = None
self.clipboard = None
self.last_width = 100
self.last_height = 40
self.is_editing = True
self.theme_index = 0
self.is_overwriting = False
self.previous_term_code = None
2022-01-13 00:51:04 +10:00
self.history = []
2022-01-01 17:37:20 +10:00
@property
def cursor_x(self):
line_length = len(self.text_widget.actual_text[self.cursor_y])
return min(self._cursor_x, line_length)
@cursor_x.setter
def cursor_x(self, x):
self._cursor_x = x
@property
def cursor_y(self):
return self._cursor_y
@cursor_y.setter
def cursor_y(self, y):
if y < 0 or y >= len(self.text_widget):
raise IndexError
self._cursor_y = y
@property
def scroll_position(self):
return self.view_widget.position
@scroll_position.setter
def scroll_position(self, position):
self.view_widget.position = position
2022-01-01 17:37:20 +10:00
def get_selection_interval(self):
mark_x, mark_y = self.mark
(start_y, start_x), (end_y, end_x) = sorted(
[(mark_y, mark_x), (self.cursor_y, self.cursor_x)])
return (start_x, start_y), (end_x, end_y)
def set_text(self, text):
try:
self.text_widget = Code(text, self.path)
except pygments.util.ClassNotFound: # No lexer for path
self.text_widget = Text(text)
self.decor_widget = Decor(self.text_widget,
lambda appearance: add_highlights(self, appearance))
2022-01-01 17:37:20 +10:00
self.view_widget = fill3.View.from_widget(self.decor_widget)
self.view_widget.portal.is_scroll_limited = True
if not self.is_left_aligned:
self.view_widget.portal.is_left_aligned = False
2022-01-01 17:37:20 +10:00
self.cursor_x, self.cursor_y = 0, 0
self.original_text = self.text_widget.actual_text.copy()
2022-01-04 00:10:32 +10:00
def prefix(self):
pass
2022-01-01 17:37:20 +10:00
def load(self, path):
self.path = os.path.normpath(path)
with open(path) as file_:
self.set_text(file_.read())
2022-01-01 17:37:20 +10:00
def save(self):
2022-01-04 00:10:32 +10:00
if self.previous_term_code == terminal.CTRL_X:
with open(self.path, "w") as file_:
file_.write(self.text_widget.get_text())
2022-01-04 00:10:32 +10:00
self.original_text = self.text_widget.actual_text.copy()
2022-01-01 17:37:20 +10:00
def backspace(self):
if self.cursor_x == 0:
if self.cursor_y != 0:
self.set_mark()
self.cursor_left()
self.delete_selection()
else:
line = self.text_widget[self.cursor_y]
new_line = line[:self.cursor_x-1] + line[self.cursor_x:]
self.cursor_x -= 1
self.text_widget[self.cursor_y] = new_line
def cursor_left(self):
if self.cursor_x == 0:
self.cursor_up()
self.jump_to_end_of_line()
else:
self.cursor_x -= 1
def cursor_right(self):
if self.cursor_x == len(self.text_widget.actual_text[self.cursor_y]):
self.cursor_down()
self.jump_to_start_of_line()
else:
self.cursor_x += 1
def cursor_up(self):
self.cursor_y -= 1
def cursor_down(self):
self.cursor_y += 1
def page_up(self):
new_y = self.cursor_y - self.last_height // 2
self.cursor_x, self.cursor_y = 0, max(0, new_y)
def page_down(self):
new_y = self.cursor_y + self.last_height // 2
self.cursor_x, self.cursor_y = 0, min(len(self.text_widget.text) - 1, new_y)
2022-01-01 17:37:20 +10:00
def jump_to_start_of_line(self):
self.cursor_x = 0
def jump_to_end_of_line(self):
self.cursor_x = len(self.text_widget.actual_text[self.cursor_y])
def open_line(self):
line = self.text_widget[self.cursor_y]
self.text_widget[self.cursor_y:self.cursor_y+1] = \
[line[:self.cursor_x], line[self.cursor_x:]]
def enter(self):
self.open_line()
self.cursor_x, self.cursor_y = 0, self.cursor_y + 1
def set_mark(self):
self.mark = self.cursor_x, self.cursor_y
def drop_highlight(self):
self.mark = None
def copy_selection(self):
if self.mark is not None:
(start_x, start_y), (end_x, end_y) = self.get_selection_interval()
selection = [self.text_widget[line_num] for line_num in range(start_y, end_y+1)]
2022-01-01 17:37:20 +10:00
selection[-1] = selection[-1][:end_x]
selection[0] = selection[0][start_x:]
self.clipboard = selection
self.mark = None
def delete_selection(self):
if self.mark is not None:
(start_x, start_y), (end_x, end_y) = self.get_selection_interval()
self.copy_selection()
start_line = self.text_widget[start_y]
end_line = self.text_widget[end_y]
new_line = start_line[:start_x] + end_line[end_x:]
self.text_widget[start_y:end_y+1] = [new_line]
self.cursor_x, self.cursor_y = start_x, start_y
def insert_text(self, text, is_overwriting=False):
2022-01-01 17:37:20 +10:00
try:
current_line = self.text_widget[self.cursor_y]
replace_count = len(text) if is_overwriting else 0
self.text_widget[self.cursor_y] = (current_line[:self.cursor_x] + text
+ current_line[self.cursor_x+replace_count:])
2022-01-01 17:37:20 +10:00
except IndexError:
self.text_widget.append(text)
self.cursor_x += len(text)
def delete_character(self):
self.cursor_right()
self.backspace()
def delete_right(self):
self.set_mark()
self.next_word()
self.delete_selection()
def paste_from_clipboard(self):
if self.clipboard is not None:
for line in self.clipboard[:-1]:
self.insert_text(line)
self.enter()
self.insert_text(self.clipboard[-1])
def _is_on_empty_line(self):
return self.text_widget[self.cursor_y].strip() == ""
def _jump_to_block_edge(self, direction_func):
self.jump_to_start_of_line()
while self._is_on_empty_line():
direction_func()
while not self._is_on_empty_line():
direction_func()
def jump_to_block_start(self):
return self._jump_to_block_edge(self.cursor_up)
def jump_to_block_end(self):
return self._jump_to_block_edge(self.cursor_down)
WORD_CHARS = string.ascii_letters + string.digits
def _current_character(self):
try:
return self.text_widget[self.cursor_y][self.cursor_x]
except IndexError:
return "\n"
def next_word(self):
while self._current_character() not in Editor.WORD_CHARS:
self.cursor_right()
while self._current_character() in Editor.WORD_CHARS:
self.cursor_right()
def previous_word(self):
self.cursor_left()
while self._current_character() not in Editor.WORD_CHARS:
self.cursor_left()
while self._current_character() in Editor.WORD_CHARS:
self.cursor_left()
self.cursor_right()
def delete_backward(self):
self.set_mark()
with contextlib.suppress(IndexError):
self.previous_word()
self.delete_selection()
def delete_line(self):
empty_selection = self.text_widget[self.cursor_y][self.cursor_x:].strip() == ""
self.set_mark()
self.jump_to_end_of_line()
self.delete_selection()
if empty_selection:
self.delete_character()
2022-01-08 15:05:40 +10:00
def _indent_level(self):
2022-01-08 10:01:57 +10:00
if self.cursor_y == 0:
2022-01-08 15:05:40 +10:00
return 0
2022-01-08 10:01:57 +10:00
self.jump_to_start_of_line()
self.cursor_up()
while self._current_character() == " ":
self.cursor_right()
2022-01-08 15:05:40 +10:00
return self.cursor_x
def tab_align(self):
if self.cursor_y == 0:
return
indent = self._indent_level()
2022-01-08 10:01:57 +10:00
self.cursor_down()
self.jump_to_start_of_line()
self.set_mark()
while self._current_character() == " ":
self.cursor_right()
self.delete_selection()
self.insert_text(" " * indent)
2022-01-08 15:05:40 +10:00
def _line_indent(self, y):
line = self.text_widget[y]
for index, char in enumerate(line):
if char != " ":
return index
return 0
def comment_lines(self):
if self.mark is None:
if self.text_widget[self.cursor_y].strip() == "":
self.text_widget[self.cursor_y] = "# "
self.cursor_x = 2
else:
try:
index = self.text_widget[self.cursor_y].index("#")
self.cursor_x = index + 1
except ValueError: # '#' not in line
2022-01-08 15:05:40 +10:00
self.jump_to_end_of_line()
self.insert_text(" # ")
else:
(start_x, start_y), (end_x, end_y) = self.get_selection_interval()
if end_x != 0 and not self.cursor_x == len(self.text_widget[end_y]):
self.enter()
self.cursor_left()
if start_x != 0:
new_line = (self.text_widget[start_y][:start_x] + "# " +
self.text_widget[start_y][start_x:])
self.text_widget[start_y] = new_line
self.cursor_x = len(new_line)
start_y += 1
if end_x != 0:
end_y += 1
mid_lines = range(start_y, end_y)
try:
min_indent = min(self._line_indent(y) for y in mid_lines
if self.text_widget[y].strip() != "")
except ValueError:
pass
2022-01-08 15:05:40 +10:00
else:
if all(self.text_widget[y][min_indent:min_indent+2] == "# "
or self.text_widget[y].strip() == "" for y in mid_lines):
for y in mid_lines:
line = self.text_widget[y]
if line.strip() != "":
self.text_widget[y] = line[:min_indent] + line[min_indent + 2:]
else:
for y in mid_lines:
line = self.text_widget[y]
if line.strip() != "":
self.text_widget[y] = line[:min_indent] + "# " + line[min_indent:]
2022-01-08 15:05:40 +10:00
self.mark = None
2022-01-01 17:37:20 +10:00
def join_lines(self):
if self.cursor_y == 0:
self.jump_to_start_of_line()
else:
left_part = self.text_widget[self.cursor_y-1].rstrip()
right_part = self.text_widget[self.cursor_y].lstrip()
new_line = right_part if left_part == "" else (left_part + " " + right_part)
2022-01-01 17:37:20 +10:00
self.text_widget[self.cursor_y-1:self.cursor_y+1] = [new_line]
self.cursor_x, self.cursor_y = len(left_part), self.cursor_y - 1
def highlight_block(self):
self.jump_to_block_end()
self.set_mark()
self.jump_to_block_start()
def syntax_highlight_all(self):
self.text_widget.syntax_highlight_all()
def center_cursor(self):
view_x, view_y = self.view_widget.position
new_y = max(0, self.cursor_y - self.last_height // 2)
self.view_widget.position = view_x, new_y
def cycle_syntax_highlighting(self):
self.theme_index += 1
if self.theme_index == len(Editor.THEMES):
self.theme_index = 0
theme = self.THEMES[self.theme_index]
self.text_widget.theme = theme
self.text_widget.syntax_highlight_all()
def quit(self):
fill3.SHUTDOWN_EVENT.set()
def ctrl_c(self):
if self.previous_term_code == terminal.CTRL_X:
self.quit()
def ring_bell(self):
2022-01-13 19:29:27 +10:00
if "unittest" not in sys.modules:
print("\a", end="")
2022-01-13 00:51:04 +10:00
def undo(self):
self.text_widget[:], self._cursor_x, self._cursor_y = self.history.pop()
2022-01-13 00:51:04 +10:00
def toggle_overwrite(self):
self.is_overwriting = not self.is_overwriting
def abort_command(self):
self.mark = None
self.ring_bell()
2022-01-01 17:37:20 +10:00
def get_text(self):
return self.text_widget.get_text()
def follow_cursor(self):
height = self.last_height
height -= 2 # header + scrollbar
2022-01-01 17:37:20 +10:00
width = self.last_width
width -= 1 # scrollbar
view_x, view_y = self.view_widget.position
if self.cursor_y >= view_y + height or self.cursor_y < view_y:
new_y = self.cursor_y - height // 2
else:
new_y = view_y
if self.cursor_x >= view_x + width or self.cursor_x < view_x:
new_x = self.cursor_x - width // 2
else:
new_x = view_x
self.view_widget.position = max(0, new_x), max(0, new_y)
_PRINTABLE = string.printable[:-5]
2022-01-13 00:51:04 +10:00
def add_to_history(self):
self.history.append((self.text_widget.actual_text.copy(), self._cursor_x, self._cursor_y))
2022-01-01 17:37:20 +10:00
def on_keyboard_input(self, term_code):
if term_code not in [terminal.CTRL_UNDERSCORE, terminal.CTRL_Z]:
2022-01-13 00:51:04 +10:00
self.add_to_history()
2022-01-01 17:37:20 +10:00
if term_code in Editor.KEY_MAP:
try:
2022-01-01 17:37:20 +10:00
Editor.KEY_MAP[term_code](self)
except IndexError:
self.ring_bell()
2022-01-01 17:37:20 +10:00
elif term_code in self._PRINTABLE:
self.insert_text(term_code, is_overwriting=self.is_overwriting)
2022-01-01 17:37:20 +10:00
else:
self.insert_text(repr(term_code))
2022-01-04 00:10:32 +10:00
self.previous_term_code = term_code
2022-01-01 17:37:20 +10:00
self.follow_cursor()
fill3.APPEARANCE_CHANGED_EVENT.set()
def scroll(self, dx, dy):
view_x, view_y = self.scroll_position
self.scroll_position = view_x + dx, view_y + dy
def on_mouse_press(self, x, y):
view_x, view_y = self.view_widget.position
self.cursor_x = x + view_x
self.cursor_y = min(y + view_y - 1, len(self.text_widget) - 1)
self.last_mouse_position = (x, y)
def on_mouse_drag(self, x, y):
last_x, last_y = self.last_mouse_position
self.scroll(last_x - x, last_y - y)
self.last_mouse_position = (x, y)
def on_mouse_input(self, term_code):
action, flag, x, y = terminal.decode_mouse_input(term_code)
if action == terminal.MOUSE_PRESS:
self.on_mouse_press(x, y)
elif action == terminal.MOUSE_DRAG:
self.on_mouse_drag(x, y)
self.follow_cursor()
fill3.APPEARANCE_CHANGED_EVENT.set()
def appearance(self):
return self.decor_widget.appearance()
2022-01-01 17:37:20 +10:00
_HEADER_STYLE = termstr.CharStyle(fg_color=termstr.Color.white, bg_color=termstr.Color.green)
@functools.lru_cache(maxsize=100)
def get_header(self, path, width, cursor_x, cursor_y, is_changed):
change_marker = "*" if is_changed else ""
cursor_position = f"Line {cursor_y+1} Column {cursor_x+1:<3}"
path_part = (path + change_marker).ljust(width - len(cursor_position) - 2)
2022-01-01 17:37:20 +10:00
return (termstr.TermStr(" " + path_part, self._HEADER_STYLE).bold() +
termstr.TermStr(cursor_position + " ", self._HEADER_STYLE))
def appearance_for(self, dimensions):
2022-01-01 17:37:20 +10:00
width, height = dimensions
2022-01-21 09:20:57 +10:00
text_width = self.text_widget.max_line_length
2022-01-01 17:37:20 +10:00
is_changed = self.text_widget.actual_text != self.original_text
header = self.get_header(self.path, width, self.cursor_x, self.cursor_y, is_changed)
2022-01-01 17:37:20 +10:00
self.last_width = width
self.last_height = height
result = [header] + self.view_widget.appearance_for((width, height - 1))
2022-01-01 17:37:20 +10:00
return result
KEY_MAP = {
2022-01-04 00:10:32 +10:00
terminal.CTRL_S: save, terminal.BACKSPACE: backspace, terminal.LEFT: cursor_left,
2022-01-01 17:37:20 +10:00
terminal.CTRL_B: cursor_left, terminal.RIGHT: cursor_right, terminal.CTRL_F: cursor_right,
terminal.UP: cursor_up, terminal.CTRL_P: cursor_up, terminal.DOWN: cursor_down,
terminal.CTRL_N: cursor_down, terminal.CTRL_A: jump_to_start_of_line,
terminal.CTRL_E: jump_to_end_of_line, terminal.CTRL_O: open_line, terminal.ENTER: enter,
terminal.CTRL_SPACE: set_mark, terminal.CTRL_G: drop_highlight,
terminal.PAGE_DOWN: page_down, terminal.CTRL_V: page_down, terminal.PAGE_UP: page_up,
terminal.ALT_v: page_up, terminal.ALT_w: copy_selection, terminal.CTRL_W: delete_selection,
2022-01-07 09:27:29 +10:00
terminal.CTRL_D: delete_character, terminal.DELETE: delete_character,
terminal.ALT_d: delete_right, terminal.CTRL_Y: paste_from_clipboard,
terminal.CTRL_UP: jump_to_block_start, terminal.CTRL_DOWN: jump_to_block_end,
terminal.ALT_f: next_word, terminal.CTRL_RIGHT: next_word, terminal.ALT_RIGHT: next_word,
2022-01-01 17:37:20 +10:00
terminal.ALT_b: previous_word, terminal.CTRL_LEFT: previous_word,
terminal.ALT_LEFT: previous_word, terminal.ALT_BACKSPACE: delete_backward,
terminal.ALT_CARROT: join_lines, terminal.ALT_h: highlight_block,
terminal.ALT_H: highlight_block, terminal.CTRL_R: syntax_highlight_all,
2022-01-08 15:05:40 +10:00
terminal.CTRL_L: center_cursor, terminal.ALT_SEMICOLON: comment_lines,
terminal.ALT_c: cycle_syntax_highlighting, terminal.CTRL_X: prefix, terminal.ESC: quit,
2022-01-13 00:51:04 +10:00
terminal.CTRL_C: ctrl_c, terminal.CTRL_K: delete_line, terminal.TAB: tab_align,
terminal.CTRL_UNDERSCORE: undo, terminal.CTRL_Z: undo, terminal.CTRL_G: abort_command,
terminal.INSERT: toggle_overwrite}
2022-01-01 17:37:20 +10:00
def main():
editor = Editor()
editor.load(sys.argv[1])
asyncio.run(fill3.tui("Editor", editor))
2022-01-01 17:37:20 +10:00
if __name__ == "__main__":
main()