diff --git a/core/terminal.js b/core/terminal.js
index 16f590c..e497051 100644
--- a/core/terminal.js
+++ b/core/terminal.js
@@ -30,20 +30,65 @@
after a delay. */
var imageTimeout = null;
- // Text to be displayed in the terminal
+ // Text to be displayed in the terminal as array holding lines of text
var termText = [ "" ];
+ var termCursorX = 0;
+ var termCursorY = 0;
+
// Map of terminal line number to text to display before it
var termExtraText = {};
+
// List of (jquerified) DOM elements for each line
var elements = [];
- var termCursorX = 0;
- var termCursorY = 0;
- var termControlChars = [];
+ // current control character sequence as string
+ var termControlChars = '';
// maximum lines on the terminal
var MAX_LINES = 2048;
+ // mapping of display attribute type/ID to HTML in line style - in response to ANSI Seq - esc [ ...
+ const attributes = [
+ {ID:0,name:'resetAll',styleStr:''}, // 0m resets all
+ {ID:1,name:'foregroud',styleStr:'color:'}, //styleStr+colour 30->37m set, 39m clear
+ {ID:2,name:'bold',styleStr:'font-weight:'}, //styleStr+'bolder' 1m set, 22m clear
+ // {ID:2,name:'faint',styleStr:'font-weight:'}, styleStr+'lighter' 2m set, 22m clear
+ {ID:3,name:'italic',styleStr:'font-style:italic'}, // 3m set, 23m clear
+ {ID:4,name:'underline',styleStr:'text-decoration:underline'}, // 4m set, 24m clear
+ {ID:5,name:'crossedOut',styleStr:'text-decoration:line-through'}, // 9m set, 29m clear
+ {ID:6,name:'background',styleStr:'background-color:'} //style+colour 40->47 set, 49 clear
+ ]
+
+ /* Display Attributes of each termText line as array of array of objects
+ set during recievedCharacter() processing. object properties:
+ seq: int - sequence order of attribute in termtext line
+ pos: int - character position of style on termtext line
+ type: int - type of attribute corresponding to attribute ID
+ value: string - parameter (depending on type) eg reset,on,off,colour
+ */
+ var termAttribute = [];
+
+ /* Styles currently active during HTML creation. As array:
+ index = ID-1 of corresponding attribute
+ value (string) = undefined || complete style string (as per attributes[]) inc colour/font weight value
+ */
+ var activeStyles = [];
+
+ const fullModalAttribs = true; //set true then any active attribute styles carry to new lines
+
+ // map of ANSI display attribute colours for code 0->7, index = code
+ const colorMap = ["black","red","green","yellow","blue","magenta","cyan","white"];
+
+ // updates active style for a termAttribute object
+ function setActiveStyles(obj){
+ switch (obj.value) {
+ case 'reset' : activeStyles = [] ; break;
+ case 'on' : activeStyles[obj.type-1] = attributes[obj.type].styleStr ; break;
+ case 'off' : activeStyles[obj.type-1] = undefined ; break;
+ default : activeStyles[obj.type-1] = attributes[obj.type].styleStr + obj.value; break;
+ }
+ }
+
function init()
{
// Add stuff we need
@@ -368,16 +413,45 @@
for (var l in termText)
termText[l] = (l==0?">":":") + termText[l];
// reset other stuff...
+ termAttribute = [];
termExtraText = {};
// leave X cursor where it was...
termCursorY -= currentLine.line; // move Y cursor back
- termControlChars = [];
+ termControlChars = '';
// finally update the HTML
updateTerminal();
// fire off a clear terminal processor
Espruino.callProcessor("terminalClear");
};
+ /**
+ * function buildAttribSpans
+ * returns {string} updated line with HTML spans created to style as per corresponding line attributes
+ * with text elements in line Escaped via Utils.escapeHTML()
+ * param {string} line - line form termText[]
+ * param {array of objects} attribs - line attributes as defined in termAttribute[] for the line of text
+ */
+ var buildAttribSpans = function (line, attribs) {
+ // full modal mode uses any current attribte styles set on previous lines
+ if (!fullModalAttribs && !attribs) return Espruino.Core.Utils.escapeHTML(line);
+ if (!attribs) return "" + Espruino.Core.Utils.escapeHTML(line) + "" ;
+
+ var result = attribs.reduce(function (acc, obj, i, arr) {
+ setActiveStyles(obj);
+ let end = !arr[i + 1] ? line.length : arr[i + 1].pos;
+ if (end == obj.pos) return acc; // no text just styles
+ return (
+ acc +
+ "" +
+ Espruino.Core.Utils.escapeHTML(line.slice(obj.pos, end)) +
+ ""
+ );
+ }, Espruino.Core.Utils.escapeHTML(line.slice(0, attribs[0].pos))); // initial value is any text before first attribute position
+ return result;
+ };
+
var updateTerminal = function() {
var terminal = $("#terminal");
// gather a list of elements for each line
@@ -394,6 +468,7 @@
if (termText.length > MAX_LINES) {
var removedLines = termText.length - MAX_LINES;
termText = termText.slice(removedLines);
+ termAttribute = termAttribute.slice(removedLines);
termCursorY -= removedLines;
var newTermExtraText = {};
for (var i in termExtraText) {
@@ -421,16 +496,16 @@
elements[i].remove();
// now write this to the screen
var t = [];
- for (var y in termText) {
+ for (var y in termText) {
var line = termText[y];
- if (y == termCursorY) {
+ if (y == termCursorY) { // current line
var ch = Espruino.Core.Utils.getSubString(line,termCursorX,1);
line = Espruino.Core.Utils.escapeHTML(
Espruino.Core.Utils.getSubString(line,0,termCursorX)) +
"" + Espruino.Core.Utils.escapeHTML(ch) + "" +
Espruino.Core.Utils.escapeHTML(Espruino.Core.Utils.getSubString(line,termCursorX+1));
} else {
- line = Espruino.Core.Utils.escapeHTML(line);
+ line = buildAttribSpans(line,termAttribute[y]);
// handle URLs
line = line.replace(/(https?:\/\/[-a-zA-Z0-9@:%._\+~#=\/\?]+)/g, '$1');
}
@@ -443,6 +518,7 @@
imageTimeout = setTimeout(convertInlineImages, 1000);
}
+
// extra text is for stuff like tutorials
if (termExtraText[y])
line = termExtraText[y] + line;
@@ -480,84 +556,166 @@
return str.substr(0,s+1);
}
+ // Add Character string (str) to termText for output
+ var addCharacters = function (str){
+ if (termText[termCursorY]===undefined) termText[termCursorY]="";
+ termText[termCursorY] = trimRight(
+ Espruino.Core.Utils.getSubString(termText[termCursorY],0,termCursorX) +
+ str +
+ Espruino.Core.Utils.getSubString(termText[termCursorY],termCursorX+1));
+ termCursorX = termCursorX+str.length;
+ // check for the 'prompt', eg '>' or 'debug>'
+ // if we have it, send a 'terminalPrompt' message
+ // if (str == ">".charCodeAt(0)) {
+ if (str == ">" ) {
+ var prompt = termText[termCursorY];
+ if (prompt==">" || prompt=="debug>")
+ Espruino.callProcessor("terminalPrompt", prompt);
+ }
+ }
+
var handleReceivedCharacter = function (/*char*/ch) {
- // SGA Version for issue #154
- //console.log("IN = "+ch);
- if (termControlChars.length==0) {
- switch (ch) {
- case 8 : {
- if (termCursorX>0) termCursorX--;
- } break;
- case 10 : { // line feed
- Espruino.callProcessor("terminalNewLine", termText[termCursorY]);
- termCursorX = 0; termCursorY++;
- while (termCursorY >= termText.length) termText.push("");
- } break;
- case 13 : { // carriage return
- termCursorX = 0;
- } break;
- case 27 : {
- termControlChars = [ 27 ];
- } break;
- case 19 : break; // XOFF
- case 17 : break; // XON
- case 0xC2 : break; // UTF8 for <255 - ignore this
- default : {
- // Else actually add character
- if (termText[termCursorY]===undefined) termText[termCursorY]="";
- termText[termCursorY] = trimRight(
- Espruino.Core.Utils.getSubString(termText[termCursorY],0,termCursorX) +
- String.fromCharCode(ch) +
- Espruino.Core.Utils.getSubString(termText[termCursorY],termCursorX+1));
- termCursorX++;
- // check for the 'prompt', eg '>' or 'debug>'
- // if we have it, send a 'terminalPrompt' message
- if (ch == ">".charCodeAt(0)) {
- var prompt = termText[termCursorY];
- if (prompt==">" || prompt=="debug>")
- Espruino.callProcessor("terminalPrompt", prompt);
- }
+ switch (termControlChars.length) {
+ case 0 : {
+ switch (ch) {
+ case 8 : // BS
+ if (termCursorX>0) termCursorX--;
+ break;
+ case 10 : // line feed
+ Espruino.callProcessor("terminalNewLine", termText[termCursorY]);
+ termCursorX = 0; termCursorY++;
+ while (termCursorY >= termText.length) termText.push("");
+ break;
+ case 13 : // carriage return
+ termCursorX = 0;
+ break;
+ case 27 : // Esc
+ termControlChars = String.fromCharCode(27);
+ break;
+ case 19 : break; // XOFF
+ case 17 : break; // XON
+ case 0xC2 : break; // UTF8 for <255 - ignore this
+ default : addCharacters(String.fromCharCode(ch)); // Else actually add character
}
+ break;
}
- } else if (termControlChars[0]==27) { // Esc
- if (termControlChars[1]==91) { // Esc [
- if (termControlChars[2]==63) {
- if (termControlChars[3]==55) {
- if (ch!=108)
- console.log("Expected 27, 91, 63, 55, 108 - no line overflow sequence");
- termControlChars = [];
- } else {
- if (ch==55) {
- termControlChars = [27, 91, 63, 55];
- } else termControlChars = [];
- }
- } else {
- termControlChars = [];
- switch (ch) {
- case 63: termControlChars = [27, 91, 63]; break;
- case 65: if (termCursorY > 0) termCursorY--; break; // up FIXME should add extra lines in...
- case 66: termCursorY++; while (termCursorY >= termText.length) termText.push(""); break; // down FIXME should add extra lines in...
- case 67: termCursorX++; break; // right
- case 68: if (termCursorX > 0) termCursorX--; break; // left
- case 74: termText[termCursorY] = termText[termCursorY].substr(0,termCursorX); // Delete to right + down
- termText = termText.slice(0,termCursorY+1);
- break;
- case 75: termText[termCursorY] = termText[termCursorY].substr(0,termCursorX); break; // Delete to right
- }
- }
- } else {
- switch (ch) {
- case 91: {
- termControlChars = [27, 91];
- } break;
- default: {
- termControlChars = [];
- }
- }
- }
- } else termControlChars = [];
-};
-
+ case 1 :
+ if (termControlChars == '\x1B') // Esc
+ switch (ch) {
+ case 91: termControlChars += '['; break; // Esc [
+ default: termControlChars = '';
+ } else termControlChars = '';
+ break;
+ case 2 :
+ if (termControlChars == '\x1B[') { // Esc [
+ switch (ch) {
+ case 63: termControlChars += '?'; break; // Esc [ ?
+ case 65: // up
+ if (termCursorY > 0) termCursorY--;
+ termControlChars = '';
+ break; // old FIXME should add extra lines in...
+ case 66: // down
+ termCursorY++;
+ while (termCursorY >= termText.length) termText.push("");
+ termControlChars = '';
+ break; // old FIXME should add extra lines in...
+ case 67: //right
+ termCursorX++;
+ termControlChars = '';
+ break;
+ case 68: // left
+ if (termCursorX > 0) termCursorX--;
+ termControlChars = '';
+ break;
+ case 74: // Delete to right + down
+ termText[termCursorY] = termText[termCursorY].substr(0,termCursorX);
+ termText = termText.slice(0,termCursorY+1);
+ termControlChars = '';
+ break;
+ case 75: // K Delete to right
+ termText[termCursorY] = termText[termCursorY].substr(0,termCursorX);
+ termControlChars = '';
+ break;
+ default:
+ termControlChars += String.fromCharCode(ch)
+ if (/\x1B\[[0-49]/.test(termControlChars)) break; // setting display attribute - more to come
+ termControlChars = '';
+ }
+ } else termControlChars = '';
+ break;
+ case 3 :
+ termControlChars += String.fromCharCode(ch);
+ if (/\x1B\[\?7/.test(termControlChars)) break; // Esc [ ? 7 - line overflow sequence - more to come
+ if (/\x1B\[[34][0-79]/.test(termControlChars)) break; // colour device attribute - more to come
+ if (/\x1B\[[2][2349]/.test(termControlChars)) break; // device attribute - more to come
+
+ if (/\x1B\[0m/.test(termControlChars)) { //reset all - add to term line attributes
+ termAttribute[termCursorY] = !termAttribute[termCursorY]
+ ? [].concat({seq:0,pos:termCursorX,type:0,value:'reset'})
+ : termAttribute[termCursorY].concat({seq:termAttribute[termCursorY].length,pos:termCursorX,type:0,value:'reset'})
+ termControlChars = '';
+ break;
+ }
+ if (/\x1B\[[12]m/.test(termControlChars)) { //set bold/faint text - add to term line attribute
+ let weight = termControlChars.slice(2,3)==1? 'bolder': 'lighter';
+ termAttribute[termCursorY] = !termAttribute[termCursorY]
+ ? [].concat({seq:0,pos:termCursorX,type:2,value:weight})
+ : termAttribute[termCursorY].concat({seq:termAttribute[termCursorY].length,pos:termCursorX,type:2,value:weight})
+ termControlChars = '';
+ break;
+ }
+ if (/\x1B\[[349]m/.test(termControlChars)) { //set italic/underline/line-thru - add to term line attributes
+ let attribType = termControlChars.slice(2,3)=='9'? 5: +termControlChars.slice(2,3);
+ termAttribute[termCursorY] = !termAttribute[termCursorY]
+ ? [].concat({seq:0,pos:termCursorX,type:attribType,value:'on'})
+ : termAttribute[termCursorY].concat({seq:termAttribute[termCursorY].length,pos:termCursorX,type:attribType,value:'on'})
+ termControlChars = '';
+ break;
+ }
+ termControlChars = '';
+ break;
+ case 4 :
+ termControlChars += String.fromCharCode(ch);
+ if (/\x1B\[\?7/.test(termControlChars)) { // Esc [ ? 7
+ if (ch!=108) { // Esc [ ? 7 l
+ console.log("Expected 27, 91, 63, 55, 108 - no line overflow sequence");
+ }
+ termControlChars = ''; // got Esc [ ? 7 l or not - reset term control chars
+ break;
+ }
+ if (/\x1B\[[3][0-7]m/.test(termControlChars)) { //set foreground colour - add to term line attributes
+ termAttribute[termCursorY] = !termAttribute[termCursorY]
+ ? [].concat( {seq:0,pos:termCursorX,type:1,value:colorMap[termControlChars.slice(3,4)]})
+ : termAttribute[termCursorY].concat( {seq:termAttribute[termCursorY].length,pos:termCursorX,type:1,value:colorMap[termControlChars.slice(3,4)]})
+ termControlChars = '';
+ break;
+ }
+ if (/\x1B\[[4][0-7]m/.test(termControlChars)) { //set background colour - add to term line attributes
+ termAttribute[termCursorY] = !termAttribute[termCursorY]
+ ? [].concat( {seq:0,pos:termCursorX,type:6,value:colorMap[termControlChars.slice(3,4)]})
+ : termAttribute[termCursorY].concat( {seq:termAttribute[termCursorY].length,pos:termCursorX,type:6,value:colorMap[termControlChars.slice(3,4)]})
+ termControlChars = '';
+ break;
+ }
+ if (/\x1B\[2[2349]m/.test(termControlChars)) { //turn off fontWeight(bold,faint)/italic/underline/line-thru - add to term line attributes
+ let attribType = termControlChars.slice(3,4)=='9'? 5: +termControlChars.slice(3,4);
+ termAttribute[termCursorY] = !termAttribute[termCursorY]
+ ? [].concat( {seq:0,pos:termCursorX,type:attribType,value:'off'})
+ : termAttribute[termCursorY].concat( {seq:termAttribute[termCursorY].length,pos:termCursorX,type:attribType,value:'off'})
+ termControlChars = '';
+ break;
+ }
+ if (/\x1B\[[34]9m/.test(termControlChars)) { //turn off foreground/background colour - add to term line attributes
+ let attribType = termControlChars.slice(2,3)=='3'? 1: 6;
+ termAttribute[termCursorY] = !termAttribute[termCursorY]
+ ? [].concat( {seq:0,pos:termCursorX,type:attribType,value:'off'})
+ : termAttribute[termCursorY].concat( {seq:termAttribute[termCursorY].length,pos:termCursorX,type:attribType,value:'off'})
+ termControlChars = '';
+ break;
+ }
+ default: termControlChars = '';
+ }
+ }
// ----------------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------------