New Otto Feature: REPL Autocomplete
Apr 27, 2016
2 minutes read

While you're reading this, keep in mind that I'm available for hire! If you've got a JavaScript project getting out of hand, or a Golang program that's more "stop" than "go," feel free to get in touch with me. I might be able to help you. You can find my resume here.

Today I added support for tab completion to otto’s REPL. It’s a pretty early implementation, but it’s already pretty useful. In the future, I hope to port the REPL behaviour from node.js, but it’s incredibly complex and some of it might not even make sense in the context of otto.

If you’re using otto in a project, I’d love to hear about it! If you have any issues or questions while you’re using otto, please feel free to contact me or open an issue on GitHub. I’m also available to add features to otto on a commission basis.

Right now, it’ll autocomplete the names of object properties and the names of variables in scope. It’ll only complete property names if there are no computed properties in the current expression. That is, if you have a.b., it’ll try to autocomplete the end when you hit tab. If you instead have a['b']., it might get confused.

The way that it works is that it (rather na├»vely) finds the last expression in the current line, evaluates all but the last segment of it. If the result of that evaluation is an object, it uses the keys of it, and anything above it in the prototype chain, as candidates for autocompletion. An example: suppose you’ve run a = {b1: 1, b2: 2, c: 3}, and you put a. into the terminal and hit tab. Otto will evaluate a, get the keys b1, b2, and c, filter them down to only b1 and b2 (since they begin with b), and display them as options.

A picture says a thousand words.

animated demo of autocomplete functionality in a terminal

So, anyway, if you’ve gotten this far you probably want to give this a go yourself. Here’s some code that you can run to try it out.

package main

import (
  "github.com/robertkrimen/otto"
  "github.com/robertkrimen/otto/repl"
)

func main() {
  vm := otto.New()

  if err := repl.Run(vm); err != nil {
    panic(err)
  }
}

To make this all happen, I had to implement a new function on otto’s Object type - KeysByParent() [][]string. It returns a slice of slices (a two-dimensional array in other languages) representing the keys of that object, the keys of its prototype, the keys of its prototype’s prototype, and so on. Previously, there was only the Keys() []string function, which returns the keys of that particular object. That Keys() []string function purposefully doesn’t follow the prototype chain to find more accessible keys.


Back to posts