| // Copyright 2015 The Cobalt Authors. All Rights Reserved. | |
| // | |
| // 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. | |
| function CommandInput(elem) { | |
| // The top-level HTML element this class uses for input interaction. | |
| this.inputElem = elem; | |
| // Buffer containing previously executed commands. | |
| this.commandHistory = []; | |
| // The text the user is currently editing in the input element. | |
| this.currCommand = ''; | |
| // Index of the character at the cursor position (after the last | |
| // entry/deletion point). | |
| this.cursorPos = 0; | |
| // The period of the cursor blinking in milliseconds. | |
| this.blinkInterval = 600; | |
| // The time at which the cursor blink state was last updated (ms). | |
| this.lastBlink = 0; | |
| // The current blink state (on/off) of the cursor. | |
| this.blinkOn = false; | |
| // The index of the currently displayed command. If the current command has | |
| // not yet been stored in the history buffer, this index will be equal to the | |
| // length of the history buffer. | |
| this.commandIndex = 0; | |
| // Used to store the current command when traversing back through history. | |
| this.pendingCommand = ''; | |
| this.updateText(); | |
| } | |
| // The blinking cursor is always on the character immediately after the | |
| // insertion/deletion position. | |
| CommandInput.prototype.insertStringBehindCursor = function(c) { | |
| var cmd = this.currCommand; | |
| var pos = this.cursorPos; | |
| this.currCommand = cmd.substring(0, pos); | |
| this.currCommand += c; | |
| this.currCommand += cmd.substring(pos, cmd.length); | |
| this.cursorPos += c.length; | |
| this.updateText(); | |
| } | |
| // The blinking cursor is always on the character immediately after the | |
| // insertion/deletion position. | |
| CommandInput.prototype.deleteCharBehindCursor = function() { | |
| var cmd = this.currCommand; | |
| var pos = this.cursorPos; | |
| if (cmd.length > 0 && pos > 0) { | |
| this.currCommand = cmd.substring(0, pos - 1); | |
| this.currCommand += cmd.substring(pos, cmd.length); | |
| this.cursorPos -= 1; | |
| this.updateText(); | |
| } | |
| } | |
| CommandInput.prototype.deleteCharAtCursor = function() { | |
| var cmd = this.currCommand; | |
| var pos = this.cursorPos; | |
| if (cmd.length > 0 && pos < cmd.length) { | |
| this.currCommand = cmd.substring(0, pos); | |
| this.currCommand += cmd.substring(pos + 1, cmd.length); | |
| this.updateText(); | |
| } | |
| } | |
| // The move parameter is an integer that specifies how many characters to the | |
| // right to move the input cursor. A negative value will move the cursor that | |
| // many characters to the right. | |
| CommandInput.prototype.moveCursor = function(move) { | |
| this.cursorPos += move; | |
| if (this.cursorPos < 0) { | |
| this.cursorPos = 0; | |
| } else if (this.cursorPos > this.currCommand.length) { | |
| this.cursorPos = this.currCommand.length; | |
| } | |
| this.updateText(); | |
| } | |
| // The currently displayed command. This can either be a new command, or a | |
| // command from the history buffer that the user has navigated to. | |
| CommandInput.prototype.getCurrentCommand = function() { | |
| return this.currCommand; | |
| } | |
| // Set the current command to any command from the history. This is used to | |
| // support shell-style execution of any previously executed command. | |
| // The idx parameter should be an integer: positive specifies an absolute | |
| // index, negative a number of commands back from the current one. | |
| CommandInput.prototype.setCurrentCommandFromHistory = function(idx) { | |
| if (idx < 0) { | |
| idx = this.commandHistory.length + idx; | |
| } | |
| if (idx >= 0 && idx < this.commandHistory.length) { | |
| this.currCommand = this.commandHistory[idx]; | |
| } else { | |
| this.currCommand = ''; | |
| } | |
| } | |
| // Set the current command to the previous command in the history buffer, if | |
| // one exists. If the current command has not yet been executed, store it in | |
| // the pending command member, in case the user decides to navigate forward to | |
| // it again. | |
| CommandInput.prototype.back = function() { | |
| if (this.commandIndex > 0) { | |
| if (this.commandIndex == this.commandHistory.length) { | |
| this.pendingCommand = this.currCommand; | |
| } | |
| this.commandIndex -= 1; | |
| this.currCommand = this.commandHistory[this.commandIndex]; | |
| this.cursorPos = this.currCommand.length; | |
| this.updateText(); | |
| } | |
| } | |
| // Set the current command to the next command in the history buffer. If we | |
| // have gone past the most recent command in the buffer, restore the pending | |
| // command, if one exists. | |
| CommandInput.prototype.forward = function() { | |
| if (this.commandIndex < this.commandHistory.length) { | |
| this.commandIndex += 1; | |
| if (this.commandIndex < this.commandHistory.length) { | |
| this.currCommand = this.commandHistory[this.commandIndex]; | |
| } else { | |
| this.currCommand = this.pendingCommand; | |
| } | |
| this.cursorPos = this.currCommand.length; | |
| this.updateText(); | |
| } | |
| } | |
| // Stores the current command in the history buffer to make it | |
| // available for execution again using the back/forward methods. | |
| // Only stores if the current command is different from the previous. | |
| CommandInput.prototype.storeCurrentCommand = function() { | |
| var idx = this.commandHistory.length; | |
| if (this.commandHistory[idx - 1] != this.currCommand) { | |
| this.commandHistory[idx] = this.currCommand; | |
| } | |
| } | |
| // Clears the current command. | |
| CommandInput.prototype.clearCurrentCommand = function() { | |
| this.commandIndex = this.commandHistory.length; | |
| this.currCommand = ''; | |
| this.pendingCommand = ''; | |
| this.cursorPos = 0; | |
| this.updateText(); | |
| } | |
| // Stores and clears the current command. This will typically be called when | |
| // the user presses Enter to execute the current command. | |
| CommandInput.prototype.storeAndClearCurrentCommand = function() { | |
| this.storeCurrentCommand(); | |
| this.clearCurrentCommand(); | |
| } | |
| // Called on the animation timer to make the cursor blink with a period | |
| // defined by this.blinkInterval. | |
| CommandInput.prototype.animateBlink = function() { | |
| var t = window.performance.now(); | |
| if (t > this.lastBlink + this.blinkInterval) { | |
| this.lastBlink = t; | |
| this.blinkOn = !this.blinkOn; | |
| this.updateCursor(); | |
| } | |
| } | |
| CommandInput.prototype.escapeTagChars = function(text) { | |
| return text.replace(/&/g, '&').replace(/</g,'<').replace(/>/g,'>'); | |
| } | |
| // Replaces tag characters with their escaped equivalents for safe HTML display. | |
| // Adjusts the cursor position accordingly. | |
| // After escaping the tag characters, the cursor span may be longer than | |
| // one character, hence we calculate cursorStart/End. | |
| CommandInput.prototype.escapeTagCharsAndAdjustCursor = | |
| function(rawCommand, cursorPos) { | |
| var beforeCursor = rawCommand.substring(0, cursorPos); | |
| var atCursor = rawCommand.substring(cursorPos, cursorPos + 1); | |
| var afterCursor = rawCommand.substring(cursorPos + 1, rawCommand.length); | |
| beforeCursor = this.escapeTagChars(beforeCursor); | |
| atCursor = this.escapeTagChars(atCursor); | |
| afterCursor = this.escapeTagChars(afterCursor); | |
| var result = {}; | |
| result.commandAsHTML = beforeCursor + atCursor + afterCursor; | |
| result.cursorStart = beforeCursor.length; | |
| result.cursorEnd = beforeCursor.length + (atCursor.length || 1); | |
| return result; | |
| } | |
| // Called when the current command text changes. | |
| CommandInput.prototype.updateText = function() { | |
| var modifiedCommand = this.escapeTagCharsAndAdjustCursor(this.currCommand, | |
| this.cursorPos); | |
| var cursorStart = modifiedCommand.cursorStart; | |
| var cursorEnd = modifiedCommand.cursorEnd; | |
| var commandAsHTML = modifiedCommand.commandAsHTML; | |
| var html = ''; | |
| if (cursorEnd <= commandAsHTML.length) { | |
| html = commandAsHTML.substring(0, cursorStart); | |
| html += '<span id="cursor">'; | |
| html += commandAsHTML.substring(cursorStart, cursorEnd); | |
| html += '</span>'; | |
| html += commandAsHTML.substring(cursorEnd, commandAsHTML.length); | |
| } else { | |
| html = commandAsHTML + '<span id="cursor"> </span>'; | |
| } | |
| this.inputElem.innerHTML = html; | |
| this.blinkOn = true; | |
| this.updateCursor(); | |
| } | |
| // Called when the text or the cursor position changes to maintain the blinking | |
| // cursor in the correct position. This should only be called when necessary, | |
| // as it creates HTML nodes. | |
| CommandInput.prototype.updateCursor = function() { | |
| var cursor = document.querySelector('#cursor'); | |
| if (this.blinkOn) { | |
| cursor.style.backgroundColor = '#FFFFFF'; | |
| cursor.style.color = '#000000'; | |
| } else { | |
| cursor.style.backgroundColor = '#000000'; | |
| cursor.style.color = '#FFFFFF'; | |
| } | |
| } | |
| CommandInput.prototype.getHistory = function() { | |
| return this.commandHistory; | |
| } |