detached e486f77

style(tui): tighten code panel gutters and expand tabs

Reduces gutter padding between line numbers and code, replaces tab characters with 4 spaces in code panels so Go and other tab-indented languages render at consistent width.
commit
e486f7757984f0a1783b87877bfd5f3e6f493b14
author
patriceckhart · · 2026-04-23 17:39 UTC
parent
ba3f328
signature
unsigned
1 file · +20 -7
M internal/tui/view.go +20 -7
@@ -11,6 +11,13 @@ import (
1111 "github.com/patriceckhart/zot/internal/provider"
1212 )
1313
14+// expandTabs replaces tab characters with 4 spaces so code from
15+// tab-indented languages (Go, Makefiles, etc.) renders at a
16+// consistent width instead of the terminal's default 8-column tabs.
17+func expandTabs(s string) string {
18+ return strings.ReplaceAll(s, "\t", " ")
19+}
20+
1421 // pathFromToolArgs returns the "path" argument from a tool_call's
1522 // JSON arguments, or "" if the args aren't a JSON object or don't
1623 // include one. Used to pick a syntax language for rendering the
@@ -777,7 +784,7 @@ func (v *View) renderDiffRow(line string, width, color int, lineNo int, mark byt
777784 if len(line) == 0 {
778785 return ""
779786 }
780- code := line[1:] // strip the leading marker; we re-emit it in colour
787+ code := expandTabs(line[1:]) // strip the leading marker; expand tabs
781788
782789 // Syntax-highlight the code half when we know the language. Use
783790 // the same HighlightCode pipeline as renderNumberedFile so the
@@ -805,11 +812,11 @@ func (v *View) renderDiffRow(line string, width, color int, lineNo int, mark byt
805812 var gutterText string
806813 switch mark {
807814 case '+':
808- gutterText = fmt.Sprintf("+%5d\t", lineNo)
815+ gutterText = fmt.Sprintf("+%3d ", lineNo)
809816 case '-':
810- gutterText = fmt.Sprintf("-%5d\t", lineNo)
817+ gutterText = fmt.Sprintf("-%3d ", lineNo)
811818 default:
812- gutterText = fmt.Sprintf(" %5d\t", lineNo)
819+ gutterText = fmt.Sprintf(" %3d ", lineNo)
813820 }
814821 var gutter string
815822 if mark == ' ' {
@@ -943,8 +950,8 @@ func (v *View) renderNumberedFile(text, sourcePath string) []string {
943950 codes = append(codes, l)
944951 continue
945952 }
946- gutter := l[:idx+1] // keep the tab so alignment is preserved
947- code := l[idx+1:]
953+ gutter := l[:idx] + " " // replace tab with single space
954+ code := expandTabs(l[idx+1:])
948955 gutters = append(gutters, gutter)
949956 codes = append(codes, code)
950957 }
@@ -1135,6 +1142,12 @@ func (v *View) renderRawFile(text, sourcePath string, startLine int) []string {
11351142 code := lines[:codeEnd]
11361143 footer := lines[codeEnd:]
11371144
1145+ // Expand tabs to 4 spaces so Go / Makefile code renders
1146+ // at a consistent width.
1147+ for i, c := range code {
1148+ code[i] = expandTabs(c)
1149+ }
1150+
11381151 lang := LanguageFromPath(sourcePath)
11391152 var highlighted []string
11401153 if lang != "" {
@@ -1154,7 +1167,7 @@ func (v *View) renderRawFile(text, sourcePath string, startLine int) []string {
11541167
11551168 out := make([]string, 0, len(lines))
11561169 for i, c := range highlighted {
1157- gutter := fmt.Sprintf("%6d\t", startLine+i)
1170+ gutter := fmt.Sprintf("%4d ", startLine+i)
11581171 out = append(out, " "+v.Theme.FG256(v.Theme.Muted, gutter)+c)
11591172 }
11601173 for _, f := range footer {
@@ -11,6 +11,13 @@ import (
11 "github.com/patriceckhart/zot/internal/provider"11 "github.com/patriceckhart/zot/internal/provider"
12 )12 )
13 13
14+// expandTabs replaces tab characters with 4 spaces so code from
15+// tab-indented languages (Go, Makefiles, etc.) renders at a
16+// consistent width instead of the terminal's default 8-column tabs.
17+func expandTabs(s string) string {
18+ return strings.ReplaceAll(s, "\t", " ")
19+}
20+
14 // pathFromToolArgs returns the "path" argument from a tool_call's21 // pathFromToolArgs returns the "path" argument from a tool_call's
15 // JSON arguments, or "" if the args aren't a JSON object or don't22 // JSON arguments, or "" if the args aren't a JSON object or don't
16 // include one. Used to pick a syntax language for rendering the23 // include one. Used to pick a syntax language for rendering the
@@ -777,7 +784,7 @@ func (v *View) renderDiffRow(line string, width, color int, lineNo int, mark byt
777 if len(line) == 0 {784 if len(line) == 0 {
778 return ""785 return ""
779 }786 }
780- code := line[1:] // strip the leading marker; we re-emit it in colour787+ code := expandTabs(line[1:]) // strip the leading marker; expand tabs
781 788
782 // Syntax-highlight the code half when we know the language. Use789 // Syntax-highlight the code half when we know the language. Use
783 // the same HighlightCode pipeline as renderNumberedFile so the790 // the same HighlightCode pipeline as renderNumberedFile so the
@@ -805,11 +812,11 @@ func (v *View) renderDiffRow(line string, width, color int, lineNo int, mark byt
805 var gutterText string812 var gutterText string
806 switch mark {813 switch mark {
807 case '+':814 case '+':
808- gutterText = fmt.Sprintf("+%5d\t", lineNo)815+ gutterText = fmt.Sprintf("+%3d ", lineNo)
809 case '-':816 case '-':
810- gutterText = fmt.Sprintf("-%5d\t", lineNo)817+ gutterText = fmt.Sprintf("-%3d ", lineNo)
811 default:818 default:
812- gutterText = fmt.Sprintf(" %5d\t", lineNo)819+ gutterText = fmt.Sprintf(" %3d ", lineNo)
813 }820 }
814 var gutter string821 var gutter string
815 if mark == ' ' {822 if mark == ' ' {
@@ -943,8 +950,8 @@ func (v *View) renderNumberedFile(text, sourcePath string) []string {
943 codes = append(codes, l)950 codes = append(codes, l)
944 continue951 continue
945 }952 }
946- gutter := l[:idx+1] // keep the tab so alignment is preserved953+ gutter := l[:idx] + " " // replace tab with single space
947- code := l[idx+1:]954+ code := expandTabs(l[idx+1:])
948 gutters = append(gutters, gutter)955 gutters = append(gutters, gutter)
949 codes = append(codes, code)956 codes = append(codes, code)
950 }957 }
@@ -1135,6 +1142,12 @@ func (v *View) renderRawFile(text, sourcePath string, startLine int) []string {
1135 code := lines[:codeEnd]1142 code := lines[:codeEnd]
1136 footer := lines[codeEnd:]1143 footer := lines[codeEnd:]
1137 1144
1145+ // Expand tabs to 4 spaces so Go / Makefile code renders
1146+ // at a consistent width.
1147+ for i, c := range code {
1148+ code[i] = expandTabs(c)
1149+ }
1150+
1138 lang := LanguageFromPath(sourcePath)1151 lang := LanguageFromPath(sourcePath)
1139 var highlighted []string1152 var highlighted []string
1140 if lang != "" {1153 if lang != "" {
@@ -1154,7 +1167,7 @@ func (v *View) renderRawFile(text, sourcePath string, startLine int) []string {
1154 1167
1155 out := make([]string, 0, len(lines))1168 out := make([]string, 0, len(lines))
1156 for i, c := range highlighted {1169 for i, c := range highlighted {
1157- gutter := fmt.Sprintf("%6d\t", startLine+i)1170+ gutter := fmt.Sprintf("%4d ", startLine+i)
1158 out = append(out, " "+v.Theme.FG256(v.Theme.Muted, gutter)+c)1171 out = append(out, " "+v.Theme.FG256(v.Theme.Muted, gutter)+c)
1159 }1172 }
1160 for _, f := range footer {1173 for _, f := range footer {

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close