Mercurial > hg > rlgwebd
comparison termemu.js @ 0:bd412f63ce0d
Put this project under version control, finally.
author | John "Elwin" Edwards <elwin@sdf.org> |
---|---|
date | Sun, 06 May 2012 08:45:40 -0700 |
parents | |
children | ee22eb9ab009 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:bd412f63ce0d |
---|---|
1 /* termemu.js: a mostly xterm-compatible terminal emulator for a webpage */ | |
2 /* SELF-HOSTING 2011-09-23 */ | |
3 | |
4 // How detailed the debugging should be. | |
5 var debugSuppress = 1; | |
6 // Some char values. | |
7 var csiPre = [63, 62, 33]; | |
8 var csiPost = [36, 34, 39, 32]; | |
9 function csiFinal(code) { | |
10 /* @A-Z */ | |
11 if (code >= 64 && code <= 90) | |
12 return true; | |
13 /* `a-z{| */ | |
14 if (code >= 96 && code <= 124) | |
15 return true; | |
16 return false; | |
17 } | |
18 var esc7ctl = [68, 69, 72, 77, 78, 79, 80, 86, 87, 88, 90, 91, 92, 93, 94, 95]; | |
19 var escSingle = [55, 56, 61, 62, 70, 99, 108, 109, 110, 111, 124, 125, 126]; | |
20 var escDouble = [32, 35, 37, 40, 41, 42, 43, 45, 46, 47]; | |
21 | |
22 var decChars = {96: 0x2666, 97: 0x2592, 102: 0xB0, 103: 0xB1, | |
23 106: 0x2518, 107: 0x2510, 108: 0x250C, 109: 0x2514, | |
24 110: 0x253C, 111: 0x23BA, 112: 0x23BB, 113: 0x2500, | |
25 114: 0x23BC, 115: 0x23BD, 116: 0x251C, 117: 0x2524, | |
26 118: 0x2534, 119: 0x252C, 120: 0x2502, 121: 0x2264, | |
27 122: 0x2265, 123: 0x03C0, 124: 0x2260, 125: 0xA3, 126: 0xB7}; | |
28 | |
29 /* Not everything that should be saved by DECSC has been implemented yet. */ | |
30 function Cursor(src) { | |
31 if (src) { | |
32 this.x = src.x; | |
33 this.y = src.y; | |
34 this.bold = src.bold; | |
35 this.inverse = src.inverse; | |
36 this.uline = src.uline; | |
37 this.fg = src.fg; | |
38 this.bg = src.bg; | |
39 this.cset = src.cset; | |
40 } | |
41 else { | |
42 this.x = 0; | |
43 this.y = 0; | |
44 this.bold = false; | |
45 this.inverse = false; | |
46 this.uline = false; | |
47 this.fg = null; | |
48 this.bg = null; | |
49 this.cset = "B"; | |
50 } | |
51 return; | |
52 } | |
53 | |
54 // An object representing the terminal emulator. | |
55 var termemu = { | |
56 sessid: null, // Session key assigned by the server | |
57 /* Some elements of the page. */ | |
58 inwrap: null, // A non-table div wrapping the screen | |
59 view: null, // The div holding the terminal screen | |
60 screen: null, // The div representing the active screen area | |
61 normbuf: null, // The normal screen buffer | |
62 altbuf: null, // The alternate screen buffer | |
63 histbuf: null, // The screen history buffer | |
64 fgColor: "#b2b2b2", // Default color for text | |
65 bgColor: "black", // Default background color | |
66 c: null, // Contains cursor position and text attributes | |
67 offedge: false, // Going off the edge doesn't mean adding a new line | |
68 clearAttrs: function () { | |
69 /* Make sure to reset ALL attribute properties and NOTHING else. */ | |
70 this.c.bold = false; | |
71 this.c.inverse = false; | |
72 this.c.uline = false; | |
73 this.c.fg = null; | |
74 this.c.bg = null; | |
75 }, | |
76 saved: null, // saved cursor | |
77 normc: null, // Stores the normal screen buffer cursor when using altbuf | |
78 ansicolors: ["#000000", "#b21818", "#18b218", "#b26818", "#1818b2", | |
79 "#b218b2", "#18b2b2", "#b2b2b2"], | |
80 brightcolors: ["#686868", "#ff5454", "#54ff54", "#ffff54", "#5454ff", | |
81 "#ff54ff", "#54ffff", "#ffffff"], | |
82 cssColor: function (fg) { | |
83 /* returns a CSS color specification for the text or background */ | |
84 var n; | |
85 var fallback; | |
86 var cube6 = ["00", "5f", "87", "af", "d7", "ff"]; | |
87 if (this.c.inverse) | |
88 fg = !fg; | |
89 if (fg) { | |
90 n = this.c.fg; | |
91 fallback = this.fgColor; | |
92 if (n == null) | |
93 return fallback; | |
94 } | |
95 else { | |
96 n = this.c.bg; | |
97 fallback = this.bgColor; | |
98 if (n == null) | |
99 return fallback; | |
100 } | |
101 if (n < 0) | |
102 return fallback; | |
103 else if (n < 8) { | |
104 if (this.c.bold && fg) | |
105 return this.brightcolors[n]; | |
106 else | |
107 return this.ansicolors[n]; | |
108 } | |
109 else if (n < 16) | |
110 return this.brightcolors[n - 8]; | |
111 else if (n < 232) { | |
112 var r = cube6[Math.floor((n - 16) / 36)]; | |
113 var g = cube6[Math.floor((n - 16) / 6) % 6]; | |
114 var b = cube6[(n - 16) % 6]; | |
115 return "#" + r + g + b; | |
116 } | |
117 else if (n < 256) { | |
118 var colstr = ((n - 232) * 10 + 8).toString(16); | |
119 if (colstr.length < 2) | |
120 colstr = "0" + colstr; | |
121 return "#" + colstr + colstr + colstr; | |
122 } | |
123 else | |
124 return fallback; | |
125 }, | |
126 scrT: 0, // top and bottom of scrolling region | |
127 scrB: 23, | |
128 // These keyboard-related things don't really belong here. | |
129 shift: false, | |
130 shiftp: function () { | |
131 return this.shift; | |
132 }, | |
133 toggleshift: function () { | |
134 this.shift = !this.shift; | |
135 }, | |
136 ctrl: false, | |
137 ctrlp: function () { | |
138 return this.ctrl; | |
139 }, | |
140 togglectrl: function () { | |
141 this.ctrl = !this.ctrl; | |
142 }, | |
143 init: function (divID) { | |
144 /* Makes a div full of character cells. */ | |
145 if (this.screen != null) | |
146 return; | |
147 var owrap = document.getElementById(divID); | |
148 if (!owrap) | |
149 return; | |
150 while (owrap.firstChild != null) | |
151 owrap.removeChild(owrap.firstChild); | |
152 this.c = new Cursor(null); | |
153 /* Create the contents of the terminal div */ | |
154 this.inwrap = document.createElement("div"); | |
155 this.inwrap.id = "inwrap"; | |
156 owrap.appendChild(this.inwrap); | |
157 var termdiv = document.createElement("div"); | |
158 termdiv.id = "term"; | |
159 termdiv.style.fontSize = "12px"; | |
160 this.inwrap.appendChild(termdiv); | |
161 /* Set up the screen buffers */ | |
162 this.histbuf = document.createElement("div"); | |
163 this.histbuf.id = "histbuf"; | |
164 termdiv.appendChild(this.histbuf); | |
165 this.normbuf = document.createElement("div"); | |
166 this.normbuf.id = "normbuf"; | |
167 termdiv.appendChild(this.normbuf); | |
168 for (var row = 0; row < 24; row++) { | |
169 this.normbuf.appendChild(this.makeRow()); | |
170 } | |
171 this.altbuf = document.createElement("div"); | |
172 this.altbuf.id = "altbuf"; | |
173 termdiv.appendChild(this.altbuf); | |
174 this.altbuf.style.display = "none"; | |
175 /* altbuf will be filled when it is used. */ | |
176 /* Attach them. */ | |
177 this.view = termdiv; | |
178 this.screen = this.normbuf; | |
179 this.resize(); | |
180 this.cmove(0, 0); | |
181 }, | |
182 valign: function () { | |
183 if (this.screen == this.normbuf) | |
184 this.inwrap.scrollTop = this.histbuf.clientHeight; | |
185 }, | |
186 resize: function () { | |
187 var owrap = document.getElementById("termwrap"); | |
188 /* Set the height up properly. */ | |
189 this.inwrap.style.height = this.screen.scrollHeight.toString() + "px"; | |
190 this.valign(); | |
191 // Figure out how wide the vertical scrollbar is. | |
192 var dwid = this.inwrap.offsetWidth - this.inwrap.clientWidth; | |
193 // And resize accordingly. | |
194 this.inwrap.style.width = (this.view.scrollWidth + dwid).toString() + "px"; | |
195 owrap.style.width = this.inwrap.offsetWidth.toString() + "px"; | |
196 return; | |
197 }, | |
198 comseq: [], // Part of an impending control sequence | |
199 flipCursor: function () { | |
200 /* Swaps the text and background colors of the active location. */ | |
201 /* This will change when other cursor styles are supported. */ | |
202 if (this.c.x != null && this.c.y != null) { | |
203 var oldcell = this.screen.childNodes[this.c.y].childNodes[this.c.x]; | |
204 var tempswap = oldcell.style.color; | |
205 oldcell.style.color = oldcell.style.backgroundColor; | |
206 oldcell.style.backgroundColor = tempswap; | |
207 } | |
208 return; | |
209 }, | |
210 saveCursor: function () { | |
211 this.saved = new Cursor(this.c); | |
212 return; | |
213 }, | |
214 restoreCursor: function () { | |
215 if (!this.saved) { | |
216 this.cmove(0, 0); | |
217 this.c = new Cursor(null); | |
218 } | |
219 else { | |
220 this.cmove(this.saved.y, this.saved.x); | |
221 this.c = new Cursor(this.saved); | |
222 } | |
223 return; | |
224 }, | |
225 toAltBuf: function () { | |
226 if (this.screen == this.altbuf) | |
227 return; | |
228 while (this.altbuf.firstChild != null) | |
229 this.altbuf.removeChild(this.altbuf.firstChild); | |
230 for (var i = 0; i < 24; i++) { | |
231 this.altbuf.appendChild(this.makeRow()); | |
232 } | |
233 this.normc = new Cursor(this.c); | |
234 this.altbuf.style.display = "table-row-group"; | |
235 this.normbuf.style.display = "none"; | |
236 this.histbuf.style.display = "none"; | |
237 this.screen = this.altbuf; | |
238 debug(0, "Altbuf with charset " + this.c.cset); | |
239 return; | |
240 }, | |
241 toNormBuf: function () { | |
242 if (this.screen == this.normbuf) | |
243 return; | |
244 this.altbuf.style.display = "none"; | |
245 this.normbuf.style.display = "table-row-group"; | |
246 this.histbuf.style.display = "table-row-group"; | |
247 this.screen = this.normbuf; | |
248 this.valign(); | |
249 /* The cursor isn't actually at this position in normbuf, but cmove will | |
250 * flip it anyway. Flip it again to compensate. */ | |
251 this.flipCursor(); | |
252 this.cmove(this.normc.y, this.normc.x); | |
253 this.c = new Cursor(this.normc); | |
254 }, | |
255 cmove: function (y, x) { | |
256 /* Move the cursor. NOTE coords are [row, col] as in curses. */ | |
257 /* If x or y is null, that coordinate is not altered. */ | |
258 /* Sanity checks and initializations. */ | |
259 if (x == null) { | |
260 if (this.c.x != null) | |
261 x = this.c.x; | |
262 else | |
263 return; | |
264 } | |
265 else { | |
266 this.offedge = false; | |
267 if (x < 0) | |
268 x = 0; | |
269 else if (x > 79) | |
270 x = 79; | |
271 } | |
272 if (y == null) { | |
273 if (this.c.y != null) | |
274 y = this.c.y; | |
275 else | |
276 return; | |
277 } | |
278 else if (y < 0) | |
279 y = 0; | |
280 else if (y > 23) | |
281 y = 23; | |
282 /* Un-reverse video the current location. */ | |
283 this.flipCursor(); | |
284 this.c.x = x; | |
285 this.c.y = y; | |
286 /* Reverse-video the new location. */ | |
287 this.flipCursor(); | |
288 return; | |
289 }, | |
290 historize: function (n) { | |
291 if (n < 0 || n >= this.screen.childNodes.length) | |
292 return; | |
293 var oldrow = this.screen.childNodes[n]; | |
294 if (this.screen != this.altbuf && this.scrT == 0) { | |
295 this.histbuf.appendChild(oldrow); | |
296 } | |
297 else { | |
298 this.screen.removeChild(oldrow); | |
299 } | |
300 /* These may not be the correct heights... */ | |
301 this.inwrap.style.height = this.screen.clientHeight.toString() + "px"; | |
302 this.valign(); | |
303 }, | |
304 scroll: function (lines) { | |
305 if (lines == 0) | |
306 return; | |
307 var count; | |
308 if (lines > 0) | |
309 count = lines; | |
310 else | |
311 count = -lines; | |
312 this.flipCursor(); | |
313 while (count > 0) { | |
314 var blankrow = this.makeRow(); | |
315 /* Careful with the order */ | |
316 if (lines > 0) { | |
317 if (this.scrB == 23) | |
318 this.screen.appendChild(blankrow); | |
319 else | |
320 this.screen.insertBefore(blankrow, this.screen.childNodes[this.scrB | |
321 + 1]); | |
322 this.historize(this.scrT); | |
323 } | |
324 else { | |
325 /* Historize here? */ | |
326 this.screen.removeChild(this.screen.childNodes[this.scrB]); | |
327 this.screen.insertBefore(blankrow, this.screen.childNodes[this.scrT]); | |
328 } | |
329 count--; | |
330 } | |
331 this.valign(); // needed? | |
332 this.flipCursor(); | |
333 return; | |
334 }, | |
335 newline: function (doReturn) { | |
336 if (this.c.y == this.scrB) | |
337 this.scroll(1) | |
338 else if (this.c.y < 23) | |
339 this.cmove(this.c.y + 1, null); | |
340 /* If the cursor is at the bottom but outside the scrolling region, | |
341 * nothing can be done. */ | |
342 if (doReturn) { | |
343 this.cmove(null, 0); | |
344 } | |
345 }, | |
346 antinewline: function () { | |
347 if (this.c.y == this.scrT) | |
348 this.scroll(-1); | |
349 else if (this.c.y > 0) | |
350 this.cmove(this.c.y - 1, null); | |
351 }, | |
352 advance: function () { | |
353 if (this.c.x < 79) | |
354 this.cmove(null, this.c.x + 1); | |
355 else { | |
356 this.offedge = true; | |
357 } | |
358 }, | |
359 placechar: function (str) { | |
360 if (this.offedge) { | |
361 this.newline(true); | |
362 } | |
363 var nextch = str.charAt(0); | |