11package console
22
33import (
4- "bytes"
5- "errors"
6- "fmt"
7- "os"
8- "regexp"
94 "strings"
10- "unicode"
11- "unicode/utf8"
125
6+ "github.com/reeflective/readline"
137 "github.com/rsteube/carapace"
148 "github.com/rsteube/carapace/pkg/style"
159 completer "github.com/rsteube/carapace/pkg/x"
16- "github.com/rsteube/carapace/pkg/xdg"
1710
18- "github.com/reeflective/readline"
11+ "github.com/reeflective/console/internal/completion"
12+ "github.com/reeflective/console/internal/line"
1913)
2014
21- func (c * Console ) complete (line []rune , pos int ) readline.Completions {
15+ func (c * Console ) complete (input []rune , pos int ) readline.Completions {
2216 menu := c .activeMenu ()
2317
2418 // Ensure the carapace library is called so that the function
@@ -27,7 +21,7 @@ func (c *Console) complete(line []rune, pos int) readline.Completions {
2721
2822 // Split the line as shell words, only using
2923 // what the right buffer (up to the cursor)
30- args , prefixComp , prefixLine := splitArgs ( line , pos )
24+ args , prefixComp , prefixLine := completion . SplitArgs ( input , pos )
3125
3226 // Prepare arguments for the carapace completer
3327 // (we currently need those two dummies for avoiding a panic).
@@ -42,7 +36,7 @@ func (c *Console) complete(line []rune, pos int) readline.Completions {
4236
4337 for idx , val := range completions .Values {
4438 raw [idx ] = readline.Completion {
45- Value : unescapeValue (prefixComp , prefixLine , val .Value ),
39+ Value : line . UnescapeValue (prefixComp , prefixLine , val .Value ),
4640 Display : val .Display ,
4741 Description : val .Description ,
4842 Style : style .SGR (val .Style ),
@@ -112,280 +106,32 @@ func (c *Console) justifyCommandComps(comps readline.Completions) readline.Compl
112106 return comps
113107}
114108
115- func (c * Console ) defaultStyleConfig () {
116- // If carapace config file is found, just return.
117- if dir , err := xdg .UserConfigDir (); err == nil {
118- _ , err := os .Stat (fmt .Sprintf ("%v/carapace/styles.json" , dir ))
119- if err == nil {
120- return
121- }
122- }
123-
124- // Overwrite all default styles for color
125- for i := 1 ; i < 13 ; i ++ {
126- styleStr := fmt .Sprintf ("carapace.Highlight%d" , i )
127- style .Set (styleStr , "bright-white" )
128- }
129-
130- // Overwrite all default styles for flags
131- style .Set ("carapace.FlagArg" , "bright-white" )
132- style .Set ("carapace.FlagMultiArg" , "bright-white" )
133- style .Set ("carapace.FlagNoArg" , "bright-white" )
134- style .Set ("carapace.FlagOptArg" , "bright-white" )
135- }
136-
137- // splitArgs splits the line in valid words, prepares them in various ways before calling
138- // the completer with them, and also determines which parts of them should be used as
139- // prefixes, in the completions and/or in the line.
140- func splitArgs (line []rune , pos int ) (args []string , prefixComp , prefixLine string ) {
141- line = line [:pos ]
142-
143- // Remove all colors from the string
144- line = []rune (strip (string (line )))
145-
146- // Split the line as shellwords, return them if all went fine.
147- args , remain , err := splitCompWords (string (line ))
148-
149- // We might have either no error and args, or no error and
150- // the cursor ready to complete a new word (last character
151- // in line is a space).
152- // In some of those cases we append a single dummy argument
153- // for the completer to understand we want a new word comp.
154- mustComplete , args , remain := mustComplete (line , args , remain , err )
155- if mustComplete {
156- return sanitizeArgs (args ), "" , remain
157- }
158-
159- // But the completion candidates themselves might need slightly
160- // different prefixes, for an optimal completion experience.
161- arg , prefixComp , prefixLine := adjustQuotedPrefix (remain , err )
162-
163- // The remainder is everything following the open charater.
164- // Pass it as is to the carapace completion engine.
165- args = append (args , arg )
166-
167- return sanitizeArgs (args ), prefixComp , prefixLine
168- }
169-
170- func mustComplete (line []rune , args []string , remain string , err error ) (bool , []string , string ) {
171- dummyArg := ""
172-
173- // Empty command line, complete the root command.
174- if len (args ) == 0 || len (line ) == 0 {
175- return true , append (args , dummyArg ), remain
176- }
177-
178- // If we have an error, we must handle it later.
109+ // highlightSyntax - Entrypoint to all input syntax highlighting in the Wiregost console.
110+ func (c * Console ) highlightSyntax (input []rune ) string {
111+ // Split the line as shellwords
112+ args , unprocessed , err := line .Split (string (input ), true )
179113 if err != nil {
180- return false , args , remain
181- }
182-
183- lastChar := line [len (line )- 1 ]
184-
185- // No remain and a trailing space means we want to complete
186- // for the next word, except when this last space was escaped.
187- if remain == "" && unicode .IsSpace (lastChar ) {
188- if strings .HasSuffix (string (line ), "\\ " ) {
189- return true , args , args [len (args )- 1 ]
190- }
191-
192- return true , append (args , dummyArg ), remain
193- }
194-
195- // Else there is a character under the cursor, which means we are
196- // in the middle/at the end of a posentially completed word.
197- return true , args , remain
198- }
199-
200- func adjustQuotedPrefix (remain string , err error ) (arg , comp , line string ) {
201- arg = remain
202-
203- switch {
204- case errors .Is (err , errUnterminatedDoubleQuote ):
205- comp = "\" "
206- line = comp + arg
207- case errors .Is (err , errUnterminatedSingleQuote ):
208- comp = "'"
209- line = comp + arg
210- case errors .Is (err , errUnterminatedEscape ):
211- arg = strings .ReplaceAll (arg , "\\ " , "" )
212- }
213-
214- return arg , comp , line
215- }
216-
217- // sanitizeArg unescapes a restrained set of characters.
218- func sanitizeArgs (args []string ) (sanitized []string ) {
219- for _ , arg := range args {
220- arg = replacer .Replace (arg )
221- sanitized = append (sanitized , arg )
222- }
223-
224- return sanitized
225- }
226-
227- // when the completer has returned us some completions, we sometimes
228- // needed to post-process them a little before passing them to our shell.
229- func unescapeValue (prefixComp , prefixLine , val string ) string {
230- quoted := strings .HasPrefix (prefixLine , "\" " ) ||
231- strings .HasPrefix (prefixLine , "'" )
232-
233- if quoted {
234- val = strings .ReplaceAll (val , "\\ " , " " )
235- }
236-
237- return val
238- }
239-
240- // split has been copied from go-shellquote and slightly modified so as to also
241- // return the remainder when the parsing failed because of an unterminated quote.
242- func splitCompWords (input string ) (words []string , remainder string , err error ) {
243- var buf bytes.Buffer
244- words = make ([]string , 0 )
245-
246- for len (input ) > 0 {
247- // skip any splitChars at the start
248- char , read := utf8 .DecodeRuneInString (input )
249- if strings .ContainsRune (splitChars , char ) {
250- input = input [read :]
251- continue
252- } else if char == escapeChar {
253- // Look ahead for escaped newline so we can skip over it
254- next := input [read :]
255- if len (next ) == 0 {
256- remainder = string (escapeChar )
257- err = errUnterminatedEscape
258-
259- return words , remainder , err
260- }
261-
262- c2 , l2 := utf8 .DecodeRuneInString (next )
263- if c2 == '\n' {
264- input = next [l2 :]
265- continue
266- }
267- }
268-
269- var word string
270-
271- word , input , err = splitCompWord (input , & buf )
272- if err != nil {
273- return words , word + input , err
274- }
275-
276- words = append (words , word )
277- }
278-
279- return words , remainder , nil
280- }
281-
282- // splitWord has been modified to return the remainder of the input (the part that has not been
283- // added to the buffer) even when an error is returned.
284- func splitCompWord (input string , buf * bytes.Buffer ) (word string , remainder string , err error ) {
285- buf .Reset ()
286-
287- raw:
288- {
289- cur := input
290- for len (cur ) > 0 {
291- char , read := utf8 .DecodeRuneInString (cur )
292- cur = cur [read :]
293- switch {
294- case char == singleChar :
295- buf .WriteString (input [0 : len (input )- len (cur )- read ])
296- input = cur
297- goto single
298- case char == doubleChar :
299- buf .WriteString (input [0 : len (input )- len (cur )- read ])
300- input = cur
301- goto double
302- case char == escapeChar :
303- buf .WriteString (input [0 : len (input )- len (cur )- read ])
304- buf .WriteRune (char )
305- input = cur
306- goto escape
307- case strings .ContainsRune (splitChars , char ):
308- buf .WriteString (input [0 : len (input )- len (cur )- read ])
309- return buf .String (), cur , nil
310- }
311- }
312- if len (input ) > 0 {
313- buf .WriteString (input )
314- input = ""
315- }
316- goto done
317- }
318-
319- escape:
320- {
321- if len (input ) == 0 {
322- input = buf .String () + input
323- return "" , input , errUnterminatedEscape
324- }
325- c , l := utf8 .DecodeRuneInString (input )
326- if c != '\n' {
327- buf .WriteString (input [:l ])
328- }
329- input = input [l :]
114+ args = append (args , unprocessed )
330115 }
331116
332- goto raw
117+ done := make ([]string , 0 ) // List of processed words, append to
118+ remain := args // List of words to process, draw from
119+ trimmed := line .TrimSpaces (remain ) // Match stuff against trimmed words
333120
334- single:
335- {
336- i := strings .IndexRune (input , singleChar )
337- if i == - 1 {
338- return "" , input , errUnterminatedSingleQuote
339- }
340- buf .WriteString (input [0 :i ])
341- input = input [i + 1 :]
342- goto raw
121+ // Highlight the root command when found.
122+ cmd , _ , _ := c .activeMenu ().Find (trimmed )
123+ if cmd != nil {
124+ done , remain = line .HighlightCommand (done , args , c .activeMenu ().Command , c .cmdHighlight )
343125 }
344126
345- double:
346- {
347- cur := input
348- for len (cur ) > 0 {
349- c , read := utf8 .DecodeRuneInString (cur )
350- cur = cur [read :]
351- switch c {
352- case doubleChar :
353- buf .WriteString (input [0 : len (input )- len (cur )- read ])
354- input = cur
355- goto raw
356- case escapeChar :
357- // bash only supports certain escapes in double-quoted strings
358- char2 , l2 := utf8 .DecodeRuneInString (cur )
359- cur = cur [l2 :]
360- if strings .ContainsRune (doubleEscapeChars , char2 ) {
361- buf .WriteString (input [0 : len (input )- len (cur )- read - l2 ])
127+ // Highlight command flags
128+ done , remain = line .HighlightCommandFlags (done , remain , c .flagHighlight )
362129
363- if char2 != '\n' {
364- buf .WriteRune (char2 )
365- }
366- input = cur
367- }
368- }
369- }
130+ // Done with everything, add remainind, non-processed words
131+ done = append (done , remain ... )
370132
371- return "" , input , errUnterminatedDoubleQuote
372- }
133+ // Join all words.
134+ highlighted := strings . Join ( done , "" )
373135
374- done:
375- return buf .String (), input , nil
136+ return highlighted
376137}
377-
378- const ansi = "[\u001B \u009B ][[\\ ]()#;?]*(?:(?:(?:[a-zA-Z\\ d]*(?:;[a-zA-Z\\ d]*)*)?\u0007 )|(?:(?:\\ d{1,4}(?:;\\ d{0,4})*)?[\\ dA-PRZcf-ntqry=><~]))"
379-
380- var re = regexp .MustCompile (ansi )
381-
382- // strip removes all ANSI escaped color sequences in a string.
383- func strip (str string ) string {
384- return re .ReplaceAllString (str , "" )
385- }
386-
387- var replacer = strings .NewReplacer (
388- "\n " , ` ` ,
389- "\t " , ` ` ,
390- "\\ " , " " , // User-escaped spaces in words.
391- )
0 commit comments