package console
import (
"bytes"
"errors"
"fmt"
"os"
"regexp"
"strings"
"unicode"
"unicode/utf8"
"github.com/rsteube/carapace"
"github.com/rsteube/carapace/pkg/style"
completer "github.com/rsteube/carapace/pkg/x"
"github.com/rsteube/carapace/pkg/xdg"
"github.com/reeflective/readline"
)
func (c *Console ) complete (line []rune , pos int ) readline .Completions {
menu := c .activeMenu ()
carapace .Gen (menu .Command )
args , prefixComp , prefixLine := splitArgs (line , pos )
args = append ([]string {c .name , "_carapace" }, args ...)
completions , err := completer .Complete (menu .Command , args ...)
raw := make ([]readline .Completion , len (completions .Values ))
for idx , val := range completions .Values .Decolor () {
raw [idx ] = readline .Completion {
Value : unescapeValue (prefixComp , prefixLine , val .Value ),
Display : val .Display ,
Description : val .Description ,
Style : val .Style ,
Tag : val .Tag ,
}
if !completions .Nospace .Matches (val .Value ) {
raw [idx ].Value = val .Value + " "
}
}
comps := readline .CompleteRaw (raw )
comps = comps .Usage ("%s" , completions .Usage )
comps = c .justifyCommandComps (comps )
if err != nil {
comps = readline .CompleteMessage ("failed to load config: " + err .Error())
}
for _ , msg := range completions .Messages .Get () {
comps = comps .Merge (readline .CompleteMessage (msg ))
}
suffixes , err := completions .Nospace .MarshalJSON ()
if len (suffixes ) > 0 && err == nil {
comps = comps .NoSpace ([]rune (string (suffixes ))...)
}
comps = comps .Prefix (prefixComp )
comps .PREFIX = prefixLine
completer .ClearStorage ()
menu .resetPreRun ()
menu .hideFilteredCommands (menu .Command )
return comps
}
func (c *Console ) justifyCommandComps (comps readline .Completions ) readline .Completions {
justified := []string {}
comps .EachValue (func (comp readline .Completion ) readline .Completion {
if !strings .HasSuffix (comp .Tag , "commands" ) {
return comp
}
justified = append (justified , comp .Tag )
return comp
})
if len (justified ) > 0 {
return comps .JustifyDescriptions (justified ...)
}
return comps
}
func (c *Console ) defaultStyleConfig () {
if dir , err := xdg .UserConfigDir (); err == nil {
_ , err := os .Stat (fmt .Sprintf ("%v/carapace/styles.json" , dir ))
if err == nil {
return
}
}
for i := 1 ; i < 13 ; i ++ {
styleStr := fmt .Sprintf ("carapace.Highlight%d" , i )
style .Set (styleStr , "bright-white" )
}
style .Set ("carapace.FlagArg" , "bright-white" )
style .Set ("carapace.FlagMultiArg" , "bright-white" )
style .Set ("carapace.FlagNoArg" , "bright-white" )
style .Set ("carapace.FlagOptArg" , "bright-white" )
}
func splitArgs(line []rune , pos int ) (args []string , prefixComp , prefixLine string ) {
line = line [:pos ]
line = []rune (strip (string (line )))
args , remain , err := splitCompWords (string (line ))
mustComplete , args , remain := mustComplete (line , args , remain , err )
if mustComplete {
return sanitizeArgs (args ), "" , remain
}
arg , prefixComp , prefixLine := adjustQuotedPrefix (remain , err )
args = append (args , arg )
return sanitizeArgs (args ), prefixComp , prefixLine
}
func mustComplete(line []rune , args []string , remain string , err error ) (bool , []string , string ) {
dummyArg := ""
if len (args ) == 0 || len (line ) == 0 {
return true , append (args , dummyArg ), remain
}
if err != nil {
return false , args , remain
}
lastChar := line [len (line )-1 ]
if remain == "" && unicode .IsSpace (lastChar ) {
if strings .HasSuffix (string (line ), "\\ " ) {
return true , args , args [len (args )-1 ]
}
return true , append (args , dummyArg ), remain
}
return true , args , remain
}
func adjustQuotedPrefix(remain string , err error ) (arg , comp , line string ) {
arg = remain
switch {
case errors .Is (err , errUnterminatedDoubleQuote ):
comp = "\""
line = comp + arg
case errors .Is (err , errUnterminatedSingleQuote ):
comp = "'"
line = comp + arg
case errors .Is (err , errUnterminatedEscape ):
arg = strings .ReplaceAll (arg , "\\" , "" )
}
return arg , comp , line
}
func sanitizeArgs(args []string ) (sanitized []string ) {
for _ , arg := range args {
arg = replacer .Replace (arg )
sanitized = append (sanitized , arg )
}
return sanitized
}
func unescapeValue(prefixComp , prefixLine , val string ) string {
quoted := strings .HasPrefix (prefixLine , "\"" ) ||
strings .HasPrefix (prefixLine , "'" )
if quoted {
val = strings .ReplaceAll (val , "\\ " , " " )
}
return val
}
func splitCompWords(input string ) (words []string , remainder string , err error ) {
var buf bytes .Buffer
words = make ([]string , 0 )
for len (input ) > 0 {
char , read := utf8 .DecodeRuneInString (input )
if strings .ContainsRune (splitChars , char ) {
input = input [read :]
continue
} else if char == escapeChar {
next := input [read :]
if len (next ) == 0 {
remainder = string (escapeChar )
err = errUnterminatedEscape
return words , remainder , err
}
c2 , l2 := utf8 .DecodeRuneInString (next )
if c2 == '\n' {
input = next [l2 :]
continue
}
}
var word string
word , input , err = splitCompWord (input , &buf )
if err != nil {
return words , word + input , err
}
words = append (words , word )
}
return words , remainder , nil
}
func splitCompWord(input string , buf *bytes .Buffer ) (word string , remainder string , err error ) {
buf .Reset ()
raw :
{
cur := input
for len (cur ) > 0 {
char , read := utf8 .DecodeRuneInString (cur )
cur = cur [read :]
switch {
case char == singleChar :
buf .WriteString (input [0 : len (input )-len (cur )-read ])
input = cur
goto single
case char == doubleChar :
buf .WriteString (input [0 : len (input )-len (cur )-read ])
input = cur
goto double
case char == escapeChar :
buf .WriteString (input [0 : len (input )-len (cur )-read ])
buf .WriteRune (char )
input = cur
goto escape
case strings .ContainsRune (splitChars , char ):
buf .WriteString (input [0 : len (input )-len (cur )-read ])
return buf .String (), cur , nil
}
}
if len (input ) > 0 {
buf .WriteString (input )
input = ""
}
goto done
}
escape :
{
if len (input ) == 0 {
input = buf .String () + input
return "" , input , errUnterminatedEscape
}
c , l := utf8 .DecodeRuneInString (input )
if c != '\n' {
buf .WriteString (input [:l ])
}
input = input [l :]
}
goto raw
single :
{
i := strings .IndexRune (input , singleChar )
if i == -1 {
return "" , input , errUnterminatedSingleQuote
}
buf .WriteString (input [0 :i ])
input = input [i +1 :]
goto raw
}
double :
{
cur := input
for len (cur ) > 0 {
c , read := utf8 .DecodeRuneInString (cur )
cur = cur [read :]
switch c {
case doubleChar :
buf .WriteString (input [0 : len (input )-len (cur )-read ])
input = cur
goto raw
case escapeChar :
char2 , l2 := utf8 .DecodeRuneInString (cur )
cur = cur [l2 :]
if strings .ContainsRune (doubleEscapeChars , char2 ) {
buf .WriteString (input [0 : len (input )-len (cur )-read -l2 ])
if char2 != '\n' {
buf .WriteRune (char2 )
}
input = cur
}
}
}
return "" , input , errUnterminatedDoubleQuote
}
done :
return buf .String (), input , nil
}
const ansi = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))"
var re = regexp .MustCompile (ansi )
func strip(str string ) string {
return re .ReplaceAllString (str , "" )
}
var replacer = strings .NewReplacer (
"\n" , ` ` ,
"\t" , ` ` ,
"\\ " , " " ,
)
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .