mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 19:11:25 +01:00
feat: fit list tables to the terminal and write TSV when piped
Lists had no header, wrapped every row once a title was longer than the terminal, and were space-padded when piped, so cut -f and awk -F'\t' could not split them and empty cells collapsed. terminal.Table now takes column specs and a Display. On a terminal it prints an uppercase header, aligns columns two spaces apart and shrinks flexible columns, widest first, to Streams.Width (the terminal, then $COLUMNS, then 80), cutting by display width with "…". States use the terminal's ANSI 16 colors, hex colors fall back to the 256-color palette unless COLORTERM advertises 24-bit, and OSC 8 links appear only on the first column, independently of color (TEA_HYPERLINKS=auto|always|never). Piped tables are tab-separated without header, color or links, and times are RFC3339 UTC instead of "about 2 hours ago". terminal.View lays out single resources (title, badges, metadata, fields, body, footer URL) and renders markdown bodies with glamour on a terminal only (TEA_MARKDOWN=raw keeps them raw). Shared formatters cover relative times, timestamps, sizes, durations and status icons. In the app, writeTable and writeView are the new list and view writers: a "Showing N of M …" context line, an empty-state message on stderr and the next-page hint. writeList, writeItem and batch keep working and now render through the new table: tab-separated when piped, truncated at the widest column on a terminal, with headers for issue list and pr list. Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
17 files changed
+1253
-103
No files matched your search
@@ -9,30 +9,62 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func env(vars map[string]string) func(string) string {
|
||||
return func(key string) string { return vars[key] }
|
||||
}
|
||||
|
||||
func TestColorEnabledRespectsPreferenceAndEnvironment(t *testing.T) {
|
||||
getenv := func(key string) string {
|
||||
if key == "NO_COLOR" {
|
||||
return "1"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
noColor := env(map[string]string{"NO_COLOR": "1"})
|
||||
require.True(t, ColorEnabled("always", false, nil))
|
||||
require.False(t, ColorEnabled("never", true, nil))
|
||||
require.True(t, ColorEnabled("auto", true, func(string) string { return "" }))
|
||||
require.False(t, ColorEnabled("auto", false, func(string) string { return "" }))
|
||||
require.False(t, ColorEnabled("auto", true, getenv))
|
||||
require.True(t, ColorEnabled("always", true, getenv))
|
||||
require.False(t, ColorEnabled("auto", true, func(key string) string { return map[string]string{"TERM": "dumb"}[key] }))
|
||||
require.True(t, ColorEnabled("auto", true, env(nil)))
|
||||
require.False(t, ColorEnabled("auto", false, env(nil)))
|
||||
require.False(t, ColorEnabled("auto", true, noColor))
|
||||
require.True(t, ColorEnabled("always", true, noColor))
|
||||
require.False(t, ColorEnabled("auto", true, env(map[string]string{"TERM": "dumb"})))
|
||||
}
|
||||
|
||||
func TestHyperlinksEnabledIgnoresColorPolicy(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
preference string
|
||||
tty bool
|
||||
env map[string]string
|
||||
want bool
|
||||
}{
|
||||
{"auto", true, nil, true},
|
||||
{"", true, map[string]string{"NO_COLOR": "1"}, true},
|
||||
{"auto", false, nil, false},
|
||||
{"auto", true, map[string]string{"TERM": "dumb"}, false},
|
||||
{"always", false, map[string]string{"TERM": "dumb"}, true},
|
||||
{"never", true, nil, false},
|
||||
} {
|
||||
require.Equal(t, tc.want, HyperlinksEnabled(tc.preference, tc.tty, env(tc.env)), "%+v", tc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrueColor(t *testing.T) {
|
||||
require.True(t, TrueColor(env(map[string]string{"COLORTERM": "truecolor"})))
|
||||
require.True(t, TrueColor(env(map[string]string{"COLORTERM": "24bit"})))
|
||||
require.False(t, TrueColor(env(map[string]string{"COLORTERM": "yes"})))
|
||||
require.False(t, TrueColor(nil))
|
||||
}
|
||||
|
||||
func TestColorizeAndEmphasis(t *testing.T) {
|
||||
require.Equal(t, "bug", Colorize("ff0000", "bug", false))
|
||||
require.Equal(t, "bug", Colorize("bad", "bug", true))
|
||||
require.Equal(t, "\x1b[38;2;255;0;0mbug\x1b[0m", Colorize("#ff0000", "bug", true))
|
||||
require.Equal(t, "\x1b[32mopen\x1b[0m", Colorize("green", "open", true))
|
||||
require.Equal(t, "\x1b[90mdraft\x1b[0m", Colorize("muted", "draft", true))
|
||||
require.Equal(t, "open", Emphasis("open", false))
|
||||
require.Equal(t, "\x1b[1mopen\x1b[0m", Emphasis("open", true))
|
||||
}
|
||||
|
||||
func TestPaintApproximatesHexColorsWithoutTrueColor(t *testing.T) {
|
||||
for hex, index := range map[string]string{"ff0000": "196", "00ff00": "46", "000000": "16", "ffffff": "231", "808080": "244", "d73a4a": "167", "7057ff": "63"} {
|
||||
require.Equal(t, "\x1b[38;5;"+index+"mx\x1b[0m", paint(hex, "x", false), hex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHyperlink(t *testing.T) {
|
||||
require.Equal(t, "#7", Hyperlink("https://example.org/i/7", "#7", false))
|
||||
require.Equal(t, "\x1b]8;;https://example.org/i/7\x1b\\#7\x1b]8;;\x1b\\", Hyperlink("https://example.org/i/7", "#7", true))
|
||||
|
||||
Reference in new issue
Block a user