@@ -111,6 +111,7 @@ func (m model) startHostForm(editID string, duplicate bool) (model, tea.Cmd) {
111111 m .formDefaultUser = ""
112112 m .formUserConfigs = nil
113113 m .formUserCursor = 0
114+ m .formScroll = 0
114115 m .formPathSuggestions = nil
115116 m .formPathSuggestIndex = 0
116117
@@ -180,6 +181,12 @@ func (m model) updateHostForm(msg tea.Msg) (tea.Model, tea.Cmd) {
180181 if m .cyclePathSuggestion (- 1 ) {
181182 return m , nil
182183 }
184+ case "pgdown" :
185+ m .formScroll += 4
186+ return m , nil
187+ case "pgup" :
188+ m .formScroll = max (0 , m .formScroll - 4 )
189+ return m , nil
183190 case "left" , "h" :
184191 switch m .formFocus {
185192 case fSelectedUser :
@@ -250,6 +257,12 @@ func (m model) cycleFormFocus(dir int) (tea.Model, tea.Cmd) {
250257 }
251258 cur = (cur + dir + len (focuses )) % len (focuses )
252259 m .formFocus = focuses [cur ]
260+ if m .formFocus >= fSelectedUser {
261+ // Auto-jump to lower section when entering per-user auth controls.
262+ m .formScroll = 9999
263+ } else {
264+ m .formScroll = 0
265+ }
253266
254267 if idx := formInputIdx (m .formFocus ); idx >= 0 {
255268 m .refreshPathSuggestions ()
@@ -387,7 +400,17 @@ func (m model) viewHostForm() string {
387400
388401 formW := 90
389402 formH := 38
390- colW := (formW - 6 ) / 2
403+ contentW := formW - 6
404+ colGap := 2
405+ colW := (contentW - colGap ) / 2
406+
407+ // Keep input widths in sync with the rendered layout so fields use available space.
408+ m .formInputs [0 ].Width = colW
409+ m .formInputs [1 ].Width = colW
410+ m .formInputs [2 ].Width = colW
411+ m .formInputs [3 ].Width = colW
412+ m .formInputs [4 ].Width = min (10 , colW )
413+ m .formInputs [5 ].Width = contentW
391414
392415 var b strings.Builder
393416 b .WriteString (titleStyle .Render ("📝 " + title ) + "\n \n " )
@@ -424,7 +447,7 @@ func (m model) viewHostForm() string {
424447 b .WriteString (hintStyle .Render (" Comma-separated usernames. Example: main, ubuntu, deploy" ) + "\n \n " )
425448
426449 if len (m .formUserConfigs ) > 0 {
427- b .WriteString (m .renderSelectedUserSection ())
450+ b .WriteString (m .renderSelectedUserSection (contentW ))
428451 } else {
429452 b .WriteString (hintStyle .Render (" Type usernames above to configure per-user auth settings." ) + "\n " )
430453 }
@@ -433,18 +456,35 @@ func (m model) viewHostForm() string {
433456 b .WriteString ("\n " + errorStyle .Render ("✗ " + m .formErr ) + "\n " )
434457 }
435458
436- b .WriteString ("\n " + statusBarStyle .Render ("tab/↑↓ navigate • space default user • ←→ adjust • enter save • esc cancel" ))
459+ b .WriteString ("\n " + statusBarStyle .Render ("tab/↑↓ navigate • space default user • ←→ adjust • pgup/pgdn scroll • enter save • esc cancel" ))
437460
438461 content := b .String ()
462+ content = m .renderFormViewport (content , formH )
463+ return boxStyle .Width (formW ).Render (content )
464+ }
465+
466+ func (m model ) renderFormViewport (content string , height int ) string {
439467 lines := strings .Split (content , "\n " )
440- for len (lines ) < formH {
441- lines = append (lines , "" )
468+ if len (lines ) <= height {
469+ for len (lines ) < height {
470+ lines = append (lines , "" )
471+ }
472+ return strings .Join (lines , "\n " )
442473 }
443- content = strings .Join (lines [:formH ], "\n " )
444- return boxStyle .Width (formW ).Render (content )
474+
475+ maxOffset := len (lines ) - height
476+ offset := m .formScroll
477+ if offset < 0 {
478+ offset = 0
479+ }
480+ if offset > maxOffset {
481+ offset = maxOffset
482+ }
483+
484+ return strings .Join (lines [offset :offset + height ], "\n " )
445485}
446486
447- func (m model ) renderSelectedUserSection () string {
487+ func (m model ) renderSelectedUserSection (contentW int ) string {
448488 user := m .currentFormUser ()
449489 if user == nil {
450490 return ""
@@ -463,7 +503,9 @@ func (m model) renderSelectedUserSection() string {
463503 }
464504 b .WriteString ("\n " )
465505
466- cardBorder := lipgloss .NewStyle ().Border (lipgloss .RoundedBorder ()).BorderForeground (highlight ).Padding (1 , 2 ).Width (80 )
506+ cardBorder := lipgloss .NewStyle ().Border (lipgloss .RoundedBorder ()).BorderForeground (highlight ).Padding (1 , 2 ).Width (contentW )
507+ cardContentW := max (20 , contentW - 6 )
508+ m .formInputs [6 ].Width = cardContentW
467509
468510 var card strings.Builder
469511 headerLabel := "👤 " + user .Username
@@ -502,7 +544,7 @@ func (m model) renderSelectedUserSection() string {
502544 card .WriteString (credLabel + "\n " )
503545 card .WriteString (m .formInputs [6 ].View () + "\n " )
504546 card .WriteString (hintStyle .Render (" Key path or paste private key" ) + "\n " )
505- card .WriteString (m .renderPathSuggestions ())
547+ card .WriteString (m .renderPathSuggestions (cardContentW ))
506548 if m .formEditing != "" && (user .ExistingKeyPath != "" || len (user .ExistingEncKey ) > 0 ) {
507549 card .WriteString (hintStyle .Render (" Leave empty to keep current SSH key" ) + "\n " )
508550 }
@@ -727,6 +769,7 @@ func (m *model) acceptPathSuggestion() bool {
727769 }
728770 suggestion := m .formPathSuggestions [m .formPathSuggestIndex ]
729771 input .SetValue (suggestion )
772+ input .CursorEnd ()
730773 if m .formFocus == fSelectedUserCredential {
731774 m .storeSelectedUserCredentialInput ()
732775 }
@@ -850,20 +893,36 @@ func identityConflictMessage(aliasConflict, hostnameConflict bool) string {
850893 }
851894}
852895
853- func (m model ) renderPathSuggestions () string {
896+ func (m model ) renderPathSuggestions (contentW int ) string {
854897 if len (m .formPathSuggestions ) == 0 {
855898 return ""
856899 }
857- var parts []string
900+ itemW := max (20 , contentW - 2 )
901+ var b strings.Builder
902+ b .WriteString (hintStyle .Render (" Suggestions (tab accept • ctrl+n/p select):" ) + "\n " )
903+
858904 for i , s := range m .formPathSuggestions {
905+ label := truncateForWidth (s , itemW - 4 )
906+ prefix := " "
859907 style := lipgloss .NewStyle ().Foreground (subtle )
860908 if i == m .formPathSuggestIndex {
861- style = lipgloss .NewStyle ().Foreground (highlight ).Bold (true )
862- s = "> " + s
863- } else {
864- s = " " + s
909+ prefix = "▸ "
910+ style = lipgloss .NewStyle ().Foreground (lipgloss .Color ("#111827" )).Background (highlight ).Bold (true ).Padding (0 , 1 )
865911 }
866- parts = append (parts , style .Render (s ))
912+ line := prefix + label
913+ b .WriteString (" " + style .Render (line ) + "\n " )
914+ }
915+
916+ return b .String ()
917+ }
918+
919+ func truncateForWidth (s string , width int ) string {
920+ if width <= 3 {
921+ return s
922+ }
923+ r := []rune (s )
924+ if len (r ) <= width {
925+ return s
867926 }
868- return " " + strings . Join ( parts , " " ) + "\n "
927+ return string ( r [: width - 3 ] ) + "... "
869928}
0 commit comments