33
44import { Suggestion , SuggestionBlob } from "../runtime/model.js" ;
55import { getSuggestions } from "../runtime/runtime.js" ;
6- import { ISTerm } from "../isterm/pty.js" ;
6+ import { ISTerm , ISTermPatch } from "../isterm/pty.js" ;
77import { renderBox , truncateText , truncateMultilineText } from "./utils.js" ;
8- import ansi from "ansi-escapes" ;
98import chalk from "chalk" ;
109import { Shell } from "../utils/shell.js" ;
1110import log from "../utils/log.js" ;
@@ -17,11 +16,8 @@ const descriptionWidth = 30;
1716const descriptionHeight = 5 ;
1817const borderWidth = 2 ;
1918const activeSuggestionBackgroundColor = "#7D56F4" ;
20- export const MAX_LINES = borderWidth + Math . max ( maxSuggestions , descriptionHeight ) ;
21- type SuggestionsSequence = {
22- data : string ;
23- rows : number ;
24- } ;
19+ export const MAX_LINES = borderWidth + Math . max ( maxSuggestions , descriptionHeight ) + 1 ; // accounts when there is a unhandled newline at the end of the command
20+ export const MIN_WIDTH = borderWidth + descriptionWidth ;
2521
2622export type KeyPressEvent = [ string | null | undefined , KeyPress ] ;
2723
@@ -67,100 +63,89 @@ export class SuggestionManager {
6763 this . #activeSuggestionIdx = 0 ;
6864 }
6965
70- private _renderArgumentDescription ( description : string | undefined , x : number ) {
71- if ( ! description ) return "" ;
72- return renderBox ( [ truncateText ( description , descriptionWidth - borderWidth ) ] , descriptionWidth , x ) ;
66+ private _renderArgumentDescription ( description : string | undefined ) {
67+ if ( ! description ) return [ ] ;
68+ return renderBox ( [ truncateText ( description , descriptionWidth - borderWidth ) ] , descriptionWidth ) ;
7369 }
7470
75- private _renderDescription ( description : string | undefined , x : number ) {
71+ private _renderDescription ( description : string | undefined ) {
7672 if ( ! description ) return "" ;
77- return renderBox ( truncateMultilineText ( description , descriptionWidth - borderWidth , descriptionHeight ) , descriptionWidth , x ) ;
73+ return renderBox ( truncateMultilineText ( description , descriptionWidth - borderWidth , descriptionHeight ) , descriptionWidth ) ;
7874 }
7975
80- private _descriptionRows ( description : string | undefined ) {
81- if ( ! description ) return 0 ;
82- return truncateMultilineText ( description , descriptionWidth - borderWidth , descriptionHeight ) . length ;
83- }
84-
85- private _renderSuggestions ( suggestions : Suggestion [ ] , activeSuggestionIdx : number , x : number ) {
76+ private _renderSuggestions ( suggestions : Suggestion [ ] , activeSuggestionIdx : number ) {
8677 return renderBox (
8778 suggestions . map ( ( suggestion , idx ) => {
8879 const suggestionText = `${ suggestion . icon } ${ suggestion . name } ` ;
8980 const truncatedSuggestion = truncateText ( suggestionText , suggestionWidth - 2 ) ;
9081 return idx == activeSuggestionIdx ? chalk . bgHex ( activeSuggestionBackgroundColor ) ( truncatedSuggestion ) : truncatedSuggestion ;
9182 } ) ,
9283 suggestionWidth ,
93- x ,
9484 ) ;
9585 }
9686
97- validate ( suggestion : SuggestionsSequence ) : SuggestionsSequence {
98- const commandText = this . #term. getCommandState ( ) . commandText ;
99- return ! commandText ? { data : "" , rows : 0 } : suggestion ;
87+ private _calculatePadding ( description : string ) : { padding : number ; swapDescription : boolean } {
88+ const wrappedPadding = this . #term. getCursorState ( ) . cursorX % this . #term. cols ;
89+ const maxPadding = description . length !== 0 ? this . #term. cols - suggestionWidth - descriptionWidth : this . #term. cols - suggestionWidth ;
90+ const swapDescription = wrappedPadding > maxPadding && description . length !== 0 ;
91+ const swappedPadding = swapDescription ? Math . max ( wrappedPadding - descriptionWidth , 0 ) : wrappedPadding ;
92+ const padding = Math . min ( Math . min ( wrappedPadding , swappedPadding ) , maxPadding ) ;
93+ return { padding, swapDescription } ;
10094 }
10195
102- async render ( remainingLines : number ) : Promise < SuggestionsSequence > {
103- await this . _loadSuggestions ( ) ;
96+ private _calculateRowPadding ( padding : number , swapDescription : boolean , suggestionContent ?: string , descriptionContent ?: string ) : number {
97+ if ( swapDescription ) {
98+ return descriptionContent == null ? padding + descriptionWidth : padding ;
99+ }
100+ return suggestionContent == null ? padding + suggestionWidth : padding ;
101+ }
102+
103+ async exec ( ) : Promise < void > {
104+ return await this . _loadSuggestions ( ) ;
105+ }
106+
107+ render ( direction : "above" | "below" ) : ISTermPatch [ ] {
104108 if ( ! this . #suggestBlob) {
105- return { data : "" , rows : 0 } ;
109+ return [ ] ;
106110 }
107111 const { suggestions, argumentDescription } = this . #suggestBlob;
108112
109113 const page = Math . min ( Math . floor ( this . #activeSuggestionIdx / maxSuggestions ) + 1 , Math . floor ( suggestions . length / maxSuggestions ) + 1 ) ;
110114 const pagedSuggestions = suggestions . filter ( ( _ , idx ) => idx < page * maxSuggestions && idx >= ( page - 1 ) * maxSuggestions ) ;
111115 const activePagedSuggestionIndex = this . #activeSuggestionIdx % maxSuggestions ;
112116 const activeDescription = pagedSuggestions . at ( activePagedSuggestionIndex ) ?. description || argumentDescription || "" ;
113-
114- const wrappedPadding = this . #term. getCursorState ( ) . cursorX % this . #term. cols ;
115- const maxPadding = activeDescription . length !== 0 ? this . #term. cols - suggestionWidth - descriptionWidth : this . #term. cols - suggestionWidth ;
116- const swapDescription = wrappedPadding > maxPadding && activeDescription . length !== 0 ;
117- const swappedPadding = swapDescription ? Math . max ( wrappedPadding - descriptionWidth , 0 ) : wrappedPadding ;
118- const clampedLeftPadding = Math . min ( Math . min ( wrappedPadding , swappedPadding ) , maxPadding ) ;
117+ const { swapDescription, padding } = this . _calculatePadding ( activeDescription ) ;
119118
120119 if ( suggestions . length <= this . #activeSuggestionIdx) {
121120 this . #activeSuggestionIdx = Math . max ( suggestions . length - 1 , 0 ) ;
122121 }
123122
124123 if ( pagedSuggestions . length == 0 ) {
125124 if ( argumentDescription != null ) {
126- return {
127- data :
128- ansi . cursorHide +
129- ansi . cursorUp ( 2 ) +
130- ansi . cursorForward ( clampedLeftPadding ) +
131- this . _renderArgumentDescription ( argumentDescription , clampedLeftPadding ) ,
132- rows : 3 ,
133- } ;
125+ return this . _renderArgumentDescription ( argumentDescription ) . map ( ( row ) => ( { startX : padding , length : descriptionWidth , data : row } ) ) ;
134126 }
135- return { data : "" , rows : 0 } ;
127+ return [ ] ;
136128 }
137-
138- const suggestionRowsUsed = pagedSuggestions . length + borderWidth ;
139- let descriptionRowsUsed = this . _descriptionRows ( activeDescription ) + borderWidth ;
140- let rows = Math . max ( descriptionRowsUsed , suggestionRowsUsed ) ;
141- if ( rows <= remainingLines ) {
142- descriptionRowsUsed = suggestionRowsUsed ;
143- rows = suggestionRowsUsed ;
129+ const descriptionUI = this . _renderDescription ( activeDescription ) ;
130+ const suggestionUI = this . _renderSuggestions ( pagedSuggestions , activePagedSuggestionIndex ) ;
131+ const ui = [ ] ;
132+ const maxRows = Math . max ( descriptionUI . length , suggestionUI . length ) ;
133+ for ( let i = 0 ; i < maxRows ; i ++ ) {
134+ const [ suggestionUIRow , descriptionUIRow ] =
135+ direction == "above"
136+ ? [ suggestionUI [ i - maxRows + suggestionUI . length ] , descriptionUI [ i - maxRows + descriptionUI . length ] ]
137+ : [ suggestionUI [ i ] , descriptionUI [ i ] ] ;
138+
139+ const data = swapDescription ? ( descriptionUIRow ?? "" ) + ( suggestionUIRow ?? "" ) : ( suggestionUIRow ?? "" ) + ( descriptionUIRow ?? "" ) ;
140+ const rowPadding = this . _calculateRowPadding ( padding , swapDescription , suggestionUIRow , descriptionUIRow ) ;
141+
142+ ui . push ( {
143+ startX : rowPadding ,
144+ length : ( suggestionUIRow == null ? 0 : suggestionWidth ) + ( descriptionUIRow == null ? 0 : descriptionWidth ) ,
145+ data : data ,
146+ } ) ;
144147 }
145-
146- const descriptionUI =
147- ansi . cursorUp ( descriptionRowsUsed - 1 ) +
148- ( swapDescription
149- ? this . _renderDescription ( activeDescription , clampedLeftPadding )
150- : this . _renderDescription ( activeDescription , clampedLeftPadding + suggestionWidth ) ) +
151- ansi . cursorDown ( descriptionRowsUsed - 1 ) ;
152- const suggestionUI =
153- ansi . cursorUp ( suggestionRowsUsed - 1 ) +
154- ( swapDescription
155- ? this . _renderSuggestions ( pagedSuggestions , activePagedSuggestionIndex , clampedLeftPadding + descriptionWidth )
156- : this . _renderSuggestions ( pagedSuggestions , activePagedSuggestionIndex , clampedLeftPadding ) ) +
157- ansi . cursorDown ( suggestionRowsUsed - 1 ) ;
158-
159- const ui = swapDescription ? descriptionUI + suggestionUI : suggestionUI + descriptionUI ;
160- return {
161- data : ansi . cursorHide + ansi . cursorForward ( clampedLeftPadding ) + ui + ansi . cursorShow ,
162- rows,
163- } ;
148+ return ui ;
164149 }
165150
166151 update ( keyPress : KeyPress ) : boolean {
0 commit comments