@@ -5,9 +5,11 @@ import (
55 "strconv"
66
77 "github.com/reeflective/readline/internal/color"
8+ "github.com/reeflective/readline/internal/completion"
89 "github.com/reeflective/readline/internal/core"
910 "github.com/reeflective/readline/internal/strutil"
1011 "github.com/reeflective/readline/internal/term"
12+ "github.com/reeflective/readline/internal/ui"
1113)
1214
1315// Refresh recomputes and redisplays the entire readline interface, except
@@ -43,9 +45,9 @@ func (e *Engine) Refresh() {
4345 if e .prompt .LastUsed () == 0 && e .line .Lines () > 0 {
4446 var indicator string
4547 if e .opts .GetBool ("multiline-column-numbered" ) {
46- indicator = fmt .Sprintf (" \x1b [1;30m%d \x1b [0m " , 1 )
48+ indicator = fmt .Sprintf (color . FgBlackBright + "%d" + color . Reset + " " , 1 )
4749 } else {
48- indicator = " \x1b [1;30m \U00002502 \x1b [0m"
50+ indicator = ui . DefaultMultilineColumn
4951 }
5052
5153 e .startCols += indicatorWidth
@@ -67,10 +69,53 @@ func (e *Engine) Refresh() {
6769 e .lineCol , e .lineRows = core .CoordinatesLine (e .line , e .startCols )
6870 }
6971
70- // 3. Input Area Rendering
72+ // Ensure that we have enough space to print the line.
73+ // We probe the terminal to verify that we are not at the bottom of the screen.
74+ // If we are, we scroll the screen to make space for the line.
75+ if e .lineRows > 1 {
76+ // 1. Probe the terminal height.
77+ // We move the cursor down to the last line of the input line,
78+ // and check if the cursor is at the expected position.
79+ term .MoveCursorDown (e .lineRows - 1 )
80+ _ , actualRow := e .keys .GetCursorPos ()
81+ term .MoveCursorUp (e .lineRows - 1 )
82+
83+ // 2. Calculate the overshoot.
84+ expectedRow := e .startRows + e .lineRows - 1
85+ overshoot := expectedRow - actualRow
86+
87+ // 3. Scroll the screen if needed.
88+ if overshoot > 0 {
89+ // Move to the bottom of the terminal.
90+ term .MoveCursorDown (actualRow - e .startRows )
91+
92+ // Scroll the screen by printing newlines.
93+ for i := 0 ; i < overshoot ; i ++ {
94+ fmt .Print ("\n " )
95+ }
96+
97+ // Update the start row to reflect the scrolling.
98+ e .startRows -= overshoot
99+
100+ // Move the cursor back up to the new start position.
101+ term .MoveCursorUp (e .lineRows - 1 )
102+ term .MoveCursorForwards (e .startCols )
103+ }
104+ }
71105
106+ // 3. Input Area Rendering
72107 e .renderInputArea ()
108+
73109 // 4. Helpers Rendering
110+ // We clear everything below the input area to ensure that no artifacts
111+ // from previous renders (like longer lines or helpers) remain visible.
112+ term .MoveCursorDown (1 )
113+ term .MoveCursorBackwards (term .GetWidth ())
114+ fmt .Print (term .ClearScreenBelow )
115+ term .MoveCursorUp (1 )
116+ term .MoveCursorForwards (e .lineCol )
117+
118+ e .renderHelpers ()
74119
75120 // 5. Final Cursor Positioning
76121 // The cursor is currently at the end of the input line (lineRows, lineCol).
@@ -88,6 +133,68 @@ func (e *Engine) Refresh() {
88133func (e * Engine ) renderInputArea () {
89134 e .displayLineRefactored ()
90135 e .renderMultilineIndicators ()
136+ e .renderRightPrompt ()
137+ }
138+
139+ func (e * Engine ) renderHelpers () {
140+ e .completer .Autocomplete ()
141+
142+ // 1. Check if we have anything to print.
143+ hintRows := ui .CoordinatesHint (e .hint )
144+ compMatches := e .completer .Matches ()
145+ compSkip := e .completer .DisplaySkipped ()
146+
147+ // 2. Clear below the input line to remove artifacts,
148+ // unless we are at the bottom of the screen.
149+ termHeight := term .GetLength ()
150+ if (e .startRows + e .lineRows ) < termHeight {
151+ term .MoveCursorDown (1 )
152+ term .MoveCursorBackwards (term .GetWidth ())
153+ fmt .Print (term .ClearScreenBelow )
154+ term .MoveCursorUp (1 )
155+ term .MoveCursorForwards (e .lineCol )
156+ }
157+
158+ if hintRows == 0 && (compMatches == 0 || compSkip ) {
159+ e .hintRows = 0
160+ e .compRows = 0
161+
162+ return
163+ }
164+
165+ fmt .Print (term .NewlineReturn )
166+
167+ // 3. Display Hints
168+ ui .DisplayHint (e .hint )
169+ e .hintRows = ui .CoordinatesHint (e .hint )
170+
171+ // 4. Display Completions
172+ if compMatches > 0 && ! compSkip {
173+ completion .Display (e .completer , e .AvailableHelperLines ())
174+ e .compRows = completion .Coordinates (e .completer )
175+ } else {
176+ e .completer .ResetUsedRows ()
177+ e .compRows = 0
178+ }
179+
180+ // 5. Restore Cursor to the "bottom of input area"
181+ // The cursor is currently at the bottom of the helpers.
182+ // We need to move it back up to the line just below the input text.
183+ term .MoveCursorUp (e .compRows )
184+ term .MoveCursorUp (e .hintRows )
185+ term .MoveCursorUp (1 )
186+
187+ // We are now on the same row as the end of the input line,
188+ // but at column 0. We need to move to e.lineCol.
189+ term .MoveCursorForwards (e .lineCol )
190+ }
191+
192+ func (e * Engine ) renderRightPrompt () {
193+ e .prompt .RightPrint (e .lineCol , true )
194+
195+ // Restore cursor to the end of the input line.
196+ term .MoveCursorBackwards (term .GetWidth ())
197+ term .MoveCursorForwards (e .lineCol )
91198}
92199
93200func (e * Engine ) displayLineRefactored () {
@@ -121,43 +228,49 @@ func (e *Engine) renderMultilineIndicators() {
121228 if e .line .Lines () == 0 {
122229 return
123230 }
231+
124232 // 1. Determine if we need to print columns.
125233 columns := e .opts .GetBool ("multiline-column" ) ||
126234 e .opts .GetBool ("multiline-column-numbered" ) ||
127235 e .opts .GetString ("multiline-column-custom" ) != ""
128236 promptEmpty := e .prompt .LastUsed () == 0
237+
129238 // If no columns are requested and the prompt is not empty, we have nothing to do.
130239 if ! columns && ! promptEmpty {
131240 return
132241 }
242+
133243 // 2. Move to the top of the input area (first line).
134244 term .MoveCursorUp (e .lineRows )
135245 term .MoveCursorBackwards (term .GetWidth ())
246+
136247 // 3. Print the indicators for subsequent lines (1..N).
137248 printedLines := 0
138249 numbered := e .opts .GetBool ("multiline-column-numbered" )
139- pipe := "\x1b [1;30m\U00002502 \x1b [0m"
140- angle := "\x1b [1;30m\U00002514 \x1b [0m"
250+
251+ // Indicators
252+ pipe := ui .DefaultMultilineColumn
141253
142254 for i := 1 ; i <= e .line .Lines (); i ++ {
143- var indicator string
255+ fmt .Print ("\n " )
256+
144257 if numbered {
145- indicator = fmt .Sprintf (" \x1b [1;30m%d \x1b [0m " , i + 1 )
258+ fmt .Print ( fmt . Sprintf (color . FgBlackBright + "%d" + color . Reset + " " , i + 1 ) )
146259 } else if i == e .line .Lines () {
147- indicator = angle
260+ e . prompt . SecondaryPrint ()
148261 } else {
149- indicator = pipe
262+ fmt . Print ( pipe )
150263 }
151264
152- fmt .Print ("\n " + indicator )
153-
154265 printedLines ++
155266 }
267+
156268 // 4. Return cursor to the bottom of the input area.
157269 correction := e .lineRows - printedLines
158270 if correction > 0 {
159271 term .MoveCursorDown (correction )
160272 }
273+
161274 // 5. Restore horizontal position to the end of the input text.
162275 term .MoveCursorBackwards (term .GetWidth ())
163276 term .MoveCursorForwards (e .lineCol )
0 commit comments