Tiny Text Editor (Simpler than vi) in Python

This is a complete tiny text editor in plain Python.

Read the mental model first

Download source file

Quick Run (from repo root)

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

It keeps things intentionally simple:

  • open or create a text file
  • show lines with numbers
  • append, edit, and delete lines
  • save to disk
  • warn on unsaved quit

Code (Pythonic + Short)

  1from dataclasses import dataclass, field
  2from pathlib import Path
  3
  4
  5@dataclass
  6class Editor:
  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 edit(self, line_no: int, new_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[index] = new_text
 36        self.dirty = True
 37        print("Line updated.")
 38
 39    def delete(self, line_no: int) -> 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.pop(index)
 45        self.dirty = True
 46        print("Line deleted.")
 47
 48    def save(self) -> None:
 49        self.file_path.write_text(
 50            "\n".join(self.lines) + ("\n" if self.lines else "")
 51        )
 52        self.dirty = False
 53        print(f"Saved to {self.file_path}.")
 54
 55
 56def read_line_no(prompt: str) -> int:
 57    raw = input(prompt).strip()
 58    return int(raw) if raw.isdigit() else -1
 59
 60
 61def main() -> None:
 62    file_name = input("File name (default notes.txt): ").strip() or "notes.txt"
 63    editor = Editor(Path(file_name))
 64    editor.load()
 65
 66    print("Commands: show, append, edit, delete, save, quit")
 67
 68    while True:
 69        command = input("> ").strip().lower()
 70
 71        if command == "show":
 72            editor.show()
 73        elif command == "append":
 74            editor.append(input("Text: "))
 75        elif command == "edit":
 76            line_no = read_line_no("Line number: ")
 77            if line_no > 0:
 78                editor.edit(line_no, input("New text: "))
 79            else:
 80                print("Please enter a number.")
 81        elif command == "delete":
 82            line_no = read_line_no("Line number: ")
 83            if line_no > 0:
 84                editor.delete(line_no)
 85            else:
 86                print("Please enter a number.")
 87        elif command == "save":
 88            editor.save()
 89        elif command == "quit":
 90            if editor.dirty:
 91                confirm = input("Unsaved changes. Type quit! to force: ").strip()
 92                if confirm != "quit!":
 93                    continue
 94            print("Bye.")
 95            break
 96        else:
 97            print("Use: show, append, edit, delete, save, quit")
 98
 99
100if __name__ == "__main__":
101    main()

Run It

1python tiny_text_editor.py

Why this is pythonic

  • dataclass keeps editor state in one simple object.
  • Path handles files cleanly with the standard library.
  • Small focused methods make the control flow easy to explain.