* Some mostly placeholder work for @art, etc. in View properties (e.g. menu.json)
* Lots of work on MultiLineEditTextView2. WIP text insertion, retrieval, etc. * Tabs working good at a basic level
This commit is contained in:
parent
feab2e0233
commit
832442288e
|
@ -6,9 +6,10 @@ var Config = require('./config.js').config;
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
|
|
||||||
exports.parseAsset = parseAsset;
|
exports.parseAsset = parseAsset;
|
||||||
exports.getArtAsset = getArtAsset;
|
exports.getArtAsset = getArtAsset;
|
||||||
exports.resolveConfigAsset = resolveConfigAsset;
|
exports.resolveConfigAsset = resolveConfigAsset;
|
||||||
|
exports.getViewPropertyAsset = getViewPropertyAsset;
|
||||||
|
|
||||||
var ALL_ASSETS = [
|
var ALL_ASSETS = [
|
||||||
'art',
|
'art',
|
||||||
|
@ -74,3 +75,11 @@ function resolveConfigAsset(from) {
|
||||||
return from;
|
return from;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getViewPropertyAsset(src) {
|
||||||
|
if(!_.isString(src) || '@' !== src.charAt(0)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseAsset(src);
|
||||||
|
};
|
||||||
|
|
|
@ -28,6 +28,25 @@ var _ = require('lodash');
|
||||||
DOWN/^X RIGHT/^D PGDN/^C END/^G
|
DOWN/^X RIGHT/^D PGDN/^C END/^G
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//
|
||||||
|
// Some other interesting implementations, resources, etc.
|
||||||
|
//
|
||||||
|
// Editors - BBS
|
||||||
|
// * https://github.com/M-griffin/Enthral/blob/master/src/msg_fse.cpp
|
||||||
|
//
|
||||||
|
// Editors - Other
|
||||||
|
// * http://joe-editor.sourceforge.net/
|
||||||
|
// * http://www.jbox.dk/downloads/edit.c
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// To-Do
|
||||||
|
//
|
||||||
|
// * Word wrap from pos to next { eol : true } when inserting text
|
||||||
|
// * Page up/down just divide by and set top index
|
||||||
|
// * Index pos % for emit scroll events
|
||||||
|
// *
|
||||||
|
|
||||||
var SPECIAL_KEY_MAP_DEFAULT = {
|
var SPECIAL_KEY_MAP_DEFAULT = {
|
||||||
lineFeed : [ 'return' ],
|
lineFeed : [ 'return' ],
|
||||||
exit : [ 'esc' ],
|
exit : [ 'esc' ],
|
||||||
|
@ -43,6 +62,7 @@ var SPECIAL_KEY_MAP_DEFAULT = {
|
||||||
clearLine : [ 'ctrl + y' ],
|
clearLine : [ 'ctrl + y' ],
|
||||||
pageUp : [ 'page up' ],
|
pageUp : [ 'page up' ],
|
||||||
pageDown : [ 'page down' ],
|
pageDown : [ 'page down' ],
|
||||||
|
insert : [ 'insert', 'ctrl + v' ],
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.MultiLineEditTextView2 = MultiLineEditTextView2;
|
exports.MultiLineEditTextView2 = MultiLineEditTextView2;
|
||||||
|
@ -69,7 +89,9 @@ function MultiLineEditTextView2(options) {
|
||||||
// * http://www.ansi-bbs.org/ansi-bbs2/control_chars/
|
// * http://www.ansi-bbs.org/ansi-bbs2/control_chars/
|
||||||
// * http://www.bbsdocumentary.com/library/PROGRAMS/GRAPHICS/ANSI/bansi.txt
|
// * http://www.bbsdocumentary.com/library/PROGRAMS/GRAPHICS/ANSI/bansi.txt
|
||||||
//
|
//
|
||||||
this.tabWidth = _.isNumber(options.tabWidth) ? options.tabWidth : 8;
|
// This seems overkill though, so let's default to 4 :)
|
||||||
|
//
|
||||||
|
this.tabWidth = _.isNumber(options.tabWidth) ? options.tabWidth : 4;
|
||||||
|
|
||||||
this.textLines = [];
|
this.textLines = [];
|
||||||
this.topVisibleIndex = 0;
|
this.topVisibleIndex = 0;
|
||||||
|
@ -96,8 +118,17 @@ function MultiLineEditTextView2(options) {
|
||||||
return self.textLines.length - (self.topVisibleIndex + row) - 1;
|
return self.textLines.length - (self.topVisibleIndex + row) - 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.getNextEndOfLineIndex = function(startIndex) {
|
||||||
|
for(var i = startIndex; i < self.textLines.length; ++i) {
|
||||||
|
if(self.textLines[i].eol) {
|
||||||
|
return i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i + 1;
|
||||||
|
};
|
||||||
|
|
||||||
this.redrawVisibleArea = function() {
|
this.redrawVisibleArea = function() {
|
||||||
assert(self.topVisibleIndex < self.textLines.length);
|
assert(self.topVisibleIndex <= self.textLines.length);
|
||||||
|
|
||||||
self.client.term.write(self.getSGR());
|
self.client.term.write(self.getSGR());
|
||||||
self.client.term.write(ansi.hideCursor());
|
self.client.term.write(ansi.hideCursor());
|
||||||
|
@ -127,7 +158,7 @@ function MultiLineEditTextView2(options) {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getTextEndOfLineColumn = function(index) {
|
this.getTextEndOfLineColumn = function(index) {
|
||||||
return Math.max(0, self.getText(index).length - 1);
|
return Math.max(0, self.getText(index).length);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getRenderText = function(index) {
|
this.getRenderText = function(index) {
|
||||||
|
@ -139,11 +170,42 @@ function MultiLineEditTextView2(options) {
|
||||||
return text;
|
return text;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.getOutputText = function(startIndex, endIndex) {
|
||||||
|
var lines;
|
||||||
|
if(startIndex === endIndex) {
|
||||||
|
lines = [ self.textLines[startIndex] ];
|
||||||
|
} else {
|
||||||
|
lines = self.textLines.slice(startIndex, endIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Convert lines to contiguous string -- all expanded
|
||||||
|
// tabs put back to single '\t' characters.
|
||||||
|
//
|
||||||
|
var text = '';
|
||||||
|
var re = new RegExp('\\t{' + (self.tabWidth - 1) + '}', 'g');
|
||||||
|
for(var i = 0; i < lines.length; ++i) {
|
||||||
|
text += lines[i].text.replace(re, '\t');
|
||||||
|
if(lines[i].eof) {
|
||||||
|
text += '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
};
|
||||||
|
|
||||||
this.replaceCharacterInText = function(c, index, col) {
|
this.replaceCharacterInText = function(c, index, col) {
|
||||||
self.textLines[index].text = strUtil.replaceAt(
|
self.textLines[index].text = strUtil.replaceAt(
|
||||||
self.textLines[index].text, col, c);
|
self.textLines[index].text, col, c);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.insertCharacterInText = function(c, index, col) {
|
||||||
|
self.textLines[index].text = [
|
||||||
|
self.textLines[index].text.slice(0, col),
|
||||||
|
c,
|
||||||
|
self.textLines[index].text.slice(col)
|
||||||
|
].join('');
|
||||||
|
};
|
||||||
|
|
||||||
this.getRemainingTabWidth = function(col) {
|
this.getRemainingTabWidth = function(col) {
|
||||||
if(!_.isNumber(col)) {
|
if(!_.isNumber(col)) {
|
||||||
col = self.cursorPos.col;
|
col = self.cursorPos.col;
|
||||||
|
@ -170,6 +232,7 @@ function MultiLineEditTextView2(options) {
|
||||||
//
|
//
|
||||||
// RegExp below is JavaScript '\s' minus the '\t'
|
// RegExp below is JavaScript '\s' minus the '\t'
|
||||||
//
|
//
|
||||||
|
console.log(s)
|
||||||
var re = new RegExp(
|
var re = new RegExp(
|
||||||
'\t|[ \f\n\r\v\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006' +
|
'\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');
|
'\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+', 'g');
|
||||||
|
@ -205,7 +268,9 @@ function MultiLineEditTextView2(options) {
|
||||||
//
|
//
|
||||||
// Expand tab given position
|
// Expand tab given position
|
||||||
//
|
//
|
||||||
word += self.expandTab(wrapped[i].length, '\t');
|
// Nice info here: http://c-for-dummies.com/blog/?p=424
|
||||||
|
//
|
||||||
|
word += self.expandTab(wrapped[i].length + word.length, '\t');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,14 +320,14 @@ function MultiLineEditTextView2(options) {
|
||||||
index = self.textLines.length;
|
index = self.textLines.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tempLines = text
|
text = text
|
||||||
.replace(/\b/g, '')
|
.replace(/\b/g, '')
|
||||||
.split(/\r\n|[\n\v\f\r\x85\u2028\u2029]/g);
|
.split(/\r\n|[\n\v\f\r\x85\u2028\u2029]/g);
|
||||||
|
|
||||||
var wrapped;
|
var wrapped;
|
||||||
|
|
||||||
for(var i = 0; i < tempLines.length; ++i) {
|
for(var i = 0; i < text.length; ++i) {
|
||||||
wrapped = self.wordWrapSingleLine(tempLines[i], self.dimens.width);
|
wrapped = self.wordWrapSingleLine(text[i], self.dimens.width);
|
||||||
|
|
||||||
for(var j = 0; j < wrapped.length - 1; ++j) {
|
for(var j = 0; j < wrapped.length - 1; ++j) {
|
||||||
self.textLines.splice(index++, 0, { text : wrapped[j] } );
|
self.textLines.splice(index++, 0, { text : wrapped[j] } );
|
||||||
|
@ -280,23 +345,77 @@ function MultiLineEditTextView2(options) {
|
||||||
self.client.term.write(ansi.goto(absPos.row, absPos.col));
|
self.client.term.write(ansi.goto(absPos.row, absPos.col));
|
||||||
};
|
};
|
||||||
|
|
||||||
this.keyPressCharacter = function(c, row, col) {
|
this.keyPressCharacter = function(c) {
|
||||||
|
var index = self.getTextLinesIndex();
|
||||||
|
|
||||||
var index = self.getTextLinesIndex(row);
|
//
|
||||||
if(!_.isNumber(col)) {
|
// :TODO: stuff that needs to happen
|
||||||
col = self.cursorPos.col;
|
// * Break up into smaller methods
|
||||||
}
|
// * Even in overtype mode, word wrapping must apply if past bounds
|
||||||
|
// * A lot of this can be used for backspacing also
|
||||||
// :TODO: Even in overtypeMode, word wrapping must apply for e.g.
|
// * See how Sublime treats tabs in *non* overtype mode... just overwrite them?
|
||||||
// if a user types past bounds
|
//
|
||||||
|
|
||||||
if(self.overtypeMode) {
|
if(self.overtypeMode) {
|
||||||
// :TODO: special handing for insert over eol mark?
|
// :TODO: special handing for insert over eol mark?
|
||||||
self.replaceCharacterInText(c, index, col);
|
self.replaceCharacterInText(c, index, self.cursorPos.col);
|
||||||
self.cursorPos.col++;
|
self.cursorPos.col++;
|
||||||
self.client.term.write(c);
|
self.client.term.write(c);
|
||||||
} else {
|
} else {
|
||||||
|
self.insertCharacterInText(c, index, self.cursorPos.col);
|
||||||
|
self.cursorPos.col++;
|
||||||
|
|
||||||
|
if(self.getText(index).length > self.dimens.width) {
|
||||||
|
//
|
||||||
|
// We'll need to word wrap and ajust text below up
|
||||||
|
// the next eol. Then, redraw from this point down
|
||||||
|
//
|
||||||
|
// 1) Word wrap current line -> next actual EOL into a
|
||||||
|
// temp array. For this we'll need the output version
|
||||||
|
// of the buffer.
|
||||||
|
//
|
||||||
|
var nextEolIndex = self.getNextEndOfLineIndex(self.getTextLinesIndex());
|
||||||
|
var newLines = self.wordWrapSingleLine(self.getOutputText(index, nextEolIndex));
|
||||||
|
|
||||||
|
//
|
||||||
|
// Replace index -> nextEolIndex with our newly rendered line objects
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Replace existing entries in textLines up to nextEolIndex, then
|
||||||
|
// insert any additional required lines
|
||||||
|
//
|
||||||
|
var i = 0;
|
||||||
|
var j;
|
||||||
|
for(j = index; j < nextEolIndex; ++j) {
|
||||||
|
self.textLines[j].text = newLines[i++];
|
||||||
|
}
|
||||||
|
|
||||||
|
// :TODO: this part isn't working yet:
|
||||||
|
while(i < newLines.length) {
|
||||||
|
self.textLines.splice(j, 0, { text : newLines[i] } );
|
||||||
|
if(newLines.length == i) {
|
||||||
|
self.textLines[j].eof = true;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
++j;
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(newLines)
|
||||||
|
console.log(self.textLines)
|
||||||
|
} else {
|
||||||
|
//
|
||||||
|
// We must only redraw from col -> end of current visible line
|
||||||
|
//
|
||||||
|
var absPos = self.getAbsolutePosition(self.cursorPos.row, self.cursorPos.col);
|
||||||
|
self.client.term.write(
|
||||||
|
ansi.hideCursor() +
|
||||||
|
self.getRenderText(index).slice(self.cursorPos.col - 1) +
|
||||||
|
ansi.goto(absPos.row, absPos.col) +
|
||||||
|
ansi.showCursor()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -379,6 +498,11 @@ function MultiLineEditTextView2(options) {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.keyPressInsert = function() {
|
||||||
|
// :TODO: emit event
|
||||||
|
self.overtypeMode = !self.overtypeMode;
|
||||||
|
};
|
||||||
|
|
||||||
this.adjustCursorIfPastEndOfLine = function(forceUpdate) {
|
this.adjustCursorIfPastEndOfLine = function(forceUpdate) {
|
||||||
var eolColumn = self.getTextEndOfLineColumn();
|
var eolColumn = self.getTextEndOfLineColumn();
|
||||||
if(self.cursorPos.col > eolColumn) {
|
if(self.cursorPos.col > eolColumn) {
|
||||||
|
@ -406,6 +530,10 @@ function MultiLineEditTextView2(options) {
|
||||||
// tabstop in that direction.
|
// tabstop in that direction.
|
||||||
//
|
//
|
||||||
if('right' === direction) {
|
if('right' === direction) {
|
||||||
|
// :TODO: This is not working correctly...
|
||||||
|
// A few observations:
|
||||||
|
// 1) Right/left should probably allow to land on a tab
|
||||||
|
// and only jump once another arrow is hit -- this lets the user edit @ that position
|
||||||
var move = self.getRemainingTabWidth() - 1;
|
var move = self.getRemainingTabWidth() - 1;
|
||||||
self.cursorPos.col += move;
|
self.cursorPos.col += move;
|
||||||
self.client.term.write(ansi.right(move));
|
self.client.term.write(ansi.right(move));
|
||||||
|
@ -446,12 +574,17 @@ function MultiLineEditTextView2(options) {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.cursorEndOfPreviousLine = function() {
|
this.cursorEndOfPreviousLine = function() {
|
||||||
if(self.topVisibleIndex > 0) {
|
// e.g. when scrolling left past start of line
|
||||||
if(self.cursorPos.row > 0) {
|
var moveToEnd;
|
||||||
self.cursorPos.row--;
|
if(self.cursorPos.row > 0) {
|
||||||
} else {
|
self.cursorPos.row--;
|
||||||
self.scrollDocumentDown();
|
moveToEnd = true;
|
||||||
}
|
} else if(self.topVisibleIndex > 0) {
|
||||||
|
self.scrollDocumentDown();
|
||||||
|
moveToEnd = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(moveToEnd) {
|
||||||
self.keyPressEnd(); // same as pressing 'end'
|
self.keyPressEnd(); // same as pressing 'end'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -490,12 +623,15 @@ MultiLineEditTextView2.prototype.redraw = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
MultiLineEditTextView2.prototype.setText = function(text) {
|
MultiLineEditTextView2.prototype.setText = function(text) {
|
||||||
this.textLines = [];
|
this.textLines = [ ];
|
||||||
//text = "Tab:\r\n\tA\tB\tC\tD\tE\tF\tG\tH\tI\tJ\tK\tL\tM\tN\tO\tP\nA reeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeally long word!!!";
|
//text = "Tab:\r\n\tA\tB\tC\tD\tE\tF\tG\r\n reeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeally long word!!!";
|
||||||
|
text = require('fs').readFileSync('/home/nuskooler/Downloads/test_text.txt', { encoding : 'utf-8'});
|
||||||
|
|
||||||
this.insertText(text);//, 0, 0);
|
this.insertText(text);//, 0, 0);
|
||||||
this.cursorEndOfDocument();
|
this.cursorEndOfDocument();
|
||||||
|
|
||||||
this.overtypeMode = true; // :TODO: remove... testing
|
console.log(this.textLines)
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var HANDLED_SPECIAL_KEYS = [
|
var HANDLED_SPECIAL_KEYS = [
|
||||||
|
@ -503,6 +639,7 @@ var HANDLED_SPECIAL_KEYS = [
|
||||||
'home', 'end',
|
'home', 'end',
|
||||||
'pageUp', 'pageDown',
|
'pageUp', 'pageDown',
|
||||||
'lineFeed',
|
'lineFeed',
|
||||||
|
'insert',
|
||||||
];
|
];
|
||||||
|
|
||||||
MultiLineEditTextView2.prototype.onKeyPress = function(ch, key) {
|
MultiLineEditTextView2.prototype.onKeyPress = function(ch, key) {
|
||||||
|
|
|
@ -161,6 +161,35 @@ function ViewController(options) {
|
||||||
|
|
||||||
function setViewProp(propName, setter) {
|
function setViewProp(propName, setter) {
|
||||||
if(conf[propName]) {
|
if(conf[propName]) {
|
||||||
|
var propValue;
|
||||||
|
var propAsset = asset.getViewPropertyAsset(conf[propName]);
|
||||||
|
if(propAsset) {
|
||||||
|
switch(propAsset.type) {
|
||||||
|
case 'config' :
|
||||||
|
propValue = asset.resolveConfigAsset(config[propName]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// :TODO: handle @art (e.g. text : @art ...)
|
||||||
|
|
||||||
|
default :
|
||||||
|
propValue = propValue = conf[propName];
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
propValue = conf[propName];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(propValue) {
|
||||||
|
if(setter) {
|
||||||
|
setter(propValue);
|
||||||
|
} else {
|
||||||
|
view[propName] = propValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
var propValue = asset.resolveConfigAsset(conf[propName]);
|
var propValue = asset.resolveConfigAsset(conf[propName]);
|
||||||
if(propValue) {
|
if(propValue) {
|
||||||
if(setter) {
|
if(setter) {
|
||||||
|
@ -169,6 +198,7 @@ function ViewController(options) {
|
||||||
view[propName] = propValue;
|
view[propName] = propValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -371,7 +371,8 @@
|
||||||
"MT1" : {
|
"MT1" : {
|
||||||
"width" : 67,
|
"width" : 67,
|
||||||
"height" : 17,
|
"height" : 17,
|
||||||
"text" : "Hints:\n * Press CTRL-Y to clear a line\n * Arrow keys to move around\n\nTry editing the text below:\nThis\tis\ttabbbed!\nLet me be sick... I want to get up. Get me something to be sick in... Stop the film... Please stop it... I can't stand it any more. Stop it please... please.\n\nWell, that was a very promising start. By my calculations, you should be starting to feel alright again. Yes? Dr. Brodsky's pleased with you. Now \ttomorrow \tthere'll \tbe \ttwo \tsessions, \tof\t course, morning and afternoon.\n\nYou mean, I have to viddy two sessions in one day?\n\nI imagine you'll be feeling a little bit limp by the end of the day. But we have to be hard on you. You have to be cured.\n",
|
"text" : "@art:demo_multi_line_edit_text_view_text.txt",
|
||||||
|
//"text" : "Hints:\n * Press CTRL-Y to clear a line\n * Arrow keys to move around\n\nTry editing the text below:\nThis\tis\ttabbbed!\nLet me be sick... I want to get up. Get me something to be sick in... Stop the film... Please stop it... I can't stand it any more. Stop it please... please.\n\nWell, that was a very promising start. By my calculations, you should be starting to feel alright again. Yes? Dr. Brodsky's pleased with you. Now \ttomorrow \tthere'll \tbe \ttwo \tsessions, \tof\t course, morning and afternoon.\n\nYou mean, I have to viddy two sessions in one day?\n\nI imagine you'll be feeling a little bit limp by the end of the day. But we have to be hard on you. You have to be cured.\n",
|
||||||
"focus" : true
|
"focus" : true
|
||||||
},
|
},
|
||||||
"BT5" : {
|
"BT5" : {
|
||||||
|
|
Loading…
Reference in New Issue