Chat: a sender label is a name drawn as a path line's chip
A name standing alone above its messages was a coloured word with nothing to say it was a label — at a glance it read like something someone typed. It now sits in a chip, and not a chip of its own invention: ui/widgets. name_chip builds a one-hop PathLine and asks it for its line, so the fill, the ink, the padding, the rounded cap and the closing point are whatever a route's segments are wearing today and cannot drift from them. A sender wears exactly what that node wears as a hop — hue in the fill under dark ink, the keyless grey where no key places them, and our own messages headed by the ★ on its neutral grey rather than by the word "you". Only the group header. An @mention inside a body is part of what was said, and framing it would put a chip in the middle of a sentence. Degrading comes free with the borrowing: where the terminal can't draw separators — the PicoCalc console among them — a path line is arrow text and one hop is just the name in its hue, which is what a sender label was before it was framed. The specimen card grows a chips row, which doubles as a one-hop sample of the route language. And the unknown sender takes node.unknown rather than muted: one hex apart here, a whole slot apart on the console, and an unidentified sender is content you can still act on, not chrome. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
e5c756995e
commit
f22ffa5011
7 files changed
+137
-142
No files matched your search
+24
-4
@@ -436,15 +436,22 @@ class ChatScreen(Screen):
|
||||
return (self._name(message.peer) or message.peer or "?"), message.text
|
||||
|
||||
def _group_header(self, sender: str, *, is_self: bool = False) -> Text:
|
||||
"""Build the sender header that starts a group: the name, chipped, in its hue.
|
||||
"""Build the sender header that starts a group: the name, drawn as a chip.
|
||||
|
||||
The chip is what separates a *label* from the prose under it — a name standing
|
||||
alone above its messages reads as a tag rather than as a coloured word someone
|
||||
typed. Only this one is framed: an ``@mention`` inside a body (see
|
||||
typed. It is a path line's own chip (see :func:`~meshterm.ui.widgets.name_chip`),
|
||||
so a sender wears exactly what that node wears as a hop in a route, our own
|
||||
messages included: the ``★``, never our name.
|
||||
|
||||
Only this one is framed: an ``@mention`` inside a body (see
|
||||
:meth:`_render_mentions`) is part of what was said, and inscribing it would put a
|
||||
chip in the middle of a sentence.
|
||||
"""
|
||||
return name_chip(sender, self._sender_style(sender, is_self=is_self))
|
||||
if is_self:
|
||||
return name_chip("", you=True)
|
||||
# ``·`` is a channel line that arrived with no sender prefix — nobody to key on.
|
||||
return name_chip(sender, None if sender == "·" else self._sender_key(sender))
|
||||
|
||||
def _body_lines(
|
||||
self, body: str, message: ChatMessage, width: int, *, selected: bool = False
|
||||
@@ -560,10 +567,23 @@ class ChatScreen(Screen):
|
||||
return "you" # white, out of the per-sender hue range — always easy to spot
|
||||
if sender == "·":
|
||||
return "node.unknown"
|
||||
return name_style(sender, self._sender_key(sender, mention=mention))
|
||||
|
||||
def _sender_key(self, sender: str, *, mention: bool = False) -> Optional[str]:
|
||||
"""The key a sender name resolves back to, or ``None`` when nothing places it.
|
||||
|
||||
Contacts first, then the recorder's stored names; in a *direct* thread a sender
|
||||
label that resolves to nothing falls back to the peer's own key, since the only
|
||||
two people in the conversation are us and them. A mention names an arbitrary
|
||||
person, so that fallback must not apply to one.
|
||||
|
||||
Shared by the colour (:meth:`_sender_style`) and the chip
|
||||
(:meth:`_group_header`) so the two can't disagree about who a name is.
|
||||
"""
|
||||
key = self._key_of(sender)
|
||||
if not key and not self._is_channel and not mention:
|
||||
key = self._peer_key or None
|
||||
return name_style(sender, key)
|
||||
return key
|
||||
|
||||
# --- input ---------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -116,15 +116,16 @@ def specimen_lines() -> list[RenderableType]:
|
||||
names.append("me", style="you")
|
||||
lines.append(names)
|
||||
|
||||
# The same names framed — the chat's sender labels. On the console this row shows
|
||||
# them bare, which is the point: there is no dark grey to field a chip with there.
|
||||
# The same names as chips — the chat's sender labels, drawn through the path line's
|
||||
# own segment, so this row is also a one-hop sample of the route language: hue in the
|
||||
# fill, our end the star, a node nobody can place in the keyless grey.
|
||||
chips = Text("chips ")
|
||||
for name, key in _DEMO_NAMES[:2]:
|
||||
chips.append_text(name_chip(name, name_style(name, key)))
|
||||
chips.append_text(name_chip(name, key))
|
||||
chips.append(" ")
|
||||
chips.append_text(name_chip("me", "you"))
|
||||
chips.append_text(name_chip("", you=True))
|
||||
chips.append(" ")
|
||||
chips.append_text(name_chip("·", "node.unknown"))
|
||||
chips.append_text(name_chip("·"))
|
||||
lines.append(chips)
|
||||
|
||||
lines.append(Text("heard ").append_text(_ages_row()))
|
||||
|
||||
@@ -136,11 +136,6 @@ MESH_THEME = Theme(
|
||||
# only PicoCalc's footer_fkeys ever asks for them.
|
||||
"fkey.chip": "bold #ffffff on #475569",
|
||||
"fkey.chip.shift": "bold #ffffff on #16a34a",
|
||||
# The field a node name is inscribed on when a surface draws it as a chip (see
|
||||
# ui.widgets.name_chip): the same neutral dark grey pathline fills our own ``★``
|
||||
# chip with, so the two chip languages agree. Background only — the ink is always
|
||||
# the node's own hue, laid over this.
|
||||
"name.chip": "on #3f3f46",
|
||||
# The prose voices — what a markdown page's inline marks are drawn in (see
|
||||
# ui.markdown). Headings borrow the styles the rest of the app already heads
|
||||
# sections with (``brand``, ``accent``), so only the *body* marks need names of
|
||||
@@ -317,13 +312,6 @@ MESH_THEME_16 = Theme(
|
||||
# Shift bank, white text on both.
|
||||
"fkey.chip": "bold color(15) on color(7)",
|
||||
"fkey.chip.shift": "bold color(15) on color(2)",
|
||||
# The name chip's field, which this console cannot actually draw: the palette's
|
||||
# dark grey is slot 8 and backgrounds stop at 7, so there is no neutral field for
|
||||
# a coloured name to sit on (the F-key lane's light grey is far too bright to take
|
||||
# a node hue). The name is drawn bare here instead — ui.widgets.name_chip fields a
|
||||
# chip only where the platform has the colour for one — and this entry exists so
|
||||
# the style name resolves on both platforms, as every style name must.
|
||||
"name.chip": "on color(0)",
|
||||
# Prose on the console. The page's body text is the default light grey (slot 7),
|
||||
# so emphasis is the one place the VT's bold-is-brightness rule *is* the design:
|
||||
# ``md.strong`` says bold on purpose and lands on white, exactly the step up the
|
||||
|
||||
+22
-77
@@ -18,7 +18,6 @@ from rich.progress import (
|
||||
TextColumn,
|
||||
TimeElapsedColumn,
|
||||
)
|
||||
from rich.style import Style
|
||||
from rich.table import Table
|
||||
from rich.text import Text
|
||||
|
||||
@@ -52,14 +51,8 @@ from .marks import (
|
||||
LabelRgbOf,
|
||||
parse_hex,
|
||||
)
|
||||
from .pathline import (
|
||||
POWERLINE_ROUND_CLOSE,
|
||||
POWERLINE_ROUND_OPEN,
|
||||
PathLine,
|
||||
path_line,
|
||||
)
|
||||
from .termfont import powerline_full
|
||||
from .theme import active_theme, glyph, mark_rgb, name_style, node_style, snr_style
|
||||
from .pathline import SELF_GLYPH, PathHop, PathLine, path_line
|
||||
from .theme import glyph, mark_rgb, name_style, node_style, snr_style
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..core.discovery import DiscoveredDevice
|
||||
@@ -502,82 +495,34 @@ def highlighted_hash(
|
||||
return text
|
||||
|
||||
|
||||
#: The theme name of the field a chipped name sits on (defined by both themes).
|
||||
_CHIP_FILL = "name.chip"
|
||||
def name_chip(label: str, key: Optional[str] = None, *, you: bool = False) -> Text:
|
||||
"""A node name drawn as a path line's single chip — THE way a surface *frames* a name.
|
||||
|
||||
#: The chip's two end caps, in the two shapes a terminal can give them. The rounded caps
|
||||
#: (:data:`~meshterm.ui.pathline.POWERLINE_ROUND_OPEN` / ``_CLOSE``) are the lozenge a
|
||||
#: path line already closes on, so a chipped name and a chipped hop round the same way —
|
||||
#: but they live in a full Nerd Font patch's extended block. Without one the caps fall
|
||||
#: back to the half blocks: ``▐`` fills a cell's right half and ``▌`` its left, so drawn in
|
||||
#: the fill over the page they widen the field by half a cell at each end and taper it,
|
||||
#: exactly the way the device picker's Bluetooth badge is rounded off (see
|
||||
#: :func:`~meshterm.ui.device_picker._type_cell`). Two cells of overhead either way.
|
||||
_ROUND_CAPS = (POWERLINE_ROUND_OPEN, POWERLINE_ROUND_CLOSE)
|
||||
_HALF_CAPS = ("▐", "▌")
|
||||
Not an imitation of the chip language but a use of it: this builds a one-hop
|
||||
:class:`~meshterm.ui.pathline.PathLine` and asks it for its line, so the fill, the
|
||||
ink, the padding, the rounded cap and the closing point are all whatever a route's
|
||||
segments are wearing today, and can never drift from them (JP, 2026-08-12). The colour
|
||||
reads exactly as it does in a route — the *fill* carries the node's hue with dark ink
|
||||
on it, a node no key can place takes the keyless grey, and our own end is the map's
|
||||
``★`` on its neutral dark grey rather than a name.
|
||||
|
||||
|
||||
def name_chip(label: str, style: str) -> Text:
|
||||
"""A node name inscribed in a chip — THE way a surface *frames* a name.
|
||||
|
||||
The name keeps its own hue (the caller passes whatever style it would have drawn the
|
||||
name in — a key-derived palette colour, the ``you`` white, the ``node.unknown`` grey)
|
||||
and that hue becomes the chip's ink, laid on the neutral dark grey field
|
||||
:data:`_CHIP_FILL` names. Nothing about which colour a name gets changes here; the
|
||||
chip only gives the colour something to sit on, so a sender label reads as a tag
|
||||
rather than as a stray coloured word above a message.
|
||||
|
||||
The ink is resolved to a concrete :class:`~rich.style.Style` and *added* to the fill
|
||||
rather than concatenated as text: Rich silently drops a style string that mixes a
|
||||
theme name with anything else (``"node.unknown on #3f3f46"`` renders as plain text),
|
||||
which is exactly the shape a naive f-string would build here.
|
||||
|
||||
A platform whose console cannot field the chip draws the bare name instead — see
|
||||
:func:`_bind_chip`, and the ``name.chip`` note in the 16-slot theme.
|
||||
Degrading is inherited too: where the terminal can't draw powerline separators, a path
|
||||
line is arrow-mode text and a lone hop is simply the name in its own hue — which is
|
||||
what a sender label was before it was framed, and what the PicoCalc console (whose font
|
||||
carries no separator glyph) goes on drawing.
|
||||
|
||||
Args:
|
||||
label: The name as it should read (already resolved; this never renames it).
|
||||
style: The style the name would wear uncipped — a theme name or a literal
|
||||
``"bold #rrggbb"``, either way the node's own colour.
|
||||
Ignored when ``you`` — our own end is the star, never our name.
|
||||
key: Any known prefix of the node's key, which picks the hue. ``None`` is a node
|
||||
we can't place: the keyless grey, colour being reserved for keyed identities.
|
||||
you: This is our own node — the ``★``, exactly as a route draws our end of it.
|
||||
|
||||
Returns:
|
||||
The chip as a :class:`Text`, or the plain styled name where chips aren't drawn.
|
||||
The chip as a one-line :class:`Text`.
|
||||
"""
|
||||
if not _chip_fielded:
|
||||
return Text(label, style=style)
|
||||
fill = active_theme().styles[_CHIP_FILL]
|
||||
themed = active_theme().styles.get(style)
|
||||
ink = themed if themed is not None else Style.parse(style)
|
||||
# The caps are the fill drawn *over the page* — foreground only, so the half of the
|
||||
# cell that isn't chip stays whatever the row behind it is.
|
||||
edge = Style(color=fill.bgcolor)
|
||||
# Asked per call rather than bound: the verdict is cached after the first ask, and
|
||||
# binding it would move termfont's installed-font scan onto every import of this
|
||||
# module — including a bare ``meshterm --help``, which draws nothing at all.
|
||||
open_cap, close_cap = _ROUND_CAPS if powerline_full() else _HALF_CAPS
|
||||
text = Text()
|
||||
text.append(open_cap, style=edge)
|
||||
text.append(label, style=ink + fill)
|
||||
text.append(close_cap, style=edge)
|
||||
return text
|
||||
|
||||
|
||||
#: Whether this platform draws the chip at all, rather than the bare name (bound below).
|
||||
_chip_fielded: bool = True
|
||||
|
||||
|
||||
@on_platform
|
||||
def _bind_chip(platform: Platform) -> None:
|
||||
"""Decide whether the platform can field a chip behind a name.
|
||||
|
||||
The chip is a *field* behind a name, and the 16-slot console has none to offer: its
|
||||
one dark grey is slot 8 and backgrounds stop at 7, so the choice there is between a
|
||||
name on light grey (which drowns the hue the chip exists to carry) and no chip at
|
||||
all. Truecolor is the honest proxy for "this platform has a neutral dark grey", the
|
||||
same test the heat gradient binds on.
|
||||
"""
|
||||
global _chip_fielded
|
||||
_chip_fielded = platform.truecolor
|
||||
hop = PathHop(SELF_GLYPH, you=True) if you else PathHop(label, key=key or None)
|
||||
return PathLine([hop]).text()
|
||||
|
||||
|
||||
def _shorten_hash(value: str, hash_bytes: Optional[int]) -> str:
|
||||
|
||||
+19
-1
@@ -19,7 +19,7 @@ always see the colours the app really emits.
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Iterable, Iterator, Union
|
||||
from typing import Callable, Iterable, Iterator, Union
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -53,6 +53,24 @@ def _plain_path_rendering(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
|
||||
powerline_support.cache_clear()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def powerline(monkeypatch: pytest.MonkeyPatch) -> Callable[[bool], None]:
|
||||
"""Pin the powerline verdict for a test that is *about* chips.
|
||||
|
||||
The suite runs with ``MESHTERM_POWERLINE=0`` so screen assertions don't change with
|
||||
the developer's glass (see the module docstring); a test of the chip language itself
|
||||
needs the other answer, and asks for it through the supported override rather than by
|
||||
reaching into the widget. ``monkeypatch`` restores the env and the autouse fixture
|
||||
above clears the memoized verdict either side, so nothing leaks.
|
||||
"""
|
||||
|
||||
def _set(on: bool) -> None:
|
||||
monkeypatch.setenv("MESHTERM_POWERLINE", "1" if on else "0")
|
||||
powerline_support.cache_clear()
|
||||
|
||||
return _set
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _reset_platform() -> Iterator[None]:
|
||||
"""Every test starts and ends on :data:`~meshterm.platforms.REGULAR`.
|
||||
|
||||
+12
-12
@@ -976,7 +976,8 @@ def test_channel_transcript_groups_by_sender() -> None:
|
||||
rendered = _strip_ansi("\n".join(screen._render_grouped(80)))
|
||||
|
||||
assert rendered.count("Alice") == 1 # the two Alice messages share one header
|
||||
assert "Bob" in rendered and "you" in rendered
|
||||
# Our own group is headed by the ★ a route draws our end with, never by our name.
|
||||
assert "Bob" in rendered and "★" in rendered
|
||||
assert "hi" in rendered and "again" in rendered # bodies present, prefix stripped
|
||||
assert "Alice: hi" not in rendered # the raw name prefix is lifted into the header
|
||||
# Each message keeps its own timestamp on its line, even when grouped under one sender.
|
||||
@@ -986,10 +987,11 @@ def test_channel_transcript_groups_by_sender() -> None:
|
||||
assert stamps[0] != stamps[1] # grouped Alice messages show distinct times
|
||||
|
||||
|
||||
def test_transcript_inscribes_the_sender_label_but_never_a_mention() -> None:
|
||||
"""The name that *opens* a group is a tag; the same name inside a body is prose."""
|
||||
def test_transcript_inscribes_the_sender_label_but_never_a_mention(powerline) -> None:
|
||||
"""The name that *opens* a group is a chip; the same name inside a body is prose."""
|
||||
from meshterm.ui.widgets import name_chip
|
||||
|
||||
powerline(True) # this is a test *about* the chip, so ask for a glass that draws one
|
||||
conv = Conversation(label="#public", is_channel=True, channel_idx=0)
|
||||
messages = [
|
||||
ChatMessage(text="Alice: @[Bob] you around?", is_channel=True, channel_idx=0),
|
||||
@@ -997,14 +999,11 @@ def test_transcript_inscribes_the_sender_label_but_never_a_mention() -> None:
|
||||
screen = ChatScreen(conv, messages, send=None, names={}, session=_StubSession())
|
||||
rendered = _strip_ansi("\n".join(screen._render_grouped(80)))
|
||||
|
||||
# Built through the widget rather than spelled out: which caps it uses depends on the
|
||||
# font the session found, and the point here is the framing, not the glyph.
|
||||
def chipped(name: str) -> str:
|
||||
return name_chip(name, "bold #ffffff").plain
|
||||
|
||||
assert chipped("Alice") in rendered # the header, inscribed
|
||||
assert "@Bob" in rendered # the mention, as it was typed
|
||||
assert chipped("Bob") not in rendered # …and not framed mid-sentence
|
||||
# Built through the widget rather than spelled out: the caps are the path line's, and
|
||||
# what is being asserted here is the framing, not the glyph.
|
||||
assert name_chip("Alice").plain in rendered # the header, as a chip
|
||||
assert "@Bob" in rendered # the mention, as it was typed
|
||||
assert name_chip("Bob").plain not in rendered # …and never framed mid-sentence
|
||||
|
||||
|
||||
def test_unidentified_sender_takes_the_node_grey_not_the_chrome_grey() -> None:
|
||||
@@ -1230,7 +1229,8 @@ def test_channel_own_messages_do_not_merge_with_remote_namesake() -> None:
|
||||
]
|
||||
screen = _channel_screen(messages)
|
||||
rendered = _strip_ansi("\n".join(screen._render_grouped(80)))
|
||||
assert rendered.count("you") == 2 # two separate headers, not one merged group
|
||||
# Two headers, and not merely un-merged: theirs is the name they sent, ours is the ★.
|
||||
assert rendered.count("you") == 1 and rendered.count("★") == 1
|
||||
|
||||
|
||||
def _channel_screen(messages, session=None, key_of=None) -> ChatScreen:
|
||||
|
||||
+54
-31
@@ -7,7 +7,16 @@ and every caller inherits it.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from meshterm.platforms import PICOCALC, REGULAR, set_platform
|
||||
from rich.style import Style
|
||||
from rich.text import Text
|
||||
|
||||
from meshterm.ui.pathline import (
|
||||
POWERLINE_ROUND_CLOSE,
|
||||
POWERLINE_ROUND_OPEN,
|
||||
PathHop,
|
||||
PathLine,
|
||||
)
|
||||
from meshterm.ui.theme import node_style
|
||||
from meshterm.ui.widgets import (
|
||||
_format_age,
|
||||
format_ago,
|
||||
@@ -17,6 +26,11 @@ from meshterm.ui.widgets import (
|
||||
)
|
||||
|
||||
|
||||
def _span_style(text: Text, run: str) -> Style:
|
||||
"""The style over ``run`` in ``text``, parsed — pathline spans carry style *strings*."""
|
||||
return Style.parse(next(str(s.style) for s in text.spans if text.plain[s.start : s.end] == run))
|
||||
|
||||
|
||||
def test_format_age_is_the_bare_column_form() -> None:
|
||||
"""The lane form stays suffix-free at every magnitude, for aligned age columns."""
|
||||
assert _format_age(None) == "never"
|
||||
@@ -192,40 +206,49 @@ def test_revisit_note_is_absent_when_nothing_repeats() -> None:
|
||||
assert revisit_note(()) is None
|
||||
|
||||
|
||||
def test_name_chip_frames_the_name_without_recolouring_it() -> None:
|
||||
"""The chip adds a field; the hue it fields is exactly the one it was handed."""
|
||||
chip = name_chip("Alice", "bold #ff00aa")
|
||||
def test_name_chip_is_a_path_line_of_one_hop() -> None:
|
||||
"""The framing isn't imitated — it *is* the route language, so it can't drift from it."""
|
||||
chip = name_chip("Alice", "a1b2")
|
||||
hop = PathLine([PathHop("Alice", key="a1b2")]).text()
|
||||
|
||||
assert chip.plain[1:-1] == "Alice" # a cap either side, the name untouched between
|
||||
styles = {chip.plain[s.start : s.end]: s.style for s in chip.spans}
|
||||
assert styles["Alice"].color.name == "#ff00aa" # its own hue, still
|
||||
assert styles["Alice"].bgcolor is not None # now with something under it
|
||||
# The caps are the fill drawn over the page — foreground only, so the half-cell that
|
||||
# isn't chip keeps whatever the row behind it is.
|
||||
for cap in (chip.plain[0], chip.plain[-1]):
|
||||
assert styles[cap].bgcolor is None
|
||||
assert styles[cap].color == styles["Alice"].bgcolor
|
||||
assert chip.plain == hop.plain
|
||||
assert [(s.start, s.end, str(s.style)) for s in chip.spans] == [
|
||||
(s.start, s.end, str(s.style)) for s in hop.spans
|
||||
]
|
||||
|
||||
|
||||
def test_name_chip_takes_a_theme_name_as_readily_as_a_hex() -> None:
|
||||
"""``node.unknown``/``you`` arrive as theme names — the fill must not flatten them.
|
||||
def test_name_chip_wears_the_hue_as_its_fill_padded_like_every_chip(powerline) -> None:
|
||||
"""A route's colour scheme: the node's hue fills the chip, the ink on it is dark."""
|
||||
powerline(True)
|
||||
chip = name_chip("Alice", "a1b2")
|
||||
|
||||
Rich drops a style string that mixes a theme name with anything else, so a chip built
|
||||
by concatenation renders the name unstyled. This is that regression, pinned.
|
||||
"""
|
||||
chip = name_chip("·", "node.unknown")
|
||||
|
||||
style = next(s.style for s in chip.spans if chip.plain[s.start : s.end] == "·")
|
||||
assert style.color is not None and style.bgcolor is not None
|
||||
assert style.color.name != style.bgcolor.name # not the invisible-on-itself case
|
||||
assert chip.plain.strip(POWERLINE_ROUND_OPEN + POWERLINE_ROUND_CLOSE) == " Alice "
|
||||
ink = _span_style(chip, "Alice")
|
||||
assert ink.bgcolor.name == node_style("a1b2").removeprefix("bold ") # the hue
|
||||
assert ink.color.name == "#0f172a" # the chip ink every hop is lettered in
|
||||
|
||||
|
||||
def test_name_chip_draws_bare_where_the_console_has_no_field_for_it() -> None:
|
||||
"""PicoCalc's greys stop at slot 7 as backgrounds — so the name goes unframed there."""
|
||||
set_platform(PICOCALC)
|
||||
try:
|
||||
chip = name_chip("Alice", "bold #ff00aa")
|
||||
finally:
|
||||
set_platform(REGULAR)
|
||||
def test_name_chip_draws_us_as_the_star_never_as_our_name(powerline) -> None:
|
||||
"""Our end of a route is the map's own marker; a sender label is no different."""
|
||||
powerline(True)
|
||||
chip = name_chip("you", you=True)
|
||||
|
||||
assert chip.plain == "Alice" # no caps, no field — and no tofu on the console font
|
||||
assert "★" in chip.plain and "you" not in chip.plain
|
||||
assert _span_style(chip, "★").bgcolor.name == "#3f3f46" # neutral grey, not a hue
|
||||
|
||||
|
||||
def test_name_chip_greys_a_node_it_cannot_place(powerline) -> None:
|
||||
"""No key, no hue — colour is reserved for keyed identities, in a chip as in a route."""
|
||||
powerline(True)
|
||||
keyless = name_chip("·")
|
||||
|
||||
assert _span_style(keyless, "·").bgcolor.name == "#64748b" # the keyless grey
|
||||
|
||||
|
||||
def test_name_chip_falls_back_to_the_bare_name_without_powerline(powerline) -> None:
|
||||
"""No separator glyph to draw a chip with — the PicoCalc's console font among them."""
|
||||
powerline(False)
|
||||
chip = name_chip("Alice", "a1b2")
|
||||
|
||||
assert chip.plain == "Alice" # exactly what an arrow-mode path line makes of one hop
|
||||
assert str(next(iter(chip.spans)).style) == node_style("a1b2") # still its own hue
|
||||
Reference in new issue
Block a user