commands: implement fuzzy completion for commands and options
Change the option to enable fuzzy completion to be fuzzy-complete, since it's no longer only used for folders Signed-off-by: Kt Programs <ktprograms@gmail.com> Acked-by: Koni Marti <koni.marti@gmail.com>
This commit is contained in:
parent
55ae3d2cab
commit
cc172970a0
12 changed files with 90 additions and 78 deletions
|
@ -6,8 +6,8 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
|
"git.sr.ht/~rjarry/aerc/commands"
|
||||||
"git.sr.ht/~rjarry/aerc/models"
|
"git.sr.ht/~rjarry/aerc/models"
|
||||||
"git.sr.ht/~rjarry/aerc/widgets"
|
"git.sr.ht/~rjarry/aerc/widgets"
|
||||||
"git.sr.ht/~sircmpwn/getopt"
|
"git.sr.ht/~sircmpwn/getopt"
|
||||||
|
@ -24,24 +24,43 @@ func (Recover) Aliases() []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Recover) Complete(aerc *widgets.Aerc, args []string) []string {
|
func (Recover) Complete(aerc *widgets.Aerc, args []string) []string {
|
||||||
|
acct := aerc.SelectedAccount()
|
||||||
|
if acct == nil {
|
||||||
|
return make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
// file name of temp file is hard-coded in the NewComposer() function
|
// file name of temp file is hard-coded in the NewComposer() function
|
||||||
files, err := filepath.Glob(
|
files, err := filepath.Glob(
|
||||||
filepath.Join(os.TempDir(), "aerc-compose-*.eml"),
|
filepath.Join(os.TempDir(), "aerc-compose-*.eml"),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []string{}
|
return make([]string, 0)
|
||||||
}
|
}
|
||||||
arg := strings.Join(args, " ")
|
// if nothing is entered yet, return all files
|
||||||
if arg != "" {
|
if len(args) == 0 {
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
if args[0] == "-" {
|
||||||
|
return []string{"-f"}
|
||||||
|
} else if args[0] == "-f" {
|
||||||
|
if len(args) == 1 {
|
||||||
for i, file := range files {
|
for i, file := range files {
|
||||||
files[i] = strings.Join([]string{arg, file}, " ")
|
files[i] = args[0] + " " + file
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return files
|
return files
|
||||||
|
} else {
|
||||||
|
// only accepts one file to recover
|
||||||
|
return commands.FilterList(files, args[1], args[0]+" ", acct.UiConfig().FuzzyComplete)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// only accepts one file to recover
|
||||||
|
return commands.FilterList(files, args[0], "", acct.UiConfig().FuzzyComplete)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Recover) Execute(aerc *widgets.Aerc, args []string) error {
|
func (Recover) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
if len(Recover{}.Complete(aerc, args)) == 0 {
|
// Complete() expects to be passed only the arguments, not including the command name
|
||||||
|
if len(Recover{}.Complete(aerc, args[1:])) == 0 {
|
||||||
return errors.New("No messages to recover.")
|
return errors.New("No messages to recover.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"git.sr.ht/~rjarry/aerc/commands"
|
||||||
"git.sr.ht/~rjarry/aerc/lib/sort"
|
"git.sr.ht/~rjarry/aerc/lib/sort"
|
||||||
"git.sr.ht/~rjarry/aerc/widgets"
|
"git.sr.ht/~rjarry/aerc/widgets"
|
||||||
)
|
)
|
||||||
|
@ -19,6 +20,11 @@ func (Sort) Aliases() []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Sort) Complete(aerc *widgets.Aerc, args []string) []string {
|
func (Sort) Complete(aerc *widgets.Aerc, args []string) []string {
|
||||||
|
acct := aerc.SelectedAccount()
|
||||||
|
if acct == nil {
|
||||||
|
return make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
supportedCriteria := []string{
|
supportedCriteria := []string{
|
||||||
"arrival",
|
"arrival",
|
||||||
"cc",
|
"cc",
|
||||||
|
@ -35,7 +41,7 @@ func (Sort) Complete(aerc *widgets.Aerc, args []string) []string {
|
||||||
last := args[len(args)-1]
|
last := args[len(args)-1]
|
||||||
var completions []string
|
var completions []string
|
||||||
currentPrefix := strings.Join(args, " ") + " "
|
currentPrefix := strings.Join(args, " ") + " "
|
||||||
// if there is a completed criteria then suggest all again or an option
|
// if there is a completed criteria or option then suggest all again
|
||||||
for _, criteria := range append(supportedCriteria, "-r") {
|
for _, criteria := range append(supportedCriteria, "-r") {
|
||||||
if criteria == last {
|
if criteria == last {
|
||||||
for _, criteria := range supportedCriteria {
|
for _, criteria := range supportedCriteria {
|
||||||
|
@ -54,11 +60,8 @@ func (Sort) Complete(aerc *widgets.Aerc, args []string) []string {
|
||||||
return []string{currentPrefix + "-r"}
|
return []string{currentPrefix + "-r"}
|
||||||
}
|
}
|
||||||
// the last item is not complete
|
// the last item is not complete
|
||||||
for _, criteria := range supportedCriteria {
|
completions = commands.FilterList(supportedCriteria, last, currentPrefix,
|
||||||
if strings.HasPrefix(criteria, last) {
|
acct.UiConfig().FuzzyComplete)
|
||||||
completions = append(completions, currentPrefix+criteria)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return completions
|
return completions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
@ -74,12 +73,14 @@ func (cmds *Commands) GetCompletions(aerc *widgets.Aerc, cmd string) []string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nothing entered, list all commands
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
names := cmds.Names()
|
names := cmds.Names()
|
||||||
sort.Strings(names)
|
sort.Strings(names)
|
||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// complete options
|
||||||
if len(args) > 1 || cmd[len(cmd)-1] == ' ' {
|
if len(args) > 1 || cmd[len(cmd)-1] == ' ' {
|
||||||
if cmd, ok := cmds.dict()[args[0]]; ok {
|
if cmd, ok := cmds.dict()[args[0]]; ok {
|
||||||
var completions []string
|
var completions []string
|
||||||
|
@ -101,13 +102,9 @@ func (cmds *Commands) GetCompletions(aerc *widgets.Aerc, cmd string) []string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// complete available commands
|
||||||
names := cmds.Names()
|
names := cmds.Names()
|
||||||
options := make([]string, 0)
|
options := FilterList(names, args[0], "", aerc.SelectedAccount().UiConfig().FuzzyComplete)
|
||||||
for _, name := range names {
|
|
||||||
if strings.HasPrefix(name, args[0]) {
|
|
||||||
options = append(options, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(options) > 0 {
|
if len(options) > 0 {
|
||||||
return options
|
return options
|
||||||
|
@ -116,35 +113,23 @@ func (cmds *Commands) GetCompletions(aerc *widgets.Aerc, cmd string) []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFolders(aerc *widgets.Aerc, args []string) []string {
|
func GetFolders(aerc *widgets.Aerc, args []string) []string {
|
||||||
out := make([]string, 0)
|
|
||||||
acct := aerc.SelectedAccount()
|
acct := aerc.SelectedAccount()
|
||||||
if acct == nil {
|
if acct == nil {
|
||||||
return out
|
return make([]string, 0)
|
||||||
}
|
}
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return acct.Directories().List()
|
return acct.Directories().List()
|
||||||
}
|
}
|
||||||
for _, dir := range acct.Directories().List() {
|
return FilterList(acct.Directories().List(), args[0], "", acct.UiConfig().FuzzyComplete)
|
||||||
if foundInString(dir, args[0], acct.UiConfig().FuzzyFolderComplete) {
|
|
||||||
out = append(out, dir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompletionFromList provides a convenience wrapper for commands to use in the
|
// CompletionFromList provides a convenience wrapper for commands to use in the
|
||||||
// Complete function. It simply matches the items provided in valid
|
// Complete function. It simply matches the items provided in valid
|
||||||
func CompletionFromList(valid []string, args []string) []string {
|
func CompletionFromList(aerc *widgets.Aerc, valid []string, args []string) []string {
|
||||||
out := make([]string, 0)
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return valid
|
return valid
|
||||||
}
|
}
|
||||||
for _, v := range valid {
|
return FilterList(valid, args[0], "", aerc.SelectedAccount().UiConfig().FuzzyComplete)
|
||||||
if hasCaseSmartPrefix(v, args[0]) {
|
|
||||||
out = append(out, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetLabels(aerc *widgets.Aerc, args []string) []string {
|
func GetLabels(aerc *widgets.Aerc, args []string) []string {
|
||||||
|
@ -172,27 +157,14 @@ func GetLabels(aerc *widgets.Aerc, args []string) []string {
|
||||||
}
|
}
|
||||||
trimmed := strings.TrimLeft(last, "+-")
|
trimmed := strings.TrimLeft(last, "+-")
|
||||||
|
|
||||||
out := make([]string, 0)
|
|
||||||
for _, label := range acct.Labels() {
|
|
||||||
if hasCaseSmartPrefix(label, trimmed) {
|
|
||||||
var prev string
|
var prev string
|
||||||
if len(others) > 0 {
|
if len(others) > 0 {
|
||||||
prev = others + " "
|
prev = others + " "
|
||||||
}
|
}
|
||||||
out = append(out, fmt.Sprintf("%v%v%v", prev, prefix, label))
|
out := FilterList(acct.Labels(), trimmed, prev+prefix, acct.UiConfig().FuzzyComplete)
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func foundInString(s, substring string, fuzzy bool) bool {
|
|
||||||
if fuzzy {
|
|
||||||
return caseInsensitiveContains(s, substring)
|
|
||||||
} else {
|
|
||||||
return hasCaseSmartPrefix(s, substring)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// hasCaseSmartPrefix checks whether s starts with prefix, using a case
|
// hasCaseSmartPrefix checks whether s starts with prefix, using a case
|
||||||
// sensitive match if and only if prefix contains upper case letters.
|
// sensitive match if and only if prefix contains upper case letters.
|
||||||
func hasCaseSmartPrefix(s, prefix string) bool {
|
func hasCaseSmartPrefix(s, prefix string) bool {
|
||||||
|
@ -202,11 +174,6 @@ func hasCaseSmartPrefix(s, prefix string) bool {
|
||||||
return strings.HasPrefix(strings.ToLower(s), strings.ToLower(prefix))
|
return strings.HasPrefix(strings.ToLower(s), strings.ToLower(prefix))
|
||||||
}
|
}
|
||||||
|
|
||||||
func caseInsensitiveContains(s, substr string) bool {
|
|
||||||
s, substr = strings.ToUpper(s), strings.ToUpper(substr)
|
|
||||||
return strings.Contains(s, substr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasUpper(s string) bool {
|
func hasUpper(s string) bool {
|
||||||
for _, r := range s {
|
for _, r := range s {
|
||||||
if unicode.IsUpper(r) {
|
if unicode.IsUpper(r) {
|
||||||
|
|
|
@ -32,7 +32,7 @@ func (Header) Aliases() []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Header) Complete(aerc *widgets.Aerc, args []string) []string {
|
func (Header) Complete(aerc *widgets.Aerc, args []string) []string {
|
||||||
return commands.CompletionFromList(headers, args)
|
return commands.CompletionFromList(aerc, headers, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Header) Execute(aerc *widgets.Aerc, args []string) error {
|
func (Header) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
|
|
|
@ -20,17 +20,16 @@ func (ChangeTab) Aliases() []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ChangeTab) Complete(aerc *widgets.Aerc, args []string) []string {
|
func (ChangeTab) Complete(aerc *widgets.Aerc, args []string) []string {
|
||||||
|
acct := aerc.SelectedAccount()
|
||||||
|
if acct == nil {
|
||||||
|
return make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return aerc.TabNames()
|
return aerc.TabNames()
|
||||||
}
|
}
|
||||||
joinedArgs := strings.Join(args, " ")
|
joinedArgs := strings.Join(args, " ")
|
||||||
out := make([]string, 0)
|
return FilterList(aerc.TabNames(), joinedArgs, "", acct.UiConfig().FuzzyComplete)
|
||||||
for _, tab := range aerc.TabNames() {
|
|
||||||
if strings.HasPrefix(tab, joinedArgs) {
|
|
||||||
out = append(out, tab)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ChangeTab) Execute(aerc *widgets.Aerc, args []string) error {
|
func (ChangeTab) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
|
|
|
@ -31,7 +31,7 @@ func (Archive) Aliases() []string {
|
||||||
|
|
||||||
func (Archive) Complete(aerc *widgets.Aerc, args []string) []string {
|
func (Archive) Complete(aerc *widgets.Aerc, args []string) []string {
|
||||||
valid := []string{"flat", "year", "month"}
|
valid := []string{"flat", "year", "month"}
|
||||||
return commands.CompletionFromList(valid, args)
|
return commands.CompletionFromList(aerc, valid, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Archive) Execute(aerc *widgets.Aerc, args []string) error {
|
func (Archive) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
|
|
|
@ -10,6 +10,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/lithammer/fuzzysearch/fuzzy"
|
||||||
|
|
||||||
"git.sr.ht/~rjarry/aerc/lib"
|
"git.sr.ht/~rjarry/aerc/lib"
|
||||||
"git.sr.ht/~rjarry/aerc/models"
|
"git.sr.ht/~rjarry/aerc/models"
|
||||||
"git.sr.ht/~rjarry/aerc/widgets"
|
"git.sr.ht/~rjarry/aerc/widgets"
|
||||||
|
@ -194,3 +196,21 @@ func MsgInfoFromUids(store *lib.MessageStore, uids []uint32) ([]*models.MessageI
|
||||||
}
|
}
|
||||||
return infos, nil
|
return infos, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FilterList takes a list of valid completions and filters it, either
|
||||||
|
// by case smart prefix, or by fuzzy matching, prepending "prefix" to each completion
|
||||||
|
func FilterList(valid []string, search, prefix string, isFuzzy bool) []string {
|
||||||
|
out := make([]string, 0)
|
||||||
|
if isFuzzy {
|
||||||
|
for _, v := range fuzzy.RankFindFold(search, valid) {
|
||||||
|
out = append(out, prefix+v.Target)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, v := range valid {
|
||||||
|
if hasCaseSmartPrefix(v, search) {
|
||||||
|
out = append(out, prefix+v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
|
@ -124,9 +124,10 @@ stylesets-dirs=
|
||||||
# Default: default
|
# Default: default
|
||||||
styleset-name=default
|
styleset-name=default
|
||||||
|
|
||||||
# Activates fuzzy search for IMAP folders: the typed string is search in the
|
# Activates fuzzy search in commands and their arguments: the typed string is
|
||||||
# folder tree in any position, not necessarily at the beginning.
|
# searched in the command or option in any position, and need not be
|
||||||
#fuzzy-folder-complete=false
|
# consecutive characters in the command or option.
|
||||||
|
#fuzzy-complete=false
|
||||||
|
|
||||||
#[ui:account=foo]
|
#[ui:account=foo]
|
||||||
#
|
#
|
||||||
|
|
|
@ -44,7 +44,7 @@ type UIConfig struct {
|
||||||
EmptyDirlist string `ini:"empty-dirlist"`
|
EmptyDirlist string `ini:"empty-dirlist"`
|
||||||
MouseEnabled bool `ini:"mouse-enabled"`
|
MouseEnabled bool `ini:"mouse-enabled"`
|
||||||
ThreadingEnabled bool `ini:"threading-enabled"`
|
ThreadingEnabled bool `ini:"threading-enabled"`
|
||||||
FuzzyFolderComplete bool `ini:"fuzzy-folder-complete"`
|
FuzzyComplete bool `ini:"fuzzy-complete"`
|
||||||
NewMessageBell bool `ini:"new-message-bell"`
|
NewMessageBell bool `ini:"new-message-bell"`
|
||||||
Spinner string `ini:"spinner"`
|
Spinner string `ini:"spinner"`
|
||||||
SpinnerDelimiter string `ini:"spinner-delimiter"`
|
SpinnerDelimiter string `ini:"spinner-delimiter"`
|
||||||
|
@ -622,7 +622,7 @@ func LoadConfigFromFile(root *string, logger *log.Logger) (*AercConfig, error) {
|
||||||
EmptyDirlist: "(no folders)",
|
EmptyDirlist: "(no folders)",
|
||||||
MouseEnabled: false,
|
MouseEnabled: false,
|
||||||
NewMessageBell: true,
|
NewMessageBell: true,
|
||||||
FuzzyFolderComplete: false,
|
FuzzyComplete: false,
|
||||||
Spinner: "[..] , [..] , [..] , [..] , [..], [..] , [..] , [..] ",
|
Spinner: "[..] , [..] , [..] , [..] , [..], [..] , [..] , [..] ",
|
||||||
SpinnerDelimiter: ",",
|
SpinnerDelimiter: ",",
|
||||||
DirListFormat: "%n %>r",
|
DirListFormat: "%n %>r",
|
||||||
|
|
|
@ -236,11 +236,11 @@ These options are configured in the *[ui]* section of aerc.conf.
|
||||||
|
|
||||||
Have a look at *aerc-stylesets*(7) as to how a styleset looks like.
|
Have a look at *aerc-stylesets*(7) as to how a styleset looks like.
|
||||||
|
|
||||||
*fuzzy-folder-complete*
|
*fuzzy-complete*
|
||||||
When finding a folder with cf or move, for example, the popover will
|
When typing a command or option, the popover will now show not only the
|
||||||
how not only the folders /starting/ with the string input by the user,
|
items /starting/ with the string input by the user, but it will also show
|
||||||
but it will show coincidences of folders /containing/ the string in any
|
instances of items /containing/ the string, starting at any position and
|
||||||
position of their name. This is case-independent.
|
need not be consecutive characters in the command or option.
|
||||||
|
|
||||||
*threading-enabled*
|
*threading-enabled*
|
||||||
Enable a threaded viewing of messages, works with IMAP (when there's
|
Enable a threaded viewing of messages, works with IMAP (when there's
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -23,6 +23,7 @@ require (
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
github.com/imdario/mergo v0.3.12
|
github.com/imdario/mergo v0.3.12
|
||||||
github.com/kyoh86/xdg v1.2.0
|
github.com/kyoh86/xdg v1.2.0
|
||||||
|
github.com/lithammer/fuzzysearch v1.1.3
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.14
|
github.com/mattn/go-isatty v0.0.14
|
||||||
github.com/mattn/go-pointer v0.0.1 // indirect
|
github.com/mattn/go-pointer v0.0.1 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -168,6 +168,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kyoh86/xdg v1.2.0 h1:CERuT/ShdTDj+A2UaX3hQ3mOV369+Sj+wyn2nIRIIkI=
|
github.com/kyoh86/xdg v1.2.0 h1:CERuT/ShdTDj+A2UaX3hQ3mOV369+Sj+wyn2nIRIIkI=
|
||||||
github.com/kyoh86/xdg v1.2.0/go.mod h1:/mg8zwu1+qe76oTFUBnyS7rJzk7LLC0VGEzJyJ19DHs=
|
github.com/kyoh86/xdg v1.2.0/go.mod h1:/mg8zwu1+qe76oTFUBnyS7rJzk7LLC0VGEzJyJ19DHs=
|
||||||
|
github.com/lithammer/fuzzysearch v1.1.3 h1:+t5SevHLfi3IHcTx7LT3S+od4OcUmjzxD1xmnvtgG38=
|
||||||
|
github.com/lithammer/fuzzysearch v1.1.3/go.mod h1:1R1LRNk7yKid1BaQkmuLQaHruxcC4HmAH30Dh61Ih1Q=
|
||||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
|
Loading…
Reference in a new issue