diff --git a/core/button_view.js b/core/button_view.js index 9f68d6cc..dec1cd10 100644 --- a/core/button_view.js +++ b/core/button_view.js @@ -19,14 +19,12 @@ function ButtonView(options) { util.inherits(ButtonView, TextView); -ButtonView.prototype.onKeyPress = function(key, isSpecial) { - ButtonView.super_.prototype.onKeyPress.call(this, key, isSpecial); - - // allow spacebar to 'click' buttons - // :TODO: need to check configurable mapping here - if(' ' === key) { +ButtonView.prototype.onKeyPress = function(ch, key) { + if(' ' === ch) { this.emit('action', 'accept'); } + + ButtonView.super_.prototype.onKeyPress.call(this, ch, key); }; ButtonView.prototype.getData = function() { diff --git a/core/client.js b/core/client.js index b99593b9..fe906fbc 100644 --- a/core/client.js +++ b/core/client.js @@ -116,7 +116,7 @@ var RE_META_KEYCODE = new RegExp('^' + RE_META_KEYCODE_ANYWHERE.source + '$' var RE_FUNCTION_KEYCODE_ANYWHERE = new RegExp('(?:\u001b+)(O|N|\\[|\\[\\[)(?:' + [ '(\\d+)(?:;(\\d+))?([~^$])', '(?:M([@ #!a`])(.)(.))', // mouse stuff - '(?:1;)?(\\d+)?([a-zA-Z])' + '(?:1;)?(\\d+)?([a-zA-Z@])' ].join('|') + ')'); var RE_FUNCTION_KEYCODE = new RegExp('^' + RE_FUNCTION_KEYCODE_ANYWHERE.source); @@ -234,9 +234,6 @@ function Client(input, output) { '[8~' : { name : 'end' }, // rxvt with modifiers - - - /* rxvt keys with modifiers */ '[a' : { name : 'up arrow', shift : true }, '[b' : { name : 'down arrow', shift : true }, '[c' : { name : 'right arrow', shift : true }, @@ -263,8 +260,9 @@ function Client(input, output) { '[7^' : { name : 'home', ctrl : true }, '[8^' : { name : 'end', ctrl : true }, - // SyncTERM + // SyncTERM / EtherTerm '[K' : { name : 'end' }, + '[@' : { name : 'insert' }, // other '[Z' : { name : 'tab', shift : true }, @@ -294,8 +292,6 @@ function Client(input, output) { buf = buf.concat(data.split('')); // remainder - console.log(buf) - buf.forEach(function bufPart(s) { var key = { seq : s, @@ -382,88 +378,13 @@ function Client(input, output) { } if(key || ch) { + Log.trace( { key : key, ch : ch }, 'User keyboard input'); + self.emit('key press', ch, key); } }); }); - // - // Peek at |data| and emit for any specialized handling - // such as ANSI control codes or user/keyboard input - // - self.on('dataXX', function onData(data) { - var len = data.length; - var c; - var name; - - if(1 === len) { - c = data[0]; - - if(0x00 === c) { - // ignore single NUL - return; - } - - name = ANSI_KEY_NAME_MAP[c]; - if(name) { - self.emit('special key', name); - self.emit('key press', data, true); - } else { - self.emit('key press', data, false); - } - } - - if(0x1b !== data[0]) { - return; - } - - if(3 === len) { - if(0x5b === data[1]) { - name = ANSI_KEY_CSI_NAME_MAP[data[2]]; - if(name) { - self.emit('special key', name); - self.emit('key press', data, true); - } - } else if(0x4f === data[1]) { - name = ANSI_F_KEY_NAME_MAP_1[data[2]]; - if(name) { - self.emit('special key', name); - self.emit('key press', data, true); - } - } - } else if(5 === len && 0x5b === data[1] && 0x7e === data[4]) { - var code = parseInt(data.slice(2,4), 10); - - if(!isNaN(code)) { - name = ANSI_F_KEY_NAME_MAP_2[code]; - if(name) { - self.emit('special key', name); - self.emit('key press', data, true); - } - } - } else if(len > 3) { - // :TODO: Implement various responses to DSR's & such - // See e.g. http://www.vt100.net/docs/vt100-ug/chapter3.html - var dsrResponseRe = /\u001b\[([0-9\;]+)([R])/g; - var match; - var args; - do { - match = dsrResponseRe.exec(data); - - if(null !== match) { - switch(match[2]) { - case 'R' : - args = getIntArgArray(match[1].split(';')); - if(2 === args.length) { - self.emit('cursor position report', args); - } - break; - } - } - } while(0 !== dsrResponseRe.lastIndex); - } - }); - self.detachCurrentMenuModule = function() { if(self.currentMenuModule) { self.currentMenuModule.leave(); diff --git a/core/multi_line_edit_text_view2.js b/core/multi_line_edit_text_view2.js index 1502233f..dd99d4dc 100644 --- a/core/multi_line_edit_text_view2.js +++ b/core/multi_line_edit_text_view2.js @@ -89,6 +89,13 @@ function MultiLineEditTextView2(options) { return index; }; + this.getRemainingLinesBelowRow = function(row) { + if(!_.isNumber(row)) { + row = self.cursorPos.row; + } + return self.textLines.length - (self.topVisibleIndex + row) - 1; + }; + this.redrawVisibleArea = function() { assert(self.topVisibleIndex < self.textLines.length); @@ -137,10 +144,16 @@ function MultiLineEditTextView2(options) { self.textLines[index].text, col, c); }; + this.getRemainingTabWidth = function(col) { + if(!_.isNumber(col)) { + col = self.cursorPos.col; + } + return self.tabWidth - (col % self.tabWidth); + }; + this.expandTab = function(col, expandChar) { expandChar = expandChar || ' '; - var count = self.tabWidth - (col % self.tabWidth); - return new Array(count).join(expandChar); + return new Array(self.getRemainingTabWidth(col)).join(expandChar); }; this.wordWrapSingleLine = function(s, width) { @@ -153,7 +166,10 @@ function MultiLineEditTextView2(options) { // * Tabs in Sublime Text 3 are also treated as a word, so, e.g. // "\t" may resolve to " " and must fit within the space. // - // note: we cannot simply use \s below as it includes \t + // * If a word is ultimately too long to fit, break it up until it does. + // + // RegExp below is JavaScript '\s' minus the '\t' + // var re = new RegExp( '\t|[ \f\n\r\v​\u00a0\u1680​\u180e\u2000​\u2001\u2002​\u2003\u2004\u2005\u2006​' + '\u2007\u2008​\u2009\u200a​\u2028\u2029​\u202f\u205f​\u3000]+', 'g'); @@ -164,11 +180,13 @@ function MultiLineEditTextView2(options) { var word; function addWord() { - if(wrapped[i].length + word.length > self.dimens.width) { - wrapped[++i] = word; - } else { - wrapped[i] += word; - } + word.match(new RegExp('.{0,' + self.dimens.width + '}', 'g')).forEach(function wrd(w) { + if(wrapped[i].length + w.length > self.dimens.width) { + wrapped[++i] = w; + } else { + wrapped[i] += w; + } + }); } do { @@ -317,7 +335,7 @@ function MultiLineEditTextView2(options) { self.client.term.write(ansi.left()); // :TODO: handle landing on a tab } else { - // :TODO: goto previous line if possible and scroll if needed + self.cursorEndOfPreviousLine(); } }; @@ -327,10 +345,9 @@ function MultiLineEditTextView2(options) { self.cursorPos.col++; self.client.term.write(ansi.right()); - // :TODO: handle landing on a tab + self.adjustCursorToNextTab('right'); } else { - // :TODO: goto next line; scroll if needed, etc. - + self.cursorBeginOfNextLine(); } }; @@ -362,18 +379,40 @@ function MultiLineEditTextView2(options) { }; - this.adjustCursorIfPastEndOfLine = function(alwaysUpdateCursor) { + this.adjustCursorIfPastEndOfLine = function(forceUpdate) { var eolColumn = self.getTextEndOfLineColumn(); if(self.cursorPos.col > eolColumn) { self.cursorPos.col = eolColumn; - alwaysUpdateCursor = true; + forceUpdate = true; } - if(alwaysUpdateCursor) { + if(forceUpdate) { self.moveClientCusorToCursorPos(); } }; + this.adjustCursorToNearestTab = function() { + // + // When pressing up or down and landing on a tab, jump + // to the nearest tabstop -- right or left. + // + + }; + + this.adjustCursorToNextTab = function(direction) { + if('\t' === self.getText()[self.cursorPos.col]) { + // + // When pressing right or left, jump to the next + // tabstop in that direction. + // + if('right' === direction) { + var move = self.getRemainingTabWidth() - 1; + self.cursorPos.col += move; + self.client.term.write(ansi.right(move)); + } + } + }; + this.cursorStartOfDocument = function() { self.topVisibleIndex = 0; self.cursorPos = { row : 0, col : 0 }; @@ -391,12 +430,38 @@ function MultiLineEditTextView2(options) { self.moveClientCusorToCursorPos(); }; + this.cursorBeginOfNextLine = function() { + // e.g. when scrolling right past eol + var linesBelow = self.getRemainingLinesBelowRow(); + + if(linesBelow > 0) { + var lastVisibleRow = Math.min(self.dimens.height, self.textLines.length) - 1; + if(self.cursorPos.row < lastVisibleRow) { + self.cursorPos.row++; + } else { + self.scrollDocumentUp(); + } + self.keyPressHome(); // same as pressing 'home' + } + }; + + this.cursorEndOfPreviousLine = function() { + if(self.topVisibleIndex > 0) { + if(self.cursorPos.row > 0) { + self.cursorPos.row--; + } else { + self.scrollDocumentDown(); + } + self.keyPressEnd(); // same as pressing 'end' + } + }; + this.scrollDocumentUp = function() { // // Note: We scroll *up* when the cursor goes *down* beyond // the visible area! // - var linesBelow = self.textLines.length - (self.topVisibleIndex + self.cursorPos.row) - 1; + var linesBelow = self.getRemainingLinesBelowRow(); if(linesBelow > 0) { self.topVisibleIndex++; self.redraw(); @@ -426,8 +491,7 @@ MultiLineEditTextView2.prototype.redraw = function() { MultiLineEditTextView2.prototype.setText = function(text) { this.textLines = []; - //text = 'Supper fluffy bunny test thing\nHello, everyone!\n\nStuff and thing and what nots\r\na\tb\tc\td\te'; - //text = "You. Now \ttomorrow \tthere'll \tbe \ttwo \tsessions, \tof\t course, morning and afternoon."; + //text = "Tab:\r\n\tA\tB\tC\tD\tE\tF\tG\tH\tI\tJ\tK\tL\tM\tN\tO\tP\nA reeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeally long word!!!"; this.insertText(text);//, 0, 0); this.cursorEndOfDocument(); @@ -462,37 +526,3 @@ MultiLineEditTextView2.prototype.onKeyPress = function(ch, key) { MultiLineEditTextView2.super_.prototype.onKeyPress.call(this, ch, key); } }; - -/* -MultiLineEditTextView2.prototype.onKeyPress = function(key, isSpecial) { - if(isSpecial) { - return; - } - - this.keyPressCharacter(key); - - MultiLineEditTextView2.super_.prototype.onKeyPress.call(this, key, isSpecial); -}; - - - -MultiLineEditTextView2.prototype.onSpecialKeyPress = function(keyName) { - - var self = this; - - console.log(keyName); - - - var handled = false; - HANDLED_SPECIAL_KEYS.forEach(function key(arrowKey) { - if(self.isSpecialKeyMapped(arrowKey, keyName)) { - self[_.camelCase('keyPress ' + arrowKey)](); - handled = true; - } - }); - - if(!handled) { - MultiLineEditTextView2.super_.prototype.onSpecialKeyPress.call(this, keyName); - } -}; -*/ \ No newline at end of file diff --git a/core/view.js b/core/view.js index 5c1b6c05..6937d5b7 100644 --- a/core/view.js +++ b/core/view.js @@ -5,6 +5,7 @@ var events = require('events'); var util = require('util'); var assert = require('assert'); var ansi = require('./ansi_term.js'); + var _ = require('lodash'); exports.View = View; @@ -79,7 +80,7 @@ function View(options) { } this.isSpecialKeyMapped = function(keySet, keyName) { - return this.specialKeyMap[keySet].indexOf(keyName) > -1; + return _.has(this.specialKeyMap, keySet) && this.specialKeyMap[keySet].indexOf(keyName) > -1; }; this.getANSIColor = function(color) { @@ -177,25 +178,6 @@ View.prototype.setFocus = function(focused) { this.hasFocus = focused; this.restoreCursor(); }; -/* -View.prototype.onKeyPress = function(key, isSpecial) { - assert(this.hasFocus, 'View does not have focus'); - assert(this.acceptsInput, 'View does not accept input'); - assert(1 === key.length); -}; - -View.prototype.onSpecialKeyPress = function(keyName) { - assert(this.hasFocus, 'View does not have focus'); - assert(this.acceptsInput, 'View does not accept input'); - assert(this.specialKeyMap, 'No special key map defined'); - - if(this.isSpecialKeyMapped('accept', keyName)) { - this.emit('action', 'accept'); - } else if(this.isSpecialKeyMapped('next', keyName)) { - this.emit('action', 'next'); - } -}; -*/ View.prototype.onKeyPress = function(ch, key) { assert(this.hasFocus, 'View does not have focus'); diff --git a/core/view_controller.js b/core/view_controller.js index 19c4bdb6..5a2f25b6 100644 --- a/core/view_controller.js +++ b/core/view_controller.js @@ -40,9 +40,6 @@ function ViewController(options) { // Process key presses treating form submit mapped // keys special. Everything else is forwarded on to // the focused View, if any. // - - console.log('ch=' + ch + ' / ' + JSON.stringify(key)); - if(key) { var submitViewId = self.submitKeyMap[key.name]; if(submitViewId) { @@ -57,32 +54,6 @@ function ViewController(options) { } }; - /* - this.clientKeyPressHandler = function(key, isSpecial) { - if(isSpecial) { - return; - } - - if(self.focusedView && self.focusedView.acceptsInput) { - key = 'string' === typeof key ? key : key.toString(); - self.focusedView.onKeyPress(key, isSpecial); - } - }; - - this.clientSpecialKeyHandler = function(keyName) { - - var submitViewId = self.submitKeyMap[keyName]; - if(submitViewId) { - self.switchFocus(submitViewId); - self.submitForm(); - } else { - if(self.focusedView && self.focusedView.acceptsInput) { - self.focusedView.onSpecialKeyPress(keyName); - } - } - }; - */ - this.viewActionListener = function(action) { switch(action) { case 'next' : diff --git a/mods/menu.json b/mods/menu.json index 4669657e..cdf8b02e 100644 --- a/mods/menu.json +++ b/mods/menu.json @@ -162,7 +162,7 @@ "text" : "Apply" }, "BT13" : { - "submit" : [ "esc" ], + "submit" : [ "escape" ], "text" : "Cancel" } }, @@ -282,7 +282,7 @@ "BT5" : { "width" : 8, "text" : "< Back", - "submit" : [ "esc" ] + "submit" : [ "escape" ] } }, "submit" : { @@ -317,7 +317,7 @@ }, "BT8" : { "text" : "< Back", - "submit" : [ "esc" ] + "submit" : [ "escape" ] } }, "submit" : { @@ -346,7 +346,7 @@ }, "BT5" : { "text" : "< Back", - "submit" : [ "esc" ] + "submit" : [ "escape" ] } }, "submit" : { @@ -376,7 +376,7 @@ }, "BT5" : { "text" : "< Back", - "submit" : [ "esc" ] + "submit" : [ "escape" ] } }, "submit" : { @@ -459,7 +459,7 @@ }, "ET5" : { "password" : true, - "submit" : [ "esc" ], + "submit" : [ "escape" ], "fillChar" : "#" }, "TM6" : {