parent
2df35a9064
commit
45375c1aec
1 changed files with 22 additions and 127 deletions
|
|
@ -25,8 +25,8 @@ import cwcwidth
|
||||||
@functools.lru_cache(maxsize=100)
|
@functools.lru_cache(maxsize=100)
|
||||||
def highlight_str(line, bg_color, transparency=0.6):
|
def highlight_str(line, bg_color, transparency=0.6):
|
||||||
def blend_style(style):
|
def blend_style(style):
|
||||||
return termstr.CharStyle(termstr.blend_color(style.fg_rgb_color, bg_color, transparency),
|
return termstr.CharStyle(termstr.blend_color(style.fg_color, bg_color, transparency),
|
||||||
termstr.blend_color(style.bg_rgb_color, bg_color, transparency),
|
termstr.blend_color(style.bg_color, bg_color, transparency),
|
||||||
is_bold=style.is_bold, is_italic=style.is_italic,
|
is_bold=style.is_bold, is_italic=style.is_italic,
|
||||||
is_underlined=style.is_underlined)
|
is_underlined=style.is_underlined)
|
||||||
return termstr.TermStr(line).transform_style(blend_style)
|
return termstr.TermStr(line).transform_style(blend_style)
|
||||||
|
|
@ -274,7 +274,6 @@ class Parts:
|
||||||
self.lexer = lexer
|
self.lexer = lexer
|
||||||
self.lines = parts_lines(source, lexer, editor.text_widget.theme)
|
self.lines = parts_lines(source, lexer, editor.text_widget.theme)
|
||||||
self.width, self.height = None, None
|
self.width, self.height = None, None
|
||||||
self.is_focused = True
|
|
||||||
self.set_cursor()
|
self.set_cursor()
|
||||||
|
|
||||||
def set_cursor(self):
|
def set_cursor(self):
|
||||||
|
|
@ -291,11 +290,6 @@ class Parts:
|
||||||
x, y = self.editor.view_widget.portal.position
|
x, y = self.editor.view_widget.portal.position
|
||||||
self.editor.view_widget.portal.position = x, self.editor.cursor_y - 1
|
self.editor.view_widget.portal.position = x, self.editor.cursor_y - 1
|
||||||
|
|
||||||
def escape_parts_browser(self):
|
|
||||||
self.editor.parts_widget = None
|
|
||||||
self.editor.is_editing = True
|
|
||||||
self.editor.center_cursor()
|
|
||||||
|
|
||||||
def cursor_left(self):
|
def cursor_left(self):
|
||||||
self._move_cursor(-1)
|
self._move_cursor(-1)
|
||||||
|
|
||||||
|
|
@ -303,15 +297,14 @@ class Parts:
|
||||||
self._move_cursor(1)
|
self._move_cursor(1)
|
||||||
|
|
||||||
def on_keyboard_input(self, term_code):
|
def on_keyboard_input(self, term_code):
|
||||||
match term_code:
|
if term_code == terminal.ESC:
|
||||||
case terminal.ESC:
|
self.editor.parts_widget = None
|
||||||
self.escape_parts_browser()
|
self.editor.is_editing = True
|
||||||
case terminal.DOWN:
|
self.editor.center_cursor()
|
||||||
self.escape_parts_browser()
|
elif term_code == terminal.LEFT:
|
||||||
case terminal.LEFT:
|
self.cursor_left()
|
||||||
self.cursor_left()
|
elif term_code == terminal.RIGHT:
|
||||||
case terminal.RIGHT:
|
self.cursor_right()
|
||||||
self.cursor_right()
|
|
||||||
fill3.APPEARANCE_CHANGED_EVENT.set()
|
fill3.APPEARANCE_CHANGED_EVENT.set()
|
||||||
|
|
||||||
def appearance(self):
|
def appearance(self):
|
||||||
|
|
@ -325,8 +318,7 @@ class Parts:
|
||||||
if len(result) > height:
|
if len(result) > height:
|
||||||
appearance, coords = wrap_text(parts, width - 1, pad_char)
|
appearance, coords = wrap_text(parts, width - 1, pad_char)
|
||||||
line_num = coords[self.cursor][0] // (width - 1)
|
line_num = coords[self.cursor][0] // (width - 1)
|
||||||
if self.is_focused:
|
appearance[line_num] = highlight_line(appearance[line_num],
|
||||||
appearance[line_num] = highlight_line(appearance[line_num],
|
|
||||||
self.editor.text_widget.theme)
|
self.editor.text_widget.theme)
|
||||||
view_widget = fill3.View.from_widget(fill3.Fixed(appearance))
|
view_widget = fill3.View.from_widget(fill3.Fixed(appearance))
|
||||||
if line_num >= height:
|
if line_num >= height:
|
||||||
|
|
@ -335,9 +327,8 @@ class Parts:
|
||||||
view_widget.portal.limit_scroll(self.dimensions, (width, len(appearance)))
|
view_widget.portal.limit_scroll(self.dimensions, (width, len(appearance)))
|
||||||
result = view_widget.appearance_for(self.dimensions)
|
result = view_widget.appearance_for(self.dimensions)
|
||||||
else:
|
else:
|
||||||
if self.is_focused:
|
line_num = coords[self.cursor][0] // width
|
||||||
line_num = coords[self.cursor][0] // width
|
result[line_num] = highlight_line(result[line_num])
|
||||||
result[line_num] = highlight_line(result[line_num], self.editor.text_widget.theme)
|
|
||||||
fg_color = termstr.Color.grey_100
|
fg_color = termstr.Color.grey_100
|
||||||
bg_color = parse_rgb(self.editor.text_widget.theme.background_color)
|
bg_color = parse_rgb(self.editor.text_widget.theme.background_color)
|
||||||
result.append(termstr.TermStr("─").bg_color(bg_color).fg_color(fg_color) * width)
|
result.append(termstr.TermStr("─").bg_color(bg_color).fg_color(fg_color) * width)
|
||||||
|
|
@ -870,11 +861,10 @@ class TextEditor:
|
||||||
|
|
||||||
def on_mouse_input(self, term_code):
|
def on_mouse_input(self, term_code):
|
||||||
action, flag, x, y = terminal.decode_mouse_input(term_code)
|
action, flag, x, y = terminal.decode_mouse_input(term_code)
|
||||||
match action:
|
if action == terminal.MOUSE_PRESS:
|
||||||
case terminal.MOUSE_PRESS:
|
self.on_mouse_press(x, y)
|
||||||
self.on_mouse_press(x, y)
|
elif action == terminal.MOUSE_DRAG:
|
||||||
case terminal.MOUSE_DRAG:
|
self.on_mouse_drag(x, y)
|
||||||
self.on_mouse_drag(x, y)
|
|
||||||
self.follow_cursor()
|
self.follow_cursor()
|
||||||
fill3.APPEARANCE_CHANGED_EVENT.set()
|
fill3.APPEARANCE_CHANGED_EVENT.set()
|
||||||
|
|
||||||
|
|
@ -889,14 +879,14 @@ class TextEditor:
|
||||||
path_colored = lscolors.path_colored(path) + change_marker
|
path_colored = lscolors.path_colored(path) + change_marker
|
||||||
path_part = path_colored.ljust(width - len(cursor_position) - 2)
|
path_part = path_colored.ljust(width - len(cursor_position) - 2)
|
||||||
header = " " + path_part + cursor_position + " "
|
header = " " + path_part + cursor_position + " "
|
||||||
return termstr.TermStr(header).bg_color(termstr.Color.grey_30)
|
return termstr.TermStr(header).bg_color(termstr.Color.grey_50)
|
||||||
|
|
||||||
def appearance_for(self, dimensions):
|
def appearance_for(self, dimensions):
|
||||||
width, height = dimensions
|
width, height = dimensions
|
||||||
if self.parts_widget is None:
|
if self.parts_widget is None:
|
||||||
parts_appearance = []
|
parts_appearance = []
|
||||||
else:
|
else:
|
||||||
self.parts_widget.dimensions = width, height // 5
|
self.parts_widget.dimensions = width, height // 4
|
||||||
parts_appearance = self.parts_widget.appearance()
|
parts_appearance = self.parts_widget.appearance()
|
||||||
self.parts_height = len(parts_appearance)
|
self.parts_height = len(parts_appearance)
|
||||||
is_changed = self.text_widget.lines != self.original_text
|
is_changed = self.text_widget.lines != self.original_text
|
||||||
|
|
@ -933,105 +923,10 @@ class TextEditor:
|
||||||
(terminal.CTRL_C, ">"): indent, (terminal.CTRL_C, "<"): dedent}
|
(terminal.CTRL_C, ">"): indent, (terminal.CTRL_C, "<"): dedent}
|
||||||
|
|
||||||
|
|
||||||
class FileBrowser:
|
|
||||||
|
|
||||||
def __init__(self, paths):
|
|
||||||
self.parts = [self._path_colored(path) for path in paths]
|
|
||||||
self.cursor = 0
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _path_colored(path):
|
|
||||||
return termstr.TermStr(os.path.basename(path), lscolors._charstyle_of_path(path))
|
|
||||||
|
|
||||||
def cursor_left(self):
|
|
||||||
self.cursor = (self.cursor - 1) % len(self.parts)
|
|
||||||
|
|
||||||
def cursor_right(self):
|
|
||||||
self.cursor = (self.cursor + 1) % len(self.parts)
|
|
||||||
|
|
||||||
def appearance(self):
|
|
||||||
width, height = self.dimensions
|
|
||||||
parts = self.parts.copy()
|
|
||||||
parts[self.cursor] = parts[self.cursor].invert()
|
|
||||||
result, coords = wrap_text(parts, width)
|
|
||||||
if len(result) > height:
|
|
||||||
appearance, coords = wrap_text(parts, width - 1)
|
|
||||||
line_num = coords[self.cursor][0] // (width - 1)
|
|
||||||
appearance[line_num] = highlight_line(appearance[line_num])
|
|
||||||
view_widget = fill3.View.from_widget(fill3.Fixed(appearance))
|
|
||||||
if line_num >= height:
|
|
||||||
x, y = view_widget.portal.position
|
|
||||||
view_widget.portal.position = x, line_num // height * height
|
|
||||||
view_widget.portal.limit_scroll(self.dimensions, (width, len(appearance)))
|
|
||||||
result = view_widget.appearance_for(self.dimensions)
|
|
||||||
else:
|
|
||||||
line_num = coords[self.cursor][0] // width
|
|
||||||
result[line_num] = highlight_line(result[line_num])
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class TextFilesEditor:
|
|
||||||
|
|
||||||
def __init__(self, paths):
|
|
||||||
self.paths = paths
|
|
||||||
self.file_browser = FileBrowser(paths)
|
|
||||||
self.is_browsing = False
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
@functools.cache
|
|
||||||
def get_editor(path):
|
|
||||||
editor = TextEditor()
|
|
||||||
editor.load(path)
|
|
||||||
return editor
|
|
||||||
|
|
||||||
def current_editor(self):
|
|
||||||
return self.get_editor(self.paths[self.file_browser.cursor])
|
|
||||||
|
|
||||||
def open_parts_browser(self):
|
|
||||||
editor = self.current_editor()
|
|
||||||
if editor.parts_widget is None:
|
|
||||||
editor.show_parts_list()
|
|
||||||
editor.parts_widget.is_focused = False
|
|
||||||
|
|
||||||
def on_keyboard_input(self, term_code):
|
|
||||||
if self.is_browsing:
|
|
||||||
match term_code:
|
|
||||||
case terminal.DOWN:
|
|
||||||
self.is_browsing = False
|
|
||||||
self.current_editor().parts_widget.is_focused = True
|
|
||||||
case terminal.LEFT:
|
|
||||||
self.file_browser.cursor_left()
|
|
||||||
self.open_parts_browser()
|
|
||||||
case terminal.RIGHT:
|
|
||||||
self.file_browser.cursor_right()
|
|
||||||
self.open_parts_browser()
|
|
||||||
case terminal.ESC:
|
|
||||||
self.is_browsing = False
|
|
||||||
self.current_editor().parts_widget.escape_parts_browser()
|
|
||||||
elif term_code == terminal.UP and self.current_editor().parts_widget is not None:
|
|
||||||
self.is_browsing = True
|
|
||||||
self.current_editor().parts_widget.is_focused = False
|
|
||||||
else:
|
|
||||||
self.current_editor().on_keyboard_input(term_code)
|
|
||||||
fill3.APPEARANCE_CHANGED_EVENT.set()
|
|
||||||
|
|
||||||
def on_mouse_input(self, term_code):
|
|
||||||
self.current_editor().on_mouse_input(term_code)
|
|
||||||
|
|
||||||
def appearance_for(self, dimensions):
|
|
||||||
width, height = dimensions
|
|
||||||
if self.is_browsing:
|
|
||||||
self.file_browser.dimensions = width, height // 5
|
|
||||||
file_browser_appearance = self.file_browser.appearance()
|
|
||||||
else:
|
|
||||||
file_browser_appearance = []
|
|
||||||
editor_dimensions = width, height - len(file_browser_appearance)
|
|
||||||
return file_browser_appearance + self.current_editor().appearance_for(editor_dimensions)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
editor = TextFilesEditor(sys.argv[1:])
|
editor = TextEditor()
|
||||||
asyncio.run(fill3.tui("Text Editor", editor))
|
editor.load(sys.argv[1])
|
||||||
|
asyncio.run(fill3.tui("Editor", editor))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue