Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support tab completions #209

Merged
merged 12 commits into from
Aug 17, 2022
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@ go 1.18
require (
cloud.google.com/go v0.97.0
github.com/alecthomas/kong v0.2.22
github.com/posener/complete v1.2.3
github.com/stretchr/testify v1.7.0
github.com/willabides/kongplete v0.3.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.0.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

replace github.com/willabides/kongplete v0.3.0 => github.com/jotaen/kongplete v0.3.1-0.20220728081706-d7fdeab9b894
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,16 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jotaen/kongplete v0.3.1-0.20220728081706-d7fdeab9b894 h1:7iMyO3bat2s1pQP3oeaRf7fAxrd7UijSNpUM0X92g0c=
github.com/jotaen/kongplete v0.3.1-0.20220728081706-d7fdeab9b894/go.mod h1:VPdrG6LY+tP0LMkSBuTgIQ8c6+P8wvIDHVJzDdDh9Fw=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
Expand All @@ -163,7 +169,11 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab h1:ZjX6I48eZSFetPb41dHudEyVr5v953N15TsNZXlkcWY=
github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab/go.mod h1:/PfPXh0EntGc3QAAyUaviy4S9tzy4Zp0e2ilq4voC6E=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
Expand Down
6 changes: 3 additions & 3 deletions src/app/cli/bookmarks.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (opt *BookmarksList) Run(ctx app.Context) error {
type BookmarksInfo struct {
Dir bool `name:"dir" type:"string" help:"Display the directory"`
File bool `name:"file" type:"string" help:"Display the file name"`
Name string `arg:"" name:"bookmark" type:"string" help:"The path of the bookmark"`
Name string `arg:"" name:"bookmark" type:"string" predictor:"bookmark" help:"The path of the bookmark"`
}

func (opt *BookmarksInfo) Run(ctx app.Context) error {
Expand Down Expand Up @@ -78,7 +78,7 @@ func (opt *BookmarksInfo) Run(ctx app.Context) error {
}

type BookmarksSet struct {
File string `arg:"" type:"string" help:".klg source file"`
File string `arg:"" type:"string" predictor:"file" help:".klg source file"`
Name string `arg:"" name:"bookmark" type:"string" optional:"1" help:"The name of the bookmark."`
Force bool `name:"force" help:"Force to set, even if target file does not exist or is invalid"`
lib.QuietArgs
Expand Down Expand Up @@ -128,7 +128,7 @@ func (opt *BookmarksSet) Run(ctx app.Context) error {

type BookmarksUnset struct {
// The name is not optional here, to avoid accidental invocations
Name string `arg:"" name:"bookmark" type:"string" help:"The name of the bookmark"`
Name string `arg:"" name:"bookmark" type:"string" predictor:"bookmark" help:"The name of the bookmark"`
lib.QuietArgs
}

Expand Down
22 changes: 22 additions & 0 deletions src/app/cli/completion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package cli

import (
"github.com/jotaen/klog/src/app"
)

type Completion struct{}

func (c *Completion) Help() string {
return "The printed shell code is for instructing your shell to use tab completions for klog. " +
"Place the code into your shell initialization file, e.g. `~/.bashrc`. " +
"You can either paste it verbatim, or you source it dynamically via `. <(klog completion)`."
}

func (c *Completion) Run(ctx app.Context) error {
completion, err := ctx.Completion()
if err != nil {
return err
}
ctx.Print(completion)
return nil
}
12 changes: 7 additions & 5 deletions src/app/cli/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ type Cli struct {
Bk Bookmarks `cmd:"" group:"Bookmarks" hidden:"" help:"Alias"`

// Misc
Edit Edit `cmd:"" group:"Misc" help:"Opens a file or bookmark in your editor"`
Goto Goto `cmd:"" group:"Misc" help:"Opens the file explorer at the given location"`
Json Json `cmd:"" group:"Misc" help:"Converts records to JSON"`
Info Info `cmd:"" group:"Misc" default:"withargs" help:"Displays meta info about klog"`
Version Version `cmd:"" group:"Misc" help:"Prints version info and check for updates"`
Edit Edit `cmd:"" group:"Misc" help:"Opens a file or bookmark in your editor"`
Goto Goto `cmd:"" group:"Misc" help:"Opens the file explorer at the given location"`
Json Json `cmd:"" group:"Misc" help:"Converts records to JSON"`
Info Info `cmd:"" group:"Misc" default:"withargs" help:"Displays meta info about klog"`
Version Version `cmd:"" group:"Misc" help:"Prints version info and check for updates"`
Completion Completion `cmd:"" group:"Misc" help:"Output shell code for initialising shell completions for klog"`
Completions Completion `cmd:"" group:"Misc" hidden:"" help:"Alias"`
}
38 changes: 18 additions & 20 deletions src/app/cli/lib/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import (
)

type InputFilesArgs struct {
File []app.FileOrBookmarkName `arg:"" optional:"" type:"string" name:"file or bookmark" help:".klg source file(s) (if empty the bookmark is used)"`
File []app.FileOrBookmarkName `arg:"" optional:"" type:"string" predictor:"file_or_bookmark" name:"file or bookmark" help:".klg source file(s) (if empty the bookmark is used)"`
}

type OutputFileArgs struct {
File app.FileOrBookmarkName `arg:"" optional:"" type:"string" name:"file or bookmark" help:".klg source file (if empty the bookmark is used)"`
File app.FileOrBookmarkName `arg:"" optional:"" type:"string" predictor:"file_or_bookmark" name:"file or bookmark" help:".klg source file (if empty the bookmark is used)"`
}

type AtDateArgs struct {
Expand Down Expand Up @@ -110,24 +110,22 @@ type FilterArgs struct {
Today bool `name:"today" group:"Filter (shortcuts)" help:"Records at today’s date"`
Yesterday bool `name:"yesterday" group:"Filter (shortcuts)" help:"Records at yesterday’s date"`
Tomorrow bool `name:"tomorrow" group:"Filter (shortcuts)" help:"Records at tomorrow’s date"`
ThisXXX bool `name:"this-***" group:"Filter (shortcuts)" help:"Records of the current week/quarter/month/year (e.g. --this-year)"`
LastXXX bool `name:"last-***" group:"Filter (shortcuts)" help:"Records of the previous week/quarter/month/year (e.g. --last-month)"`
ThisWeek bool `name:"this-week" hidden:""`
ThisWeekAlias bool `name:"thisweek" hidden:""`
LastWeek bool `name:"last-week" hidden:""`
LastWeekAlias bool `name:"lastweek" hidden:""`
ThisMonth bool `name:"this-month" hidden:""`
ThisMonthAlias bool `name:"thismonth" hidden:""`
LastMonth bool `name:"last-month" hidden:""`
LastMonthAlias bool `name:"lastmonth" hidden:""`
ThisQuarter bool `name:"this-quarter" hidden:""`
ThisQuarterAlias bool `name:"thisquarter" hidden:""`
LastQuarter bool `name:"last-quarter" hidden:""`
LastQuarterAlias bool `name:"lastquarter" hidden:""`
ThisYear bool `name:"this-year" hidden:""`
ThisYearAlias bool `name:"thisyear" hidden:""`
LastYear bool `name:"last-year" hidden:""`
LastYearAlias bool `name:"lastyear" hidden:""`
ThisWeek bool `name:"this-week" group:"Filter (shortcuts)" help:"Records of the current week"`
ThisWeekAlias bool `name:"thisweek" group:"Filter (shortcuts)" hidden:""`
LastWeek bool `name:"last-week" group:"Filter (shortcuts)" help:"Records of the last week"`
LastWeekAlias bool `name:"lastweek" group:"Filter (shortcuts)" hidden:""`
ThisMonth bool `name:"this-month" group:"Filter (shortcuts)" help:"Records of the current month"`
ThisMonthAlias bool `name:"thismonth" group:"Filter (shortcuts)" hidden:""`
LastMonth bool `name:"last-month" group:"Filter (shortcuts)" help:"Records of the last month"`
LastMonthAlias bool `name:"lastmonth" group:"Filter (shortcuts)" hidden:""`
ThisQuarter bool `name:"this-quarter" group:"Filter (shortcuts)" help:"Records of the current quarter"`
ThisQuarterAlias bool `name:"thisquarter" group:"Filter (shortcuts)" hidden:""`
LastQuarter bool `name:"last-quarter" group:"Filter (shortcuts)" help:"Records of the last quarter"`
LastQuarterAlias bool `name:"lastquarter" group:"Filter (shortcuts)" hidden:""`
ThisYear bool `name:"this-year" group:"Filter (shortcuts)" help:"Records of the current year"`
ThisYearAlias bool `name:"thisyear" group:"Filter (shortcuts)" hidden:""`
LastYear bool `name:"last-year" group:"Filter (shortcuts)" help:"Records of the last year"`
LastYearAlias bool `name:"lastyear" group:"Filter (shortcuts)" hidden:""`
}

func (args *FilterArgs) ApplyFilter(now gotime.Time, rs []Record) []Record {
Expand Down
File renamed without changes.
15 changes: 14 additions & 1 deletion src/app/cli/main/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import (
"github.com/jotaen/klog/src/app/cli/lib"
"github.com/jotaen/klog/src/service"
"github.com/jotaen/klog/src/service/period"
"github.com/willabides/kongplete"
"reflect"
)

func Run(homeDir string, meta app.Meta, isDebug bool, args []string) (int, error) {
ctx := app.NewContext(homeDir, meta, lib.CliSerialiser{}, isDebug)
kongApp, nErr := kong.New(
&cli.Cli{},
kong.Name("klog"),
Expand Down Expand Up @@ -66,6 +66,19 @@ func Run(homeDir string, meta app.Meta, isDebug bool, args []string) (int, error
if cErr != nil {
return -1, cErr
}

completion := func() (string, error) {
return kongplete.GetCompletionFromContext(kongCtx)
}

ctx := app.NewContext(homeDir, meta, lib.CliSerialiser{}, isDebug, completion)

// When klog is invoked by shell completion (specifically, when the
// bash-specific COMP_LINE environment variable is set), the
// kongplete.Complete function generates a list of possible completions,
// prints them one per line to stdout, and then exits the program early.
kongplete.Complete(kongApp, kongplete.WithPredictors(CompletionPredictors(ctx)))

kongCtx.BindTo(ctx, (*app.Context)(nil))

rErr := kongCtx.Run()
Expand Down
29 changes: 29 additions & 0 deletions src/app/cli/main/completion_predictors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package klog

import (
"github.com/jotaen/klog/src/app"
"github.com/posener/complete"
)

func predictBookmarks(ctx app.Context) complete.Predictor {
thunk := func() []string {
names := make([]string, 0)
bookmarksCollection, err := ctx.ReadBookmarks()
if err != nil {
return names
}
for _, bookmark := range bookmarksCollection.All() {
names = append(names, bookmark.Name().ValuePretty())
}
return names
}
return complete.PredictFunc(func(a complete.Args) []string { return thunk() })
}

func CompletionPredictors(ctx app.Context) map[string]complete.Predictor {
return map[string]complete.Predictor{
"file": complete.PredictFiles("*.klg"),
"bookmark": predictBookmarks(ctx),
"file_or_bookmark": complete.PredictOr(complete.PredictFiles("*.klg"), predictBookmarks(ctx)),
}
}
4 changes: 4 additions & 0 deletions src/app/cli/testutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,8 @@ func (ctx *TestingContext) SetSerialiser(s parser.Serialiser) {
ctx.serialiser = s
}

func (ctx *TestingContext) Completion() (string, app.Error) {
return "", nil
}

func (ctx *TestingContext) Debug(_ func()) {}
20 changes: 19 additions & 1 deletion src/app/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ type Context interface {
// SetSerialiser sets a new serialiser.
SetSerialiser(parser.Serialiser)

// Completion returns the shell initialization code for CLI completions.
Completion() (string, Error)

// Debug takes a void function that is only executed in debug mode.
Debug(func())
}
Expand All @@ -92,7 +95,7 @@ type Meta struct {
}

// NewContext creates a new Context object.
func NewContext(homeDir string, meta Meta, serialiser parser.Serialiser, isDebug bool) Context {
func NewContext(homeDir string, meta Meta, serialiser parser.Serialiser, isDebug bool, completion func() (string, error)) Context {
if meta.Version == "" {
meta.Version = "v?.?"
}
Expand All @@ -104,6 +107,7 @@ func NewContext(homeDir string, meta Meta, serialiser parser.Serialiser, isDebug
serialiser,
meta,
isDebug,
completion,
}
}

Expand All @@ -112,6 +116,7 @@ type context struct {
serialiser parser.Serialiser
meta Meta
isDebug bool
completion func() (string, error)
}

func (ctx *context) Print(text string) {
Expand Down Expand Up @@ -394,6 +399,19 @@ func (ctx *context) SetSerialiser(s parser.Serialiser) {
ctx.serialiser = s
}

func (ctx *context) Completion() (string, Error) {
c, err := ctx.completion()
if err != nil {
return "", NewErrorWithCode(
GENERAL_ERROR,
"Cannot determine completion initialization",
"The only supported shells are zsh, bash and fish.",
err,
)
}
return c, nil
}

func (ctx *context) Debug(task func()) {
if ctx.isDebug {
task()
Expand Down