2015-06-01 03:18:23 +00:00
|
|
|
|
/* jslint node: true */
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
var View = require('./view.js').View;
|
|
|
|
|
var miscUtil = require('./misc_util.js');
|
|
|
|
|
var strUtil = require('./string_util.js');
|
|
|
|
|
var ansi = require('./ansi_term.js');
|
2015-06-01 23:10:27 +00:00
|
|
|
|
//var TextBuffer = require('./text_buffer.js').TextBuffer;
|
2015-06-01 03:18:23 +00:00
|
|
|
|
|
|
|
|
|
var assert = require('assert');
|
|
|
|
|
var _ = require('lodash');
|
|
|
|
|
|
2015-06-03 04:18:00 +00:00
|
|
|
|
var SPECIAL_KEY_MAP_DEFAULT = {
|
|
|
|
|
lineFeed : [ 'enter' ],
|
|
|
|
|
exit : [ 'esc' ],
|
|
|
|
|
backspace : [ 'backspace' ],
|
|
|
|
|
del : [ 'del' ],
|
|
|
|
|
tabs : [ 'tab' ],
|
|
|
|
|
up : [ 'up arrow' ],
|
|
|
|
|
down : [ 'down arrow' ],
|
|
|
|
|
end : [ 'end' ],
|
|
|
|
|
home : [ 'home' ],
|
|
|
|
|
left : [ 'left arrow' ],
|
|
|
|
|
right : [ 'right arrow' ],
|
|
|
|
|
clearLine : [ 'end of medium' ],
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-01 03:18:23 +00:00
|
|
|
|
exports.MultiLineEditTextView2 = MultiLineEditTextView2;
|
|
|
|
|
|
|
|
|
|
function MultiLineEditTextView2(options) {
|
|
|
|
|
if(!_.isBoolean(options.acceptsFocus)) {
|
|
|
|
|
options.acceptsFocus = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!_.isBoolean(this.acceptsInput)) {
|
|
|
|
|
options.acceptsInput = true;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-03 04:18:00 +00:00
|
|
|
|
if(!_.isObject(options.specialKeyMap)) {
|
|
|
|
|
options.specialKeyMap = SPECIAL_KEY_MAP_DEFAULT;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-01 03:18:23 +00:00
|
|
|
|
View.call(this, options);
|
|
|
|
|
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
|
|
//
|
2015-06-01 23:10:27 +00:00
|
|
|
|
// ANSI seems to want tabs to default to 8 characters. See the following:
|
2015-06-01 03:18:23 +00:00
|
|
|
|
// * http://www.ansi-bbs.org/ansi-bbs2/control_chars/
|
|
|
|
|
// * http://www.bbsdocumentary.com/library/PROGRAMS/GRAPHICS/ANSI/bansi.txt
|
|
|
|
|
//
|
|
|
|
|
this.tabWidth = _.isNumber(options.tabWidth) ? options.tabWidth : 8;
|
|
|
|
|
|
2015-06-02 05:00:54 +00:00
|
|
|
|
this.textLines = [];
|
|
|
|
|
this.topVisibleIndex = 0;
|
2015-06-03 04:18:00 +00:00
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// cursorPos represents zero-based row, col positions
|
|
|
|
|
// within the editor itself
|
|
|
|
|
//
|
2015-06-02 22:36:55 +00:00
|
|
|
|
this.cursorPos = { col : 0, row : 0 };
|
2015-06-01 03:18:23 +00:00
|
|
|
|
|
2015-06-03 04:18:00 +00:00
|
|
|
|
this.getTextLinesIndex = function(row) {
|
|
|
|
|
var index = self.topVisibleIndex + self.cursorPos.row;
|
|
|
|
|
return index;
|
|
|
|
|
};
|
|
|
|
|
|
2015-06-01 03:18:23 +00:00
|
|
|
|
this.redrawVisibleArea = function() {
|
2015-06-02 05:00:54 +00:00
|
|
|
|
assert(self.topVisibleIndex < self.textLines.length);
|
2015-06-01 03:18:23 +00:00
|
|
|
|
|
2015-06-02 05:00:54 +00:00
|
|
|
|
self.client.term.write(self.getSGR());
|
2015-06-02 22:36:55 +00:00
|
|
|
|
self.client.term.write(ansi.hideCursor());
|
2015-06-01 23:10:27 +00:00
|
|
|
|
|
2015-06-02 05:00:54 +00:00
|
|
|
|
var bottomIndex = Math.min(self.topVisibleIndex + self.dimens.height, self.textLines.length);
|
|
|
|
|
var row = self.position.row;
|
|
|
|
|
for(var i = self.topVisibleIndex; i < bottomIndex; i++) {
|
|
|
|
|
self.client.term.write(ansi.goto(row, this.position.col));
|
2015-06-02 22:36:55 +00:00
|
|
|
|
//self.client.term.write(self.getRenderText(self.textLines[i].text));
|
|
|
|
|
self.client.term.write(self.getRenderText(i));
|
2015-06-02 05:00:54 +00:00
|
|
|
|
++row;
|
|
|
|
|
}
|
2015-06-02 22:36:55 +00:00
|
|
|
|
self.client.term.write(ansi.showCursor());
|
2015-06-01 23:10:27 +00:00
|
|
|
|
};
|
2015-06-02 05:00:54 +00:00
|
|
|
|
|
2015-06-02 22:36:55 +00:00
|
|
|
|
this.getVisibleText = function(index) {
|
2015-06-03 04:18:00 +00:00
|
|
|
|
index = _.isNumber(index) ? index : self.getTextLinesIndex(self.cursorPos.row);
|
2015-06-02 22:36:55 +00:00
|
|
|
|
return self.textLines[index].text.replace(/\t/g, ' ');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.getRenderText = function(index) {
|
|
|
|
|
var text = self.getVisibleText(index);
|
|
|
|
|
var remain = self.dimens.width - text.length;
|
|
|
|
|
if(remain > 0) {
|
|
|
|
|
text += new Array(remain).join(' ');
|
|
|
|
|
}
|
|
|
|
|
return text;
|
|
|
|
|
};
|
|
|
|
|
|
2015-06-01 23:10:27 +00:00
|
|
|
|
this.expandTab = function(col, expandChar) {
|
|
|
|
|
expandChar = expandChar || ' ';
|
|
|
|
|
var count = self.tabWidth - (col % self.tabWidth);
|
|
|
|
|
return new Array(count).join(expandChar);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.wordWrapSingleLine = function(s, width) {
|
|
|
|
|
//
|
|
|
|
|
// Notes
|
|
|
|
|
// * Sublime Text 3 for example considers spaces after a word
|
|
|
|
|
// part of said word. For example, "word " would be wraped
|
|
|
|
|
// in it's entirity.
|
|
|
|
|
//
|
|
|
|
|
// * Tabs in Sublime Text 3 are also treated as a word, so, e.g.
|
|
|
|
|
// "\t" may resolve to " " and must fit within the space.
|
|
|
|
|
//
|
2015-06-02 05:00:54 +00:00
|
|
|
|
// note: we cannot simply use \s below as it includes \t
|
|
|
|
|
var re = new RegExp(
|
2015-06-02 22:36:55 +00:00
|
|
|
|
'\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');
|
2015-06-01 23:10:27 +00:00
|
|
|
|
var m;
|
|
|
|
|
var wordStart;
|
|
|
|
|
var wrapped = [ '' ];
|
|
|
|
|
var i = 0;
|
|
|
|
|
var word;
|
|
|
|
|
|
|
|
|
|
function addWord() {
|
|
|
|
|
if(wrapped[i].length + word.length > self.dimens.width) {
|
|
|
|
|
wrapped[++i] = word;
|
|
|
|
|
} else {
|
|
|
|
|
wrapped[i] += word;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
wordStart = re.lastIndex + (_.isObject(m) ? m[0].length - 1 : 0);
|
|
|
|
|
m = re.exec(s);
|
|
|
|
|
|
|
|
|
|
if(null !== m) {
|
2015-06-02 05:00:54 +00:00
|
|
|
|
word = s.substring(wordStart, re.lastIndex - 1);
|
2015-06-01 23:10:27 +00:00
|
|
|
|
|
|
|
|
|
switch(m[0].charAt(0)) {
|
|
|
|
|
case ' ' :
|
|
|
|
|
word += m[0];
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case '\t' :
|
|
|
|
|
//
|
|
|
|
|
// Expand tab given position
|
|
|
|
|
//
|
|
|
|
|
word += self.expandTab(wrapped[i].length, '\t');
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addWord();
|
|
|
|
|
}
|
|
|
|
|
} while(0 !== re.lastIndex);
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Remainder
|
|
|
|
|
//
|
|
|
|
|
word = s.substring(wordStart);
|
|
|
|
|
addWord();
|
|
|
|
|
|
|
|
|
|
return wrapped;
|
|
|
|
|
};
|
|
|
|
|
|
2015-06-03 04:18:00 +00:00
|
|
|
|
this.insertText = function(text, index, col) {
|
2015-06-01 23:10:27 +00:00
|
|
|
|
//
|
|
|
|
|
// Perform the following on |text|:
|
|
|
|
|
// * Normalize various line feed formats -> \n
|
|
|
|
|
// * Remove some control characters (e.g. \b)
|
|
|
|
|
// * Word wrap lines such that they fit in the visible workspace.
|
|
|
|
|
// Each actual line will then take 1:n elements in textLines[].
|
|
|
|
|
// * Each tab will be appropriately expanded and take 1:n \t
|
|
|
|
|
// characters. This allows us to know when we're in tab space
|
|
|
|
|
// when doing cursor movement/etc.
|
|
|
|
|
//
|
|
|
|
|
//
|
|
|
|
|
// Try to handle any possible newline that can be fed to us.
|
|
|
|
|
// See http://stackoverflow.com/questions/5034781/js-regex-to-split-by-line
|
|
|
|
|
//
|
2015-06-03 04:18:00 +00:00
|
|
|
|
// :TODO: support index/col insertion point
|
2015-06-02 05:00:54 +00:00
|
|
|
|
|
2015-06-03 04:18:00 +00:00
|
|
|
|
if(_.isNumber(index)) {
|
2015-06-02 05:00:54 +00:00
|
|
|
|
if(_.isNumber(col)) {
|
|
|
|
|
//
|
2015-06-03 04:18:00 +00:00
|
|
|
|
// Modify text to have information from index
|
2015-06-02 05:00:54 +00:00
|
|
|
|
// before and and after column
|
|
|
|
|
//
|
|
|
|
|
// :TODO: Need to clean this string (e.g. collapse tabs)
|
|
|
|
|
text = self.textLines
|
|
|
|
|
|
2015-06-03 04:18:00 +00:00
|
|
|
|
// :TODO: Remove original line @ index
|
2015-06-02 05:00:54 +00:00
|
|
|
|
}
|
|
|
|
|
} else {
|
2015-06-03 04:18:00 +00:00
|
|
|
|
index = self.textLines.length;
|
2015-06-02 05:00:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-06-01 23:10:27 +00:00
|
|
|
|
var tempLines = text
|
|
|
|
|
.replace(/\b/g, '')
|
|
|
|
|
.split(/\r\n|[\n\v\f\r\x85\u2028\u2029]/g);
|
|
|
|
|
|
|
|
|
|
var wrapped;
|
2015-06-02 05:00:54 +00:00
|
|
|
|
|
2015-06-01 23:10:27 +00:00
|
|
|
|
for(var i = 0; i < tempLines.length; ++i) {
|
|
|
|
|
wrapped = self.wordWrapSingleLine(tempLines[i], self.dimens.width);
|
2015-06-02 05:00:54 +00:00
|
|
|
|
|
|
|
|
|
for(var j = 0; j < wrapped.length - 1; ++j) {
|
2015-06-03 04:18:00 +00:00
|
|
|
|
self.textLines.splice(index++, 0, { text : wrapped[j] } );
|
2015-06-02 05:00:54 +00:00
|
|
|
|
}
|
2015-06-03 04:18:00 +00:00
|
|
|
|
self.textLines.splice(index++, 0, { text : wrapped[wrapped.length - 1], eol : true });
|
2015-06-01 23:10:27 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
2015-06-02 05:00:54 +00:00
|
|
|
|
|
2015-06-02 22:36:55 +00:00
|
|
|
|
this.getAbsolutePosition = function(row, col) {
|
|
|
|
|
return { row : self.position.row + self.cursorPos.row, col : self.position.col + self.cursorPos.col };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.moveClientCusorToCursorPos = function() {
|
|
|
|
|
var absPos = self.getAbsolutePosition(self.cursorPos.row, self.cursorPos.col);
|
|
|
|
|
self.client.term.write(ansi.goto(absPos.row, absPos.col));
|
|
|
|
|
};
|
|
|
|
|
|
2015-06-02 05:00:54 +00:00
|
|
|
|
this.cursorUp = function() {
|
2015-06-03 04:18:00 +00:00
|
|
|
|
if(self.cursorPos.row > 0) {
|
|
|
|
|
self.cursorPos.row--;
|
|
|
|
|
self.client.term.write(ansi.up());
|
|
|
|
|
|
|
|
|
|
// :TODO: self.makeTabAdjustment('up')
|
|
|
|
|
} else if(self.topVisibleIndex > 0) {
|
|
|
|
|
|
|
|
|
|
}
|
2015-06-02 05:00:54 +00:00
|
|
|
|
};
|
2015-06-02 22:36:55 +00:00
|
|
|
|
|
|
|
|
|
this.cursorDown = function() {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.cursorLeft = function() {
|
2015-06-03 04:18:00 +00:00
|
|
|
|
if(self.cursorPos.col > 0) {
|
|
|
|
|
self.cursorPos.col--;
|
|
|
|
|
self.client.term.write(ansi.left());
|
|
|
|
|
// :TODO: handle landing on a tab
|
|
|
|
|
} else {
|
|
|
|
|
// :TODO: goto previous line if possible and scroll if needed
|
|
|
|
|
}
|
2015-06-02 22:36:55 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.cursorRight = function() {
|
|
|
|
|
var colEnd = self.getVisibleText(self.cursorPos.row).length;
|
|
|
|
|
if(self.cursorPos.col < colEnd) {
|
|
|
|
|
self.cursorPos.col++;
|
|
|
|
|
self.client.term.write(ansi.right());
|
2015-06-03 04:18:00 +00:00
|
|
|
|
|
|
|
|
|
// :TODO: handle landing on a tab
|
2015-06-02 22:36:55 +00:00
|
|
|
|
} else {
|
|
|
|
|
// :TODO: goto next line; scroll if needed, etc.
|
2015-06-03 04:18:00 +00:00
|
|
|
|
|
2015-06-02 22:36:55 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.cursorHome = function() {
|
2015-06-03 04:18:00 +00:00
|
|
|
|
var firstNonWhitespace = self.getVisibleText().search(/\S/);
|
|
|
|
|
if(-1 !== firstNonWhitespace) {
|
|
|
|
|
self.cursorPos.col = firstNonWhitespace;
|
|
|
|
|
} else {
|
|
|
|
|
self.cursorPos.col = 0;
|
|
|
|
|
}
|
|
|
|
|
console.log(self.getVisibleText())
|
|
|
|
|
self.moveClientCusorToCursorPos();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.cursorEnd = function() {
|
|
|
|
|
self.cursorPos.col = Math.max(self.getVisibleText().length - 1, 0);
|
|
|
|
|
self.moveClientCusorToCursorPos();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.cursorStartOfText = function() {
|
2015-06-02 22:36:55 +00:00
|
|
|
|
self.topVisibleIndex = 0;
|
|
|
|
|
self.cursorPos = { row : 0, col : 0 };
|
|
|
|
|
|
|
|
|
|
self.redraw();
|
|
|
|
|
self.moveClientCusorToCursorPos();
|
|
|
|
|
};
|
|
|
|
|
|
2015-06-03 04:18:00 +00:00
|
|
|
|
this.cursorEndOfText = function() {
|
2015-06-02 22:36:55 +00:00
|
|
|
|
self.topVisibleIndex = Math.max(self.textLines.length - self.dimens.height, 0);
|
2015-06-03 04:18:00 +00:00
|
|
|
|
self.cursorPos.row = (self.textLines.length - self.topVisibleIndex) - 1;
|
|
|
|
|
self.cursorPos.col = self.getVisibleText().length; // uses row set above
|
2015-06-02 22:36:55 +00:00
|
|
|
|
|
|
|
|
|
self.redraw();
|
|
|
|
|
self.moveClientCusorToCursorPos();
|
|
|
|
|
};
|
2015-06-03 04:18:00 +00:00
|
|
|
|
|
2015-06-01 03:18:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
require('util').inherits(MultiLineEditTextView2, View);
|
|
|
|
|
|
|
|
|
|
MultiLineEditTextView2.prototype.redraw = function() {
|
|
|
|
|
MultiLineEditTextView2.super_.prototype.redraw.call(this);
|
|
|
|
|
|
|
|
|
|
this.redrawVisibleArea();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MultiLineEditTextView2.prototype.setText = function(text) {
|
2015-06-01 23:10:27 +00:00
|
|
|
|
this.textLines = [];
|
2015-06-02 22:36:55 +00:00
|
|
|
|
//text = 'Supper fluffy bunny test thing\nHello, everyone!\n\nStuff and thing and what nots\r\na\tb\tc\td\te';
|
2015-06-02 05:00:54 +00:00
|
|
|
|
//text = "You. Now \ttomorrow \tthere'll \tbe \ttwo \tsessions, \tof\t course, morning and afternoon.";
|
|
|
|
|
this.insertText(text);//, 0, 0);
|
2015-06-01 23:10:27 +00:00
|
|
|
|
|
|
|
|
|
console.log(this.textLines)
|
2015-06-02 05:00:54 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MultiLineEditTextView2.prototype.onSpecialKeyPress = function(keyName) {
|
|
|
|
|
|
|
|
|
|
var self = this;
|
|
|
|
|
|
2015-06-03 04:18:00 +00:00
|
|
|
|
console.log(keyName);
|
|
|
|
|
|
|
|
|
|
// :TODO: Determine CTRL-* keys for various things
|
|
|
|
|
// See http://www.bbsdocumentary.com/library/PROGRAMS/GRAPHICS/ANSI/bansi.txt
|
|
|
|
|
// http://wiki.synchro.net/howto:editor:slyedit#edit_mode
|
|
|
|
|
// http://sublime-text-unofficial-documentation.readthedocs.org/en/latest/reference/keyboard_shortcuts_win.html
|
|
|
|
|
|
|
|
|
|
/* Mystic
|
|
|
|
|
[^B] Reformat Paragraph [^O] Show this help file
|
|
|
|
|
[^I] Insert tab space [^Q] Enter quote mode
|
|
|
|
|
[^K] Cut current line of text [^V] Toggle insert/overwrite
|
|
|
|
|
[^U] Paste previously cut text [^Y] Delete current line
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BASIC MOVEMENT COMMANDS
|
|
|
|
|
|
|
|
|
|
UP/^E LEFT/^S PGUP/^R HOME/^F
|
|
|
|
|
DOWN/^X RIGHT/^D PGDN/^C END/^G
|
|
|
|
|
*/
|
|
|
|
|
|
2015-06-02 22:36:55 +00:00
|
|
|
|
[ 'up', 'down', 'left', 'right', 'home', 'end' ].forEach(function key(arrowKey) {
|
2015-06-02 05:00:54 +00:00
|
|
|
|
if(self.isSpecialKeyMapped(arrowKey, keyName)) {
|
|
|
|
|
self['cursor' + arrowKey.substring(0,1).toUpperCase() + arrowKey.substring(1)]();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2015-06-03 04:18:00 +00:00
|
|
|
|
// TEMP HACK FOR TESTING -----
|
|
|
|
|
if(self.isSpecialKeyMapped('lineFeed', keyName)) {
|
|
|
|
|
//self.cursorStartOfText();
|
|
|
|
|
self.cursorEndOfText();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//MultiLineEditTextView2.super_.prototype.onSpecialKeyPress.call(this, keyName);
|
2015-06-02 22:36:55 +00:00
|
|
|
|
};
|