Skip to content

Commit dafbf03

Browse files
committed
incomplete - add an edit command
1 parent c0487ab commit dafbf03

5 files changed

Lines changed: 113 additions & 3 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
AtOffice Shell is a polished open-source Python CLI that combines jokes, todos, alarms, world clocks, and a productivity-focused terminal shell.
44

5-
![AtOffice Shell banner](docs/screenshot-placeholder.png)
65

76
## Key Features
87

@@ -42,6 +41,7 @@ addtask "Review PR" "Work"
4241
time
4342
world
4443
settings
44+
edit
4545
```
4646

4747
## CLI Commands

atoffice_shell/project.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
import re
55
import shlex
66
import subprocess
7+
from pathlib import Path
78

89
import typer
10+
from textual.app import App, ComposeResult
11+
from textual.binding import Binding
12+
from textual.widgets import Footer, Header, TextArea
913

1014
from .pyfunny import joke, joke_trex
1115
from .settings import launch_settings
@@ -34,6 +38,56 @@ def _prompt_command() -> tuple[str, str, str]:
3438
return raw_command, command, argument
3539

3640

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+
3791
def _handle_joke_command(command: str) -> bool:
3892
if command in {"joke", "joke_trex", "joke-trex"}:
3993
if command == "joke":
@@ -72,6 +126,26 @@ def _handle_todo_command(command: str) -> bool:
72126
return False
73127

74128

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+
75149
def _handle_local_command(command: str, argument: str) -> str:
76150
if command == "addtask" and not argument:
77151
typer.secho(
@@ -167,6 +241,8 @@ def run_interactive_shell() -> None:
167241
continue
168242
if _handle_cd_command(raw_command):
169243
continue
244+
if _handle_edit_command(raw_command):
245+
continue
170246
if _handle_os_fallback(raw_command):
171247
continue
172248

atoffice_shell/pyfunny.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
DEFAULT_JOKE_CHARACTER = "cow"
1313
DEFAULT_JOKE_SOUND = "cow-sound.mp3"
1414

15+
# Hi! This commit was made through the `edit` command!
16+
# This one too!
1517

1618
def _sound_path(filename: str) -> Path:
1719
return Path(__file__).resolve().parent / "chronoterm" / "sounds" / filename

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dependencies = [
2828
"playsound==1.2.2",
2929
"pytz>=2024.1",
3030
"platformdirs>=3.0.0",
31+
"textual>=0.86.0",
3132
]
3233

3334
[project.urls]

tests/test_cli.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typer.testing import CliRunner
55

66
from atoffice_shell.cli import app
7-
from atoffice_shell.project import _handle_cd_command, _handle_local_command, _handle_os_fallback
7+
from atoffice_shell.project import _handle_cd_command, _handle_edit_command, _handle_local_command, _handle_os_fallback
88

99

1010
runner = CliRunner()
@@ -86,4 +86,35 @@ def test_addtask_missing_arguments_is_blocked(monkeypatch) -> None:
8686
)
8787

8888
assert _handle_local_command("addtask", "") == "handled"
89-
assert messages and "Missing arguments" in messages[0][0]
89+
90+
91+
def test_edit_requires_filename(monkeypatch) -> None:
92+
messages = []
93+
94+
monkeypatch.setattr(
95+
"atoffice_shell.project.typer.secho",
96+
lambda message, fg=None: messages.append((message, fg)),
97+
)
98+
99+
assert _handle_edit_command("edit") is True
100+
assert messages and "Syntax: edit <filename>" in messages[0][0]
101+
102+
103+
def test_edit_launches_editor_for_existing_file(monkeypatch, tmp_path) -> None:
104+
file_path = tmp_path / "note.txt"
105+
file_path.write_text("hello", encoding="utf-8")
106+
107+
calls = {}
108+
109+
class FakeEditor:
110+
def __init__(self, filename: str, initial_text: str) -> None:
111+
calls["filename"] = filename
112+
calls["initial_text"] = initial_text
113+
114+
def run(self) -> None:
115+
calls["ran"] = True
116+
117+
monkeypatch.setattr("atoffice_shell.project.AtonEditor", FakeEditor)
118+
119+
assert _handle_edit_command(f"edit {file_path}") is True
120+
assert calls == {"filename": str(file_path), "initial_text": "hello", "ran": True}

0 commit comments

Comments
 (0)