diff --git a/.hof/shadow/cli/cmd/hof/cmd/eval.go b/.hof/shadow/cli/cmd/hof/cmd/eval.go index 330673f9e..a2635e8db 100644 --- a/.hof/shadow/cli/cmd/hof/cmd/eval.go +++ b/.hof/shadow/cli/cmd/hof/cmd/eval.go @@ -32,6 +32,7 @@ func init() { EvalCmd.Flags().BoolVarP(&(flags.EvalFlags.Resolve), "resolve", "", false, "resolve references in value") EvalCmd.Flags().BoolVarP(&(flags.EvalFlags.Defaults), "defaults", "", false, "use default values if not set") EvalCmd.Flags().BoolVarP(&(flags.EvalFlags.Final), "final", "", true, "finalize the value") + EvalCmd.Flags().BoolVarP(&(flags.EvalFlags.Tui), "tui", "", false, "open hof's TUI and browse your CUE") } func EvalRun(args []string) (err error) { diff --git a/.hof/shadow/cli/cmd/hof/cmd/root.go b/.hof/shadow/cli/cmd/hof/cmd/root.go index 356f6fb22..77f0c215f 100644 --- a/.hof/shadow/cli/cmd/hof/cmd/root.go +++ b/.hof/shadow/cli/cmd/hof/cmd/root.go @@ -142,6 +142,7 @@ func RootInit() { RootCmd.AddCommand(VetCmd) RootCmd.AddCommand(ChatCmd) RootCmd.AddCommand(RunCmd) + RootCmd.AddCommand(TuiCmd) RootCmd.AddCommand(FeedbackCmd) } diff --git a/.hof/shadow/cli/cmd/hof/cmd/tui.go b/.hof/shadow/cli/cmd/hof/cmd/tui.go new file mode 100644 index 000000000..ff19a45f5 --- /dev/null +++ b/.hof/shadow/cli/cmd/hof/cmd/tui.go @@ -0,0 +1,82 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/hofstadter-io/hof/cmd/hof/ga" +) + +var tuiLong = `hidden command for tui experiments` + +func TuiRun(args []string) (err error) { + + // you can safely comment this print out + fmt.Println("not implemented") + + return err +} + +var TuiCmd = &cobra.Command{ + + Use: "tui", + + Hidden: true, + + Short: "hidden command for tui experiments", + + Long: tuiLong, + + Run: func(cmd *cobra.Command, args []string) { + + ga.SendCommandPath(cmd.CommandPath()) + + var err error + + // Argument Parsing + + err = TuiRun(args) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + }, +} + +func init() { + extra := func(cmd *cobra.Command) bool { + + return false + } + + ohelp := TuiCmd.HelpFunc() + ousage := TuiCmd.UsageFunc() + + help := func(cmd *cobra.Command, args []string) { + + ga.SendCommandPath(cmd.CommandPath() + " help") + + if extra(cmd) { + return + } + ohelp(cmd, args) + } + usage := func(cmd *cobra.Command) error { + if extra(cmd) { + return nil + } + return ousage(cmd) + } + + thelp := func(cmd *cobra.Command, args []string) { + help(cmd, args) + } + tusage := func(cmd *cobra.Command) error { + return usage(cmd) + } + TuiCmd.SetHelpFunc(thelp) + TuiCmd.SetUsageFunc(tusage) + +} diff --git a/.hof/shadow/cli/cmd/hof/flags/eval.go b/.hof/shadow/cli/cmd/hof/flags/eval.go index a53109793..9b7b4415f 100644 --- a/.hof/shadow/cli/cmd/hof/flags/eval.go +++ b/.hof/shadow/cli/cmd/hof/flags/eval.go @@ -18,6 +18,7 @@ type EvalFlagpole struct { Resolve bool Defaults bool Final bool + Tui bool } var EvalFlags EvalFlagpole diff --git a/cmd/hof/cmd/eval.go b/cmd/hof/cmd/eval.go index 3face471f..8b6d4e15b 100644 --- a/cmd/hof/cmd/eval.go +++ b/cmd/hof/cmd/eval.go @@ -33,6 +33,7 @@ func init() { EvalCmd.Flags().BoolVarP(&(flags.EvalFlags.Resolve), "resolve", "", false, "resolve references in value") EvalCmd.Flags().BoolVarP(&(flags.EvalFlags.Defaults), "defaults", "", false, "use default values if not set") EvalCmd.Flags().BoolVarP(&(flags.EvalFlags.Final), "final", "", true, "finalize the value") + EvalCmd.Flags().BoolVarP(&(flags.EvalFlags.Tui), "tui", "", false, "open hof's TUI and browse your CUE") } func EvalRun(args []string) (err error) { diff --git a/cmd/hof/cmd/root.go b/cmd/hof/cmd/root.go index 2c39e7f89..3728b9080 100644 --- a/cmd/hof/cmd/root.go +++ b/cmd/hof/cmd/root.go @@ -142,6 +142,7 @@ func RootInit() { RootCmd.AddCommand(VetCmd) RootCmd.AddCommand(ChatCmd) RootCmd.AddCommand(RunCmd) + RootCmd.AddCommand(TuiCmd) RootCmd.AddCommand(FeedbackCmd) } diff --git a/cmd/hof/cmd/tui.go b/cmd/hof/cmd/tui.go new file mode 100644 index 000000000..f65e14db7 --- /dev/null +++ b/cmd/hof/cmd/tui.go @@ -0,0 +1,87 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/hofstadter-io/hof/cmd/hof/flags" + "github.com/hofstadter-io/hof/cmd/hof/ga" + + "github.com/hofstadter-io/hof/lib/tui" +) + +var tuiLong = `hidden command for tui experiments` + +func TuiRun(args []string) (err error) { + + // you can safely comment this print out + // fmt.Println("not implemented") + + err = tui.Cmd(args, flags.RootPflags) + + return err +} + +var TuiCmd = &cobra.Command{ + + Use: "tui", + + Hidden: true, + + Short: "hidden command for tui experiments", + + Long: tuiLong, + + Run: func(cmd *cobra.Command, args []string) { + + ga.SendCommandPath(cmd.CommandPath()) + + var err error + + // Argument Parsing + + err = TuiRun(args) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + }, +} + +func init() { + extra := func(cmd *cobra.Command) bool { + + return false + } + + ohelp := TuiCmd.HelpFunc() + ousage := TuiCmd.UsageFunc() + + help := func(cmd *cobra.Command, args []string) { + + ga.SendCommandPath(cmd.CommandPath() + " help") + + if extra(cmd) { + return + } + ohelp(cmd, args) + } + usage := func(cmd *cobra.Command) error { + if extra(cmd) { + return nil + } + return ousage(cmd) + } + + thelp := func(cmd *cobra.Command, args []string) { + help(cmd, args) + } + tusage := func(cmd *cobra.Command) error { + return usage(cmd) + } + TuiCmd.SetHelpFunc(thelp) + TuiCmd.SetUsageFunc(tusage) + +} diff --git a/cmd/hof/flags/eval.go b/cmd/hof/flags/eval.go index a53109793..9b7b4415f 100644 --- a/cmd/hof/flags/eval.go +++ b/cmd/hof/flags/eval.go @@ -18,6 +18,7 @@ type EvalFlagpole struct { Resolve bool Defaults bool Final bool + Tui bool } var EvalFlags EvalFlagpole diff --git a/design/cmds/cue.cue b/design/cmds/cue.cue index 45e3ff013..dda3d0198 100644 --- a/design/cmds/cue.cue +++ b/design/cmds/cue.cue @@ -165,6 +165,13 @@ EvalCommand: schema.Command & { Type: "bool" Default: "true" Help: "finalize the value" + }, { + Name: "tui" + Long: "tui" + Short: "" + Type: "bool" + Default: "false" + Help: "open hof's TUI and browse your CUE" }] } diff --git a/design/cmds/run.cue b/design/cmds/run.cue index 9bfff35b6..bd4d66603 100644 --- a/design/cmds/run.cue +++ b/design/cmds/run.cue @@ -4,9 +4,7 @@ import ( "github.com/hofstadter-io/hofmod-cli/schema" ) -// TODO, JAMStack / HofKit RunCommand: schema.Command & { - TBD: "β" Name: "run" Usage: "run" Aliases: ["r"] diff --git a/design/cmds/tui.cue b/design/cmds/tui.cue new file mode 100644 index 000000000..6a7f73a9c --- /dev/null +++ b/design/cmds/tui.cue @@ -0,0 +1,13 @@ +package cmds + +import ( + "github.com/hofstadter-io/hofmod-cli/schema" +) + +TuiCommand: schema.Command & { + Name: "tui" + Usage: "tui" + Short: "hidden command for tui experiments" + Long: Short + Hidden: true +} diff --git a/design/main.cue b/design/main.cue index 88425d261..d6c4e820b 100644 --- a/design/main.cue +++ b/design/main.cue @@ -43,6 +43,7 @@ CLI: schema.Cli & { // beta commands cmds.ChatCommand, cmds.RunCommand, + cmds.TuiCommand, // additional commands cmds.FeedbackCommand, diff --git a/go.mod b/go.mod index 36a47fd85..f97a276f6 100644 --- a/go.mod +++ b/go.mod @@ -59,8 +59,10 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 // indirect github.com/acomagu/bufpipe v1.0.4 // indirect github.com/adrg/xdg v0.4.0 // indirect + github.com/alecthomas/chroma v0.10.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chzyer/readline v1.5.1 // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/cockroachdb/apd/v3 v3.2.0 // indirect github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect @@ -74,6 +76,9 @@ require ( github.com/emicklei/proto v1.12.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/gammazero/deque v0.2.1 // indirect + github.com/gdamore/encoding v1.0.0 // indirect + github.com/gdamore/tcell v1.4.0 // indirect + github.com/gdamore/tcell/v2 v2.6.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-stack/stack v1.8.1 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect @@ -87,6 +92,7 @@ require ( github.com/klauspost/compress v1.16.7 // indirect github.com/kr/text v0.2.0 // indirect github.com/labstack/gommon v0.4.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect @@ -109,6 +115,7 @@ require ( github.com/prometheus/procfs v0.11.1 // indirect github.com/protocolbuffers/txtpbfmt v0.0.0-20230730201308-0c31dbd32b9f // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rivo/tview v0.0.0-20230826224341-9754ab44dc1c // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect diff --git a/go.sum b/go.sum index 37894bc58..5e55bd02d 100644 --- a/go.sum +++ b/go.sum @@ -67,6 +67,8 @@ github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= +github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= +github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= @@ -84,8 +86,12 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -108,6 +114,7 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docker/cli v24.0.5+incompatible h1:WeBimjvS0eKdH4Ygx+ihVq1Q++xg36M/rMi4aXAvodc= @@ -142,6 +149,12 @@ github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0 github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU= github.com/gammazero/workerpool v1.1.3 h1:WixN4xzukFoN0XSeXF6puqEqFTl2mECI9S6W44HWy9Q= github.com/gammazero/workerpool v1.1.3/go.mod h1:wPjyBLDbyKnUn2XwwyD3EEwo9dHutia9/fwNmSHWACc= +github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= +github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU= +github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0= +github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg= +github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= @@ -273,6 +286,10 @@ github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8 github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= +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/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucsky/cuid v1.2.1 h1:MtJrL2OFhvYufUIn48d35QGXyeTC8tn0upumW9WwTHg= github.com/lucsky/cuid v1.2.1/go.mod h1:QaaJqckboimOmhRSJXSx/+IT+VTfxfPGSo/6mfgUfmE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -288,7 +305,9 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= @@ -345,7 +364,10 @@ github.com/protocolbuffers/txtpbfmt v0.0.0-20230730201308-0c31dbd32b9f h1:8SXWXW github.com/protocolbuffers/txtpbfmt v0.0.0-20230730201308-0c31dbd32b9f/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/tview v0.0.0-20230826224341-9754ab44dc1c h1:cuvKygt6v1OTsZSAXW2sc9tI6x0YEnxVct3DMv/0Ii4= +github.com/rivo/tview v0.0.0-20230826224341-9754ab44dc1c/go.mod h1:nVwGv4MP47T0jvlk7KuTTjjuSmrGO4JF0iaiNt4bufE= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -542,6 +564,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -575,6 +598,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/lib/cuecmd/eval.go b/lib/cuecmd/eval.go index 16ea93379..5a1baa445 100644 --- a/lib/cuecmd/eval.go +++ b/lib/cuecmd/eval.go @@ -83,21 +83,25 @@ func Eval(args []string, rflags flags.RootPflagpole, cflags flags.EvalFlagpole) pkg = bi.ID() } - err = writeOutput( - val, - pkg, - opts, - fopts, - cflags.Out, - cflags.Outfile, - cflags.Expression, - rflags.Schema, - cflags.Escape, - cflags.Defaults, - wantErrorsInValue, - ) - if err != nil { - return err + if cflags.Tui { + err = runTUI(R, cflags) + } else { + err = writeOutput( + val, + pkg, + opts, + fopts, + cflags.Out, + cflags.Outfile, + cflags.Expression, + rflags.Schema, + cflags.Escape, + cflags.Defaults, + wantErrorsInValue, + ) + if err != nil { + return err + } } return nil diff --git a/lib/cuecmd/tui.go b/lib/cuecmd/tui.go new file mode 100644 index 000000000..c09280be0 --- /dev/null +++ b/lib/cuecmd/tui.go @@ -0,0 +1,32 @@ +package cuecmd + +import ( + "golang.org/x/crypto/ssh/terminal" + + "github.com/hofstadter-io/hof/cmd/hof/flags" + "github.com/hofstadter-io/hof/lib/runtime" + "github.com/hofstadter-io/hof/lib/tui/app" + "github.com/hofstadter-io/hof/lib/tui/pages" +) + +func runTUI(R *runtime.Runtime, cflags flags.EvalFlagpole) (err error) { + // stuff to ensure we don't mess up the user's terminal + oldState, err := terminal.MakeRaw(0) + if err != nil { + return err + } + defer terminal.Restore(0, oldState) + + // application + App := app.NewApp() + + App.Runtime = R + + // setup pages + App.Pages = App.Pages. + AddPage("eval", pages.NewEvalPage(App), true, true) + + err = App.SetRoot(App.Pages, true).Run() + + return err +} diff --git a/lib/tui/app/app.go b/lib/tui/app/app.go new file mode 100644 index 000000000..f9b22fcc5 --- /dev/null +++ b/lib/tui/app/app.go @@ -0,0 +1,108 @@ +package app + +import ( + "fmt" + + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" + + "github.com/hofstadter-io/hof/lib/runtime" +) + +type App struct { + *tview.Application + + loglevel string + + Logger func(string) + + Runtime *runtime.Runtime + + Pages *tview.Pages + + helpShown bool + HelpModal tview.Primitive +} + +func NewApp() *App { + app := &App{ + Application: tview.NewApplication(), + loglevel: "info", + } + + app.EnableMouse(true) + + + text := tview.NewTextView() + text. + SetBorder(true). + SetTitle(" Help ") + fmt.Fprint(text, helpMsg) + + width, height := 100, 30 + + app.HelpModal = tview. + NewFlex(). + AddItem(nil, 0, 1, false). + AddItem(tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(nil, 0, 1, false). + AddItem(text, height, 1, true). + AddItem(nil, 0, 1, false), width, 1, true). + AddItem(nil, 0, 1, false) + + app.Pages = tview.NewPages(). + AddPage("app-help", app.HelpModal, true, false) + + // setup keys + app.SetInputCapture(func(ek *tcell.EventKey) *tcell.EventKey { + app.Logger(fmt.Sprintf("app: %q %v %v\n", ek.Rune(), ek.Key(), ek.Modifiers())) + + // enum keys + switch ek.Key() { + case tcell.KeyEscape: + if app.helpShown { + app.Pages.HidePage("app-help") + app.helpShown = false + return nil + } + // we aren't handling this + return ek + + } + + // rune keys + switch ek.Rune() { + case '?': + app.helpShown = true + app.Pages.ShowPage("app-help") + app.Pages.SendToFront("app-help") + return nil + } + // we aren't handling this + return ek + }) + + return app +} + +const helpMsg = ` +Hof TUI help system + +, : open / close a value + +C - dive into a value (make it the new root) +u - back out of a value (unwind the vals to root) + +j, down arrow, right arrow: Move (the selection) down by one node. +k, ⬆⇦: Move (the selection) up by one node. +g, home: Move (the selection) to the top. +G, end: Move (the selection) to the bottom. + +J: Move (the selection) up one level +K: Move (the selection) to the last node one level down +Ctrl-F, page down: Move (the selection) down by one page. +Ctrl-B, page up: Move (the selection) up by one page. + +? - this help + +` diff --git a/lib/tui/cmd.go b/lib/tui/cmd.go new file mode 100644 index 000000000..b5b764705 --- /dev/null +++ b/lib/tui/cmd.go @@ -0,0 +1,68 @@ +package tui + +import ( + "os" + + "golang.org/x/crypto/ssh/terminal" + + "github.com/hofstadter-io/hof/cmd/hof/flags" + "github.com/hofstadter-io/hof/lib/tui/app" + "github.com/hofstadter-io/hof/lib/tui/pages" +) + + + +func Cmd(args []string, rflags flags.RootPflagpole) error { + // stuff to ensure we don't mess up the user's terminal + oldState, err := terminal.MakeRaw(0) + if err != nil { + return err + } + defer terminal.Restore(0, oldState) + + // only arg is the default file to open, for now... + path := "" + if len(args) > 0 { + path = args[0] + } + if path == "" { + p, err := os.Getwd() + if err != nil { + return err + } + path = p + } + + // setup new app + App := app.NewApp() + + // setup pages + App.Pages = App.Pages. + AddPage("vem", pages.NewVemPage(App, path), true, true) + + return App.SetRoot(App.Pages, true).Run() +} + + + + + +//func Cmd(args []string, rflags flags.RootPflagpole) error { + +// rl, err := readline.New("> ") +// if err != nil { +// panic(err) +// } +// defer rl.Close() + +// for { +// line, err := rl.Readline() +// if err != nil { // io.EOF +// break +// } +// line = strings.TrimSpace(line) +// fmt.Println(line) +// } + +// return nil +//} diff --git a/lib/tui/components/file-browser.go b/lib/tui/components/file-browser.go new file mode 100644 index 000000000..2e5072b4e --- /dev/null +++ b/lib/tui/components/file-browser.go @@ -0,0 +1,107 @@ +package components + +import ( + "os" + "path/filepath" + "sort" + + "github.com/rivo/tview" + "github.com/gdamore/tcell/v2" + + "github.com/hofstadter-io/hof/lib/tui/app" +) + +type FileBrowser struct { + Dir string + + OnOpen func(string) + + *tview.TreeView + + Root *tview.TreeNode + Node *tview.TreeNode + + App *app.App +} + + +func NewFileBrowser(app *app.App, dir string, onopen func(path string)) *FileBrowser { + fb := &FileBrowser { + App: app, + Dir: dir, + OnOpen: onopen, + } + + // file browser + fb.Root = tview.NewTreeNode(dir) + fb.Root.SetColor(tcell.ColorRed) + fb.AddAt(fb.Root, dir) + + // tree view + fb.TreeView = tview.NewTreeView() + fb. + SetRoot(fb.Root). + SetCurrentNode(fb.Root) + fb.SetBorder(true) + + // set our selected handler + fb.SetSelectedFunc(fb.OnSelect) + + + return fb +} + +func (FB *FileBrowser) OnSelect(node *tview.TreeNode) { + reference := node.GetReference() + if reference == nil { + return // Selecting the root node does nothing. + } + + children := node.GetChildren() + if len(children) == 0 { + // Load and show files in this directory. + path := reference.(string) + info, _ := os.Lstat(path) + if info.IsDir() { + FB.AddAt(node, path) + } else { + FB.OnOpen(path) + } + } else { + // Collapse if visible, expand if collapsed. + node.SetExpanded(!node.IsExpanded()) + } +} + + +func (FB *FileBrowser) AddAt(target *tview.TreeNode, path string) { + // get files at path + files, err := os.ReadDir(path) + if err != nil { + panic(err) + } + + // sort dirs first, then by name + sort.Slice(files, func(x, y int) bool { + X, Y := files[x], files[y] + // deal with file vs dir + if X.IsDir() && !Y.IsDir() { + return true + } else if !X.IsDir() && Y.IsDir() { + return false + } else { + return X.Name() < Y.Name() + } + }) + + // build tree nodes + for _, file := range files { + node := tview.NewTreeNode(file.Name()). + SetReference(filepath.Join(path, file.Name())) + // SetSelectable(file.IsDir()) + if file.IsDir() { + node.SetColor(tcell.ColorGreen) + } + target.AddChild(node) + } +} diff --git a/lib/tui/components/shell.go b/lib/tui/components/shell.go new file mode 100644 index 000000000..d4fcec512 --- /dev/null +++ b/lib/tui/components/shell.go @@ -0,0 +1,38 @@ +package components + +import ( + "github.com/rivo/tview" + + "github.com/hofstadter-io/hof/lib/tui/app" +) + +type Shell struct { + *tview.TextArea + + text string + + App *app.App +} + +func NewShell(app *app.App) *Shell { + s := &Shell{ + TextArea: tview.NewTextArea(), + App: app, + } + + // lower-level setup + s.SetTitle("Shell"). + SetBorder(true) + + return s +} + +func (S *Shell) Write(text string) { + S.text = text + S.SetText(S.text, true) +} + +func (S *Shell) Append(text string) { + S.text += text + S.SetText(S.text, true) +} diff --git a/lib/tui/components/text-editor.go b/lib/tui/components/text-editor.go new file mode 100644 index 000000000..15cade07e --- /dev/null +++ b/lib/tui/components/text-editor.go @@ -0,0 +1,83 @@ +package components + +import ( + "fmt" + "os" + "io" + + "github.com/alecthomas/chroma/lexers" + "github.com/alecthomas/chroma/quick" + "github.com/rivo/tview" + + "github.com/hofstadter-io/hof/lib/tui/app" +) + +type TextEditor struct { + *tview.TextView + + App *app.App + W io.Writer + + OnChange func() +} + +func NewTextEditor(app *app.App, onchange func()) *TextEditor { + + te := &TextEditor{ + TextView: tview.NewTextView(), + App: app, + OnChange: onchange, + } + te.SetWordWrap(true). + SetDynamicColors(true). + SetBorder(true) + te.SetChangedFunc(te.OnChange) + + te.W = tview.ANSIWriter(te) + + return te +} + +func (ED *TextEditor) OpenFile(path string) { + ED.App.Logger("ED.OpenFile: " + path + "\n") + + body, err := os.ReadFile(path) + if err != nil { + ED.App.Logger("error: " + err.Error()) + } + + l := lexers.Match(path) + lexer := "text" + if l != nil { + lexer = l.Config().Name + } else { + var s string + if len(body) > 512 { + s = string(body[:512]) + } else { + s = string(body) + } + + l = lexers.Analyse(s) + if l != nil { + lexer = l.Config().Name + } + } + + ED.SetTitle(fmt.Sprintf("%s (%s)", path, lexer)) + + ED.Clear() + + err = quick.Highlight(ED.W, string(body), lexer, "terminal256", "solarized-dark") + if err != nil { + ED.App.Logger("error: " + err.Error()) + } + + ED.Focus(func(p tview.Primitive){}) + +} + +func (ED *TextEditor) Focus(delegate func(p tview.Primitive)) { + ED.App.Logger("ED.Focus\n") + delegate(ED.TextView) +} diff --git a/lib/tui/components/value-browser.go b/lib/tui/components/value-browser.go new file mode 100644 index 000000000..e6ef36185 --- /dev/null +++ b/lib/tui/components/value-browser.go @@ -0,0 +1,178 @@ +package components + +import ( + "bytes" + "fmt" + "strings" + + "cuelang.org/go/cue" + "github.com/rivo/tview" + "github.com/gdamore/tcell/v2" + + "github.com/hofstadter-io/hof/lib/tui/app" +) + +type ValueBrowser struct { + *tview.TreeView + + OnFieldSelect func(string) + + Root *tview.TreeNode + + App *app.App + + Value cue.Value +} + +func NewValueBrowser(app *app.App, val cue.Value, OnFieldSelect func(path string)) *ValueBrowser { + FB := &ValueBrowser { + App: app, + Value: val, + OnFieldSelect: OnFieldSelect, + } + + // tree view + FB.TreeView = tview.NewTreeView() + FB.Root = tview.NewTreeNode("no results yet") + FB.Root.SetColor(tcell.ColorSilver) + + FB. + SetRoot(FB.Root). + SetCurrentNode(FB.Root) + + // set our selected handler + FB.SetSelectedFunc(FB.OnSelect) + FB.SetBorder(true) + + return FB +} + +func (FB *ValueBrowser) Rebuild(path string) { + if path == "" { + path = "" + } + + FB.Root = tview.NewTreeNode(path) + FB.Root.SetColor(tcell.ColorSilver) + FB.AddAt(FB.Root, path) + + FB. + SetRoot(FB.Root). + SetCurrentNode(FB.Root) + +} + +func (FB *ValueBrowser) OnSelect(node *tview.TreeNode) { + reference := node.GetReference() + if reference == nil { + return // Selecting the root node does nothing. + } + + children := node.GetChildren() + if len(children) == 0 { + // Load and show files in this directory. + path := reference.(string) + FB.AddAt(node, path) + } else { + // Collapse if visible, expand if collapsed. + node.SetExpanded(!node.IsExpanded()) + } +} + + +func (FB *ValueBrowser) AddAt(target *tview.TreeNode, path string) { + FB.App.Logger(fmt.Sprintf("FB.AddAt: %s\n", path)) + + if strings.HasPrefix(path, "") { + path = "" + } + if strings.HasPrefix(path, ".") { + path = path[1:] + } + val := FB.Value.LookupPath(cue.ParsePath(path)) + // FB.App.Logger(fmt.Sprintf("#v\n", val)) + + if val.Err() != nil { + FB.App.Logger(fmt.Sprintf("Error: %s\n", val.Err())) + return + } + + // get fields at path, need to know what format options are at play here + var iter *cue.Iterator + switch val.IncompleteKind() { + case cue.StructKind: + iter, _ = val.Fields() + case cue.ListKind: + i, _ := val.List() + iter = &i + } + if iter == nil { + FB.App.Logger(fmt.Sprintf("nil iter for: %s\n", path)) + return + } + + // sort dirs first, then by name + //sort.Slice(files, func(x, y int) bool { + // X, Y := files[x], files[y] + // // deal with file vs dir + // if X.IsDir() && !Y.IsDir() { + // return true + // } else if !X.IsDir() && Y.IsDir() { + // return false + // } else { + // return X.Name() < Y.Name() + // } + //}) + + // build tree nodes + for iter.Next() { + sel := iter.Selector() + value := iter.Value() + attrs := value.Attributes(cue.ValueAttr) + + fullpath := path + // input value that we are iterating over + switch val.IncompleteKind() { + case cue.ListKind: + fullpath += fmt.Sprintf("[%s]", sel) + default: + fullpath += fmt.Sprintf(".%s", sel) + } + + + var node *tview.TreeNode + + var buf bytes.Buffer + for _, a := range attrs { + fmt.Fprintf(&buf, "%v", a) + } + attr := buf.String() + + switch value.IncompleteKind() { + case cue.StructKind: + l := fmt.Sprintf("{ %s }:", sel) + line := fmt.Sprintf("%-42s [goldenrod]%s", l, attr) + node = tview.NewTreeNode(line) + node. + SetColor(tcell.ColorCornflowerBlue). + SetSelectable(true) + + case cue.ListKind: + l := fmt.Sprintf("[ %s ]:", sel) + node = tview.NewTreeNode(l) + node. + SetColor(tcell.ColorLime). + SetSelectable(true) + + + default: + l := fmt.Sprintf("%s: %v %s", sel, value, attr) + node = tview.NewTreeNode(l) + + } + + + node.SetReference(fullpath) + target.AddChild(node) + } +} diff --git a/lib/tui/components/value-evaluator.go b/lib/tui/components/value-evaluator.go new file mode 100644 index 000000000..de8a1b037 --- /dev/null +++ b/lib/tui/components/value-evaluator.go @@ -0,0 +1,102 @@ +package components + +import ( + "fmt" + "time" + + "cuelang.org/go/cue" + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" + + "github.com/hofstadter-io/hof/lib/cuetils" + "github.com/hofstadter-io/hof/lib/tui/app" + "github.com/hofstadter-io/hof/lib/watch" +) + +type ValueEvaluator struct { + *tview.Flex + + App *app.App + + Edit *tview.TextArea + View *ValueBrowser + + // that's funky! + debouncer func(func()) +} + +func NewValueEvaluator(app *app.App) (*ValueEvaluator) { + root := &ValueEvaluator{ + App: app, + } + + root.Flex = tview.NewFlex().SetDirection(tview.FlexRow) + // with two panels + + // TODO, options form + + // editor + root.Edit = tview.NewTextArea() + root.Edit. + SetTitle("expression(s)"). + SetBorder(true) + + // results + root.View = NewValueBrowser(app, app.Runtime.Value, func(string){}) + root.View. + SetTitle("results"). + SetBorder(true) + + // layout + root.Flex. + AddItem(root.Edit, 0, 1, true). + AddItem(root.View, 0, 2, false) + + + // change debouncer + root.debouncer = watch.NewDebouncer(time.Millisecond * 500) + root.Edit.SetChangedFunc(func() { + + root.App.Logger(".") + + root.debouncer(func(){ + root.App.Logger("!") + ctx := root.App.Runtime.CueContext + val := root.App.Runtime.Value + src := root.Edit.GetText() + + v := ctx.CompileString(src, cue.Scope(val)) + + if v.Err() != nil { + s := cuetils.CueErrorToString(v.Err()) + root.App.Logger(s) + } else { + root.View.Value = v + root.View.Rebuild(v.Path().String()) + root.App.Logger("$") + } + root.App.Draw() + }) + + }) + + + + // key handlers + root.Edit.SetInputCapture(func(ek *tcell.EventKey) *tcell.EventKey { + + root.App.Logger(fmt.Sprintln("edit: ", ek.Key() == tcell.KeyCtrlSpace, (ek.Key() == tcell.KeyCR), ek.Modifiers() & tcell.ModCtrl)) + + if (ek.Key() == tcell.KeyCR) && (ek.Modifiers() & tcell.ModCtrl == tcell.ModCtrl) { + + root.App.Logger("eval...\n") + + return nil + } + + // we aren't handling this + return ek + }) + + return root +} diff --git a/lib/tui/keys/keys.go b/lib/tui/keys/keys.go new file mode 100644 index 000000000..7f4354748 --- /dev/null +++ b/lib/tui/keys/keys.go @@ -0,0 +1,3 @@ +package tui + +// all the key & mouse handlers diff --git a/lib/tui/layouts/default.go b/lib/tui/layouts/default.go new file mode 100644 index 000000000..32ee5914e --- /dev/null +++ b/lib/tui/layouts/default.go @@ -0,0 +1,37 @@ +package layouts + +import ( + "github.com/rivo/tview" +) + +type DefaultLayout struct { + *tview.Flex + + Main *tview.Flex + + Tree tview.Primitive + Text tview.Primitive + Repl tview.Primitive +} + +func NewDefaultLayout(tree, text, repl tview.Primitive) *DefaultLayout { + layout := &DefaultLayout{ + Tree: tree, + Text: text, + Repl: repl, + } + + // flexbox + layout.Flex = tview.NewFlex() + layout.Main = tview.NewFlex().SetDirection(tview.FlexRow) + + layout.Flex. + AddItem(layout.Tree, 42, 1, false). + AddItem(layout.Main, 0, 2, false) + + layout.Main. + AddItem(layout.Text, 0, 3, false). + AddItem(layout.Repl, 10, 1, false) + + return layout +} diff --git a/lib/tui/layouts/evaluator.go b/lib/tui/layouts/evaluator.go new file mode 100644 index 000000000..479d39aed --- /dev/null +++ b/lib/tui/layouts/evaluator.go @@ -0,0 +1,37 @@ +package layouts + +import ( + "github.com/rivo/tview" +) + +type EvaluatorLayout struct { + *tview.Flex + + Main *tview.Flex + View tview.Primitive + Eval tview.Primitive + Repl tview.Primitive +} + +func NewEvaluatorLayout(view, eval, repl tview.Primitive) *EvaluatorLayout { + + layout := &EvaluatorLayout{ + View: view, + Eval: eval, + Repl: repl, + } + + // main layout + layout.Main = tview.NewFlex() + layout.Main. + AddItem(layout.View, 0, 1, false). + AddItem(layout.Eval, 0, 1, false) + + // page-layout + layout.Flex = tview.NewFlex().SetDirection(tview.FlexRow) + layout.Flex. + AddItem(layout.Main, 0, 2, false). + AddItem(layout.Repl, 10, 1, false) + + return layout +} diff --git a/lib/tui/old/demo.go b/lib/tui/old/demo.go new file mode 100644 index 000000000..c53eec0f9 --- /dev/null +++ b/lib/tui/old/demo.go @@ -0,0 +1,168 @@ +package main + +import ( + "fmt" + "io" + "io/ioutil" + "log" + "strconv" + "strings" + "time" + + "github.com/chzyer/readline" +) + +func usage(w io.Writer) { + io.WriteString(w, "commands:\n") + io.WriteString(w, completer.Tree(" ")) +} + +// Function constructor - constructs new function for listing given directory +func listFiles(path string) func(string) []string { + return func(line string) []string { + names := make([]string, 0) + files, _ := ioutil.ReadDir(path) + for _, f := range files { + names = append(names, f.Name()) + } + return names + } +} + +var completer = readline.NewPrefixCompleter( + readline.PcItem("mode", + readline.PcItem("vi"), + readline.PcItem("emacs"), + ), + readline.PcItem("login"), + readline.PcItem("say", + readline.PcItemDynamic(listFiles("./"), + readline.PcItem("with", + readline.PcItem("following"), + readline.PcItem("items"), + ), + ), + readline.PcItem("hello"), + readline.PcItem("bye"), + ), + readline.PcItem("setprompt"), + readline.PcItem("setpassword"), + readline.PcItem("bye"), + readline.PcItem("help"), + readline.PcItem("go", + readline.PcItem("build", readline.PcItem("-o"), readline.PcItem("-v")), + readline.PcItem("install", + readline.PcItem("-v"), + readline.PcItem("-vv"), + readline.PcItem("-vvv"), + ), + readline.PcItem("test"), + ), + readline.PcItem("sleep"), +) + +func filterInput(r rune) (rune, bool) { + switch r { + // block CtrlZ feature + case readline.CharCtrlZ: + return r, false + } + return r, true +} + +func main() { + l, err := readline.NewEx(&readline.Config{ + Prompt: "\033[31m»\033[0m ", + HistoryFile: "/tmp/readline.tmp", + AutoComplete: completer, + InterruptPrompt: "^C", + EOFPrompt: "exit", + + HistorySearchFold: true, + FuncFilterInputRune: filterInput, + }) + if err != nil { + panic(err) + } + defer l.Close() + l.CaptureExitSignal() + + setPasswordCfg := l.GenPasswordConfig() + setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { + l.SetPrompt(fmt.Sprintf("Enter password(%v): ", len(line))) + l.Refresh() + return nil, 0, false + }) + + log.SetOutput(l.Stderr()) + for { + line, err := l.Readline() + if err == readline.ErrInterrupt { + if len(line) == 0 { + break + } else { + continue + } + } else if err == io.EOF { + break + } + + line = strings.TrimSpace(line) + switch { + case strings.HasPrefix(line, "mode "): + switch line[5:] { + case "vi": + l.SetVimMode(true) + case "emacs": + l.SetVimMode(false) + default: + println("invalid mode:", line[5:]) + } + case line == "mode": + if l.IsVimMode() { + println("current mode: vim") + } else { + println("current mode: emacs") + } + case line == "login": + pswd, err := l.ReadPassword("please enter your password: ") + if err != nil { + break + } + println("you enter:", strconv.Quote(string(pswd))) + case line == "help": + usage(l.Stderr()) + case line == "setpassword": + pswd, err := l.ReadPasswordWithConfig(setPasswordCfg) + if err == nil { + println("you set:", strconv.Quote(string(pswd))) + } + case strings.HasPrefix(line, "setprompt"): + if len(line) <= 10 { + log.Println("setprompt ") + break + } + l.SetPrompt(line[10:]) + case strings.HasPrefix(line, "say"): + line := strings.TrimSpace(line[3:]) + if len(line) == 0 { + log.Println("say what?") + break + } + go func() { + for range time.Tick(time.Second) { + log.Println(line) + } + }() + case line == "bye": + goto exit + case line == "sleep": + log.Println("sleep 4 second") + time.Sleep(4 * time.Second) + case line == "": + default: + log.Println("you said:", strconv.Quote(line)) + } + } +exit: +} diff --git a/lib/tui/old/multiline.go b/lib/tui/old/multiline.go new file mode 100644 index 000000000..2192cf6d8 --- /dev/null +++ b/lib/tui/old/multiline.go @@ -0,0 +1,41 @@ +package main + +import ( + "strings" + + "github.com/chzyer/readline" +) + +func main() { + rl, err := readline.NewEx(&readline.Config{ + Prompt: "> ", + HistoryFile: "/tmp/readline-multiline", + DisableAutoSaveHistory: true, + }) + if err != nil { + panic(err) + } + defer rl.Close() + + var cmds []string + for { + line, err := rl.Readline() + if err != nil { + break + } + line = strings.TrimSpace(line) + if len(line) == 0 { + continue + } + cmds = append(cmds, line) + if !strings.HasSuffix(line, ";") { + rl.SetPrompt(">>> ") + continue + } + cmd := strings.Join(cmds, " ") + cmds = cmds[:0] + rl.SetPrompt("> ") + rl.SaveHistory(cmd) + println(cmd) + } +} diff --git a/lib/tui/pages/about.go b/lib/tui/pages/about.go new file mode 100644 index 000000000..76e382dbd --- /dev/null +++ b/lib/tui/pages/about.go @@ -0,0 +1 @@ +package pages diff --git a/lib/tui/pages/eval.go b/lib/tui/pages/eval.go new file mode 100644 index 000000000..a5c8e1104 --- /dev/null +++ b/lib/tui/pages/eval.go @@ -0,0 +1,57 @@ +package pages + +import ( + "strings" + "github.com/rivo/tview" + + "github.com/hofstadter-io/hof/lib/tui/app" + "github.com/hofstadter-io/hof/lib/tui/components" + "github.com/hofstadter-io/hof/lib/tui/layouts" +) + +type EvalPage struct { + *Page + + App *app.App + + View *components.ValueBrowser + Eval *components.ValueEvaluator + Repl *components.Shell +} + +func NewEvalPage(app *app.App) *EvalPage { + page := &EvalPage{ + Page: new(Page), + App: app, + } + + // setup shell + page.Repl = components.NewShell(app) + app.Logger = page.Repl.Append + + // setup file browser + onNodeSelect := func(path string) { + // app.Logger("onNodeSelect: " + path) + } + page.View = components.NewValueBrowser(app, app.Runtime.Value, onNodeSelect) + // file browser + n := app.Runtime.Value.Path().String() + page.View.Rebuild(n) + + + + page.View.SetTitle(strings.Join(page.App.Runtime.Entrypoints, " ")) + + page.Eval = components.NewValueEvaluator(app) + + + page.Name = "Value Browser" + layout := layouts.NewEvaluatorLayout(page.View, page.Eval, page.Repl) + page.Flex = layout.Flex + + return page +} + +func (P *EvalPage) Focus(delegate func(p tview.Primitive)) { + delegate(P.View) +} diff --git a/lib/tui/pages/flow.go b/lib/tui/pages/flow.go new file mode 100644 index 000000000..76e382dbd --- /dev/null +++ b/lib/tui/pages/flow.go @@ -0,0 +1 @@ +package pages diff --git a/lib/tui/pages/help.go b/lib/tui/pages/help.go new file mode 100644 index 000000000..76e382dbd --- /dev/null +++ b/lib/tui/pages/help.go @@ -0,0 +1 @@ +package pages diff --git a/lib/tui/pages/hof.go b/lib/tui/pages/hof.go new file mode 100644 index 000000000..76e382dbd --- /dev/null +++ b/lib/tui/pages/hof.go @@ -0,0 +1 @@ +package pages diff --git a/lib/tui/pages/page.go b/lib/tui/pages/page.go new file mode 100644 index 000000000..d2a083fe2 --- /dev/null +++ b/lib/tui/pages/page.go @@ -0,0 +1,11 @@ +package pages + +import ( + "github.com/rivo/tview" +) + +type Page struct { + Name string + + *tview.Flex +} diff --git a/lib/tui/pages/vem.go b/lib/tui/pages/vem.go new file mode 100644 index 000000000..37882d340 --- /dev/null +++ b/lib/tui/pages/vem.go @@ -0,0 +1,64 @@ +package pages + +import ( + "os" + + "github.com/rivo/tview" + + "github.com/hofstadter-io/hof/lib/tui/app" + "github.com/hofstadter-io/hof/lib/tui/components" + "github.com/hofstadter-io/hof/lib/tui/layouts" +) + +type VemPage struct { + *Page + + App *app.App + + Tree *components.FileBrowser + Text *components.TextEditor + Repl *components.Shell +} + +func NewVemPage(app *app.App, dir string) *VemPage { + page := &VemPage{ + Page: new(Page), + App: app, + } + + // setup shell + page.Repl = components.NewShell(app) + app.Logger = page.Repl.Append + + // setup text editor + onTextUpdate := func() { + // page.App.Logger("onTextUpdate") + } + page.Text = components.NewTextEditor(app, onTextUpdate) + + // setup file browser + onFileSelect := func(path string) { + page.Text.OpenFile(path) + } + page.Tree = components.NewFileBrowser(app, dir, onFileSelect) + + + page.Name = "vem" + layout := layouts.NewDefaultLayout( + page.Tree, page.Text, page.Repl, + ) + page.Flex = layout.Flex + + return page +} + +func (P *VemPage) Focus(delegate func(p tview.Primitive)) { + P.App.Logger("VemPage.Focus\n") + info, _ := os.Lstat(P.Tree.Dir) + if info.IsDir() { + delegate(P.Tree) + } else { + delegate(P.Text) + } + +} diff --git a/lib/tui/readme.md b/lib/tui/readme.md new file mode 100644 index 000000000..9f8c3f8ed --- /dev/null +++ b/lib/tui/readme.md @@ -0,0 +1,4 @@ +- https://github.com/skanehira/tson +- https://github.com/gdamore/tcell +- https://github.com/rivo/tview +- https://github.com/creack/pty