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.
This commit is contained in:
Michael Renner 2026-01-21 23:20:00 +01:00 committed by GitHub
parent 0363a10c69
commit 620239bd3f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 58 additions and 1 deletions

View file

@ -6,6 +6,10 @@
- `codeBlockIndent` property on `MarkdownTheme` to customize code block content indentation (default: 2 spaces)
### Fixed
- Autolinked emails no longer display redundant `(mailto:...)` suffix in markdown output
## [0.49.2] - 2026-01-19
## [0.49.1] - 2026-01-18

View file

@ -379,7 +379,10 @@ export class Markdown implements Component {
const linkText = this.renderInlineTokens(token.tokens || []);
// If link text matches href, only show the link once
// Compare raw text (token.text) not styled text (linkText) since linkText has ANSI codes
if (token.text === token.href) {
// For mailto: links, strip the prefix before comparing (autolinked emails have
// text="foo@bar.com" but href="mailto:foo@bar.com")
const hrefForComparison = token.href.startsWith("mailto:") ? token.href.slice(7) : token.href;
if (token.text === token.href || token.text === hrefForComparison) {
result += this.theme.link(this.theme.underline(linkText)) + this.getDefaultStylePrefix();
} else {
result +=

View file

@ -644,6 +644,56 @@ again, hello world`,
});
});
describe("Links", () => {
it("should not duplicate URL for autolinked emails", () => {
const markdown = new Markdown("Contact user@example.com for help", 0, 0, defaultMarkdownTheme);
const lines = markdown.render(80);
const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, ""));
const joinedPlain = plainLines.join(" ");
// Should contain the email once, not duplicated with mailto:
assert.ok(joinedPlain.includes("user@example.com"), "Should contain email");
assert.ok(!joinedPlain.includes("mailto:"), "Should not show mailto: prefix for autolinked emails");
});
it("should not duplicate URL for bare URLs", () => {
const markdown = new Markdown("Visit https://example.com for more", 0, 0, defaultMarkdownTheme);
const lines = markdown.render(80);
const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, ""));
const joinedPlain = plainLines.join(" ");
// URL should appear only once
const urlCount = (joinedPlain.match(/https:\/\/example\.com/g) || []).length;
assert.strictEqual(urlCount, 1, "URL should appear exactly once");
});
it("should show URL for explicit markdown links with different text", () => {
const markdown = new Markdown("[click here](https://example.com)", 0, 0, defaultMarkdownTheme);
const lines = markdown.render(80);
const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, ""));
const joinedPlain = plainLines.join(" ");
// Should show both link text and URL
assert.ok(joinedPlain.includes("click here"), "Should contain link text");
assert.ok(joinedPlain.includes("(https://example.com)"), "Should show URL in parentheses");
});
it("should show URL for explicit mailto links with different text", () => {
const markdown = new Markdown("[Email me](mailto:test@example.com)", 0, 0, defaultMarkdownTheme);
const lines = markdown.render(80);
const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, ""));
const joinedPlain = plainLines.join(" ");
// Should show both link text and mailto URL
assert.ok(joinedPlain.includes("Email me"), "Should contain link text");
assert.ok(joinedPlain.includes("(mailto:test@example.com)"), "Should show mailto URL in parentheses");
});
});
describe("HTML-like tags in text", () => {
it("should render content with HTML-like tags as text", () => {
// When the model emits something like <thinking>content</thinking> in regular text,