Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 31 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ _textual-image_ offers both Rich renderables and Textual Widgets that leverage t

- **Terminal Graphics Protocol (TGP)**: Initially introduced by the [Kitty](https://sw.kovidgoyal.net/kitty/) terminal emulator. While support is partially available in other terminals, it doesn't seem to be really usable there.
- **Sixel Graphics**: Supported by various terminal emulators including [xterm](https://invisible-island.net/xterm/) and a lot of others.
- **iTerm2 Inline Images Protocol**: Originally developed for [iTerm2](https://iterm2.com/), this protocol is now supported by several other terminal emulators for displaying inline images.

_Note_: As implementation of these protocols differ a lot feedback on different terminal emulators is very welcome.

Expand All @@ -23,21 +24,21 @@ See the Support Matrix below on what was tested already.

[^1]: Based on [Are We Sixel Yet?](https://www.arewesixelyet.com/)

| Terminal | TGP support | Sixel support | Works with textual-image |
|---------------------|:-----------:|:-------------:|:------------------------:|
| Black Box | ❌ | ✅ | ✅ |
| foot | ❌ | ✅ | ✅ |
| GNOME Terminal | ❌ | ❌ | |
| iTerm2 | ❌ | ✅ | ✅ |
| kitty | ✅ | ❌ | ✅ |
| konsole | ✅ | ✅ | ✅ |
| tmux | ⚠️ | ✅ | ✅ |
| Visual Studio Code | ❌ | ✅ | ✅ |
| Warp | ❌ | ❌ | ❌ |
| wezterm | ✅ | ✅ | ✅ |
| Windows Console | ❌ | ❌ | |
| Windows Terminal | ❌ | ✅ | ✅ |
| xterm | ❌ | ✅ | ✅ |
| Terminal | TGP support | Sixel support | iTerm2 support | Works with textual-image |
|---------------------|:-----------:|:-------------:|:--------------:|:------------------------:|
| Black Box | ❌ | ✅ | ❌ | ✅ |
| foot | ❌ | ✅ | ✅ | ✅ |
| GNOME Terminal | ❌ | ❌ | ❌ | |
| iTerm2 | ❌ | ✅ | ✅ | ✅ |
| kitty | ✅ | ❌ | ❌ | ✅ |
| konsole | ✅ | ✅ | ✅ | ✅ |
| tmux | ⚠️ | ✅ | ⚠️ | ✅ |
| Visual Studio Code | ❌ | ✅ | ✅ | ✅ |
| Warp | ❌ | ❌ | ❌ | ❌ |
| wezterm | ✅ | ✅ | ✅ | ✅ |
| Windows Console | ❌ | ❌ | ❌ | |
| Windows Terminal | ❌ | ✅ | ❌ | ✅ |
| xterm | ❌ | ✅ | ❌ | ✅ |

✅ = Supported; ❌ = Not Supported; ⚠️ = Requires additional terminal/tmux configuration

Expand All @@ -46,6 +47,7 @@ See the Support Matrix below on what was tested already.
**Homepage**: https://gitlab.gnome.org/raggesilver/blackbox
**TGP support**: No
**Sixel support**: Yes
**iTerm2 support**: No
**Works**: Yes

**Notes**:
Expand All @@ -59,6 +61,7 @@ This is the case for the Flatpak version of BlackBox, but not on most Linux dist
**Homepage**: https://codeberg.org/dnkl/foot
**TGP support**: No
**Sixel support**: Yes
**iTerm2 support**: Yes
**Works**: Yes

**Notes:**
Expand All @@ -69,6 +72,7 @@ Works out of the box, no known issues.
**Homepage**: https://gitlab.gnome.org/GNOME/gnome-terminal
**TGP support**: No
**Sixel support**: No
**iTerm2 support**: No
**Works**: No

**Notes:**
Expand All @@ -79,6 +83,7 @@ Relies on VTE Sixel implementation (<https://gitlab.gnome.org/GNOME/vte/-/issues
**Homepage**: https://iterm2.com/
**TGP support**: No
**Sixel support**: Yes
**iTerm2 support**: Yes
**Works**: Yes

**Notes:**
Expand All @@ -89,6 +94,7 @@ Works out of the box.
**Homepage**: https://sw.kovidgoyal.net/kitty/
**TGP support**: Yes
**Sixel support**: No
**iTerm2 support**: No
**Works**: Yes

**Notes:**
Expand All @@ -99,6 +105,7 @@ Works out of the box.
**Homepage**: https://konsole.kde.org/
**TGP support**: Partially
**Sixel support**: Yes
**iTerm2 support**: Yes
**Works**: Yes

**Notes:**
Expand All @@ -109,10 +116,11 @@ TGP support is not in a usable state. However, Sixel is working out of the box w
**Homepage**: https://github.com/tmux/tmux/wiki
**TGP support**: Via passthrough
**Sixel support**: Yes
**iTerm2 support**: Via passthrough
**Works**: Partially

**Notes:**
tmux does not render TGP itself, but TGP can work when tmux passes the Kitty graphics control sequences through to a TGP-enabled outer terminal and preserves truecolor output. Sixel should use tmux's native Sixel support instead, which requires tmux to know that the outer terminal supports Sixel.
tmux does not render TGP or iTerm2 inline images itself, but these protocols can work when tmux passes the control sequences through to a compatible outer terminal and preserves truecolor output. Sixel should use tmux's native Sixel support instead, which requires tmux to know that the outer terminal supports Sixel.

For a broad setup, add this to `.tmux.conf`, then restart tmux or reload the config:

Expand All @@ -128,16 +136,18 @@ If you prefer a narrower rule, replace `*` with the terminal name shown by `tmux
**Homepage**: https://code.visualstudio.com/
**TGP support**: No
**Sixel support**: Yes
**iTerm2 support**: Yes
**Works**: Yes

**Notes:**
The `terminal.integrated.enableImages` setting has to be enabled.
The `terminal.integrated.enableImages` setting has to be enabled for Sixel support. iTerm2 inline image protocol works out of the box.

### Warp

**Homepage**: https://www.warp.dev/
**TGP support**: No
**Sixel support**: No
**iTerm2 support**: No
**Works**: No

**Notes:**
Expand All @@ -148,6 +158,7 @@ Warp partially supports TGP and reports so when queried for it. But as it does n
**Homepage**: https://wezfurlong.org/wezterm/index.html
**TGP support**: No
**Sixel support**: Yes
**iTerm2 support**: Yes
**Works**: Yes

**Notes:**
Expand All @@ -157,6 +168,7 @@ Works out of the box.

**TGP support**: No
**Sixel support**: No
**iTerm2 support**: No
**Works**: No

**Notes:**
Expand All @@ -167,6 +179,7 @@ Windows Console and Windows Terminal are two different pieces of software. The l
**Homepage**: https://github.com/microsoft/terminal
**TGP support**: No
**Sixel support**: Yes
**iTerm2 support**: No
**Works**: Yes

**Notes:**
Expand All @@ -177,6 +190,7 @@ Sixel support was added in version 1.22, please make sure you're on that version
**Homepage**: https://invisible-island.net/xterm/
**TGP support**: No
**Sixel support**: Yes
**iTerm2 support**: No
**Works**: Yes

**Notes:**
Expand Down
36 changes: 36 additions & 0 deletions tests/renderable/__snapshots__/test_iterm2.ambr

Large diffs are not rendered by default.

18 changes: 11 additions & 7 deletions tests/renderable/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,26 @@

def test_determining_best_renderable() -> None:
import textual_image.renderable
from textual_image.renderable import halfcell, sixel, tgp, unicode
from textual_image.renderable import halfcell, iterm2, sixel, tgp, unicode

with patch("sys.__stdout__.isatty", return_value=True):
with patch("textual_image.renderable.tgp.query_terminal_support", return_value=True):
with patch("textual_image.renderable.iterm2.query_terminal_support", return_value=True):
module = reload(textual_image.renderable)
assert module.Image is tgp.Image
assert module.Image is iterm2.Image

with patch("textual_image.renderable.tgp.query_terminal_support", return_value=False):
with patch("textual_image.renderable.iterm2.query_terminal_support", return_value=False):
with patch("textual_image.renderable.sixel.query_terminal_support", return_value=True):
module = reload(textual_image.renderable)
assert module.Image is sixel.Image

with patch("textual_image.renderable.tgp.query_terminal_support", return_value=False):
with patch("textual_image.renderable.sixel.query_terminal_support", return_value=False):
module = reload(textual_image.renderable)
assert module.Image is halfcell.Image
with patch("textual_image.renderable.tgp.query_terminal_support", return_value=True):
module = reload(textual_image.renderable)
assert module.Image is tgp.Image

with patch("textual_image.renderable.tgp.query_terminal_support", return_value=False):
module = reload(textual_image.renderable)
assert module.Image is halfcell.Image

with patch("sys.__stdout__.isatty", return_value=False):
module = reload(textual_image.renderable)
Expand Down
68 changes: 68 additions & 0 deletions tests/renderable/test_iterm2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from unittest.mock import patch

from rich.console import Console
from rich.measure import Measurement
from syrupy.assertion import SnapshotAssertion

from tests.data import CONSOLE_OPTIONS, TEST_IMAGE
from tests.utils import render


def test_build_iterm2_sequence() -> None:
from textual_image.renderable.iterm2 import _build_iterm2_sequence

seq = _build_iterm2_sequence("abc123", 100, 200)
assert seq.startswith("\x1b]1337;File=")
assert "width=100px" in seq
assert "height=200px" in seq
assert "inline=1" in seq
assert "preserveAspectRatio=0" in seq
assert ":abc123\x07" in seq


def test_render(snapshot: SnapshotAssertion) -> None:
from textual_image.renderable.iterm2 import Image

renderable = Image(TEST_IMAGE, width=4)
assert render(renderable) == snapshot


def test_measure() -> None:
from textual_image.renderable.iterm2 import Image

renderable = Image(TEST_IMAGE, width=4)
assert renderable.__rich_measure__(Console(), CONSOLE_OPTIONS) == Measurement(4, 4)


def test_cleanup() -> None:
from textual_image.renderable.iterm2 import Image

Image(TEST_IMAGE, width=4).cleanup()


def test_query_terminal_support_no_stdout() -> None:
from textual_image.renderable.iterm2 import query_terminal_support

with patch("sys.__stdout__", None):
assert not query_terminal_support()


def test_query_terminal_support_iterm2_env() -> None:
from textual_image.renderable.iterm2 import query_terminal_support

with patch("sys.__stdout__"), patch.dict("os.environ", {"TERM_PROGRAM": "iTerm2"}):
assert query_terminal_support()


def test_query_terminal_support_wezterm_env() -> None:
from textual_image.renderable.iterm2 import query_terminal_support

with patch("sys.__stdout__"), patch.dict("os.environ", {"TERM_PROGRAM": "WezTerm"}):
assert query_terminal_support()


def test_query_terminal_support_unknown_env() -> None:
from textual_image.renderable.iterm2 import query_terminal_support

with patch("sys.__stdout__"), patch.dict("os.environ", {"TERM_PROGRAM": "xterm-256color"}):
assert not query_terminal_support()
Loading
Loading