|
4 | 4 | import re |
5 | 5 | import shlex |
6 | 6 | import subprocess |
| 7 | +from pathlib import Path |
7 | 8 |
|
8 | 9 | import typer |
| 10 | +from textual.app import App, ComposeResult |
| 11 | +from textual.binding import Binding |
| 12 | +from textual.widgets import Footer, Header, TextArea |
9 | 13 |
|
10 | 14 | from .pyfunny import joke, joke_trex |
11 | 15 | from .settings import launch_settings |
@@ -34,6 +38,56 @@ def _prompt_command() -> tuple[str, str, str]: |
34 | 38 | return raw_command, command, argument |
35 | 39 |
|
36 | 40 |
|
| 41 | +class AtonEditor(App): |
| 42 | + """Simple full-screen text editor for ATON shell files.""" |
| 43 | + |
| 44 | + inherit_bindings = True |
| 45 | + |
| 46 | + CSS = """ |
| 47 | + Screen { padding: 0; } |
| 48 | + #editor { height: 1fr; } |
| 49 | + Footer { height: 1; } |
| 50 | + """ |
| 51 | + |
| 52 | + BINDINGS = [ |
| 53 | + ("ctrl+shift+s", "save_file", "Ctrl+Shift+S Save"), |
| 54 | + ("ctrl+shift+q", "quit_app", "Ctrl+Shift+Q Quit"), |
| 55 | + ] |
| 56 | + |
| 57 | + def __init__(self, file_path: str, **kwargs) -> None: |
| 58 | + super().__init__(**kwargs) |
| 59 | + self.file_path = file_path |
| 60 | + self.file_content = "" |
| 61 | + |
| 62 | + import os |
| 63 | + |
| 64 | + if os.path.exists(file_path): |
| 65 | + try: |
| 66 | + with open(file_path, "r", encoding="utf-8") as handle: |
| 67 | + self.file_content = handle.read() |
| 68 | + except Exception as error: |
| 69 | + self.file_content = f"Error reading file: {error}" |
| 70 | + |
| 71 | + def compose(self) -> ComposeResult: |
| 72 | + yield Header() |
| 73 | + yield TextArea(self.file_content, id="editor_text_area") |
| 74 | + yield Footer() |
| 75 | + |
| 76 | + def on_mount(self) -> None: |
| 77 | + self.text_area.focus() |
| 78 | + |
| 79 | + def action_save_file(self) -> None: |
| 80 | + text_area = self.query_one("#editor_text_area", TextArea) |
| 81 | + try: |
| 82 | + with open(self.file_path, "w", encoding="utf-8") as handle: |
| 83 | + handle.write(text_area.text) |
| 84 | + except Exception: |
| 85 | + pass |
| 86 | + |
| 87 | + def action_quit_app(self) -> None: |
| 88 | + self.exit() |
| 89 | + |
| 90 | + |
37 | 91 | def _handle_joke_command(command: str) -> bool: |
38 | 92 | if command in {"joke", "joke_trex", "joke-trex"}: |
39 | 93 | if command == "joke": |
@@ -72,6 +126,26 @@ def _handle_todo_command(command: str) -> bool: |
72 | 126 | return False |
73 | 127 |
|
74 | 128 |
|
| 129 | +def _handle_edit_command(raw_command: str) -> bool: |
| 130 | + command, argument = _split_command(raw_command) |
| 131 | + if command != "edit": |
| 132 | + return False |
| 133 | + |
| 134 | + if not argument.strip(): |
| 135 | + typer.secho("⚠️ Syntax: edit <filename>", fg=typer.colors.YELLOW) |
| 136 | + return True |
| 137 | + |
| 138 | + file_path = Path(argument.strip()) |
| 139 | + initial_text = file_path.read_text(encoding="utf-8") if file_path.exists() else "" |
| 140 | + |
| 141 | + try: |
| 142 | + AtonEditor(str(file_path), initial_text).run() |
| 143 | + except Exception as error: |
| 144 | + typer.secho(f"Editor error: {error}", fg=typer.colors.RED) |
| 145 | + |
| 146 | + return True |
| 147 | + |
| 148 | + |
75 | 149 | def _handle_local_command(command: str, argument: str) -> str: |
76 | 150 | if command == "addtask" and not argument: |
77 | 151 | typer.secho( |
@@ -167,6 +241,8 @@ def run_interactive_shell() -> None: |
167 | 241 | continue |
168 | 242 | if _handle_cd_command(raw_command): |
169 | 243 | continue |
| 244 | + if _handle_edit_command(raw_command): |
| 245 | + continue |
170 | 246 | if _handle_os_fallback(raw_command): |
171 | 247 | continue |
172 | 248 |
|
|
0 commit comments