external_editor.pyΒΆ

 1#!/usr/bin/env python
 2
 3"""Example: hand the terminal to an external editor and show what was written in it.
 4
 5Press :kbd:`e` to edit the text in ``$EDITOR`` (``vi`` when it is not set) and :kbd:`q` to exit.
 6"""
 7
 8from __future__ import annotations
 9
10import os
11import pathlib
12import shlex
13import subprocess
14import tempfile
15
16import urwid
17
18text_widget = urwid.Text("Press e to edit this text in $EDITOR, q to exit.\n")
19
20
21def edit_text(loop: urwid.MainLoop) -> None:
22    """Run the editor on a temporary file holding the text and show the edited content.
23
24    :param loop: main loop whose screen is paused while the editor runs
25    """
26    # $EDITOR is a shell command line such as "vim -f" or "code --wait", not an executable name
27    editor = shlex.split(os.environ.get("EDITOR", "")) or ["vi"]
28    fd, name = tempfile.mkstemp(suffix=".txt")
29    path = pathlib.Path(name)
30    try:
31        with os.fdopen(fd, "w", encoding="utf-8") as file:
32            file.write(str(text_widget.text))
33        with loop.paused():
34            subprocess.run([*editor, str(path)], check=False)  # noqa: S603  # Example can be insecure
35        text_widget.set_text(path.read_text(encoding="utf-8"))
36    finally:
37        path.unlink(missing_ok=True)
38
39
40def main() -> None:
41    """Run the example."""
42
43    def handle_key(key: str | tuple[str, int, int, int]) -> None:
44        """Handle keys not consumed by the widgets: :kbd:`e` to edit, :kbd:`q` to exit.
45
46        :param key: unhandled key or mouse event
47        :raises urwid.ExitMainLoop: when the user presses :kbd:`q`
48        """
49        if key == "e":
50            edit_text(loop)
51        elif key in {"q", "Q"}:
52            raise urwid.ExitMainLoop
53
54    loop = urwid.MainLoop(urwid.Filler(text_widget, valign=urwid.TOP), unhandled_input=handle_key)
55    loop.run()
56
57
58if __name__ == "__main__":
59    main()