From 280fbf5e99942ce0fc624849622d716033ec8309 Mon Sep 17 00:00:00 2001
From: deuce <>
Date: Thu, 12 Nov 2015 03:52:47 +0000
Subject: [PATCH] Add load/remote screen copies which parse the ANSI sent to
 the remote. Use default of 80x24 for both. Use the screen object to parse
 ANSI and keep the systems idea of the "Current Attribute" correct.

Once this is all fixed up, I'll have everything I need for getstr()!
---
 exec/dorkit/screen.js | 369 ++++++++++++++++++++++++++++++++++++++++++
 exec/load/dorkit.js   |  53 ++++--
 2 files changed, 411 insertions(+), 11 deletions(-)
 create mode 100644 exec/dorkit/screen.js

diff --git a/exec/dorkit/screen.js b/exec/dorkit/screen.js
new file mode 100644
index 0000000000..5d65da7a9f
--- /dev/null
+++ b/exec/dorkit/screen.js
@@ -0,0 +1,369 @@
+/*
+ * Screen object...
+ * This object is the estimate of what a screen looks like modeled
+ * as a Graphic object (Screen.graphic).
+ * 
+ * The Graphic object should not be written directly, but instead,
+ * all data should be provided using the print() method.
+ */
+
+if (js.global.Graphic === undefined)
+	load("graphic.js");
+if (js.global.Attribute === undefined)
+	load("attribute.js");
+
+function Screen(w, h, attr, fill)
+{
+	this.graphic = new Graphic(w, h, attr, fill);
+	this.escbuf = '';
+	this.pos = {x:0, y:0};
+	this.stored_pos = {x:0, y:0};
+	this.attr = new Attribute(7);
+}
+
+Screen.prototype.print=function(str) {
+	var m;
+	var ext;
+	var pb;
+	var ib;
+	var fb;
+	var p;
+	var chars;
+	var remain;
+	var i;
+	var tg;
+	var seq;
+
+	function writech(scr, ch) {
+		var i;
+		var gr;
+
+		function check_scrollup(scr) {
+			while (scr.pos.y >= scr.graphic.height) {
+				// Scroll up...
+				gr = scr.graphic.get(0,1,scr.graphic.width-1, scr.graphic.height-2);
+				scr.graphic.put(gr,0,0);
+				for (i=0; i<scr.graphic.width; i++) {
+					scr.graphic.data[i][scr.graphic.height-1].ch = scr.graphic.ch;
+					scr.graphic.data[i][scr.graphic.height-1].attr = scr.attr.value;
+				}
+				scr.pos.y--;
+			}
+		}
+
+		// Handle special chars.
+		switch(ch) {
+			case '\x00':	// NUL is not displayed.
+				break;
+			case '\x07':	// Beep is not displayed.
+				break;
+			case '\x08':	// Backspace.
+				scr.pos.x--;
+				if (scr.pos.x < 0)
+					scr.pos.x = 0;
+				break;
+			case '\x09':	// Tab
+				do {
+					scr.pos.x++;
+				} while(scr.pos.x % 8);
+				if (scr.pos.x >= scr.graphic.width)
+					scr.pos.x = scr.graphic.width-1;
+				break;
+			case '\x0a':	// Linefeed
+				scr.pos.y++;
+				check_scrollup(scr);
+				break;
+			case '\x0c':	// For feed (clear screen and home)
+				scr.graphic.clear();
+				scr.pos.x=0;
+				scr.pos.y=0;
+				break;
+			case '\x0d':	// Carriage return
+				scr.pos.x = 0;
+				break;
+			default:
+				scr.graphic.data[scr.pos.x][scr.pos.y].ch = ch;
+				scr.graphic.data[scr.pos.x][scr.pos.y].attr = new Attribute(scr.attr);
+				scr.pos.x++;
+				if (scr.pos.x >= scr.graphic.width) {
+					scr.pos.x = 0;
+					scr.pos.y++;
+					check_scrollup(scr);
+				}
+				break;
+		}
+	}
+
+	function param_defaults(params, defaults) {
+		var i;
+
+		for (i=0; i<defaults.length; i++) {
+			if (params[i] == undefined || params[i].length == 0)
+				params[i]=defaults[i];
+		}
+		for (i=0; i<params.length; i++) {
+			if (params[i]===undefined || params[i]==='')
+				params[i] = 0;
+			else
+				params[i] = parseInt(params[i], 10);
+		}
+	}
+
+	// Prepend the ESC buffer to avoid failing on split ANSI
+	str = this.escbuf + str;
+	this.escbuf = '';
+
+	while((m=str.match(/^(.*?)\x1b\[([<-\?]{0,1})([0-;]*)([ -\/]*)([@-~])([\x00-\xff]*)$/)) !== null) {
+		chars = m[1];
+		ext = m[2];
+		pb = m[3];
+		ib = m[4];
+		fb = m[5];
+		remain = m[6];
+		seq = ext + ib + fb;
+		var x;
+		var y;
+
+		str = remain;
+
+		// Send regular chars before the sequence...
+		for (i=0; i<chars.length; i++)
+			writech(this, chars[i]);
+
+		// We don't support any : paramters... strip and ignore.
+		p = pb.replace(/:[^;:]*/g, '');
+		p = p.split(';');
+
+		switch(fb) {
+			case '@':	// Insert character
+				param_defaults(p, [1]);
+				if (p[1] > this.graphic.width - this.pos.x)
+					p[1] = this.graphic.width - this.pos.x;
+				if (this.pos.x < this.graphic.width-1) {
+					tg = this.graphic.get(this.pos.x, this.pos.y, this.graphic.width-1 - p[1], this.pos.y);
+					tg = this.graphic.put(this.pos.x + p[1], this.pos.y, this.graphic.width - 1, this.pos.y);
+				}
+				for (x = 0; x<p[1]; x++) {
+					this.graphic.data[this.pos.x + x][this.pos.y].ch = this.graphic.ch;
+					this.graphic.data[this.pos.x + x][this.pos.y].attr = this.attr.value;
+				}
+				break;
+			case 'A':	// Cursor Up
+				param_defaults(p, [1]);
+				this.pos.y -= p[0];
+				if (this.pos.y < 0)
+					this.pos.y = 0;
+				break;
+			case 'B':	// Cursor Down
+				param_defaults(p, [1]);
+				this.pos.y += p[0];
+				if (this.pos.y >= this.graphic.height)
+					this.pos.y = this.graphic.height-1;
+				break;
+			case 'C':	// Cursor Right
+				param_defaults(p, [1]);
+				this.pos.x += p[0];
+				if (this.pos.x >= this.graphic.width)
+					this.pos.x = this.graphic.width-1;
+				break;
+			case 'D':	// Cursor Left
+				param_defaults(p, [1]);
+				this.pos.x -= p[0];
+				if (this.pos.x < 0)
+					this.pos.x = 0;
+				break;
+			case 'H':	// Cursor position
+			case 'f':
+				param_defaults(p, [1,1]);
+				if (p[0] >= 0 && p[0] < this.graphic.height && p[1] >= 0 && p[1] <= this.graphic.width) {
+					this.pos.x = p[1];
+					this.pos.y = p[0];
+				}
+				break;
+			case 'J':	// Erase in screen
+				param_defaults(p, [0]);
+				switch(p[0]) {
+					case 0:	// Erase to end of screen...
+						for (x = this.pos.x; x<this.pos.width; x++) {
+							this.graphic.data[x][this.pos.y].ch = this.graphic.ch;
+							this.graphic.data[x][this.pos.y].attr = this.attr.value;
+						}
+						for (y = this.pos.y+1; y<this.pos.height; y++) {
+							for (x = 0; x<this.graphic.width; x++) {
+								this.graphic.data[x][y].ch = this.graphic.ch;
+								this.graphic.data[x][y].attr = this.attr.value;
+							}
+						}
+						break;
+					case 1:	// Erase to beginning of screen...
+						for (y = 0; y < this.pos.y; y++) {
+							for (x = 0; x<this.graphic.width; x++) {
+								this.graphic.data[x][y].ch = this.graphic.ch;
+								this.graphic.data[x][y].attr = this.attr.value;
+							}
+						}
+						for (x = 0; x<=this.pos.x; x++) {
+							this.graphic.data[x][this.pos.y].ch = this.graphic.ch;
+							this.graphic.data[x][this.pos.y].attr = this.attr.value;
+						}
+						break;
+					case 2:	// Erase entire screen (Most BBS terminals also move to 1/1)
+						this.graphic.clear();
+						break;
+				}
+				break;
+			case 'K':	// Erase in line
+				param_defaults(p, [0]);
+				switch(p[0]) {
+					case 0:	// Erase to eol
+						for (x = this.pos.x; x<this.pos.width; x++) {
+							this.graphic.data[x][this.pos.y].ch = this.graphic.ch;
+							this.graphic.data[x][this.pos.y].attr = this.attr.value;
+						}
+						break;
+					case 1:	// Erase to start of line
+						for (x = 0; x<=this.pos.x; x++) {
+							this.graphic.data[x][this.pos.y].ch = this.graphic.ch;
+							this.graphic.data[x][this.pos.y].attr = this.attr.value;
+						}
+						break;
+					case 2:	// Erase entire line
+						for (x = 0; x<this.graphic.width; x++) {
+							this.graphic.data[x][this.pos.y].ch = this.graphic.ch;
+							this.graphic.data[x][this.pos.y].attr = this.attr.value;
+						}
+						break;
+					default:
+						break;
+				}
+				break;
+			case 'P':	// Delete character
+				param_defaults(p, [1]);
+				if (p[1] > this.graphic.width - this.pos.x)
+					p[1] = this.graphic.width - this.pos.x;
+				if (this.pos.x < this.graphic.width-1) {
+					tg = this.graphic.get(this.pos.x + p[1], this.pos.y, this.graphic.width - 1, this.pos.y);
+					tg = this.graphic.put(this.pos.x, this.pos.y, (this.graphic.width - 1) - p[1], this.pos.y);
+				}
+				for (x = 0; x<p[1]; x++) {
+					this.graphic.data[(this.width - 1) - x][this.pos.y].ch = this.graphic.ch;
+					this.graphic.data[(this.width - 1) - x][this.pos.y].attr = this.attr.value;
+				}
+				break;
+			case 'X':	// Erase character
+				param_defaults(p, [1]);
+				if (p[1] > this.graphic.width - this.pos.x)
+					p[1] = this.graphic.width - this.pos.x;
+				for (x = 0; x<p[1]; x++) {
+					this.graphic.data[this.pos.x + x][this.pos.y].ch = this.graphic.ch;
+					this.graphic.data[this.pos.x + x][this.pos.y].attr = this.attr.value;
+				}
+				break;
+			case 'm':
+				param_defaults(p, [0]);
+				for (i=0; i<p.length; i++) {
+					switch(p[i]) {
+						case 0:
+							this.attr.value = this.graphic.attribute.value;
+							break;
+						case 1:
+							this.attr.bright = true;
+							break;
+						case 2:
+						case 22:
+							this.attr.bright = false;
+							break;
+						case 5:
+						case 6:
+							this.attr.blink = true;
+							break;
+						case 7:
+							tg = this.attr.bg;
+							this.attr.bg = this.attr.fg;
+							this.attr.fg = tg;
+							break;
+						case 8:
+							this.attr.fg = this.attr.bg;
+							break;
+						case 25:
+							this.attr.blink = false;
+							break;
+						case 30:
+							this.attr.fg = Attribute.BLACK;
+							break;
+						case 31:
+							this.attr.fg = Attribute.RED;
+							break;
+						case 32:
+							this.attr.fg = Attribute.GREEN;
+							break;
+						case 33:
+							this.attr.fg = Attribute.YELLOW;
+							break;
+						case 34:
+							this.attr.fg = Attribute.BLUE;
+							break;
+						case 35:
+							this.attr.fg = Attribute.MAGENTA;
+							break;
+						case 36:
+							this.attr.fg = Attribute.CYAN;
+							break;
+						case 37:
+							this.attr.fg = Attribute.WHITE;
+							break;
+						case 40:
+							this.attr.bg = Attribute.BLACK;
+							break;
+						case 41:
+							this.attr.bg = Attribute.RED;
+							break;
+						case 42:
+							this.attr.bg = Attribute.GREEN;
+							break;
+						case 43:
+							this.attr.bg = Attribute.YELLOW;
+							break;
+						case 44:
+							this.attr.bg = Attribute.BLUE;
+							break;
+						case 45:
+							this.attr.bg = Attribute.MAGENTA;
+							break;
+						case 46:
+							this.attr.bg = Attribute.CYAN;
+							break;
+						case 47:
+							this.attr.bg = Attribute.WHITE;
+							break;
+					}
+				}
+				break;
+			case 's':
+				this.saved_pos.x = this.pos.x;
+				this.saved_pos.y = this.pos.y;
+				break;
+			case 'u':
+				this.pos.x = this.saved_pos.x;
+				this.pos.y = this.saved_pos.y;
+				break;
+			// Still TODO...
+			case 'n':	// Device status report... no action from this object.
+			case 'Z':	// Back tabulate
+			case 'S':	// Scroll up
+			case 'T':	// Scroll down
+			case 'L':	// Insert line
+			case 'M':	// Delete line (also ANSI music!)
+			default:
+				log("Sent unsupported ANSI sequence '"+ext+pb+ib+fb+"' please let shurd@sasktel.net net know about this so it can be fixed.");
+		}
+	}
+	if ((m=str.match(/^(.*?)(\x1b(\[([<-\?]{0,1})([0-;]*)([ -\/]*)([@-~])?)?)$/)) !== null) {
+		str = m[1];
+		this.escbuf = m[2];
+	}
+	// Send regular chars before the sequence...
+	for (i=0; i<str.length; i++)
+		writech(this, str[i]);
+
+};
diff --git a/exec/load/dorkit.js b/exec/load/dorkit.js
index 9779c0cf48..10ecdd4d93 100644
--- a/exec/load/dorkit.js
+++ b/exec/load/dorkit.js
@@ -1,8 +1,7 @@
 js.load_path_list.unshift(js.exec_dir+"/dorkit/");
 if (js.global.system !== undefined)
 	js.load_path_list.unshift(system.exec_dir+"/dorkit/");
-load("attribute.js");
-load("graphic.js");
+load("screen.js");
 
 var dk = {
 	console:{
@@ -73,14 +72,25 @@ var dk = {
 
 		x:1,					// Current column (1-based)
 		y:1,					// Current row (1-based)
-		attr:new Attribute(7),	// Current attribute
+		_attr:new Attribute(7),
+		get attr() {
+			return this._attr;
+		},
+		set attr(val) {
+			var n = new Attribute(val);
+			this.print(n.ansi(this._attr));
+			this._attr = n;
+		},
 		ansi:true,				// ANSI support is enabled
 		charset:'cp437',		// Supported character set
 		local:true,				// True if writes should go to the local screen
 		remote:true,			// True if writes should go to the remote terminal
-		rows:undefined,			// Rows in users terminal
-		cols:undefined,			// Columns in users terminal
+		rows:24,				// Rows in users terminal
+		cols:80,				// Columns in users terminal
+
 		keybuf:'',
+		local_screen:new Screen(80, 24, 7, ' '),
+		remote_screen:new Screen(80, 24, 7, ' '),
 
 		/*
 		 * Returns a string with ^A codes converted to ANSI or stripped
@@ -198,6 +208,7 @@ var dk = {
 		 * sets the current attribute to 7
 		 */
 		clear:function() {
+			this.attr=7;
 			if (this.local)
 				this.local_io.clear();
 			if (this.remote)
@@ -232,19 +243,29 @@ var dk = {
 		 * or undefined on error (ie: invalid block specified).
 		 */
 		getblock:function(sx,sy,ex,ey) {
+			return this.remote_screen.graphic.get(sx,sy,ex,ey);
 		},
 
 		/*
 		 * Writes a string unmodified.
-		 * TODO: This needs to parse ANSI and set attr...
 		 */
 		print:function(string) {
 			var m;
 
-			if (this.local)
+			if (this.local) {
+				if (this.local_screen !== undefined) {
+					this.local_screen.print(string);
+					this._attr.value = this.local_screen.attr.value;
+				}
 				this.local_io.print(string);
-			if (this.remote)
+			}
+			if (this.remote) {
+				if (this.remote_screen !== undefined) {
+					this.remote_screen.print(string);
+					this.attr.value = this.remote_screen.attr.value;
+				}
 				this.remote_io.print(string);
+			}
 		},
 
 		/*
@@ -258,14 +279,14 @@ var dk = {
 		 * Writes a string after parsing ^A codes.
 		 */
 		aprint:function(string) {
-			this.println(this.parse_ctrla(line));
+			this.println(this.parse_ctrla(line, this.attr));
 		},
 
 		/*
 		 * Writes a string after parsing ^A codes and appends a "\r\n".
 		 */
 		aprintln:function(line) {
-			this.println(this.parse_ctrla(line));
+			this.println(this.parse_ctrla(line, this.attr));
 		},
 
 		/*
@@ -455,6 +476,10 @@ var dk = {
 			if(this.console.waitkey(500)) {
 				if (this.console.getkey() == this.console.key.POSITION_REPORT) {
 					// TODO: Should we trust the drop file on number of rows?
+					if (this.console.cols != this.console.last_pos.x || this.console.rows != this.console.last_pos.y) {
+						this.console.remote_screen = new Screen(this.console.last_pos.x, this.console.last_pos.y, 7, ' ');
+						this.console.local_screen = new Screen(this.console.last_pos.x, this.console.last_pos.y, 7, ' ');
+					}
 					this.console.cols = this.console.last_pos.x;
 					this.console.rows = this.console.last_pos.y;
 					this.console.ansi = true;
@@ -468,6 +493,7 @@ var dk = {
 	parse_dropfile:function(path) {
 		var f = new File(path);
 		var df;
+		var rows;
 
 		if (!f.open("r"))
 			return false;
@@ -511,7 +537,12 @@ var dk = {
 				this.codepage = '7-bit';
 				break;
 		}
-		this.rows = parseInt(df[20], 10);
+		rows = parseInt(df[20], 10);
+		if (rows != this.console.rows) {
+			this.console.remote_screen = new Screen(this.console.cols, rows, 7, ' ');
+			this.console.local_screen = new Screen(this.console.cols, rows, 7, ' ');
+		}
+		this.rows = rows;
 		this.user.expert_mode = (df[21].toUpperCase === 'Y') ? true : false;
 		this.user.conference = df[22].split(/\s*,\s*/);
 		this.user.curr_conference = parseInt(df[23]);
-- 
GitLab