Code sync
This commit is contained in:
@@ -4,7 +4,7 @@ set -euo pipefail
|
|||||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
mkdir -p "$ROOT_DIR/bin"
|
mkdir -p "$ROOT_DIR/bin"
|
||||||
go build -o "$ROOT_DIR/bin/nexus" "$ROOT_DIR"
|
go build -o "$ROOT_DIR/bin/giteditor" "$ROOT_DIR"
|
||||||
|
|
||||||
echo "Built $ROOT_DIR/bin/nexus"
|
echo "Built $ROOT_DIR/bin/giteditor"
|
||||||
|
|
||||||
|
|||||||
@@ -523,28 +523,26 @@ func (m tuiModel) View() string {
|
|||||||
if height <= 0 {
|
if height <= 0 {
|
||||||
height = 36
|
height = 36
|
||||||
}
|
}
|
||||||
|
width = maxInt(1, width-1)
|
||||||
|
|
||||||
top := m.renderTopBar(width)
|
top := m.renderTopBar(width)
|
||||||
bottom := m.renderBottomBar(width)
|
bottom := m.renderBottomBar(width)
|
||||||
bodyHeight := height - lipgloss.Height(top) - lipgloss.Height(bottom) - 2
|
bodyHeight := maxInt(1, height-lipgloss.Height(top)-lipgloss.Height(bottom))
|
||||||
if bodyHeight < 12 {
|
leftWidth, rightWidth, separator := tuiColumnWidths(width)
|
||||||
bodyHeight = 12
|
|
||||||
}
|
|
||||||
|
|
||||||
leftWidth := clamp(width*38/100, 30, 54)
|
|
||||||
if width-leftWidth-1 < 34 {
|
|
||||||
leftWidth = maxInt(24, width-35)
|
|
||||||
}
|
|
||||||
rightWidth := maxInt(30, width-leftWidth-1)
|
|
||||||
|
|
||||||
left := m.renderCommitPane(leftWidth, bodyHeight)
|
left := m.renderCommitPane(leftWidth, bodyHeight)
|
||||||
right := m.renderDetailPane(rightWidth, bodyHeight)
|
right := m.renderDetailPane(rightWidth, bodyHeight)
|
||||||
body := lipgloss.JoinHorizontal(lipgloss.Top, left, " ", right)
|
body := lipgloss.JoinHorizontal(lipgloss.Top, left, strings.Repeat(" ", separator), right)
|
||||||
|
|
||||||
return appBaseStyle.Width(width).Render(strings.Join([]string{top, body, bottom}, "\n"))
|
return appBaseStyle.Width(width).Render(strings.Join([]string{top, body, bottom}, "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m tuiModel) renderTopBar(width int) string {
|
func (m tuiModel) renderTopBar(width int) string {
|
||||||
|
barStyle := lipgloss.NewStyle().
|
||||||
|
Width(contentWidthFor(lipgloss.NewStyle().Padding(0, 1), width)).
|
||||||
|
Background(lipgloss.Color("#0C1824")).
|
||||||
|
Padding(0, 1)
|
||||||
|
contentWidth := contentWidthFor(barStyle, width)
|
||||||
title := lipgloss.NewStyle().
|
title := lipgloss.NewStyle().
|
||||||
Bold(true).
|
Bold(true).
|
||||||
Foreground(colorCyan).
|
Foreground(colorCyan).
|
||||||
@@ -568,27 +566,30 @@ func (m tuiModel) renderTopBar(width int) string {
|
|||||||
flags = "working..."
|
flags = "working..."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
repoWidth := maxInt(4, contentWidth-lipgloss.Width(title)-lipgloss.Width(flags)-6)
|
||||||
line := lipgloss.JoinHorizontal(
|
line := lipgloss.JoinHorizontal(
|
||||||
lipgloss.Center,
|
lipgloss.Center,
|
||||||
title,
|
title,
|
||||||
" ",
|
" ",
|
||||||
mutedStyle.Render(truncateText(repoText, maxInt(8, width-lipgloss.Width(title)-lipgloss.Width(flags)-8))),
|
mutedStyle.Render(truncateText(repoText, repoWidth)),
|
||||||
)
|
)
|
||||||
line = line + strings.Repeat(" ", maxInt(1, width-lipgloss.Width(line)-lipgloss.Width(flags)-2)) + dimStyle.Render(flags)
|
line = line + strings.Repeat(" ", maxInt(1, contentWidth-lipgloss.Width(line)-lipgloss.Width(flags))) + dimStyle.Render(flags)
|
||||||
|
line = truncateText(line, contentWidth)
|
||||||
|
|
||||||
if m.overlay == overlaySearch {
|
if m.overlay == overlaySearch {
|
||||||
search := focusedFieldStyle.Width(width - 4).Render(fieldLabelStyle.Render("Search") + "\n" + m.searchInput.View())
|
search := renderBox(focusedFieldStyle, width, 0, fieldLabelStyle.Render("Search")+"\n"+m.searchInput.View())
|
||||||
return lipgloss.NewStyle().Width(width).Render(line + "\n" + search)
|
return lipgloss.NewStyle().Width(width).Render(line + "\n" + search)
|
||||||
}
|
}
|
||||||
|
|
||||||
return lipgloss.NewStyle().
|
return barStyle.Render(line)
|
||||||
Width(width).
|
|
||||||
Background(lipgloss.Color("#0C1824")).
|
|
||||||
Padding(0, 1).
|
|
||||||
Render(line)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m tuiModel) renderBottomBar(width int) string {
|
func (m tuiModel) renderBottomBar(width int) string {
|
||||||
|
barStyle := lipgloss.NewStyle().
|
||||||
|
Width(contentWidthFor(lipgloss.NewStyle().Padding(0, 1), width)).
|
||||||
|
Background(lipgloss.Color("#0C1824")).
|
||||||
|
Padding(0, 1)
|
||||||
|
contentWidth := contentWidthFor(barStyle, width)
|
||||||
statusColor := colorCyan
|
statusColor := colorCyan
|
||||||
switch m.statusKind {
|
switch m.statusKind {
|
||||||
case statusSuccess:
|
case statusSuccess:
|
||||||
@@ -597,7 +598,7 @@ func (m tuiModel) renderBottomBar(width int) string {
|
|||||||
statusColor = colorRed
|
statusColor = colorRed
|
||||||
}
|
}
|
||||||
|
|
||||||
status := lipgloss.NewStyle().Foreground(statusColor).Render(truncateText(m.status, width-4))
|
status := lipgloss.NewStyle().Foreground(statusColor).Render(truncateText(m.status, contentWidth))
|
||||||
help := "Tab form/list / search p path r reload x reset a apply f force t tags ? help q quit"
|
help := "Tab form/list / search p path r reload x reset a apply f force t tags ? help q quit"
|
||||||
if m.focus == focusForm {
|
if m.focus == focusForm {
|
||||||
help = "Tab next field Shift+Tab previous Esc list Enter newline in message Ctrl+C quit"
|
help = "Tab next field Shift+Tab previous Esc list Enter newline in message Ctrl+C quit"
|
||||||
@@ -612,11 +613,7 @@ func (m tuiModel) renderBottomBar(width int) string {
|
|||||||
help = "Type FORCE then Enter to rewrite and force push, Esc to cancel"
|
help = "Type FORCE then Enter to rewrite and force push, Esc to cancel"
|
||||||
}
|
}
|
||||||
|
|
||||||
return lipgloss.NewStyle().
|
return barStyle.Render(status + "\n" + dimStyle.Render(truncateText(help, contentWidth)))
|
||||||
Width(width).
|
|
||||||
Background(lipgloss.Color("#0C1824")).
|
|
||||||
Padding(0, 1).
|
|
||||||
Render(status + "\n" + dimStyle.Render(truncateText(help, width-4)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m tuiModel) renderCommitPane(width int, height int) string {
|
func (m tuiModel) renderCommitPane(width int, height int) string {
|
||||||
@@ -624,8 +621,8 @@ func (m tuiModel) renderCommitPane(width int, height int) string {
|
|||||||
if m.focus == focusList && m.overlay == overlayNone {
|
if m.focus == focusList && m.overlay == overlayNone {
|
||||||
style = activePanelStyle
|
style = activePanelStyle
|
||||||
}
|
}
|
||||||
innerWidth := maxInt(10, width-2)
|
innerWidth := maxInt(1, contentWidthFor(style, width))
|
||||||
innerHeight := maxInt(6, height-2)
|
innerHeight := maxInt(1, contentHeightFor(style, height))
|
||||||
|
|
||||||
filtered, selectedIndex, _ := selectionAfterFilter(m.commits, m.draftByHash, m.selectedHash, m.searchInput.Value())
|
filtered, selectedIndex, _ := selectionAfterFilter(m.commits, m.draftByHash, m.selectedHash, m.searchInput.Value())
|
||||||
header := headerStyle.Render(fmt.Sprintf("Commits %d/%d", len(filtered), len(m.commits)))
|
header := headerStyle.Render(fmt.Sprintf("Commits %d/%d", len(filtered), len(m.commits)))
|
||||||
@@ -636,7 +633,7 @@ func (m tuiModel) renderCommitPane(width int, height int) string {
|
|||||||
lines := []string{truncateText(header, innerWidth)}
|
lines := []string{truncateText(header, innerWidth)}
|
||||||
if len(filtered) == 0 {
|
if len(filtered) == 0 {
|
||||||
lines = append(lines, "", mutedStyle.Render("No commits match this filter."))
|
lines = append(lines, "", mutedStyle.Render("No commits match this filter."))
|
||||||
return style.Width(width).Height(height).Render(fitLines(lines, innerHeight, innerWidth))
|
return renderBox(style, width, height, fitLines(lines, innerHeight, innerWidth))
|
||||||
}
|
}
|
||||||
|
|
||||||
visibleSlots := maxInt(1, (innerHeight-2)/5)
|
visibleSlots := maxInt(1, (innerHeight-2)/5)
|
||||||
@@ -656,7 +653,7 @@ func (m tuiModel) renderCommitPane(width int, height int) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return style.Width(width).Height(height).Render(fitLines(lines, innerHeight, innerWidth))
|
return renderBox(style, width, height, fitLines(lines, innerHeight, innerWidth))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m tuiModel) renderCommitItem(commit CommitRecord, selected bool, width int) string {
|
func (m tuiModel) renderCommitItem(commit CommitRecord, selected bool, width int) string {
|
||||||
@@ -688,9 +685,9 @@ func (m tuiModel) renderCommitItem(commit CommitRecord, selected bool, width int
|
|||||||
}, "\n")
|
}, "\n")
|
||||||
|
|
||||||
if selected {
|
if selected {
|
||||||
return selectedItemStyle.Width(width).Render(block)
|
return renderBox(selectedItemStyle, width, 0, block)
|
||||||
}
|
}
|
||||||
return itemStyle.Width(width).Render(block)
|
return renderBox(itemStyle, width, 0, block)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m tuiModel) renderDetailPane(width int, height int) string {
|
func (m tuiModel) renderDetailPane(width int, height int) string {
|
||||||
@@ -698,8 +695,8 @@ func (m tuiModel) renderDetailPane(width int, height int) string {
|
|||||||
if m.focus == focusForm && m.overlay == overlayNone {
|
if m.focus == focusForm && m.overlay == overlayNone {
|
||||||
style = activePanelStyle
|
style = activePanelStyle
|
||||||
}
|
}
|
||||||
innerWidth := maxInt(20, width-2)
|
innerWidth := maxInt(1, contentWidthFor(style, width))
|
||||||
innerHeight := maxInt(6, height-2)
|
innerHeight := maxInt(1, contentHeightFor(style, height))
|
||||||
|
|
||||||
var content string
|
var content string
|
||||||
switch m.overlay {
|
switch m.overlay {
|
||||||
@@ -713,7 +710,7 @@ func (m tuiModel) renderDetailPane(width int, height int) string {
|
|||||||
content = m.renderFormPane(innerWidth, innerHeight)
|
content = m.renderFormPane(innerWidth, innerHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
return style.Width(width).Height(height).Render(content)
|
return renderBox(style, width, height, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m tuiModel) renderPathPane(width int, height int) string {
|
func (m tuiModel) renderPathPane(width int, height int) string {
|
||||||
@@ -722,7 +719,7 @@ func (m tuiModel) renderPathPane(width int, height int) string {
|
|||||||
"",
|
"",
|
||||||
"Enter a Git worktree path. Nexus starts with the current directory, but you can switch repositories here at any time.",
|
"Enter a Git worktree path. Nexus starts with the current directory, but you can switch repositories here at any time.",
|
||||||
"",
|
"",
|
||||||
focusedFieldStyle.Width(width).Render(fieldLabelStyle.Render("Path") + "\n" + m.pathInput.View()),
|
renderBox(focusedFieldStyle, width, 0, fieldLabelStyle.Render("Path")+"\n"+m.pathInput.View()),
|
||||||
"",
|
"",
|
||||||
dimStyle.Render("Press Enter to load. Press Esc to return to the current repository."),
|
dimStyle.Render("Press Enter to load. Press Esc to return to the current repository."),
|
||||||
}
|
}
|
||||||
@@ -743,7 +740,7 @@ func (m tuiModel) renderConfirmPane(width int, height int) string {
|
|||||||
lipgloss.NewStyle().Foreground(colorRed).Bold(true).Render("Force push is enabled."),
|
lipgloss.NewStyle().Foreground(colorRed).Bold(true).Render("Force push is enabled."),
|
||||||
"Type FORCE to confirm rewriting history and pushing with --force-with-lease.",
|
"Type FORCE to confirm rewriting history and pushing with --force-with-lease.",
|
||||||
"",
|
"",
|
||||||
focusedFieldStyle.Width(minInt(width, 24)).Render(fieldLabelStyle.Render("Confirmation")+"\n"+m.confirmInput.View()),
|
renderBox(focusedFieldStyle, minInt(width, 24), 0, fieldLabelStyle.Render("Confirmation")+"\n"+m.confirmInput.View()),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
lines = append(lines, "", "Press y to continue, or n/Esc to cancel.")
|
lines = append(lines, "", "Press y to continue, or n/Esc to cancel.")
|
||||||
@@ -794,7 +791,7 @@ func (m tuiModel) renderFormPane(width int, height int) string {
|
|||||||
lines := []string{headerStyle.Render(truncateText(title, width)), ""}
|
lines := []string{headerStyle.Render(truncateText(title, width)), ""}
|
||||||
|
|
||||||
cellGap := 2
|
cellGap := 2
|
||||||
cellWidth := maxInt(18, (width-cellGap)/2)
|
cellWidth := maxInt(1, (width-cellGap)/2)
|
||||||
rows := [][2]int{
|
rows := [][2]int{
|
||||||
{formAuthorName, formAuthorEmail},
|
{formAuthorName, formAuthorEmail},
|
||||||
{formAuthorDate, formCommitterName},
|
{formAuthorDate, formCommitterName},
|
||||||
@@ -809,10 +806,17 @@ func (m tuiModel) renderFormPane(width int, height int) string {
|
|||||||
"Committer date",
|
"Committer date",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, row := range rows {
|
if width >= 44 {
|
||||||
left := m.renderTextField(row[0], labels[row[0]], cellWidth)
|
for _, row := range rows {
|
||||||
right := m.renderTextField(row[1], labels[row[1]], cellWidth)
|
left := m.renderTextField(row[0], labels[row[0]], cellWidth)
|
||||||
lines = append(lines, lipgloss.JoinHorizontal(lipgloss.Top, left, strings.Repeat(" ", cellGap), right))
|
right := m.renderTextField(row[1], labels[row[1]], cellWidth)
|
||||||
|
lines = append(lines, lipgloss.JoinHorizontal(lipgloss.Top, left, strings.Repeat(" ", cellGap), right))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, row := range rows {
|
||||||
|
lines = append(lines, m.renderTextField(row[0], labels[row[0]], width))
|
||||||
|
lines = append(lines, m.renderTextField(row[1], labels[row[1]], width))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
messageHeight := clamp(height-lipgloss.Height(strings.Join(lines, "\n"))-8, 5, 10)
|
messageHeight := clamp(height-lipgloss.Height(strings.Join(lines, "\n"))-8, 5, 10)
|
||||||
@@ -840,7 +844,7 @@ func (m tuiModel) renderTextField(index int, label string, width int) string {
|
|||||||
if m.focus == focusForm && m.overlay == overlayNone && m.formFocus == index {
|
if m.focus == focusForm && m.overlay == overlayNone && m.formFocus == index {
|
||||||
style = focusedFieldStyle
|
style = focusedFieldStyle
|
||||||
}
|
}
|
||||||
return style.Width(width).Render(fieldLabelStyle.Render(label) + "\n" + m.inputs[index].View())
|
return renderBox(style, width, 0, fieldLabelStyle.Render(label)+"\n"+m.inputs[index].View())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m tuiModel) renderMessageField(width int, height int) string {
|
func (m tuiModel) renderMessageField(width int, height int) string {
|
||||||
@@ -848,7 +852,7 @@ func (m tuiModel) renderMessageField(width int, height int) string {
|
|||||||
if m.focus == focusForm && m.overlay == overlayNone && m.formFocus == formMessage {
|
if m.focus == focusForm && m.overlay == overlayNone && m.formFocus == formMessage {
|
||||||
style = focusedFieldStyle
|
style = focusedFieldStyle
|
||||||
}
|
}
|
||||||
return style.Width(width).Height(height).Render(fieldLabelStyle.Render("Commit message") + "\n" + m.message.View())
|
return renderBox(style, width, height, fieldLabelStyle.Render("Commit message")+"\n"+m.message.View())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *tuiModel) updateComponentSizes() {
|
func (m *tuiModel) updateComponentSizes() {
|
||||||
@@ -860,21 +864,17 @@ func (m *tuiModel) updateComponentSizes() {
|
|||||||
if height <= 0 {
|
if height <= 0 {
|
||||||
height = 36
|
height = 36
|
||||||
}
|
}
|
||||||
leftWidth := clamp(width*38/100, 30, 54)
|
_, rightWidth, _ := tuiColumnWidths(width)
|
||||||
if width-leftWidth-1 < 34 {
|
innerRight := maxInt(1, contentWidthFor(panelStyle, rightWidth))
|
||||||
leftWidth = maxInt(24, width-35)
|
|
||||||
}
|
|
||||||
rightWidth := maxInt(30, width-leftWidth-1)
|
|
||||||
innerRight := maxInt(20, rightWidth-4)
|
|
||||||
cellWidth := maxInt(18, (innerRight-2)/2)
|
cellWidth := maxInt(18, (innerRight-2)/2)
|
||||||
|
|
||||||
for i := range m.inputs {
|
for i := range m.inputs {
|
||||||
m.inputs[i].Width = maxInt(8, cellWidth-4)
|
m.inputs[i].Width = maxInt(4, contentWidthFor(blurredFieldStyle, cellWidth))
|
||||||
}
|
}
|
||||||
m.message.SetWidth(maxInt(12, innerRight-4))
|
m.message.SetWidth(maxInt(4, contentWidthFor(blurredFieldStyle, innerRight)))
|
||||||
m.message.SetHeight(clamp(height/4, 5, 10))
|
m.message.SetHeight(clamp(height/4, 5, 10))
|
||||||
m.searchInput.Width = maxInt(16, width-10)
|
m.searchInput.Width = maxInt(16, width-10)
|
||||||
m.pathInput.Width = maxInt(20, innerRight-8)
|
m.pathInput.Width = maxInt(4, contentWidthFor(focusedFieldStyle, innerRight))
|
||||||
m.confirmInput.Width = 12
|
m.confirmInput.Width = 12
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1225,11 +1225,16 @@ func wrapBlockLines(lines []string, width int) []string {
|
|||||||
|
|
||||||
func fitLines(lines []string, height int, width int) string {
|
func fitLines(lines []string, height int, width int) string {
|
||||||
out := make([]string, 0, height)
|
out := make([]string, 0, height)
|
||||||
for _, line := range lines {
|
for _, block := range lines {
|
||||||
|
for _, line := range strings.Split(block, "\n") {
|
||||||
|
if len(out) >= height {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
out = append(out, truncateText(line, width))
|
||||||
|
}
|
||||||
if len(out) >= height {
|
if len(out) >= height {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
out = append(out, truncateText(line, width))
|
|
||||||
}
|
}
|
||||||
for len(out) < height {
|
for len(out) < height {
|
||||||
out = append(out, "")
|
out = append(out, "")
|
||||||
@@ -1327,3 +1332,49 @@ func maxInt(a int, b int) int {
|
|||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func contentWidthFor(style lipgloss.Style, outerWidth int) int {
|
||||||
|
return maxInt(1, outerWidth-style.GetHorizontalFrameSize())
|
||||||
|
}
|
||||||
|
|
||||||
|
func contentHeightFor(style lipgloss.Style, outerHeight int) int {
|
||||||
|
return maxInt(1, outerHeight-style.GetVerticalFrameSize())
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderBox(style lipgloss.Style, outerWidth int, outerHeight int, content string) string {
|
||||||
|
if outerWidth > 0 {
|
||||||
|
style = style.Width(contentWidthFor(style, outerWidth))
|
||||||
|
}
|
||||||
|
if outerHeight > 0 {
|
||||||
|
style = style.Height(contentHeightFor(style, outerHeight))
|
||||||
|
}
|
||||||
|
return style.Render(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tuiColumnWidths(totalWidth int) (int, int, int) {
|
||||||
|
if totalWidth <= 2 {
|
||||||
|
return maxInt(1, totalWidth), 1, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
separator := 1
|
||||||
|
if totalWidth < 50 {
|
||||||
|
separator = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
left := clamp(totalWidth*38/100, 24, 54)
|
||||||
|
minRight := 24
|
||||||
|
if totalWidth < 70 {
|
||||||
|
minRight = maxInt(10, totalWidth/2)
|
||||||
|
}
|
||||||
|
maxLeft := totalWidth - separator - minRight
|
||||||
|
if maxLeft < 10 {
|
||||||
|
maxLeft = maxInt(1, totalWidth-separator-1)
|
||||||
|
}
|
||||||
|
left = clamp(left, 1, maxLeft)
|
||||||
|
right := totalWidth - separator - left
|
||||||
|
if right < 1 {
|
||||||
|
right = 1
|
||||||
|
left = maxInt(1, totalWidth-separator-right)
|
||||||
|
}
|
||||||
|
return left, right, separator
|
||||||
|
}
|
||||||
|
|||||||
+45
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
@@ -111,3 +112,47 @@ func TestRepositoryLoadErrorOpensPathEntryState(t *testing.T) {
|
|||||||
t.Fatalf("expected path input to keep attempted path %q, got %q", path, model.pathInput.Value())
|
t.Fatalf("expected path input to keep attempted path %q, got %q", path, model.pathInput.Value())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestViewFitsTerminalDimensions(t *testing.T) {
|
||||||
|
model := newTUIModel(NewApp(), "/tmp/repo")
|
||||||
|
model.width = 80
|
||||||
|
model.height = 24
|
||||||
|
|
||||||
|
initialView := model.View()
|
||||||
|
if height := lipgloss.Height(initialView); height > model.height {
|
||||||
|
t.Fatalf("expected initial view height <= %d, got %d", model.height, height)
|
||||||
|
}
|
||||||
|
if width := maxRenderedLineWidth(initialView); width > model.width-1 {
|
||||||
|
t.Fatalf("expected initial max line width <= %d, got %d", model.width-1, width)
|
||||||
|
}
|
||||||
|
|
||||||
|
model.handleRepoLoaded(repoLoadedMsg{
|
||||||
|
repo: RepositoryState{
|
||||||
|
Path: "/tmp/repo",
|
||||||
|
Clean: true,
|
||||||
|
CurrentBranch: "main",
|
||||||
|
},
|
||||||
|
commits: []CommitRecord{
|
||||||
|
{Hash: "1111111111111111111111111111111111111111", ShortHash: "1111111", AuthorName: "Ada Lovelace", AuthorEmail: "ada@example.com", AuthorDate: "2024-01-01T10:00:00Z", CommitterName: "Ada Lovelace", CommitterEmail: "ada@example.com", CommitterDate: "2024-01-01T10:00:00Z", Message: "Add a compact terminal interface that should not wrap every line"},
|
||||||
|
{Hash: "2222222222222222222222222222222222222222", ShortHash: "2222222", AuthorName: "Grace Hopper", AuthorEmail: "grace@example.com", AuthorDate: "2024-01-02T10:00:00Z", CommitterName: "Grace Hopper", CommitterEmail: "grace@example.com", CommitterDate: "2024-01-02T10:00:00Z", Message: "Fix rendering dimensions"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
view := model.View()
|
||||||
|
if height := lipgloss.Height(view); height > model.height {
|
||||||
|
t.Fatalf("expected view height <= %d, got %d", model.height, height)
|
||||||
|
}
|
||||||
|
if width := maxRenderedLineWidth(view); width > model.width-1 {
|
||||||
|
t.Fatalf("expected max line width <= %d, got %d", model.width-1, width)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func maxRenderedLineWidth(value string) int {
|
||||||
|
width := 0
|
||||||
|
for _, line := range strings.Split(value, "\n") {
|
||||||
|
if lineWidth := lipgloss.Width(line); lineWidth > width {
|
||||||
|
width = lineWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return width
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user