Skip to content

Commit 32fa331

Browse files
committed
feat(ui): two-column help overlay with colored headers and keys
Arrange keybinding sections in two balanced columns to reduce vertical height. Section headers use accent color, keys use annotation color via raw ANSI sequences.
1 parent 4d27300 commit 32fa331

1 file changed

Lines changed: 94 additions & 17 deletions

File tree

ui/model.go

Lines changed: 94 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1320,20 +1320,25 @@ func (m Model) formatKeysForHelp(action keymap.Action) string {
13201320
return strings.Join(display, " / ")
13211321
}
13221322

1323-
// helpOverlay returns a bordered help popup with keybinding sections.
1323+
// helpOverlay returns a bordered help popup with keybinding sections arranged in two columns.
13241324
// sections and key bindings are rendered dynamically from the keymap.
13251325
func (m Model) helpOverlay() string {
13261326
sections := m.keymap.HelpSections()
13271327

1328-
var buf strings.Builder
1329-
for i, sec := range sections {
1330-
if i > 0 {
1331-
buf.WriteString("\n")
1332-
}
1333-
buf.WriteString(sec.Name)
1334-
buf.WriteString("\n")
1328+
// render each section into a block of lines
1329+
type sectionBlock struct {
1330+
lines []string
1331+
}
1332+
var blocks []sectionBlock
1333+
1334+
reset := "\033[0m"
1335+
headerColor := m.ansiFg(m.styles.colors.Accent)
1336+
keyColor := m.ansiFg(m.styles.colors.Annotation)
1337+
1338+
for _, sec := range sections {
1339+
var block sectionBlock
1340+
block.lines = append(block.lines, headerColor+sec.Name+reset)
13351341

1336-
// compute max key width for column alignment
13371342
type helpLine struct{ keys, desc string }
13381343
lines := make([]helpLine, 0, len(sec.Entries))
13391344
maxW := 0
@@ -1346,25 +1351,93 @@ func (m Model) helpOverlay() string {
13461351
}
13471352
for _, l := range lines {
13481353
pad := max(maxW-runewidth.StringWidth(l.keys), 0)
1349-
fmt.Fprintf(&buf, " %s%s %s\n", l.keys, strings.Repeat(" ", pad), l.desc)
1354+
block.lines = append(block.lines, fmt.Sprintf(" %s%s%s%s %s",
1355+
keyColor, l.keys, reset, strings.Repeat(" ", pad), l.desc))
13501356
}
1357+
blocks = append(blocks, block)
13511358

1352-
// add Markdown TOC note after Pane section
1359+
// add Markdown TOC section after Pane
13531360
if sec.Name == "Pane" {
1354-
buf.WriteString("\n")
1355-
m.writeTOCHelpSection(&buf)
1361+
var tocBuf strings.Builder
1362+
m.writeTOCHelpSection(&tocBuf)
1363+
tocLines := strings.Split(strings.TrimRight(tocBuf.String(), "\n"), "\n")
1364+
blocks = append(blocks, sectionBlock{lines: tocLines})
1365+
}
1366+
}
1367+
1368+
// count total lines (with blank line separators between sections)
1369+
totalLines := 0
1370+
for _, b := range blocks {
1371+
totalLines += len(b.lines) + 1 // +1 for separator
1372+
}
1373+
1374+
// assign sections to left/right columns, keeping sections intact
1375+
var leftBlocks, rightBlocks []sectionBlock
1376+
leftLines := 0
1377+
half := totalLines / 2
1378+
for _, b := range blocks {
1379+
blockSize := len(b.lines) + 1
1380+
if leftLines < half {
1381+
leftBlocks = append(leftBlocks, b)
1382+
leftLines += blockSize
1383+
} else {
1384+
rightBlocks = append(rightBlocks, b)
1385+
}
1386+
}
1387+
1388+
// render column from blocks
1389+
renderColumn := func(colBlocks []sectionBlock) []string {
1390+
var result []string
1391+
for i, b := range colBlocks {
1392+
if i > 0 {
1393+
result = append(result, "")
1394+
}
1395+
result = append(result, b.lines...)
1396+
}
1397+
return result
1398+
}
1399+
1400+
left := renderColumn(leftBlocks)
1401+
right := renderColumn(rightBlocks)
1402+
1403+
// find max visible width of left column for padding (ANSI-aware)
1404+
leftWidth := 0
1405+
for _, line := range left {
1406+
if w := lipgloss.Width(line); w > leftWidth {
1407+
leftWidth = w
13561408
}
13571409
}
13581410

1359-
help := strings.TrimRight(buf.String(), "\n")
1411+
gap := 4
1412+
// join columns side by side
1413+
maxRows := max(len(left), len(right))
1414+
var buf strings.Builder
1415+
for i := range maxRows {
1416+
l := ""
1417+
if i < len(left) {
1418+
l = left[i]
1419+
}
1420+
// pad left column to fixed width (ANSI-aware width)
1421+
pad := max(leftWidth-lipgloss.Width(l), 0)
1422+
buf.WriteString(l)
1423+
buf.WriteString(strings.Repeat(" ", pad))
1424+
1425+
if i < len(right) {
1426+
buf.WriteString(strings.Repeat(" ", gap))
1427+
buf.WriteString(right[i])
1428+
}
1429+
if i < maxRows-1 {
1430+
buf.WriteString("\n")
1431+
}
1432+
}
13601433

13611434
border := lipgloss.NormalBorder()
13621435
boxStyle := lipgloss.NewStyle().
13631436
Border(border).
13641437
BorderForeground(lipgloss.Color(m.styles.colors.Accent)).
13651438
Padding(1, 2)
13661439

1367-
return boxStyle.Render(help)
1440+
return boxStyle.Render(buf.String())
13681441
}
13691442

13701443
// writeTOCHelpSection writes the Markdown TOC contextual help section.
@@ -1401,10 +1474,14 @@ func (m Model) writeTOCHelpSection(buf *strings.Builder) {
14011474
}
14021475
}
14031476

1404-
buf.WriteString("Markdown TOC (single-file full-context mode)\n")
1477+
reset := "\033[0m"
1478+
headerColor := m.ansiFg(m.styles.colors.Accent)
1479+
keyColor := m.ansiFg(m.styles.colors.Annotation)
1480+
1481+
buf.WriteString(headerColor + "Markdown TOC (single-file full-context mode)" + reset + "\n")
14051482
for _, l := range lines {
14061483
pad := max(maxW-runewidth.StringWidth(l.keys), 0)
1407-
fmt.Fprintf(buf, " %s%s %s\n", l.keys, strings.Repeat(" ", pad), l.desc)
1484+
fmt.Fprintf(buf, " %s%s%s%s %s\n", keyColor, l.keys, reset, strings.Repeat(" ", pad), l.desc)
14081485
}
14091486
}
14101487

0 commit comments

Comments
 (0)