Skip to content

Commit

Permalink
Merge pull request #20 from AnonymouX47/lazy-load-tui
Browse files Browse the repository at this point in the history
Lazy-load the TUI
  • Loading branch information
AnonymouX47 authored Jun 24, 2024
2 parents 25ec80f + 64c2699 commit b2b768e
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 82 deletions.
4 changes: 2 additions & 2 deletions src/termvisage/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ def finish_multi_logging():
write_tty(RESTORE_WINDOW_TITLE_b)
# Explicit cleanup is necessary since the top-level `Image` widgets
# will still hold references to the `BaseImage` instances
for _, image_w in cli.url_images:
image_w._ti_image.close()
for _, image in cli.url_images:
image.close()


# Session-specific temporary data directory.
Expand Down
40 changes: 25 additions & 15 deletions src/termvisage/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,17 @@
from tempfile import mkdtemp
from threading import current_thread
from time import sleep
from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Union
from typing import (
TYPE_CHECKING,
Any,
Callable,
Dict,
Generator,
List,
Optional,
Tuple,
Union,
)
from urllib.parse import urlparse

import PIL
Expand All @@ -36,12 +46,11 @@
from term_image.image import BlockImage, ITerm2Image, KittyImage, Size, auto_image_class
from term_image.utils import get_terminal_name_version, get_terminal_size, write_tty

from . import logging, notify, tui
from . import logging, notify
from .config import config_options, init_config
from .ctlseqs import ERASE_IN_LINE_LEFT_b
from .exit_codes import FAILURE, INVALID_ARG, NO_VALID_SOURCE, SUCCESS
from .logging import LoggingThread, init_log, log, log_exception
from .tui.widgets import Image

try:
import fcntl # noqa: F401
Expand All @@ -50,6 +59,9 @@
else:
OS_HAS_FCNTL = True

if TYPE_CHECKING:
from term_image.image import BaseImage


# Checks for CL arguments that have possible invalid values and don't have corresponding
# config options. See `check_arg()`.
Expand Down Expand Up @@ -539,15 +551,15 @@ def update_dict(base: dict, update: dict):

