Skip to content

fix(cli): keep hyperlink terminators unescaped in text rows - #234

Merged
sergej-koscejev merged 3 commits into
mainfrom
fix/hyperlink-escaping
Nov 12, 2026
Merged

sergej-koscejev merged 3 commits into
mainfrom
fix/hyperlink-escaping

Conversation

@sergej-koscejev

@sergej-koscejev sergej-koscejev commented Nov 12, 2026 •

Copy link
Copy Markdown
Member

Summary

When mops writes to a terminal, text rows link node references to MPS with OSC 8. textRow escaped the whole column, including the link markup, so each ESC \ terminator became ESC \\ and terminals such as iTerm2 printed a stray \ before and after the reference.

A row column is now a TextColumn(text, link). textRow escapes only the text and then wraps it in the link markup. The URL is built from the original reference.

 textRow(columns)
-  join columns with tabs, escape each column string        # link markup escaped too
+  for each TextColumn: escape text, then wrap it in the OSC 8 link if it has one
 nodeReferenceColumn(reference, format)                      # new; the one place that maps a format to a column
 renderNodeReference(...) = nodeReferenceColumn(...).render() # unescaped, for lines outside text rows

Paths changed (all text rows that hold a node reference):

  • ls / list node and root rows (ListRenderer)
  • find result rows: node, usage owner, and parent columns (find/resultRendering.kt)
  • check finding rows (check/checkResultRendering.kt)

Paths left as is:

  • find empty-result metadata line (# no … in the subtree of X <ref>, find/emptyResult.kt): not a text row and not escaped, so its link was already correct. It goes through renderNodeReference, which now uses nodeReferenceColumn.
  • find --refs-only (find/SearchCommand.kt): never links (withoutLink()).
  • diagnose and get module rows: no node references.

Whether references are linked is now part of NodeReferenceFormat, so renderers take one value instead of a format plus a hyperlinks flag:

Format Shows
SERIALIZED the serialized reference
LINKED (new) the serialized reference, as an OSC 8 link to the MPS node URL
URL the MPS node URL

NodeReferenceFormat.default() is the one console check: LINKED when stdout is a console, SERIALIZED otherwise. MopsCommand uses it unless --refs-as-urls is given. find --refs-only uses format.withoutLink(), so it stays plain.

Evidence

  • Before: ls row with hyperlinks on (bytes from the new test's failing run):

    0000000           r   o   o   t  \t   T   e   s   t   C   l   a   s   s
    0000020  \t   C   l   a   s   s   C   o   n   c   e   p   t  \t 033   ]
    ...
    0000140   4   5   6 033   \   \   r   :   1   2   3   (   e   x   a   m
    0000160   p   l   e   .   m   o   d   e   l   )   /   4   5   6 033   ]
    0000200   8   ;   ; 033   \   \  \n
    

    After:

    0000000           r   o   o   t  \t   T   e   s   t   C   l   a   s   s
    0000020  \t   C   l   a   s   s   C   o   n   c   e   p   t  \t 033   ]
    ...
    0000140   4   5   6 033   \   r   :   1   2   3   (   e   x   a   m   p
    0000160   l   e   .   m   o   d   e   l   )   /   4   5   6 033   ]   8
    0000200   ;   ; 033   \  \n
    
  • New HyperlinkedTextRowsTest (hyperlinks on): ls, find (node and parent) and check rows keep ESC \ unescaped; a tab in a reference is escaped as \t in the shown text and percent-encoded (%09) in the URL. All 4 failed before the fix and pass after.

  • SearchCommandTest (all four find subcommands): with LINKED, text rows carry the link and --refs-only prints plain references. The --refs-only test fails when withoutLink() is removed.

  • Ran :cli:test (381 tests, 0 failures), detektMain, checkReference, checkExamples: all pass. detektTest reports 5 issues that are also on main, in code this PR does not change (CliTestFixtures.kt, RecordingDaemon.kt, the SearchSubcommand constructor in SearchCommandTest.kt, checkResponseFixtures.kt, GradleHomeDiscoveryTest.kt). Full ./gradlew check was not run.

Merge Danger

Blast Radius: terminal-only

Only output to a console changes; piped output and JSON are the same as before.

@sergej-koscejev
sergej-koscejev added this pull request to the merge queue Nov 12, 2026
Merged via the queue into main with commit d4de954 Nov 12, 2026
16 of 17 checks passed
@sergej-koscejev
sergej-koscejev deleted the fix/hyperlink-escaping branch November 12, 2026 21:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant