diff --git a/.gitignore b/.gitignore index 28fdb01..d4db9aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea/workspace.xml .idea/tasks.xml -.DS_Store \ No newline at end of file +.DS_Store +bower_components diff --git a/.idea/misc.xml b/.idea/misc.xml index 223f438..1162f43 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,8 +1,5 @@ - - $APPLICATION_HOME_DIR$/lib/webide.jar!/resources/html5-schema/html5.rnc - diff --git a/README.md b/README.md index 91ead01..c2379c2 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -josh.js 0.3 +josh.js 0.2 =========== -http://sdether.github.com/josh.js/ +http://sdether.github.io/josh.js/ -***Javascript Online SHell*** provides a toolkit for building bash-like command line consoles for web pages. It consists of the following components: +***Javascript Online SHell*** provides a toolkit for building bash-like command line consoles for web pages. JOSH enables the visitor who prefers the bash-like command to maneuver through web content using a console rather than clicking with a mouse. This toolkit is most useful for people who like to use command line because it is faster and more effective than using a mouse. It is easier and convienent to access hierarchical information using JOSH command history, change or display current directory. It consists of the following components: * `readline.js` - full readline support for ctrl sequences, tab, history, etc. * `shell.js` - visual presentation of the shell and command handling @@ -11,6 +11,19 @@ http://sdether.github.com/josh.js/ * `history.js` - localStorage backed command history * `killring.js` - killring for kill & yank handling in readline +## What to use Josh for and when to use it + +Josh allows developers to build their own command line interface to any sites. It supports full CLI Readline in the browser like TAB completion, emacs-style line editing, killring and history with reverse search. When you are tired of clicking your way through a hierachy tree, Josh will come in handy. It will helps you browse or navigate text files quickly and minimal the using of mouse click. + +## Tutorials +* Hello world - put a console on a web page and add a new custom command with completion +* Quake Console - Create a quake-style console with ls, + cd, pwd and bash filename tab-completion +* GitHub Console - Extend the Quake Console to talk to GitHub's REST API to navigate repositories, their branches and file system + +## Articles +* CLI all the things: Introducing Josh.js Article about the origins of Josh.js with an example console for wordpress sites. + ## License josh.js is licensed under the Apache 2.0 License @@ -121,22 +134,36 @@ By implementing the functions `getNode` and `getChildNodes`, this library adds p `history.js` implements a localStorage back command history storage that persists over page changes and reloads. It is used by the `shell.js` history command to list all executed commands, and by `readline.js` for up/down arrow and reverse search capabilities. ### killring.js -`killing.js` implements the kill and yank behavior as well as state tracking, i.e. multiple consecutive kills are combined as a single kill and killring rotation tracks the previous yank, so that the `readline.js` can remove the previous yank and replace it with the rotated text. +`killring.js` implements the kill and yank behavior as well as state tracking, i.e. multiple consecutive kills are combined as a single kill and killring rotation tracks the previous yank, so that the `readline.js` can remove the previous yank and replace it with the rotated text. ## Changelog -**0.3** -- 2013/02/13 -* Removed all html used for Shell UI generation from config to `Shell.templates`, so that they can easily be customized (see: [Issue 11](https://github.com/sdether/josh.js/issues/11 ) +**0.2.10** -- 2014/04/03 +* Added bower support (pr#19 - @bricef) +* Code clean-up for closure compiler issues (pr#20 - @aaronmars) + +**0.2.9** -- 2013/08/31 +* Added ability to bind ReadLine/Shell to an element. +* Added ability to bind/unbind keys (could be used to replace emacs bindings of readline, but primarily added to unbind some keys for using readline on input elements. +* Created input.js for easy binding of readline to either an input element or a span behaving like an input. + +**0.2.8** -- 2013/03/13 +* Added handling of . and .. in Josh.PathHandler.pathcompletionhandler, so that a trailing .. completes to ../ and . to ./ +* Removed the hardcoded **strong** in the input template, making it a span instead so it can be styled via css instead. +* The prompt value itself is now assumed to be html instead of plain text, allowing for richer formatting without changing the input template. + +**0.2.7** -- 2013/02/13 +* Removed all html used for Shell UI generation from config to `Shell.templates`, so that they can easily be customized (see: [Issue 11](https://github.com/sdether/josh.js/issues/11)) * Removed `PathHandler.templates`. PathHandler now attches its templates to `Shell.templates` as well **0.2.6** -- 2013/01/21 -* Removed Activation/Deactivation keybindings from Readline, making it an outside concern (see: [Issue 2](https://github.com/sdether/josh.js/issues/2 ) +* Removed Activation/Deactivation keybindings from Readline, making it an outside concern (see: [Issue 2](https://github.com/sdether/josh.js/issues/2)) * Fixed Backspace regression introduced by 0.2.5 * Fixed `M-d` not deleting last character of line * Example shell can now be resized (via jquery-ui.resizable) **0.2.5** -- 2013/01/14 -* Implemented missing Readline behavior (see: [Issue 1](https://github.com/sdether/josh.js/issues/1 ) +* Implemented missing Readline behavior (see: [Issue 1](https://github.com/sdether/josh.js/issues/1)) * Added scrollbar to sample implemenation (also adds scrollwheel support) **0.2.4** -- 2013/01/14 diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..9971f61 --- /dev/null +++ b/bower.json @@ -0,0 +1,33 @@ +{ + "name": "josh.js", + "version": "0.2.10", + "homepage": "http://sdether.github.io/josh.js/", + "authors": [ + "Arne Claassen " + ], + "description": "Javascript Online SHell provides a toolkit for building bash-like command line consoles for web pages.", + "main": [ + "js/readline.js", + "js/history.js", + "js/killring.js", + "js/input.js", + "js/pathhandler.js", + "js/shell.js" + ], + "keywords": [ + "shell", + "josh" + ], + "license": "Apache 2.0", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "jquery": "~3.1.0", + "lodash": "~4.14.2" + } +} diff --git a/index.html b/index.html index 1813906..06d1ce2 100755 --- a/index.html +++ b/index.html @@ -1,49 +1,75 @@ - - - Shell testbed + + + Shell testbed - - - - - - - - - - - - - + + + + + + + + + + + + + +
-
+
-

Press ~ to activate console.

+

Press ~ to activate console.

+

+ Cmd1: +

+ +

+ Cmd2: +

+ \ No newline at end of file diff --git a/js/example.js b/js/example.js index 0f00691..f8d2cc8 100644 --- a/js/example.js +++ b/js/example.js @@ -1,5 +1,5 @@ /*------------------------------------------------------------------------* - * Copyright 2013 Arne F. Claassen + * Copyright 2013-2014 Arne F. Claassen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,7 +45,7 @@ // ------------------------- // Setup the `Underscore` template for displaying items in the `KillRing`. - var killringItemTemplate = _.template("
<% _.each(items, function(item, i) { %>
<%- i %> <%- item %>
<% }); %>
") + var killringItemTemplate = _.template("
<% _.each(items, function(item, i) { %>
<%- i %> <%- item %>
<% }); %>
"); // Create a the command `killring` which will display all text currently in the `KillRing`, by attaching // a handler to the `Shell`. @@ -125,7 +125,7 @@ // references like `.` and `..`. In implementations that let you explore an hierarchy on a server, this function // would live on the server side and be called remotely via `getNode`. function findNode(current, parts, callback) { - if(!parts || parts.length == 0) { + if(!parts || parts.length === 0) { return callback(current); } if(parts[0] == ".") { @@ -147,12 +147,12 @@ // ----------------------- // Activation and display behavior happens at document ready time. - $(document).ready(function() { + $(root).ready(function() { // The default name for the div the shell uses as its container is `shell-panel`, although that can be changed via - // the shell config parameter `shell-view-id`. The `Shell` display model relies on a div to contain a 'view'. The - // acts as the view-port, i.e. the visible portion of the shell content, while the view is appended to and - // scrolled up as new content is added. + // the shell config parameter `shell-panel-id`. The `Shell` display model relies on a 'panel' to contain a 'view'. + // The 'panel' acts as the view-port, i.e. the visible portion of the shell content, while the 'view' is appended + // to and scrolled up as new content is added. var $consolePanel = $('#shell-panel'); // We use **jquery-ui**'s `resizable` to let us drag the bottom edge of the console up and down. @@ -167,8 +167,8 @@ } // Mimicking *Quake*-style dropdown consoles, we activate and show on `~`. - _console.log("activating shell"); if(event.keyCode == 126) { + _console.log("activating shell"); event.preventDefault(); shell.activate(); $consolePanel.slideDown(); @@ -208,7 +208,7 @@ boot: {}, dev: {}, etc: { - default: {}, + 'default': {}, 'rc.d': {}, sysconfig: {}, x11: {} @@ -254,7 +254,7 @@ }, src: {} }, - var: { + 'var': { lib: {}, lock: {}, run: {}, diff --git a/js/history.js b/js/history.js index 49fb27a..7e4e38c 100644 --- a/js/history.js +++ b/js/history.js @@ -1,5 +1,5 @@ /* ------------------------------------------------------------------------* - * Copyright 2013 Arne F. Claassen + * Copyright 2013-2014 Arne F. Claassen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,11 @@ var Josh = Josh || {}; var _key = config.key || 'josh.history'; if (_storage) { - var data = _storage.getItem(_key); + try { + var data = _storage.getItem(_key); + } catch(e) { + _console.log("Error accessing storage"); + } if (data) { _history = JSON.parse(data); _searchCursor = _cursor = _history.length - 1; @@ -38,7 +42,11 @@ var Josh = Josh || {}; } function save() { if (_storage) { - _storage.setItem(_key, JSON.stringify(_history)); + try { + _storage.setItem(_key, JSON.stringify(_history)); + } catch(e) { + _console.log("Error accessing storage"); + } } } diff --git a/js/input.js b/js/input.js new file mode 100644 index 0000000..3085db2 --- /dev/null +++ b/js/input.js @@ -0,0 +1,172 @@ +/* ------------------------------------------------------------------------* + * Copyright 2013-2014 Arne F. Claassen + * + * 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. + *-------------------------------------------------------------------------*/ + +var Josh = Josh || {}; +(function (root, $, _) { + $.fn.josh_caretTo = function (index) { + return this.queue(function (next) { + if (this.createTextRange) { + var range = this.createTextRange(); + range.move("character", index); + range.select(); + } else if (this.selectionStart !== null) { + this.setSelectionRange(index, index); + } + next(); + }); + }; + $.fn.josh_caretPosition = function () { + var el = this.get(0); + if (el.createTextRange) { + var range = el.createTextRange(); + range.moveStart('character', -el.value.length); + return range.text.length; + } else if (el.selectionStart !== null) { + return el.selectionStart; + } + return 0; + }; + + var history = Josh.History(); + var killring = new Josh.KillRing(); + + Josh.Input = function (config) { + config = config || {}; + + // instance fields + var _console = config.console || (Josh.Debug && root.console ? root.console : { + log: function () { + } + }); + + var _id = "#" + config.id; + var _blinktime = config.blinktime || 500; + var _active = false; + var _cursor_visible = false; + var _isInput = false; + var _history = config.history || history; + var _killring = config.killring || killring; + var _text; + var self = { + templates: { + span: _.template('') + }, + history: _history, + killring: _killring + }; + + $(document).ready(function () { + var $input = $(_id); + var el = $input.get(0); + var readline = new Josh.ReadLine({ + history: _history, + killring: _killring, + console: _console + }); + self.readline = readline; + readline.attach(el); + var activate = null; + _isInput = $input.is('input'); + if (_isInput) { + + _console.log(_id + ' is an input'); + + function renderInput(line) { + var text = line ? line.text : ''; + _text = text; + $input.val(text); + $input.josh_caretTo(line.cursor); + } + readline.onChange(renderInput); + $input.click(function() { + var line = readline.getLine(); + line.cursor = $input.josh_caretPosition(); + readline.setLine(line); + }); + + activate = function() { + // Note: have to re-render with a setTimeout, because on focus, but after the onfocus event is processed, + // the input will select all, invalidating our render + setTimeout(function() { + renderInput(readline.getLine()); + }, 0); + }; + } else { + _console.log(_id + ' is a non-input element'); + $input.html(self.templates.span()); + if(typeof $input.attr('tabindex') === 'undefined') { + $input.attr('tabindex',0); + } + var $left = $input.find('.left'); + var $right = $input.find('.right'); + var $cursor = $input.find('.cursor'); + + function renderSpan(line) { + var text = line.text || ''; + _text = text; + var cursorIdx = line.cursor || 0; + var left = _.escape(text.substr(0, cursorIdx)).replace(/ /g, ' '); + var cursor = text.substr(cursorIdx, 1); + var right = _.escape(text.substr(cursorIdx + 1)).replace(/ /g, ' '); + $left.html(left); + if (!cursor) { + $cursor.html(' ').css('textDecoration', 'underline'); + } else { + $cursor.text(cursor).css('textDecoration', 'underline'); + } + $right.html(right); + } + + function blinkCursor() { + if (!_active) { + return; + } + root.setTimeout(function () { + if (!_active) { + return; + } + _cursor_visible = !_cursor_visible; + if (_cursor_visible) { + $cursor.css('textDecoration', 'underline'); + } else { + $cursor.css('textDecoration', ''); + } + blinkCursor(); + }, _blinktime); + } + + activate = function () { + blinkCursor(); + } + readline.onChange(renderSpan); + } + readline.unbind({keyCode: Josh.Keys.Special.Tab}); + readline.unbind({'char': 'R', ctrlKey: true}); + readline.onActivate(function () { + _active = true; + activate(); + }); + readline.onDeactivate(function () { + _active = false; + if (_text) { + _history.accept(_text); + } + }); + + }); + return self; + } +})(this, $, _); diff --git a/js/killring.js b/js/killring.js index dc22874..822af2b 100644 --- a/js/killring.js +++ b/js/killring.js @@ -1,5 +1,5 @@ /* ------------------------------------------------------------------------* - * Copyright 2013 Arne F. Claassen + * Copyright 2013-2014 Arne F. Claassen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/js/pathhandler.js b/js/pathhandler.js index 62964aa..bf1148f 100644 --- a/js/pathhandler.js +++ b/js/pathhandler.js @@ -1,5 +1,5 @@ /* ------------------------------------------------------------------------* - * Copyright 2013 Arne F. Claassen + * Copyright 2013-2014 Arne F. Claassen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -94,6 +94,12 @@ var Josh = Josh || {}; var lastPathSeparator = arg.lastIndexOf("/"); var parent = arg.substr(0, lastPathSeparator + 1); partial = arg.substr(lastPathSeparator + 1); + if(partial === '..' || partial === '.') { + return callback({ + completion: '/', + suggestions: [] + }); + } _console.log("completing children via parent '" + parent+"' w/ partial '"+partial+"'"); return self.getNode(parent, function(node) { if(!node) { diff --git a/js/readline.js b/js/readline.js index 2534ce6..a710f23 100644 --- a/js/readline.js +++ b/js/readline.js @@ -1,5 +1,5 @@ /* ------------------------------------------------------------------------* - * Copyright 2013 Arne F. Claassen + * Copyright 2013-2014 Arne F. Claassen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,26 +15,28 @@ *-------------------------------------------------------------------------*/ var Josh = Josh || {}; -Josh.Version = "0.2.6"; +Josh.Version = "0.2.10"; (function(root) { - var SPECIAL = { - 8: 'BACKSPACE', - 9: 'TAB', - 13: 'ENTER', - 19: 'PAUSE', - 20: 'CAPS_LOCK', - 27: 'ESCAPE', - 32: 'SPACE', - 33: 'PAGE_UP', - 34: 'PAGE_DOWN', - 35: 'END', - 36: 'HOME', - 37: 'LEFT', - 38: 'UP', - 39: 'RIGHT', - 40: 'DOWN', - 45: 'INSERT', - 46: 'DELETE' + Josh.Keys = { + Special: { + Backspace: 8, + Tab: 9, + Enter: 13, + Pause: 19, + CapsLock: 20, + Escape: 27, + Space: 32, + PageUp: 33, + PageDown: 34, + End: 35, + Home: 36, + Left: 37, + Up: 38, + Right: 39, + Down: 40, + Insert: 45, + Delete: 46 + } }; Josh.ReadLine = function(config) { @@ -46,8 +48,9 @@ Josh.Version = "0.2.6"; } }); var _history = config.history || new Josh.History(); - var _deactivationKey = config.deactivationKey || { keyCode: 27 }; // Esc var _killring = config.killring || new Josh.KillRing(); + var _boundToElement = config.element ? true : false; + var _element = config.element || root; var _active = false; var _onActivate; var _onDeactivate; @@ -69,6 +72,72 @@ Josh.Version = "0.2.6"; var _completionActive; var _cmdQueue = []; var _suspended = false; + var _cmdMap = { + complete: cmdComplete, + done: cmdDone, + noop: cmdNoOp, + history_top: cmdHistoryTop, + history_end: cmdHistoryEnd, + history_next: cmdHistoryNext, + history_previous: cmdHistoryPrev, + end: cmdEnd, + home: cmdHome, + left: cmdLeft, + right: cmdRight, + cancel: cmdCancel, + 'delete': cmdDeleteChar, + backspace: cmdBackspace, + kill_eof: cmdKillToEOF, + kill_wordback: cmdKillWordBackward, + kill_wordforward: cmdKillWordForward, + yank: cmdYank, + clear: cmdClear, + search: cmdReverseSearch, + wordback: cmdBackwardWord, + wordforward: cmdForwardWord, + yank_rotate: cmdRotate + }; + var _keyMap = { + 'default': { + 8: cmdBackspace, // Backspace + 9: cmdComplete, // Tab + 13: cmdDone, // Enter + 27: cmdEsc, // Esc + 33: cmdHistoryTop, // Page Up + 34: cmdHistoryEnd, // Page Down + 35: cmdEnd, // End + 36: cmdHome, // Home + 37: cmdLeft, // Left + 38: cmdHistoryPrev, // Up + 39: cmdRight, // Right + 40: cmdHistoryNext, // Down + 46: cmdDeleteChar, // Delete + 10: cmdNoOp, // Pause + 19: cmdNoOp, // Caps Lock + 45: cmdNoOp // Insert + }, + control: { + 65: cmdHome, // A + 66: cmdLeft, // B + 67: cmdCancel, // C + 68: cmdDeleteChar, // D + 69: cmdEnd, // E + 70: cmdRight, // F + 80: cmdHistoryPrev, // P + 78: cmdHistoryNext, // N + 75: cmdKillToEOF, // K + 89: cmdYank, // Y + 76: cmdClear, // L + 82: cmdReverseSearch // R + }, + meta: { + 8: cmdKillWordBackward, // Backspace + 66: cmdBackwardWord, // B + 68: cmdKillWordForward, // D + 70: cmdForwardWord, // F + 89: cmdRotate // Y + } + }; // public methods var self = { @@ -87,6 +156,36 @@ Josh.Version = "0.2.6"; _onDeactivate(); } }, + bind: function(key, action) { + var k = getKey(key); + var cmd = _cmdMap[action]; + if(!cmd) { + return; + } + _keyMap[k.modifier][k.code]; + }, + unbind: function(key) { + var k = getKey(key); + delete _keyMap[k.modifier][k.code]; + }, + attach: function(el) { + if(_element) { + self.detach(); + } + _console.log("attaching"); + _console.log(el); + _element = el; + _boundToElement = true; + addEvent(_element, "focus", self.activate); + addEvent(_element, "blur", self.deactivate); + subscribeToKeys(); + }, + detach: function() { + removeEvent(_element, "focus", self.activate); + removeEvent(_element, "blur", self.deactivate); + _element = null; + _boundToElement = false; + }, onActivate: function(completionHandler) { _onActivate = completionHandler; }, @@ -125,10 +224,31 @@ Josh.Version = "0.2.6"; text: _text, cursor: _cursor }; + }, + setLine: function(line) { + _text = line.text; + _cursor = line.cursor; + refresh(); } }; // private methods + function addEvent(element, name, callback) { + if(element.addEventListener) { + element.addEventListener(name, callback, false); + } else if(element.attachEvent) { + element.attachEvent('on' + name, callback); + } + } + + function removeEvent(element, name, callback) { + if(element.removeEventListener) { + element.removeEventListener(name, callback, false); + } else if(element.detachEvent) { + element.detachEvent('on' + name, callback); + } + } + function getKeyInfo(e) { var code = e.keyCode || e.charCode; var c = String.fromCharCode(code); @@ -142,6 +262,22 @@ Josh.Version = "0.2.6"; }; } + function getKey(key) { + var k = { + modifier: 'default', + code: key.keyCode + }; + if(key.metaKey || key.altKey) { + k.modifier = 'meta'; + } else if(key.ctrlKey) { + k.modifier = 'control'; + } + if(key['char']) { + k.code = key['char'].charCodeAt(0); + } + return k; + } + function queue(cmd) { if(_suspended) { _cmdQueue.push(cmd); @@ -525,164 +661,69 @@ Josh.Version = "0.2.6"; return left + ins + right; } + function subscribeToKeys() { - // set up key capture - root.onkeydown = function(e) { - e = e || window.event; - - // return as unhandled if we're not active or the key is just a modifier key - if(!_active || e.keyCode == 16 || e.keyCode == 17 || e.keyCode == 18 || e.keyCode == 91) { - return true; - } - - var cmd = null; - - // check for some special first keys, regardless of modifiers - switch(e.keyCode) { - case 8: // Backspace - cmd = cmdBackspace; - break; - case 9: // Tab - cmd = cmdComplete; - break; - case 13: // Enter - cmd = cmdDone; - break; - case 27: // Esc - cmd = cmdEsc; - break; - case 33: // Page Up - cmd = cmdHistoryTop; - break; - case 34: // Page Down - cmd = cmdHistoryEnd; - break; - case 35: // End - cmd = cmdEnd; - break; - case 36: // Home - cmd = cmdHome; - break; - case 37: // Left - cmd = cmdLeft; - break; - case 38: // Up - cmd = cmdHistoryPrev; - break; - case 39: // Right - cmd = cmdRight; - break; - case 40: // Down - cmd = cmdHistoryNext; - break; - case 46: // Delete - cmd = cmdDeleteChar; - break; - - // these we catch and have no commands for - case 10: // Pause - case 19: // Caps Lock - case 45: // Insert - cmd = cmdNoOp; - break; - - // all others we don't handle at this level - default: - break; - } - - // intercept ctrl- and meta- sequences (may override the non-modifier cmd captured above - if(e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey) { - switch(e.keyCode) { - case 65: // A - cmd = cmdHome; - break; - case 66: // B - cmd = cmdLeft; - break; - case 67: // C - cmd = cmdCancel; - break; - case 68: // D - cmd = cmdDeleteChar; - break; - case 69: // E - cmd = cmdEnd; - break; - case 70: // F - cmd = cmdRight; - break; - case 80: // P - cmd = cmdHistoryPrev; - break; - case 78: // N - cmd = cmdHistoryNext; - break; - case 75: // K - cmd = cmdKillToEOF; - break; - case 89: // Y - cmd = cmdYank; - break; - case 76: // L - cmd = cmdClear; - break; - case 82: // R - cmd = cmdReverseSearch; - break; - } - } else if((e.altKey || e.metaKey) && !e.ctrlKey && !e.shiftKey) { - switch(e.keyCode) { - case 8: // Backspace - cmd = cmdKillWordBackward; - break; - case 66: // B - cmd = cmdBackwardWord; - break; - case 68: // D - cmd = cmdKillWordForward; - break; - case 70: // F - cmd = cmdForwardWord; - break; - case 89: // Y - cmd = cmdRotate; - break; + // set up key capture + _element.onkeydown = function(e) { + e = e || window.event; + + // return as unhandled if we're not active or the key is just a modifier key + if(!_active || e.keyCode == 16 || e.keyCode == 17 || e.keyCode == 18 || e.keyCode == 91) { + return true; } - } - if(!cmd) { - return true; - } - queue(cmd); - e.preventDefault(); - e.stopPropagation(); - e.cancelBubble = true; - return false; - }; - root.onkeypress = function(e) { - if(!_active) { - return true; - } - var key = getKeyInfo(e); - if(key.code == 0 || e.defaultPrevented) { - return false; - } - queue(function cmdKeyPress() { - if(_inSearch) { - addSearchText(key.character); - } else { - addText(key.character); + // check for some special first keys, regardless of modifiers + _console.log("key: " + e.keyCode); + var cmd = _keyMap['default'][e.keyCode]; + // intercept ctrl- and meta- sequences (may override the non-modifier cmd captured above + var mod; + if(e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey) { + mod = _keyMap.control[e.keyCode]; + if(mod) { + cmd = mod; + } + } else if((e.altKey || e.metaKey) && !e.ctrlKey && !e.shiftKey) { + mod = _keyMap.meta[e.keyCode]; + if(mod) { + cmd = mod; + } } - }); - e.preventDefault(); - e.stopPropagation(); - e.cancelBubble = true; - return false; - }; + if(!cmd) { + return true; + } + queue(cmd); + e.preventDefault(); + e.stopPropagation(); + e.cancelBubble = true; + return false; + }; + _element.onkeypress = function(e) { + if(!_active) { + return true; + } + var key = getKeyInfo(e); + if(key.code == 0 || e.defaultPrevented || e.metaKey || e.altKey || e.ctrlKey) { + return false; + } + queue(function cmdKeyPress() { + if(_inSearch) { + addSearchText(key.character); + } else { + addText(key.character); + } + }); + e.preventDefault(); + e.stopPropagation(); + e.cancelBubble = true; + return false; + }; + } + if(_boundToElement) { + self.attach(_element); + } else { + subscribeToKeys(); + } return self; }; })(this); - - diff --git a/js/shell.js b/js/shell.js index 6b36c29..ba27216 100644 --- a/js/shell.js +++ b/js/shell.js @@ -1,5 +1,5 @@ /* ------------------------------------------------------------------------* - * Copyright 2013 Arne F. Claassen + * Copyright 2013-2014 Arne F. Claassen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -86,7 +86,7 @@ var Josh = Josh || {}; history: _.template("
<% _.each(items, function(cmd, i) { %>
<%- i %> <%- cmd %>
<% }); %>
"), help: _.template("
Commands:
<% _.each(commands, function(cmd) { %>
 <%- cmd %>
<% }); %>
"), bad_command: _.template('
Unrecognized command: <%=cmd%>
'), - input_cmd: _.template('
 
'), + input_cmd: _.template('
 
'), input_search: _.template('
(reverse-i-search)`\': 
'), suggest: _.template("
<% _.each(suggestions, function(suggestion) { %>
<%- suggestion %>
<% }); %>
") }, @@ -147,7 +147,7 @@ var Josh = Josh || {}; var left = _.escape(text.substr(0, cursorIdx)).replace(/ /g, ' '); var cursor = text.substr(cursorIdx, 1); var right = _.escape(text.substr(cursorIdx + 1)).replace(/ /g, ' '); - $(id(_input_id) + ' .prompt').text(_prompt); + $(id(_input_id) + ' .prompt').html(_prompt); $(id(_input_id) + ' .input .left').html(left); if(!cursor) { $(id(_input_id) + ' .input .cursor').html(' ').css('textDecoration', 'underline');