⋯ 793 hidden lines@@ -794,6 +794,11 @@ func (i *Interactive) redraw() { 794 } else if len(dialog) == 0 && i.fileSuggest.Active(currentInput) { 794 } else if len(dialog) == 0 && i.fileSuggest.Active(currentInput) { 795 suggest = i.fileSuggest.Render(currentInput, i.cfg.Theme, cols) 795 suggest = i.fileSuggest.Render(currentInput, i.cfg.Theme, cols) 796 } 796 } 797 + if len(suggest) > 0 { 798 + // One blank row above the popup so it doesn't sit flush 799 + // against the chat / welcome content above. 800 + suggest = append([]string{"" }, suggest...) 801 + } 797 802 798 // Busy prefix shown at the far left of the status bar. The 803 // Busy prefix shown at the far left of the status bar. The 799 // spinner glyph and its funny-line message share the `zot` 804 // spinner glyph and its funny-line message share the `zot` ⋯ 45 hidden lines@@ -845,13 +850,16 @@ func (i *Interactive) redraw() { 845 } 850 } 846 } 851 } 847 852 848 - // Bottom-sticky sections (always visible, never scroll). One 853 + // Bottom-sticky sections (always visible, never scroll). Each 849 - // blank row above statusLines so the spinner / model / cwd 854 + // non-empty subsection (dialog, suggest popup, sliding-in queue) 850 - // block doesn't sit flush against the chat content above it, 855 + // is preceded by one blank row so it has air above the chat 851 - // one blank between status bar and editor for input breathing 856 + // content. The status block and editor get their own dedicated 852 - // room, and one trailing blank at the very bottom so the 857 + // blanks so spacing stays consistent whether or not a dialog or 853 - // editor isn't flush against the terminal edge. 858 + // popup is showing. 854 - bottom := make([]string, 0, len(dialog)+len(suggest)+len(queue)+len(edLines)+4) 859 + bottom := make([]string, 0, len(dialog)+len(suggest)+len(queue)+len(edLines)+8) 860 + if len(dialog) > 0 { 861 + bottom = append(bottom, "" ) 862 + } 855 bottom = append(bottom, dialog...) 863 bottom = append(bottom, dialog...) 856 bottom = append(bottom, suggest...) 864 bottom = append(bottom, suggest...) 857 bottom = append(bottom, queue...) 865 bottom = append(bottom, queue...) ⋯ 114 hidden lines@@ -972,28 +980,35 @@ func (i *Interactive) redraw() { 972 // the blinking cursor shows where the user is actually typing. 980 // the blinking cursor shows where the user is actually typing. 973 // Dialogs without a cursor (model picker, /help, /login, etc.) 981 // Dialogs without a cursor (model picker, /help, /login, etc.) 974 // return -1 and the main editor keeps the cursor. 982 // return -1 and the main editor keeps the cursor. 983 + // dialogLead is 1 when the bottom region prepends a blank above 984 + // the dialog block (whenever a dialog is showing) so popup-side 985 + // cursor positions still land in the right cell. 986 + dialogLead := 0 987 + if len(dialog) > 0 { 988 + dialogLead = 1 989 + } 975 // +2 accounts for the blank row above statusLines (so the 990 // +2 accounts for the blank row above statusLines (so the 976 // status block has air above it) and the blank row between 991 // status block has air above it) and the blank row between 977 // statusLines and edLines (input breathing room). Without 992 // statusLines and edLines (input breathing room). Without 978 // these the rendered cursor would land on a blank instead of 993 // these the rendered cursor would land on a blank instead of 979 // inside the editor row. 994 // inside the editor row. 980 - cursorRow := len(visibleChat) + len(dialog) + len(suggest) + len(queue) + 1 + len(statusLines) + 1 + curR 995 + cursorRow := len(visibleChat) + dialogLead + len(dialog) + len(suggest) + len(queue) + 1 + len(statusLines) + 1 + curR 981 cursorCol := curC 996 cursorCol := curC 982 if i.btwDialog.Active() { 997 if i.btwDialog.Active() { 983 if r, c := i.btwDialog.CursorPos(cols); r >= 0 { 998 if r, c := i.btwDialog.CursorPos(cols); r >= 0 { 984 - cursorRow = len(visibleChat) + r 999 + cursorRow = len(visibleChat) + dialogLead + r 985 cursorCol = c 1000 cursorCol = c 986 } 1001 } 987 } 1002 } 988 if i.dialog.Active() { 1003 if i.dialog.Active() { 989 if r, c := i.dialog.CursorPos(cols); r >= 0 { 1004 if r, c := i.dialog.CursorPos(cols); r >= 0 { 990 - cursorRow = len(visibleChat) + r 1005 + cursorRow = len(visibleChat) + dialogLead + r 991 cursorCol = c 1006 cursorCol = c 992 } 1007 } 993 } 1008 } 994 if i.sessionDialog.Active() { 1009 if i.sessionDialog.Active() { 995 if r, c := i.sessionDialog.CursorPos(); r >= 0 { 1010 if r, c := i.sessionDialog.CursorPos(); r >= 0 { 996 - cursorRow = len(visibleChat) + r 1011 + cursorRow = len(visibleChat) + dialogLead + r 997 cursorCol = c 1012 cursorCol = c 998 } 1013 } 999 } 1014 }