Skip to content

Commit

Permalink
Merge branch 'main' into key-bar
Browse files Browse the repository at this point in the history
  • Loading branch information
AnonymouX47 committed May 21, 2024
2 parents 25cb576 + c3718a6 commit c7587f1
Show file tree
Hide file tree
Showing 9 changed files with 166 additions and 48 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- config: `thumbnail`, `thumbnail cache` and `thumbnail size` config options.
- args: `--thumbnail/--no-thumbnail` command-line option.
- tui: `Force Render` action to the `menu` context ([#13]).
- tui: Mouse scroll event handling for the image list (menu) ([2354639]).

### Changed
- cli,tui: Revamped the *max pixels* setting ([#13]).
Expand All @@ -36,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[c64f195]: https://github.com/AnonymouX47/termvisage/commit/c64f195a79557fdf5a9323db907a5716a12d6440
[9ea0572]: https://github.com/AnonymouX47/termvisage/commit/9ea0572e6db35984a4ae0af1691edfd179e5d393
[b90ceef]: https://github.com/AnonymouX47/termvisage/commit/b90ceefdd35a23eacb0e7199ea018776e79d7a14
[2354639]: https://github.com/AnonymouX47/termvisage/commit/235463981bad139ae93eeeec1079449d7d40dacf


## [0.1.0] - 2023-06-03
Expand Down
7 changes: 4 additions & 3 deletions src/termvisage/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from term_image.utils import write_tty

from .ctlseqs import RESTORE_WINDOW_TITLE_b, SAVE_WINDOW_TITLE_b, SET_WINDOW_TITLE_b
from .exit_codes import FAILURE, INTERRUPTED, codes


Expand Down Expand Up @@ -68,9 +69,9 @@ def finish_multi_logging():
logger = _logging.getLogger("termvisage")
logger.setLevel(_logging.INFO)

write_tty(SAVE_WINDOW_TITLE_b)
write_tty(SET_WINDOW_TITLE_b % b"TermVisage")
try:
write_tty(b"\033[22;2t") # Save window title
write_tty(b"\033]2;TermVisage\033\\") # Set window title
exit_code = cli.main()
except KeyboardInterrupt:
interrupted = True
Expand Down Expand Up @@ -115,7 +116,7 @@ def finish_multi_logging():
logger.info(f"Session ended with return-code {exit_code} ({codes[exit_code]})")
return exit_code
finally:
write_tty(b"\033[23;2t") # Restore window title
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:
Expand Down
7 changes: 3 additions & 4 deletions src/termvisage/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@

from . import logging, notify, tui
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 Thread, init_log, log, log_exception
from .logging_multi import Process
from .tui.widgets import Image
from .utils import CSI

try:
import fcntl # noqa: F401
Expand Down Expand Up @@ -742,7 +742,7 @@ def main() -> None:
try:
ImageClass(None)
except StyleError: # Instantiation isn't permitted
write_tty(f"{CSI}1K\r".encode()) # Erase emitted APCs
write_tty(ERASE_IN_LINE_LEFT_b + b"\r") # Erase any emitted APCs
log(
f"The '{ImageClass}' render style is not supported in the current "
"terminal! To use it anyways, add '--force-style'.",
Expand All @@ -752,7 +752,6 @@ def main() -> None:
return FAILURE
except TypeError: # Instantiation is permitted
if not ImageClass.is_supported():
write_tty(f"{CSI}1K\r".encode()) # Erase emitted APCs
log(
f"The '{ImageClass}' render style might not be fully supported in "
"the current terminal... using it anyways.",
Expand All @@ -762,7 +761,7 @@ def main() -> None:

# Some APCs (e.g kitty's) used for render style support detection get emitted on
# some non-supporting terminal emulators
write_tty(f"{CSI}1K\r".encode()) # Erase emitted APCs
write_tty(ERASE_IN_LINE_LEFT_b + b"\r") # Erase any emitted APCs

log(f"Using '{ImageClass}' render style", logger, verbose=True)
style_parser = style_parsers.get(ImageClass.style)
Expand Down
115 changes: 115 additions & 0 deletions src/termvisage/ctlseqs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""Control Sequences
See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
"""

from __future__ import annotations

__all__: list[str] = [] # Updated later on

# Parameters
# ======================================================================================

C = "%c"
Ps = "%d"
Pt = "%s"


# ============================ START OF CONTROL SEQUENCES ==============================

_START = None # Marks the beginning control sequence definitions


# C0
# ======================================================================================

BEL = "\x07"
ESC = "\x1b"

BEL_b: bytes
ESC_b: bytes

# C1
# ======================================================================================

APC = f"{ESC}_"
CSI = f"{ESC}["
OSC = f"{ESC}]"
ST = f"{ESC}\\"

APC_b: bytes
CSI_b: bytes
OSC_b: bytes
ST_b: bytes

# Functions Beginning With CSI
# ======================================================================================

ERASE_IN_LINE_LEFT = f"{CSI}1K"
RESTORE_WINDOW_TITLE = f"{CSI}23;2t"
SAVE_WINDOW_TITLE = f"{CSI}22;2t"

ERASE_IN_LINE_LEFT_b: bytes
RESTORE_WINDOW_TITLE_b: bytes
SAVE_WINDOW_TITLE_b: bytes

# # Select Graphic Rendition
# # ====================================================================================

SGR = f"{CSI}{Pt}m"
SGR_FG_8 = SGR % f"3{Ps}"
SGR_SHORT = SGR % Ps

SGR_b: bytes
SGR_FG_8_b: bytes
SGR_SHORT_b: bytes

SGR_BLUE = SGR_FG_8 % 4
SGR_BOLD = SGR_SHORT % 1
SGR_DEFAULT = SGR % ""
SGR_ITALIC = SGR_SHORT % 3
SGR_NOT_BOLD_FAINT = SGR_SHORT % 22
SGR_NOT_ITALIC = SGR_SHORT % 23
SGR_RED = SGR_FG_8 % 1
SGR_YELLOW = SGR_FG_8 % 3

SGR_BLUE_b: bytes
SGR_BOLD_b: bytes
SGR_DEFAULT_b: bytes
SGR_ITALIC_b: bytes
SGR_NOT_BOLD_FAINT_b: bytes
SGR_NOT_ITALIC_b: bytes
SGR_RED_b: bytes
SGR_YELLOW_b: bytes

# Operating System Commands
# ======================================================================================

TEXT_PARAM_SET = f"{OSC}{Ps};{Pt}{ST}"

TEXT_PARAM_SET_b: bytes

SET_WINDOW_TITLE = TEXT_PARAM_SET % (2, Pt)

SET_WINDOW_TITLE_b: bytes

# Kitty Graphics Protocol
# See https://sw.kovidgoyal.net/kitty/graphics-protocol/
# ======================================================================================

KITTY_START = f"{APC}G"
KITTY_DELETE_CURSOR_IMAGES = f"{KITTY_START}a=d,d=C;{ST}"

KITTY_START_b: bytes
KITTY_DELETE_CURSOR_IMAGES_b: bytes


# `bytes` Versions of Control Sequences
# ======================================================================================

module_items = tuple(globals().items())
for name, value in module_items[module_items.index(("_START", None)) + 1 :]:
globals()[f"{name}_b"] = value.encode()
__all__.extend((name, f"{name}_b"))

del _START, module_items
8 changes: 4 additions & 4 deletions src/termvisage/notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@

from . import __main__, cli, logging, tui
from .config import config_options
from .ctlseqs import SGR_BLUE, SGR_DEFAULT, SGR_RED, SGR_YELLOW
from .tui import main, widgets
from .utils import COLOR_RESET, CSI

DEBUG = INFO = 0
WARNING = 1
Expand Down Expand Up @@ -154,11 +154,11 @@ def notify(

if not tui.active:
print(
(f"{CSI}34m{context}:{COLOR_RESET} " if context else "")
(f"{SGR_BLUE}{context}:{SGR_DEFAULT} " if context else "")
+ (
f"{CSI}31m{msg}{COLOR_RESET}"
f"{SGR_RED}{msg}{SGR_DEFAULT}"
if level >= ERROR
else f"{CSI}33m{msg}{COLOR_RESET}" if level == WARNING else msg
else f"{SGR_YELLOW}{msg}{SGR_DEFAULT}" if level == WARNING else msg
),
file=stderr if level >= WARNING else stdout,
)
Expand Down
6 changes: 1 addition & 5 deletions src/termvisage/tui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@

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

from .. import logging, notify
from ..config import config_options
from ..utils import CSI
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
Expand Down Expand Up @@ -150,7 +149,6 @@ def init(
anim_render_manager.start()

try:
write_tty(f"{CSI}?1049h".encode()) # Switch to the alternate buffer
next(main.displayer)
main.loop.run()
if main.THUMBNAIL:
Expand All @@ -169,8 +167,6 @@ def init(
anim_render_manager.join()
raise
finally:
# urwid fails to restore the normal buffer on some terminals
write_tty(f"{CSI}?1049l".encode()) # Switch back to the normal buffer
main.displayer.close()
active = False
os.close(main.update_pipe)
Expand Down
31 changes: 14 additions & 17 deletions src/termvisage/tui/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@
import PIL
import urwid
from term_image.image import BaseImage
from term_image.utils import write_tty

from .. import logging, notify, tui
from ..config import context_keys, expand_key
from ..ctlseqs import BEL_b
from .keys import (
disable_actions,
enable_actions,
keys,
menu_nav,
no_globals,
set_image_grid_actions,
set_image_view_actions,
Expand Down Expand Up @@ -338,37 +339,33 @@ def process_input(key: str) -> bool:
or key in {"resized", expand_key[0]}
):
func, state = keys["global"][key]
func() if state else print("\a", end="", flush=True)
func() if state else write_tty(BEL_b)
found = True

elif key[0] == "mouse press": # strings also support subscription
# change context if the pane in focus changed.
if _context in {"image", "image-grid"} and viewer.focus_position == 0:
set_context("menu")
menu_nav()
found = True
elif _context == "menu":
if viewer.focus_position == 1:
if not context_keys["menu"]["Switch Pane"][4]:
# Set focus back to the menu if "menu::Switch Pane" is disabled
viewer.focus_position = 0
elif _context == "menu" and viewer.focus_position == 1:
if not context_keys["menu"]["Switch Pane"][4]:
# Set focus back to the menu if "menu::Switch Pane" is disabled
viewer.focus_position = 0
else:
if view.original_widget is image_box:
set_context("image")
set_image_view_actions()
else:
if view.original_widget is image_box:
set_context("image")
set_image_view_actions()
else:
set_context("image-grid")
set_image_grid_actions()
else: # Update image view
menu_nav()
set_context("image-grid")
set_image_grid_actions()
found = True

else:
func, state = keys[_context].get(key, (None, None))
if state:
func()
elif state is False:
print("\a", end="", flush=True)
write_tty(BEL_b)
found = state is not None

return bool(found)
Expand Down
30 changes: 23 additions & 7 deletions src/termvisage/tui/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import urwid
from term_image.image import BaseImage, Size
from term_image.utils import get_terminal_name_version
from term_image.utils import get_terminal_name_version, write_tty
from urwid import (
PACK,
AttrMap,
Expand All @@ -27,7 +27,7 @@

from .. import logging
from ..config import config_options, context_keys, navi
from ..utils import KITTY_DELETE_CURSOR_IMAGES_b
from ..ctlseqs import BEL_b, KITTY_DELETE_CURSOR_IMAGES_b, KITTY_START_b
from . import keys, main as tui_main
from .render import (
anim_render_queue,
Expand Down Expand Up @@ -70,7 +70,7 @@ def mouse_event(
) -> bool:
if event == "mouse press" and button == 1:
if not self._ti_enabled:
print("\a", end="", flush=True)
write_tty(BEL_b)
return False

if self._ti_func:
Expand Down Expand Up @@ -644,7 +644,7 @@ def content(self, trim_left=0, trim_top=0, cols=None, rows=None, attr_map=None):
for line in self.lines[-min(0, pad_up) : min(0, pad_down) or len(self.lines)]:
yield [
fill_left,
*(delete * line.startswith(b"\033_G")),
*(delete * line.startswith(KITTY_START_b)),
(None, "U", line),
fill_right,
disguise,
Expand Down Expand Up @@ -682,9 +682,7 @@ def __init__(self, widget, title="", title_attr=None):
top_w = Columns(
[
(PACK, Text("┌")),
Columns(
[(PACK, AttrMap(title_w, title_attr or "default")), Divider("─")]
),
Columns([(PACK, AttrMap(title_w, title_attr)), Divider("─")]),
(PACK, Text("┐")),
]
)
Expand Down Expand Up @@ -738,6 +736,24 @@ def keypress(self, size: Tuple[int, int], key: str) -> Optional[str]:
ret = super().keypress(size, key)
return key if key in navi else ret

def mouse_event(
self,
size: tuple[int, int],
event: str,
button: int,
col: int,
row: int,
focus: bool,
) -> bool:
if not focus:
return True

super().mouse_event(size, event, button, col, row, focus)
keys.menu_nav()

# Allow the event to be further handled by `.tui.main.process_input()`.
return False

def render(self, size: Tuple[int, int], focus: bool = False):
self._ti_height = size[1] # Used by MenuScanner
return super().render(size, focus)
Expand Down
Loading

0 comments on commit c7587f1

Please sign in to comment.