Skip to content

Commit

Permalink
Merge pull request #19 from Syuparn/enable-cursor
Browse files Browse the repository at this point in the history
enable to use cursor
  • Loading branch information
Syuparn authored Apr 16, 2023
2 parents f83fcfb + 566211d commit b556a46
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 14 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ Hello, world!
tmpl:4> ^C
```

In REPL mode, you can use histories and function autocompletes. See [peterh/liner](https://github.com/peterh/liner) for details.

# functions

Functions in [Sprig](http://masterminds.github.io/sprig/) are available.
Expand Down
70 changes: 70 additions & 0 deletions completer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package main

import (
"strings"

"github.com/peterh/liner"
)

func newWordCompleter() liner.WordCompleter {
return func(line string, pos int) (head string, completions []string, tail string) {
head, partial, tail := splitLine(line, pos)

if partial == "" {
// match to nothing
return
}

// function auto-completes
for name := range funcMap() {
if strings.HasPrefix(name, partial) {
completions = append(completions, name)
}
}
return
}
}

func splitLine(line string, pos int) (head, partial, tail string) {
startPos := partialStartPos(line, pos, "{}()<>=| ")
endPos := partialEndPos(line, pos, "{}()<>=| ")

head = subStr(line, 0, startPos)
tail = subStr(line, endPos, len(line))
partial = subStr(line, startPos, endPos)
return
}

func partialStartPos(line string, pos int, delims string) int {
delimPos := strings.LastIndexAny(subStr(line, 0, pos), delims)
if delimPos < 0 {
return 0
}
return delimPos + 1
}

func partialEndPos(line string, pos int, delims string) int {
relativeDelimPos := strings.IndexAny(subStr(line, pos, len(line)), delims)
if relativeDelimPos < 0 {
return len(line)
}
return pos + relativeDelimPos
}

func subStr(str string, start, end int) string {
if start >= len(str) {
return ""
}
if end <= 0 {
return ""
}

if end > len(str) {
end = len(str)
}
if start < 0 {
start = 0
}

return str[start:end]
}
123 changes: 123 additions & 0 deletions completer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package main

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestNewCompleter(t *testing.T) {
c := newWordCompleter()

type completion struct {
head string
completions []string
tail string
}

tests := []struct {
title string
line string
pos int
expected completion
}{
{
"empty",
"",
0,
completion{
head: "",
completions: []string{},
tail: "",
},
},
{
"only partials",
"low",
3,
completion{
head: "",
completions: []string{"lower"},
tail: "",
},
},
{
"cursor is not at the end",
"low",
1,
completion{
head: "",
completions: []string{"lower"},
tail: "",
},
},
{
"two tokens",
"if low",
6,
completion{
head: "if ",
completions: []string{"lower"},
tail: "",
},
},
{
"two tokens and cursor in the middle",
"if low",
3,
completion{
head: "if ",
completions: []string{"lower"},
tail: "",
},
},
{
"cursor points non-function",
"if low",
0,
completion{
head: "",
completions: []string{},
tail: " low",
},
},
{
"many tokens (cursor at the end)",
"1 | add int6",
12,
completion{
head: "1 | add ",
completions: []string{"int64"},
tail: "",
},
},
{
"many tokens (cursor in the middle)",
"1 | ad int64",
4,
completion{
head: "1 | ",
completions: []string{"add", "add1f", "add1", "addf", "adler32sum"},
tail: " int64",
},
},
}

for _, tt := range tests {
t.Run(tt.title, func(t *testing.T) {
head, completions, tail := c(tt.line, tt.pos)

assert.Equal(t, tt.expected.head, head, "wrong head")
assert.ElementsMatch(t, tt.expected.completions, completions, "wrong completions")
assert.Equal(t, tt.expected.tail, tail, "wrong tail")
})
}
}

func allFuncNames() []string {
names := []string{}
for name := range funcMap() {
names = append(names, name)
}
return names
}
9 changes: 8 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,23 @@ require (
github.com/google/uuid v1.2.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/mitchellh/copystructure v1.1.1 // indirect
github.com/stretchr/testify v1.7.0 // indirect
github.com/stretchr/testify v1.7.0
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
)

require github.com/peterh/liner v1.2.2

require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/imdario/mergo v0.3.11 // indirect
github.com/mattn/go-runewidth v0.0.3 // indirect
github.com/mitchellh/reflectwalk v1.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@ github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.1.1 h1:Bp6x9R1Wn16SIz3OfeDr0b7RnCG2OB66Y7PQyC/cvq4=
github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE=
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw=
github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI=
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/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
Expand All @@ -40,10 +44,13 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 h1:kwrAHlwJ0DUBZwQ238v+Uod/3eZ8B2K5rYsUHBQvzmI=
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
Expand Down
35 changes: 22 additions & 13 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"text/template"

"golang.org/x/xerrors"

"github.com/peterh/liner"
)

var (
Expand Down Expand Up @@ -85,28 +87,35 @@ func runPipeMode(tmplGen *template.Template, tmplStr string) error {
}

func runREPLMode(tmplGen *template.Template) error {
scanner := bufio.NewScanner(os.Stdin)
line := liner.NewLiner()
defer line.Close()

line.SetCtrlCAborts(true)
line.SetWordCompleter(newWordCompleter())

tmplStr := ""
lineNum := 1

for {
// show prompt
fmt.Printf("tmpl:%d> ", lineNum)

ok := scanner.Scan()
if !ok {
// end of repl
// HACK: prepend \n to break line even if repl is stopped by SIGINT
fmt.Println("\nBye.")
return nil
inputStr, err := line.Prompt(fmt.Sprintf("tmpl:%d> ", lineNum))
if err != nil {
if err == liner.ErrPromptAborted {
// end of repl
// HACK: prepend \n to break line even if repl is stopped by SIGINT
fmt.Println("\nBye.")
return nil
}
fmt.Fprintf(os.Stderr, "input error:\n%v\n", err)
continue
}

line := scanner.Text() + "\n"
line.AppendHistory(inputStr)
newLine := inputStr + "\n"

// NOTE: whole history is necessary to refer previous variable statement
// HACK: insert "\034"(, which is not printable) to detect
// output generated by the latest input line
tmpl, err := tmplGen.Parse(tmplStr + "\034" + line)
tmpl, err := tmplGen.Parse(tmplStr + "\034" + newLine)
if err != nil {
fmt.Fprintf(os.Stderr, "template error:\n%v\n", err)
continue
Expand All @@ -129,7 +138,7 @@ func runREPLMode(tmplGen *template.Template) error {
diffStr := splitted[1]
fmt.Println(diffStr)

tmplStr = tmplStr + line
tmplStr = tmplStr + newLine
lineNum++
}
}
Expand Down

0 comments on commit b556a46

Please sign in to comment.