Skip to content
Open
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
11 changes: 10 additions & 1 deletion internal/tui/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ type KeyMap struct {
// keybindings for changing theme
SwitchTheme key.Binding

// keybindings for diff view
ToggleDiffView key.Binding

// keybindings for navigation
FocusNext key.Binding
FocusPrev key.Binding
Expand Down Expand Up @@ -89,7 +92,7 @@ func (k KeyMap) FullHelp() []HelpSection {
},
{
Title: "Misc",
Bindings: []key.Binding{k.SwitchTheme, k.ToggleHelp, k.Escape, k.Quit},
Bindings: []key.Binding{k.SwitchTheme, k.ToggleDiffView, k.ToggleHelp, k.Escape, k.Quit},
},
}
}
Expand Down Expand Up @@ -151,6 +154,12 @@ func DefaultKeyMap() KeyMap {
key.WithHelp("<c+t>", "switch theme"),
),

// diff view
ToggleDiffView: key.NewBinding(
key.WithKeys("ctrl+d"),
key.WithHelp("<c+d>", "toggle diff view"),
),

// navigation
FocusNext: key.NewBinding(
key.WithKeys("tab"),
Expand Down
73 changes: 57 additions & 16 deletions internal/tui/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ type Model struct {
confirmCallback func(bool) tea.Cmd
// New fields for command history
CommandHistory []string
// Diff view mode: nil = auto (respects threshold), true = split, false = unified
forcedDiffViewMode *bool
}

// initialModel creates the initial state of the application.
Expand Down Expand Up @@ -99,22 +101,23 @@ func initialModel() Model {
historyVP.SetContent("Command history will appear here...")

return Model{
theme: Themes[selectedThemeName],
themeNames: themeNames,
themeIndex: indexOf(themeNames, selectedThemeName),
focusedPanel: StatusPanel,
activeSourcePanel: StatusPanel,
help: help.New(),
helpViewport: viewport.New(0, 0),
showHelp: false,
git: gc,
repoName: repoName,
branchName: branchName,
panels: panels,
mode: modeNormal,
textInput: ti,
descriptionInput: ta,
CommandHistory: []string{},
theme: Themes[selectedThemeName],
themeNames: themeNames,
themeIndex: indexOf(themeNames, selectedThemeName),
focusedPanel: StatusPanel,
activeSourcePanel: StatusPanel,
help: help.New(),
helpViewport: viewport.New(0, 0),
showHelp: false,
git: gc,
repoName: repoName,
branchName: branchName,
panels: panels,
mode: modeNormal,
textInput: ti,
descriptionInput: ta,
CommandHistory: []string{},
forcedDiffViewMode: nil,
}
}

Expand Down Expand Up @@ -147,6 +150,44 @@ func (m *Model) nextTheme() {
m.theme = Themes[m.themeNames[m.themeIndex]]
}

// toggleDiffView cycles through diff view modes with behavior that matches the
// currently visible layout:
// - If we're in auto mode, pressing the key switches to the *opposite* of what
// auto currently resolves to (so from an auto-split view you go directly to
// forced unified, and from auto-unified you go to forced split).
// - If we're already in a forced mode, we keep the original cycle:
// split -> unified -> auto.
func (m *Model) toggleDiffView() {
const splitViewThreshold = 80

if m.forcedDiffViewMode == nil {
// Auto mode: determine what the main panel would currently do,
// then toggle to the opposite.

// This mirrors the width calculation used in updateMainPanel for the
// right-hand (main) panel content.
rightPanelWidth := int(float64(m.width)*(1-leftPanelWidthRatio)) - borderWidth - 2
useSplitAuto := rightPanelWidth >= splitViewThreshold && rightPanelWidth > 60

if useSplitAuto {
// Auto would render split; user expects a single press to go to unified.
falseVal := false
m.forcedDiffViewMode = &falseVal
} else {
// Auto would render unified; user expects a single press to go to split.
trueVal := true
m.forcedDiffViewMode = &trueVal
}
} else if *m.forcedDiffViewMode {
// Currently forced split - switch to forced unified.
falseVal := false
m.forcedDiffViewMode = &falseVal
} else {
// Currently forced unified - switch back to auto.
m.forcedDiffViewMode = nil
}
}

// panelShortHelp returns a slice of key.Binding for the focused Panel.
func (m *Model) panelShortHelp() []key.Binding {
switch m.focusedPanel {
Expand Down
7 changes: 6 additions & 1 deletion internal/tui/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,11 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case key.Matches(msg, keys.SwitchTheme):
m.nextTheme()

case key.Matches(msg, keys.ToggleDiffView):
m.toggleDiffView()
cmd = m.updateMainPanel()
cmds = append(cmds, cmd)

case key.Matches(msg, keys.FocusNext), key.Matches(msg, keys.FocusPrev),
key.Matches(msg, keys.FocusZero), key.Matches(msg, keys.FocusOne),
key.Matches(msg, keys.FocusTwo), key.Matches(msg, keys.FocusThree),
Expand Down Expand Up @@ -502,7 +507,7 @@ func (m *Model) updateMainPanel() tea.Cmd {
// Apply adaptive visual styling: split-view for wide terminals, unified for narrow ones
// Calculate right panel width (approximately 65% of total width minus borders)
rightPanelWidth := int(float64(m.width)*(1-leftPanelWidthRatio)) - borderWidth - 2
content = renderAdaptiveDiffView(content, rightPanelWidth, m.theme)
content = renderAdaptiveDiffView(content, rightPanelWidth, m.theme, m.forcedDiffViewMode)
return mainContentUpdatedMsg{content: content}
}
}
Expand Down
Loading