Commit graph

43 commits

Author SHA1 Message Date
Petr Houška
54280c4ff0
fix(tui): hide HTML comments in Markdown rendering (#11755)
Fixes #11710.

## Problem

The tui `Markdown` component printed `<!-- ... -->` comments verbatim.
marked lexes them as `html` tokens and both the block and inline `html`
cases pushed `token.raw` as text, so comments leaked into every Markdown
surface (Extension Updates, What's New, `/changelog`, messages).

```
## [1.0.0]

<!-- release-notes:start -->
Fixed a bug.

See <!-- todo: link --> the docs.
```

rendered both comments.

## Fix

- After lexing (next to `trimPartialClosingFences`, before tokens are
cached), remove block `html` tokens that contain only comments,
recursing into lists and blockquotes. If the removed comment sat between
two blank lines, or at the start/end, one blank line is dropped too, so
no extra gap remains.
- Block HTML with other content keeps rendering as text, with comments
stripped (`<!-- x --> text` renders `text`).
- Inline comments are skipped; if text before ended with a space, the
next text's leading space is dropped (`See <!-- c --> the docs.` renders
`See the docs.`).
- An unclosed block comment runs to the end of the block (CommonMark),
so streamed comments stay hidden.
- Other HTML still renders as text (`with <tag> markers`).

Parsing stays in marked; the change only filters tokens marked already
produced. Cost: one pass over top-level tokens per lex (cached), a regex
on `html` tokens only.

## Tests

`packages/tui/test/markdown.test.ts`, "HTML comments": compares
rendering with comments against the same Markdown without them (issue
repro, blank-line handling, multi-line/unclosed, blockquotes and lists,
other HTML kept).

This PR is AI-generated.
2026-11-03 22:33:03 +01:00
Armin Ronacher
0f21a3319d fix(coding-agent,tui): reuse syntax highlighting and rendered Markdown blocks
Codemode and write rows keep their last highlighting in a CodeHighlighter
that checks theme and grammar changes itself. Markdown reuses the top-level
blocks of its last render, held weakly, and assistant messages reuse their
Markdown instances while streaming. Text.setText() keeps its wrapped lines
for unchanged text.

closes #10971
2026-11-01 14:40:56 +01:00
Petr Houška
2ce9d527d9
fix(tui): drop space after styled word at wrap boundary, reset strikethrough at line end (#11591)
Fixes #10925.

## Problem

`splitIntoTokensWithAnsi()` attaches pending escape codes to the next
visible character. The code that closes a styled word therefore lands on
the following space: `**commodo** consequat` tokenizes as
`\x1b[1mcommodo`, `\x1b[22m `, `consequat`.

`wrapSingleLine()` detected whitespace with `token.trim() === ""`, which
is false for `"\x1b[22m "`. When the styled word ended exactly at the
wrap width, that space was treated as a word and moved to the next line:

```
before: ["aaaa \x1b[1mbold", "\x1b[1m\x1b[22m next", "word"]   (width 9)
after:  ["aaaa \x1b[1mbold\x1b[22m", "next word"]
```

Because the closing code moved to the next line, the style also stayed
open into the right padding of the wrapped line. Separately,
`getLineEndReset()` closed only underline at line end, so strikethrough
that spans a wrap continued into the padding.

## Fix

- Detect whitespace tokens after stripping escape codes.
- When a whitespace token is dropped at a wrap, append its escape codes
to the wrapped line and update the tracker, so the style closes there
and is not re-opened on the next line.
- Close strikethrough (`\x1b[29m`) at line end, like underline.
`getActiveCodes()` re-opens it on the next line. Background is
unaffected.
- Whitespace wider than an empty line that carries codes (e.g.
background spaces followed by a trailing reset) keeps only the codes.
Without this, treating it as whitespace skipped the long-token path, and
the trailing reset blocked the final `trimEnd()`, leaving a line wider
than `width`.
- When a long word follows, the line before it is now trimmed before the
line-end reset is added. Otherwise the reset (now also emitted for
strikethrough) blocks the final `trimEnd()` and leaves trailing spaces.

## Tests

- `wrap-ansi.test.ts`: boundary closing code, dropped space with a color
reset, strikethrough reset and restore across a wrap, strikethrough
reset with background preserved.
- `markdown.test.ts`: padded Markdown with a background at an exact
boundary for bold and strikethrough.
- The new tests fail without the fix. With the old code, rendering the
issue text through `UserMessageComponent` (dark theme, padding 1) at
widths 40-120 produced 5 bad lines at width 78. With the fix it produces
none.
- Fuzzing over 1.2M random styled inputs with SGR and OSC 8 codes found
no line wider than `width`, and no case where the old wrapper laid out
text the same as the unstyled input but the new one does not.
- Benchmark (`wrapTextWithAnsi`, width 77, about 5-18k chars, median of
6 processes) against `main`: plain -5%, light styling -0.5%, heavy
styling +1%, long strikethrough +0%, all within noise. The whitespace
check scans character codes and stops at the first visible text instead
of allocating a stripped copy.
- `npm run check` passes. All `packages/tui` tests pass. `./test.sh`
shows no new failures; the remaining failures are environment-specific:
the clipboard tests also fail on `main` here, and the durable, env
(ssh), and chord failures are in packages that do not depend on
`pi-tui`.
2026-10-29 22:08:18 +01:00
xu0o0
fa16851520
fix(tui): stop bare URLs at CJK punctuation (#10934)
This PR:
- Stop bare URLs at CJK punctuation, preserving the following text.
- Leave CJK characters without punctuation, explicit Markdown links, and
angle autolinks unchanged.
- Add regression tests.

Fixes #10933

Before:

<img width="846" height="198" alt="screenshot-2026-10-14_17-13-48"
src="https://github.com/user-attachments/assets/be2d8283-ce3f-4a79-988b-28cb48d268d9"
/>


After:

<img width="776" height="188" alt="screenshot-2026-10-14_17-14-14"
src="https://github.com/user-attachments/assets/b827498c-2446-441e-9d8f-ff24398e3eda"
/>
2026-10-28 10:11:40 +01:00
robopipi[bot]
cd1ea342ae
fix(tui): fall back to raw LaTeX when display math is wider than the content width (#11487)
Display math (`$$…$$`, `\[…\]`) renders as several 2D rows: numerator,
fraction bar and denominator are separate lines. When the layout was
wider than the Markdown content width, `render()` wrapped each row with
`wrapTextWithAnsi` on its own. Only the overflowing row broke, so the
rows below moved down and the equation no longer lined up. For example,
`dx` landed between the fraction bar and the denominators.

The `latexBlock` case in `Markdown.renderToken()` now checks the
rendered layout against the available width. If any row is too wide, it
uses the raw LaTeX source, the same fallback already used when parsing
fails. The raw source then wraps like normal text. Layouts that fit
render exactly as before.

**Testing**
- Added a regression test in `packages/tui/test/markdown.test.ts` using
the equation from the issue. At width 40 it renders the 2D layout
unchanged. At width 30 it falls back to the raw `\[ … \]` source, with
no fraction bars and no line wider than 30. The test fails without the
fix and passes with it.
- `node --test test/markdown.test.ts`: 93 pass. `node --test
test/latex.test.ts`: 111 pass.
- `npm run check` reports no errors in the touched files. The only
errors are 6 type errors in `packages/ai/test`, caused by stale
generated model data, and they are the same on `main` without this
change.
- Reproduction at width 30, before the fix:
  ```
  ∂² u   ∂² u   n   1   1
  ──── + ──── = ∑   ── +∫ f(x)
  dx
  ∂ x²   ∂ y²  i=1  i²  0
  ```
  After the fix, the raw source, wrapped:
  ```
  \[
  \frac{\partial^2 u}{\partial
  x^2} + \frac{\partial^2
  ...
  \]
  ```

Fixes #11483

Requested by @badlogic

Co-authored-by: triagent <triagent@localhost>
2026-10-27 16:09:08 +01:00
robopipi[bot]
8a2ebd3854
fix(tui): stop nested blockquote rendering from growing exponentially (#11321)
**Problem.** A line of many `>` characters makes `marked` produce one
nested blockquote token per `>`. The blockquote branch of
`Markdown.renderToken` renders its children at `Math.max(1, width - 2)`
columns, then wraps each child line again at that width and adds a `│ `
prefix.

Once the nesting uses up the width (about 39 levels at 80 columns), the
width stays clamped at 1. From then on, each child line (`│ x`, 3
columns wide) is wrapped into about 3 lines at every further level, so
the output roughly triples per level. At 80 columns:

- 50 levels: 4096 lines, about 30 s to render;
- 55 levels: `push(...lines)` exceeds the JavaScript argument limit and
throws `RangeError: Maximum call stack size exceeded`, which crashes pi.
Reopening the session crashes it again.

**Fix.**
- When there is no room left for another border (`width < 3`), the
blockquote branch now renders its child tokens at the same width without
adding a border and without wrapping them again.
- Child lines are appended with a loop instead of a spread `push`.

Quotes that fit in the width render exactly as before.

**Testing.**
- Added a regression test in `packages/tui/test/markdown.test.ts`: 60
nested `>` rendered at width 80 give one line that keeps the content.
Without the fix the test fails with the RangeError; with it, all 92
tests in that file pass (`node --test test/markdown.test.ts`).
- `npm run check` passes.
- Reproduction script rendering `">".repeat(n) + "x"` at width 80:
- before: 50 levels gave 4096 lines in 29.5 s, and 55 or 60 levels threw
a RangeError;
  - after: 40, 50, 60 and 200 levels each give 1 line in about 10 ms.

**Not covered.** A run of about 2000 `>` still overflows the stack
inside `marked`'s own lexer, before pi's renderer runs. That is a
separate limit and is not changed here.

Fixes #11272

Requested by @davidbrai

Co-authored-by: triagent <triagent@localhost>
2026-10-23 12:46:03 +02:00
Petr Houška
2ed3118b00 fix(tui): stop unclosed display math from swallowing Markdown tables
An unclosed $$ opener could pair with a later $$ across a GFM table, or
be treated as pending streaming math, hiding the table. Block-math
tokens no longer span a table start, and the pending-math fallback
skips content containing a table.

fixes #11159
2026-10-20 19:52:00 +02:00
Bucky
b2d0da6fe3
fix(tui): render CJK emphasis next to punctuation (#10730)
- fixes #10154
2026-10-20 11:04:48 +02:00
Armin Ronacher
fa0e1f48ac fix(tui): improve LaTeX compatibility and layouts
closes #8827
2026-09-19 23:28:04 +02:00
Ramiz Wachtler
8f2ae3fadd
fix(tui): prevent wrapped table link color leaks (#8363) 2026-08-20 15:58:47 +02:00
Armin Ronacher
aa601d7ba0 fix(tui): correct LaTeX whitespace and matrix layouts 2026-08-06 13:23:38 +02:00
Armin Ronacher
05e89b4188 feat(tui): render LaTeX math in Markdown 2026-08-05 21:42:11 +02:00
Armin Ronacher
583f153d50 fix(tui): normalize source filenames 2026-08-01 14:38:13 +02:00
Alexey Zaytsev
714978bf51
Markdown api (#7231)
* feat(tui): export bundled Marked parser

* feat(coding-agent): add width-aware Markdown transformers

---------

Co-authored-by: Mario Zechner <badlogicgames@gmail.com>
2026-07-30 22:39:04 +02:00
Mario Zechner
c13ffe1877 feat(tui): add alternate-screen renderer
Closes #7304
2026-07-30 12:59:48 +02:00
Armin Ronacher
f2e9d75388 fix(coding-agent): preserve backslash escapes in user messages
closes #6105
2026-06-27 19:24:14 +02:00
Alexey Zaytsev
3095977d13
fix(tui): stabilize streaming code fence rendering (#5846) 2026-06-20 14:58:30 +02:00
Armin Ronacher
17721d5e01 fix(tui): preserve unordered user list markers (closes #5657) 2026-06-12 20:22:53 +02:00
Sviatoslav Abakumov
dfd4571cb0
fix(tui): separate list items with blank lines in loose lists 2026-06-09 23:53:18 +04:00
Armin Ronacher
2531fc130d fix(ui): preserve user ordered-list markers (closes #5013) 2026-05-27 01:03:33 +02:00
Mario Zechner
2e02c74dcb chore: pin dependencies and use native TypeScript 2026-05-20 12:46:17 +02:00
Armin Ronacher
ae9450dc51 chore(ts): use source import extensions 2026-05-20 00:04:03 +02:00
Sviatoslav Abakumov
863341fbf5
fix(tui): render the checkboxes in the to-do list items 2026-05-10 22:20:40 +04:00
Sviatoslav Abakumov
116bffeb88
feat(tui): wrap list items with indent 2026-05-08 23:03:22 +04:00
Omair Ahmed
e8743e870b
feat(tui): use OSC 8 hyperlinks in Markdown when terminal supports them (#3248)
TerminalCapabilities already tracks hyperlinks: boolean and returns true
for Ghostty, Kitty, WezTerm, and iTerm2, but nothing generated OSC 8
sequences. This completes that stub.

Changes to packages/tui:
- terminal-image.ts: add hyperlink(text, url) and setCapabilities()
- index.ts: export hyperlink and setCapabilities
- utils.ts: extend AnsiCodeTracker to track active OSC 8 URLs
  - process() now handles OSC 8 open/close sequences
  - getActiveCodes() re-emits the OSC 8 open at each line start
  - getLineEndReset() closes the OSC 8 hyperlink before each line break
  This ensures hyperlinks wrap correctly across multiple lines.
- components/markdown.ts: link renderer uses hyperlink() when
  getCapabilities().hyperlinks is true; falls back to (url) text
- Tests: new wrap-ansi tests for OSC 8 line-wrapping; terminal-image
  tests for hyperlink(); markdown tests covering both code paths;
  table-cell width test pinned to hyperlinks:false (checks raw columns)

closes #3239

Co-authored-by: AI (Pi/Claude Sonnet 4.6) <noreply@pi.dev>
Co-authored-by: Mario Zechner <badlogicgames@gmail.com>
2026-04-16 22:13:36 +02:00
Armin Ronacher
db5274b48c fix(markdown): require double-tilde strikethrough delimiters 2026-04-16 12:37:13 +02:00
Aliou Diallo
41377ee8ec
fix(tui): sync test awaits with throttled render pipeline (#3076)
Tests assumed renders complete synchronously after requestRender() +
nextTick + flush(), but commit 6f5f37f8 changed requestRender() to
schedule via setTimeout with a 16ms minimum interval. Tests reading
viewport state before doRender() runs saw stale data.

Add VirtualTerminal.waitForRender() helper that waits for the throttled
render to settle (nextTick + 20ms + xterm flush), and replace all
terminal.flush() calls after render-triggering actions with it.

Fixes 8 failing tests across overlay-short-content, tui-render, and
markdown test suites.
2026-04-13 10:57:48 +02:00
Mario Zechner
09e9de5749 fix(tui): stop heading underline leaking into padding 2026-03-31 22:40:20 +02:00
Mario Zechner
ab771a84b2 fix(tui): preserve heading styling after inline code spans
Headings applied styling (bold+cyan) as an outer wrapper around the
result of renderInlineTokens. When inline elements like codespan emitted
ANSI resets, the outer heading style was killed and only the default
text style was restored via stylePrefix.

Fix: pass a heading-specific InlineStyleContext into renderInlineTokens
so each text segment gets heading styling directly, and stylePrefix
restores heading style after inline elements.
2026-03-23 01:07:38 +01:00
Markus Ylisiurunen
962c12444d
fix(tui): remove trailing markdown block spacing (#2152) 2026-03-14 15:50:19 +01:00
Mario Zechner
9673f52919 fix(tui): render blockquote list content correctly
closes #1787
2026-03-04 20:25:55 +01:00
Fero
20ca6836b0
fix(tui): blockquote multiline rendering and wrapping (#1073)
* fix(tui): blockquote multiline rendering and wrapping

Fix two bugs in blockquote rendering:

1. ANSI codes split incorrectly on newlines - when text tokens contain
   newlines, applyDefaultStyle() wrapped the entire string including the
   newline in ANSI codes. Any caller splitting by \n would break the codes.
   Fixed by splitting text by newlines before styling in renderInlineTokens().

2. Wrapped blockquote lines lost the │ border - long blockquote lines
   that wrapped to multiple lines only had the border on the first line.
   Fixed by wrapping within the blockquote case and adding border to each line.

* test(tui): add test for inline formatting inside blockquotes
2026-01-30 03:02:38 +01:00
Thomas Mustier
e7b9209daf
fix(tui): improve table rendering with row dividers and min width (#997)
* fix(tui): improve table rendering

* fix(tui): handle narrow table widths
2026-01-28 02:42:08 +01:00
Michael Renner
620239bd3f
fix(tui): prevent duplicate URL display for autolinked emails (#888)
Autolinked emails like user@example.com were rendered as
'user@example.com (mailto:user@example.com)' because the comparison
token.text === token.href failed (text lacks mailto: prefix).

Now strips mailto: prefix before comparing, so autolinked emails
display without redundant URL in parentheses.
2026-01-21 23:20:00 +01:00
Ogulcan Celik
a5441706f3 fix(tui): numbered list items showing "1." when code blocks break list continuity 2026-01-12 17:09:51 +01:00
Mario Zechner
da6a2fb5ea test(tui): cover style reset cases 2026-01-11 19:01:16 +01:00
Ahmed Kamal
c1113deea9
Fix markdown tables overflowing/wrapping in TUI (width-aware rendering) (#206)
Fix markdown tables overflowing/wrapping in TUI
2025-12-17 17:13:27 +01:00
Markus Ylisiurunen
92577316e0
Fix double new line issues in markdown rendering (#199)
* prevent double blank lines after markdown elements

* prevent double blank lines after markdown elements
2025-12-16 00:25:06 +01:00
Markus Ylisiurunen
db886746b4
prevent double blank lines after markdown code blocks (#173) 2025-12-13 02:04:49 +01:00
Peter Steinberger
c43f1d307c Add CI workflow and fix workspace tests 2025-12-02 12:53:53 +00:00
Mario Zechner
85adcf22bf Release v0.8.0 2025-11-21 03:12:42 +01:00
Mario Zechner
22d8a0ae4a Release v0.7.18 2025-11-18 22:08:44 +01:00
Mario Zechner
c75f53f6f2 Improve edit tool diff display with context-aware rendering
- Add generateDiffString() function in edit tool to create unified diffs with line numbers and 4 lines of context
- Store only the formatted diff string in tool result details instead of full file contents
- Update tool-execution renderer to parse and colorize the diff string
- Filter out message_update events from session saving to prevent verbose session files
- Add markdown nested list and table rendering tests
2025-11-12 20:09:11 +01:00