66 "fmt"
77 "os"
88 "os/signal"
9+ "strings"
910 "sync"
1011 "syscall"
1112
@@ -20,10 +21,13 @@ type app struct {
2021 stdinChannel chan byte
2122 fd int
2223 prevState * term.State
23- cmdLine []byte
2424 content []string
2525 commandSignature string
2626 once sync.Once
27+ ansiMachine stdinAnsi
28+ history [][]byte
29+ historyCursor int
30+ scrollOffset int
2731}
2832
2933func (src * app ) Write (bytes []byte ) (int , error ) {
@@ -47,15 +51,29 @@ func (src *app) ListenStdin(context context.Context) {
4751 }
4852 }
4953}
54+
55+ func (src * app ) setHistoryTail (b []byte ) {
56+ src .history [len (src .history )- 1 ] = b
57+ }
58+
59+ func (src * app ) historyTail () []byte {
60+ return src .history [len (src .history )- 1 ]
61+ }
62+
63+ func (src * app ) currentCmd () []byte {
64+ return src .history [src .historyCursor ]
65+ }
66+
5067func (src * app ) Submissions () <- chan string {
5168 return src .submissionChan
5269}
5370
54- func visibleContent (content []string , height int ) []string {
71+ func visibleContent (content []string , height int , scrollOffset int ) []string {
5572 currentRows := len (content )
73+ endRow := max (currentRows - scrollOffset , 0 )
5674 // ngl i forgot why we adding plus 1.. oh well
57- startRow := max (currentRows - (height + 1 ), 0 )
58- return content [startRow :]
75+ startRow := max (endRow - (height + 1 ), 0 )
76+ return content [startRow :endRow ]
5977}
6078
6179func formatCommandEcho (cmd string ) string {
@@ -70,7 +88,7 @@ func (src *app) DrawContent(finalDraw bool) error {
7088 if ! finalDraw {
7189 fmt .Print (ansi .ClearScreen + ansi .CursorHome )
7290 }
73- drawableRows := visibleContent (src .content , height )
91+ drawableRows := visibleContent (src .content , height , src . scrollOffset )
7492 for i := range drawableRows {
7593 fmt .Print (drawableRows [i ])
7694 }
@@ -79,11 +97,18 @@ func (src *app) DrawContent(finalDraw bool) error {
7997 return nil
8098 }
8199 ansi .MoveCursorTo (height , 0 )
100+ if src .scrollOffset > 0 {
101+ fmt .Print (ansi .Format (fmt .Sprintf ("[↑ %d] " , src .scrollOffset ), ansi .Yellow , ansi .Bold ))
102+ }
82103 fmt .Printf (ansi .Format ("%v> " , ansi .Blue ), src .commandSignature )
83- fmt .Print (string (src .cmdLine ))
104+ fmt .Print (string (src .currentCmd () ))
84105 return nil
85106}
86107
108+ func (src * app ) traverseHistory (delta int ) {
109+ src .historyCursor = clamp (0 , src .historyCursor + delta , len (src .history )- 1 )
110+ }
111+
87112func (src * app ) Run (context context.Context ) error {
88113
89114 // this could be an argument but i aint feeling yet
@@ -122,19 +147,57 @@ func (src *app) Run(context context.Context) error {
122147 case <- context .Done ():
123148 return nil
124149 case newStdinInput := <- src .stdinChannel :
125- newCmd , isSubmission := constructCmdLine (newStdinInput , src .cmdLine )
126- if isSubmission {
127- src .content = append (src .content , formatCommandEcho (string (newCmd )))
128- src .cmdLine = []byte {}
129- src .submissionChan <- string (newCmd )
130- } else {
131- src .cmdLine = newCmd
150+ ansiSeq , ansiState := src .ansiMachine .handle (newStdinInput )
151+ // src.content = append(src.content, fmt.Sprintf("ansi machine handling %v, at state %v\n", ansiSeq, ansiState))
152+ switch ansiState {
153+ case ansiStateIdle :
154+ // no ansi sequence ongoing so its just presentation bytes
155+ newCmd , isSubmission := constructCmdLine (newStdinInput , src .currentCmd ())
156+ if isSubmission {
157+ src .content = append (src .content , formatCommandEcho (string (newCmd )))
158+ if len (src .historyTail ()) > 0 {
159+ src .history = append (src .history , []byte {})
160+ }
161+ src .scrollOffset = 0
162+ src .submissionChan <- string (newCmd )
163+ } else {
164+ src .setHistoryTail (newCmd )
165+ }
166+ // feel a bit awkward doing this every input stroke, we can get back to it later
167+ src .historyCursor = len (src .history ) - 1
168+ case ansiStateCSITerm :
169+ switch string (ansiSeq ) {
170+ case ansi .ArrowKeyUp :
171+ src .traverseHistory (- 1 )
172+ case ansi .ArrowKeyDown :
173+ src .traverseHistory (1 )
174+ case ansi .PageUpKey :
175+ if _ , h , err := term .GetSize (src .fd ); err == nil {
176+ maxOffset := max (len (src .content )- (h + 1 ), 0 )
177+ // substract one cuz we need to account for persistent command line
178+ src .scrollOffset = min (src .scrollOffset + h - 1 , maxOffset )
179+ } else {
180+ // TODO: do something here idk
181+ }
182+ case ansi .PageDownKey :
183+ if _ , h , err := term .GetSize (src .fd ); err == nil {
184+ // ditto subtraction
185+ src .scrollOffset = max (src .scrollOffset - (h - 1 ), 0 )
186+ }
187+ default :
188+ // src.content = append(src.content, fmt.Sprintf("unhandled csi %v, %v\n", strconv.Itoa(int(ansiState)), ansiSeq))
189+ }
190+ default :
191+ // src.content = append(src.content, fmt.Sprintf("unhandled state %v, %v\n", strconv.Itoa(int(ansiState)), ansiSeq))
132192 }
193+
133194 if err := src .DrawContent (false ); err != nil {
134195 return err
135196 }
136197 case newDisplayInput := <- src .DisplayChannel :
137- src .content = append (src .content , newDisplayInput )
198+ for line := range strings .Lines (newDisplayInput ) {
199+ src .content = append (src .content , line )
200+ }
138201 if err := src .DrawContent (false ); err != nil {
139202 return err
140203 }
@@ -164,5 +227,8 @@ func CreateApp(commandSignature string) *app {
164227 submissionChan : submissionChan ,
165228 content : make ([]string , 0 ),
166229 commandSignature : commandSignature ,
230+ ansiMachine : newStdinAnsi (),
231+ history : [][]byte {[]byte {}},
232+ historyCursor : 0 ,
167233 }
168234}
0 commit comments