TUI Word Processor in Python

This is a complete terminal word processor in plain Python.

Read the mental model first

Download source file

Quick Run (from repo root)

1cd Site/static/code/examples/python
2python tui_word_processor.py

It supports:

  • append, insert, edit, and delete by line number
  • save/open files
  • live word and line counts
  • unsaved-change warning on quit

Code (Pythonic + Short)

  1from dataclasses import dataclass, field
  2from pathlib import Path
  3
  4
  5@dataclass
  6class Document:
  7    file_path: Path
  8    lines: list[str] = field(default_factory=list)
  9    dirty: bool = False
 10
 11    def load(self) -> None:
 12        if not self.file_path.exists():
 13            print(f"New file: {self.file_path}")
 14            return
 15        self.lines = self.file_path.read_text().splitlines()
 16        print(f"Loaded {len(self.lines)} line(s).")
 17
 18    def show(self) -> None:
 19        if not self.lines:
 20            print("(empty)")
 21            return
 22        for i, line in enumerate(self.lines, start=1):
 23            print(f"{i:>3} | {line}")
 24
 25    def append(self, text: str) -> None:
 26        self.lines.append(text)
 27        self.dirty = True
 28        print(f"Appended line {len(self.lines)}.")
 29
 30    def insert(self, line_no: int, text: str) -> None:
 31        index = line_no - 1
 32        if not 0 <= index <= len(self.lines):
 33            print("Line out of range.")
 34            return
 35        self.lines.insert(index, text)
 36        self.dirty = True
 37        print("Inserted.")
 38
 39    def edit(self, line_no: int, text: str) -> None:
 40        index = line_no - 1
 41        if not 0 <= index < len(self.lines):
 42            print("Line out of range.")
 43            return
 44        self.lines[index] = text
 45        self.dirty = True
 46        print("Updated.")
 47
 48    def delete(self, line_no: int) -> None:
 49        index = line_no - 1
 50        if not 0 <= index < len(self.lines):
 51            print("Line out of range.")
 52            return
 53        self.lines.pop(index)
 54        self.dirty = True
 55        print("Deleted.")
 56
 57    def stats(self) -> None:
 58        word_count = sum(len(line.split()) for line in self.lines)
 59        print(f"Lines: {len(self.lines)}, Words: {word_count}")
 60
 61    def save(self) -> None:
 62        self.file_path.write_text("\n".join(self.lines) + ("\n" if self.lines else ""))
 63        self.dirty = False
 64        print(f"Saved to {self.file_path}.")
 65
 66
 67def read_line_no(prompt: str) -> int:
 68    raw = input(prompt).strip()
 69    return int(raw) if raw.isdigit() else -1
 70
 71
 72def main() -> None:
 73    file_name = input("File name (default draft.txt): ").strip() or "draft.txt"
 74    doc = Document(Path(file_name))
 75    doc.load()
 76
 77    print("Commands: show, append, insert, edit, delete, stats, save, quit")
 78
 79    while True:
 80        command = input("> ").strip().lower()
 81
 82        if command == "show":
 83            doc.show()
 84        elif command == "append":
 85            doc.append(input("Text: "))
 86        elif command == "insert":
 87            line_no = read_line_no("Line number: ")
 88            if line_no > 0:
 89                doc.insert(line_no, input("Text: "))
 90            else:
 91                print("Please enter a number.")
 92        elif command == "edit":
 93            line_no = read_line_no("Line number: ")
 94            if line_no > 0:
 95                doc.edit(line_no, input("New text: "))
 96            else:
 97                print("Please enter a number.")
 98        elif command == "delete":
 99            line_no = read_line_no("Line number: ")
100            if line_no > 0:
101                doc.delete(line_no)
102            else:
103                print("Please enter a number.")
104        elif command == "stats":
105            doc.stats()
106        elif command == "save":
107            doc.save()
108        elif command == "quit":
109            if doc.dirty:
110                confirm = input("Unsaved changes. Type quit! to force: ").strip()
111                if confirm != "quit!":
112                    continue
113            break
114        else:
115            print("Use: show, append, insert, edit, delete, stats, save, quit")
116
117
118if __name__ == "__main__":
119    main()

Run It

1python tui_word_processor.py

Why this is pythonic

  • dataclass is a clean document model.
  • Methods do one thing each.
  • sum(len(line.split())...) gives clear stats logic.