Skip to content

Conversation

@thehunmonkgroup
Copy link
Member

@thehunmonkgroup thehunmonkgroup commented Dec 28, 2025

Motivation

This change introduces a small, generic markup layer so VIT can carry raw terminal escape sequences (like OSC‑8 hyperlinks) without corrupting them or letting them affect layout sizing. That makes it possible for custom formatters to inject clickable links in supported terminals (e.g., WezTerm) while keeping column widths and search behavior stable.

What Changed

New vit/markup.py helper module with:

RawText — a tiny wrapper for raw byte sequences (e.g., OSC‑8)

normalize_markup() — converts markup containing RawText into byte-safe markup for urwid

markup_display_width() — computes visible width while ignoring RawText

markup_to_str() — reconstructs a search-safe string from markup, ignoring RawText

Task table rendering now normalizes markup and uses markup_display_width() for column sizing.

Search string reconstruction is now robust to mixed str/bytes/RawText markup.

Why This Matters

Before this, any control sequence embedded in a description would be encoded with replacement by urwid, producing ?]8;;... instead of a real hyperlink. By exposing a RawText container, formatter authors can inject raw OSC‑8 sequences safely without breaking layout or search.

Example: Custom Description Formatter with OSC‑8 Links

Below is a minimal example formatter that turns Jira-style prefixes into clickable terminal hyperlinks. Save as ~/.vit/formatters/description.py:

from vit.formatter import String
from vit.util import unicode_len
from vit.markup import RawText

JIRA_URL = "https://someurl.com/browse/"

class Description(String):
    def format(self, description, task):
        if not description:
            return self.empty()

        # Parse prefix like "ABC-123: something"
        try:
            jira_id, rest = description.split(":", 1)
        except ValueError:
            # fall back to normal behavior
            width = unicode_len(description)
            return (width, self.colorize_description(description))

        # OSC-8 hyperlink sequence (ESC ] 8 ;; URL BEL ... ESC ] 8 ;; BEL)
        link_start = RawText(f"\x1b]8;;{JIRA_URL}{jira_id}\x07".encode("utf-8"))
        link_end = RawText(b"\x1b]8;;\x07")

        # Build visible text using markup with RawText
        visible = f"{jira_id}:{rest}"
        colorized = self.colorize_description(visible)

        # Prepend the raw hyperlink markers around the Jira ID
        # This keeps the visible text unchanged but makes the ID clickable.
        colorized[0] = (
            colorized[0][0],
            link_start,
        )
        colorized.insert(1, (None, f"{jira_id}"))
        colorized.insert(2, (None, link_end))
        colorized.insert(3, (None, f":{rest}"))

        width = unicode_len(visible)
        if task["annotations"]:
            annotation_width, colorized = self.format_combined(colorized, task)
            width = max(width, annotation_width)

        return (width, colorized)

NOTE: The code changes and example code were generated by Codex. I did review the code that would be committed to VIT, and it seems solid, however, I did not verify the example override, or if the code added to VIT would work as expected -- this will need to be verified by someone prior to merge

### Motivation

- Allow markup to carry raw byte sequences (escape sequences) so formatters can inject them without affecting visible text layout.
- Ensure width calculations ignore injected raw bytes so column sizing remains correct when raw sequences are present.
- Make search and string-reconstruction robust to mixed unicode/byte/tuple markup so searches do not match or break on raw bytes.

### Description

- Add `vit/markup.py` with a frozen `RawText` dataclass and helper functions: `markup_contains_raw`, `markup_to_bytes`, `normalize_markup`, `markup_display_width`, and `markup_to_str`.
- Normalize markup in `TaskTable.build_row_column` by calling `normalize_markup` and compute column widths via `markup_display_width` rather than naive `unicode_len`.
- Update `Application.reconstitute_markup_element_as_string` to use `markup_to_str` so search string reconstruction safely handles byte/raw markup.

### Testing

- Ran small Python introspection checks including `urwid.util.decompose_tagmarkup([(None, b'foo'), b'bar'])` which returned `(b'foobar', [])`, indicating byte-markup handling behaves as expected (succeeded).
- Inspected `TextCanvas` and `StandardTextLayout` source via quick REPL scripts to verify layout/width helpers are available (succeeded).
- No full unit test suite or integration tests were run for this change.
@thehunmonkgroup thehunmonkgroup changed the title Move vit.markup imports to top-level and use markup helpers Add RawText markup support for escape sequences with safe width and search handling Dec 29, 2025
@linuxcaffe
Copy link

👍

@thehunmonkgroup
Copy link
Member Author

Codex did not provide a sufficient implementation of this feature, in particular the RawText objects are still fed into Urwid's text rendering, I think they would need to be emitted outside of that to avoid the escaping issues.

I don't plan on working this PR any further, but leaving it open in case anyone wants to take a crack at it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants