merge m-c to fx-team
authorTim Taubert <tim.taubert@gmx.de>
Fri, 06 Jul 2012 12:59:15 +0200
changeset 98461 874900dd7b775c666b4d333b1e544f262d92da6d
parent 98460 4b1249ae1906544429f9cf7ba4b77da31f78a89b (current diff)
parent 98379 f8a4f9332cc824ce133d8fb329be2107dd6e1a55 (diff)
child 98462 eb12d458e0fbc90e286472d287341461ec004674
child 98515 a8f682801a6d7c2bd2a65562f1b217e332caa1bb
push id900
push usertim.taubert@gmx.de
push dateFri, 06 Jul 2012 10:59:57 +0000
treeherderfx-team@874900dd7b77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone16.0a1
merge m-c to fx-team
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -563,16 +563,26 @@ html|*#gcli-output-frame,
 }
 
 .gclitoolbar-input-node,
 .gclitoolbar-complete-node,
 .gclitoolbar-prompt {
   direction: ltr;
 }
 
+#developer-toolbar-webconsole[error-count] > .toolbarbutton-icon {
+  display: none;
+}
+
+#developer-toolbar-webconsole[error-count]:before {
+  content: attr(error-count);
+  display: -moz-box;
+  -moz-box-pack: center;
+}
+
 /* Responsive Mode */
 
 vbox[anonid=browserContainer][responsivemode] {
   overflow: auto;
 }
 
 .devtools-responsiveui-toolbar:-moz-locale-dir(rtl) {
   -moz-box-pack: end;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1395,17 +1395,17 @@ var gBrowserInit = {
       document.getElementById("menu_devToolbar").hidden = false;
       document.getElementById("Tools:DevToolbar").removeAttribute("disabled");
 #ifdef MENUBAR_CAN_AUTOHIDE
       document.getElementById("appmenu_devToolbar").hidden = false;
 #endif
 
       // Show the toolbar if it was previously visible
       if (gPrefService.getBoolPref("devtools.toolbar.visible")) {
-        DeveloperToolbar.show();
+        DeveloperToolbar.show(false);
       }
     }
 
     // Enable Inspector?
     let enabled = gPrefService.getBoolPref("devtools.inspector.enabled");
     if (enabled) {
       document.getElementById("menu_pageinspect").hidden = false;
       document.getElementById("Tools:Inspect").removeAttribute("disabled");
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -1032,31 +1032,31 @@
             <hbox class="gclitoolbar-prompt">
               <label class="gclitoolbar-prompt-label">&#187;</label>
             </hbox>
             <hbox class="gclitoolbar-complete-node"/>
             <textbox class="gclitoolbar-input-node" rows="1"/>
           </stack>
           <toolbarbutton id="developer-toolbar-webconsole"
                          label="&webConsoleButton.label;"
-                         class="devtools-toolbarbutton"
+                         class="developer-toolbar-button"
                          command="Tools:WebConsole"/>
           <toolbarbutton id="developer-toolbar-inspector"
                          label="&inspectorButton.label;"
-                         class="devtools-toolbarbutton"
+                         class="developer-toolbar-button"
                          hidden="true"
                          command="Tools:Inspect"/>
           <toolbarbutton id="developer-toolbar-styleeditor"
                          label="&styleeditor.label;"
-                         class="devtools-toolbarbutton"
+                         class="developer-toolbar-button"
                          hidden="true"
                          command="Tools:StyleEditor"/>
           <toolbarbutton id="developer-toolbar-debugger"
                          label="&debuggerMenu.label2;"
-                         class="devtools-toolbarbutton"
+                         class="developer-toolbar-button"
                          hidden="true"
                          command="Tools:Debugger"/>
 #ifndef XP_MACOSX
           <toolbarbutton id="developer-toolbar-closebutton"
                          class="devtools-closebutton"
                          oncommand="DeveloperToolbar.hide();"
                          tooltiptext="&devToolbarCloseButton.tooltiptext;"/>
 #endif
--- a/browser/devtools/commandline/gcli.jsm
+++ b/browser/devtools/commandline/gcli.jsm
@@ -143,31 +143,28 @@ define('gcli/index', ['require', 'export
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gcli/types/basic', ['require', 'exports', 'module' , 'gcli/l10n', 'gcli/types', 'gcli/types/spell', 'gcli/types/selection', 'gcli/argument'], function(require, exports, module) {
+define('gcli/types/basic', ['require', 'exports', 'module' , 'gcli/l10n', 'gcli/types', 'gcli/types/selection', 'gcli/argument'], function(require, exports, module) {
 
 
 var l10n = require('gcli/l10n');
 var types = require('gcli/types');
 var Type = require('gcli/types').Type;
 var Status = require('gcli/types').Status;
 var Conversion = require('gcli/types').Conversion;
 var ArrayConversion = require('gcli/types').ArrayConversion;
-var Speller = require('gcli/types/spell').Speller;
 var SelectionType = require('gcli/types/selection').SelectionType;
 
-var Argument = require('gcli/argument').Argument;
-var TrueNamedArgument = require('gcli/argument').TrueNamedArgument;
-var FalseNamedArgument = require('gcli/argument').FalseNamedArgument;
+var BlankArgument = require('gcli/argument').BlankArgument;
 var ArrayArgument = require('gcli/argument').ArrayArgument;
 
 
 /**
  * Registration and de-registration.
  */
 exports.startup = function() {
   types.registerType(StringType);
@@ -258,17 +255,17 @@ NumberType.prototype.getMax = function()
     if (typeof this._max === 'number') {
       return this._max;
     }
   }
   return undefined;
 };
 
 NumberType.prototype.parse = function(arg) {
-  if (arg.text.replace(/\s/g, '').length === 0) {
+  if (arg.text.replace(/^\s*-?/, '').length === 0) {
     return new Conversion(undefined, arg, Status.INCOMPLETE, '');
   }
 
   var value = parseInt(arg.text, 10);
   if (isNaN(value)) {
     return new Conversion(undefined, arg, Status.ERROR,
         l10n.lookupFormat('typesNumberNan', [ arg.text ]));
   }
@@ -343,34 +340,34 @@ function BooleanType(typeSpec) {
 BooleanType.prototype = Object.create(SelectionType.prototype);
 
 BooleanType.prototype.lookup = [
   { name: 'false', value: false },
   { name: 'true', value: true }
 ];
 
 BooleanType.prototype.parse = function(arg) {
-  if (arg instanceof TrueNamedArgument) {
+  if (arg.type === 'TrueNamedArgument') {
     return new Conversion(true, arg);
   }
-  if (arg instanceof FalseNamedArgument) {
+  if (arg.type === 'FalseNamedArgument') {
     return new Conversion(false, arg);
   }
   return SelectionType.prototype.parse.call(this, arg);
 };
 
 BooleanType.prototype.stringify = function(value) {
   if (value == null) {
     return '';
   }
   return '' + value;
 };
 
 BooleanType.prototype.getBlank = function() {
-  return new Conversion(false, new Argument(), Status.VALID, '', this.lookup);
+  return new Conversion(false, new BlankArgument(), Status.VALID, '', this.lookup);
 };
 
 BooleanType.prototype.name = 'boolean';
 
 exports.BooleanType = BooleanType;
 
 
 /**
@@ -470,17 +467,17 @@ ArrayType.prototype.stringify = function
   if (values == null) {
     return '';
   }
   // BUG 664204: Check for strings with spaces and add quotes
   return values.join(' ');
 };
 
 ArrayType.prototype.parse = function(arg) {
-  if (arg instanceof ArrayArgument) {
+  if (arg.type === 'ArrayArgument') {
     var conversions = arg.getArguments().map(function(subArg) {
       var conversion = this.subtype.parse(subArg);
       // Hack alert. ArrayConversion needs to be able to answer questions
       // about the status of individual conversions in addition to the
       // overall state. This allows us to do that easily.
       subArg.conversion = conversion;
       return conversion;
     }, this);
@@ -604,20 +601,20 @@ exports.lookupFormat = function(key, swa
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 define('gcli/types', ['require', 'exports', 'module' , 'gcli/argument'], function(require, exports, module) {
-var types = exports;
 
 
 var Argument = require('gcli/argument').Argument;
+var BlankArgument = require('gcli/argument').BlankArgument;
 
 
 /**
  * Some types can detect validity, that is to say they can distinguish between
  * valid and invalid values.
  * We might want to change these constants to be numbers for better performance
  */
 var Status = {
@@ -666,17 +663,19 @@ var Status = {
       }
       if (status > combined) {
         combined = status;
       }
     }
     return combined;
   }
 };
-types.Status = Status;
+
+exports.Status = Status;
+
 
 /**
  * The type.parse() method converts an Argument into a value, Conversion is
  * a wrapper to that value.
  * Conversion is needed to collect a number of properties related to that
  * conversion in one place, i.e. to handle errors and provide traceability.
  * @param value The result of the conversion
  * @param arg The data from which the conversion was made
@@ -715,33 +714,31 @@ function Conversion(value, arg, status, 
     throw new Error('Missing arg');
   }
 
   this._status = status || Status.VALID;
   this.message = message;
   this.predictions = predictions;
 }
 
-types.Conversion = Conversion;
-
 /**
  * Ensure that all arguments that are part of this conversion know what they
  * are assigned to.
  * @param assignment The Assignment (param/conversion link) to inform the
  * argument about.
  */
 Conversion.prototype.assign = function(assignment) {
   this.arg.assign(assignment);
 };
 
 /**
  * Work out if there is information provided in the contained argument.
  */
 Conversion.prototype.isDataProvided = function() {
-  return !this.arg.isBlank();
+  return this.arg.type !== 'BlankArgument';
 };
 
 /**
  * 2 conversions are equal if and only if their args are equal (argEquals) and
  * their values are equal (valueEquals).
  * @param that The conversion object to compare against.
  */
 Conversion.prototype.equals = function(that) {
@@ -853,16 +850,25 @@ Conversion.prototype.constrainPrediction
   index = index % predictions.length;
   if (index < 0) {
     index = predictions.length + index;
   }
   return index;
 };
 
 /**
+ * Constant to allow everyone to agree on the maximum number of predictions
+ * that should be provided. We actually display 1 less than this number.
+ */
+Conversion.maxPredictions = 11;
+
+exports.Conversion = Conversion;
+
+
+/**
  * ArrayConversion is a special Conversion, needed because arrays are converted
  * member by member rather then as a whole, which means we can track the
  * conversion if individual array elements. So an ArrayConversion acts like a
  * normal Conversion (which is needed as Assignment requires a Conversion) but
  * it can also be devolved into a set of Conversions for each array member.
  */
 function ArrayConversion(conversions, arg) {
   this.arg = arg;
@@ -926,17 +932,17 @@ ArrayConversion.prototype.valueEquals = 
 };
 
 ArrayConversion.prototype.toString = function() {
   return '[ ' + this.conversions.map(function(conversion) {
     return conversion.toString();
   }, this).join(', ') + ' ]';
 };
 
-types.ArrayConversion = ArrayConversion;
+exports.ArrayConversion = ArrayConversion;
 
 
 /**
  * Most of our types are 'static' e.g. there is only one type of 'string',
  * however some types like 'selection' and 'deferred' are customizable.
  * The basic Type type isn't useful, but does provide documentation about what
  * types do.
  */
@@ -998,50 +1004,50 @@ Type.prototype.decrement = function(valu
 
 /**
  * The 'blank value' of most types is 'undefined', but there are exceptions;
  * This allows types to specify a better conversion from empty string than
  * 'undefined'.
  * 2 known examples of this are boolean -> false and array -> []
  */
 Type.prototype.getBlank = function() {
-  return this.parse(new Argument());
+  return this.parse(new BlankArgument());
 };
 
 /**
  * This is something of a hack for the benefit of DeferredType which needs to
  * be able to lie about it's type for fields to accept it as one of their own.
  * Sub-types can ignore this unless they're DeferredType.
  */
 Type.prototype.getType = function() {
   return this;
 };
 
-types.Type = Type;
+exports.Type = Type;
 
 /**
  * Private registry of types
  * Invariant: types[name] = type.name
  */
 var registeredTypes = {};
 
-types.getTypeNames = function() {
+exports.getTypeNames = function() {
   return Object.keys(registeredTypes);
 };
 
 /**
  * Add a new type to the list available to the system.
  * You can pass 2 things to this function - either an instance of Type, in
  * which case we return this instance when #getType() is called with a 'name'
  * that matches type.name.
  * Also you can pass in a constructor (i.e. function) in which case when
  * #getType() is called with a 'name' that matches Type.prototype.name we will
  * pass the typeSpec into this constructor.
  */
-types.registerType = function(type) {
+exports.registerType = function(type) {
   if (typeof type === 'object') {
     if (type instanceof Type) {
       if (!type.name) {
         throw new Error('All registered types must have a name');
       }
       registeredTypes[type.name] = type;
     }
     else {
@@ -1054,35 +1060,35 @@ types.registerType = function(type) {
     }
     registeredTypes[type.prototype.name] = type;
   }
   else {
     throw new Error('Unknown type: ' + type);
   }
 };
 
-types.registerTypes = function registerTypes(newTypes) {
+exports.registerTypes = function registerTypes(newTypes) {
   Object.keys(newTypes).forEach(function(name) {
     var type = newTypes[name];
     type.name = name;
     newTypes.registerType(type);
   });
 };
 
 /**
  * Remove a type from the list available to the system
  */
-types.deregisterType = function(type) {
+exports.deregisterType = function(type) {
   delete registeredTypes[type.name];
 };
 
 /**
  * Find a type, previously registered using #registerType()
  */
-types.getType = function(typeSpec) {
+exports.getType = function(typeSpec) {
   var type;
   if (typeof typeSpec === 'string') {
     type = registeredTypes[typeSpec];
     if (typeof type === 'function') {
       type = new type({});
     }
     return type;
   }
@@ -1120,16 +1126,27 @@ types.getType = function(typeSpec) {
  * limitations under the License.
  */
 
 define('gcli/argument', ['require', 'exports', 'module' ], function(require, exports, module) {
 var argument = exports;
 
 
 /**
+ * Thinking out loud here:
+ * Arguments are an area where we could probably refactor things a bit better.
+ * The split process in Requisition creates a set of Arguments, which are then
+ * assigned. The assign process sometimes converts them into subtypes of
+ * Argument. We might consider that what gets assigned is _always_ one of the
+ * subtypes (or actually a different type hierarchy entirely) and that we
+ * don't manipulate the prefix/text/suffix but just use the 'subtypes' as
+ * filters which present a view of the underlying original Argument.
+ */
+
+/**
  * We record where in the input string an argument comes so we can report
  * errors against those string positions.
  * @param text The string (trimmed) that contains the argument
  * @param prefix Knowledge of quotation marks and whitespace used prior to the
  * text in the input string allows us to re-generate the original input from
  * the arguments.
  * @param suffix Any quotation marks and whitespace used after the text.
  * Whitespace is normally placed in the prefix to the succeeding argument, but
@@ -1144,16 +1161,18 @@ function Argument(text, prefix, suffix) 
   }
   else {
     this.text = text;
     this.prefix = prefix !== undefined ? prefix : '';
     this.suffix = suffix !== undefined ? suffix : '';
   }
 }
 
+Argument.prototype.type = 'Argument';
+
 /**
  * Return the result of merging these arguments.
  * case and some of the arguments are in quotation marks?
  */
 Argument.prototype.merge = function(following) {
   // Is it possible that this gets called when we're merging arguments
   // for the single string?
   return new Argument(
@@ -1178,25 +1197,16 @@ Argument.prototype.beget = function(repl
     prefix = (options.prefixSpace ? ' ' : '') + quote;
     suffix = quote;
   }
 
   return new Argument(replText, prefix, suffix);
 };
 
 /**
- * Is there any visible content to this argument?
- */
-Argument.prototype.isBlank = function() {
-  return this.text === '' &&
-      this.prefix.trim() === '' &&
-      this.suffix.trim() === '';
-};
-
-/**
  * We need to keep track of which assignment we've been assigned to
  */
 Argument.prototype.assign = function(assignment) {
   this.assignment = assignment;
 };
 
 /**
  * Sub-classes of Argument are collections of arguments, getArgs() gets access
@@ -1251,20 +1261,53 @@ Argument.merge = function(argArray, star
     }
     else {
       joined = joined.merge(arg);
     }
   }
   return joined;
 };
 
+/**
+ * For test/debug use only. The output from this function is subject to wanton
+ * random change without notice, and should not be relied upon to even exist
+ * at some later date.
+ */
+Object.defineProperty(Argument.prototype, '_summaryJson', {
+  get: function() {
+    var assignStatus = this.assignment == null ?
+            'null' :
+            this.assignment.param.name;
+    return '<' + this.prefix + ':' + this.text + ':' + this.suffix + '>' +
+        ' (a=' + assignStatus + ',' + ' t=' + this.type + ')';
+  },
+  enumerable: true
+});
+
 argument.Argument = Argument;
 
 
 /**
+ * BlankArgument is a marker that the argument wasn't typed but is there to
+ * fill a slot. Assignments begin with their arg set to a BlankArgument.
+ */
+function BlankArgument() {
+  this.text = '';
+  this.prefix = '';
+  this.suffix = '';
+}
+
+BlankArgument.prototype = Object.create(Argument.prototype);
+
+BlankArgument.prototype.type = 'BlankArgument';
+
+argument.BlankArgument = BlankArgument;
+
+
+/**
  * ScriptArgument is a marker that the argument is designed to be Javascript.
  * It also implements the special rules that spaces after the { or before the
  * } are part of the pre/suffix rather than the content, and that they are
  * never 'blank' so they can be used by Requisition._split() and not raise an
  * ERROR status due to being blank.
  */
 function ScriptArgument(text, prefix, suffix) {
   this.text = text !== undefined ? text : '';
@@ -1279,16 +1322,18 @@ function ScriptArgument(text, prefix, su
   while (this.text.charAt(this.text.length - 1) === ' ') {
     this.suffix = ' ' + this.suffix;
     this.text = this.text.slice(0, -1);
   }
 }
 
 ScriptArgument.prototype = Object.create(Argument.prototype);
 
+ScriptArgument.prototype.type = 'ScriptArgument';
+
 /**
  * Returns a new Argument like this one but with the text set to
  * <tt>replText</tt> and the end adjusted to fit.
  * @param replText Text to replace the old text value
  */
 ScriptArgument.prototype.beget = function(replText, options) {
   var prefix = this.prefix;
   var suffix = this.suffix;
@@ -1296,25 +1341,16 @@ ScriptArgument.prototype.beget = functio
   if (options && options.normalize) {
     prefix = '{ ';
     suffix = ' }';
   }
 
   return new ScriptArgument(replText, prefix, suffix);
 };
 
-/**
- * ScriptArguments are never blank due to the '{' and '}' and their special use
- * for the command argument requires them not to be blank even when there is
- * no text.
- */
-ScriptArgument.prototype.isBlank = function() {
-  return false;
-};
-
 argument.ScriptArgument = ScriptArgument;
 
 
 /**
  * Commands like 'echo' with a single string argument, and used with the
  * special format like: 'echo a b c' effectively have a number of arguments
  * merged together.
  */
@@ -1333,16 +1369,18 @@ function MergedArgument(args, start, end
   var arg = Argument.merge(this.args);
   this.text = arg.text;
   this.prefix = arg.prefix;
   this.suffix = arg.suffix;
 }
 
 MergedArgument.prototype = Object.create(Argument.prototype);
 
+MergedArgument.prototype.type = 'MergedArgument';
+
 /**
  * Keep track of which assignment we've been assigned to, and allow the
  * original args to do the same.
  */
 MergedArgument.prototype.assign = function(assignment) {
   this.args.forEach(function(arg) {
     arg.assign(assignment);
   }, this);
@@ -1379,16 +1417,18 @@ function TrueNamedArgument(name, arg) {
   this.arg = arg;
   this.text = arg ? arg.text : '--' + name;
   this.prefix = arg ? arg.prefix : ' ';
   this.suffix = arg ? arg.suffix : '';
 }
 
 TrueNamedArgument.prototype = Object.create(Argument.prototype);
 
+TrueNamedArgument.prototype.type = 'TrueNamedArgument';
+
 TrueNamedArgument.prototype.assign = function(assignment) {
   if (this.arg) {
     this.arg.assign(assignment);
   }
   this.assignment = assignment;
 };
 
 TrueNamedArgument.prototype.getArgs = function() {
@@ -1422,16 +1462,18 @@ argument.TrueNamedArgument = TrueNamedAr
 function FalseNamedArgument() {
   this.text = '';
   this.prefix = '';
   this.suffix = '';
 }
 
 FalseNamedArgument.prototype = Object.create(Argument.prototype);
 
+FalseNamedArgument.prototype.type = 'FalseNamedArgument';
+
 FalseNamedArgument.prototype.getArgs = function() {
   return [ ];
 };
 
 FalseNamedArgument.prototype.equals = function(that) {
   if (this === that) {
     return true;
   }
@@ -1447,39 +1489,44 @@ argument.FalseNamedArgument = FalseNamed
 
 
 /**
  * A named argument is for cases where we have input in one of the following
  * formats:
  * <ul>
  * <li>--param value
  * <li>-p value
- * <li>--pa value
- * <li>-p:value
- * <li>--param=value
- * <li>etc
  * </ul>
- * The general format is:
- * /--?{unique-param-name-prefix}[ :=]{value}/
  * We model this as a normal argument but with a long prefix.
  */
 function NamedArgument(nameArg, valueArg) {
   this.nameArg = nameArg;
   this.valueArg = valueArg;
 
-  this.text = valueArg.text;
-  this.prefix = nameArg.toString() + valueArg.prefix;
-  this.suffix = valueArg.suffix;
+  if (valueArg == null) {
+    this.text = '';
+    this.prefix = nameArg.toString();
+    this.suffix = '';
+  }
+  else {
+    this.text = valueArg.text;
+    this.prefix = nameArg.toString() + valueArg.prefix;
+    this.suffix = valueArg.suffix;
+  }
 }
 
 NamedArgument.prototype = Object.create(Argument.prototype);
 
+NamedArgument.prototype.type = 'NamedArgument';
+
 NamedArgument.prototype.assign = function(assignment) {
   this.nameArg.assign(assignment);
-  this.valueArg.assign(assignment);
+  if (this.valueArg != null) {
+    this.valueArg.assign(assignment);
+  }
   this.assignment = assignment;
 };
 
 NamedArgument.prototype.getArgs = function() {
   return [ this.nameArg, this.valueArg ];
 };
 
 NamedArgument.prototype.equals = function(that) {
@@ -1508,16 +1555,18 @@ argument.NamedArgument = NamedArgument;
  * can be jointly assigned to a single array parameter
  */
 function ArrayArgument() {
   this.args = [];
 }
 
 ArrayArgument.prototype = Object.create(Argument.prototype);
 
+ArrayArgument.prototype.type = 'ArrayArgument';
+
 ArrayArgument.prototype.addArgument = function(arg) {
   this.args.push(arg);
 };
 
 ArrayArgument.prototype.addArguments = function(args) {
   Array.prototype.push.apply(this.args, args);
 };
 
@@ -1540,17 +1589,17 @@ ArrayArgument.prototype.getArgs = functi
 ArrayArgument.prototype.equals = function(that) {
   if (this === that) {
     return true;
   }
   if (that == null) {
     return false;
   }
 
-  if (!(that instanceof ArrayArgument)) {
+  if (!(that.type === 'ArrayArgument')) {
     return false;
   }
 
   if (this.args.length !== that.args.length) {
     return false;
   }
 
   for (var i = 0; i < this.args.length; i++) {
@@ -1571,16 +1620,318 @@ ArrayArgument.prototype.toString = funct
   }, this).join(',') + '}';
 };
 
 argument.ArrayArgument = ArrayArgument;
 
 
 });
 /*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+define('gcli/types/selection', ['require', 'exports', 'module' , 'gcli/l10n', 'gcli/types', 'gcli/types/spell'], function(require, exports, module) {
+
+
+var l10n = require('gcli/l10n');
+var types = require('gcli/types');
+var Type = require('gcli/types').Type;
+var Status = require('gcli/types').Status;
+var Conversion = require('gcli/types').Conversion;
+var Speller = require('gcli/types/spell').Speller;
+
+
+/**
+ * Registration and de-registration.
+ */
+exports.startup = function() {
+  types.registerType(SelectionType);
+};
+
+exports.shutdown = function() {
+  types.unregisterType(SelectionType);
+};
+
+
+/**
+ * A selection allows the user to pick a value from known set of options.
+ * An option is made up of a name (which is what the user types) and a value
+ * (which is passed to exec)
+ * @param typeSpec Object containing properties that describe how this
+ * selection functions. Properties include:
+ * - lookup: An array of objects, one for each option, which contain name and
+ *   value properties. lookup can be a function which returns this array
+ * - data: An array of strings - alternative to 'lookup' where the valid values
+ *   are strings. i.e. there is no mapping between what is typed and the value
+ *   that is used by the program
+ * - stringifyProperty: Conversion from value to string is generally a process
+ *   of looking through all the valid options for a matching value, and using
+ *   the associated name. However the name maybe available directly from the
+ *   value using a property lookup. Setting 'stringifyProperty' allows
+ *   SelectionType to take this shortcut.
+ * - cacheable : If lookup is a function, then we normally assume that
+ *   the values fetched can change. Setting 'cacheable' enables internal
+ *   caching.
+ */
+function SelectionType(typeSpec) {
+  if (typeSpec) {
+    Object.keys(typeSpec).forEach(function(key) {
+      this[key] = typeSpec[key];
+    }, this);
+  }
+}
+
+SelectionType.prototype = Object.create(Type.prototype);
+
+SelectionType.prototype.stringify = function(value) {
+  if (value == null) {
+    return '';
+  }
+  if (this.stringifyProperty != null) {
+    return value[this.stringifyProperty];
+  }
+  var name = null;
+  var lookup = this.getLookup();
+  lookup.some(function(item) {
+    if (item.value === value) {
+      name = item.name;
+      return true;
+    }
+    return false;
+  }, this);
+  return name;
+};
+
+/**
+ * If typeSpec contained cacheable:true then calls to parse() work on cached
+ * data. clearCache() enables the cache to be cleared.
+ */
+SelectionType.prototype.clearCache = function() {
+  delete this._cachedLookup;
+};
+
+/**
+ * There are several ways to get selection data. This unifies them into one
+ * single function.
+ * @return An array of objects with name and value properties.
+ */
+SelectionType.prototype.getLookup = function() {
+  if (this._cachedLookup) {
+    return this._cachedLookup;
+  }
+
+  if (this.lookup) {
+    if (typeof this.lookup === 'function') {
+      if (this.cacheable) {
+        this._cachedLookup = this.lookup();
+        return this._cachedLookup;
+      }
+      return this.lookup();
+    }
+    return this.lookup;
+  }
+
+  if (Array.isArray(this.data)) {
+    this.lookup = this._dataToLookup(this.data);
+    return this.lookup;
+  }
+
+  if (typeof(this.data) === 'function') {
+    return this._dataToLookup(this.data());
+  }
+
+  throw new Error('SelectionType has no data');
+};
+
+/**
+ * Selection can be provided with either a lookup object (in the 'lookup'
+ * property) or an array of strings (in the 'data' property). Internally we
+ * always use lookup, so we need a way to convert a 'data' array to a lookup.
+ */
+SelectionType.prototype._dataToLookup = function(data) {
+  return data.map(function(option) {
+    return { name: option, value: option };
+  }, this);
+};
+
+/**
+ * Return a list of possible completions for the given arg.
+ * @param arg The initial input to match
+ * @return A trimmed array of string:value pairs
+ */
+SelectionType.prototype._findPredictions = function(arg) {
+  var predictions = [];
+  var lookup = this.getLookup();
+  var i, option;
+  var maxPredictions = Conversion.maxPredictions;
+
+  // If the arg has a suffix then we're kind of 'done'. Only an exact match
+  // will do.
+  if (arg.suffix.length > 0) {
+    for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) {
+      option = lookup[i];
+      if (option.name === arg.text) {
+        this._addToPredictions(predictions, option, arg);
+      }
+    }
+
+    return predictions;
+  }
+
+  // Start with prefix matching
+  for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) {
+    option = lookup[i];
+    if (option.name.indexOf(arg.text) === 0) {
+      this._addToPredictions(predictions, option, arg);
+    }
+  }
+
+  // Try infix matching if we get less half max matched
+  if (predictions.length < (maxPredictions / 2)) {
+    for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) {
+      option = lookup[i];
+      if (option.name.indexOf(arg.text) !== -1) {
+        if (predictions.indexOf(option) === -1) {
+          this._addToPredictions(predictions, option, arg);
+        }
+      }
+    }
+  }
+
+  // Try fuzzy matching if we don't get a prefix match
+  if (false && predictions.length === 0) {
+    var speller = new Speller();
+    var names = lookup.map(function(opt) {
+      return opt.name;
+    });
+    speller.train(names);
+    var corrected = speller.correct(arg.text);
+    if (corrected) {
+      lookup.forEach(function(opt) {
+        if (opt.name === corrected) {
+          predictions.push(opt);
+        }
+      }, this);
+    }
+  }
+
+  return predictions;
+};
+
+/**
+ * Add an option to our list of predicted options.
+ * We abstract out this portion of _findPredictions() because CommandType needs
+ * to make an extra check before actually adding which SelectionType does not
+ * need to make.
+ */
+SelectionType.prototype._addToPredictions = function(predictions, option, arg) {
+  predictions.push(option);
+};
+
+SelectionType.prototype.parse = function(arg) {
+  var predictions = this._findPredictions(arg);
+
+  if (predictions.length === 0) {
+    var msg = l10n.lookupFormat('typesSelectionNomatch', [ arg.text ]);
+    return new Conversion(undefined, arg, Status.ERROR, msg, predictions);
+  }
+
+  // This is something of a hack it basically allows us to tell the
+  // setting type to forget its last setting hack.
+  if (this.noMatch) {
+    this.noMatch();
+  }
+
+  var value = predictions[0].value;
+
+  if (predictions[0].name === arg.text) {
+    return new Conversion(value, arg, Status.VALID, '', predictions);
+  }
+
+  return new Conversion(undefined, arg, Status.INCOMPLETE, '', predictions);
+};
+
+/**
+ * For selections, up is down and black is white. It's like this, given a list
+ * [ a, b, c, d ], it's natural to think that it starts at the top and that
+ * going up the list, moves towards 'a'. However 'a' has the lowest index, so
+ * for SelectionType, up is down and down is up.
+ * Sorry.
+ */
+SelectionType.prototype.decrement = function(value) {
+  var lookup = this.getLookup();
+  var index = this._findValue(lookup, value);
+  if (index === -1) {
+    index = 0;
+  }
+  index++;
+  if (index >= lookup.length) {
+    index = 0;
+  }
+  return lookup[index].value;
+};
+
+/**
+ * See note on SelectionType.decrement()
+ */
+SelectionType.prototype.increment = function(value) {
+  var lookup = this.getLookup();
+  var index = this._findValue(lookup, value);
+  if (index === -1) {
+    // For an increment operation when there is nothing to start from, we
+    // want to start from the top, i.e. index 0, so the value before we
+    // 'increment' (see note above) must be 1.
+    index = 1;
+  }
+  index--;
+  if (index < 0) {
+    index = lookup.length - 1;
+  }
+  return lookup[index].value;
+};
+
+/**
+ * Walk through an array of { name:.., value:... } objects looking for a
+ * matching value (using strict equality), returning the matched index (or -1
+ * if not found).
+ * @param lookup Array of objects with name/value properties to search through
+ * @param value The value to search for
+ * @return The index at which the match was found, or -1 if no match was found
+ */
+SelectionType.prototype._findValue = function(lookup, value) {
+  var index = -1;
+  for (var i = 0; i < lookup.length; i++) {
+    var pair = lookup[i];
+    if (pair.value === value) {
+      index = i;
+      break;
+    }
+  }
+  return index;
+};
+
+SelectionType.prototype.name = 'selection';
+
+exports.SelectionType = SelectionType;
+
+
+});
+/*
  * Copyright (c) 2009 Panagiotis Astithas
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
  * copyright notice and this permission notice appear in all copies.
  *
  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
@@ -1737,339 +2088,74 @@ exports.Speller = Speller;
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gcli/types/selection', ['require', 'exports', 'module' , 'gcli/l10n', 'gcli/types', 'gcli/types/spell'], function(require, exports, module) {
-
-
-var l10n = require('gcli/l10n');
-var types = require('gcli/types');
-var Type = require('gcli/types').Type;
-var Status = require('gcli/types').Status;
-var Conversion = require('gcli/types').Conversion;
-var Speller = require('gcli/types/spell').Speller;
-
-
-/**
- * Registration and de-registration.
- */
-exports.startup = function() {
-  types.registerType(SelectionType);
-};
-
-exports.shutdown = function() {
-  types.unregisterType(SelectionType);
-};
-
-
-/**
- * A selection allows the user to pick a value from known set of options.
- * An option is made up of a name (which is what the user types) and a value
- * (which is passed to exec)
- * @param typeSpec Object containing properties that describe how this
- * selection functions. Properties include:
- * - lookup: An array of objects, one for each option, which contain name and
- *   value properties. lookup can be a function which returns this array
- * - data: An array of strings - alternative to 'lookup' where the valid values
- *   are strings. i.e. there is no mapping between what is typed and the value
- *   that is used by the program
- * - stringifyProperty: Conversion from value to string is generally a process
- *   of looking through all the valid options for a matching value, and using
- *   the associated name. However the name maybe available directly from the
- *   value using a property lookup. Setting 'stringifyProperty' allows
- *   SelectionType to take this shortcut.
- * - cacheable : If lookup is a function, then we normally assume that
- *   the values fetched can change. Setting 'cacheable' enables internal
- *   caching.
- */
-function SelectionType(typeSpec) {
-  if (typeSpec) {
-    Object.keys(typeSpec).forEach(function(key) {
-      this[key] = typeSpec[key];
-    }, this);
-  }
-}
-
-SelectionType.prototype = Object.create(Type.prototype);
-
-SelectionType.prototype.maxPredictions = 10;
-
-SelectionType.prototype.stringify = function(value) {
-  if (value == null) {
-    return '';
-  }
-  if (this.stringifyProperty != null) {
-    return value[this.stringifyProperty];
-  }
-  var name = null;
-  var lookup = this.getLookup();
-  lookup.some(function(item) {
-    if (item.value === value) {
-      name = item.name;
-      return true;
-    }
-    return false;
-  }, this);
-  return name;
-};
-
-/**
- * If typeSpec contained cacheable:true then calls to parse() work on cached
- * data. clearCache() enables the cache to be cleared.
- */
-SelectionType.prototype.clearCache = function() {
-  delete this._cachedLookup;
-};
-
-/**
- * There are several ways to get selection data. This unifies them into one
- * single function.
- * @return An array of objects with name and value properties.
- */
-SelectionType.prototype.getLookup = function() {
-  if (this._cachedLookup) {
-    return this._cachedLookup;
-  }
-
-  if (this.lookup) {
-    if (typeof this.lookup === 'function') {
-      if (this.cacheable) {
-        this._cachedLookup = this.lookup();
-        return this._cachedLookup;
-      }
-      return this.lookup();
-    }
-    return this.lookup;
-  }
-
-  if (Array.isArray(this.data)) {
-    this.lookup = this._dataToLookup(this.data);
-    return this.lookup;
-  }
-
-  if (typeof(this.data) === 'function') {
-    return this._dataToLookup(this.data());
-  }
-
-  throw new Error('SelectionType has no data');
-};
-
-/**
- * Selection can be provided with either a lookup object (in the 'lookup'
- * property) or an array of strings (in the 'data' property). Internally we
- * always use lookup, so we need a way to convert a 'data' array to a lookup.
- */
-SelectionType.prototype._dataToLookup = function(data) {
-  return data.map(function(option) {
-    return { name: option, value: option };
-  }, this);
-};
-
-/**
- * Return a list of possible completions for the given arg.
- * @param arg The initial input to match
- * @return A trimmed array of string:value pairs
- */
-SelectionType.prototype._findPredictions = function(arg) {
-  var predictions = [];
-  var lookup = this.getLookup();
-  var i, option;
-
-  // If the arg has a suffix then we're kind of 'done'. Only an exact match
-  // will do.
-  if (arg.suffix.length > 0) {
-    for (i = 0; i < lookup.length && predictions.length < this.maxPredictions; i++) {
-      option = lookup[i];
-      if (option.name === arg.text) {
-        this._addToPredictions(predictions, option, arg);
-      }
-    }
-
-    return predictions;
-  }
-
-  // Start with prefix matching
-  for (i = 0; i < lookup.length && predictions.length < this.maxPredictions; i++) {
-    option = lookup[i];
-    if (option.name.indexOf(arg.text) === 0) {
-      this._addToPredictions(predictions, option, arg);
-    }
-  }
-
-  // Try infix matching if we get less half max matched
-  if (predictions.length < (this.maxPredictions / 2)) {
-    for (i = 0; i < lookup.length && predictions.length < this.maxPredictions; i++) {
-      option = lookup[i];
-      if (option.name.indexOf(arg.text) !== -1) {
-        if (predictions.indexOf(option) === -1) {
-          this._addToPredictions(predictions, option, arg);
-        }
-      }
-    }
-  }
-
-  // Try fuzzy matching if we don't get a prefix match
-  if (false && predictions.length === 0) {
-    var speller = new Speller();
-    var names = lookup.map(function(opt) {
-      return opt.name;
-    });
-    speller.train(names);
-    var corrected = speller.correct(arg.text);
-    if (corrected) {
-      lookup.forEach(function(opt) {
-        if (opt.name === corrected) {
-          predictions.push(opt);
-        }
-      }, this);
-    }
-  }
-
-  return predictions;
-};
-
-/**
- * Add an option to our list of predicted options.
- * We abstract out this portion of _findPredictions() because CommandType needs
- * to make an extra check before actually adding which SelectionType does not
- * need to make.
- */
-SelectionType.prototype._addToPredictions = function(predictions, option, arg) {
-  predictions.push(option);
-};
-
-SelectionType.prototype.parse = function(arg) {
-  var predictions = this._findPredictions(arg);
-
-  if (predictions.length === 0) {
-    var msg = l10n.lookupFormat('typesSelectionNomatch', [ arg.text ]);
-    return new Conversion(undefined, arg, Status.ERROR, msg, predictions);
-  }
-
-  // This is something of a hack it basically allows us to tell the
-  // setting type to forget its last setting hack.
-  if (this.noMatch) {
-    this.noMatch();
-  }
-
-  var value = predictions[0].value;
-
-  if (predictions[0].name === arg.text) {
-    return new Conversion(value, arg, Status.VALID, '', predictions);
-  }
-
-  return new Conversion(undefined, arg, Status.INCOMPLETE, '', predictions);
-};
-
-/**
- * For selections, up is down and black is white. It's like this, given a list
- * [ a, b, c, d ], it's natural to think that it starts at the top and that
- * going up the list, moves towards 'a'. However 'a' has the lowest index, so
- * for SelectionType, up is down and down is up.
- * Sorry.
- */
-SelectionType.prototype.decrement = function(value) {
-  var lookup = this.getLookup();
-  var index = this._findValue(lookup, value);
-  if (index === -1) {
-    index = 0;
-  }
-  index++;
-  if (index >= lookup.length) {
-    index = 0;
-  }
-  return lookup[index].value;
-};
-
-/**
- * See note on SelectionType.decrement()
- */
-SelectionType.prototype.increment = function(value) {
-  var lookup = this.getLookup();
-  var index = this._findValue(lookup, value);
-  if (index === -1) {
-    // For an increment operation when there is nothing to start from, we
-    // want to start from the top, i.e. index 0, so the value before we
-    // 'increment' (see note above) must be 1.
-    index = 1;
-  }
-  index--;
-  if (index < 0) {
-    index = lookup.length - 1;
-  }
-  return lookup[index].value;
-};
-
-/**
- * Walk through an array of { name:.., value:... } objects looking for a
- * matching value (using strict equality), returning the matched index (or -1
- * if not found).
- * @param lookup Array of objects with name/value properties to search through
- * @param value The value to search for
- * @return The index at which the match was found, or -1 if no match was found
- */
-SelectionType.prototype._findValue = function(lookup, value) {
-  var index = -1;
-  for (var i = 0; i < lookup.length; i++) {
-    var pair = lookup[i];
-    if (pair.value === value) {
-      index = i;
-      break;
-    }
-  }
-  return index;
-};
-
-SelectionType.prototype.name = 'selection';
-
-exports.SelectionType = SelectionType;
-
-
-});
-/*
- * Copyright 2012, Mozilla Foundation and contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
 define('gcli/types/command', ['require', 'exports', 'module' , 'gcli/canon', 'gcli/l10n', 'gcli/types', 'gcli/types/selection'], function(require, exports, module) {
 
 
 var canon = require('gcli/canon');
 var l10n = require('gcli/l10n');
 var types = require('gcli/types');
 var SelectionType = require('gcli/types/selection').SelectionType;
 var Status = require('gcli/types').Status;
 var Conversion = require('gcli/types').Conversion;
 
 
 /**
  * Registration and de-registration.
  */
 exports.startup = function() {
   types.registerType(CommandType);
+  types.registerType(ParamType);
 };
 
 exports.shutdown = function() {
   types.unregisterType(CommandType);
+  types.unregisterType(ParamType);
+};
+
+
+/**
+ * Select from the available commands.
+ * This is very similar to a SelectionType, however the level of hackery in
+ * SelectionType to make it handle Commands correctly was to high, so we
+ * simplified.
+ * If you are making changes to this code, you should check there too.
+ */
+function ParamType(typeSpec) {
+  this.requisition = typeSpec.requisition;
+  this.isIncompleteName = typeSpec.isIncompleteName;
+  this.stringifyProperty = 'name';
+}
+
+ParamType.prototype = Object.create(SelectionType.prototype);
+
+ParamType.prototype.name = 'param';
+
+ParamType.prototype.lookup = function() {
+  var displayedParams = [];
+  var command = this.requisition.commandAssignment.value;
+  command.params.forEach(function(param) {
+    var arg = this.requisition.getAssignment(param.name).arg;
+    if (!param.isPositionalAllowed && arg.type === "BlankArgument") {
+      displayedParams.push({ name: '--' + param.name, value: param });
+    }
+  }, this);
+  return displayedParams;
+};
+
+ParamType.prototype.parse = function(arg) {
+  return this.isIncompleteName ?
+      SelectionType.prototype.parse.call(this, arg) :
+      new Conversion(undefined, arg, Status.ERROR, l10n.lookup('cliUnusedArg'));
 };
 
 
 /**
  * Select from the available commands.
  * This is very similar to a SelectionType, however the level of hackery in
  * SelectionType to make it handle Commands correctly was to high, so we
  * simplified.
@@ -2155,26 +2241,27 @@ CommandType.prototype.parse = function(a
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gcli/canon', ['require', 'exports', 'module' , 'gcli/util', 'gcli/l10n', 'gcli/types', 'gcli/types/basic'], function(require, exports, module) {
+define('gcli/canon', ['require', 'exports', 'module' , 'gcli/util', 'gcli/l10n', 'gcli/types', 'gcli/types/basic', 'gcli/types/selection'], function(require, exports, module) {
 var canon = exports;
 
 
 var util = require('gcli/util');
 var l10n = require('gcli/l10n');
 
 var types = require('gcli/types');
 var Status = require('gcli/types').Status;
 var BooleanType = require('gcli/types/basic').BooleanType;
+var SelectionType = require('gcli/types/selection').SelectionType;
 
 /**
  * Implement the localization algorithm for any documentation objects (i.e.
  * description and manual) in a command.
  * @param data The data assigned to a description or manual property
  * @param onUndefined If data == null, should we return the data untouched or
  * lookup a 'we don't know' key in it's place.
  */
@@ -2210,16 +2297,17 @@ function lookup(data, onUndefined) {
             'locales=' + JSON.stringify(locales) + ', ' +
             'description=' + JSON.stringify(data));
     return '(No description)';
   }
 
   return l10n.lookup(onUndefined);
 }
 
+
 /**
  * The command object is mostly just setup around a commandSpec (as passed to
  * #addCommand()).
  */
 function Command(commandSpec) {
   Object.keys(commandSpec).forEach(function(key) {
     this[key] = commandSpec[key];
   }, this);
@@ -2336,21 +2424,28 @@ function Parameter(paramSpec, command, g
       }
     }
     catch (ex) {
       console.error('In ' + this.command.name + '/' + this.name +
         ': ' + ex);
     }
   }
 
-  // Some typed (boolean, array) have a non 'undefined' blank value. Give the
+  // Some types (boolean, array) have a non 'undefined' blank value. Give the
   // type a chance to override the default defaultValue of undefined
   if (this.defaultValue === undefined) {
     this.defaultValue = this.type.getBlank().value;
   }
+
+  // All parameters that can only be set via a named parameter must have a
+  // non-undefined default value
+  if (!this.isPositionalAllowed && this.defaultValue === undefined) {
+    console.error('In ' + this.command.name + '/' + this.name +
+            ': Missing defaultValue for optional parameter.');
+  }
 }
 
 /**
  * Does the given name uniquely identify this param (among the other params
  * in this command)
  * @param name The name to check
  */
 Parameter.prototype.isKnownAs = function(name) {
@@ -4853,30 +4948,32 @@ define('gcli/ui/domtemplate', ['require'
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gcli/cli', ['require', 'exports', 'module' , 'gcli/util', 'gcli/ui/view', 'gcli/canon', 'gcli/promise', 'gcli/types', 'gcli/types/basic', 'gcli/argument'], function(require, exports, module) {
+define('gcli/cli', ['require', 'exports', 'module' , 'gcli/util', 'gcli/ui/view', 'gcli/l10n', 'gcli/canon', 'gcli/promise', 'gcli/types', 'gcli/types/basic', 'gcli/argument'], function(require, exports, module) {
 
 
 var util = require('gcli/util');
 var view = require('gcli/ui/view');
+var l10n = require('gcli/l10n');
 
 var canon = require('gcli/canon');
 var Promise = require('gcli/promise').Promise;
 
 var Status = require('gcli/types').Status;
 var Conversion = require('gcli/types').Conversion;
 var ArrayType = require('gcli/types/basic').ArrayType;
 var StringType = require('gcli/types/basic').StringType;
 var BooleanType = require('gcli/types/basic').BooleanType;
+var NumberType = require('gcli/types/basic').NumberType;
 
 var Argument = require('gcli/argument').Argument;
 var ArrayArgument = require('gcli/argument').ArrayArgument;
 var NamedArgument = require('gcli/argument').NamedArgument;
 var TrueNamedArgument = require('gcli/argument').TrueNamedArgument;
 var MergedArgument = require('gcli/argument').MergedArgument;
 var ScriptArgument = require('gcli/argument').ScriptArgument;
 
@@ -4911,39 +5008,32 @@ exports.shutdown = function() {
  * <li>onAssignmentChange: Either the value or the text has changed. It is
  * likely that any UI component displaying this argument will need to be
  * updated.
  * The event object looks like:
  * <tt>{ assignment: ..., conversion: ..., oldConversion: ... }</tt>
  * @constructor
  */
 function Assignment(param, paramIndex) {
+  // The parameter that we are assigning to
   this.param = param;
+
+  this.conversion = undefined;
+
+  // The index of this parameter in the parent Requisition. paramIndex === -1
+  // is the command assignment although this should not be relied upon, it is
+  // better to test param instanceof CommandAssignment
   this.paramIndex = paramIndex;
+
   this.onAssignmentChange = util.createEvent('Assignment.onAssignmentChange');
 
   this.setBlank();
 }
 
 /**
- * The parameter that we are assigning to
- * @readonly
- */
-Assignment.prototype.param = undefined;
-
-Assignment.prototype.conversion = undefined;
-
-/**
- * The index of this parameter in the parent Requisition. paramIndex === -1
- * is the command assignment although this should not be relied upon, it is
- * better to test param instanceof CommandAssignment
- */
-Assignment.prototype.paramIndex = undefined;
-
-/**
  * Easy accessor for conversion.arg.
  * This is a read-only property because writes to arg should be done through
  * the 'conversion' property.
  */
 Object.defineProperty(Assignment.prototype, 'arg', {
   get: function() {
     return this.conversion.arg;
   },
@@ -5016,17 +5106,17 @@ Assignment.prototype.setBlank = function
  * Argument of '' if needed.
  */
 Assignment.prototype.ensureVisibleArgument = function() {
   // It isn't clear if we should be sending events from this method.
   // It should only be called when structural changes are happening in which
   // case we're going to ignore the event anyway. But on the other hand
   // perhaps this function shouldn't need to know how it is used, and should
   // do the inefficient thing.
-  if (!this.conversion.arg.isBlank()) {
+  if (this.conversion.arg.type !== 'BlankArgument') {
     return false;
   }
 
   var arg = this.conversion.arg.beget('', {
     prefixSpace: this.param instanceof CommandAssignment
   });
   this.conversion = this.param.type.parse(arg);
   this.conversion.assign(this);
@@ -5038,23 +5128,23 @@ Assignment.prototype.ensureVisibleArgume
  * Work out what the status of the current conversion is which involves looking
  * not only at the conversion, but also checking if data has been provided
  * where it should.
  * @param arg For assignments with multiple args (e.g. array assignments) we
  * can narrow the search for status to a single argument.
  */
 Assignment.prototype.getStatus = function(arg) {
   if (this.param.isDataRequired && !this.conversion.isDataProvided()) {
-    return Status.ERROR;
+    return Status.INCOMPLETE;
   }
 
   // Selection/Boolean types with a defined range of values will say that
   // '' is INCOMPLETE, but the parameter may be optional, so we don't ask
   // if the user doesn't need to enter something and hasn't done so.
-  if (!this.param.isDataRequired && this.arg.isBlank()) {
+  if (!this.param.isDataRequired && this.arg.type === 'BlankArgument') {
     return Status.VALID;
   }
 
   return this.conversion.getStatus(arg);
 };
 
 /**
  * Replace the current value with the lower value if such a concept exists.
@@ -5084,16 +5174,36 @@ Assignment.prototype.increment = functio
 
 /**
  * Helper when we're rebuilding command lines.
  */
 Assignment.prototype.toString = function() {
   return this.conversion.toString();
 };
 
+/**
+ * For test/debug use only. The output from this function is subject to wanton
+ * random change without notice, and should not be relied upon to even exist
+ * at some later date.
+ */
+Object.defineProperty(Assignment.prototype, '_summaryJson', {
+  get: function() {
+    return {
+      param: this.param.name + '/' + this.param.type.name,
+      defaultValue: this.param.defaultValue,
+      arg: this.conversion.arg._summaryJson,
+      value: this.value,
+      message: this.getMessage(),
+      status: this.getStatus().toString(),
+      predictionCount: this.getPredictions().length
+    };
+  },
+  enumerable: true
+});
+
 exports.Assignment = Assignment;
 
 
 /**
  * How to dynamically execute JavaScript code
  */
 var customEval = eval;
 
@@ -5177,41 +5287,37 @@ CommandAssignment.prototype.getStatus = 
 };
 
 exports.CommandAssignment = CommandAssignment;
 
 
 /**
  * Special assignment used when ignoring parameters that don't have a home
  */
-function UnassignedAssignment() {
+function UnassignedAssignment(requisition, arg, isIncompleteName) {
   this.param = new canon.Parameter({
     name: '__unassigned',
-    type: 'string'
+    description: l10n.lookup('cliOptions'),
+    type: {
+      name: 'param',
+      requisition: requisition,
+      isIncompleteName: isIncompleteName
+    },
   });
   this.paramIndex = -1;
   this.onAssignmentChange = util.createEvent('UnassignedAssignment.onAssignmentChange');
 
-  this.setBlank();
+  this.conversion = this.param.type.parse(arg);
+  this.conversion.assign(this);
 }
 
 UnassignedAssignment.prototype = Object.create(Assignment.prototype);
 
 UnassignedAssignment.prototype.getStatus = function(arg) {
-  return Status.ERROR;
-};
-
-UnassignedAssignment.prototype.setUnassigned = function(args) {
-  if (!args || args.length === 0) {
-    this.setBlank();
-  }
-  else {
-    var conversion = this.param.type.parse(new MergedArgument(args));
-    this.setConversion(conversion);
-  }
+  return this.conversion.getStatus();
 };
 
 
 /**
  * A Requisition collects the information needed to execute a command.
  *
  * (For a definition of the term, see http://en.wikipedia.org/wiki/Requisition)
  * This term is used because carries the notion of a work-flow, or process to
@@ -5263,17 +5369,17 @@ function Requisition(environment, doc) {
 
   // The count of assignments. Excludes the commandAssignment
   this.assignmentCount = 0;
 
   // Used to store cli arguments in the order entered on the cli
   this._args = [];
 
   // Used to store cli arguments that were not assigned to parameters
-  this._unassigned = new UnassignedAssignment();
+  this._unassigned = [];
 
   // Temporarily set this to true to prevent _assignmentChanged resetting
   // argument positions
   this._structuralChangeInProgress = false;
 
   this.commandAssignment.onAssignmentChange.add(this._commandAssignmentChanged, this);
   this.commandAssignment.onAssignmentChange.add(this._assignmentChanged, this);
 
@@ -5426,17 +5532,17 @@ Requisition.prototype.cloneAssignments =
  * The overall status is the most severe status.
  * There is no such thing as an INCOMPLETE overall status because the
  * definition of INCOMPLETE takes into account the cursor position to say 'this
  * isn't quite ERROR because the user can fix it by typing', however overall,
  * this is still an error status.
  */
 Requisition.prototype.getStatus = function() {
   var status = Status.VALID;
-  if (!this._unassigned.arg.isBlank()) {
+  if (this._unassigned.length !== 0) {
     return Status.ERROR;
   }
   this.getAssignments(true).forEach(function(assignment) {
     var assignStatus = assignment.getStatus();
     if (assignStatus > status) {
       status = assignStatus;
     }
   }, this);
@@ -5473,16 +5579,38 @@ Requisition.prototype.getAssignments = f
   }
   Object.keys(this._assignments).forEach(function(name) {
     assignments.push(this.getAssignment(name));
   }, this);
   return assignments;
 };
 
 /**
+ * Alter the given assignment using the given arg. This function is better than
+ * calling assignment.setConversion(assignment.param.type.parse(arg)) because
+ * it adjusts the args in this requisition to keep things up to date
+ */
+Requisition.prototype.setAssignment = function(assignment, arg) {
+  var originalArg = assignment.arg;
+  var conversion = assignment.param.type.parse(arg);
+  assignment.setConversion(conversion);
+
+  // If this argument isn't assigned to anything (i.e. it was created by
+  // assignment.setBlank) we need to add it into the _args array so
+  // requisition.toString can make sense
+  if (originalArg.type === 'BlankArgument') {
+    this._args.push(arg);
+  }
+  else {
+    var index = this._args.indexOf(originalArg);
+    this._args[index] = conversion.arg;
+  }
+};
+
+/**
  * Reset all the assignments to their default values
  */
 Requisition.prototype.setBlankArguments = function() {
   this.getAssignments().forEach(function(assignment) {
     assignment.setBlank();
   }, this);
 };
 
@@ -5505,25 +5633,17 @@ Requisition.prototype.complete = functio
   var predictions = assignment.conversion.getPredictions();
   if (predictions.length > 0) {
     this.onTextChange.holdFire();
 
     var prediction = assignment.conversion.getPredictionAt(predictionChoice);
 
     // Mutate this argument to hold the completion
     var arg = assignment.arg.beget(prediction.name);
-    var conversion = assignment.param.type.parse(arg);
-    assignment.setConversion(conversion);
-
-    // If this argument isn't assigned to anything (i.e. it was created by
-    // assignment.setBlank) we need to add it into the _args array so
-    // requisition.toString can make sense
-    if (this._args.indexOf(arg) === -1) {
-      this._args.push(arg);
-    }
+    this.setAssignment(assignment, arg);
 
     if (prediction.incomplete) {
       // This is the easy case - the prediction is incomplete - no need to add
       // any spaces
       return;
     }
 
     // The prediction reported !incomplete, which means it's complete so we
@@ -5537,46 +5657,40 @@ Requisition.prototype.complete = functio
     // Also if there is already a space in those positions, don't add another
 
     var nextIndex = assignment.paramIndex + 1;
     var nextAssignment = this.getAssignment(nextIndex);
     if (nextAssignment) {
       // Add a space onto the next argument (if there isn't one there already)
       var nextArg = nextAssignment.conversion.arg;
       if (nextArg.prefix.charAt(0) !== ' ') {
-        nextArg.prefix = ' ' + nextArg.prefix;
-        var nextConversion = nextAssignment.param.type.parse(nextArg);
-        nextAssignment.setConversion(nextConversion);
-
-        // If this argument isn't assigned to anything (i.e. it was created by
-        // assignment.setBlank) we need to add it into the _args array so
-        // requisition.toString can make sense
-        if (this._args.indexOf(nextArg) === -1) {
-          this._args.push(nextArg);
-        }
+        nextArg = new Argument(nextArg.text, ' ' + nextArg.prefix, nextArg.suffix);
+        this.setAssignment(nextAssignment, nextArg);
       }
     }
     else {
       // There is no next argument, this must be the last assignment, so just
       // add the space to the prefix of this argument
-      var conversion = assignment.conversion;
-      var arg = conversion.arg;
+      arg = assignment.conversion.arg;
       if (arg.suffix.charAt(arg.suffix.length - 1) !== ' ') {
-        arg.suffix = arg.suffix + ' ';
-
-        // It's tempting to think - "we're calling setConversion twice in one
+        // It's tempting to think - "we're calling setAssignment twice in one
         // call to complete, the first time to complete the text, the second
         // to add a space, why not save the event cascade and do it once"
         // However if we're setting up the command, the number of parameters
         // changes as a result, so our call to getAssignment(nextIndex) will
         // produce the wrong answer
-        assignment.setConversion(conversion);
+        arg = new Argument(arg.text, arg.prefix, arg.suffix + ' ');
+        this.setAssignment(assignment, arg);
       }
     }
 
+    if (assignment instanceof UnassignedAssignment) {
+      this.update(this.toString());
+    }
+
     this.onTextChange();
     this.onTextChange.resumeFire();
   }
 };
 
 /**
  * Extract a canonical version of the input
  */
@@ -5672,19 +5786,26 @@ Requisition.prototype.toString = functio
  * the end don't need a space prefix.
  * While this is quite a niche function, it has 2 benefits:
  * - it's more correct because we can distinguish between final whitespace that
  *   is part of an unclosed string, and parameter separating whitespace.
  * - also it's faster than toString() the whole thing and checking the end char
  * @return true iff the last character is interpreted as parameter separating
  * whitespace
  */
-Requisition.prototype.typedEndsWithWhitespace = function() {
+Requisition.prototype.typedEndsWithSeparator = function() {
+  // This is not as easy as doing (this.toString().slice(-1) === ' ')
+  // See the doc comments above; We're checking for separators, not spaces
   if (this._args) {
-    return this._args.slice(-1)[0].suffix.slice(-1) === ' ';
+    var lastArg = this._args.slice(-1)[0];
+    if (lastArg.suffix.slice(-1) === ' ') {
+      return true;
+    }
+    return lastArg.text === '' && lastArg.suffix === ''
+        && lastArg.prefix.slice(-1) === ' ';
   }
 
   return this.toCanonicalString().slice(-1) === ' ';
 };
 
 /**
  * Return an array of Status scores so we can create a marked up
  * version of the command line input.
@@ -5705,18 +5826,26 @@ Requisition.prototype.getInputStatusMark
   for (var i = 0; i < argTraces.length; i++) {
     var argTrace = argTraces[i];
     var arg = argTrace.arg;
     var status = Status.VALID;
     if (argTrace.part === 'text') {
       status = arg.assignment.getStatus(arg);
       // Promote INCOMPLETE to ERROR  ...
       if (status === Status.INCOMPLETE) {
-        // If the cursor is not in a position to be able to complete it
-        if (arg !== cTrace.arg || cTrace.part !== 'text') {
+        // If the cursor is in the prefix or suffix of an argument then we
+        // don't consider it in the argument for the purposes of preventing
+        // the escalation to ERROR. However if this is a NamedArgument, then we
+        // allow the suffix (as space between 2 parts of the argument) to be in.
+        // We use arg.assignment.arg not arg because we're looking at the arg
+        // that got put into the assignment not as returned by tokenize()
+        var isNamed = (cTrace.arg.assignment.arg.type === 'NamedArgument');
+        var isInside = cTrace.part === 'text' ||
+                        (isNamed && cTrace.part === 'suffix');
+        if (arg.assignment !== cTrace.arg.assignment || !isInside) {
           // And if we're not in the command
           if (!(arg.assignment instanceof CommandAssignment)) {
             status = Status.ERROR;
           }
         }
       }
     }
 
@@ -5764,40 +5893,43 @@ Requisition.prototype.getAssignmentAt = 
     // prefix and text are clearly part of the argument
     for (j = 0; j < arg.prefix.length; j++) {
       assignForPos.push(assignment);
     }
     for (j = 0; j < arg.text.length; j++) {
       assignForPos.push(assignment);
     }
 
-    // suffix looks forwards
-    if (this._args.length > i + 1) {
+    // suffix is part of the argument only if this is a named parameter,
+    // otherwise it looks forwards
+    if (arg.assignment.arg.type === 'NamedArgument') {
+      // leave the argument as it is
+    }
+    else if (this._args.length > i + 1) {
       // first to the next argument
       assignment = this._args[i + 1].assignment;
     }
-    else if (assignment &&
-        assignment.paramIndex + 1 < this.assignmentCount) {
+    else if (assignment && assignment.paramIndex + 1 < this.assignmentCount) {
       // then to the next assignment
       assignment = this.getAssignment(assignment.paramIndex + 1);
     }
 
     for (j = 0; j < arg.suffix.length; j++) {
       assignForPos.push(assignment);
     }
   }
 
   // Possible shortcut, we don't really need to go through all the args
   // to work out the solution to this
 
   var reply = assignForPos[cursor - 1];
 
   if (!reply) {
     throw new Error('Missing assignment.' +
-      ' cursor=' + cursor + ' text.length=' + this.toString().length);
+        ' cursor=' + cursor + ' text=' + this.toString());
   }
 
   return reply;
 };
 
 /**
  * Entry point for keyboard accelerators or anything else that wants to execute
  * a command. There are 3 ways to call <tt>exec()</tt>:
@@ -5893,36 +6025,56 @@ Requisition.prototype.exec = function(in
 
   this.update('');
   return output;
 };
 
 /**
  * Called by the UI when ever the user interacts with a command line input
  * @param typed The contents of the input field
- * <p>The general sequence is:
- * <ul>
- * <li>_tokenize(): convert _typed into _parts
- * <li>_split(): convert _parts into _command and _unparsedArgs
- * <li>_assign(): convert _unparsedArgs into requisition
- * </ul>
  */
 Requisition.prototype.update = function(typed) {
   this._structuralChangeInProgress = true;
 
   this._args = this._tokenize(typed);
   var args = this._args.slice(0); // i.e. clone
   this._split(args);
   this._assign(args);
 
   this._structuralChangeInProgress = false;
   this.onTextChange();
 };
 
 /**
+ * For test/debug use only. The output from this function is subject to wanton
+ * random change without notice, and should not be relied upon to even exist
+ * at some later date.
+ */
+Object.defineProperty(Requisition.prototype, '_summaryJson', {
+  get: function() {
+    var summary = {
+      $args: this._args.map(function(arg) {
+        return arg._summaryJson;
+      }),
+      _command: this.commandAssignment._summaryJson,
+      _unassigned: this._unassigned.forEach(function(assignment) {
+        return assignment._summaryJson;
+      })
+    };
+
+    Object.keys(this._assignments).forEach(function(name) {
+      summary[name] = this.getAssignment(name)._summaryJson;
+    }.bind(this));
+
+    return summary;
+  },
+  enumerable: true
+});
+
+/**
  * Requisition._tokenize() is a state machine. These are the states.
  */
 var In = {
   /**
    * The last character was ' '.
    * Typing a ' ' character will not change the mode
    * Typing one of '"{ will change mode to SINGLE_Q, DOUBLE_Q or SCRIPT.
    * Anything else goes into SIMPLE mode.
@@ -6156,17 +6308,17 @@ function isSimple(typed) {
  * Looks in the canon for a command extension that matches what has been
  * typed at the command line.
  */
 Requisition.prototype._split = function(args) {
   // Handle the special case of the user typing { javascript(); }
   // We use the hidden 'eval' command directly rather than shift()ing one of
   // the parameters, and parse()ing it.
   var conversion;
-  if (args[0] instanceof ScriptArgument) {
+  if (args[0].type === 'ScriptArgument') {
     // Special case: if the user enters { console.log('foo'); } then we need to
     // use the hidden 'eval' command
     conversion = new Conversion(evalCommand, new ScriptArgument());
     this.commandAssignment.setConversion(conversion);
     return;
   }
 
   var argsUsed = 1;
@@ -6197,48 +6349,57 @@ Requisition.prototype._split = function(
   for (var i = 0; i < argsUsed; i++) {
     args.shift();
   }
 
   // This could probably be re-written to consume args as we go
 };
 
 /**
+ * Add all the passed args to the list of unassigned assignments.
+ */
+Requisition.prototype._addUnassignedArgs = function(args) {
+  args.forEach(function(arg) {
+    this._unassigned.push(new UnassignedAssignment(this, arg, false));
+  }.bind(this));
+};
+
+/**
  * Work out which arguments are applicable to which parameters.
  */
 Requisition.prototype._assign = function(args) {
+  this._unassigned = [];
+
   if (!this.commandAssignment.value) {
-    this._unassigned.setUnassigned(args);
+    this._addUnassignedArgs(args);
     return;
   }
 
   if (args.length === 0) {
     this.setBlankArguments();
-    this._unassigned.setBlank();
     return;
   }
 
   // Create an error if the command does not take parameters, but we have
   // been given them ...
   if (this.assignmentCount === 0) {
-    this._unassigned.setUnassigned(args);
+    this._addUnassignedArgs(args);
     return;
   }
 
   // Special case: if there is only 1 parameter, and that's of type
   // text, then we put all the params into the first param
   if (this.assignmentCount === 1) {
     var assignment = this.getAssignment(0);
     if (assignment.param.type instanceof StringType) {
       var arg = (args.length === 1) ?
         args[0] :
         new MergedArgument(args);
       var conversion = assignment.param.type.parse(arg);
       assignment.setConversion(conversion);
-      this._unassigned.setBlank();
       return;
     }
   }
 
   // Positional arguments can still be specified by name, but if they are
   // then we need to ignore them when working them out positionally
   var names = this.getParameterNames();
 
@@ -6258,17 +6419,17 @@ Requisition.prototype._assign = function
         });
 
         // boolean parameters don't have values, default to false
         if (assignment.param.type instanceof BooleanType) {
           arg = new TrueNamedArgument(null, arg);
         }
         else {
           var valueArg = null;
-          if (i + 1 >= args.length) {
+          if (i + 1 <= args.length) {
             valueArg = args.splice(i, 1)[0];
           }
           arg = new NamedArgument(arg, valueArg);
         }
 
         if (assignment.param.type instanceof ArrayType) {
           var arrayArg = arrayArgs[assignment.param.name];
           if (!arrayArg) {
@@ -6307,33 +6468,47 @@ Requisition.prototype._assign = function
       if (!arrayArg) {
         arrayArg = new ArrayArgument();
         arrayArgs[assignment.param.name] = arrayArg;
       }
       arrayArg.addArguments(args);
       args = [];
     }
     else {
-      var arg = (args.length > 0) ?
-          args.splice(0, 1)[0] :
-          new Argument();
-
-      var conversion = assignment.param.type.parse(arg);
-      assignment.setConversion(conversion);
+      if (args.length === 0) {
+        assignment.setBlank();
+      }
+      else {
+        var arg = args.splice(0, 1)[0];
+        // --foo and -f are named parameters, -4 is a number. So '-' is either
+        // the start of a named parameter or a number depending on the context
+        var isIncompleteName = assignment.param.type instanceof NumberType ?
+            /-[-a-zA-Z_]/.test(arg.text) :
+            arg.text.charAt(0) === '-';
+
+        if (isIncompleteName) {
+          this._unassigned.push(new UnassignedAssignment(this, arg, true));
+        }
+        else {
+          var conversion = assignment.param.type.parse(arg);
+          assignment.setConversion(conversion);
+        }
+      }
     }
   }, this);
 
   // Now we need to assign the array argument (if any)
   Object.keys(arrayArgs).forEach(function(name) {
     var assignment = this.getAssignment(name);
     var conversion = assignment.param.type.parse(arrayArgs[name]);
     assignment.setConversion(conversion);
   }, this);
 
-  this._unassigned.setUnassigned(args);
+  // What's left is can't be assigned, but we need to extract
+  this._addUnassignedArgs(args);
 };
 
 exports.Requisition = Requisition;
 
 /**
  * A simple object to hold information about the output of a command
  */
 function Output(options) {
@@ -6863,20 +7038,16 @@ FocusManager.prototype._checkShow = func
   }
 };
 
 /**
  * Calculate if we should be showing or hidden taking into account all the
  * available inputs
  */
 FocusManager.prototype._shouldShowTooltip = function() {
-  if (!this._hasFocus) {
-    return { visible: false, reason: '!hasFocus' };
-  }
-
   if (eagerHelper.value === Eagerness.NEVER) {
     return { visible: false, reason: 'eagerHelper !== NEVER' };
   }
 
   if (eagerHelper.value === Eagerness.ALWAYS) {
     return { visible: true, reason: 'eagerHelper !== ALWAYS' };
   }
 
@@ -6895,20 +7066,16 @@ FocusManager.prototype._shouldShowToolti
   return { visible: false, reason: 'default' };
 };
 
 /**
  * Calculate if we should be showing or hidden taking into account all the
  * available inputs
  */
 FocusManager.prototype._shouldShowOutput = function() {
-  if (!this._hasFocus) {
-    return { visible: false, reason: '!hasFocus' };
-  }
-
   if (this._recentOutput) {
     return { visible: true, reason: 'recentOutput' };
   }
 
   return { visible: false, reason: 'default' };
 };
 
 exports.FocusManager = FocusManager;
@@ -7698,22 +7865,24 @@ JavascriptField.DEFAULT_VALUE = '__Javas
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gcli/ui/fields/menu', ['require', 'exports', 'module' , 'gcli/util', 'gcli/argument', 'gcli/canon', 'gcli/ui/domtemplate', 'text!gcli/ui/fields/menu.css', 'text!gcli/ui/fields/menu.html'], function(require, exports, module) {
+define('gcli/ui/fields/menu', ['require', 'exports', 'module' , 'gcli/util', 'gcli/l10n', 'gcli/argument', 'gcli/types', 'gcli/canon', 'gcli/ui/domtemplate', 'text!gcli/ui/fields/menu.css', 'text!gcli/ui/fields/menu.html'], function(require, exports, module) {
 
 
 var util = require('gcli/util');
+var l10n = require('gcli/l10n');
 
 var Argument = require('gcli/argument').Argument;
+var Conversion = require('gcli/types').Conversion;
 var canon = require('gcli/canon');
 
 var domtemplate = require('gcli/ui/domtemplate');
 
 var menuCss = require('text!gcli/ui/fields/menu.css');
 var menuHtml = require('text!gcli/ui/fields/menu.html');
 
 
@@ -7754,16 +7923,21 @@ function Menu(options) {
 
   // Contains the items that should be displayed
   this.items = null;
 
   this.onItemClick = util.createEvent('Menu.onItemClick');
 }
 
 /**
+ * Allow the template engine to get at localization strings
+ */
+Menu.prototype.l10n = l10n.propertyLookup;
+
+/**
  * Avoid memory leaks
  */
 Menu.prototype.destroy = function() {
   delete this.element;
   delete this.template;
   delete this.document;
 };
 
@@ -7798,16 +7972,21 @@ Menu.prototype.show = function(items, ma
     }.bind(this));
   }
 
   if (this.items.length === 0) {
     this.element.style.display = 'none';
     return;
   }
 
+  if (this.items.length >= Conversion.maxPredictions) {
+    this.items.splice(-1);
+    this.items.hasMore = true;
+  }
+
   var options = this.template.cloneNode(true);
   domtemplate.template(options, this, this.templateOptions);
 
   util.clearElement(this.element);
   this.element.appendChild(options);
 
   this.element.style.display = 'block';
 };
@@ -7898,23 +8077,26 @@ Menu.prototype.setMaxHeight = function(h
 
 exports.Menu = Menu;
 
 
 });
 define("text!gcli/ui/fields/menu.css", [], "");
 
 define("text!gcli/ui/fields/menu.html", [], "\n" +
-  "<table class=\"gcli-menu-template\" aria-live=\"polite\">\n" +
-  "  <tr class=\"gcli-menu-option\" foreach=\"item in ${items}\"\n" +
-  "      onclick=\"${onItemClickInternal}\" title=\"${item.manual}\">\n" +
-  "    <td class=\"gcli-menu-name\">${item.name}</td>\n" +
-  "    <td class=\"gcli-menu-desc\">${item.description}</td>\n" +
-  "  </tr>\n" +
-  "</table>\n" +
+  "<div>\n" +
+  "  <table class=\"gcli-menu-template\" aria-live=\"polite\">\n" +
+  "    <tr class=\"gcli-menu-option\" foreach=\"item in ${items}\"\n" +
+  "        onclick=\"${onItemClickInternal}\" title=\"${item.manual}\">\n" +
+  "      <td class=\"gcli-menu-name\">${item.name}</td>\n" +
+  "      <td class=\"gcli-menu-desc\">${item.description}</td>\n" +
+  "    </tr>\n" +
+  "  </table>\n" +
+  "  <div class=\"gcli-menu-more\" if=\"${items.hasMore}\">${l10n.fieldMenuMore}</div>\n" +
+  "</div>\n" +
   "");
 
 /*
  * Copyright 2012, Mozilla Foundation and contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -9240,23 +9422,26 @@ Inputter.prototype.onKeyUp = function(ev
       // should select the incorrect part of the input for an easy fix
     }
 
     this._choice = null;
     return;
   }
 
   if (ev.keyCode === KeyEvent.DOM_VK_TAB && !ev.shiftKey) {
+    // Being able to complete 'nothing' is OK if there is some context, but
+    // when there is nothing on the command line it jsut looks bizarre.
+    var hasContents = (this.element.value.length > 0);
     // If the TAB keypress took the cursor from another field to this one,
     // then they get the keydown/keypress, and we get the keyup. In this
     // case we don't want to do any completion.
     // If the time of the keydown/keypress of TAB was close (i.e. within
     // 1 second) to the time of the keyup then we assume that we got them
     // both, and do the completion.
-    if (this.lastTabDownAt + 1000 > ev.timeStamp) {
+    if (hasContents && this.lastTabDownAt + 1000 > ev.timeStamp) {
       // It's possible for TAB to not change the input, in which case the
       // textChanged event will not fire, and the caret move will not be
       // processed. So we check that this is done first
       this._caretChange = Caret.TO_ARG_END;
       var inputState = this.getInputState();
       this._processCaretChange(inputState);
       if (this._choice == null) {
         this._choice = 0;
@@ -9510,185 +9695,147 @@ Completer.prototype.resized = function(e
   this.element.style.top = ev.top + 'px';
   this.element.style.height = ev.height + 'px';
   this.element.style.lineHeight = ev.height + 'px';
   this.element.style.left = ev.left + 'px';
   this.element.style.width = ev.width + 'px';
 };
 
 /**
- * Is the completion given, a "strict" completion of the user inputted value?
- * A completion is considered "strict" only if it the user inputted value is an
- * exact prefix of the completion (ignoring leading whitespace)
- */
-function isStrictCompletion(inputValue, completion) {
-  // Strip any leading whitespace from the user inputted value because the
-  // completion will never have leading whitespace.
-  inputValue = inputValue.replace(/^\s*/, '');
-  // Strict: "ec" -> "echo"
-  // Non-Strict: "ls *" -> "ls foo bar baz"
-  return completion.indexOf(inputValue) === 0;
-}
-
-/**
  * Bring the completion element up to date with what the requisition says
  */
 Completer.prototype.update = function(ev) {
   if (ev && ev.choice != null) {
     this.choice = ev.choice;
   }
-  this._preTemplateUpdate();
-
+
+  var data = this._getCompleterTemplateData();
   var template = this.template.cloneNode(true);
-  domtemplate.template(template, this, { stack: 'completer.html' });
+  domtemplate.template(template, data, { stack: 'completer.html' });
 
   util.clearElement(this.element);
   while (template.hasChildNodes()) {
     this.element.appendChild(template.firstChild);
   }
 };
 
 /**
- * Update the state of a number of internal variables in preparation for
- * templating. Some of these properties are interdependent, so it makes sense
- * to do them in one go.
- */
-Completer.prototype._preTemplateUpdate = function() {
-  this.input = this.inputter.getInputState();
-
-  this.directTabText = '';
-  this.arrowTabText = '';
-
-  // What text should we display as the tab text, and should it be given as a
-  // '-> full' or as 'suffix' (which depends on if the completion is a strict
-  // completion or not)
-  if (this.input.typed.trim().length === 0) {
-    return;
-  }
-
+ * Calculate the properties required by the template process for completer.html
+ */
+Completer.prototype._getCompleterTemplateData = function() {
+  var input = this.inputter.getInputState();
+
+  // directTabText is for when the current input is a prefix of the completion
+  // arrowTabText is for when we need to use an -> to show what will be used
+  var directTabText = '';
+  var arrowTabText = '';
   var current = this.inputter.assignment;
-  var prediction = current.conversion.getPredictionAt(this.choice);
-  if (!prediction) {
-    return;
-  }
-
-  var tabText = prediction.name;
-  var existing = current.arg.text;
-
-  if (existing === tabText) {
-    return;
-  }
-
-  if (isStrictCompletion(existing, tabText) &&
-          this.input.cursor.start === this.input.typed.length) {
-    // Display the suffix of the prediction as the completion
-    var numLeadingSpaces = existing.match(/^(\s*)/)[0].length;
-
-    this.directTabText = tabText.slice(existing.length - numLeadingSpaces);
-  }
-  else {
-    // Display the '-> prediction' at the end of the completer element
-    // These JS escapes are aka &nbsp;&rarr; the right arrow
-    this.arrowTabText = ' \u00a0\u21E5 ' + tabText;
-  }
-};
-
-/**
- * A proxy to requisition.getInputStatusMarkup which converts space to &nbsp;
- * in the string member (for HTML display) and converts status to an
- * appropriate class name (i.e. lower cased, prefixed with gcli-in-)
- */
-Object.defineProperty(Completer.prototype, 'statusMarkup', {
-  get: function() {
-    var markup = this.requisition.getInputStatusMarkup(this.input.cursor.start);
-    markup.forEach(function(member) {
-      member.string = member.string.replace(/ /g, '\u00a0'); // i.e. &nbsp;
-      member.className = 'gcli-in-' + member.status.toString().toLowerCase();
-    }, this);
-    return markup;
-  },
-  enumerable: true
-});
-
-/**
- * The text for the 'jump to scratchpad' feature, or null if it is disabled
- */
-Object.defineProperty(Completer.prototype, 'scratchLink', {
-  get: function() {
-    if (!this.scratchpad) {
-      return null;
-    }
-    var command = this.requisition.commandAssignment.value;
-    return command && command.name === '{' ? this.scratchpad.linkText : null;
-  },
-  enumerable: true
-});
-
-/**
- * Is the entered command a JS command with no closing '}'?
- * TWEAK: This code should be considered for promotion to Requisition
- */
-Object.defineProperty(Completer.prototype, 'unclosedJs', {
-  get: function() {
-    var command = this.requisition.commandAssignment.value;
-    var jsCommand = command && command.name === '{';
-    var unclosedJs = jsCommand &&
-        this.requisition.getAssignment(0).arg.suffix.indexOf('}') === -1;
-    return unclosedJs;
-  },
-  enumerable: true
-});
-
-/**
- * Accessor for the list of parameters to be filled in
- */
-Object.defineProperty(Completer.prototype, 'emptyParameters', {
-  get: function() {
-    var typedEndSpace = this.requisition.typedEndsWithWhitespace();
-    // Cache computed property
-    var directTabText = this.directTabText;
-    // If this is the first blank assignment we might not need a space prefix
-    // also we skip [param] text if we have directTabText, but only for the
-    // first blank param.
-    var firstBlankParam = true;
-    var params = [];
-    this.requisition.getAssignments().forEach(function(assignment) {
-      if (!assignment.param.isPositionalAllowed) {
-        return;
+
+  if (input.typed.trim().length !== 0) {
+    var prediction = current.conversion.getPredictionAt(this.choice);
+    if (prediction) {
+      var tabText = prediction.name;
+      var existing = current.arg.text;
+
+      if (existing !== tabText) {
+        // Decide to use directTabText or arrowTabText
+        // Strip any leading whitespace from the user inputted value because the
+        // tabText will never have leading whitespace.
+        var inputValue = existing.replace(/^\s*/, '');
+        var isStrictCompletion = tabText.indexOf(inputValue) === 0;
+        if (isStrictCompletion && input.cursor.start === input.typed.length) {
+          // Display the suffix of the prediction as the completion
+          var numLeadingSpaces = existing.match(/^(\s*)/)[0].length;
+
+          directTabText = tabText.slice(existing.length - numLeadingSpaces);
+        }
+        else {
+          // Display the '-> prediction' at the end of the completer element
+          // These JS escapes are aka &nbsp;&rarr; the right arrow
+          arrowTabText = ' \u00a0\u21E5 ' + tabText;
+        }
       }
-
-      if (!assignment.arg.isBlank()) {
-        if (directTabText !== '') {
-          firstBlankParam = false;
-        }
-        return;
-      }
-
-      if (directTabText !== '' && firstBlankParam) {
+    }
+  }
+
+  // statusMarkup is wrapper around requisition.getInputStatusMarkup converting
+  // space to &nbsp; in the string member (for HTML display) and status to an
+  // appropriate class name (i.e. lower cased, prefixed with gcli-in-)
+  var statusMarkup = this.requisition.getInputStatusMarkup(input.cursor.start);
+  statusMarkup.forEach(function(member) {
+    member.string = member.string.replace(/ /g, '\u00a0'); // i.e. &nbsp;
+    member.className = 'gcli-in-' + member.status.toString().toLowerCase();
+  }, this);
+
+  // Calculate the list of parameters to be filled in
+  var trailingSeparator = this.requisition.typedEndsWithSeparator();
+  // We generate an array of emptyParameter markers for each positional
+  // parameter to the current command.
+  // Generally each emptyParameter marker begins with a space to separate it
+  // from whatever came before, unless what comes before ends in a space.
+  // Also if we've got a directTabText prediction or we're in a NamedParameter
+  // then we don't want any text for that parameter at all.
+  // The algorithm to add spaces needs to take this into account.
+
+  var firstBlankParam = true;
+  var emptyParameters = [];
+  this.requisition.getAssignments().forEach(function(assignment) {
+    if (!assignment.param.isPositionalAllowed) {
+      return;
+    }
+    if (current.arg.type === 'NamedArgument') {
+      return;
+    }
+
+    if (assignment.arg.toString().trim() !== '') {
+      if (directTabText !== '') {
         firstBlankParam = false;
-        return;
       }
-
-      var text = (assignment.param.isDataRequired) ?
-          '<' + assignment.param.name + '>' :
-          '[' + assignment.param.name + ']';
-
-      // Add a space if we don't have one at the end of the input or if
-      // this isn't the first param we've mentioned
-      if (!typedEndSpace || !firstBlankParam) {
-        text = '\u00a0' + text; // i.e. &nbsp;
-      }
-
+      return;
+    }
+
+    if (directTabText !== '' && firstBlankParam) {
       firstBlankParam = false;
-      params.push(text);
-    }.bind(this));
-    return params;
-  },
-  enumerable: true
-});
+      return;
+    }
+
+    var text = (assignment.param.isDataRequired) ?
+        '<' + assignment.param.name + '>' :
+        '[' + assignment.param.name + ']';
+
+    // Add a space if we don't have one at the end of the input or if
+    // this isn't the first param we've mentioned
+    if (!trailingSeparator || !firstBlankParam) {
+      text = '\u00a0' + text; // i.e. &nbsp;
+    }
+
+    firstBlankParam = false;
+    emptyParameters.push(text);
+  }.bind(this));
+
+  var command = this.requisition.commandAssignment.value;
+  var jsCommand = command && command.name === '{';
+
+  // Is the entered command a JS command with no closing '}'?
+  // TWEAK: This code should be considered for promotion to Requisition
+  var unclosedJs = jsCommand &&
+      this.requisition.getAssignment(0).arg.suffix.indexOf('}') === -1;
+
+  // The text for the 'jump to scratchpad' feature, or '' if it is disabled
+  var link = this.scratchpad && jsCommand ? this.scratchpad.linkText : '';
+
+  return {
+    statusMarkup: statusMarkup,
+    directTabText: directTabText,
+    emptyParameters: emptyParameters,
+    arrowTabText: arrowTabText,
+    unclosedJs: unclosedJs,
+    scratchLink: link
+  };
+};
 
 exports.Completer = Completer;
 
 
 });
 define("text!gcli/ui/completer.html", [], "\n" +
   "<description>\n" +
   "  <loop foreach=\"member in ${statusMarkup}\">\n" +
--- a/browser/devtools/commandline/test/browser_gcli_web.js
+++ b/browser/devtools/commandline/test/browser_gcli_web.js
@@ -80,19 +80,21 @@ registerCleanupFunction(function tearDow
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gclitest/index', ['require', 'exports', 'module' , 'gclitest/suite'], function(require, exports, module) {
+define('gclitest/index', ['require', 'exports', 'module' , 'gclitest/suite', 'gcli/settings', 'gcli/ui/display'], function(require, exports, module) {
 
   var examiner = require('gclitest/suite').examiner;
+  var settings = require('gcli/settings');
+  var Display = require('gcli/ui/display').Display;
 
   // A minimum fake dom to get us through the JS tests
   var fakeWindow = {
     isFake: true,
     document: { title: 'Fake DOM' }
   };
   fakeWindow.window = fakeWindow;
   examiner.defaultOptions = {
@@ -113,27 +115,73 @@ define('gclitest/index', ['require', 'ex
    *   A reduced set of tests will run if left undefined
    * - detailedResultLog (default=false) do we output a test summary to
    *   |console.log| on test completion.
    * - hideExec (default=false) Set the |hidden| property in calls to
    *   |requisition.exec()| which prevents the display from becoming messed up,
    *   however use of hideExec restricts the set of tests that are run
    */
   exports.run = function(options) {
-    examiner.run(options || {});
+    options = options || {};
+    examiner.mergeDefaultOptions(options);
+
+    examiner.reset();
+    examiner.run(options);
 
     // A better set of default than those specified above, come from the set
     // that are passed to run().
     examiner.defaultOptions = {
       window: options.window,
       display: options.display,
       hideExec: options.hideExec
     };
   };
 
+  /**
+   * This is the equivalent of gcli/index.createDisplay() except it:
+   * - Sets window.display: to actual Display object (not the thin proxy
+   *   returned by gcli/index.createDisplay() and this function)
+   * - Registers all the test commands, and provides a function to re-register
+   *   them - window.testCommands() (running the test suite un-registers them)
+   * - Runs the unit tests automatically on startup
+   * - Registers a 'test' command to re-run the unit tests
+   */
+  exports.createDisplay = function(options) {
+    options = options || {};
+    if (options.settings != null) {
+      settings.setDefaults(options.settings);
+    }
+
+    window.display = new Display(options);
+    var requisition = window.display.requisition;
+
+    exports.run({
+      window: window,
+      display: window.display,
+      hideExec: true
+    });
+
+    window.testCommands = function() {
+      require([ 'gclitest/mockCommands' ], function(mockCommands) {
+        mockCommands.setup();
+      });
+    };
+    window.testCommands();
+
+    return {
+      /**
+       * The exact shape of the object returned by exec is likely to change in
+       * the near future. If you do use it, please expect your code to break.
+       */
+      exec: requisition.exec.bind(requisition),
+      update: requisition.update.bind(requisition),
+      destroy: window.display.destroy.bind(window.display)
+    };
+  };
+
 });
 /*
  * Copyright 2012, Mozilla Foundation and contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -141,33 +189,34 @@ define('gclitest/index', ['require', 'ex
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gclitest/suite', ['require', 'exports', 'module' , 'gcli/index', 'test/examiner', 'gclitest/testCanon', 'gclitest/testCli', 'gclitest/testCompletion', 'gclitest/testExec', 'gclitest/testHelp', 'gclitest/testHistory', 'gclitest/testInputter', 'gclitest/testIntro', 'gclitest/testJs', 'gclitest/testKeyboard', 'gclitest/testPref', 'gclitest/testRequire', 'gclitest/testResource', 'gclitest/testScratchpad', 'gclitest/testSettings', 'gclitest/testSpell', 'gclitest/testSplit', 'gclitest/testTokenize', 'gclitest/testTooltip', 'gclitest/testTypes', 'gclitest/testUtil'], function(require, exports, module) {
+define('gclitest/suite', ['require', 'exports', 'module' , 'gcli/index', 'test/examiner', 'gclitest/testCanon', 'gclitest/testCli', 'gclitest/testCompletion', 'gclitest/testExec', 'gclitest/testHelp', 'gclitest/testHistory', 'gclitest/testInputter', 'gclitest/testIncomplete', 'gclitest/testIntro', 'gclitest/testJs', 'gclitest/testKeyboard', 'gclitest/testPref', 'gclitest/testRequire', 'gclitest/testResource', 'gclitest/testScratchpad', 'gclitest/testSettings', 'gclitest/testSpell', 'gclitest/testSplit', 'gclitest/testTokenize', 'gclitest/testTooltip', 'gclitest/testTypes', 'gclitest/testUtil'], function(require, exports, module) {
 
   // We need to make sure GCLI is initialized before we begin testing it
   require('gcli/index');
 
   var examiner = require('test/examiner');
 
   // It's tempting to want to unify these strings and make addSuite() do the
   // call to require(), however that breaks the build system which looks for
   // the strings passed to require
   examiner.addSuite('gclitest/testCanon', require('gclitest/testCanon'));
   examiner.addSuite('gclitest/testCli', require('gclitest/testCli'));
   examiner.addSuite('gclitest/testCompletion', require('gclitest/testCompletion'));
   examiner.addSuite('gclitest/testExec', require('gclitest/testExec'));
   examiner.addSuite('gclitest/testHelp', require('gclitest/testHelp'));
   examiner.addSuite('gclitest/testHistory', require('gclitest/testHistory'));
   examiner.addSuite('gclitest/testInputter', require('gclitest/testInputter'));
+  examiner.addSuite('gclitest/testIncomplete', require('gclitest/testIncomplete'));
   examiner.addSuite('gclitest/testIntro', require('gclitest/testIntro'));
   examiner.addSuite('gclitest/testJs', require('gclitest/testJs'));
   examiner.addSuite('gclitest/testKeyboard', require('gclitest/testKeyboard'));
   examiner.addSuite('gclitest/testPref', require('gclitest/testPref'));
   examiner.addSuite('gclitest/testRequire', require('gclitest/testRequire'));
   examiner.addSuite('gclitest/testResource', require('gclitest/testResource'));
   examiner.addSuite('gclitest/testScratchpad', require('gclitest/testScratchpad'));
   examiner.addSuite('gclitest/testSettings', require('gclitest/testSettings'));
@@ -226,30 +275,28 @@ examiner.addSuite = function(name, suite
  * with the specified options in |run()|.
  */
 examiner.defaultOptions = {};
 
 /**
  * Add properties to |options| from |examiner.defaultOptions| when |options|
  * does not have a value for a given name.
  */
-function mergeDefaultOptions(options) {
+examiner.mergeDefaultOptions = function(options) {
   Object.keys(examiner.defaultOptions).forEach(function(name) {
     if (options[name] == null) {
       options[name] = examiner.defaultOptions[name];
     }
   });
-}
+};
 
 /**
  * Run the tests defined in the test suite synchronously
  */
 examiner.run = function(options) {
-  mergeDefaultOptions(options);
-
   Object.keys(examiner.suites).forEach(function(suiteName) {
     var suite = examiner.suites[suiteName];
     suite.run(options);
   }.bind(this));
 
   if (options.detailedResultLog) {
     examiner.detailedResultLog();
   }
@@ -259,17 +306,16 @@ examiner.run = function(options) {
 
   return examiner.suites;
 };
 
 /**
  * Run all the tests asynchronously
  */
 examiner.runAsync = function(options, callback) {
-  mergeDefaultOptions(options);
   this._runAsyncInternal(0, options, callback);
 };
 
 /**
  * Run all the test suits asynchronously
  */
 examiner._runAsyncInternal = function(i, options, callback) {
   if (i >= Object.keys(examiner.suites).length) {
@@ -298,16 +344,25 @@ examiner.toRemote = function() {
     summary: {
       checks: this.checks,
       status: this.status
     }
   };
 };
 
 /**
+ * Reset all the tests to their original state
+ */
+examiner.reset = function() {
+  Object.keys(examiner.suites).forEach(function(suiteName) {
+    examiner.suites[suiteName].reset();
+  }, this);
+};
+
+/**
  * The number of checks in this set of test suites is the sum of the checks in
  * the test suites.
  */
 Object.defineProperty(examiner, 'checks', {
   get: function() {
     return  Object.keys(examiner.suites).reduce(function(current, suiteName) {
       return current + examiner.suites[suiteName].checks;
     }.bind(this), 0);
@@ -371,27 +426,35 @@ function Suite(suiteName, suite) {
     if (testName !== 'setup' && testName !== 'shutdown') {
       var test = new Test(this, testName, suite[testName]);
       this.tests[testName] = test;
     }
   }.bind(this));
 }
 
 /**
+ * Reset all the tests to their original state
+ */
+Suite.prototype.reset = function() {
+  Object.keys(this.tests).forEach(function(testName) {
+    this.tests[testName].reset();
+  }, this);
+};
+
+/**
  * Run all the tests in this suite synchronously
  */
 Suite.prototype.run = function(options) {
   if (!this._setup(options)) {
     return;
   }
 
   Object.keys(this.tests).forEach(function(testName) {
-    var test = this.tests[testName];
-    test.run(options);
-  }.bind(this));
+    this.tests[testName].run(options);
+  }, this);
 
   this._shutdown(options);
 };
 
 /**
  * Run all the tests in this suite asynchronously
  */
 Suite.prototype.runAsync = function(options, callback) {
@@ -536,30 +599,39 @@ function Test(suite, name, func) {
   this.title = name.replace(/^test/, '').replace(/([A-Z])/g, ' $1');
 
   this.failures = [];
   this.status = stati.notrun;
   this.checks = 0;
 }
 
 /**
+ * Reset the test to its original state
+ */
+Test.prototype.reset = function() {
+  this.failures = [];
+  this.status = stati.notrun;
+  this.checks = 0;
+};
+
+/**
  * Run just a single test
  */
 Test.prototype.run = function(options) {
   assert.currentTest = this;
   this.status = stati.executing;
   this.failures = [];
   this.checks = 0;
 
   try {
     this.func.apply(this.suite, [ options ]);
   }
   catch (ex) {
     assert.ok(false, '' + ex);
-    console.error(ex);
+    console.error(ex.stack);
     if ((options.isNode || options.isFirefox) && ex.stack) {
       console.error(ex.stack);
     }
   }
 
   if (this.status === stati.executing) {
     this.status = stati.pass;
   }
@@ -765,20 +837,32 @@ define('gclitest/testCanon', ['require',
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gclitest/helpers', ['require', 'exports', 'module' , 'test/assert'], function(require, exports, module) {
+define('gclitest/helpers', ['require', 'exports', 'module' , 'test/assert', 'gcli/util'], function(require, exports, module) {
 
 
 var test = require('test/assert');
+var util = require('gcli/util');
+
+
+var cachedOptions = undefined;
+
+exports.setup = function(opts) {
+  cachedOptions = opts;
+};
+
+exports.shutdown = function(opts) {
+  cachedOptions = undefined;
+};
 
 /**
  * Check that we can parse command input.
  * Doesn't execute the command, just checks that we grok the input properly:
  *
  * helpers.status({
  *   // Test inputs
  *   typed: "ech",           // Required
@@ -787,68 +871,243 @@ var test = require('test/assert');
  *   // Thing to check
  *   status: "INCOMPLETE",   // One of "VALID", "ERROR", "INCOMPLETE"
  *   emptyParameters: [ "<message>" ], // Still to type
  *   directTabText: "o",     // Simple completion text
  *   arrowTabText: "",       // When the completion is not an extension
  *   markup: "VVVIIIEEE",    // What state should the error markup be in
  * });
  */
-exports.status = function(options, tests) {
+exports.status = function(options, checks) {
   var requisition = options.display.requisition;
   var inputter = options.display.inputter;
   var completer = options.display.completer;
 
-  if (tests.typed) {
-    inputter.setInput(tests.typed);
+  if (checks.typed) {
+    inputter.setInput(checks.typed);
   }
   else {
-    test.ok(false, "Missing typed for " + JSON.stringify(tests));
+    test.ok(false, "Missing typed for " + JSON.stringify(checks));
     return;
   }
 
-  if (tests.cursor) {
-    inputter.setCursor(tests.cursor);
+  if (checks.cursor) {
+    inputter.setCursor(checks.cursor);
   }
 
-  if (tests.status) {
-    test.is(requisition.getStatus().toString(), tests.status,
-            "status for " + tests.typed);
+  if (checks.status) {
+    test.is(requisition.getStatus().toString(),
+            checks.status,
+            "status for " + checks.typed);
   }
 
-  if (tests.emptyParameters != null) {
-    var realParams = completer.emptyParameters;
-    test.is(realParams.length, tests.emptyParameters.length,
-            'emptyParameters.length for \'' + tests.typed + '\'');
-
-    if (realParams.length === tests.emptyParameters.length) {
+  var data = completer._getCompleterTemplateData();
+  if (checks.emptyParameters != null) {
+    var realParams = data.emptyParameters;
+    test.is(realParams.length,
+            checks.emptyParameters.length,
+            'emptyParameters.length for \'' + checks.typed + '\'');
+
+    if (realParams.length === checks.emptyParameters.length) {
       for (var i = 0; i < realParams.length; i++) {
-        test.is(realParams[i].replace(/\u00a0/g, ' '), tests.emptyParameters[i],
-                'emptyParameters[' + i + '] for \'' + tests.typed + '\'');
+        test.is(realParams[i].replace(/\u00a0/g, ' '),
+                checks.emptyParameters[i],
+                'emptyParameters[' + i + '] for \'' + checks.typed + '\'');
       }
     }
   }
 
-  if (tests.markup) {
-    var cursor = tests.cursor ? tests.cursor.start : tests.typed.length;
+  if (checks.markup) {
+    var cursor = checks.cursor ? checks.cursor.start : checks.typed.length;
     var statusMarkup = requisition.getInputStatusMarkup(cursor);
     var actualMarkup = statusMarkup.map(function(s) {
       return Array(s.string.length + 1).join(s.status.toString()[0]);
     }).join('');
-    test.is(tests.markup, actualMarkup, 'markup for ' + tests.typed);
+
+    test.is(checks.markup,
+            actualMarkup,
+            'markup for ' + checks.typed);
+  }
+
+  if (checks.directTabText) {
+    test.is(data.directTabText,
+            checks.directTabText,
+            'directTabText for \'' + checks.typed + '\'');
+  }
+
+  if (checks.arrowTabText) {
+    test.is(' \u00a0\u21E5 ' + checks.arrowTabText,
+            data.arrowTabText,
+            'arrowTabText for \'' + checks.typed + '\'');
+  }
+};
+
+/**
+ * We're splitting status into setup() which alters the state of the system
+ * and check() which ensures that things are in the right place afterwards.
+ */
+exports.setInput = function(typed, cursor) {
+  cachedOptions.display.inputter.setInput(typed);
+
+  if (cursor) {
+    cachedOptions.display.inputter.setCursor({ start: cursor, end: cursor });
+  }
+};
+
+/**
+ * Simulate pressing TAB in the input field
+ */
+exports.pressTab = function() {
+  // requisition.complete({ start: 5, end: 5 }, 0);
+
+  var fakeEvent = {
+    keyCode: util.KeyEvent.DOM_VK_TAB,
+    preventDefault: function() { },
+    timeStamp: new Date().getTime()
+  };
+  cachedOptions.display.inputter.onKeyDown(fakeEvent);
+  cachedOptions.display.inputter.onKeyUp(fakeEvent);
+};
+
+/**
+ * check() is the new status. Similar API except that it doesn't attempt to
+ * alter the display/requisition at all, and it makes extra checks.
+ * Available checks:
+ *   input: The text displayed in the input field
+ *   cursor: The position of the start of the cursor
+ *   status: One of "VALID", "ERROR", "INCOMPLETE"
+ *   emptyParameters: Array of parameters still to type. e.g. [ "<message>" ]
+ *   directTabText: Simple completion text
+ *   arrowTabText: When the completion is not an extension (without arrow)
+ *   markup: What state should the error markup be in. e.g. "VVVIIIEEE"
+ *   args: Maps of checks to make against the arguments:
+ *     value: i.e. assignment.value (which ignores defaultValue)
+ *     type: Argument/BlankArgument/MergedArgument/etc i.e. what's assigned
+ *           Care should be taken with this since it's something of an
+ *           implementation detail
+ *     arg: The toString value of the argument
+ *     status: i.e. assignment.getStatus
+ *     message: i.e. assignment.getMessage
+ *     name: For commands - checks assignment.value.name
+ */
+exports.check = function(checks) {
+  var requisition = cachedOptions.display.requisition;
+  var completer = cachedOptions.display.completer;
+  var actual = completer._getCompleterTemplateData();
+
+  if (checks.input) {
+    test.is(cachedOptions.display.inputter.element.value,
+            checks.input,
+            'input');
+  }
+
+  if (checks.cursor) {
+    test.is(cachedOptions.display.inputter.element.selectionStart,
+            checks.cursor,
+            'cursor');
+  }
+
+  if (checks.status) {
+    test.is(requisition.getStatus().toString(),
+            checks.status,
+            'status');
   }
 
-  if (tests.directTabText) {
-    test.is(completer.directTabText, tests.directTabText,
-            'directTabText for \'' + tests.typed + '\'');
+  if (checks.markup) {
+    var cursor = cachedOptions.display.inputter.element.selectionStart;
+    var statusMarkup = requisition.getInputStatusMarkup(cursor);
+    var actualMarkup = statusMarkup.map(function(s) {
+      return Array(s.string.length + 1).join(s.status.toString()[0]);
+    }).join('');
+
+    test.is(checks.markup,
+            actualMarkup,
+            'markup');
+  }
+
+  if (checks.emptyParameters) {
+    var actualParams = actual.emptyParameters;
+    test.is(actualParams.length,
+            checks.emptyParameters.length,
+            'emptyParameters.length');
+
+    if (actualParams.length === checks.emptyParameters.length) {
+      for (var i = 0; i < actualParams.length; i++) {
+        test.is(actualParams[i].replace(/\u00a0/g, ' '),
+                checks.emptyParameters[i],
+                'emptyParameters[' + i + ']');
+      }
+    }
+  }
+
+  if (checks.directTabText) {
+    test.is(actual.directTabText,
+            checks.directTabText,
+            'directTabText');
+  }
+
+  if (checks.arrowTabText) {
+    test.is(actual.arrowTabText,
+            ' \u00a0\u21E5 ' + checks.arrowTabText,
+            'arrowTabText');
   }
 
-  if (tests.arrowTabText) {
-    test.is(completer.arrowTabText, ' \u00a0\u21E5 ' + tests.arrowTabText,
-            'arrowTabText for \'' + tests.typed + '\'');
+  if (checks.args) {
+    Object.keys(checks.args).forEach(function(paramName) {
+      var check = checks.args[paramName];
+
+      var assignment;
+      if (paramName === 'command') {
+        assignment = requisition.commandAssignment;
+      }
+      else {
+        assignment = requisition.getAssignment(paramName);
+      }
+
+      if (assignment == null) {
+        test.ok(false, 'Unknown parameter: ' + paramName);
+        return;
+      }
+
+      if (check.value) {
+        test.is(assignment.value,
+                check.value,
+                'checkStatus value for ' + paramName);
+      }
+
+      if (check.name) {
+        test.is(assignment.value.name,
+                check.name,
+                'checkStatus name for ' + paramName);
+      }
+
+      if (check.type) {
+        test.is(assignment.arg.type,
+                check.type,
+                'checkStatus type for ' + paramName);
+      }
+
+      if (check.arg) {
+        test.is(assignment.arg.toString(),
+                check.arg,
+                'checkStatus arg for ' + paramName);
+      }
+
+      if (check.status) {
+        test.is(assignment.getStatus().toString(),
+                check.status,
+                'checkStatus status for ' + paramName);
+      }
+
+      if (check.message) {
+        test.is(assignment.getMessage(),
+                check.message,
+                'checkStatus message for ' + paramName);
+      }
+    });
   }
 };
 
 /**
  * Execute a command:
  *
  * helpers.exec({
  *   // Test inputs
@@ -1163,45 +1422,46 @@ exports.testTsv = function() {
   test.is('number', typeof assign2.value);
   test.is(1, assignC.paramIndex);
 };
 
 exports.testInvalid = function() {
   update({ typed: 'zxjq', cursor: { start: 4, end: 4 } });
   test.is(        'EEEE', statuses);
   test.is('zxjq', requ.commandAssignment.arg.text);
-  test.is('', requ._unassigned.arg.text);
+  test.is(0, requ._unassigned.length);
   test.is(-1, assignC.paramIndex);
 
   update({ typed: 'zxjq ', cursor: { start: 5, end: 5 } });
   test.is(        'EEEEV', statuses);
   test.is('zxjq', requ.commandAssignment.arg.text);
-  test.is('', requ._unassigned.arg.text);
+  test.is(0, requ._unassigned.length);
   test.is(-1, assignC.paramIndex);
 
   update({ typed: 'zxjq one', cursor: { start: 8, end: 8 } });
   test.is(        'EEEEVEEE', statuses);
   test.is('zxjq', requ.commandAssignment.arg.text);
-  test.is('one', requ._unassigned.arg.text);
+  test.is(1, requ._unassigned.length);
+  test.is('one', requ._unassigned[0].arg.text);
 };
 
 exports.testSingleString = function() {
   update({ typed: 'tsr', cursor: { start: 3, end: 3 } });
   test.is(        'VVV', statuses);
   test.is(Status.ERROR, status);
   test.is('tsr', requ.commandAssignment.value.name);
-  test.ok(assign1.arg.isBlank());
+  test.ok(assign1.arg.type === 'BlankArgument');
   test.is(undefined, assign1.value);
   test.is(undefined, assign2);
 
   update({ typed: 'tsr ', cursor: { start: 4, end: 4 } });
   test.is(        'VVVV', statuses);
   test.is(Status.ERROR, status);
   test.is('tsr', requ.commandAssignment.value.name);
-  test.ok(assign1.arg.isBlank());
+  test.ok(assign1.arg.type === 'BlankArgument');
   test.is(undefined, assign1.value);
   test.is(undefined, assign2);
 
   update({ typed: 'tsr h', cursor: { start: 5, end: 5 } });
   test.is(        'VVVVV', statuses);
   test.is(Status.VALID, status);
   test.is('tsr', requ.commandAssignment.value.name);
   test.is('h', assign1.arg.text);
@@ -1252,17 +1512,17 @@ exports.testSingleNumber = function() {
   test.is(undefined, assign1.value);
 };
 
 exports.testElement = function(options) {
   update({ typed: 'tse', cursor: { start: 3, end: 3 } });
   test.is(        'VVV', statuses);
   test.is(Status.ERROR, status);
   test.is('tse', requ.commandAssignment.value.name);
-  test.ok(assign1.arg.isBlank());
+  test.ok(assign1.arg.type === 'BlankArgument');
   test.is(undefined, assign1.value);
 
   if (!options.isNode) {
     update({ typed: 'tse :root', cursor: { start: 9, end: 9 } });
     test.is(        'VVVVVVVVV', statuses);
     test.is(Status.VALID, status);
     test.is('tse', requ.commandAssignment.value.name);
     test.is(':root', assign1.arg.text);
@@ -1598,17 +1858,18 @@ exports.tsu = {
 };
 
 exports.tsn = {
   name: 'tsn'
 };
 
 exports.tsnDif = {
   name: 'tsn dif',
-  params: [ { name: 'text', type: 'string' } ],
+  description: 'tsn dif',
+  params: [ { name: 'text', type: 'string', description: 'tsn dif text' } ],
   exec: createExec('tsnDif')
 };
 
 exports.tsnExt = {
   name: 'tsn ext',
   params: [ { name: 'text', type: 'string' } ],
   exec: createExec('tsnExt')
 };
@@ -1667,29 +1928,52 @@ exports.tsm = {
   ],
   exec: createExec('tsm')
 };
 
 exports.tsg = {
   name: 'tsg',
   description: 'a param group test',
   params: [
-    { name: 'solo', type: { name: 'selection', data: [ 'aaa', 'bbb', 'ccc' ] } },
+    {
+      name: 'solo',
+      type: { name: 'selection', data: [ 'aaa', 'bbb', 'ccc' ] },
+      description: 'solo param'
+    },
     {
       group: 'First',
       params: [
-        { name: 'txt1', type: 'string', defaultValue: null },
-        { name: 'bool', type: 'boolean' }
+        {
+          name: 'txt1',
+          type: 'string',
+          defaultValue: null,
+          description: 'txt1 param'
+        },
+        {
+          name: 'bool',
+          type: 'boolean',
+          description: 'bool param'
+        }
       ]
     },
     {
       group: 'Second',
       params: [
-        { name: 'txt2', type: 'string', defaultValue: 'd' },
-        { name: 'num', type: { name: 'number', min: 40 }, defaultValue: 42 }
+        {
+          name: 'txt2',
+          type: 'string',
+          defaultValue: 'd',
+          description: 'txt2 param'
+        },
+        {
+          name: 'num',
+          type: { name: 'number', min: 40 },
+          defaultValue: 42,
+          description: 'num param'
+        }
       ]
     }
   ],
   exec: createExec('tsg')
 };
 
 
 });
@@ -1704,211 +1988,312 @@ exports.tsg = {
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gclitest/testCompletion', ['require', 'exports', 'module' , 'test/assert', 'gclitest/mockCommands'], function(require, exports, module) {
+define('gclitest/testCompletion', ['require', 'exports', 'module' , 'test/assert', 'gclitest/helpers', 'gclitest/mockCommands'], function(require, exports, module) {
 
 
 var test = require('test/assert');
+var helpers = require('gclitest/helpers');
 var mockCommands = require('gclitest/mockCommands');
 
 
-exports.setup = function() {
+exports.setup = function(options) {
   mockCommands.setup();
-};
-
-exports.shutdown = function() {
-  mockCommands.shutdown();
+  helpers.setup(options);
 };
 
-
-function type(typed, tests, options) {
-  var inputter = options.display.inputter;
-  var completer = options.display.completer;
-
-  inputter.setInput(typed);
-
-  if (tests.cursor) {
-    inputter.setCursor({ start: tests.cursor, end: tests.cursor });
-  }
-
-  if (tests.emptyParameters == null) {
-    tests.emptyParameters = [];
-  }
-
-  var realParams = completer.emptyParameters;
-  test.is(tests.emptyParameters.length, realParams.length,
-          'emptyParameters.length for \'' + typed + '\'');
-
-  if (realParams.length === tests.emptyParameters.length) {
-    for (var i = 0; i < realParams.length; i++) {
-      test.is(tests.emptyParameters[i], realParams[i].replace(/\u00a0/g, ' '),
-              'emptyParameters[' + i + '] for \'' + typed + '\'');
-    }
-  }
-
-  if (tests.directTabText) {
-    test.is(tests.directTabText, completer.directTabText,
-            'directTabText for \'' + typed + '\'');
-  }
-  else {
-    test.is('', completer.directTabText,
-            'directTabText for \'' + typed + '\'');
-  }
-
-  if (tests.arrowTabText) {
-    test.is(' \u00a0\u21E5 ' + tests.arrowTabText,
-            completer.arrowTabText,
-            'arrowTabText for \'' + typed + '\'');
-  }
-  else {
-    test.is('', completer.arrowTabText,
-            'arrowTabText for \'' + typed + '\'');
-  }
-}
+exports.shutdown = function(options) {
+  mockCommands.shutdown();
+  helpers.shutdown(options);
+};
 
 exports.testActivate = function(options) {
   if (!options.display) {
     test.log('No display. Skipping activate tests');
     return;
   }
 
-  type('', { }, options);
-
-  type(' ', { }, options);
-
-  type('tsr', {
+  helpers.setInput('');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput(' ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsr');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ ' <text>' ]
-  }, options);
-
-  type('tsr ', {
+  });
+
+  helpers.setInput('tsr ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ '<text>' ]
-  }, options);
-
-  type('tsr b', { }, options);
-
-  type('tsb', {
+  });
+
+  helpers.setInput('tsr b');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsb');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ ' [toggle]' ]
-  }, options);
-
-  type('tsm', {
+  });
+
+  helpers.setInput('tsm');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ ' <abc>', ' <txt>', ' <num>' ]
-  }, options);
-
-  type('tsm ', {
+  });
+
+  helpers.setInput('tsm ');
+  helpers.check({
     emptyParameters: [ ' <txt>', ' <num>' ],
+    arrowTabText: '',
     directTabText: 'a'
-  }, options);
-
-  type('tsm a', {
+  });
+
+  helpers.setInput('tsm a');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ ' <txt>', ' <num>' ]
-  }, options);
-
-  type('tsm a ', {
+  });
+
+  helpers.setInput('tsm a ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ '<txt>', ' <num>' ]
-  }, options);
-
-  type('tsm a  ', {
+  });
+
+  helpers.setInput('tsm a  ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ '<txt>', ' <num>' ]
-  }, options);
-
-  type('tsm a  d', {
+  });
+
+  helpers.setInput('tsm a  d');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ ' <num>' ]
-  }, options);
-
-  type('tsm a "d d"', {
+  });
+
+  helpers.setInput('tsm a "d d"');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ ' <num>' ]
-  }, options);
-
-  type('tsm a "d ', {
+  });
+
+  helpers.setInput('tsm a "d ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ ' <num>' ]
-  }, options);
-
-  type('tsm a "d d" ', {
+  });
+
+  helpers.setInput('tsm a "d d" ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ '<num>' ]
-  }, options);
-
-  type('tsm a "d d ', {
+  });
+
+  helpers.setInput('tsm a "d d ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ ' <num>' ]
-  }, options);
-
-  type('tsm d r', {
+  });
+
+  helpers.setInput('tsm d r');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ ' <num>' ]
-  }, options);
-
-  type('tsm a d ', {
+  });
+
+  helpers.setInput('tsm a d ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ '<num>' ]
-  }, options);
-
-  type('tsm a d 4', { }, options);
-
-  type('tsg', {
+  });
+
+  helpers.setInput('tsm a d 4');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsg');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
     emptyParameters: [ ' <solo>' ]
-  }, options);
-
-  type('tsg ', {
+  });
+
+  helpers.setInput('tsg ');
+  helpers.check({
+    emptyParameters: [],
+    arrowTabText: '',
     directTabText: 'aaa'
-  }, options);
-
-  type('tsg a', {
+  });
+
+  helpers.setInput('tsg a');
+  helpers.check({
+    emptyParameters: [],
+    arrowTabText: '',
     directTabText: 'aa'
-  }, options);
-
-  type('tsg b', {
+  });
+
+  helpers.setInput('tsg b');
+  helpers.check({
+    emptyParameters: [],
+    arrowTabText: '',
     directTabText: 'bb'
-  }, options);
-
-  type('tsg d', { }, options);
-
-  type('tsg aa', {
+  });
+
+  helpers.setInput('tsg d');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsg aa');
+  helpers.check({
+    emptyParameters: [],
+    arrowTabText: '',
     directTabText: 'a'
-  }, options);
-
-  type('tsg aaa', { }, options);
-
-  type('tsg aaa ', { }, options);
-
-  type('tsg aaa d', { }, options);
-
-  type('tsg aaa dddddd', { }, options);
-
-  type('tsg aaa dddddd ', { }, options);
-
-  type('tsg aaa "d', { }, options);
-
-  type('tsg aaa "d d', { }, options);
-
-  type('tsg aaa "d d"', { }, options);
-
-  type('tsn ex ', { }, options);
-
-  type('selarr', {
+  });
+
+  helpers.setInput('tsg aaa');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsg aaa ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsg aaa d');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsg aaa dddddd');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsg aaa dddddd ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsg aaa "d');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsg aaa "d d');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsg aaa "d d"');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tsn ex ');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('selarr');
+  helpers.check({
+    directTabText: '',
+    emptyParameters: [],
     arrowTabText: 'tselarr'
-  }, options);
-
-  type('tselar 1', { }, options);
-
-  type('tselar 1', {
-    cursor: 7
-  }, options);
-
-  type('tselar 1', {
-    cursor: 6,
+  });
+
+  helpers.setInput('tselar 1');
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tselar 1', 7);
+  helpers.check({
+    directTabText: '',
+    arrowTabText: '',
+    emptyParameters: []
+  });
+
+  helpers.setInput('tselar 1', 6);
+  helpers.check({
+    directTabText: '',
+    emptyParameters: [],
     arrowTabText: 'tselarr'
-  }, options);
-
-  type('tselar 1', {
-    cursor: 5,
+  });
+
+  helpers.setInput('tselar 1', 5);
+  helpers.check({
+    directTabText: '',
+    emptyParameters: [],
     arrowTabText: 'tselarr'
-  }, options);
+  });
 };
 
 
 });
 /*
  * Copyright 2012, Mozilla Foundation and contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -2342,16 +2727,204 @@ exports.testOutput = function(options) {
   test.ok(!focusManager._helpRequested, 'ESCAPE = anti help');
 
   latestOutput.onClose();
 };
 
 
 });
 /*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+define('gclitest/testIncomplete', ['require', 'exports', 'module' , 'test/assert', 'gclitest/helpers', 'gclitest/mockCommands'], function(require, exports, module) {
+
+
+var test = require('test/assert');
+var helpers = require('gclitest/helpers');
+var mockCommands = require('gclitest/mockCommands');
+
+
+exports.setup = function(options) {
+  mockCommands.setup();
+  helpers.setup(options);
+};
+
+exports.shutdown = function(options) {
+  mockCommands.shutdown();
+  helpers.shutdown(options);
+};
+
+exports.testBasic = function(options) {
+  var requisition = options.display.requisition;
+
+  helpers.setInput('tsu 2 extra');
+  helpers.check({
+    args: {
+      num: { value: 2, type: 'Argument' }
+    }
+  });
+  test.is(requisition._unassigned.length, 1, 'single unassigned: tsu 2 extra');
+  test.is(requisition._unassigned[0].param.type.isIncompleteName, false,
+          'unassigned.isIncompleteName: tsu 2 extra');
+
+  helpers.setInput('tsu');
+  helpers.check({
+    args: {
+      num: { value: undefined, type: 'BlankArgument' }
+    }
+  });
+
+  helpers.setInput('tsg');
+  helpers.check({
+    args: {
+      solo: { type: 'BlankArgument' },
+      txt1: { type: 'BlankArgument' },
+      bool: { type: 'BlankArgument' },
+      txt2: { type: 'BlankArgument' },
+      num: { type: 'BlankArgument' }
+    }
+  });
+};
+
+exports.testCompleted = function(options) {
+  helpers.setInput('tsela');
+  helpers.pressTab();
+  helpers.check({
+    args: {
+      command: { name: 'tselarr', type: 'Argument' },
+      num: { type: 'Argument' },
+      arr: { type: 'ArrayArgument' },
+    }
+  });
+
+  helpers.setInput('tsn dif ');
+  helpers.check({
+    input:  'tsn dif ',
+    markup: 'VVVVVVVV',
+    cursor: 8,
+    directTabText: '',
+    arrowTabText: '',
+    status: 'ERROR',
+    emptyParameters: [ '<text>' ],
+    args: {
+      command: { name: 'tsn dif', type: 'MergedArgument' },
+      text: { type: 'BlankArgument', status: 'INCOMPLETE' }
+    }
+  });
+
+  helpers.setInput('tsn di');
+  helpers.pressTab();
+  helpers.check({
+    input:  'tsn dif ',
+    markup: 'VVVVVVVV',
+    cursor: 8,
+    directTabText: '',
+    arrowTabText: '',
+    status: 'ERROR',
+    emptyParameters: [ '<text>' ],
+    args: {
+      command: { name: 'tsn dif', type: 'Argument' },
+      text: { type: 'Argument', status: 'INCOMPLETE' }
+    }
+  });
+
+  // The above 2 tests take different routes to 'tsn dif '. The results should
+  // be similar. The difference is in args.command.type.
+
+  helpers.setInput('tsg -');
+  helpers.check({
+    input:  'tsg -',
+    markup: 'VVVVI',
+    cursor: 5,
+    directTabText: '-txt1',
+    arrowTabText: '',
+    status: 'ERROR',
+    emptyParameters: [ ],
+    args: {
+      solo: { value: undefined, status: 'INCOMPLETE' },
+      txt1: { value: undefined, status: 'VALID' },
+      bool: { value: undefined, status: 'VALID' },
+      txt2: { value: undefined, status: 'VALID' },
+      num: { value: undefined, status: 'VALID' }
+    }
+  });
+
+  helpers.pressTab();
+  helpers.check({
+    input:  'tsg --txt1 ',
+    markup: 'VVVVIIIIIIV',
+    cursor: 11,
+    directTabText: '',
+    arrowTabText: '',
+    status: 'ERROR',
+    emptyParameters: [ ], // Bug 770830: '<txt1>', ' <solo>'
+    args: {
+      solo: { value: undefined, status: 'INCOMPLETE' },
+      txt1: { value: undefined, status: 'INCOMPLETE' },
+      bool: { value: undefined, status: 'VALID' },
+      txt2: { value: undefined, status: 'VALID' },
+      num: { value: undefined, status: 'VALID' }
+    }
+  });
+
+  helpers.setInput('tsg --txt1 fred');
+  helpers.check({
+    input:  'tsg --txt1 fred',
+    markup: 'VVVVVVVVVVVVVVV',
+    directTabText: '',
+    arrowTabText: '',
+    status: 'ERROR',
+    emptyParameters: [ ], // Bug 770830: ' <solo>'
+    args: {
+      solo: { value: undefined, status: 'INCOMPLETE' },
+      txt1: { value: 'fred', status: 'VALID' },
+      bool: { value: undefined, status: 'VALID' },
+      txt2: { value: undefined, status: 'VALID' },
+      num: { value: undefined, status: 'VALID' }
+    }
+  });
+
+  // Expand out to christmas tree command line
+};
+
+exports.testIncomplete = function(options) {
+  var requisition = options.display.requisition;
+
+  helpers.setInput('tsm a a -');
+  helpers.check({
+    args: {
+      abc: { value: 'a', type: 'Argument' },
+      txt: { value: 'a', type: 'Argument' },
+      num: { value: undefined, arg: ' -', type: 'Argument', status: 'INCOMPLETE' }
+    }
+  });
+
+  helpers.setInput('tsg -');
+  helpers.check({
+    args: {
+      solo: { type: 'BlankArgument' },
+      txt1: { type: 'BlankArgument' },
+      bool: { type: 'BlankArgument' },
+      txt2: { type: 'BlankArgument' },
+      num: { type: 'BlankArgument' }
+    }
+  });
+  test.is(requisition._unassigned[0], requisition.getAssignmentAt(5),
+          'unassigned -');
+  test.is(requisition._unassigned.length, 1, 'single unassigned - tsg -');
+  test.is(requisition._unassigned[0].param.type.isIncompleteName, true,
+          'unassigned.isIncompleteName: tsg -');
+};
+
+
+});
+/*
  * Copyright 2012, Mozilla Foundation and contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  * http://www.apache.org/licenses/LICENSE-2.0
  *
@@ -3686,23 +4259,21 @@ exports.testJavascript = function() {
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gclitest/testTokenize', ['require', 'exports', 'module' , 'test/assert', 'gcli/cli', 'gcli/argument'], function(require, exports, module) {
+define('gclitest/testTokenize', ['require', 'exports', 'module' , 'test/assert', 'gcli/cli'], function(require, exports, module) {
 
 
 var test = require('test/assert');
 var Requisition = require('gcli/cli').Requisition;
-var Argument = require('gcli/argument').Argument;
-var ScriptArgument = require('gcli/argument').ScriptArgument;
 
 exports.testBlanks = function() {
   var args;
   var requ = new Requisition();
 
   args = requ._tokenize('');
   test.is(1, args.length);
   test.is('', args[0].text);
@@ -3720,257 +4291,257 @@ exports.testTokSimple = function() {
   var args;
   var requ = new Requisition();
 
   args = requ._tokenize('s');
   test.is(1, args.length);
   test.is('s', args[0].text);
   test.is('', args[0].prefix);
   test.is('', args[0].suffix);
-  test.ok(args[0] instanceof Argument);
+  test.is('Argument', args[0].type);
 
   args = requ._tokenize('s s');
   test.is(2, args.length);
   test.is('s', args[0].text);
   test.is('', args[0].prefix);
   test.is('', args[0].suffix);
-  test.ok(args[0] instanceof Argument);
+  test.is('Argument', args[0].type);
   test.is('s', args[1].text);
   test.is(' ', args[1].prefix);
   test.is('', args[1].suffix);
-  test.ok(args[1] instanceof Argument);
+  test.is('Argument', args[1].type);
 };
 
 exports.testJavascript = function() {
   var args;
   var requ = new Requisition();
 
   args = requ._tokenize('{x}');
   test.is(1, args.length);
   test.is('x', args[0].text);
   test.is('{', args[0].prefix);
   test.is('}', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 
   args = requ._tokenize('{ x }');
   test.is(1, args.length);
   test.is('x', args[0].text);
   test.is('{ ', args[0].prefix);
   test.is(' }', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 
   args = requ._tokenize('{x} {y}');
   test.is(2, args.length);
   test.is('x', args[0].text);
   test.is('{', args[0].prefix);
   test.is('}', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
   test.is('y', args[1].text);
   test.is(' {', args[1].prefix);
   test.is('}', args[1].suffix);
-  test.ok(args[1] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[1].type);
 
   args = requ._tokenize('{x}{y}');
   test.is(2, args.length);
   test.is('x', args[0].text);
   test.is('{', args[0].prefix);
   test.is('}', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
   test.is('y', args[1].text);
   test.is('{', args[1].prefix);
   test.is('}', args[1].suffix);
-  test.ok(args[1] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[1].type);
 
   args = requ._tokenize('{');
   test.is(1, args.length);
   test.is('', args[0].text);
   test.is('{', args[0].prefix);
   test.is('', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 
   args = requ._tokenize('{ ');
   test.is(1, args.length);
   test.is('', args[0].text);
   test.is('{ ', args[0].prefix);
   test.is('', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 
   args = requ._tokenize('{x');
   test.is(1, args.length);
   test.is('x', args[0].text);
   test.is('{', args[0].prefix);
   test.is('', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 };
 
 exports.testRegularNesting = function() {
   var args;
   var requ = new Requisition();
 
   args = requ._tokenize('{"x"}');
   test.is(1, args.length);
   test.is('"x"', args[0].text);
   test.is('{', args[0].prefix);
   test.is('}', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 
   args = requ._tokenize('{\'x\'}');
   test.is(1, args.length);
   test.is('\'x\'', args[0].text);
   test.is('{', args[0].prefix);
   test.is('}', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 
   args = requ._tokenize('"{x}"');
   test.is(1, args.length);
   test.is('{x}', args[0].text);
   test.is('"', args[0].prefix);
   test.is('"', args[0].suffix);
-  test.ok(args[0] instanceof Argument);
+  test.is('Argument', args[0].type);
 
   args = requ._tokenize('\'{x}\'');
   test.is(1, args.length);
   test.is('{x}', args[0].text);
   test.is('\'', args[0].prefix);
   test.is('\'', args[0].suffix);
-  test.ok(args[0] instanceof Argument);
+  test.is('Argument', args[0].type);
 };
 
 exports.testDeepNesting = function() {
   var args;
   var requ = new Requisition();
 
   args = requ._tokenize('{{}}');
   test.is(1, args.length);
   test.is('{}', args[0].text);
   test.is('{', args[0].prefix);
   test.is('}', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 
   args = requ._tokenize('{{x} {y}}');
   test.is(1, args.length);
   test.is('{x} {y}', args[0].text);
   test.is('{', args[0].prefix);
   test.is('}', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 
   args = requ._tokenize('{{w} {{{x}}}} {y} {{{z}}}');
 
   test.is(3, args.length);
 
   test.is('{w} {{{x}}}', args[0].text);
   test.is('{', args[0].prefix);
   test.is('}', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 
   test.is('y', args[1].text);
   test.is(' {', args[1].prefix);
   test.is('}', args[1].suffix);
-  test.ok(args[1] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[1].type);
 
   test.is('{{z}}', args[2].text);
   test.is(' {', args[2].prefix);
   test.is('}', args[2].suffix);
-  test.ok(args[2] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[2].type);
 
   args = requ._tokenize('{{w} {{{x}}} {y} {{{z}}}');
 
   test.is(1, args.length);
 
   test.is('{w} {{{x}}} {y} {{{z}}}', args[0].text);
   test.is('{', args[0].prefix);
   test.is('', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 };
 
 exports.testStrangeNesting = function() {
   var args;
   var requ = new Requisition();
 
   // Note: When we get real JS parsing this should break
   args = requ._tokenize('{"x}"}');
 
   test.is(2, args.length);
 
   test.is('"x', args[0].text);
   test.is('{', args[0].prefix);
   test.is('}', args[0].suffix);
-  test.ok(args[0] instanceof ScriptArgument);
+  test.is('ScriptArgument', args[0].type);
 
   test.is('}', args[1].text);
   test.is('"', args[1].prefix);
   test.is('', args[1].suffix);
-  test.ok(args[1] instanceof Argument);
+  test.is('Argument', args[1].type);
 };
 
 exports.testComplex = function() {
   var args;
   var requ = new Requisition();
 
   args = requ._tokenize(' 1234  \'12 34\'');
 
   test.is(2, args.length);
 
   test.is('1234', args[0].text);
   test.is(' ', args[0].prefix);
   test.is('', args[0].suffix);
-  test.ok(args[0] instanceof Argument);
+  test.is('Argument', args[0].type);
 
   test.is('12 34', args[1].text);
   test.is('  \'', args[1].prefix);
   test.is('\'', args[1].suffix);
-  test.ok(args[1] instanceof Argument);
+  test.is('Argument', args[1].type);
 
   args = requ._tokenize('12\'34 "12 34" \\'); // 12'34 "12 34" \
 
   test.is(3, args.length);
 
   test.is('12\'34', args[0].text);
   test.is('', args[0].prefix);
   test.is('', args[0].suffix);
-  test.ok(args[0] instanceof Argument);
+  test.is('Argument', args[0].type);
 
   test.is('12 34', args[1].text);
   test.is(' "', args[1].prefix);
   test.is('"', args[1].suffix);
-  test.ok(args[1] instanceof Argument);
+  test.is('Argument', args[1].type);
 
   test.is('\\', args[2].text);
   test.is(' ', args[2].prefix);
   test.is('', args[2].suffix);
-  test.ok(args[2] instanceof Argument);
+  test.is('Argument', args[2].type);
 };
 
 exports.testPathological = function() {
   var args;
   var requ = new Requisition();
 
   args = requ._tokenize('a\\ b \\t\\n\\r \\\'x\\\" \'d'); // a_b \t\n\r \'x\" 'd
 
   test.is(4, args.length);
 
   test.is('a b', args[0].text);
   test.is('', args[0].prefix);
   test.is('', args[0].suffix);
-  test.ok(args[0] instanceof Argument);
+  test.is('Argument', args[0].type);
 
   test.is('\t\n\r', args[1].text);
   test.is(' ', args[1].prefix);
   test.is('', args[1].suffix);
-  test.ok(args[1] instanceof Argument);
+  test.is('Argument', args[1].type);
 
   test.is('\'x"', args[2].text);
   test.is(' ', args[2].prefix);
   test.is('', args[2].suffix);
-  test.ok(args[2] instanceof Argument);
+  test.is('Argument', args[2].type);
 
   test.is('d', args[3].text);
   test.is(' \'', args[3].prefix);
   test.is('', args[3].suffix);
-  test.ok(args[3] instanceof Argument);
+  test.is('Argument', args[3].type);
 };
 
 
 });
 /*
  * Copyright 2012, Mozilla Foundation and contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -4189,66 +4760,761 @@ exports.testFindCssSelector = function(o
 
     test.is(matches.length, 1, 'multiple matches for ' + selector);
     test.is(matches[0], nodes[i], 'non-matching selector: ' + selector);
   }
 };
 
 
 });
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+define('gcli/ui/display', ['require', 'exports', 'module' , 'gcli/util', 'gcli/settings', 'gcli/ui/intro', 'gcli/ui/domtemplate', 'gcli/ui/tooltip', 'gcli/ui/output_terminal', 'gcli/ui/inputter', 'gcli/ui/completer', 'gcli/ui/focus', 'gcli/ui/prompt', 'gcli/cli', 'text!gcli/ui/display.css', 'text!gcli/ui/display.html'], function(require, exports, module) {
+
+
+var util = require('gcli/util');
+var settings = require('gcli/settings');
+var intro = require('gcli/ui/intro');
+var domtemplate = require('gcli/ui/domtemplate');
+
+var Tooltip = require('gcli/ui/tooltip').Tooltip;
+var OutputTerminal = require('gcli/ui/output_terminal').OutputTerminal;
+var Inputter = require('gcli/ui/inputter').Inputter;
+var Completer = require('gcli/ui/completer').Completer;
+var FocusManager = require('gcli/ui/focus').FocusManager;
+var Prompt = require('gcli/ui/prompt').Prompt;
+
+var Requisition = require('gcli/cli').Requisition;
+
+var displayCss = require('text!gcli/ui/display.css');
+var displayHtml = require('text!gcli/ui/display.html');
+
+
+/**
+ * createDisplay() calls 'new Display()' but returns an object which exposes a
+ * much restricted set of functions rather than all those exposed by Display.
+ * This allows for robust testing without exposing too many internals.
+ * @param options See Display() for a description of the available options.
+ */
+exports.createDisplay = function(options) {
+  if (options.settings != null) {
+    settings.setDefaults(options.settings);
+  }
+  var display = new Display(options);
+  var requisition = display.requisition;
+  return {
+    /**
+     * The exact shape of the object returned by exec is likely to change in
+     * the near future. If you do use it, please expect your code to break.
+     */
+    exec: requisition.exec.bind(requisition),
+    update: requisition.update.bind(requisition),
+    destroy: display.destroy.bind(display)
+  };
+};
+
+/**
+ * View is responsible for generating the web UI for GCLI.
+ * @param options Object containing user customization properties.
+ * See the documentation for the other components for more details.
+ * Options supported directly include:
+ * - document (default=document):
+ * - environment (default={}):
+ * - dontDecorate (default=false):
+ * - inputElement (default=#gcli-input):
+ * - completeElement (default=#gcli-row-complete):
+ * - displayElement (default=#gcli-display):
+ * - promptElement (default=#gcli-prompt):
+ */
+function Display(options) {
+  var doc = options.document || document;
+
+  this.displayStyle = undefined;
+  if (displayCss != null) {
+    this.displayStyle = util.importCss(displayCss, doc, 'gcli-css-display');
+  }
+
+  // Configuring the document is complex because on the web side, there is an
+  // active desire to have nothing to configure, where as when embedded in
+  // Firefox there could be up to 4 documents, some of which can/should be
+  // derived from some root element.
+  // When a component uses a document to create elements for use under a known
+  // root element, then we pass in the element (if we have looked it up
+  // already) or an id/document
+  this.requisition = new Requisition(options.enviroment || {}, doc);
+
+  this.focusManager = new FocusManager(options, {
+    document: doc
+  });
+
+  this.inputElement = find(doc, options.inputElement || null, 'gcli-input');
+  this.inputter = new Inputter(options, {
+    requisition: this.requisition,
+    focusManager: this.focusManager,
+    element: this.inputElement
+  });
+
+  // autoResize logic: we want Completer to keep the elements at the same
+  // position if we created the completion element, but if someone else created
+  // it, then it's their job.
+  this.completeElement = insert(this.inputElement,
+                         options.completeElement || null, 'gcli-row-complete');
+  this.completer = new Completer(options, {
+    requisition: this.requisition,
+    inputter: this.inputter,
+    autoResize: this.completeElement.gcliCreated,
+    element: this.completeElement
+  });
+
+  this.prompt = new Prompt(options, {
+    inputter: this.inputter,
+    element: insert(this.inputElement,
+                    options.promptElement || null, 'gcli-prompt')
+  });
+
+  this.element = find(doc, options.displayElement || null, 'gcli-display');
+  this.element.classList.add('gcli-display');
+
+  this.template = util.toDom(doc, displayHtml);
+  this.elements = {};
+  domtemplate.template(this.template, this.elements, { stack: 'display.html' });
+  this.element.appendChild(this.template);
+
+  this.tooltip = new Tooltip(options, {
+    requisition: this.requisition,
+    inputter: this.inputter,
+    focusManager: this.focusManager,
+    element: this.elements.tooltip,
+    panelElement: this.elements.panel
+  });
+
+  this.inputter.tooltip = this.tooltip;
+
+  this.outputElement = util.createElement(doc, 'div');
+  this.outputElement.classList.add('gcli-output');
+  this.outputList = new OutputTerminal(options, {
+    requisition: this.requisition,
+    element: this.outputElement
+  });
+
+  this.element.appendChild(this.outputElement);
+
+  intro.maybeShowIntro(this.outputList.commandOutputManager, this.requisition);
+}
+
+/**
+ * Call the destroy functions of the components that we created
+ */
+Display.prototype.destroy = function() {
+  delete this.element;
+  delete this.template;
+
+  this.outputList.destroy();
+  delete this.outputList;
+  delete this.outputElement;
+
+  this.tooltip.destroy();
+  delete this.tooltip;
+
+  this.prompt.destroy();
+  delete this.prompt;
+
+  this.completer.destroy();
+  delete this.completer;
+  delete this.completeElement;
+
+  this.inputter.destroy();
+  delete this.inputter;
+  delete this.inputElement;
+
+  this.focusManager.destroy();
+  delete this.focusManager;
+
+  this.requisition.destroy();
+  delete this.requisition;
+
+  if (this.displayStyle) {
+    this.displayStyle.parentNode.removeChild(this.displayStyle);
+  }
+  delete this.displayStyle;
+};
+
+exports.Display = Display;
+
+/**
+ * Utility to help find an element by id, throwing if it wasn't found
+ */
+function find(doc, element, id) {
+  if (!element) {
+    element = doc.getElementById(id);
+    if (!element) {
+      throw new Error('Missing element, id=' + id);
+    }
+  }
+  return element;
+}
+
+/**
+ * Utility to help find an element by id, creating it if it wasn't found
+ */
+function insert(sibling, element, id) {
+  var doc = sibling.ownerDocument;
+  if (!element) {
+    element = doc.getElementById('gcli-row-complete');
+    if (!element) {
+      element = util.createElement(doc, 'div');
+      sibling.parentNode.insertBefore(element, sibling.nextSibling);
+      element.gcliCreated = true;
+    }
+  }
+  return element;
+}
+
+
+});
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+define('gcli/ui/output_terminal', ['require', 'exports', 'module' , 'gcli/util', 'gcli/canon', 'gcli/ui/domtemplate', 'text!gcli/ui/output_view.css', 'text!gcli/ui/output_terminal.html'], function(require, exports, module) {
+
+var util = require('gcli/util');
+
+var canon = require('gcli/canon');
+var domtemplate = require('gcli/ui/domtemplate');
+
+var outputViewCss = require('text!gcli/ui/output_view.css');
+var outputViewHtml = require('text!gcli/ui/output_terminal.html');
+
+
+/**
+ * A wrapper for a set of rows|command outputs.
+ * Register with the canon to be notified when commands have output to be
+ * displayed.
+ * @param options Object containing user customization properties, including:
+ * - commandOutputManager
+ * @param components Object that links to other UI components. GCLI provided:
+ * - element: Root element to populate
+ * - requisition (optional): A click/double-click to an input row causes the
+ *   command to be sent to the input/executed if we know the requisition use
+ */
+function OutputTerminal(options, components) {
+  this.element = components.element;
+  this.requisition = components.requisition;
+
+  this.commandOutputManager = options.commandOutputManager ||
+          canon.commandOutputManager;
+  this.commandOutputManager.onOutput.add(this.outputted, this);
+
+  var document = components.element.ownerDocument;
+  if (outputViewCss != null) {
+    this.style = util.importCss(outputViewCss, document, 'gcli-output-view');
+  }
+
+  this.template = util.toDom(document, outputViewHtml);
+  this.templateOptions = { allowEval: true, stack: 'output_terminal.html' };
+}
+
+/**
+ * Avoid memory leaks
+ */
+OutputTerminal.prototype.destroy = function() {
+  if (this.style) {
+    this.style.parentNode.removeChild(this.style);
+    delete this.style;
+  }
+
+  this.commandOutputManager.onOutput.remove(this.outputted, this);
+
+  delete this.commandOutputManager;
+  delete this.requisition;
+  delete this.element;
+  delete this.template;
+};
+
+/**
+ * Monitor for new command executions
+ */
+OutputTerminal.prototype.outputted = function(ev) {
+  if (ev.output.hidden) {
+    return;
+  }
+
+  ev.output.view = new OutputView(ev.output, this);
+};
+
+/**
+ * Display likes to be able to control the height of its children
+ */
+OutputTerminal.prototype.setHeight = function(height) {
+  this.element.style.height = height + 'px';
+};
+
+exports.OutputTerminal = OutputTerminal;
+
+
+/**
+ * Adds a row to the CLI output display
+ */
+function OutputView(outputData, outputTerminal) {
+  this.outputData = outputData;
+  this.outputTerminal = outputTerminal;
+
+  this.url = util.createUrlLookup(module);
+
+  // Elements attached to this by template().
+  this.elems = {
+    rowin: null,
+    rowout: null,
+    hide: null,
+    show: null,
+    duration: null,
+    throb: null,
+    prompt: null
+  };
+
+  var template = this.outputTerminal.template.cloneNode(true);
+  domtemplate.template(template, this, this.outputTerminal.templateOptions);
+
+  this.outputTerminal.element.appendChild(this.elems.rowin);
+  this.outputTerminal.element.appendChild(this.elems.rowout);
+
+  this.outputData.onClose.add(this.closed, this);
+  this.outputData.onChange.add(this.changed, this);
+}
+
+OutputView.prototype.destroy = function() {
+  this.outputData.onChange.remove(this.changed, this);
+  this.outputData.onClose.remove(this.closed, this);
+
+  this.outputTerminal.element.removeChild(this.elems.rowin);
+  this.outputTerminal.element.removeChild(this.elems.rowout);
+
+  delete this.outputData;
+  delete this.outputTerminal;
+  delete this.url;
+  delete this.elems;
+};
+
+/**
+ * Only display a prompt if there is a command, otherwise, leave blank
+ */
+Object.defineProperty(OutputView.prototype, 'prompt', {
+  get: function() {
+    return this.outputData.canonical ? '\u00bb' : '';
+  },
+  enumerable: true
+});
+
+/**
+ * A single click on an invocation line in the console copies the command
+ * to the command line
+ */
+OutputView.prototype.copyToInput = function() {
+  if (this.outputTerminal.requisition) {
+    this.outputTerminal.requisition.update(this.outputData.typed);
+  }
+};
+
+/**
+ * A double click on an invocation line in the console executes the command
+ */
+OutputView.prototype.execute = function(ev) {
+  if (this.outputTerminal.requisition) {
+    this.outputTerminal.requisition.exec({ typed: this.outputData.typed });
+  }
+};
+
+OutputView.prototype.hideOutput = function(ev) {
+  this.elems.rowout.style.display = 'none';
+  this.elems.hide.classList.add('cmd_hidden');
+  this.elems.show.classList.remove('cmd_hidden');
+
+  ev.stopPropagation();
+};
+
+OutputView.prototype.showOutput = function(ev) {
+  this.elems.rowout.style.display = 'block';
+  this.elems.hide.classList.remove('cmd_hidden');
+  this.elems.show.classList.add('cmd_hidden');
+
+  ev.stopPropagation();
+};
+
+OutputView.prototype.closed = function(ev) {
+  this.destroy();
+};
+
+OutputView.prototype.changed = function(ev) {
+  var document = this.elems.rowout.ownerDocument;
+  var duration = this.outputData.duration != null ?
+          'completed in ' + (this.outputData.duration / 1000) + ' sec ' :
+          '';
+  duration = document.createTextNode(duration);
+  this.elems.duration.appendChild(duration);
+
+  if (this.outputData.completed) {
+    this.elems.prompt.classList.add('gcli-row-complete');
+  }
+  if (this.outputData.error) {
+    this.elems.prompt.classList.add('gcli-row-error');
+  }
+
+  this.outputData.toDom(this.elems.rowout);
+
+  // We need to see the output of the latest command entered
+  // Certain browsers have a bug such that scrollHeight is too small
+  // when content does not fill the client area of the element
+  var scrollHeight = Math.max(this.outputTerminal.element.scrollHeight,
+      this.outputTerminal.element.clientHeight);
+  this.outputTerminal.element.scrollTop =
+      scrollHeight - this.outputTerminal.element.clientHeight;
+
+  this.elems.throb.style.display = this.outputData.completed ? 'none' : 'block';
+};
+
+exports.OutputView = OutputView;
+
+
+});
+define("text!gcli/ui/output_view.css", [], "\n" +
+  ".gcli-row-in {\n" +
+  "  padding: 0 4px;\n" +
+  "  box-shadow: 0 -6px 10px -6px #ddd;\n" +
+  "  border-top: 1px solid #bbb;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-in > img {\n" +
+  "  cursor: pointer;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-hover {\n" +
+  "  display: none;\n" +
+  "  float: right;\n" +
+  "  padding: 2px 2px 0;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-in:hover > .gcli-row-hover {\n" +
+  "  display: inline;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-in:hover > .gcli-row-hover.gcli-row-hidden {\n" +
+  "  display: none;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-duration {\n" +
+  "  color: #666;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-prompt {\n" +
+  "  color: #00F;\n" +
+  "  font-weight: bold;\n" +
+  "  font-size: 120%;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-prompt.gcli-row-complete {\n" +
+  "  color: #060;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-prompt.gcli-row-error {\n" +
+  "  color: #F00;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-duration {\n" +
+  "  font-size: 80%;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-out {\n" +
+  "  margin: 0 10px 15px;\n" +
+  "  padding: 0 10px;\n" +
+  "  line-height: 1.2em;\n" +
+  "  font-size: 95%;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-out strong,\n" +
+  ".gcli-row-out b,\n" +
+  ".gcli-row-out th,\n" +
+  ".gcli-row-out h1,\n" +
+  ".gcli-row-out h2,\n" +
+  ".gcli-row-out h3 {\n" +
+  "  color: #000;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-out p {\n" +
+  "  margin: 5px 0;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-out a {\n" +
+  "  color: hsl(200,40%,40%);\n" +
+  "  text-decoration: none;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-out a:hover {\n" +
+  "  cursor: pointer;\n" +
+  "  border-bottom: 1px dotted hsl(200,40%,60%);\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-out input[type=password],\n" +
+  ".gcli-row-out input[type=text],\n" +
+  ".gcli-row-out textarea {\n" +
+  "  font-size: 120%;\n" +
+  "  background: transparent;\n" +
+  "  padding: 3px;\n" +
+  "  border-radius: 3px;\n" +
+  "  border: 1px solid #bbb;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-out table,\n" +
+  ".gcli-row-out td,\n" +
+  ".gcli-row-out th {\n" +
+  "  border: 0;\n" +
+  "  padding: 0 2px;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-terminal,\n" +
+  ".gcli-row-subterminal {\n" +
+  "  border-radius: 3px;\n" +
+  "  border: 1px solid #ddd;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-terminal {\n" +
+  "  height: 200px;\n" +
+  "  width: 620px;\n" +
+  "  font-size: 80%;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-row-subterminal {\n" +
+  "  height: 150px;\n" +
+  "  width: 300px;\n" +
+  "  font-size: 75%;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-out-shortcut {\n" +
+  "  font-weight: normal;\n" +
+  "  border: 1px solid #999;\n" +
+  "  border-radius: 3px;\n" +
+  "  color: #666;\n" +
+  "  cursor: pointer;\n" +
+  "  padding: 0 3px 1px;\n" +
+  "  margin: 1px 4px;\n" +
+  "  display: inline-block;\n" +
+  "}\n" +
+  "\n" +
+  ".gcli-out-shortcut:before {\n" +
+  "  content: '\\bb';\n" +
+  "  padding-right: 2px;\n" +
+  "  color: hsl(25,78%,50%);\n" +
+  "  font-weight: bold;\n" +
+  "  font-size: 110%;\n" +
+  "}\n" +
+  "");
+
+define("text!gcli/ui/output_terminal.html", [], "\n" +
+  "<div class=\"gcli-row\">\n" +
+  "  <!-- The div for the input (i.e. what was typed) -->\n" +
+  "  <div class=\"gcli-row-in\" save=\"${elems.rowin}\" aria-live=\"assertive\"\n" +
+  "      onclick=\"${copyToInput}\" ondblclick=\"${execute}\">\n" +
+  "\n" +
+  "    <!-- What the user actually typed -->\n" +
+  "    <span save=\"${elems.prompt}\" class=\"gcli-row-prompt ${elems.error ? 'gcli-row-error' : ''} ${elems.completed ? 'gcli-row-complete' : ''}\">${prompt}</span>\n" +
+  "    <span class=\"gcli-row-in-typed\">${outputData.canonical}</span>\n" +
+  "\n" +
+  "    <!-- The extra details that appear on hover -->\n" +
+  "    <span class=\"gcli-row-duration gcli-row-hover\" save=\"${elems.duration}\"></span>\n" +
+  "    <!--\n" +
+  "    <img class=\"gcli-row-hover\" onclick=\"${hideOutput}\" save=\"${elems.hide}\"\n" +
+  "        alt=\"Hide command output\" _src=\"${url('images/minus.png')}\"/>\n" +
+  "    <img class=\"gcli-row-hover gcli-row-hidden\" onclick=\"${showOutput}\" save=\"${elems.show}\"\n" +
+  "        alt=\"Show command output\" _src=\"${url('images/plus.png')}\"/>\n" +
+  "    <img class=\"gcli-row-hover\" onclick=\"${remove}\"\n" +
+  "        alt=\"Remove this command from the history\"\n" +
+  "        _src=\"${url('images/closer.png')}\"/>\n" +
+  "    -->\n" +
+  "    <img style=\"float:right;\" _src=\"${url('images/throbber.gif')}\" save=\"${elems.throb}\"/>\n" +
+  "  </div>\n" +
+  "\n" +
+  "  <!-- The div for the command output -->\n" +
+  "  <div class=\"gcli-row-out\" aria-live=\"assertive\" save=\"${elems.rowout}\">\n" +
+  "  </div>\n" +
+  "</div>\n" +
+  "");
+
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+define('gcli/ui/prompt', ['require', 'exports', 'module' ], function(require, exports, module) {
+
+
+/**
+ * Prompt is annoying because some systems provide a UI elements (i.e. firefox)
+ * while some expect you to overlay them on an input element (i.e. the web)
+ * Also we want to provide click -> show menu ability.
+ * @param options Object containing user customization properties, including:
+ * - promptChar (default='\u00bb') (double greater-than, a.k.a right guillemet)
+ *   The prompt is used directly in a TextNode, so no HTML entities.
+ * @param components Object that links to other UI components. GCLI provided:
+ * - element
+ * - inputter
+ */
+function Prompt(options, components) {
+  this.element = components.element;
+  this.element.classList.add('gcli-prompt');
+
+  var prompt = options.promptChar || '\u00bb';
+  var text = this.element.ownerDocument.createTextNode(prompt);
+  this.element.appendChild(text);
+
+  this.inputter = components.inputter;
+  if (this.inputter) {
+    this.inputter.onResize.add(this.resized, this);
+
+    var dimensions = this.inputter.getDimensions();
+    if (dimensions) {
+      this.resized(dimensions);
+    }
+  }
+}
+
+/**
+ * Avoid memory leaks
+ */
+Prompt.prototype.destroy = function() {
+  if (this.inputter) {
+    this.inputter.onResize.remove(this.resized, this);
+  }
+
+  delete this.element;
+};
+
+/**
+ * Ensure that the completion element is the same size and the inputter element
+ */
+Prompt.prototype.resized = function(ev) {
+  this.element.style.top = ev.top + 'px';
+  this.element.style.height = ev.height + 'px';
+  this.element.style.left = ev.left + 'px';
+  this.element.style.width = ev.width + 'px';
+};
+
+exports.Prompt = Prompt;
+
+
+});
+define("text!gcli/ui/display.css", [], "\n" +
+  ".gcli-output {\n" +
+  "  height: 100%;\n" +
+  "  overflow-x: hidden;\n" +
+  "  overflow-y: auto;\n" +
+  "  font-family: Segoe UI, Helvetica Neue, Verdana, Arial, sans-serif;\n" +
+  "}\n" +
+  "");
+
+define("text!gcli/ui/display.html", [], "\n" +
+  "<div class=\"gcli-panel\" save=\"${panel}\">\n" +
+  "  <div save=\"${tooltip}\"></div>\n" +
+  "  <div class=\"gcli-panel-connector\"></div>\n" +
+  "</div>\n" +
+  "");
+
 
 let testModuleNames = [
   'gclitest/index',
   'gclitest/suite',
   'test/examiner',
   'test/assert',
   'test/status',
   'gclitest/testCanon',
   'gclitest/helpers',
   'gclitest/testCli',
   'gclitest/mockCommands',
   'gclitest/testCompletion',
   'gclitest/testExec',
   'gclitest/testHelp',
   'gclitest/testHistory',
   'gclitest/testInputter',
+  'gclitest/testIncomplete',
   'gclitest/testIntro',
   'gclitest/testJs',
   'gclitest/testKeyboard',
   'gclitest/testPref',
   'gclitest/mockSettings',
   'gclitest/testRequire',
   'gclitest/requirable',
   'gclitest/testResource',
   'gclitest/testScratchpad',
   'gclitest/testSettings',
   'gclitest/testSpell',
   'gclitest/testSplit',
   'gclitest/testTokenize',
   'gclitest/testTooltip',
   'gclitest/testTypes',
   'gclitest/testUtil',
+  'gcli/ui/display',
+  'gcli/ui/output_terminal',
+  'text!gcli/ui/output_view.css',
+  'text!gcli/ui/output_terminal.html',
+  'gcli/ui/prompt',
+  'text!gcli/ui/display.css',
+  'text!gcli/ui/display.html',
 ];
 
 // Cached so it still exists during cleanup until we need it to
 let localDefine;
 
 const TEST_URI = "data:text/html;charset=utf-8,gcli-web";
 
 function test() {
   localDefine = define;
 
   DeveloperToolbarTest.test(TEST_URI, function(browser, tab) {
-    var gclitest = define.globalDomain.require("gclitest/index");
-    gclitest.run({
+    var examiner = define.globalDomain.require('gclitest/suite').examiner;
+    examiner.runAsync({
       display: DeveloperToolbar.display,
       isFirefox: true,
       window: browser.contentDocument.defaultView
-    });
-    finish();
+    }, finish);
   });
 }
 
 registerCleanupFunction(function() {
   testModuleNames.forEach(function(moduleName) {
     delete localDefine.modules[moduleName];
     delete localDefine.globalDomain.modules[moduleName];
   });
--- a/browser/devtools/commandline/test/head.js
+++ b/browser/devtools/commandline/test/head.js
@@ -41,280 +41,368 @@ registerCleanupFunction(function tearDow
 });
 
 /**
  * Various functions for testing DeveloperToolbar.
  * Parts of this code exist in:
  * - browser/devtools/commandline/test/head.js
  * - browser/devtools/shared/test/head.js
  */
-let DeveloperToolbarTest = {
-  /**
-   * Paranoid DeveloperToolbar.show();
-   */
-  show: function DTT_show(aCallback) {
-    if (DeveloperToolbar.visible) {
-      ok(false, "DeveloperToolbar.visible at start of openDeveloperToolbar");
-    }
-    else {
-      DeveloperToolbar.show(aCallback);
-    }
-  },
+let DeveloperToolbarTest = { };
+
+/**
+ * Paranoid DeveloperToolbar.show();
+ */
+DeveloperToolbarTest.show = function DTT_show(aCallback) {
+  if (DeveloperToolbar.visible) {
+    ok(false, "DeveloperToolbar.visible at start of openDeveloperToolbar");
+  }
+  else {
+    DeveloperToolbar.show(true, aCallback);
+  }
+};
+
+/**
+ * Paranoid DeveloperToolbar.hide();
+ */
+DeveloperToolbarTest.hide = function DTT_hide() {
+  if (!DeveloperToolbar.visible) {
+    ok(false, "!DeveloperToolbar.visible at start of closeDeveloperToolbar");
+  }
+  else {
+    DeveloperToolbar.display.inputter.setInput("");
+    DeveloperToolbar.hide();
+  }
+};
+
+/**
+ * check() is the new status. Similar API except that it doesn't attempt to
+ * alter the display/requisition at all, and it makes extra checks.
+ * Test inputs
+ *   typed: The text to type at the input
+ * Available checks:
+ *   input: The text displayed in the input field
+ *   cursor: The position of the start of the cursor
+ *   status: One of "VALID", "ERROR", "INCOMPLETE"
+ *   emptyParameters: Array of parameters still to type. e.g. [ "<message>" ]
+ *   directTabText: Simple completion text
+ *   arrowTabText: When the completion is not an extension (without arrow)
+ *   markup: What state should the error markup be in. e.g. "VVVIIIEEE"
+ *   args: Maps of checks to make against the arguments:
+ *     value: i.e. assignment.value (which ignores defaultValue)
+ *     type: Argument/BlankArgument/MergedArgument/etc i.e. what's assigned
+ *           Care should be taken with this since it's something of an
+ *           implementation detail
+ *     arg: The toString value of the argument
+ *     status: i.e. assignment.getStatus
+ *     message: i.e. assignment.getMessage
+ *     name: For commands - checks assignment.value.name
+ */
+DeveloperToolbarTest.checkInputStatus = function DTT_checkInputStatus(checks) {
+  if (!checks.emptyParameters) {
+    checks.emptyParameters = [];
+  }
+  if (!checks.directTabText) {
+    checks.directTabText = '';
+  }
+  if (!checks.arrowTabText) {
+    checks.arrowTabText = '';
+  }
 
-  /**
-   * Paranoid DeveloperToolbar.hide();
-   */
-  hide: function DTT_hide() {
-    if (!DeveloperToolbar.visible) {
-      ok(false, "!DeveloperToolbar.visible at start of closeDeveloperToolbar");
-    }
-    else {
-      DeveloperToolbar.display.inputter.setInput("");
-      DeveloperToolbar.hide();
+  var display = DeveloperToolbar.display;
+
+  if (checks.typed) {
+    display.inputter.setInput(checks.typed);
+  }
+  else {
+    ok(false, "Missing typed for " + JSON.stringify(checks));
+    return;
+  }
+
+  if (checks.cursor) {
+    display.inputter.setCursor(checks.cursor)
+  }
+
+  var cursor = checks.cursor ? checks.cursor.start : checks.typed.length;
+
+  var requisition = display.requisition;
+  var completer = display.completer;
+  var actual = completer._getCompleterTemplateData();
+
+  /*
+  if (checks.input) {
+    is(display.inputter.element.value,
+            checks.input,
+            'input');
+  }
+
+  if (checks.cursor) {
+    is(display.inputter.element.selectionStart,
+            checks.cursor,
+            'cursor');
+  }
+  */
+
+  if (checks.status) {
+    is(requisition.getStatus().toString(),
+            checks.status,
+            'status');
+  }
+
+  if (checks.markup) {
+    var statusMarkup = requisition.getInputStatusMarkup(cursor);
+    var actualMarkup = statusMarkup.map(function(s) {
+      return Array(s.string.length + 1).join(s.status.toString()[0]);
+    }).join('');
+
+    is(checks.markup,
+            actualMarkup,
+            'markup');
+  }
+
+  if (checks.emptyParameters) {
+    var actualParams = actual.emptyParameters;
+    is(actualParams.length,
+            checks.emptyParameters.length,
+            'emptyParameters.length');
+
+    if (actualParams.length === checks.emptyParameters.length) {
+      for (var i = 0; i < actualParams.length; i++) {
+        is(actualParams[i].replace(/\u00a0/g, ' '),
+                checks.emptyParameters[i],
+                'emptyParameters[' + i + ']');
+      }
     }
-  },
+  }
+
+  if (checks.directTabText) {
+    is(actual.directTabText,
+            checks.directTabText,
+            'directTabText');
+  }
+
+  if (checks.arrowTabText) {
+    is(actual.arrowTabText,
+            ' \u00a0\u21E5 ' + checks.arrowTabText,
+            'arrowTabText');
+  }
+
+  if (checks.args) {
+    Object.keys(checks.args).forEach(function(paramName) {
+      var check = checks.args[paramName];
+
+      var assignment;
+      if (paramName === 'command') {
+        assignment = requisition.commandAssignment;
+      }
+      else {
+        assignment = requisition.getAssignment(paramName);
+      }
+
+      if (assignment == null) {
+        ok(false, 'Unknown parameter: ' + paramName);
+        return;
+      }
+
+      if (check.value) {
+        is(assignment.value,
+                check.value,
+                'checkStatus value for ' + paramName);
+      }
+
+      if (check.name) {
+        is(assignment.value.name,
+                check.name,
+                'checkStatus name for ' + paramName);
+      }
+
+      if (check.type) {
+        is(assignment.arg.type,
+                check.type,
+                'checkStatus type for ' + paramName);
+      }
+
+      if (check.arg) {
+        is(assignment.arg.toString(),
+                check.arg,
+                'checkStatus arg for ' + paramName);
+      }
+
+      if (check.status) {
+        is(assignment.getStatus().toString(),
+                check.status,
+                'checkStatus status for ' + paramName);
+      }
+
+      if (check.message) {
+        is(assignment.getMessage(),
+                check.message,
+                'checkStatus message for ' + paramName);
+      }
+    });
+  }
+};
 
-  /**
-   * Check that we can parse command input.
-   * Doesn't execute the command, just checks that we grok the input properly:
-   *
-   * DeveloperToolbarTest.checkInputStatus({
-   *   // Test inputs
-   *   typed: "ech",           // Required
-   *   cursor: 3,              // Optional cursor position
-   *
-   *   // Thing to check
-   *   status: "INCOMPLETE",   // One of "VALID", "ERROR", "INCOMPLETE"
-   *   emptyParameters: [ "<message>" ], // Still to type
-   *   directTabText: "o",     // Simple completion text
-   *   arrowTabText: "",       // When the completion is not an extension
-   *   markup: "VVVIIIEEE",    // What state should the error markup be in
-   * });
-   */
-  checkInputStatus: function DTT_checkInputStatus(tests) {
-    let display = DeveloperToolbar.display;
+/**
+ * Execute a command:
+ *
+ * DeveloperToolbarTest.exec({
+ *   // Test inputs
+ *   typed: "echo hi",        // Optional, uses existing if undefined
+ *
+ *   // Thing to check
+ *   args: { message: "hi" }, // Check that the args were understood properly
+ *   outputMatch: /^hi$/,     // RegExp to test against textContent of output
+ *                            // (can also be array of RegExps)
+ *   blankOutput: true,       // Special checks when there is no output
+ * });
+ */
+DeveloperToolbarTest.exec = function DTT_exec(tests) {
+  tests = tests || {};
+
+  if (tests.typed) {
+    DeveloperToolbar.display.inputter.setInput(tests.typed);
+  }
+
+  let typed = DeveloperToolbar.display.inputter.getInputState().typed;
+  let output = DeveloperToolbar.display.requisition.exec();
+
+  is(typed, output.typed, 'output.command for: ' + typed);
+
+  if (tests.completed !== false) {
+    ok(output.completed, 'output.completed false for: ' + typed);
+  }
+  else {
+    // It is actually an error if we say something is async and it turns
+    // out not to be? For now we're saying 'no'
+    // ok(!output.completed, 'output.completed true for: ' + typed);
+  }
+
+  if (tests.args != null) {
+    is(Object.keys(tests.args).length, Object.keys(output.args).length,
+       'arg count for ' + typed);
 
-    if (tests.typed) {
-      display.inputter.setInput(tests.typed);
+    Object.keys(output.args).forEach(function(arg) {
+      let expectedArg = tests.args[arg];
+      let actualArg = output.args[arg];
+
+      if (typeof expectedArg === 'function') {
+        ok(expectedArg(actualArg), 'failed test func. ' + typed + '/' + arg);
+      }
+      else {
+        if (Array.isArray(expectedArg)) {
+          if (!Array.isArray(actualArg)) {
+            ok(false, 'actual is not an array. ' + typed + '/' + arg);
+            return;
+          }
+
+          is(expectedArg.length, actualArg.length,
+                  'array length: ' + typed + '/' + arg);
+          for (let i = 0; i < expectedArg.length; i++) {
+            is(expectedArg[i], actualArg[i],
+                    'member: "' + typed + '/' + arg + '/' + i);
+          }
+        }
+        else {
+          is(expectedArg, actualArg, 'typed: "' + typed + '" arg: ' + arg);
+        }
+      }
+    });
+  }
+
+  let displayed = DeveloperToolbar.outputPanel._div.textContent;
+
+  if (tests.outputMatch) {
+    function doTest(match, against) {
+      if (!match.test(against)) {
+        ok(false, "html output for " + typed + " against " + match.source +
+                " (textContent sent to info)");
+        info("Actual textContent");
+        info(against);
+      }
+    }
+    if (Array.isArray(tests.outputMatch)) {
+      tests.outputMatch.forEach(function(match) {
+        doTest(match, displayed);
+      });
     }
     else {
-      ok(false, "Missing typed for " + JSON.stringify(tests));
-      return;
-    }
-
-    if (tests.cursor) {
-      display.inputter.setCursor(tests.cursor)
+      doTest(tests.outputMatch, displayed);
     }
-
-    if (tests.status) {
-      is(display.requisition.getStatus().toString(),
-              tests.status, "status for " + tests.typed);
-    }
-
-    if (tests.emptyParameters == null) {
-      tests.emptyParameters = [];
-    }
+  }
 
-    let realParams = display.completer.emptyParameters;
-    is(realParams.length, tests.emptyParameters.length,
-            'emptyParameters.length for \'' + tests.typed + '\'');
-
-    if (realParams.length === tests.emptyParameters.length) {
-      for (let i = 0; i < realParams.length; i++) {
-        is(realParams[i].replace(/\u00a0/g, ' '), tests.emptyParameters[i],
-                'emptyParameters[' + i + '] for \'' + tests.typed + '\'');
-      }
+  if (tests.blankOutput != null) {
+    if (!/^$/.test(displayed)) {
+      ok(false, "html output for " + typed + " (textContent sent to info)");
+      info("Actual textContent");
+      info(displayed);
     }
-
-    if (tests.directTabText) {
-      is(display.completer.directTabText, tests.directTabText,
-              'directTabText for \'' + tests.typed + '\'');
-    }
-    else {
-      is(display.completer.directTabText, '',
-              'directTabText for \'' + tests.typed + '\'');
-    }
+  }
+};
 
-    if (tests.arrowTabText) {
-      is(display.completer.arrowTabText, ' \u00a0\u21E5 ' + tests.arrowTabText,
-              'arrowTabText for \'' + tests.typed + '\'');
-    }
-    else {
-      is(display.completer.arrowTabText, '',
-              'arrowTabText for \'' + tests.typed + '\'');
-    }
-
-    if (tests.markup) {
-      let cursor = tests.cursor ? tests.cursor.start : tests.typed.length;
-      let statusMarkup = display.requisition.getInputStatusMarkup(cursor);
-      let actualMarkup = statusMarkup.map(function(s) {
-        return Array(s.string.length + 1).join(s.status.toString()[0]);
-      }).join('');
-      is(tests.markup, actualMarkup, 'markup for ' + tests.typed);
-    }
-  },
+/**
+ * Quick wrapper around the things you need to do to run DeveloperToolbar
+ * command tests:
+ * - Set the pref 'devtools.toolbar.enabled' to true
+ * - Add a tab pointing at |uri|
+ * - Open the DeveloperToolbar
+ * - Register a cleanup function to undo the above
+ * - Run the tests
+ *
+ * @param uri The uri of a page to load. Can be 'about:blank' or 'data:...'
+ * @param testFunc A function containing the tests to run. This should
+ * arrange for 'finish()' to be called on completion.
+ */
+DeveloperToolbarTest.test = function DTT_test(uri, testFunc) {
+  let menuItem = document.getElementById("menu_devToolbar");
+  let command = document.getElementById("Tools:DevToolbar");
+  let appMenuItem = document.getElementById("appmenu_devToolbar");
 
-  /**
-   * Execute a command:
-   *
-   * DeveloperToolbarTest.exec({
-   *   // Test inputs
-   *   typed: "echo hi",        // Optional, uses existing if undefined
-   *
-   *   // Thing to check
-   *   args: { message: "hi" }, // Check that the args were understood properly
-   *   outputMatch: /^hi$/,     // RegExp to test against textContent of output
-   *                            // (can also be array of RegExps)
-   *   blankOutput: true,       // Special checks when there is no output
-   * });
-   */
-  exec: function DTT_exec(tests) {
-    tests = tests || {};
+  registerCleanupFunction(function() {
+    DeveloperToolbarTest.hide();
 
-    if (tests.typed) {
-      DeveloperToolbar.display.inputter.setInput(tests.typed);
+    // a.k.a Services.prefs.clearUserPref("devtools.toolbar.enabled");
+    if (menuItem) {
+      menuItem.hidden = true;
+    }
+    if (command) {
+      command.setAttribute("disabled", "true");
+    }
+    if (appMenuItem) {
+      appMenuItem.hidden = true;
     }
 
-    let typed = DeveloperToolbar.display.inputter.getInputState().typed;
-    let output = DeveloperToolbar.display.requisition.exec();
-
-    is(typed, output.typed, 'output.command for: ' + typed);
-
-    if (tests.completed !== false) {
-      ok(output.completed, 'output.completed false for: ' + typed);
-    }
-    else {
-      // It is actually an error if we say something is async and it turns
-      // out not to be? For now we're saying 'no'
-      // ok(!output.completed, 'output.completed true for: ' + typed);
-    }
-
-    if (tests.args != null) {
-      is(Object.keys(tests.args).length, Object.keys(output.args).length,
-         'arg count for ' + typed);
-
-      Object.keys(output.args).forEach(function(arg) {
-        let expectedArg = tests.args[arg];
-        let actualArg = output.args[arg];
-
-        if (typeof expectedArg === 'function') {
-          ok(expectedArg(actualArg), 'failed test func. ' + typed + '/' + arg);
-        }
-        else {
-          if (Array.isArray(expectedArg)) {
-            if (!Array.isArray(actualArg)) {
-              ok(false, 'actual is not an array. ' + typed + '/' + arg);
-              return;
-            }
+    // leakHunt({ DeveloperToolbar: DeveloperToolbar });
+  });
 
-            is(expectedArg.length, actualArg.length,
-                    'array length: ' + typed + '/' + arg);
-            for (let i = 0; i < expectedArg.length; i++) {
-              is(expectedArg[i], actualArg[i],
-                      'member: "' + typed + '/' + arg + '/' + i);
-            }
-          }
-          else {
-            is(expectedArg, actualArg, 'typed: "' + typed + '" arg: ' + arg);
-          }
-        }
-      });
-    }
-
-    let displayed = DeveloperToolbar.outputPanel._div.textContent;
-
-    if (tests.outputMatch) {
-      function doTest(match, against) {
-        if (!match.test(against)) {
-          ok(false, "html output for " + typed + " against " + match.source +
-                  " (textContent sent to info)");
-          info("Actual textContent");
-          info(against);
-        }
-      }
-      if (Array.isArray(tests.outputMatch)) {
-        tests.outputMatch.forEach(function(match) {
-          doTest(match, displayed);
-        });
-      }
-      else {
-        doTest(tests.outputMatch, displayed);
-      }
-    }
+  // a.k.a: Services.prefs.setBoolPref("devtools.toolbar.enabled", true);
+  if (menuItem) {
+    menuItem.hidden = false;
+  }
+  if (command) {
+    command.removeAttribute("disabled");
+  }
+  if (appMenuItem) {
+    appMenuItem.hidden = false;
+  }
 
-    if (tests.blankOutput != null) {
-      if (!/^$/.test(displayed)) {
-        ok(false, "html output for " + typed + " (textContent sent to info)");
-        info("Actual textContent");
-        info(displayed);
-      }
-    }
-  },
+  addTab(uri, function(browser, tab) {
+    DeveloperToolbarTest.show(function() {
 
-  /**
-   * Quick wrapper around the things you need to do to run DeveloperToolbar
-   * command tests:
-   * - Set the pref 'devtools.toolbar.enabled' to true
-   * - Add a tab pointing at |uri|
-   * - Open the DeveloperToolbar
-   * - Register a cleanup function to undo the above
-   * - Run the tests
-   *
-   * @param uri The uri of a page to load. Can be 'about:blank' or 'data:...'
-   * @param testFunc A function containing the tests to run. This should
-   * arrange for 'finish()' to be called on completion.
-   */
-  test: function DTT_test(uri, testFunc) {
-    let menuItem = document.getElementById("menu_devToolbar");
-    let command = document.getElementById("Tools:DevToolbar");
-    let appMenuItem = document.getElementById("appmenu_devToolbar");
-
-    registerCleanupFunction(function() {
-      DeveloperToolbarTest.hide();
-
-      // a.k.a Services.prefs.clearUserPref("devtools.toolbar.enabled");
-      if (menuItem) {
-        menuItem.hidden = true;
+      try {
+        testFunc(browser, tab);
       }
-      if (command) {
-        command.setAttribute("disabled", "true");
+      catch (ex) {
+        ok(false, "" + ex);
+        console.error(ex);
+        finish();
+        throw ex;
       }
-      if (appMenuItem) {
-        appMenuItem.hidden = true;
-      }
-
-      // leakHunt({ DeveloperToolbar: DeveloperToolbar });
     });
-
-    // a.k.a: Services.prefs.setBoolPref("devtools.toolbar.enabled", true);
-    if (menuItem) {
-      menuItem.hidden = false;
-    }
-    if (command) {
-      command.removeAttribute("disabled");
-    }
-    if (appMenuItem) {
-      appMenuItem.hidden = false;
-    }
-
-    addTab(uri, function(browser, tab) {
-      DeveloperToolbarTest.show(function() {
-
-        try {
-          testFunc(browser, tab);
-        }
-        catch (ex) {
-          ok(false, "" + ex);
-          console.error(ex);
-          finish();
-          throw ex;
-        }
-      });
-    });
-  },
+  });
 };
 
 
 /**
  * Memory leak hunter. Walks a tree of objects looking for DOM nodes.
  * Usage:
  * leakHunt({
  *   thing: thing,
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -547,18 +547,16 @@ StackFramesView.prototype = {
        resume.setAttribute("tooltiptext", L10N.getStr("resumeTooltip"));
        resume.setAttribute("checked", true);
      }
      // If we're attached, do the opposite.
      else if (aState == "attached") {
        resume.setAttribute("tooltiptext", L10N.getStr("pauseTooltip"));
        resume.removeAttribute("checked");
      }
-
-     DebuggerView.Scripts.clearSearch();
    },
 
   /**
    * Removes all elements from the stackframes container, leaving it empty.
    */
   empty: function DVF_empty() {
     while (this._frames.firstChild) {
       this._frames.removeChild(this._frames.firstChild);
--- a/browser/devtools/shared/DeveloperToolbar.jsm
+++ b/browser/devtools/shared/DeveloperToolbar.jsm
@@ -38,17 +38,16 @@ function DeveloperToolbar(aChromeWindow,
   this._doc = this._element.ownerDocument;
 
   this._lastState = NOTIFICATIONS.HIDE;
   this._pendingShowCallback = undefined;
   this._pendingHide = false;
   this._errorsCount = {};
   this._webConsoleButton = this._doc
                            .getElementById("developer-toolbar-webconsole");
-  this._webConsoleButtonLabel = this._webConsoleButton.label;
 
   try {
     GcliCommands.refreshAutoCommands(aChromeWindow);
   }
   catch (ex) {
     console.error(ex);
   }
 }
@@ -102,62 +101,62 @@ Object.defineProperty(DeveloperToolbar.p
  * Called from browser.xul in response to menu-click or keyboard shortcut to
  * toggle the toolbar
  */
 DeveloperToolbar.prototype.toggle = function DT_toggle()
 {
   if (this.visible) {
     this.hide();
   } else {
-    this.show();
+    this.show(true);
   }
 };
 
 /**
  * Even if the user has not clicked on 'Got it' in the intro, we only show it
  * once per session.
  * Warning this is slightly messed up because this.DeveloperToolbar is not the
  * same as this.DeveloperToolbar when in browser.js context.
  */
 DeveloperToolbar.introShownThisSession = false;
 
 /**
  * Show the developer toolbar
  * @param aCallback show events can be asynchronous. If supplied aCallback will
  * be called when the DeveloperToolbar is visible
  */
-DeveloperToolbar.prototype.show = function DT_show(aCallback)
+DeveloperToolbar.prototype.show = function DT_show(aFocus, aCallback)
 {
   if (this._lastState != NOTIFICATIONS.HIDE) {
     return;
   }
 
   Services.prefs.setBoolPref("devtools.toolbar.visible", true);
 
   this._notify(NOTIFICATIONS.LOAD);
   this._pendingShowCallback = aCallback;
   this._pendingHide = false;
 
   let checkLoad = function() {
     if (this.tooltipPanel && this.tooltipPanel.loaded &&
         this.outputPanel && this.outputPanel.loaded) {
-      this._onload();
+      this._onload(aFocus);
     }
   }.bind(this);
 
   this._input = this._doc.querySelector(".gclitoolbar-input-node");
   this.tooltipPanel = new TooltipPanel(this._doc, this._input, checkLoad);
   this.outputPanel = new OutputPanel(this._doc, this._input, checkLoad);
 };
 
 /**
  * Initializing GCLI can only be done when we've got content windows to write
  * to, so this needs to be done asynchronously.
  */
-DeveloperToolbar.prototype._onload = function DT_onload()
+DeveloperToolbar.prototype._onload = function DT_onload(aFocus)
 {
   this._doc.getElementById("Tools:DevToolbar").setAttribute("checked", "true");
 
   let contentDocument = this._chromeWindow.getBrowser().contentDocument;
 
   this.display = gcli.createDisplay({
     contentDocument: contentDocument,
     chromeDocument: this._doc,
@@ -189,17 +188,20 @@ DeveloperToolbar.prototype._onload = fun
   this._chromeWindow.getBrowser().tabContainer.addEventListener("TabSelect", this, false);
   this._chromeWindow.getBrowser().tabContainer.addEventListener("TabClose", this, false);
   this._chromeWindow.getBrowser().addEventListener("load", this, true);
   this._chromeWindow.getBrowser().addEventListener("beforeunload", this, true);
 
   this._initErrorsCount(this._chromeWindow.getBrowser().selectedTab);
 
   this._element.hidden = false;
-  this._input.focus();
+
+  if (aFocus) {
+    this._input.focus();
+  }
 
   this._notify(NOTIFICATIONS.SHOW);
   if (this._pendingShowCallback) {
     this._pendingShowCallback.call();
     this._pendingShowCallback = undefined;
   }
 
   // If a hide event happened while we were loading, then we need to hide.
@@ -494,21 +496,19 @@ function DT__updateErrorsCount(aChangedT
   let tabId = this._chromeWindow.getBrowser().selectedTab.linkedPanel;
   if (aChangedTabId && tabId != aChangedTabId) {
     return;
   }
 
   let errors = this._errorsCount[tabId];
 
   if (errors) {
-    this._webConsoleButton.label =
-      this._webConsoleButtonLabel + " (" + errors + ")";
-  }
-  else {
-    this._webConsoleButton.label = this._webConsoleButtonLabel;
+    this._webConsoleButton.setAttribute("error-count", errors);
+  } else {
+    this._webConsoleButton.removeAttribute("error-count");
   }
 };
 
 /**
  * Reset the errors counter for the given tab.
  *
  * @param nsIDOMElement aTab The xul:tab for which you want to reset the page
  * errors counters.
--- a/browser/devtools/shared/test/browser_toolbar_webconsole_errors_count.js
+++ b/browser/devtools/shared/test/browser_toolbar_webconsole_errors_count.js
@@ -24,18 +24,18 @@ function test() {
     oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.SHOW, onOpenToolbar);
     toolbar.doCommand();
   }
 
   ignoreAllUncaughtExceptions();
   addTab(TEST_URI, openToolbar);
 
   function getErrorsCount() {
-    let match = webconsole.label.match(/\((\d+)\)$/);
-    return match ? match[1] : 0;
+    let count = webconsole.getAttribute("error-count");
+    return count ? count : "0";
   }
 
   function onOpenToolbar() {
     ok(DeveloperToolbar.visible, "DeveloperToolbar is visible");
 
     waitForValue({
       name: "web console button shows page errors",
       validator: getErrorsCount,
--- a/browser/devtools/shared/test/head.js
+++ b/browser/devtools/shared/test/head.js
@@ -47,17 +47,17 @@ let DeveloperToolbarTest = {
   /**
    * Paranoid DeveloperToolbar.show();
    */
   show: function DTT_show(aCallback) {
     if (DeveloperToolbar.visible) {
       ok(false, "DeveloperToolbar.visible at start of openDeveloperToolbar");
     }
     else {
-      DeveloperToolbar.show(aCallback);
+      DeveloperToolbar.show(true, aCallback);
     }
   },
 
   /**
    * Paranoid DeveloperToolbar.hide();
    */
   hide: function DTT_hide() {
     if (!DeveloperToolbar.visible) {
--- a/browser/locales/en-US/chrome/browser/devtools/gcli.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/gcli.properties
@@ -15,16 +15,25 @@
 # or command parameter when no description has been provided.
 canonDescNone=(No description)
 
 # LOCALIZATION NOTE (cliEvalJavascript): The special '{' command allows entry
 # of JavaScript like traditional developer tool command lines. This describes
 # the '{' command.
 cliEvalJavascript=Enter JavaScript directly
 
+# LOCALIZATION NOTE (cliUnusedArg): When the command line has more arguments
+# than the current command can understand this is the error message shown to
+# the user.
+cliUnusedArg=Too many arguments
+
+# LOCALIZATION NOTE (cliOptions): The title of the dialog which displays the
+# options that are available to the current command.
+cliOptions=Available Options
+
 # LOCALIZATION NOTE (fieldSelectionSelect): When a command has a parameter
 # that has a number of pre-defined options the user interface presents these
 # in a drop-down menu, where the first 'option' is an indicator that a
 # selection should be made. This string describes that first option.
 fieldSelectionSelect=Select a %S…
 
 # LOCALIZATION NOTE (fieldArrayAdd): When a command has a parameter that can
 # be repeated a number of times (e.g. like the 'cat a.txt b.txt' command) the
@@ -33,16 +42,21 @@ fieldSelectionSelect=Select a %S…
 fieldArrayAdd=Add
 
 # LOCALIZATION NOTE (fieldArrayDel): When a command has a parameter that can
 # be repeated a number of times (e.g. like the 'cat a.txt b.txt' command) the
 # user interface presents buttons to add and remove arguments. This string is
 # used to remove arguments.
 fieldArrayDel=Delete
 
+# LOCALIZATION NOTE (fieldMenuMore): When the menu has displayed all the
+# matches that it should (i.e. about 10 items) then we display this to alert
+# the user that more matches are available.
+fieldMenuMore=More matches, keep typing
+
 # LOCALIZATION NOTE (jstypeParseScope): The command line provides completion
 # for JavaScript commands, however there are times when the scope of what
 # we're completing against can't be used. This error message is displayed when
 # this happens.
 jstypeParseScope=Scope lost
 
 # LOCALIZATION NOTE (jstypeParseMissing): When the command line is doing
 # JavaScript completion, sometimes the property to be completed does not
--- a/browser/themes/gnomestripe/browser.css
+++ b/browser/themes/gnomestripe/browser.css
@@ -2401,17 +2401,17 @@ html|*#gcli-output-frame {
   -moz-appearance: none;
   margin-bottom: -2px;
 }
 
 .gclitoolbar-input-node,
 .gclitoolbar-complete-node,
 .gclitoolbar-prompt {
   margin: 0;
-  -moz-margin-end: 3px;
+  -moz-margin-end: 5px;
   -moz-box-align: center;
   padding-top: 0;
   padding-bottom: 0;
   padding-right: 4px;
   border: 1px solid transparent;
   border-radius: 3px;
   text-shadow: none;
 }
@@ -2515,8 +2515,62 @@ stack[anonid=browserStack][responsivemod
 
 .devtools-responsiveui-resizehandle {
   width: 16px;
   height: 16px;
   cursor: se-resize;
   -moz-transform: translate(12px, 12px);
   background-image: url("chrome://browser/skin/devtools/responsive-se-resizer.png");
 }
+
+/* Developer Toolbar */
+
+.developer-toolbar-button {
+  -moz-appearance: none;
+  min-width: 78px;
+  min-height: 22px;
+  text-shadow: 0 -1px 0 hsla(210,8%,5%,.45);
+  border-radius: 3px;
+  color: inherit;
+  border: 1px solid transparent;
+  margin: 0 5px;
+  padding: 0 10px;
+  list-style-image: url("chrome://browser/skin/devtools/tools-icons-small.png");
+}
+
+.developer-toolbar-button:active:hover,
+.developer-toolbar-button[checked=true] {
+  border-color: hsla(210,8%,5%,.6);
+  background: rgba(0,0,0,.6);
+  box-shadow: 0 1px 2px rgba(0,0,0,.5) inset, 0 1px 0 hsla(210,16%,76%,.15);
+}
+
+.developer-toolbar-button[checked=true] {
+  color: hsl(208,100%,60%) !important;
+  background: rgba(0,0,0,.4);
+  text-shadow: 0 0 6px hsl(208,100%,60%);
+}
+
+#developer-toolbar-webconsole {
+  -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+#developer-toolbar-inspector {
+  -moz-image-region: rect(16px, 16px, 32px, 0);
+}
+
+#developer-toolbar-styleeditor {
+  -moz-image-region: rect(32px, 16px, 48px, 0);
+}
+
+#developer-toolbar-debugger {
+  -moz-image-region: rect(48px, 16px, 64px, 0);
+}
+
+/* Error counter */
+
+#developer-toolbar-webconsole[error-count]:before {
+  color: #FDF3DE;
+  min-width: 16px;
+  text-shadow: none;
+  background-image: -moz-linear-gradient(top, #B4211B, #8A1915);
+  border-radius: 1px;
+}
--- a/browser/themes/gnomestripe/devtools/gcli.css
+++ b/browser/themes/gnomestripe/devtools/gcli.css
@@ -118,8 +118,14 @@
 .gcli-menu-highlight,
 .gcli-menu-highlight.gcli-menu-option:hover {
   background-color: hsla(0,0%,0%,.3);
 }
 
 .gcli-menu-typed {
   color: hsl(25,78%,50%);
 }
+
+.gcli-menu-more {
+  font-size: 80%;
+  text-align: right;
+  -moz-padding-end: 8px;
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..12e9c72acc819abb8dcc9985d94ea3b1b9bbda2a
GIT binary patch
literal 2976
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
Wc;m0cEV&i{0000<MNUMnLSTZjh`|{E
--- a/browser/themes/gnomestripe/jar.mn
+++ b/browser/themes/gnomestripe/jar.mn
@@ -154,16 +154,17 @@ browser.jar:
   skin/classic/browser/devtools/debugger-play.png      (devtools/debugger-play.png)
   skin/classic/browser/devtools/debugger-step-in.png   (devtools/debugger-step-in.png)
   skin/classic/browser/devtools/debugger-step-out.png  (devtools/debugger-step-out.png)
   skin/classic/browser/devtools/debugger-step-over.png (devtools/debugger-step-over.png)
   skin/classic/browser/devtools/inspector-option-icon.png (devtools/inspector-option-icon.png)
   skin/classic/browser/devtools/responsive-se-resizer.png (devtools/responsive-se-resizer.png)
   skin/classic/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
   skin/classic/browser/devtools/responsive-background.png (devtools/responsive-background.png)
+  skin/classic/browser/devtools/tools-icons-small.png     (devtools/tools-icons-small.png)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-16-throbber.png
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-24-throbber.png
   skin/classic/browser/sync-32.png
   skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-128.png
   skin/classic/browser/sync-desktopIcon.png
--- a/browser/themes/pinstripe/browser.css
+++ b/browser/themes/pinstripe/browser.css
@@ -3142,17 +3142,17 @@ html|*#gcli-output-frame {
   -moz-appearance: none;
   margin-bottom: -2px;
 }
 
 .gclitoolbar-input-node,
 .gclitoolbar-complete-node,
 .gclitoolbar-prompt {
   margin: 0;
-  -moz-margin-end: 3px;
+  -moz-margin-end: 5px;
   -moz-box-align: center;
   padding-top: 0;
   padding-bottom: 0;
   padding-right: 4px;
   border: 1px solid transparent;
   border-radius: @toolbarbuttonCornerRadius@;
   text-shadow: none;
 }
@@ -3256,8 +3256,62 @@ stack[anonid=browserStack][responsivemod
 
 .devtools-responsiveui-resizehandle {
   width: 16px;
   height: 16px;
   cursor: se-resize;
   -moz-transform: translate(12px, 12px);
   background-image: url("chrome://browser/skin/devtools/responsive-se-resizer.png");
 }
+
+/* Developer Toolbar */
+
+.developer-toolbar-button {
+  -moz-appearance: none;
+  min-width: 78px;
+  min-height: 22px;
+  text-shadow: 0 -1px 0 hsla(210,8%,5%,.45);
+  border-radius: @toolbarbuttonCornerRadius@;
+  color: inherit;
+  border: 1px solid transparent;
+  margin: 0 5px;
+  padding: 0 10px;
+  list-style-image: url("chrome://browser/skin/devtools/tools-icons-small.png");
+}
+
+.developer-toolbar-button:active:hover,
+.developer-toolbar-button[checked=true] {
+  border-color: hsla(210,8%,5%,.6);
+  background: rgba(0,0,0,.6);
+  box-shadow: 0 1px 2px rgba(0,0,0,.5) inset, 0 1px 0 hsla(210,16%,76%,.15);
+}
+
+.developer-toolbar-button[checked=true] {
+  color: hsl(208,100%,60%) !important;
+  background: rgba(0,0,0,.4);
+  text-shadow: 0 0 6px hsl(208,100%,60%);
+}
+
+#developer-toolbar-webconsole {
+  -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+#developer-toolbar-inspector {
+  -moz-image-region: rect(16px, 16px, 32px, 0);
+}
+
+#developer-toolbar-styleeditor {
+  -moz-image-region: rect(32px, 16px, 48px, 0);
+}
+
+#developer-toolbar-debugger {
+  -moz-image-region: rect(48px, 16px, 64px, 0);
+}
+
+/* Error counter */
+
+#developer-toolbar-webconsole[error-count]:before {
+  color: #FDF3DE;
+  min-width: 16px;
+  text-shadow: none;
+  background-image: -moz-linear-gradient(top, #B4211B, #8A1915);
+  border-radius: 1px;
+}
--- a/browser/themes/pinstripe/devtools/gcli.css
+++ b/browser/themes/pinstripe/devtools/gcli.css
@@ -120,8 +120,14 @@
 .gcli-menu-highlight,
 .gcli-menu-highlight.gcli-menu-option:hover {
   background-color: hsla(0,0%,0%,.3);
 }
 
 .gcli-menu-typed {
   color: hsl(25,78%,50%);
 }
+
+.gcli-menu-more {
+  font-size: 80%;
+  text-align: right;
+  -moz-padding-end: 8px;
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..12e9c72acc819abb8dcc9985d94ea3b1b9bbda2a
GIT binary patch
literal 2976
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
Wc;m0cEV&i{0000<MNUMnLSTZjh`|{E
--- a/browser/themes/pinstripe/jar.mn
+++ b/browser/themes/pinstripe/jar.mn
@@ -195,16 +195,17 @@ browser.jar:
   skin/classic/browser/devtools/debugger-play.png           (devtools/debugger-play.png)
   skin/classic/browser/devtools/debugger-step-in.png        (devtools/debugger-step-in.png)
   skin/classic/browser/devtools/debugger-step-out.png       (devtools/debugger-step-out.png)
   skin/classic/browser/devtools/debugger-step-over.png      (devtools/debugger-step-over.png)
   skin/classic/browser/devtools/inspector-option-icon.png   (devtools/inspector-option-icon.png)
   skin/classic/browser/devtools/responsive-se-resizer.png   (devtools/responsive-se-resizer.png)
   skin/classic/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
   skin/classic/browser/devtools/responsive-background.png   (devtools/responsive-background.png)
+  skin/classic/browser/devtools/tools-icons-small.png       (devtools/tools-icons-small.png)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-throbber.png
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-32.png
   skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-128.png
   skin/classic/browser/sync-desktopIcon.png
   skin/classic/browser/sync-mobileIcon.png
--- a/browser/themes/winstripe/browser.css
+++ b/browser/themes/winstripe/browser.css
@@ -3076,17 +3076,17 @@ html|*#gcli-output-frame {
   -moz-appearance: none;
   margin-bottom: -2px;
 }
 
 .gclitoolbar-input-node,
 .gclitoolbar-complete-node,
 .gclitoolbar-prompt {
   margin: 0;
-  -moz-margin-end: 3px;
+  -moz-margin-end: 5px;
   -moz-box-align: center;
   padding-top: 0;
   padding-bottom: 0;
   padding-right: 4px;
   border: 1px solid transparent;
   border-radius: 3px;
   text-shadow: none;
 }
@@ -3190,8 +3190,62 @@ stack[anonid=browserStack][responsivemod
 
 .devtools-responsiveui-resizehandle {
   width: 16px;
   height: 16px;
   cursor: se-resize;
   -moz-transform: translate(12px, 12px);
   background-image: url("chrome://browser/skin/devtools/responsive-se-resizer.png");
 }
+
+/* Developer Toolbar */
+
+.developer-toolbar-button {
+  -moz-appearance: none;
+  min-width: 78px;
+  min-height: 22px;
+  text-shadow: 0 -1px 0 hsla(210,8%,5%,.45);
+  border-radius: 3px;
+  color: inherit;
+  border: 1px solid transparent;
+  margin: 0 5px;
+  padding: 0 10px;
+  list-style-image: url("chrome://browser/skin/devtools/tools-icons-small.png");
+}
+
+.developer-toolbar-button:active:hover,
+.developer-toolbar-button[checked=true] {
+  border-color: hsla(210,8%,5%,.6);
+  background: rgba(0,0,0,.6);
+  box-shadow: 0 1px 2px rgba(0,0,0,.5) inset, 0 1px 0 hsla(210,16%,76%,.1);
+}
+
+.developer-toolbar-button[checked=true] {
+  color: hsl(208,100%,60%) !important;
+  background: rgba(0,0,0,.4);
+  text-shadow: 0 0 6px hsl(208,100%,60%);
+}
+
+#developer-toolbar-webconsole {
+  -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+#developer-toolbar-inspector {
+  -moz-image-region: rect(16px, 16px, 32px, 0);
+}
+
+#developer-toolbar-styleeditor {
+  -moz-image-region: rect(32px, 16px, 48px, 0);
+}
+
+#developer-toolbar-debugger {
+  -moz-image-region: rect(48px, 16px, 64px, 0);
+}
+
+/* Error counter */
+
+#developer-toolbar-webconsole[error-count]:before {
+  color: #FDF3DE;
+  min-width: 16px;
+  text-shadow: none;
+  background-image: -moz-linear-gradient(top, #B4211B, #8A1915);
+  border-radius: 1px;
+}
--- a/browser/themes/winstripe/devtools/gcli.css
+++ b/browser/themes/winstripe/devtools/gcli.css
@@ -118,8 +118,14 @@
 .gcli-menu-highlight,
 .gcli-menu-highlight.gcli-menu-option:hover {
   background-color: hsla(0,0%,0%,.3);
 }
 
 .gcli-menu-typed {
   color: hsl(25,78%,50%);
 }
+
+.gcli-menu-more {
+  font-size: 80%;
+  text-align: right;
+  -moz-padding-end: 8px;
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..12e9c72acc819abb8dcc9985d94ea3b1b9bbda2a
GIT binary patch
literal 2976
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
Wc;m0cEV&i{0000<MNUMnLSTZjh`|{E
--- a/browser/themes/winstripe/jar.mn
+++ b/browser/themes/winstripe/jar.mn
@@ -181,16 +181,17 @@ browser.jar:
         skin/classic/browser/devtools/debugger-play.png             (devtools/debugger-play.png)
         skin/classic/browser/devtools/debugger-step-in.png          (devtools/debugger-step-in.png)
         skin/classic/browser/devtools/debugger-step-out.png         (devtools/debugger-step-out.png)
         skin/classic/browser/devtools/debugger-step-over.png        (devtools/debugger-step-over.png)
         skin/classic/browser/devtools/inspector-option-icon.png     (devtools/inspector-option-icon.png)
         skin/classic/browser/devtools/responsive-se-resizer.png     (devtools/responsive-se-resizer.png)
         skin/classic/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
         skin/classic/browser/devtools/responsive-background.png     (devtools/responsive-background.png)
+        skin/classic/browser/devtools/tools-icons-small.png         (devtools/tools-icons-small.png)
 #ifdef MOZ_SERVICES_SYNC
         skin/classic/browser/sync-throbber.png
         skin/classic/browser/sync-16.png
         skin/classic/browser/sync-32.png
         skin/classic/browser/sync-128.png
         skin/classic/browser/sync-bg.png
         skin/classic/browser/sync-desktopIcon.png
         skin/classic/browser/sync-mobileIcon.png
@@ -378,16 +379,17 @@ browser.jar:
         skin/classic/aero/browser/devtools/debugger-play.png         (devtools/debugger-play.png)
         skin/classic/aero/browser/devtools/debugger-step-in.png      (devtools/debugger-step-in.png)
         skin/classic/aero/browser/devtools/debugger-step-out.png     (devtools/debugger-step-out.png)
         skin/classic/aero/browser/devtools/debugger-step-over.png    (devtools/debugger-step-over.png)
         skin/classic/aero/browser/devtools/inspector-option-icon.png (devtools/inspector-option-icon.png)
         skin/classic/aero/browser/devtools/responsive-se-resizer.png (devtools/responsive-se-resizer.png)
         skin/classic/aero/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
         skin/classic/aero/browser/devtools/responsive-background.png (devtools/responsive-background.png)
+        skin/classic/aero/browser/devtools/tools-icons-small.png     (devtools/tools-icons-small.png)
 #ifdef MOZ_SERVICES_SYNC
         skin/classic/aero/browser/sync-throbber.png
         skin/classic/aero/browser/sync-16.png
         skin/classic/aero/browser/sync-32.png
         skin/classic/aero/browser/sync-128.png
         skin/classic/aero/browser/sync-bg.png
         skin/classic/aero/browser/sync-desktopIcon.png
         skin/classic/aero/browser/sync-mobileIcon.png