Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,9 @@ fetch --discard --timing example.com # Measure timing only
Control response formatting. Values: `auto`, `on`, `off`.

Markdown rendered on a terminal uses clickable OSC 8 hyperlinks when the
terminal supports them. Non-terminal output does not contain OSC 8 hyperlink
escapes.
terminal supports them. Terminal prose wraps to the detected terminal width,
capped at 100 columns by default. Non-terminal output does not contain OSC 8
hyperlink escapes.

```sh
fetch --format off example.com # Disable formatting
Expand Down
3 changes: 2 additions & 1 deletion docs/output-formatting.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,8 @@ Features:
- Blockquote and list marker highlighting; TTY blockquotes use a muted rule
and muted text
- Bold blue table headers with dimmed table borders and separators
- TTY prose wraps to the detected terminal width using display-cell widths;
- TTY prose wraps to the detected terminal width, capped at 100 columns by
default, using display-cell widths;
wide tables use a vertical record layout
- On TTYs, links and image alt text are rendered as clickable OSC 8 terminal
hyperlinks; non-terminal output does not contain OSC 8 hyperlink escapes
Expand Down
26 changes: 18 additions & 8 deletions internal/format/markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@ import (
"github.com/yuin/goldmark/util"
)

const maxMarkdownBlockquoteOpeners = 256
const (
maxMarkdownBlockquoteOpeners = 256
defaultMarkdownMaxWidth = 100
)

// MarkdownOptions controls terminal-only Markdown presentation.
//
// MaxWidth is a maximum display width in terminal columns. A zero value uses
// the current terminal width when the destination is a TTY. Non-terminal
// output remains raw Markdown regardless of this value.
// the smaller of the current terminal width and 100 columns when the
// destination is a TTY. Non-terminal output remains raw Markdown regardless
// of this value.
type MarkdownOptions struct {
MaxWidth int
}
Expand Down Expand Up @@ -62,18 +66,24 @@ func FormatMarkdownWithOptions(buf []byte, p *core.Printer, options MarkdownOpti

width := 0
if p.IsTerminal() {
width = options.MaxWidth
if terminalWidth := core.GetTerminalCols(); terminalWidth > 0 &&
(width <= 0 || terminalWidth < width) {
width = terminalWidth
}
width = markdownWidth(options.MaxWidth, core.GetTerminalCols())
}

r := &mdRenderer{printer: p, source: rest, width: width}
r.tty = p.IsTerminal()
return ast.Walk(doc, r.walk)
}

func markdownWidth(maxWidth, terminalWidth int) int {
if maxWidth <= 0 {
maxWidth = defaultMarkdownMaxWidth
}
if terminalWidth > 0 && terminalWidth < maxWidth {
return terminalWidth
}
return maxWidth
}

// truncateMarkdownBlockquoteOpeners caps line-leading blockquote opener runs.
// Goldmark's container handling becomes quadratic at extreme nesting depths, so
// discard openers beyond the largest depth that is useful for terminal output.
Expand Down
23 changes: 23 additions & 0 deletions internal/format/markdown_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,29 @@ func TestFormatMarkdownWrapsTerminalProseToDisplayWidth(t *testing.T) {
}
}

func TestMarkdownWidthDefaultsToTerminalWidthOr100(t *testing.T) {
tests := []struct {
name string
maxWidth int
terminalWidth int
want int
}{
{name: "narrow terminal", terminalWidth: 80, want: 80},
{name: "wide terminal", terminalWidth: 120, want: 100},
{name: "terminal unavailable", want: 100},
{name: "explicit width", maxWidth: 40, terminalWidth: 120, want: 40},
{name: "explicit width capped by terminal", maxWidth: 120, terminalWidth: 80, want: 80},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := markdownWidth(tt.maxWidth, tt.terminalWidth); got != tt.want {
t.Fatalf("markdownWidth(%d, %d) = %d, want %d", tt.maxWidth, tt.terminalWidth, got, tt.want)
}
})
}
}

func TestFormatMarkdownWrapsStyledLinkByVisibleWidth(t *testing.T) {
p := core.TestTerminalPrinter(false)
input := "[one two three four five](https://example.com/docs)"
Expand Down