Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* |
michael@0 | 2 | * Copyright 2012, Mozilla Foundation and contributors |
michael@0 | 3 | * |
michael@0 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
michael@0 | 5 | * you may not use this file except in compliance with the License. |
michael@0 | 6 | * You may obtain a copy of the License at |
michael@0 | 7 | * |
michael@0 | 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
michael@0 | 9 | * |
michael@0 | 10 | * Unless required by applicable law or agreed to in writing, software |
michael@0 | 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
michael@0 | 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
michael@0 | 13 | * See the License for the specific language governing permissions and |
michael@0 | 14 | * limitations under the License. |
michael@0 | 15 | */ |
michael@0 | 16 | |
michael@0 | 17 | 'use strict'; |
michael@0 | 18 | |
michael@0 | 19 | var util = require('../util/util'); |
michael@0 | 20 | var promise = require('../util/promise'); |
michael@0 | 21 | var domtemplate = require('../util/domtemplate'); |
michael@0 | 22 | var host = require('../util/host'); |
michael@0 | 23 | |
michael@0 | 24 | var Status = require('../types/types').Status; |
michael@0 | 25 | var cli = require('../cli'); |
michael@0 | 26 | var Requisition = require('../cli').Requisition; |
michael@0 | 27 | var CommandAssignment = require('../cli').CommandAssignment; |
michael@0 | 28 | var fields = require('../fields/fields'); |
michael@0 | 29 | var intro = require('../ui/intro'); |
michael@0 | 30 | |
michael@0 | 31 | var RESOLVED = promise.resolve(true); |
michael@0 | 32 | |
michael@0 | 33 | /** |
michael@0 | 34 | * Various ways in which we need to manipulate the caret/selection position. |
michael@0 | 35 | * A value of null means we're not expecting a change |
michael@0 | 36 | */ |
michael@0 | 37 | var Caret = exports.Caret = { |
michael@0 | 38 | /** |
michael@0 | 39 | * We are expecting changes, but we don't need to move the cursor |
michael@0 | 40 | */ |
michael@0 | 41 | NO_CHANGE: 0, |
michael@0 | 42 | |
michael@0 | 43 | /** |
michael@0 | 44 | * We want the entire input area to be selected |
michael@0 | 45 | */ |
michael@0 | 46 | SELECT_ALL: 1, |
michael@0 | 47 | |
michael@0 | 48 | /** |
michael@0 | 49 | * The whole input has changed - push the cursor to the end |
michael@0 | 50 | */ |
michael@0 | 51 | TO_END: 2, |
michael@0 | 52 | |
michael@0 | 53 | /** |
michael@0 | 54 | * A part of the input has changed - push the cursor to the end of the |
michael@0 | 55 | * changed section |
michael@0 | 56 | */ |
michael@0 | 57 | TO_ARG_END: 3 |
michael@0 | 58 | }; |
michael@0 | 59 | |
michael@0 | 60 | /** |
michael@0 | 61 | * Shared promise for loading command.html |
michael@0 | 62 | */ |
michael@0 | 63 | var commandHtmlPromise; |
michael@0 | 64 | |
michael@0 | 65 | var commandLanguage = exports.commandLanguage = { |
michael@0 | 66 | // Language implementation for GCLI commands |
michael@0 | 67 | item: 'language', |
michael@0 | 68 | name: 'commands', |
michael@0 | 69 | prompt: ':', |
michael@0 | 70 | proportionalFonts: true, |
michael@0 | 71 | |
michael@0 | 72 | constructor: function(terminal) { |
michael@0 | 73 | this.terminal = terminal; |
michael@0 | 74 | this.document = terminal.document; |
michael@0 | 75 | this.focusManager = this.terminal.focusManager; |
michael@0 | 76 | |
michael@0 | 77 | var options = this.terminal.options; |
michael@0 | 78 | this.requisition = options.requisition; |
michael@0 | 79 | if (this.requisition == null) { |
michael@0 | 80 | if (options.environment == null) { |
michael@0 | 81 | options.environment = {}; |
michael@0 | 82 | options.environment.document = options.document || this.document; |
michael@0 | 83 | options.environment.window = options.environment.document.defaultView; |
michael@0 | 84 | } |
michael@0 | 85 | |
michael@0 | 86 | this.requisition = new Requisition(options); |
michael@0 | 87 | } |
michael@0 | 88 | |
michael@0 | 89 | // We also keep track of the last known arg text for the current assignment |
michael@0 | 90 | this.lastText = undefined; |
michael@0 | 91 | |
michael@0 | 92 | // Used to effect caret changes. See _processCaretChange() |
michael@0 | 93 | this._caretChange = null; |
michael@0 | 94 | |
michael@0 | 95 | // We keep track of which assignment the cursor is in |
michael@0 | 96 | this.assignment = this.requisition.getAssignmentAt(0); |
michael@0 | 97 | |
michael@0 | 98 | if (commandHtmlPromise == null) { |
michael@0 | 99 | commandHtmlPromise = host.staticRequire(module, './command.html'); |
michael@0 | 100 | } |
michael@0 | 101 | |
michael@0 | 102 | return commandHtmlPromise.then(function(commandHtml) { |
michael@0 | 103 | this.commandDom = util.toDom(this.document, commandHtml); |
michael@0 | 104 | |
michael@0 | 105 | this.requisition.commandOutputManager.onOutput.add(this.outputted, this); |
michael@0 | 106 | var mapping = cli.getMapping(this.requisition.executionContext); |
michael@0 | 107 | mapping.terminal = this.terminal; |
michael@0 | 108 | |
michael@0 | 109 | return this; |
michael@0 | 110 | }.bind(this)); |
michael@0 | 111 | }, |
michael@0 | 112 | |
michael@0 | 113 | destroy: function() { |
michael@0 | 114 | var mapping = cli.getMapping(this.requisition.executionContext); |
michael@0 | 115 | delete mapping.terminal; |
michael@0 | 116 | |
michael@0 | 117 | this.requisition.commandOutputManager.onOutput.remove(this.outputted, this); |
michael@0 | 118 | |
michael@0 | 119 | this.terminal = undefined; |
michael@0 | 120 | this.requisition = undefined; |
michael@0 | 121 | this.commandDom = undefined; |
michael@0 | 122 | }, |
michael@0 | 123 | |
michael@0 | 124 | // From the requisition.textChanged event |
michael@0 | 125 | textChanged: function() { |
michael@0 | 126 | if (this.terminal == null) { |
michael@0 | 127 | return; // This can happen post-destroy() |
michael@0 | 128 | } |
michael@0 | 129 | |
michael@0 | 130 | if (this.terminal._caretChange == null) { |
michael@0 | 131 | // We weren't expecting a change so this was requested by the hint system |
michael@0 | 132 | // we should move the cursor to the end of the 'changed section', and the |
michael@0 | 133 | // best we can do for that right now is the end of the current argument. |
michael@0 | 134 | this.terminal._caretChange = Caret.TO_ARG_END; |
michael@0 | 135 | } |
michael@0 | 136 | |
michael@0 | 137 | var newStr = this.requisition.toString(); |
michael@0 | 138 | var input = this.terminal.getInputState(); |
michael@0 | 139 | |
michael@0 | 140 | input.typed = newStr; |
michael@0 | 141 | this._processCaretChange(input); |
michael@0 | 142 | |
michael@0 | 143 | // We don't update terminal._previousValue. Should we? |
michael@0 | 144 | // Shouldn't this really be a function of terminal? |
michael@0 | 145 | if (this.terminal.inputElement.value !== newStr) { |
michael@0 | 146 | this.terminal.inputElement.value = newStr; |
michael@0 | 147 | } |
michael@0 | 148 | this.terminal.onInputChange({ inputState: input }); |
michael@0 | 149 | |
michael@0 | 150 | // We get here for minor things like whitespace change in arg prefix, |
michael@0 | 151 | // so we ignore anything but an actual value change. |
michael@0 | 152 | if (this.assignment.arg.text === this.lastText) { |
michael@0 | 153 | return; |
michael@0 | 154 | } |
michael@0 | 155 | |
michael@0 | 156 | this.lastText = this.assignment.arg.text; |
michael@0 | 157 | |
michael@0 | 158 | this.terminal.field.update(); |
michael@0 | 159 | this.terminal.field.setConversion(this.assignment.conversion); |
michael@0 | 160 | util.setTextContent(this.terminal.descriptionEle, this.description); |
michael@0 | 161 | }, |
michael@0 | 162 | |
michael@0 | 163 | // Called internally whenever we think that the current assignment might |
michael@0 | 164 | // have changed, typically on mouse-clicks or key presses. |
michael@0 | 165 | caretMoved: function(start) { |
michael@0 | 166 | var newAssignment = this.requisition.getAssignmentAt(start); |
michael@0 | 167 | if (this.assignment !== newAssignment) { |
michael@0 | 168 | if (this.assignment.param.type.onLeave) { |
michael@0 | 169 | this.assignment.param.type.onLeave(this.assignment); |
michael@0 | 170 | } |
michael@0 | 171 | |
michael@0 | 172 | // This can be kicked off either by requisition doing an assign or by |
michael@0 | 173 | // terminal noticing a cursor movement out of a command, so we should |
michael@0 | 174 | // check that this really is a new assignment |
michael@0 | 175 | var isNew = (this.assignment !== newAssignment); |
michael@0 | 176 | |
michael@0 | 177 | this.assignment = newAssignment; |
michael@0 | 178 | this.terminal.updateCompletion(); |
michael@0 | 179 | |
michael@0 | 180 | if (isNew) { |
michael@0 | 181 | this.updateHints(); |
michael@0 | 182 | } |
michael@0 | 183 | |
michael@0 | 184 | if (this.assignment.param.type.onEnter) { |
michael@0 | 185 | this.assignment.param.type.onEnter(this.assignment); |
michael@0 | 186 | } |
michael@0 | 187 | } |
michael@0 | 188 | else { |
michael@0 | 189 | if (this.assignment && this.assignment.param.type.onChange) { |
michael@0 | 190 | this.assignment.param.type.onChange(this.assignment); |
michael@0 | 191 | } |
michael@0 | 192 | } |
michael@0 | 193 | |
michael@0 | 194 | // Warning: compare the logic here with the logic in fieldChanged, which |
michael@0 | 195 | // is slightly different. They should probably be the same |
michael@0 | 196 | var error = (this.assignment.status === Status.ERROR); |
michael@0 | 197 | this.focusManager.setError(error); |
michael@0 | 198 | }, |
michael@0 | 199 | |
michael@0 | 200 | // Called whenever the assignment that we're providing help with changes |
michael@0 | 201 | updateHints: function() { |
michael@0 | 202 | this.lastText = this.assignment.arg.text; |
michael@0 | 203 | |
michael@0 | 204 | var field = this.terminal.field; |
michael@0 | 205 | if (field) { |
michael@0 | 206 | field.onFieldChange.remove(this.terminal.fieldChanged, this.terminal); |
michael@0 | 207 | field.destroy(); |
michael@0 | 208 | } |
michael@0 | 209 | |
michael@0 | 210 | field = this.terminal.field = fields.getField(this.assignment.param.type, { |
michael@0 | 211 | document: this.terminal.document, |
michael@0 | 212 | requisition: this.requisition |
michael@0 | 213 | }); |
michael@0 | 214 | |
michael@0 | 215 | this.focusManager.setImportantFieldFlag(field.isImportant); |
michael@0 | 216 | |
michael@0 | 217 | field.onFieldChange.add(this.terminal.fieldChanged, this.terminal); |
michael@0 | 218 | field.setConversion(this.assignment.conversion); |
michael@0 | 219 | |
michael@0 | 220 | // Filled in by the template process |
michael@0 | 221 | this.terminal.errorEle = undefined; |
michael@0 | 222 | this.terminal.descriptionEle = undefined; |
michael@0 | 223 | |
michael@0 | 224 | var contents = this.terminal.tooltipTemplate.cloneNode(true); |
michael@0 | 225 | domtemplate.template(contents, this.terminal, { |
michael@0 | 226 | blankNullUndefined: true, |
michael@0 | 227 | stack: 'terminal.html#tooltip' |
michael@0 | 228 | }); |
michael@0 | 229 | |
michael@0 | 230 | util.clearElement(this.terminal.tooltipElement); |
michael@0 | 231 | this.terminal.tooltipElement.appendChild(contents); |
michael@0 | 232 | this.terminal.tooltipElement.style.display = 'block'; |
michael@0 | 233 | |
michael@0 | 234 | field.setMessageElement(this.terminal.errorEle); |
michael@0 | 235 | }, |
michael@0 | 236 | |
michael@0 | 237 | /** |
michael@0 | 238 | * See also handleDownArrow for some symmetry |
michael@0 | 239 | */ |
michael@0 | 240 | handleUpArrow: function() { |
michael@0 | 241 | // If the user is on a valid value, then we increment the value, but if |
michael@0 | 242 | // they've typed something that's not right we page through predictions |
michael@0 | 243 | if (this.assignment.getStatus() === Status.VALID) { |
michael@0 | 244 | return this.requisition.increment(this.assignment).then(function() { |
michael@0 | 245 | this.textChanged(); |
michael@0 | 246 | this.focusManager.onInputChange(); |
michael@0 | 247 | return true; |
michael@0 | 248 | }.bind(this)); |
michael@0 | 249 | } |
michael@0 | 250 | |
michael@0 | 251 | return promise.resolve(false); |
michael@0 | 252 | }, |
michael@0 | 253 | |
michael@0 | 254 | /** |
michael@0 | 255 | * See also handleUpArrow for some symmetry |
michael@0 | 256 | */ |
michael@0 | 257 | handleDownArrow: function() { |
michael@0 | 258 | if (this.assignment.getStatus() === Status.VALID) { |
michael@0 | 259 | return this.requisition.decrement(this.assignment).then(function() { |
michael@0 | 260 | this.textChanged(); |
michael@0 | 261 | this.focusManager.onInputChange(); |
michael@0 | 262 | return true; |
michael@0 | 263 | }.bind(this)); |
michael@0 | 264 | } |
michael@0 | 265 | |
michael@0 | 266 | return promise.resolve(false); |
michael@0 | 267 | }, |
michael@0 | 268 | |
michael@0 | 269 | /** |
michael@0 | 270 | * RETURN checks status and might exec |
michael@0 | 271 | */ |
michael@0 | 272 | handleReturn: function(input) { |
michael@0 | 273 | // Deny RETURN unless the command might work |
michael@0 | 274 | if (this.requisition.status !== Status.VALID) { |
michael@0 | 275 | return promise.resolve(false); |
michael@0 | 276 | } |
michael@0 | 277 | |
michael@0 | 278 | this.terminal.history.add(input); |
michael@0 | 279 | this.terminal.unsetChoice(); |
michael@0 | 280 | |
michael@0 | 281 | return this.requisition.exec().then(function() { |
michael@0 | 282 | this.textChanged(); |
michael@0 | 283 | return true; |
michael@0 | 284 | }.bind(this)); |
michael@0 | 285 | }, |
michael@0 | 286 | |
michael@0 | 287 | /** |
michael@0 | 288 | * Warning: We get TAB events for more than just the user pressing TAB in our |
michael@0 | 289 | * input element. |
michael@0 | 290 | */ |
michael@0 | 291 | handleTab: function() { |
michael@0 | 292 | // It's possible for TAB to not change the input, in which case the |
michael@0 | 293 | // textChanged event will not fire, and the caret move will not be |
michael@0 | 294 | // processed. So we check that this is done first |
michael@0 | 295 | this.terminal._caretChange = Caret.TO_ARG_END; |
michael@0 | 296 | var inputState = this.terminal.getInputState(); |
michael@0 | 297 | this._processCaretChange(inputState); |
michael@0 | 298 | |
michael@0 | 299 | this.terminal._previousValue = this.terminal.inputElement.value; |
michael@0 | 300 | |
michael@0 | 301 | // The changes made by complete may happen asynchronously, so after the |
michael@0 | 302 | // the call to complete() we should avoid making changes before the end |
michael@0 | 303 | // of the event loop |
michael@0 | 304 | var index = this.terminal.getChoiceIndex(); |
michael@0 | 305 | return this.requisition.complete(inputState.cursor, index).then(function(updated) { |
michael@0 | 306 | // Abort UI changes if this UI update has been overtaken |
michael@0 | 307 | if (!updated) { |
michael@0 | 308 | return RESOLVED; |
michael@0 | 309 | } |
michael@0 | 310 | this.textChanged(); |
michael@0 | 311 | return this.terminal.unsetChoice(); |
michael@0 | 312 | }.bind(this)); |
michael@0 | 313 | }, |
michael@0 | 314 | |
michael@0 | 315 | /** |
michael@0 | 316 | * The input text has changed in some way. |
michael@0 | 317 | */ |
michael@0 | 318 | handleInput: function(value) { |
michael@0 | 319 | this.terminal._caretChange = Caret.NO_CHANGE; |
michael@0 | 320 | |
michael@0 | 321 | return this.requisition.update(value).then(function(updated) { |
michael@0 | 322 | // Abort UI changes if this UI update has been overtaken |
michael@0 | 323 | if (!updated) { |
michael@0 | 324 | return RESOLVED; |
michael@0 | 325 | } |
michael@0 | 326 | this.textChanged(); |
michael@0 | 327 | return this.terminal.unsetChoice(); |
michael@0 | 328 | }.bind(this)); |
michael@0 | 329 | }, |
michael@0 | 330 | |
michael@0 | 331 | /** |
michael@0 | 332 | * Counterpart to |setInput| for moving the cursor. |
michael@0 | 333 | * @param cursor A JS object shaped like { start: x, end: y } |
michael@0 | 334 | */ |
michael@0 | 335 | setCursor: function(cursor) { |
michael@0 | 336 | this._caretChange = Caret.NO_CHANGE; |
michael@0 | 337 | this._processCaretChange({ |
michael@0 | 338 | typed: this.terminal.inputElement.value, |
michael@0 | 339 | cursor: cursor |
michael@0 | 340 | }); |
michael@0 | 341 | }, |
michael@0 | 342 | |
michael@0 | 343 | /** |
michael@0 | 344 | * If this._caretChange === Caret.TO_ARG_END, we alter the input object to move |
michael@0 | 345 | * the selection start to the end of the current argument. |
michael@0 | 346 | * @param input An object shaped like { typed:'', cursor: { start:0, end:0 }} |
michael@0 | 347 | */ |
michael@0 | 348 | _processCaretChange: function(input) { |
michael@0 | 349 | var start, end; |
michael@0 | 350 | switch (this._caretChange) { |
michael@0 | 351 | case Caret.SELECT_ALL: |
michael@0 | 352 | start = 0; |
michael@0 | 353 | end = input.typed.length; |
michael@0 | 354 | break; |
michael@0 | 355 | |
michael@0 | 356 | case Caret.TO_END: |
michael@0 | 357 | start = input.typed.length; |
michael@0 | 358 | end = input.typed.length; |
michael@0 | 359 | break; |
michael@0 | 360 | |
michael@0 | 361 | case Caret.TO_ARG_END: |
michael@0 | 362 | // There could be a fancy way to do this involving assignment/arg math |
michael@0 | 363 | // but it doesn't seem easy, so we cheat a move the cursor to just before |
michael@0 | 364 | // the next space, or the end of the input |
michael@0 | 365 | start = input.cursor.start; |
michael@0 | 366 | do { |
michael@0 | 367 | start++; |
michael@0 | 368 | } |
michael@0 | 369 | while (start < input.typed.length && input.typed[start - 1] !== ' '); |
michael@0 | 370 | |
michael@0 | 371 | end = start; |
michael@0 | 372 | break; |
michael@0 | 373 | |
michael@0 | 374 | default: |
michael@0 | 375 | start = input.cursor.start; |
michael@0 | 376 | end = input.cursor.end; |
michael@0 | 377 | break; |
michael@0 | 378 | } |
michael@0 | 379 | |
michael@0 | 380 | start = (start > input.typed.length) ? input.typed.length : start; |
michael@0 | 381 | end = (end > input.typed.length) ? input.typed.length : end; |
michael@0 | 382 | |
michael@0 | 383 | var newInput = { |
michael@0 | 384 | typed: input.typed, |
michael@0 | 385 | cursor: { start: start, end: end } |
michael@0 | 386 | }; |
michael@0 | 387 | |
michael@0 | 388 | if (this.terminal.inputElement.selectionStart !== start) { |
michael@0 | 389 | this.terminal.inputElement.selectionStart = start; |
michael@0 | 390 | } |
michael@0 | 391 | if (this.terminal.inputElement.selectionEnd !== end) { |
michael@0 | 392 | this.terminal.inputElement.selectionEnd = end; |
michael@0 | 393 | } |
michael@0 | 394 | |
michael@0 | 395 | this.caretMoved(start); |
michael@0 | 396 | |
michael@0 | 397 | this._caretChange = null; |
michael@0 | 398 | return newInput; |
michael@0 | 399 | }, |
michael@0 | 400 | |
michael@0 | 401 | /** |
michael@0 | 402 | * Calculate the properties required by the template process for completer.html |
michael@0 | 403 | */ |
michael@0 | 404 | getCompleterTemplateData: function() { |
michael@0 | 405 | var input = this.terminal.getInputState(); |
michael@0 | 406 | var start = input.cursor.start; |
michael@0 | 407 | var index = this.terminal.getChoiceIndex(); |
michael@0 | 408 | |
michael@0 | 409 | return this.requisition.getStateData(start, index).then(function(data) { |
michael@0 | 410 | // Calculate the statusMarkup required to show wavy lines underneath the |
michael@0 | 411 | // input text (like that of an inline spell-checker) which used by the |
michael@0 | 412 | // template process for completer.html |
michael@0 | 413 | // i.e. s/space/ /g in the string (for HTML display) and status to an |
michael@0 | 414 | // appropriate class name (i.e. lower cased, prefixed with gcli-in-) |
michael@0 | 415 | data.statusMarkup.forEach(function(member) { |
michael@0 | 416 | member.string = member.string.replace(/ /g, '\u00a0'); // i.e. |
michael@0 | 417 | member.className = 'gcli-in-' + member.status.toString().toLowerCase(); |
michael@0 | 418 | }, this); |
michael@0 | 419 | |
michael@0 | 420 | return data; |
michael@0 | 421 | }); |
michael@0 | 422 | }, |
michael@0 | 423 | |
michael@0 | 424 | /** |
michael@0 | 425 | * Called by the onFieldChange event (via the terminal) on the current Field |
michael@0 | 426 | */ |
michael@0 | 427 | fieldChanged: function(ev) { |
michael@0 | 428 | this.requisition.setAssignment(this.assignment, ev.conversion.arg, |
michael@0 | 429 | { matchPadding: true }).then(function() { |
michael@0 | 430 | this.textChanged(); |
michael@0 | 431 | }.bind(this)); |
michael@0 | 432 | |
michael@0 | 433 | var isError = ev.conversion.message != null && ev.conversion.message !== ''; |
michael@0 | 434 | this.focusManager.setError(isError); |
michael@0 | 435 | }, |
michael@0 | 436 | |
michael@0 | 437 | /** |
michael@0 | 438 | * Monitor for new command executions |
michael@0 | 439 | */ |
michael@0 | 440 | outputted: function(ev) { |
michael@0 | 441 | if (ev.output.hidden) { |
michael@0 | 442 | return; |
michael@0 | 443 | } |
michael@0 | 444 | |
michael@0 | 445 | var template = this.commandDom.cloneNode(true); |
michael@0 | 446 | var templateOptions = { stack: 'terminal.html#outputView' }; |
michael@0 | 447 | |
michael@0 | 448 | var context = this.requisition.conversionContext; |
michael@0 | 449 | var data = { |
michael@0 | 450 | onclick: context.update, |
michael@0 | 451 | ondblclick: context.updateExec, |
michael@0 | 452 | language: this, |
michael@0 | 453 | output: ev.output, |
michael@0 | 454 | promptClass: (ev.output.error ? 'gcli-row-error' : '') + |
michael@0 | 455 | (ev.output.completed ? ' gcli-row-complete' : ''), |
michael@0 | 456 | // Elements attached to this by template(). |
michael@0 | 457 | rowinEle: null, |
michael@0 | 458 | rowoutEle: null, |
michael@0 | 459 | throbEle: null, |
michael@0 | 460 | promptEle: null |
michael@0 | 461 | }; |
michael@0 | 462 | |
michael@0 | 463 | domtemplate.template(template, data, templateOptions); |
michael@0 | 464 | |
michael@0 | 465 | ev.output.promise.then(function() { |
michael@0 | 466 | var document = data.rowoutEle.ownerDocument; |
michael@0 | 467 | |
michael@0 | 468 | if (ev.output.completed) { |
michael@0 | 469 | data.promptEle.classList.add('gcli-row-complete'); |
michael@0 | 470 | } |
michael@0 | 471 | if (ev.output.error) { |
michael@0 | 472 | data.promptEle.classList.add('gcli-row-error'); |
michael@0 | 473 | } |
michael@0 | 474 | |
michael@0 | 475 | util.clearElement(data.rowoutEle); |
michael@0 | 476 | |
michael@0 | 477 | return ev.output.convert('dom', context).then(function(node) { |
michael@0 | 478 | this._linksToNewTab(node); |
michael@0 | 479 | data.rowoutEle.appendChild(node); |
michael@0 | 480 | |
michael@0 | 481 | var event = document.createEvent('Event'); |
michael@0 | 482 | event.initEvent('load', true, true); |
michael@0 | 483 | event.addedElement = node; |
michael@0 | 484 | node.dispatchEvent(event); |
michael@0 | 485 | |
michael@0 | 486 | this.terminal.scrollToBottom(); |
michael@0 | 487 | data.throbEle.style.display = ev.output.completed ? 'none' : 'block'; |
michael@0 | 488 | }.bind(this)); |
michael@0 | 489 | }.bind(this)).then(null, console.error); |
michael@0 | 490 | |
michael@0 | 491 | this.terminal.addElement(data.rowinEle); |
michael@0 | 492 | this.terminal.addElement(data.rowoutEle); |
michael@0 | 493 | this.terminal.scrollToBottom(); |
michael@0 | 494 | |
michael@0 | 495 | this.focusManager.outputted(); |
michael@0 | 496 | }, |
michael@0 | 497 | |
michael@0 | 498 | /** |
michael@0 | 499 | * Find elements with href attributes and add a target=_blank so opened links |
michael@0 | 500 | * will open in a new window |
michael@0 | 501 | */ |
michael@0 | 502 | _linksToNewTab: function(element) { |
michael@0 | 503 | var links = element.querySelectorAll('*[href]'); |
michael@0 | 504 | for (var i = 0; i < links.length; i++) { |
michael@0 | 505 | links[i].setAttribute('target', '_blank'); |
michael@0 | 506 | } |
michael@0 | 507 | return element; |
michael@0 | 508 | }, |
michael@0 | 509 | |
michael@0 | 510 | /** |
michael@0 | 511 | * Show a short introduction to this language |
michael@0 | 512 | */ |
michael@0 | 513 | showIntro: function() { |
michael@0 | 514 | intro.maybeShowIntro(this.requisition.commandOutputManager, |
michael@0 | 515 | this.requisition.conversionContext); |
michael@0 | 516 | }, |
michael@0 | 517 | }; |
michael@0 | 518 | |
michael@0 | 519 | /** |
michael@0 | 520 | * The description (displayed at the top of the hint area) should be blank if |
michael@0 | 521 | * we're entering the CommandAssignment (because it's obvious) otherwise it's |
michael@0 | 522 | * the parameter description. |
michael@0 | 523 | */ |
michael@0 | 524 | Object.defineProperty(commandLanguage, 'description', { |
michael@0 | 525 | get: function() { |
michael@0 | 526 | if (this.assignment == null || ( |
michael@0 | 527 | this.assignment instanceof CommandAssignment && |
michael@0 | 528 | this.assignment.value == null)) { |
michael@0 | 529 | return ''; |
michael@0 | 530 | } |
michael@0 | 531 | |
michael@0 | 532 | return this.assignment.param.manual || this.assignment.param.description; |
michael@0 | 533 | }, |
michael@0 | 534 | enumerable: true |
michael@0 | 535 | }); |
michael@0 | 536 | |
michael@0 | 537 | /** |
michael@0 | 538 | * Present an error message to the hint popup |
michael@0 | 539 | */ |
michael@0 | 540 | Object.defineProperty(commandLanguage, 'message', { |
michael@0 | 541 | get: function() { |
michael@0 | 542 | return this.assignment.conversion.message; |
michael@0 | 543 | }, |
michael@0 | 544 | enumerable: true |
michael@0 | 545 | }); |
michael@0 | 546 | |
michael@0 | 547 | exports.items = [ commandLanguage ]; |