def get_urls(
url_queue: Queue,
images: List[Tuple[str, Image]],
images: list[tuple[str, BaseImage]],
ImageClass: type,
) -> None:
"""Processes URL sources from a/some separate thread(s)"""
source = url_queue.get()
while source:
log(f"Getting image from {source!r}", logger, verbose=True)
try:
images.append((basename(source), Image(ImageClass.from_url(source))))
images.append((basename(source), ImageClass.from_url(source)))
# Also handles `ConnectionTimeout`
except requests.exceptions.ConnectionError:
log(f"Unable to get {source!r}", logger, _logging.ERROR)
Expand All @@ -564,14 +576,14 @@ def get_urls(

def open_files(
file_queue: Queue,
images: List[Tuple[str, Image]],
images: list[tuple[str, BaseImage]],
ImageClass: type,
) -> None:
source = file_queue.get()
while source:
log(f"Opening {source!r}", logger, verbose=True)
try:
images.append((source, Image(ImageClass.from_file(source))))
images.append((source, ImageClass.from_file(source)))
except PIL.UnidentifiedImageError as e:
log(str(e), logger, _logging.ERROR)
except OSError as e:
Expand Down Expand Up @@ -904,21 +916,17 @@ def main() -> None:
log("No valid source!", logger)
return NO_VALID_SOURCE
# Sort entries by order on the command line
images.sort(
key=lambda x: unique_sources[x[0] if x[1] is ... else x[1]._ti_image.source]
)
images.sort(key=lambda x: unique_sources[x[0] if x[1] is ... else x[1].source])

if args.cli or (
not args.tui and len(images) == 1 and isinstance(images[0][1], Image)
):
if args.cli or not args.tui and len(images) == 1 and images[0][1] is not ...:
log("Running in CLI mode", logger, direct=False)

if style_args.get("native") and len(images) > 1:
style_args["stall_native"] = False

show_name = len(args.sources) > 1
for entry in images:
image = entry[1]._ti_image
image = entry[1]
if 0 < args.max_pixels < mul(*image._original_size):
log(
f"Has more than the maximum pixel-count, skipping: {entry[0]!r}",
Expand Down Expand Up @@ -1015,6 +1023,8 @@ def main() -> None:
sys.stdout.close()
break
elif OS_HAS_FCNTL:
from . import tui

tui.init(args, style_args, images, contents, ImageClass)
else:
log(
Expand Down Expand Up @@ -1043,4 +1053,4 @@ def main() -> None:
SHOW_HIDDEN: bool
# # Used in other modules
args: argparse.Namespace | None = None
url_images: list[tuple[str, Image]] = []
url_images: list[tuple[str, BaseImage]] = []
2 changes: 0 additions & 2 deletions src/termvisage/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,6 @@ def init_config() -> None:
context_keys["global"]["Config"][3] = False # Till the config menu is implemented
expand_key[3] = False # "Expand/Collapse Footer" action should be hidden

reconfigure_tui(_context_keys)


def load_config(config_file: str) -> None:
"""Loads a user config file."""
Expand Down
18 changes: 9 additions & 9 deletions src/termvisage/notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from . import __main__, logging, tui
from .config import config_options
from .ctlseqs import SGR_FG_BLUE, SGR_FG_DEFAULT, SGR_FG_RED, SGR_FG_YELLOW
from .tui import main, widgets

DEBUG = INFO = 0
WARNING = 1
Expand All @@ -25,16 +24,16 @@
def add_notification(msg: Union[str, Tuple[str, str]]) -> None:
"""Adds a message to the TUI notification bar."""
if _alarms.full():
clear_notification(main.loop, None)
widgets.notifications.contents.insert(
clear_notification(tui.main.loop, None)
tui.widgets.notifications.contents.insert(
0, (urwid.Filler(urwid.Text(msg, wrap="ellipsis")), ("given", 1))
)
_alarms.put(main.loop.set_alarm_in(5, clear_notification))
_alarms.put(tui.main.loop.set_alarm_in(5, clear_notification))


def clear_notification(loop: urwid.MainLoop, data: Any) -> None:
"""Removes the oldest message in the TUI notification bar."""
widgets.notifications.contents.pop()
tui.widgets.notifications.contents.pop()
loop.remove_alarm(_alarms.get())


Expand Down Expand Up @@ -70,9 +69,6 @@ def load() -> None:
- elipsis-style for the CLI
- braille-style for the TUI
"""
from .tui.main import update_screen
from .tui.widgets import loading

global _n_loading

stream = stdout if stdout.isatty() else stderr
Expand Down Expand Up @@ -108,6 +104,10 @@ def load() -> None:
_loading.clear() # Signal "not loading"
_loading.wait() # Wait for a loading operation

if _n_loading > -1: # Not skipping TUI phase?
from .tui.main import update_screen
from .tui.widgets import loading

while _n_loading > -1: # TUI phase hasn't ended?
while _n_loading > 0: # Anything loading?
# Animate the TUI loading indicator
Expand Down Expand Up @@ -188,7 +188,7 @@ def start_loading() -> None:
"""Signals the start of a progressive operation."""
global _n_loading

if not (QUIET or __main__.interrupted or main.quitting):
if not (QUIET or __main__.interrupted or tui.quitting):
_n_loading += 1
_loading.set()

Expand Down
70 changes: 39 additions & 31 deletions src/termvisage/tui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,57 @@

from __future__ import annotations

import argparse
import logging as _logging
import os
from pathlib import Path
from typing import Any, Dict, Iterable, Iterator, Tuple, Union
from typing import TYPE_CHECKING, Any, Dict

import urwid
from term_image.image import GraphicsImage
from term_image.utils import get_cell_size, lock_tty
from term_image.widget import UrwidImageScreen
if TYPE_CHECKING:
import argparse

from .. import notify
from ..config import config_options
from . import main, render
from .keys import adjust_footer, update_footer_expand_collapse_icon
from .main import process_input, scan_dir_grid, scan_dir_menu, sort_key_lexi
from .widgets import Image, info_bar, main as main_widget
from term_image.image import BaseImage


def init(
args: argparse.Namespace,
style_args: Dict[str, Any],
images: Iterable[Tuple[str, Union[Image, Iterator]]],
images: list[tuple[str, BaseImage | Ellipsis]],
contents: dict,
ImageClass: type,
) -> None:
"""Initializes the TUI"""

import urwid
from term_image.image import GraphicsImage
from term_image.utils import get_cell_size, lock_tty
from term_image.widget import UrwidImageScreen

from .. import notify
from ..__main__ import TEMP_DIR
from ..config import _context_keys, config_options, reconfigure_tui
from ..logging import LoggingThread, log
from . import keys
from . import main # Loaded before `.tui.keys` to prevent circular import
from . import keys, render
from .keys import adjust_footer, update_footer_expand_collapse_icon
from .main import process_input, scan_dir_grid, scan_dir_menu, sort_key_lexi
from .widgets import Image, info_bar, main as main_widget

global active, initialized, quitting

class Loop(urwid.MainLoop):
def start(self):
update_footer_expand_collapse_icon()
adjust_footer()
return super().start()

global active, initialized
def process_input(self, keys):
if "window resize" in keys:
# "window resize" never reaches `.unhandled_input()`.
# Adjust the footer and clear grid cache.
keys.append("resized")
return super().process_input(keys)

reconfigure_tui(_context_keys)

if args.debug:
main_widget.contents.insert(
Expand All @@ -60,6 +79,9 @@ def init(
render.REPEAT = args.repeat
render.THUMBNAIL_CACHE_SIZE = config_options.thumbnail_cache

images = [
entry if entry[1] is ... else (entry[0], Image(entry[1])) for entry in images
]
images.sort(
key=lambda x: sort_key_lexi(
Path(x[0] if x[1] is ... else x[1]._ti_image.source),
Expand Down Expand Up @@ -161,7 +183,7 @@ def init(
anim_render_manager.join()
log("Exited TUI normally", logger, direct=False)
except Exception:
main.quitting = True
quitting = True
render.image_render_queue.put((None,) * 3)
image_render_manager.join()
render.anim_render_queue.put((None,) * 3)
Expand All @@ -173,21 +195,7 @@ def init(
os.close(main.update_pipe)


class Loop(urwid.MainLoop):
def start(self):
update_footer_expand_collapse_icon()
adjust_footer()
return super().start()

def process_input(self, keys):
if "window resize" in keys:
# "window resize" never reaches `.unhandled_input()`.
# Adjust the footer and clear grid cache.
keys.append("resized")
return super().process_input(keys)


active = initialized = False
active = initialized = quitting = False
palette = [
("default", "", "", "", "", ""),
("default bold", "", "", "", "bold", ""),
Expand Down
4 changes: 2 additions & 2 deletions src/termvisage/tui/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from term_image.image import GraphicsImage
from term_image.utils import get_cell_size, get_terminal_size

from .. import __version__, logging
from .. import __version__, logging, tui
from ..config import config_options, context_keys, expand_key
from . import main
from .render import resync_grid_rendering
Expand Down Expand Up @@ -312,7 +312,7 @@ def update_footer_expand_collapse_icon():
# global
@register_key(("global", "Quit"))
def quit():
main.quitting = True
tui.quitting = True
raise urwid.ExitMainLoop()


Expand Down
5 changes: 2 additions & 3 deletions src/termvisage/tui/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from term_image.image import BaseImage
from term_image.utils import write_tty

from .. import logging, notify, tui
from .. import logging, notify
from ..config import context_keys, expand_key
from ..ctlseqs import BEL_b
from .keys import (
Expand Down Expand Up @@ -678,7 +678,6 @@ def update_screen():


logger = _logging.getLogger(__name__)
quitting = False

# For grid scanning/display
grid_acknowledge = Event()
Expand Down Expand Up @@ -708,7 +707,7 @@ def update_screen():
# Set from `.tui.init()`
ImageClass: BaseImage
displayer: Generator[None, int, bool]
loop: tui.Loop
loop: urwid.MainLoop
update_pipe: int

# # Corresponding to (or derived directly from) command-line args and/or config options
Expand Down
Loading

0 comments on commit b2b768e

Please sign in to comment.