Namek Dev
a developer's log
NamekDev

Argument completion for commands in The Console

May 21, 2016

Argument completion should improve developer’s everyday life. Everywhere. That’s why I decided to have argument completion in The Console. To have the best thing - I want scriptable argument completion with already built-in filesystem paths completion.

Giving a few words about the process between hitting a TAB key and performing the magic.

Completion algorithm

Argument completion analyzes everything before caret position and after first token which is supposed to be a command name.

Let’s take a look at example of path completion:

  1. given cd c:/program f[caret_here]z
  2. it will ignore last character (the letter z) and cd which is a command name, that gives c:/program f
  3. tokenize selected part by spaces or taking quotes into account (e.g. “this string is a full token”  because it’s contained in quotes ")
  4. first take last token and pass it to completion algorithms, in this case it will be just f
  5. the token (f) cannot be completed, take one more token (last two tokens) and try auto-complete again, in this case it will take c:/program f
  6. the last string will be probably completed to C:\Program Files\

Now, what does it mean to pass an argument to completion algorithms:

  1. ask custom (scripted) command completion (module or script)
  2. ask built-in absolute path completion
  3. all results are gathered and then:
    1. if there are multiple results then display dynamic list to choose between options
    2. if there is only one option then apply it, by replacing the part with suggestion
    3. if there is no completion suggestion then continue with no action

Scripting: custom completion

Now, let’s apply it to modules and scripts in The Console.

Modules

Modules can implement multiple commands. Some of commands could auto-complete in different ways.

In other situation every single command in module could run argument completion the same way, e.g. filesystem module commands are always about file/folder path completion.

Let’s take a look at this module definition:

var currentDir = 'C:/'
module.exports = {
    onload: function() { },
    onunload: function() { },
    commands: {
        cd: function(args) {
            /* TODO change directory */
        },
        ls: function(args) {
            /* TODO list current directory contents */
        }
    }
}

We’ve got two commands here: cd  and ls . Both receive path arguments.

module.exports = {
    commands: {
        cd: function(args) { /* ... */ },
        ls: function(args) { /* ... */ }
    },

    // this argument completer will work for all commands of this module
    completeArgument: function(arg) {
        // simply use built-in algorithm to complete paths
        return Utils.completePath(arg)
    }
}

This way, all argument for commands in this module are auto-completed using a single function.

Another way to provide argument completion is to annotate command function with _completeArgument property. First implement path completer and a simple helper to wrap command functions which will improve readability:

function completePath(arg) {
    // simply use built-in algorithm to complete paths
    return Utils.completePath(arg)
}

function withCompletion(func) {
    func._completeArgument = completePathArgument
    return func
}

and apply it to the cd command:

module.exports = {
    commands: {
        cd: withCompletion(function(args) { /* ... */ }),
        ls: function(args) { /* ... */ }
    }
}

Single-file scripts

Single-file script is a code evaluated in JavaScript engine every time when command is invoked. That’s why it’s not easy to predefine whether script provides argument completion or not before evaluation is performed.

For now, a solution is to prepend script file with something like this:

/// provides: argumentCompletion

if (argToComplete) {
    return Utils.completePath(argToComplete)
}

Where provides  is supposed to have an array of features that script supports.

So, a simple case of pwd  command script would be:

/// provides: argumentCompletion

if (argToComplete) {
    return Utils.completePath(argToComplete)
}

assert(args.length == 0, "Please, no args.")
console.log(getCurrentPath())

Summary

By all means, argument completion for paths is an easy (well, somehow) job to do. However, sometimes user may want to provide more sophisticated ways of completing argument, e.g. listing possible argument options or fixing argument formats like date, time or other values. The Console gives this feature and even more - it’s even possible to print an error during completion!

Daj Się Poznać, the-console
comments powered by Disqus