Skip to content

Commit

Permalink
improved autocomplete for web version
Browse files Browse the repository at this point in the history
  • Loading branch information
mike-mcgann committed Feb 27, 2024
1 parent c9a4c81 commit f89aeb9
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 38 deletions.
37 changes: 37 additions & 0 deletions cmd/wasm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@ var (
r *repl.REPL
)

func zcCommonPrefix() js.Func {
return js.FuncOf(func(this js.Value, args []js.Value) any {
if len(args) != 1 {
panic("zcCommonPrefix: invalid number of arguments")
}
jsValues := args[0]
var outValues []string
for i := 0; i < jsValues.Length(); i++ {
outValues = append(outValues, jsValues.Index(i).String())
}
common := repl.CommonPrefix(outValues)
return common
})
}

func zcEval() js.Func {
return js.FuncOf(func(this js.Value, args []js.Value) any {
in := args[0].String()
Expand Down Expand Up @@ -77,14 +92,36 @@ func zcSetStack() js.Func {
})
}

func zcWordCompleter() js.Func {
return js.FuncOf(func(this js.Value, args []js.Value) any {
if len(args) != 2 {
panic("zcWordCompleter: invalid number of arguments")
}
line := args[0].String()
pos := args[1].Int()
prefix, candidates, suffix := r.WordCompleter(line, pos)
var jsCandidates []any
for _, c := range candidates {
jsCandidates = append(jsCandidates, c)
}
return map[string]any{
"prefix": prefix,
"candidates": jsCandidates,
"suffix": suffix,
}
})
}

func main() {
c = calc.New()
r = repl.New(c)

js.Global().Set("zcCommonPrefix", zcCommonPrefix())
js.Global().Set("zcEval", zcEval())
js.Global().Set("zcStack", zcStack())
js.Global().Set("zcStackLen", zcStackLen())
js.Global().Set("zcOpNames", zcOpNames())
js.Global().Set("zcSetStack", zcSetStack())
js.Global().Set("zcWordCompleter", zcWordCompleter())
<-make(chan struct{})
}
35 changes: 31 additions & 4 deletions pkg/repl/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (r *REPL) Init() {
r.cli = liner.NewLiner()
r.cli.SetCtrlCAborts(true)
r.cli.SetTabCompletionStyle(liner.TabPrints)
r.cli.SetWordCompleter(r.wordCompleter)
r.cli.SetWordCompleter(r.WordCompleter)

r.loadHistory()

Expand Down Expand Up @@ -181,13 +181,12 @@ func (r *REPL) getPrompt() string {
return zc.ProgName + " > "
}

func (r *REPL) wordCompleter(line string, pos int) (string, []string, string) {
func (r *REPL) WordCompleter(line string, pos int) (string, []string, string) {
endPos := pos
for endPos < len(line) {
if line[endPos] == ' ' {
break
}
endPos++
}
startPos := pos - 1
if startPos < 0 {
Expand All @@ -214,10 +213,38 @@ func (r *REPL) wordCompleter(line string, pos int) (string, []string, string) {
}
}
sort.Strings(candidates)
//fmt.Printf("\n[%v] (%v)[%v] [%v]\n", prefix, word, candidates, suffix)
return prefix, candidates, suffix
}

func CommonPrefix(vals []string) string {
if len(vals) == 0 {
return ""
}
var result []rune
for i, sval := range vals {
val := []rune(sval)
if i == 0 {
result = val
continue
}
if len(result) == 0 {
return ""
}
for j, a := range result {
if j >= len(val) {
result = result[:j]
break
}
b := val[j]
if a != b {
result = result[:j]
break
}
}
}
return string(result)
}

func colorize(color string, text string) string {
if !ansi.Enabled {
return zc.FormatStackItem(text)
Expand Down
23 changes: 23 additions & 0 deletions pkg/repl/repl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,26 @@ func TestQuote(t *testing.T) {
t.Fatalf("\n have: %v \n want: %v", have, want)
}
}

func TestCommonPrefix(t *testing.T) {
tests := []struct {
common string
vals []string
}{
{"abc", []string{"abc", "abc", "abc"}},
{"a", []string{"abc", "ab", "a"}},
{"a", []string{"a", "ab", "abc"}},
{"", []string{"a", "b", "c"}},
{"abc", []string{"abcde", "abcfg", "abch"}},
{"char-c", []string{"char-codepoint", "char-cp"}},
}

for _, test := range tests {
t.Run(test.common, func(t *testing.T) {
common := CommonPrefix(test.vals)
if common != test.common {
t.Errorf("\n have: %v \n want: %v", common, test.common)
}
})
}
}
66 changes: 32 additions & 34 deletions web/zc.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
var stackHist = []
var commandHist = []
var histPos = -1
var showCandidates = false

// var showCandidates = false
var tabs = 0

function submit() {
let line = document.querySelector("#input").value
Expand Down Expand Up @@ -97,41 +99,32 @@ function moveToEnd() {

function autoComplete() {
let e = document.querySelector('#input')
let pos = e.selectionEnd - 1
if (pos <= 0) {
pos = 0
}
let searchFor = ''
for (let i = pos; i >= 0; i--) {
if (e.value[i] === ' ') {
break
}
searchFor = e.value[i] + searchFor
}
if (searchFor === '') {

if (e.value.trim().length === 0) {
return
}

let candidates = zcOpNames().filter((e) => e.startsWith(searchFor))
if (candidates.length > 50) {
candidates = candidates.slice(0, 50)
candidates.push("...")
}
console.log('value', e.value, 'pos', e.selectionEnd)
let r = zcWordCompleter(e.value, e.selectionEnd)
console.log(r)
let common = zcCommonPrefix(r.candidates)
console.log(common)

if (candidates.length === 0) {
showCandidates = false
} else if (candidates.length === 1) {
let toAdd = candidates[0].slice(searchFor.length)
let head = e.value.slice(0, pos + 1)
let tail = e.value.slice(pos + 1)
e.value = head + toAdd + tail
e.selectionStart = e.selectionEnd = pos + toAdd.length + 1
} else if (!showCandidates) {
showCandidates = true
var middle = ''
if (r.candidates.length === 0) {
tabs = 0
} else if (r.candidates.length == 1) {
middle = r.candidates[0]
tabs = 0
} else {
candidates = candidates.map((e) => e.replace("&", "&amp;"))
document.querySelector("#popup").innerHTML = candidates.join(' ')
middle = common
if (tabs >= 2) {
let candidates = r.candidates.map((e) => e.replace("&", "&amp;"))
document.querySelector("#popup").innerHTML = candidates.join(' ')
}
}
e.value = r.prefix + middle + r.suffix
e.selectionStart = e.selectionEnd = r.prefix.length + middle.length
}

function clearPopup() {
Expand Down Expand Up @@ -162,14 +155,19 @@ window.onload = function() {
document.querySelector('#input').onkeydown = function(evt) {
clearPopup()
let keyCode = evt.code || evt.key

if (keyCode === 'Tab') {
tabs++
} else {
tabs = 0
}

if (keyCode === 'ArrowUp') {
up()
clearPopup()
showCandidates = false
} else if (keyCode === 'ArrowDown') {
down()
clearPopup();
showCandidates = false
clearPopup()
} else if (keyCode === 'Tab') {
autoComplete()
evt.preventDefault()
Expand All @@ -179,7 +177,7 @@ window.onload = function() {
}

document.querySelector('#auto').onclick = function(evt) {
showCandidates = true
tabs++
autoComplete()
document.querySelector('#input').focus()
}
Expand Down

0 comments on commit f89aeb9

Please sign in to comment.