diff --git a/pkg/tui/host_form_tab.go b/pkg/tui/host_form_tab.go index edfa7fd..64ec35d 100644 --- a/pkg/tui/host_form_tab.go +++ b/pkg/tui/host_form_tab.go @@ -83,7 +83,6 @@ func newHostFormTab(mode formMode, host *models.Host, dataDir string) *HostFormT inputs[fieldPort].Placeholder = "22" inputs[fieldPort].SetValue("22") inputs[fieldUsername].Placeholder = "root" - inputs[fieldAuthType].Placeholder = "password / key / both" inputs[fieldAuthType].SetValue("password") inputs[fieldPassword].EchoMode = textinput.EchoPassword inputs[fieldPassword].Placeholder = "Enter password" @@ -143,11 +142,22 @@ func (t *HostFormTab) Update(msg tea.Msg) (Tab, tea.Cmd) { return t, func() tea.Msg { return closeFormMsg{} } case "enter": + if t.focus == fieldAuthType { + t.toggleAuthType() + return t, nil + } if t.focus == fieldCount-1 { return t.submit() } t.nextField() + case " ", "left", "right": + if t.focus == fieldAuthType { + t.toggleAuthType() + return t, nil + } + fallthrough + case "tab", "down": t.nextField() @@ -158,6 +168,10 @@ func (t *HostFormTab) Update(msg tea.Msg) (Tab, tea.Cmd) { return t.submit() default: + if t.focus == fieldAuthType { + // ignore typing on auth type field + return t, nil + } var cmd tea.Cmd t.inputs[t.focus], cmd = t.inputs[t.focus].Update(msg) return t, cmd @@ -189,6 +203,22 @@ func (t *HostFormTab) prevField() { t.inputs[t.focus].Prompt = "> " } +func cycleAuthType(current string) string { + switch current { + case "password": + return "key" + case "key": + return "password" + default: + return "password" + } +} + +func (t *HostFormTab) toggleAuthType() { + current := t.inputs[fieldAuthType].Value() + t.inputs[fieldAuthType].SetValue(cycleAuthType(current)) +} + func (t *HostFormTab) submit() (Tab, tea.Cmd) { name := t.inputs[fieldName].Value() hostname := t.inputs[fieldHostname].Value() @@ -278,16 +308,35 @@ func (t *HostFormTab) View() string { b.WriteString(style.Render(label + ":")) b.WriteString("\n") - renderedInput := input.View() - - // Add parentheses showing auth options - if i == fieldAuthType && i != t.focus { + if i == fieldAuthType { + // Render auth type as toggle pills + current := input.Value() + pills := []string{"password", "key"} + var parts []string + for _, p := range pills { + if p == current { + if i == t.focus { + parts = append(parts, SelectedStyle.Render(" "+p+" ")) + } else { + parts = append(parts, TagStyle.Render(" "+p+" ")) + } + } else { + parts = append(parts, SubtitleStyle.Render(" "+p+" ")) + } + } b.WriteString(" ") + b.WriteString(strings.Join(parts, " ")) + b.WriteString("\n") + if i == t.focus { + b.WriteString(InfoStyle.Render(" Space/←/→ to toggle")) + } + b.WriteString("\n") } else { + renderedInput := input.View() b.WriteString(" ") + b.WriteString(renderedInput) + b.WriteString("\n\n") } - b.WriteString(renderedInput) - b.WriteString("\n\n") } // Help text diff --git a/pkg/tui/tabs.go b/pkg/tui/tabs.go index d4d55a2..3c98b0c 100644 --- a/pkg/tui/tabs.go +++ b/pkg/tui/tabs.go @@ -4,6 +4,7 @@ import ( "strings" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" ) // Tab represents a single tab in the TUI @@ -194,8 +195,17 @@ func renderTabBar(tm *TabManager) string { return TabBarStyle.Render(bar) } -// renderStatusBar renders the bottom status bar +// renderStatusBar renders the bottom status bar, centered func renderStatusBar(tm *TabManager) string { hints := "Ctrl+Tab:switch Ctrl+Q:close ↑↓:nav Enter:SSH Ctrl+N:add Ctrl+F:SFTP Ctrl+K:keys Ctrl+P:snippets q:quit" - return StatusBarStyle.Render(hints) + rendered := StatusBarStyle.Render(hints) + // Center within the terminal width + if tm.width > 0 { + w := lipgloss.Width(rendered) + if w < tm.width { + pad := (tm.width - w) / 2 + rendered = strings.Repeat(" ", pad) + rendered + } + } + return rendered }