Implement tab completion

The liner library used in internal/reader has a
system for tab completion, so we might as well
use it.

Change-Id: Ic85eeb01263b96e0aaf2a751f2658337ff67a5f6
diff --git a/cmd/sb/internal/reader/reader.go b/cmd/sb/internal/reader/reader.go
index d4a0db1..9c481b2 100644
--- a/cmd/sb/internal/reader/reader.go
+++ b/cmd/sb/internal/reader/reader.go
@@ -13,6 +13,8 @@
 	"text/scanner"
 
 	"github.com/peterh/liner"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/cmd/sb/commands"
 )
 
 type T struct {
@@ -121,6 +123,8 @@
 		line: liner.NewLiner(),
 	}
 	i.line.SetCtrlCAborts(true)
+	i.line.SetCompleter(complete)
+	i.line.SetTabCompletionStyle(liner.TabPrints)
 	return newT(i)
 }
 
@@ -139,3 +143,43 @@
 func (i *interactive) AppendHistory(query string) {
 	i.line.AppendHistory(query)
 }
+
+func complete(line string) []string {
+	// get command list to tab-complete on
+	tokens := strings.Split(line, " ")
+	cmds := commands.Commands
+	for _, token := range tokens[:len(tokens)-1] {
+		nextCommand := find(token, cmds)
+		if nextCommand == nil {
+			return nil
+		} else {
+			cmds = nextCommand.Children
+		}
+	}
+
+	lineMinusLastToken := strings.Join(tokens[:len(tokens)-1], " ")
+	if len(lineMinusLastToken) > 0 {
+		lineMinusLastToken += " "
+	}
+
+	// tab complete on command list
+	lastToken := tokens[len(tokens)-1]
+	var ret []string
+	for _, cmd := range cmds {
+		name := cmd.Name
+		if strings.HasPrefix(name, lastToken) {
+			ret = append(ret, lineMinusLastToken+name)
+		}
+	}
+
+	return ret
+}
+
+func find(name string, cmds []*cmdline.Command) *cmdline.Command {
+	for _, cmd := range cmds {
+		if cmd.Name == name {
+			return cmd
+		}
+	}
+	return nil
+}