@@ -6,11 +6,44 @@ import (
66 "fmt"
77 "os"
88 "strings"
9+ "time"
910)
1011
11- func promptInput (label string ) (string , error ) {
12+ const (
13+ colorReset = "\033 [0m"
14+ colorDim = "\033 [2m"
15+ colorAccent = "\033 [36m"
16+ colorWarn = "\033 [33m"
17+ colorBold = "\033 [1m"
18+ )
19+
20+ func supportsColor () bool {
21+ if os .Getenv ("NO_COLOR" ) != "" {
22+ return false
23+ }
24+ term := strings .TrimSpace (os .Getenv ("TERM" ))
25+ return term != "" && term != "dumb"
26+ }
27+
28+ func style (text , color string ) string {
29+ if ! supportsColor () || color == "" {
30+ return text
31+ }
32+ return color + text + colorReset
33+ }
34+
35+ func promptInput (label , hint , errMsg string ) (string , error ) {
36+ clearScreen ()
37+ fmt .Println (style (label , colorAccent ))
38+ if hint != "" {
39+ fmt .Println (style (hint , colorDim ))
40+ }
41+ if errMsg != "" {
42+ fmt .Println (style (errMsg , colorWarn ))
43+ }
44+ fmt .Println ()
45+ fmt .Print (style ("> " , colorAccent ))
1246 reader := bufio .NewReader (os .Stdin )
13- fmt .Printf ("%s: " , label )
1447 value , err := reader .ReadString ('\n' )
1548 if err != nil {
1649 return "" , err
@@ -19,6 +52,9 @@ func promptInput(label string) (string, error) {
1952}
2053
2154func selectOption (title string , options []string ) (int , error ) {
55+ if len (options ) == 0 {
56+ return 0 , errors .New ("keine Auswahloptionen verfügbar" )
57+ }
2258 fd := int (os .Stdin .Fd ())
2359 state , err := makeRaw (fd )
2460 if err != nil {
@@ -27,7 +63,7 @@ func selectOption(title string, options []string) (int, error) {
2763 defer restore (fd , state )
2864
2965 selected := 0
30- renderMenu (title , options , selected )
66+ renderMenu (title , options , selected , "" )
3167
3268 reader := bufio .NewReader (os .Stdin )
3369 for {
@@ -39,6 +75,16 @@ func selectOption(title string, options []string) (int, error) {
3975 switch b {
4076 case 3 :
4177 return 0 , errors .New ("abgebrochen" )
78+ case 'w' , 'k' :
79+ if selected > 0 {
80+ selected --
81+ }
82+ renderMenu (title , options , selected , "" )
83+ case 's' , 'j' :
84+ if selected < len (options )- 1 {
85+ selected ++
86+ }
87+ renderMenu (title , options , selected , "" )
4288 case 13 , 10 :
4389 clearScreen ()
4490 return selected , nil
@@ -47,17 +93,29 @@ func selectOption(title string, options []string) (int, error) {
4793 if err != nil {
4894 return 0 , err
4995 }
50- if seq == "[A" {
96+ if seq == "[A" || seq == "OA" {
5197 if selected > 0 {
5298 selected --
5399 }
54100 }
55- if seq == "[B" {
101+ if seq == "[B" || seq == "OB" {
56102 if selected < len (options )- 1 {
57103 selected ++
58104 }
59105 }
60- renderMenu (title , options , selected )
106+ renderMenu (title , options , selected , "" )
107+ case 0 , 224 :
108+ code , err := reader .ReadByte ()
109+ if err != nil {
110+ return 0 , err
111+ }
112+ if code == 72 && selected > 0 {
113+ selected --
114+ }
115+ if code == 80 && selected < len (options )- 1 {
116+ selected ++
117+ }
118+ renderMenu (title , options , selected , "" )
61119 }
62120 }
63121}
@@ -71,24 +129,85 @@ func readEscapeSequence(reader *bufio.Reader) (string, error) {
71129 if err != nil {
72130 return "" , err
73131 }
132+ if b1 == '[' && b2 >= '0' && b2 <= '9' {
133+ b3 , err := reader .ReadByte ()
134+ if err != nil {
135+ return "" , err
136+ }
137+ return string ([]byte {b1 , b2 , b3 }), nil
138+ }
74139 return string ([]byte {b1 , b2 }), nil
75140}
76141
77- func renderMenu (title string , options []string , selected int ) {
142+ func renderMenu (title string , options []string , selected int , hint string ) {
78143 clearScreen ()
79- fmt .Println (title )
144+ fmt .Println (style ( title , colorAccent ) )
80145 fmt .Println ()
81146 for i , option := range options {
82147 if i == selected {
83- fmt .Printf ("> %s\n " , option )
148+ fmt .Printf ("%s %s\n " , style ( ">" , colorAccent + colorBold ), style ( option , colorBold ) )
84149 continue
85150 }
86151 fmt .Printf (" %s\n " , option )
87152 }
88153 fmt .Println ()
89- fmt .Println ("Pfeiltasten zum Navigieren, Enter zum Bestätigen" )
154+ if hint == "" {
155+ hint = "Pfeiltasten (oder W/S), Enter zum Bestätigen"
156+ }
157+ fmt .Println (style (hint , colorDim ))
90158}
91159
92160func clearScreen () {
93161 fmt .Print ("\033 [H\033 [2J" )
94162}
163+
164+ func renderTextPage (title , content string ) {
165+ lines := strings .Split (content , "\n " )
166+ renderPage (title , lines )
167+ }
168+
169+ func renderPage (title string , lines []string ) {
170+ clearScreen ()
171+ fmt .Println (style (title , colorAccent ))
172+ fmt .Println ()
173+ for _ , line := range lines {
174+ if strings .TrimSpace (line ) == "" {
175+ fmt .Println ()
176+ continue
177+ }
178+ fmt .Println (line )
179+ }
180+ }
181+
182+ func renderSpinnerPage (title , message , frame string ) {
183+ renderPage (title , []string {fmt .Sprintf ("%s %s" , style (message , colorDim ), style (frame , colorAccent ))})
184+ }
185+
186+ func withSpinner (title , message string , tick time.Duration , action func () (string , error )) (string , error ) {
187+ resultCh := make (chan struct {
188+ result string
189+ err error
190+ }, 1 )
191+ go func () {
192+ result , err := action ()
193+ resultCh <- struct {
194+ result string
195+ err error
196+ }{result : result , err : err }
197+ }()
198+
199+ frames := []string {"⠋" , "⠙" , "⠹" , "⠸" , "⠼" , "⠴" , "⠦" , "⠧" , "⠇" , "⠏" }
200+ ticker := time .NewTicker (tick )
201+ defer ticker .Stop ()
202+
203+ frame := 0
204+ for {
205+ select {
206+ case res := <- resultCh :
207+ return res .result , res .err
208+ case <- ticker .C :
209+ renderSpinnerPage (title , message , frames [frame ])
210+ frame = (frame + 1 ) % len (frames )
211+ }
212+ }
213+ }
0 commit comments