1 /++ Ignore coverage
2 	Module for interacting with the user's terminal, including color output, cursor manipulation, and full-featured real-time mouse and keyboard input. Also includes high-level convenience methods, like [Terminal.getline], which gives the user a line editor with history, completion, etc. See the [#examples].
3 
4 
5 	The main interface for this module is the Terminal struct, which
6 	encapsulates the output functions and line-buffered input of the terminal, and
7 	RealTimeConsoleInput, which gives real time input.
8 
9 	Creating an instance of these structs will perform console initialization. When the struct
10 	goes out of scope, any changes in console settings will be automatically reverted.
11 
12 	Note: on Posix, it traps SIGINT and translates it into an input event. You should
13 	keep your event loop moving and keep an eye open for this to exit cleanly; simply break
14 	your event loop upon receiving a UserInterruptionEvent. (Without
15 	the signal handler, ctrl+c can leave your terminal in a bizarre state.)
16 
17 	As a user, if you have to forcibly kill your program and the event doesn't work, there's still ctrl+\
18 
19 	On Mac Terminal btw, a lot of hacks are needed and mouse support doesn't work. Most functions basically
20 	work now though.
21 
22 	Future_Roadmap:
23 	$(LIST
24 		* The CharacterEvent and NonCharacterKeyEvent types will be removed. Instead, use KeyboardEvent
25 		  on new programs.
26 
27 		* The ScrollbackBuffer will be expanded to be easier to use to partition your screen. It might even
28 		  handle input events of some sort. Its API may change.
29 
30 		* getline I want to be really easy to use both for code and end users. It will need multi-line support
31 		  eventually.
32 
33 		* I might add an expandable event loop and base level widget classes. This may be Linux-specific in places and may overlap with similar functionality in simpledisplay.d. If I can pull it off without a third module, I want them to be compatible with each other too so the two modules can be combined easily. (Currently, they are both compatible with my eventloop.d and can be easily combined through it, but that is a third module.)
34 
35 		* More advanced terminal features as functions, where available, like cursor changing and full-color functions.
36 
37 		* The module will eventually be renamed to `arsd.terminal`.
38 
39 		* More documentation.
40 	)
41 
42 	WHAT I WON'T DO:
43 	$(LIST
44 		* support everything under the sun. If it isn't default-installed on an OS I or significant number of other people
45 		  might actually use, and isn't written by me, I don't really care about it. This means the only supported terminals are:
46 		  $(LIST
47 
48 		  * xterm (and decently xterm compatible emulators like Konsole)
49 		  * Windows console
50 		  * rxvt (to a lesser extent)
51 		  * Linux console
52 		  * My terminal emulator family of applications https://github.com/adamdruppe/terminal-emulator
53 		  )
54 
55 		  Anything else is cool if it does work, but I don't want to go out of my way for it.
56 
57 		* Use other libraries, unless strictly optional. terminal.d is a stand-alone module by default and
58 		  always will be.
59 
60 		* Do a full TUI widget set. I might do some basics and lay a little groundwork, but a full TUI
61 		  is outside the scope of this module (unless I can do it really small.)
62 	)
63 +/
64 module trial.terminal;
65 
66 
67 /*
68 	Widgets:
69 		tab widget
70 		scrollback buffer
71 		partitioned canvas
72 */
73 
74 // FIXME: ctrl+d eof on stdin
75 
76 // FIXME: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686016%28v=vs.85%29.aspx
77 
78 version(Posix) {
79 	enum SIGWINCH = 28;
80 	__gshared bool windowSizeChanged = false;
81 	__gshared bool interrupted = false; /// you might periodically check this in a long operation and abort if it is set. Remember it is volatile. It is also sent through the input event loop via RealTimeConsoleInput
82 	__gshared bool hangedUp = false; /// similar to interrupted.
83 
84 	version(with_eventloop)
85 		struct SignalFired {}
86 
87 	extern(C)
88 	void sizeSignalHandler(int sigNumber) nothrow {
89 		windowSizeChanged = true;
90 		version(with_eventloop) {
91 			import arsd.eventloop;
92 			try
93 				send(SignalFired());
94 			catch(Exception) {}
95 		}
96 	}
97 	extern(C)
98 	void interruptSignalHandler(int sigNumber) nothrow {
99 		interrupted = true;
100 		version(with_eventloop) {
101 			import arsd.eventloop;
102 			try
103 				send(SignalFired());
104 			catch(Exception) {}
105 		}
106 	}
107 	extern(C)
108 	void hangupSignalHandler(int sigNumber) nothrow {
109 		hangedUp = true;
110 		version(with_eventloop) {
111 			import arsd.eventloop;
112 			try
113 				send(SignalFired());
114 			catch(Exception) {}
115 		}
116 	}
117 
118 }
119 
120 // parts of this were taken from Robik's ConsoleD
121 // https://github.com/robik/ConsoleD/blob/master/consoled.d
122 
123 // Uncomment this line to get a main() to demonstrate this module's
124 // capabilities.
125 //version = Demo
126 
127 version(Windows) {
128 	import core.sys.windows.windows;
129 	import std..string : toStringz;
130 	private {
131 		enum RED_BIT = 4;
132 		enum GREEN_BIT = 2;
133 		enum BLUE_BIT = 1;
134 	}
135 }
136 
137 version(Posix) {
138 	import core.sys.posix.termios;
139 	import core.sys.posix.unistd;
140 	import unix = core.sys.posix.unistd;
141 	import core.sys.posix.sys.types;
142 	import core.sys.posix.sys.time;
143 	import core.stdc.stdio;
144 	private {
145 		enum RED_BIT = 1;
146 		enum GREEN_BIT = 2;
147 		enum BLUE_BIT = 4;
148 	}
149 
150 	version(linux) {
151 		extern(C) int ioctl(int, int, ...);
152 		enum int TIOCGWINSZ = 0x5413;
153 	} else version(OSX) {
154 		import core.stdc.config;
155 		extern(C) int ioctl(int, c_ulong, ...);
156 		enum TIOCGWINSZ = 1074295912;
157 	} else static assert(0, "confirm the value of tiocgwinsz");
158 
159 	struct winsize {
160 		ushort ws_row;
161 		ushort ws_col;
162 		ushort ws_xpixel;
163 		ushort ws_ypixel;
164 	}
165 
166 	// I'm taking this from the minimal termcap from my Slackware box (which I use as my /etc/termcap) and just taking the most commonly used ones (for me anyway).
167 
168 	// this way we'll have some definitions for 99% of typical PC cases even without any help from the local operating system
169 
170 	enum string builtinTermcap = `
171 # Generic VT entry.
172 vg|vt-generic|Generic VT entries:\
173 	:bs:mi:ms:pt:xn:xo:it#8:\
174 	:RA=\E[?7l:SA=\E?7h:\
175 	:bl=^G:cr=^M:ta=^I:\
176 	:cm=\E[%i%d;%dH:\
177 	:le=^H:up=\E[A:do=\E[B:nd=\E[C:\
178 	:LE=\E[%dD:RI=\E[%dC:UP=\E[%dA:DO=\E[%dB:\
179 	:ho=\E[H:cl=\E[H\E[2J:ce=\E[K:cb=\E[1K:cd=\E[J:sf=\ED:sr=\EM:\
180 	:ct=\E[3g:st=\EH:\
181 	:cs=\E[%i%d;%dr:sc=\E7:rc=\E8:\
182 	:ei=\E[4l:ic=\E[@:IC=\E[%d@:al=\E[L:AL=\E[%dL:\
183 	:dc=\E[P:DC=\E[%dP:dl=\E[M:DL=\E[%dM:\
184 	:so=\E[7m:se=\E[m:us=\E[4m:ue=\E[m:\
185 	:mb=\E[5m:mh=\E[2m:md=\E[1m:mr=\E[7m:me=\E[m:\
186 	:sc=\E7:rc=\E8:kb=\177:\
187 	:ku=\E[A:kd=\E[B:kr=\E[C:kl=\E[D:
188 
189 
190 # Slackware 3.1 linux termcap entry (Sat Apr 27 23:03:58 CDT 1996):
191 lx|linux|console|con80x25|LINUX System Console:\
192         :do=^J:co#80:li#25:cl=\E[H\E[J:sf=\ED:sb=\EM:\
193         :le=^H:bs:am:cm=\E[%i%d;%dH:nd=\E[C:up=\E[A:\
194         :ce=\E[K:cd=\E[J:so=\E[7m:se=\E[27m:us=\E[36m:ue=\E[m:\
195         :md=\E[1m:mr=\E[7m:mb=\E[5m:me=\E[m:is=\E[1;25r\E[25;1H:\
196         :ll=\E[1;25r\E[25;1H:al=\E[L:dc=\E[P:dl=\E[M:\
197         :it#8:ku=\E[A:kd=\E[B:kr=\E[C:kl=\E[D:kb=^H:ti=\E[r\E[H:\
198         :ho=\E[H:kP=\E[5~:kN=\E[6~:kH=\E[4~:kh=\E[1~:kD=\E[3~:kI=\E[2~:\
199         :k1=\E[[A:k2=\E[[B:k3=\E[[C:k4=\E[[D:k5=\E[[E:k6=\E[17~:\
200 	:F1=\E[23~:F2=\E[24~:\
201         :k7=\E[18~:k8=\E[19~:k9=\E[20~:k0=\E[21~:K1=\E[1~:K2=\E[5~:\
202         :K4=\E[4~:K5=\E[6~:\
203         :pt:sr=\EM:vt#3:xn:km:bl=^G:vi=\E[?25l:ve=\E[?25h:vs=\E[?25h:\
204         :sc=\E7:rc=\E8:cs=\E[%i%d;%dr:\
205         :r1=\Ec:r2=\Ec:r3=\Ec:
206 
207 # Some other, commonly used linux console entries.
208 lx|con80x28:co#80:li#28:tc=linux:
209 lx|con80x43:co#80:li#43:tc=linux:
210 lx|con80x50:co#80:li#50:tc=linux:
211 lx|con100x37:co#100:li#37:tc=linux:
212 lx|con100x40:co#100:li#40:tc=linux:
213 lx|con132x43:co#132:li#43:tc=linux:
214 
215 # vt102 - vt100 + insert line etc. VT102 does not have insert character.
216 v2|vt102|DEC vt102 compatible:\
217 	:co#80:li#24:\
218 	:ic@:IC@:\
219 	:is=\E[m\E[?1l\E>:\
220 	:rs=\E[m\E[?1l\E>:\
221 	:eA=\E)0:as=^N:ae=^O:ac=aaffggjjkkllmmnnooqqssttuuvvwwxx:\
222 	:ks=:ke=:\
223 	:k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:\
224 	:tc=vt-generic:
225 
226 # vt100 - really vt102 without insert line, insert char etc.
227 vt|vt100|DEC vt100 compatible:\
228 	:im@:mi@:al@:dl@:ic@:dc@:AL@:DL@:IC@:DC@:\
229 	:tc=vt102:
230 
231 
232 # Entry for an xterm. Insert mode has been disabled.
233 vs|xterm|xterm-color|xterm-256color|vs100|xterm terminal emulator (X Window System):\
234 	:am:bs:mi@:km:co#80:li#55:\
235 	:im@:ei@:\
236 	:cl=\E[H\E[J:\
237 	:ct=\E[3k:ue=\E[m:\
238 	:is=\E[m\E[?1l\E>:\
239 	:rs=\E[m\E[?1l\E>:\
240 	:vi=\E[?25l:ve=\E[?25h:\
241 	:eA=\E)0:as=^N:ae=^O:ac=aaffggjjkkllmmnnooqqssttuuvvwwxx:\
242 	:kI=\E[2~:kD=\E[3~:kP=\E[5~:kN=\E[6~:\
243 	:k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:k5=\E[15~:\
244 	:k6=\E[17~:k7=\E[18~:k8=\E[19~:k9=\E[20~:k0=\E[21~:\
245 	:F1=\E[23~:F2=\E[24~:\
246 	:kh=\E[H:kH=\E[F:\
247 	:ks=:ke=:\
248 	:te=\E[2J\E[?47l\E8:ti=\E7\E[?47h:\
249 	:tc=vt-generic:
250 
251 
252 #rxvt, added by me
253 rxvt|rxvt-unicode:\
254 	:am:bs:mi@:km:co#80:li#55:\
255 	:im@:ei@:\
256 	:ct=\E[3k:ue=\E[m:\
257 	:is=\E[m\E[?1l\E>:\
258 	:rs=\E[m\E[?1l\E>:\
259 	:vi=\E[?25l:\
260 	:ve=\E[?25h:\
261 	:eA=\E)0:as=^N:ae=^O:ac=aaffggjjkkllmmnnooqqssttuuvvwwxx:\
262 	:kI=\E[2~:kD=\E[3~:kP=\E[5~:kN=\E[6~:\
263 	:k1=\E[11~:k2=\E[12~:k3=\E[13~:k4=\E[14~:k5=\E[15~:\
264 	:k6=\E[17~:k7=\E[18~:k8=\E[19~:k9=\E[20~:k0=\E[21~:\
265 	:F1=\E[23~:F2=\E[24~:\
266 	:kh=\E[7~:kH=\E[8~:\
267 	:ks=:ke=:\
268 	:te=\E[2J\E[?47l\E8:ti=\E7\E[?47h:\
269 	:tc=vt-generic:
270 
271 
272 # Some other entries for the same xterm.
273 v2|xterms|vs100s|xterm small window:\
274 	:co#80:li#24:tc=xterm:
275 vb|xterm-bold|xterm with bold instead of underline:\
276 	:us=\E[1m:tc=xterm:
277 vi|xterm-ins|xterm with insert mode:\
278 	:mi:im=\E[4h:ei=\E[4l:tc=xterm:
279 
280 Eterm|Eterm Terminal Emulator (X11 Window System):\
281         :am:bw:eo:km:mi:ms:xn:xo:\
282         :co#80:it#8:li#24:lm#0:pa#64:Co#8:AF=\E[3%dm:AB=\E[4%dm:op=\E[39m\E[49m:\
283         :AL=\E[%dL:DC=\E[%dP:DL=\E[%dM:DO=\E[%dB:IC=\E[%d@:\
284         :K1=\E[7~:K2=\EOu:K3=\E[5~:K4=\E[8~:K5=\E[6~:LE=\E[%dD:\
285         :RI=\E[%dC:UP=\E[%dA:ae=^O:al=\E[L:as=^N:bl=^G:cd=\E[J:\
286         :ce=\E[K:cl=\E[H\E[2J:cm=\E[%i%d;%dH:cr=^M:\
287         :cs=\E[%i%d;%dr:ct=\E[3g:dc=\E[P:dl=\E[M:do=\E[B:\
288         :ec=\E[%dX:ei=\E[4l:ho=\E[H:i1=\E[?47l\E>\E[?1l:ic=\E[@:\
289         :im=\E[4h:is=\E[r\E[m\E[2J\E[H\E[?7h\E[?1;3;4;6l\E[4l:\
290         :k1=\E[11~:k2=\E[12~:k3=\E[13~:k4=\E[14~:k5=\E[15~:\
291         :k6=\E[17~:k7=\E[18~:k8=\E[19~:k9=\E[20~:kD=\E[3~:\
292         :kI=\E[2~:kN=\E[6~:kP=\E[5~:kb=^H:kd=\E[B:ke=:kh=\E[7~:\
293         :kl=\E[D:kr=\E[C:ks=:ku=\E[A:le=^H:mb=\E[5m:md=\E[1m:\
294         :me=\E[m\017:mr=\E[7m:nd=\E[C:rc=\E8:\
295         :sc=\E7:se=\E[27m:sf=^J:so=\E[7m:sr=\EM:st=\EH:ta=^I:\
296         :te=\E[2J\E[?47l\E8:ti=\E7\E[?47h:ue=\E[24m:up=\E[A:\
297         :us=\E[4m:vb=\E[?5h\E[?5l:ve=\E[?25h:vi=\E[?25l:\
298         :ac=aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~:
299 
300 # DOS terminal emulator such as Telix or TeleMate.
301 # This probably also works for the SCO console, though it's incomplete.
302 an|ansi|ansi-bbs|ANSI terminals (emulators):\
303 	:co#80:li#24:am:\
304 	:is=:rs=\Ec:kb=^H:\
305 	:as=\E[m:ae=:eA=:\
306 	:ac=0\333+\257,\256.\031-\030a\261f\370g\361j\331k\277l\332m\300n\305q\304t\264u\303v\301w\302x\263~\025:\
307 	:kD=\177:kH=\E[Y:kN=\E[U:kP=\E[V:kh=\E[H:\
308 	:k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:k5=\EOT:\
309 	:k6=\EOU:k7=\EOV:k8=\EOW:k9=\EOX:k0=\EOY:\
310 	:tc=vt-generic:
311 
312 	`;
313 }
314 
315 enum Bright = 0x08;
316 
317 /// Defines the list of standard colors understood by Terminal.
318 enum Color : ushort {
319 	black = 0, /// .
320 	red = RED_BIT, /// .
321 	green = GREEN_BIT, /// .
322 	yellow = red | green, /// .
323 	blue = BLUE_BIT, /// .
324 	magenta = red | blue, /// .
325 	cyan = blue | green, /// .
326 	white = red | green | blue, /// .
327 	DEFAULT = 256,
328 }
329 
330 /// When capturing input, what events are you interested in?
331 ///
332 /// Note: these flags can be OR'd together to select more than one option at a time.
333 ///
334 /// Ctrl+C and other keyboard input is always captured, though it may be line buffered if you don't use raw.
335 /// The rationale for that is to ensure the Terminal destructor has a chance to run, since the terminal is a shared resource and should be put back before the program terminates.
336 enum ConsoleInputFlags {
337 	raw = 0, /// raw input returns keystrokes immediately, without line buffering
338 	echo = 1, /// do you want to automatically echo input back to the user?
339 	mouse = 2, /// capture mouse events
340 	paste = 4, /// capture paste events (note: without this, paste can come through as keystrokes)
341 	size = 8, /// window resize events
342 
343 	releasedKeys = 64, /// key release events. Not reliable on Posix.
344 
345 	allInputEvents = 8|4|2, /// subscribe to all input events. Note: in previous versions, this also returned release events. It no longer does, use allInputEventsWithRelease if you want them.
346 	allInputEventsWithRelease = allInputEvents|releasedKeys, /// subscribe to all input events, including (unreliable on Posix) key release events.
347 }
348 
349 /// Defines how terminal output should be handled.
350 enum ConsoleOutputType {
351 	linear = 0, /// do you want output to work one line at a time?
352 	cellular = 1, /// or do you want access to the terminal screen as a grid of characters?
353 	//truncatedCellular = 3, /// cellular, but instead of wrapping output to the next line automatically, it will truncate at the edges
354 
355 	minimalProcessing = 255, /// do the least possible work, skips most construction and desturction tasks. Only use if you know what you're doing here
356 }
357 
358 /// Some methods will try not to send unnecessary commands to the screen. You can override their judgement using a ForceOption parameter, if present
359 enum ForceOption {
360 	automatic = 0, /// automatically decide what to do (best, unless you know for sure it isn't right)
361 	neverSend = -1, /// never send the data. This will only update Terminal's internal state. Use with caution.
362 	alwaysSend = 1, /// always send the data, even if it doesn't seem necessary
363 }
364 
365 // we could do it with termcap too, getenv("TERMCAP") then split on : and replace \E with \033 and get the pieces
366 
367 /// Encapsulates the I/O capabilities of a terminal.
368 ///
369 /// Warning: do not write out escape sequences to the terminal. This won't work
370 /// on Windows and will confuse Terminal's internal state on Posix.
371 struct Terminal {
372 	///
373 	@disable this();
374 	@disable this(this);
375 	private ConsoleOutputType type;
376 
377 	version(Posix) {
378 		private int fdOut;
379 		private int fdIn;
380 		private int[] delegate() getSizeOverride;
381 		void delegate(in void[]) _writeDelegate; // used to override the unix write() system call, set it magically
382 	}
383 
384 	version(Posix) {
385 		bool terminalInFamily(string[] terms...) {
386 			import std.process;
387 			import std..string;
388 			auto term = environment.get("TERM");
389 			foreach(t; terms)
390 				if(indexOf(term, t) != -1)
391 					return true;
392 
393 			return false;
394 		}
395 
396 		// This is a filthy hack because Terminal.app and OS X are garbage who don't
397 		// work the way they're advertised. I just have to best-guess hack and hope it
398 		// doesn't break anything else. (If you know a better way, let me know!)
399 		bool isMacTerminal() {
400 			import std.process;
401 			import std..string;
402 			auto term = environment.get("TERM");
403 			return term == "xterm-256color";
404 		}
405 
406 		static string[string] termcapDatabase;
407 		static void readTermcapFile(bool useBuiltinTermcap = false) {
408 			import std.file;
409 			import std.stdio;
410 			import std..string;
411 
412 			if(!exists("/etc/termcap"))
413 				useBuiltinTermcap = true;
414 
415 			string current;
416 
417 			void commitCurrentEntry() {
418 				if(current is null)
419 					return;
420 
421 				string names = current;
422 				auto idx = indexOf(names, ":");
423 				if(idx != -1)
424 					names = names[0 .. idx];
425 
426 				foreach(name; split(names, "|"))
427 					termcapDatabase[name] = current;
428 
429 				current = null;
430 			}
431 
432 			void handleTermcapLine(in char[] line) {
433 				if(line.length == 0) { // blank
434 					commitCurrentEntry();
435 					return; // continue
436 				}
437 				if(line[0] == '#') // comment
438 					return; // continue
439 				size_t termination = line.length;
440 				if(line[$-1] == '\\')
441 					termination--; // cut off the \\
442 				current ~= strip(line[0 .. termination]);
443 				// termcap entries must be on one logical line, so if it isn't continued, we know we're done
444 				if(line[$-1] != '\\')
445 					commitCurrentEntry();
446 			}
447 
448 			if(useBuiltinTermcap) {
449 				foreach(line; splitLines(builtinTermcap)) {
450 					handleTermcapLine(line);
451 				}
452 			} else {
453 				foreach(line; File("/etc/termcap").byLine()) {
454 					handleTermcapLine(line);
455 				}
456 			}
457 		}
458 
459 		static string getTermcapDatabase(string terminal) {
460 			import std..string;
461 
462 			if(termcapDatabase is null)
463 				readTermcapFile();
464 
465 			auto data = terminal in termcapDatabase;
466 			if(data is null)
467 				return null;
468 
469 			auto tc = *data;
470 			auto more = indexOf(tc, ":tc=");
471 			if(more != -1) {
472 				auto tcKey = tc[more + ":tc=".length .. $];
473 				auto end = indexOf(tcKey, ":");
474 				if(end != -1)
475 					tcKey = tcKey[0 .. end];
476 				tc = getTermcapDatabase(tcKey) ~ tc;
477 			}
478 
479 			return tc;
480 		}
481 
482 		string[string] termcap;
483 		void readTermcap() {
484 			import std.process;
485 			import std..string;
486 			import std.array;
487 
488 			string termcapData = environment.get("TERMCAP");
489 			if(termcapData.length == 0) {
490 				termcapData = getTermcapDatabase(environment.get("TERM"));
491 			}
492 
493 			auto e = replace(termcapData, "\\\n", "\n");
494 			termcap = null;
495 
496 			foreach(part; split(e, ":")) {
497 				// FIXME: handle numeric things too
498 
499 				auto things = split(part, "=");
500 				if(things.length)
501 					termcap[things[0]] =
502 						things.length > 1 ? things[1] : null;
503 			}
504 		}
505 
506 		string findSequenceInTermcap(in char[] sequenceIn) {
507 			char[10] sequenceBuffer;
508 			char[] sequence;
509 			if(sequenceIn.length > 0 && sequenceIn[0] == '\033') {
510 				if(!(sequenceIn.length < sequenceBuffer.length - 1))
511 					return null;
512 				sequenceBuffer[1 .. sequenceIn.length + 1] = sequenceIn[];
513 				sequenceBuffer[0] = '\\';
514 				sequenceBuffer[1] = 'E';
515 				sequence = sequenceBuffer[0 .. sequenceIn.length + 1];
516 			} else {
517 				sequence = sequenceBuffer[1 .. sequenceIn.length + 1];
518 			}
519 
520 			import std.array;
521 			foreach(k, v; termcap)
522 				if(v == sequence)
523 					return k;
524 			return null;
525 		}
526 
527 		string getTermcap(string key) {
528 			auto k = key in termcap;
529 			if(k !is null) return *k;
530 			return null;
531 		}
532 
533 		// Looks up a termcap item and tries to execute it. Returns false on failure
534 		bool doTermcap(T...)(string key, T t) {
535 			import std.conv;
536 			auto fs = getTermcap(key);
537 			if(fs is null)
538 				return false;
539 
540 			int swapNextTwo = 0;
541 
542 			R getArg(R)(int idx) {
543 				if(swapNextTwo == 2) {
544 					idx ++;
545 					swapNextTwo--;
546 				} else if(swapNextTwo == 1) {
547 					idx --;
548 					swapNextTwo--;
549 				}
550 
551 				foreach(i, arg; t) {
552 					if(i == idx)
553 						return to!R(arg);
554 				}
555 				assert(0, to!string(idx) ~ " is out of bounds working " ~ fs);
556 			}
557 
558 			char[256] buffer;
559 			int bufferPos = 0;
560 
561 			void addChar(char c) {
562 				import std.exception;
563 				enforce(bufferPos < buffer.length);
564 				buffer[bufferPos++] = c;
565 			}
566 
567 			void addString(in char[] c) {
568 				import std.exception;
569 				enforce(bufferPos + c.length < buffer.length);
570 				buffer[bufferPos .. bufferPos + c.length] = c[];
571 				bufferPos += c.length;
572 			}
573 
574 			void addInt(int c, int minSize) {
575 				import std..string;
576 				auto str = format("%0"~(minSize ? to!string(minSize) : "")~"d", c);
577 				addString(str);
578 			}
579 
580 			bool inPercent;
581 			int argPosition = 0;
582 			int incrementParams = 0;
583 			bool skipNext;
584 			bool nextIsChar;
585 			bool inBackslash;
586 
587 			foreach(char c; fs) {
588 				if(inBackslash) {
589 					if(c == 'E')
590 						addChar('\033');
591 					else
592 						addChar(c);
593 					inBackslash = false;
594 				} else if(nextIsChar) {
595 					if(skipNext)
596 						skipNext = false;
597 					else
598 						addChar(cast(char) (c + getArg!int(argPosition) + (incrementParams ? 1 : 0)));
599 					if(incrementParams) incrementParams--;
600 					argPosition++;
601 					inPercent = false;
602 				} else if(inPercent) {
603 					switch(c) {
604 						case '%':
605 							addChar('%');
606 							inPercent = false;
607 						break;
608 						case '2':
609 						case '3':
610 						case 'd':
611 							if(skipNext)
612 								skipNext = false;
613 							else
614 								addInt(getArg!int(argPosition) + (incrementParams ? 1 : 0),
615 									c == 'd' ? 0 : (c - '0')
616 								);
617 							if(incrementParams) incrementParams--;
618 							argPosition++;
619 							inPercent = false;
620 						break;
621 						case '.':
622 							if(skipNext)
623 								skipNext = false;
624 							else
625 								addChar(cast(char) (getArg!int(argPosition) + (incrementParams ? 1 : 0)));
626 							if(incrementParams) incrementParams--;
627 							argPosition++;
628 						break;
629 						case '+':
630 							nextIsChar = true;
631 							inPercent = false;
632 						break;
633 						case 'i':
634 							incrementParams = 2;
635 							inPercent = false;
636 						break;
637 						case 's':
638 							skipNext = true;
639 							inPercent = false;
640 						break;
641 						case 'b':
642 							argPosition--;
643 							inPercent = false;
644 						break;
645 						case 'r':
646 							swapNextTwo = 2;
647 							inPercent = false;
648 						break;
649 						// FIXME: there's more
650 						// http://www.gnu.org/software/termutils/manual/termcap-1.3/html_mono/termcap.html
651 
652 						default:
653 							assert(0, "not supported " ~ c);
654 					}
655 				} else {
656 					if(c == '%')
657 						inPercent = true;
658 					else if(c == '\\')
659 						inBackslash = true;
660 					else
661 						addChar(c);
662 				}
663 			}
664 
665 			writeStringRaw(buffer[0 .. bufferPos]);
666 			return true;
667 		}
668 	}
669 
670 	version(Posix)
671 	/**
672 	 * Constructs an instance of Terminal representing the capabilities of
673 	 * the current terminal.
674 	 *
675 	 * While it is possible to override the stdin+stdout file descriptors, remember
676 	 * that is not portable across platforms and be sure you know what you're doing.
677 	 *
678 	 * ditto on getSizeOverride. That's there so you can do something instead of ioctl.
679 	 */
680 	this(ConsoleOutputType type, int fdIn = 0, int fdOut = 1, int[] delegate() getSizeOverride = null) {
681 		this.fdIn = fdIn;
682 		this.fdOut = fdOut;
683 		this.getSizeOverride = getSizeOverride;
684 		this.type = type;
685 
686 		readTermcap();
687 
688 		if(type == ConsoleOutputType.minimalProcessing) {
689 			_suppressDestruction = true;
690 			return;
691 		}
692 
693 		if(type == ConsoleOutputType.cellular) {
694 			doTermcap("ti");
695 			clear();
696 			moveTo(0, 0, ForceOption.alwaysSend); // we need to know where the cursor is for some features to work, and moving it is easier than querying it
697 		}
698 
699 		if(terminalInFamily("xterm", "rxvt", "screen")) {
700 			writeStringRaw("\033[22;0t"); // save window title on a stack (support seems spotty, but it doesn't hurt to have it)
701 		}
702 	}
703 
704 	version(Windows) {
705 		HANDLE hConsole;
706 		CONSOLE_SCREEN_BUFFER_INFO originalSbi;
707 	}
708 
709 	version(Windows)
710 	/// ditto
711 	this(ConsoleOutputType type) {
712 		if(type == ConsoleOutputType.cellular) {
713 			hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, null, CONSOLE_TEXTMODE_BUFFER, null);
714 			if(hConsole == INVALID_HANDLE_VALUE) {
715 				import std.conv;
716 				throw new Exception(to!string(GetLastError()));
717 			}
718 
719 			SetConsoleActiveScreenBuffer(hConsole);
720 			/*
721 http://msdn.microsoft.com/en-us/library/windows/desktop/ms686125%28v=vs.85%29.aspx
722 http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.aspx
723 			*/
724 			COORD size;
725 			/*
726 			CONSOLE_SCREEN_BUFFER_INFO sbi;
727 			GetConsoleScreenBufferInfo(hConsole, &sbi);
728 			size.X = cast(short) GetSystemMetrics(SM_CXMIN);
729 			size.Y = cast(short) GetSystemMetrics(SM_CYMIN);
730 			*/
731 
732 			// FIXME: this sucks, maybe i should just revert it. but there shouldn't be scrollbars in cellular mode
733 			//size.X = 80;
734 			//size.Y = 24;
735 			//SetConsoleScreenBufferSize(hConsole, size);
736 
737 			clear();
738 		} else {
739 			hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
740 		}
741 
742 		GetConsoleScreenBufferInfo(hConsole, &originalSbi);
743 	}
744 
745 	// only use this if you are sure you know what you want, since the terminal is a shared resource you generally really want to reset it to normal when you leave...
746 	bool _suppressDestruction;
747 
748 	version(Posix)
749 	~this() {
750 		if(_suppressDestruction) {
751 			flush();
752 			return;
753 		}
754 		if(type == ConsoleOutputType.cellular) {
755 			doTermcap("te");
756 		}
757 		if(terminalInFamily("xterm", "rxvt", "screen")) {
758 			writeStringRaw("\033[23;0t"); // restore window title from the stack
759 		}
760 		showCursor();
761 		reset();
762 		flush();
763 
764 		if(lineGetter !is null)
765 			lineGetter.dispose();
766 	}
767 
768 	version(Windows)
769 	~this() {
770 		flush(); // make sure user data is all flushed before resetting
771 		reset();
772 		showCursor();
773 
774 		if(lineGetter !is null)
775 			lineGetter.dispose();
776 
777 		auto stdo = GetStdHandle(STD_OUTPUT_HANDLE);
778 		SetConsoleActiveScreenBuffer(stdo);
779 		if(hConsole !is stdo)
780 			CloseHandle(hConsole);
781 	}
782 
783 	// lazily initialized and preserved between calls to getline for a bit of efficiency (only a bit)
784 	// and some history storage.
785 	LineGetter lineGetter;
786 
787 	int _currentForeground = Color.DEFAULT;
788 	int _currentBackground = Color.DEFAULT;
789 	RGB _currentForegroundRGB;
790 	RGB _currentBackgroundRGB;
791 	bool reverseVideo = false;
792 
793 	/++
794 		Attempts to set color according to a 24 bit value (r, g, b, each >= 0 and < 256).
795 
796 
797 		This is not supported on all terminals. It will attempt to fall back to a 256-color
798 		or 8-color palette in those cases automatically.
799 
800 		Returns: true if it believes it was successful (note that it cannot be completely sure),
801 		false if it had to use a fallback.
802 	+/
803 	bool setTrueColor(RGB foreground, RGB background, ForceOption force = ForceOption.automatic) {
804 		if(force == ForceOption.neverSend) {
805 			_currentForeground = -1;
806 			_currentBackground = -1;
807 			_currentForegroundRGB = foreground;
808 			_currentBackgroundRGB = background;
809 			return true;
810 		}
811 
812 		if(force == ForceOption.automatic && _currentForeground == -1 && _currentBackground == -1 && (_currentForegroundRGB == foreground && _currentBackgroundRGB == background))
813 			return true;
814 
815 		_currentForeground = -1;
816 		_currentBackground = -1;
817 		_currentForegroundRGB = foreground;
818 		_currentBackgroundRGB = background;
819 
820 		version(Windows) {
821 			flush();
822 			ushort setTob = cast(ushort) approximate16Color(background);
823 			ushort setTof = cast(ushort) approximate16Color(foreground);
824 			SetConsoleTextAttribute(
825 				hConsole,
826 				cast(ushort)((setTob << 4) | setTof));
827 			return false;
828 		} else {
829 			// FIXME: if the terminal reliably does support 24 bit color, use it
830 			// instead of the round off. But idk how to detect that yet...
831 
832 			// fallback to 16 color for term that i know don't take it well
833 			import std.process;
834 			import std..string;
835 			if(environment.get("TERM") == "rxvt" || environment.get("TERM") == "linux") {
836 				// not likely supported, use 16 color fallback
837 				auto setTof = approximate16Color(foreground);
838 				auto setTob = approximate16Color(background);
839 
840 				writeStringRaw(format("\033[%dm\033[3%dm\033[4%dm",
841 					(setTof & Bright) ? 1 : 0,
842 					cast(int) (setTof & ~Bright),
843 					cast(int) (setTob & ~Bright)
844 				));
845 
846 				return false;
847 			}
848 
849 			// otherwise, assume it is probably supported and give it a try
850 			writeStringRaw(format("\033[38;5;%dm\033[48;5;%dm",
851 				colorToXTermPaletteIndex(foreground),
852 				colorToXTermPaletteIndex(background)
853 			));
854 
855 			return true;
856 		}
857 	}
858 
859 	/// Changes the current color. See enum Color for the values.
860 	void color(int foreground, int background, ForceOption force = ForceOption.automatic, bool reverseVideo = false) {
861 		if(force != ForceOption.neverSend) {
862 			version(Windows) {
863 				// assuming a dark background on windows, so LowContrast == dark which means the bit is NOT set on hardware
864 				/*
865 				foreground ^= LowContrast;
866 				background ^= LowContrast;
867 				*/
868 
869 				ushort setTof = cast(ushort) foreground;
870 				ushort setTob = cast(ushort) background;
871 
872 				// this isn't necessarily right but meh
873 				if(background == Color.DEFAULT)
874 					setTob = Color.black;
875 				if(foreground == Color.DEFAULT)
876 					setTof = Color.white;
877 
878 				if(force == ForceOption.alwaysSend || reverseVideo != this.reverseVideo || foreground != _currentForeground || background != _currentBackground) {
879 					flush(); // if we don't do this now, the buffering can screw up the colors...
880 					if(reverseVideo) {
881 						if(background == Color.DEFAULT)
882 							setTof = Color.black;
883 						else
884 							setTof = cast(ushort) background | (foreground & Bright);
885 
886 						if(background == Color.DEFAULT)
887 							setTob = Color.white;
888 						else
889 							setTob = cast(ushort) (foreground & ~Bright);
890 					}
891 					SetConsoleTextAttribute(
892 						hConsole,
893 						cast(ushort)((setTob << 4) | setTof));
894 				}
895 			} else {
896 				import std.process;
897 				// I started using this envvar for my text editor, but now use it elsewhere too
898 				// if we aren't set to dark, assume light
899 				/*
900 				if(getenv("ELVISBG") == "dark") {
901 					// LowContrast on dark bg menas
902 				} else {
903 					foreground ^= LowContrast;
904 					background ^= LowContrast;
905 				}
906 				*/
907 
908 				ushort setTof = cast(ushort) foreground & ~Bright;
909 				ushort setTob = cast(ushort) background & ~Bright;
910 
911 				if(foreground & Color.DEFAULT)
912 					setTof = 9; // ansi sequence for reset
913 				if(background == Color.DEFAULT)
914 					setTob = 9;
915 
916 				import std..string;
917 
918 				if(force == ForceOption.alwaysSend || reverseVideo != this.reverseVideo || foreground != _currentForeground || background != _currentBackground) {
919 					writeStringRaw(format("\033[%dm\033[3%dm\033[4%dm\033[%dm",
920 						(foreground != Color.DEFAULT && (foreground & Bright)) ? 1 : 0,
921 						cast(int) setTof,
922 						cast(int) setTob,
923 						reverseVideo ? 7 : 27
924 					));
925 				}
926 			}
927 		}
928 
929 		_currentForeground = foreground;
930 		_currentBackground = background;
931 		this.reverseVideo = reverseVideo;
932 	}
933 
934 	private bool _underlined = false;
935 
936 	/// Note: the Windows console does not support underlining
937 	void underline(bool set, ForceOption force = ForceOption.automatic) {
938 		if(set == _underlined && force != ForceOption.alwaysSend)
939 			return;
940 		version(Posix) {
941 			if(set)
942 				writeStringRaw("\033[4m");
943 			else
944 				writeStringRaw("\033[24m");
945 		}
946 		_underlined = set;
947 	}
948 	// FIXME: do I want to do bold and italic?
949 
950 	/// Returns the terminal to normal output colors
951 	void reset() {
952 		version(Windows)
953 			SetConsoleTextAttribute(
954 				hConsole,
955 				originalSbi.wAttributes);
956 		else
957 			writeStringRaw("\033[0m");
958 
959 		_underlined = false;
960 		_currentForeground = Color.DEFAULT;
961 		_currentBackground = Color.DEFAULT;
962 		reverseVideo = false;
963 	}
964 
965 	// FIXME: add moveRelative
966 
967 	/// The current x position of the output cursor. 0 == leftmost column
968 	@property int cursorX() {
969 		return _cursorX;
970 	}
971 
972 	/// The current y position of the output cursor. 0 == topmost row
973 	@property int cursorY() {
974 		return _cursorY;
975 	}
976 
977 	private int _cursorX;
978 	private int _cursorY;
979 
980 	/// Moves the output cursor to the given position. (0, 0) is the upper left corner of the screen. The force parameter can be used to force an update, even if Terminal doesn't think it is necessary
981 	void moveTo(int x, int y, ForceOption force = ForceOption.automatic) {
982 		if(force != ForceOption.neverSend && (force == ForceOption.alwaysSend || x != _cursorX || y != _cursorY)) {
983 			executeAutoHideCursor();
984 			version(Posix) {
985 				doTermcap("cm", y, x);
986 			} else version(Windows) {
987 
988 				flush(); // if we don't do this now, the buffering can screw up the position
989 				COORD coord = {cast(short) x, cast(short) y};
990 				SetConsoleCursorPosition(hConsole, coord);
991 			} else static assert(0);
992 		}
993 
994 		_cursorX = x;
995 		_cursorY = y;
996 	}
997 
998 	/// shows the cursor
999 	void showCursor() {
1000 		version(Posix)
1001 			doTermcap("ve");
1002 		else {
1003 			CONSOLE_CURSOR_INFO info;
1004 			GetConsoleCursorInfo(hConsole, &info);
1005 			info.bVisible = true;
1006 			SetConsoleCursorInfo(hConsole, &info);
1007 		}
1008 	}
1009 
1010 	/// hides the cursor
1011 	void hideCursor() {
1012 		version(Posix) {
1013 			doTermcap("vi");
1014 		} else {
1015 			CONSOLE_CURSOR_INFO info;
1016 			GetConsoleCursorInfo(hConsole, &info);
1017 			info.bVisible = false;
1018 			SetConsoleCursorInfo(hConsole, &info);
1019 		}
1020 
1021 	}
1022 
1023 	private bool autoHidingCursor;
1024 	private bool autoHiddenCursor;
1025 	// explicitly not publicly documented
1026 	// Sets the cursor to automatically insert a hide command at the front of the output buffer iff it is moved.
1027 	// Call autoShowCursor when you are done with the batch update.
1028 	void autoHideCursor() {
1029 		autoHidingCursor = true;
1030 	}
1031 
1032 	private void executeAutoHideCursor() {
1033 		if(autoHidingCursor) {
1034 			version(Windows)
1035 				hideCursor();
1036 			else version(Posix) {
1037 				// prepend the hide cursor command so it is the first thing flushed
1038 				writeBuffer = "\033[?25l" ~ writeBuffer;
1039 			}
1040 
1041 			autoHiddenCursor = true;
1042 			autoHidingCursor = false; // already been done, don't insert the command again
1043 		}
1044 	}
1045 
1046 	// explicitly not publicly documented
1047 	// Shows the cursor if it was automatically hidden by autoHideCursor and resets the internal auto hide state.
1048 	void autoShowCursor() {
1049 		if(autoHiddenCursor)
1050 			showCursor();
1051 
1052 		autoHidingCursor = false;
1053 		autoHiddenCursor = false;
1054 	}
1055 
1056 	/*
1057 	// alas this doesn't work due to a bunch of delegate context pointer and postblit problems
1058 	// instead of using: auto input = terminal.captureInput(flags)
1059 	// use: auto input = RealTimeConsoleInput(&terminal, flags);
1060 	/// Gets real time input, disabling line buffering
1061 	RealTimeConsoleInput captureInput(ConsoleInputFlags flags) {
1062 		return RealTimeConsoleInput(&this, flags);
1063 	}
1064 	*/
1065 
1066 	/// Changes the terminal's title
1067 	void setTitle(string t) {
1068 		version(Windows) {
1069 			SetConsoleTitleA(toStringz(t));
1070 		} else {
1071 			import std..string;
1072 			if(terminalInFamily("xterm", "rxvt", "screen"))
1073 				writeStringRaw(format("\033]0;%s\007", t));
1074 		}
1075 	}
1076 
1077 	/// Flushes your updates to the terminal.
1078 	/// It is important to call this when you are finished writing for now if you are using the version=with_eventloop
1079 	void flush() {
1080 		if(writeBuffer.length == 0)
1081 			return;
1082 
1083 		version(Posix) {
1084 			if(_writeDelegate !is null) {
1085 				_writeDelegate(writeBuffer);
1086 			} else {
1087 				ssize_t written;
1088 
1089 				while(writeBuffer.length) {
1090 					written = unix.write(this.fdOut, writeBuffer.ptr, writeBuffer.length);
1091 					if(written < 0)
1092 						throw new Exception("write failed for some reason");
1093 					writeBuffer = writeBuffer[written .. $];
1094 				}
1095 			}
1096 		} else version(Windows) {
1097 			import std.conv;
1098 			// FIXME: I'm not sure I'm actually happy with this allocation but
1099 			// it probably isn't a big deal. At least it has unicode support now.
1100 			wstring writeBufferw = to!wstring(writeBuffer);
1101 			while(writeBufferw.length) {
1102 				DWORD written;
1103 				WriteConsoleW(hConsole, writeBufferw.ptr, cast(DWORD)writeBufferw.length, &written, null);
1104 				writeBufferw = writeBufferw[written .. $];
1105 			}
1106 
1107 			writeBuffer = null;
1108 		}
1109 	}
1110 
1111 	int[] getSize() {
1112 		version(Windows) {
1113 			CONSOLE_SCREEN_BUFFER_INFO info;
1114 			GetConsoleScreenBufferInfo( hConsole, &info );
1115 
1116 			int cols, rows;
1117 
1118 			cols = (info.srWindow.Right - info.srWindow.Left + 1);
1119 			rows = (info.srWindow.Bottom - info.srWindow.Top + 1);
1120 
1121 			return [cols, rows];
1122 		} else {
1123 			if(getSizeOverride is null) {
1124 				winsize w;
1125 				ioctl(0, TIOCGWINSZ, &w);
1126 				return [w.ws_col, w.ws_row];
1127 			} else return getSizeOverride();
1128 		}
1129 	}
1130 
1131 	void updateSize() {
1132 		auto size = getSize();
1133 		_width = size[0];
1134 		_height = size[1];
1135 	}
1136 
1137 	private int _width;
1138 	private int _height;
1139 
1140 	/// The current width of the terminal (the number of columns)
1141 	@property int width() {
1142 		if(_width == 0 || _height == 0)
1143 			updateSize();
1144 		return _width;
1145 	}
1146 
1147 	/// The current height of the terminal (the number of rows)
1148 	@property int height() {
1149 		if(_width == 0 || _height == 0)
1150 			updateSize();
1151 		return _height;
1152 	}
1153 
1154 	/*
1155 	void write(T...)(T t) {
1156 		foreach(arg; t) {
1157 			writeStringRaw(to!string(arg));
1158 		}
1159 	}
1160 	*/
1161 
1162 	/// Writes to the terminal at the current cursor position.
1163 	void writef(T...)(string f, T t) {
1164 		import std..string;
1165 		writePrintableString(format(f, t));
1166 	}
1167 
1168 	/// ditto
1169 	void writefln(T...)(string f, T t) {
1170 		writef(f ~ "\n", t);
1171 	}
1172 
1173 	/// ditto
1174 	void write(T...)(T t) {
1175 		import std.conv;
1176 		string data;
1177 		foreach(arg; t) {
1178 			data ~= to!string(arg);
1179 		}
1180 
1181 		writePrintableString(data);
1182 	}
1183 
1184 	/// ditto
1185 	void writeln(T...)(T t) {
1186 		write(t, "\n");
1187 	}
1188 
1189 	/+
1190 	/// A combined moveTo and writef that puts the cursor back where it was before when it finishes the write.
1191 	/// Only works in cellular mode.
1192 	/// Might give better performance than moveTo/writef because if the data to write matches the internal buffer, it skips sending anything (to override the buffer check, you can use moveTo and writePrintableString with ForceOption.alwaysSend)
1193 	void writefAt(T...)(int x, int y, string f, T t) {
1194 		import std.string;
1195 		auto toWrite = format(f, t);
1196 
1197 		auto oldX = _cursorX;
1198 		auto oldY = _cursorY;
1199 
1200 		writeAtWithoutReturn(x, y, toWrite);
1201 
1202 		moveTo(oldX, oldY);
1203 	}
1204 
1205 	void writeAtWithoutReturn(int x, int y, in char[] data) {
1206 		moveTo(x, y);
1207 		writeStringRaw(toWrite, ForceOption.alwaysSend);
1208 	}
1209 	+/
1210 
1211 	void writePrintableString(in char[] s, ForceOption force = ForceOption.automatic) {
1212 		// an escape character is going to mess things up. Actually any non-printable character could, but meh
1213 		// assert(s.indexOf("\033") == -1);
1214 
1215 		// tracking cursor position
1216 		foreach(ch; s) {
1217 			switch(ch) {
1218 				case '\n':
1219 					_cursorX = 0;
1220 					_cursorY++;
1221 				break;
1222 				case '\r':
1223 					_cursorX = 0;
1224 				break;
1225 				case '\t':
1226 					_cursorX ++;
1227 					_cursorX += _cursorX % 8; // FIXME: get the actual tabstop, if possible
1228 				break;
1229 				default:
1230 					if(ch <= 127) // way of only advancing once per dchar instead of per code unit
1231 						_cursorX++;
1232 			}
1233 
1234 			if(_wrapAround && _cursorX > width) {
1235 				_cursorX = 0;
1236 				_cursorY++;
1237 			}
1238 
1239 			if(_cursorY == height)
1240 				_cursorY--;
1241 
1242 			/+
1243 			auto index = getIndex(_cursorX, _cursorY);
1244 			if(data[index] != ch) {
1245 				data[index] = ch;
1246 			}
1247 			+/
1248 		}
1249 
1250 		writeStringRaw(s);
1251 	}
1252 
1253 	/* private */ bool _wrapAround = true;
1254 
1255 	deprecated alias writePrintableString writeString; /// use write() or writePrintableString instead
1256 
1257 	private string writeBuffer;
1258 
1259 	// you really, really shouldn't use this unless you know what you are doing
1260 	/*private*/ void writeStringRaw(in char[] s) {
1261 		// FIXME: make sure all the data is sent, check for errors
1262 		version(Posix) {
1263 			writeBuffer ~= s; // buffer it to do everything at once in flush() calls
1264 		} else version(Windows) {
1265 			writeBuffer ~= s;
1266 		} else static assert(0);
1267 	}
1268 
1269 	/// Clears the screen.
1270 	void clear() {
1271 		version(Posix) {
1272 			doTermcap("cl");
1273 		} else version(Windows) {
1274 			// http://support.microsoft.com/kb/99261
1275 			flush();
1276 
1277 			DWORD c;
1278 			CONSOLE_SCREEN_BUFFER_INFO csbi;
1279 			DWORD conSize;
1280 			GetConsoleScreenBufferInfo(hConsole, &csbi);
1281 			conSize = csbi.dwSize.X * csbi.dwSize.Y;
1282 			COORD coordScreen;
1283 			FillConsoleOutputCharacterA(hConsole, ' ', conSize, coordScreen, &c);
1284 			FillConsoleOutputAttribute(hConsole, csbi.wAttributes, conSize, coordScreen, &c);
1285 			moveTo(0, 0, ForceOption.alwaysSend);
1286 		}
1287 
1288 		_cursorX = 0;
1289 		_cursorY = 0;
1290 	}
1291 
1292 	/// gets a line, including user editing. Convenience method around the LineGetter class and RealTimeConsoleInput facilities - use them if you need more control.
1293 	/// You really shouldn't call this if stdin isn't actually a user-interactive terminal! So if you expect people to pipe data to your app, check for that or use something else.
1294 	// FIXME: add a method to make it easy to check if stdin is actually a tty and use other methods there.
1295 	string getline(string prompt = null) {
1296 		if(lineGetter is null)
1297 			lineGetter = new LineGetter(&this);
1298 		// since the struct might move (it shouldn't, this should be unmovable!) but since
1299 		// it technically might, I'm updating the pointer before using it just in case.
1300 		lineGetter.terminal = &this;
1301 
1302 		if(prompt !is null)
1303 			lineGetter.prompt = prompt;
1304 
1305 		auto input = RealTimeConsoleInput(&this, ConsoleInputFlags.raw);
1306 		auto line = lineGetter.getline(&input);
1307 
1308 		// lineGetter leaves us exactly where it was when the user hit enter, giving best
1309 		// flexibility to real-time input and cellular programs. The convenience function,
1310 		// however, wants to do what is right in most the simple cases, which is to actually
1311 		// print the line (echo would be enabled without RealTimeConsoleInput anyway and they
1312 		// did hit enter), so we'll do that here too.
1313 		writePrintableString("\n");
1314 
1315 		return line;
1316 	}
1317 
1318 }
1319 
1320 /+
1321 struct ConsoleBuffer {
1322 	int cursorX;
1323 	int cursorY;
1324 	int width;
1325 	int height;
1326 	dchar[] data;
1327 
1328 	void actualize(Terminal* t) {
1329 		auto writer = t.getBufferedWriter();
1330 
1331 		this.copyTo(&(t.onScreen));
1332 	}
1333 
1334 	void copyTo(ConsoleBuffer* buffer) {
1335 		buffer.cursorX = this.cursorX;
1336 		buffer.cursorY = this.cursorY;
1337 		buffer.width = this.width;
1338 		buffer.height = this.height;
1339 		buffer.data[] = this.data[];
1340 	}
1341 }
1342 +/
1343 
1344 /**
1345  * Encapsulates the stream of input events received from the terminal input.
1346  */
1347 struct RealTimeConsoleInput {
1348 	@disable this();
1349 	@disable this(this);
1350 
1351 	version(Posix) {
1352 		private int fdOut;
1353 		private int fdIn;
1354 		private sigaction_t oldSigWinch;
1355 		private sigaction_t oldSigIntr;
1356 		private sigaction_t oldHupIntr;
1357 		private termios old;
1358 		ubyte[128] hack;
1359 		// apparently termios isn't the size druntime thinks it is (at least on 32 bit, sometimes)....
1360 		// tcgetattr smashed other variables in here too that could create random problems
1361 		// so this hack is just to give some room for that to happen without destroying the rest of the world
1362 	}
1363 
1364 	version(Windows) {
1365 		private DWORD oldInput;
1366 		private DWORD oldOutput;
1367 		HANDLE inputHandle;
1368 	}
1369 
1370 	private ConsoleInputFlags flags;
1371 	private Terminal* terminal;
1372 	private void delegate()[] destructor;
1373 
1374 	/// To capture input, you need to provide a terminal and some flags.
1375 	public this(Terminal* terminal, ConsoleInputFlags flags) {
1376 		this.flags = flags;
1377 		this.terminal = terminal;
1378 
1379 		version(Windows) {
1380 			inputHandle = GetStdHandle(STD_INPUT_HANDLE);
1381 
1382 			GetConsoleMode(inputHandle, &oldInput);
1383 
1384 			DWORD mode = 0;
1385 			mode |= ENABLE_PROCESSED_INPUT /* 0x01 */; // this gives Ctrl+C which we probably want to be similar to linux
1386 			//if(flags & ConsoleInputFlags.size)
1387 			mode |= ENABLE_WINDOW_INPUT /* 0208 */; // gives size etc
1388 			if(flags & ConsoleInputFlags.echo)
1389 				mode |= ENABLE_ECHO_INPUT; // 0x4
1390 			if(flags & ConsoleInputFlags.mouse)
1391 				mode |= ENABLE_MOUSE_INPUT; // 0x10
1392 			// if(flags & ConsoleInputFlags.raw) // FIXME: maybe that should be a separate flag for ENABLE_LINE_INPUT
1393 
1394 			SetConsoleMode(inputHandle, mode);
1395 			destructor ~= { SetConsoleMode(inputHandle, oldInput); };
1396 
1397 
1398 			GetConsoleMode(terminal.hConsole, &oldOutput);
1399 			mode = 0;
1400 			// we want this to match linux too
1401 			mode |= ENABLE_PROCESSED_OUTPUT; /* 0x01 */
1402 			mode |= ENABLE_WRAP_AT_EOL_OUTPUT; /* 0x02 */
1403 			SetConsoleMode(terminal.hConsole, mode);
1404 			destructor ~= { SetConsoleMode(terminal.hConsole, oldOutput); };
1405 
1406 			// FIXME: change to UTF8 as well
1407 		}
1408 
1409 		version(Posix) {
1410 			this.fdIn = terminal.fdIn;
1411 			this.fdOut = terminal.fdOut;
1412 
1413 			if(fdIn != -1) {
1414 				tcgetattr(fdIn, &old);
1415 				auto n = old;
1416 
1417 				auto f = ICANON;
1418 				if(!(flags & ConsoleInputFlags.echo))
1419 					f |= ECHO;
1420 
1421 				n.c_lflag &= ~f;
1422 				tcsetattr(fdIn, TCSANOW, &n);
1423 			}
1424 
1425 			// some weird bug breaks this, https://github.com/robik/ConsoleD/issues/3
1426 			//destructor ~= { tcsetattr(fdIn, TCSANOW, &old); };
1427 
1428 			if(flags & ConsoleInputFlags.size) {
1429 				import core.sys.posix.signal;
1430 				sigaction_t n;
1431 				n.sa_handler = &sizeSignalHandler;
1432 				n.sa_mask = cast(sigset_t) 0;
1433 				n.sa_flags = 0;
1434 				sigaction(SIGWINCH, &n, &oldSigWinch);
1435 			}
1436 
1437 			{
1438 				import core.sys.posix.signal;
1439 				sigaction_t n;
1440 				n.sa_handler = &interruptSignalHandler;
1441 				n.sa_mask = cast(sigset_t) 0;
1442 				n.sa_flags = 0;
1443 				sigaction(SIGINT, &n, &oldSigIntr);
1444 			}
1445 
1446 			{
1447 				import core.sys.posix.signal;
1448 				sigaction_t n;
1449 				n.sa_handler = &hangupSignalHandler;
1450 				n.sa_mask = cast(sigset_t) 0;
1451 				n.sa_flags = 0;
1452 				sigaction(SIGHUP, &n, &oldHupIntr);
1453 			}
1454 
1455 
1456 
1457 			if(flags & ConsoleInputFlags.mouse) {
1458 				// basic button press+release notification
1459 
1460 				// FIXME: try to get maximum capabilities from all terminals
1461 				// right now this works well on xterm but rxvt isn't sending movements...
1462 
1463 				terminal.writeStringRaw("\033[?1000h");
1464 				destructor ~= { terminal.writeStringRaw("\033[?1000l"); };
1465 				// the MOUSE_HACK env var is for the case where I run screen
1466 				// but set TERM=xterm (which I do from putty). The 1003 mouse mode
1467 				// doesn't work there, breaking mouse support entirely. So by setting
1468 				// MOUSE_HACK=1002 it tells us to use the other mode for a fallback.
1469 				import std.process : environment;
1470 				if(terminal.terminalInFamily("xterm") && environment.get("MOUSE_HACK") != "1002") {
1471 					// this is vt200 mouse with full motion tracking, supported by xterm
1472 					terminal.writeStringRaw("\033[?1003h");
1473 					destructor ~= { terminal.writeStringRaw("\033[?1003l"); };
1474 				} else if(terminal.terminalInFamily("rxvt", "screen") || environment.get("MOUSE_HACK") == "1002") {
1475 					terminal.writeStringRaw("\033[?1002h"); // this is vt200 mouse with press/release and motion notification iff buttons are pressed
1476 					destructor ~= { terminal.writeStringRaw("\033[?1002l"); };
1477 				}
1478 			}
1479 			if(flags & ConsoleInputFlags.paste) {
1480 				if(terminal.terminalInFamily("xterm", "rxvt", "screen")) {
1481 					terminal.writeStringRaw("\033[?2004h"); // bracketed paste mode
1482 					destructor ~= { terminal.writeStringRaw("\033[?2004l"); };
1483 				}
1484 			}
1485 
1486 			// try to ensure the terminal is in UTF-8 mode
1487 			if(terminal.terminalInFamily("xterm", "screen", "linux") && !terminal.isMacTerminal()) {
1488 				terminal.writeStringRaw("\033%G");
1489 			}
1490 
1491 			terminal.flush();
1492 		}
1493 
1494 
1495 		version(with_eventloop) {
1496 			import arsd.eventloop;
1497 			version(Windows)
1498 				auto listenTo = inputHandle;
1499 			else version(Posix)
1500 				auto listenTo = this.fdIn;
1501 			else static assert(0, "idk about this OS");
1502 
1503 			version(Posix)
1504 			addListener(&signalFired);
1505 
1506 			if(listenTo != -1) {
1507 				addFileEventListeners(listenTo, &eventListener, null, null);
1508 				destructor ~= { removeFileEventListeners(listenTo); };
1509 			}
1510 			addOnIdle(&terminal.flush);
1511 			destructor ~= { removeOnIdle(&terminal.flush); };
1512 		}
1513 	}
1514 
1515 	version(with_eventloop) {
1516 		version(Posix)
1517 		void signalFired(SignalFired) {
1518 			if(interrupted) {
1519 				interrupted = false;
1520 				send(InputEvent(UserInterruptionEvent(), terminal));
1521 			}
1522 			if(windowSizeChanged)
1523 				send(checkWindowSizeChanged());
1524 			if(hangedUp) {
1525 				hangedUp = false;
1526 				send(InputEvent(HangupEvent(), terminal));
1527 			}
1528 		}
1529 
1530 		import arsd.eventloop;
1531 		void eventListener(OsFileHandle fd) {
1532 			auto queue = readNextEvents();
1533 			foreach(event; queue)
1534 				send(event);
1535 		}
1536 	}
1537 
1538 	~this() {
1539 		// the delegate thing doesn't actually work for this... for some reason
1540 		version(Posix)
1541 			if(fdIn != -1)
1542 				tcsetattr(fdIn, TCSANOW, &old);
1543 
1544 		version(Posix) {
1545 			if(flags & ConsoleInputFlags.size) {
1546 				// restoration
1547 				sigaction(SIGWINCH, &oldSigWinch, null);
1548 			}
1549 			sigaction(SIGINT, &oldSigIntr, null);
1550 			sigaction(SIGHUP, &oldHupIntr, null);
1551 		}
1552 
1553 		// we're just undoing everything the constructor did, in reverse order, same criteria
1554 		foreach_reverse(d; destructor)
1555 			d();
1556 	}
1557 
1558 	/**
1559 		Returns true if there iff getch() would not block.
1560 
1561 		WARNING: kbhit might consume input that would be ignored by getch. This
1562 		function is really only meant to be used in conjunction with getch. Typically,
1563 		you should use a full-fledged event loop if you want all kinds of input. kbhit+getch
1564 		are just for simple keyboard driven applications.
1565 	*/
1566 	bool kbhit() {
1567 		auto got = getch(true);
1568 
1569 		if(got == dchar.init)
1570 			return false;
1571 
1572 		getchBuffer = got;
1573 		return true;
1574 	}
1575 
1576 	/// Check for input, waiting no longer than the number of milliseconds
1577 	bool timedCheckForInput(int milliseconds) {
1578 		version(Windows) {
1579 			auto response = WaitForSingleObject(terminal.hConsole, milliseconds);
1580 			if(response  == 0)
1581 				return true; // the object is ready
1582 			return false;
1583 		} else version(Posix) {
1584 			if(fdIn == -1)
1585 				return false;
1586 
1587 			timeval tv;
1588 			tv.tv_sec = 0;
1589 			tv.tv_usec = milliseconds * 1000;
1590 
1591 			fd_set fs;
1592 			FD_ZERO(&fs);
1593 
1594 			FD_SET(fdIn, &fs);
1595 			if(select(fdIn + 1, &fs, null, null, &tv) == -1) {
1596 				return false;
1597 			}
1598 
1599 			return FD_ISSET(fdIn, &fs);
1600 		}
1601 	}
1602 
1603 	/* private */ bool anyInput_internal() {
1604 		if(inputQueue.length || timedCheckForInput(0))
1605 			return true;
1606 		version(Posix)
1607 			if(interrupted || windowSizeChanged || hangedUp)
1608 				return true;
1609 		return false;
1610 	}
1611 
1612 	private dchar getchBuffer;
1613 
1614 	/// Get one key press from the terminal, discarding other
1615 	/// events in the process. Returns dchar.init upon receiving end-of-file.
1616 	///
1617 	/// Be aware that this may return non-character key events, like F1, F2, arrow keys, etc., as private use Unicode characters. Check them against KeyboardEvent.Key if you like.
1618 	dchar getch(bool nonblocking = false) {
1619 		if(getchBuffer != dchar.init) {
1620 			auto a = getchBuffer;
1621 			getchBuffer = dchar.init;
1622 			return a;
1623 		}
1624 
1625 		if(nonblocking && !anyInput_internal())
1626 			return dchar.init;
1627 
1628 		auto event = nextEvent();
1629 		while(event.type != InputEvent.Type.KeyboardEvent || event.keyboardEvent.pressed == false) {
1630 			if(event.type == InputEvent.Type.UserInterruptionEvent)
1631 				throw new UserInterruptionException();
1632 			if(event.type == InputEvent.Type.HangupEvent)
1633 				throw new HangupException();
1634 			if(event.type == InputEvent.Type.EndOfFileEvent)
1635 				return dchar.init;
1636 
1637 			if(nonblocking && !anyInput_internal())
1638 				return dchar.init;
1639 
1640 			event = nextEvent();
1641 		}
1642 		return event.keyboardEvent.which;
1643 	}
1644 
1645 	//char[128] inputBuffer;
1646 	//int inputBufferPosition;
1647 	version(Posix)
1648 	int nextRaw(bool interruptable = false) {
1649 		if(fdIn == -1)
1650 			return 0;
1651 
1652 		char[1] buf;
1653 		try_again:
1654 		auto ret = read(fdIn, buf.ptr, buf.length);
1655 		if(ret == 0)
1656 			return 0; // input closed
1657 		if(ret == -1) {
1658 			import core.stdc.errno;
1659 			if(errno == EINTR)
1660 				// interrupted by signal call, quite possibly resize or ctrl+c which we want to check for in the event loop
1661 				if(interruptable)
1662 					return -1;
1663 				else
1664 					goto try_again;
1665 			else
1666 				throw new Exception("read failed");
1667 		}
1668 
1669 		//terminal.writef("RAW READ: %d\n", buf[0]);
1670 
1671 		if(ret == 1)
1672 			return inputPrefilter ? inputPrefilter(buf[0]) : buf[0];
1673 		else
1674 			assert(0); // read too much, should be impossible
1675 	}
1676 
1677 	version(Posix)
1678 		int delegate(char) inputPrefilter;
1679 
1680 	version(Posix)
1681 	dchar nextChar(int starting) {
1682 		if(starting <= 127)
1683 			return cast(dchar) starting;
1684 		char[6] buffer;
1685 		int pos = 0;
1686 		buffer[pos++] = cast(char) starting;
1687 
1688 		// see the utf-8 encoding for details
1689 		int remaining = 0;
1690 		ubyte magic = starting & 0xff;
1691 		while(magic & 0b1000_000) {
1692 			remaining++;
1693 			magic <<= 1;
1694 		}
1695 
1696 		while(remaining && pos < buffer.length) {
1697 			buffer[pos++] = cast(char) nextRaw();
1698 			remaining--;
1699 		}
1700 
1701 		import std.utf;
1702 		size_t throwAway; // it insists on the index but we don't care
1703 		return decode(buffer[], throwAway);
1704 	}
1705 
1706 	InputEvent checkWindowSizeChanged() {
1707 		auto oldWidth = terminal.width;
1708 		auto oldHeight = terminal.height;
1709 		terminal.updateSize();
1710 		version(Posix)
1711 		windowSizeChanged = false;
1712 		return InputEvent(SizeChangedEvent(oldWidth, oldHeight, terminal.width, terminal.height), terminal);
1713 	}
1714 
1715 
1716 	// character event
1717 	// non-character key event
1718 	// paste event
1719 	// mouse event
1720 	// size event maybe, and if appropriate focus events
1721 
1722 	/// Returns the next event.
1723 	///
1724 	/// Experimental: It is also possible to integrate this into
1725 	/// a generic event loop, currently under -version=with_eventloop and it will
1726 	/// require the module arsd.eventloop (Linux only at this point)
1727 	InputEvent nextEvent() {
1728 		terminal.flush();
1729 		if(inputQueue.length) {
1730 			auto e = inputQueue[0];
1731 			inputQueue = inputQueue[1 .. $];
1732 			return e;
1733 		}
1734 
1735 		wait_for_more:
1736 		version(Posix)
1737 		if(interrupted) {
1738 			interrupted = false;
1739 			return InputEvent(UserInterruptionEvent(), terminal);
1740 		}
1741 
1742 		version(Posix)
1743 		if(hangedUp) {
1744 			hangedUp = false;
1745 			return InputEvent(HangupEvent(), terminal);
1746 		}
1747 
1748 		version(Posix)
1749 		if(windowSizeChanged) {
1750 			return checkWindowSizeChanged();
1751 		}
1752 
1753 		auto more = readNextEvents();
1754 		if(!more.length)
1755 			goto wait_for_more; // i used to do a loop (readNextEvents can read something, but it might be discarded by the input filter) but now it goto's above because readNextEvents might be interrupted by a SIGWINCH aka size event so we want to check that at least
1756 
1757 		assert(more.length);
1758 
1759 		auto e = more[0];
1760 		inputQueue = more[1 .. $];
1761 		return e;
1762 	}
1763 
1764 	InputEvent* peekNextEvent() {
1765 		if(inputQueue.length)
1766 			return &(inputQueue[0]);
1767 		return null;
1768 	}
1769 
1770 	enum InjectionPosition { head, tail }
1771 	void injectEvent(InputEvent ev, InjectionPosition where) {
1772 		final switch(where) {
1773 			case InjectionPosition.head:
1774 				inputQueue = ev ~ inputQueue;
1775 			break;
1776 			case InjectionPosition.tail:
1777 				inputQueue ~= ev;
1778 			break;
1779 		}
1780 	}
1781 
1782 	InputEvent[] inputQueue;
1783 
1784 	version(Windows)
1785 	InputEvent[] readNextEvents() {
1786 		terminal.flush(); // make sure all output is sent out before waiting for anything
1787 
1788 		INPUT_RECORD[32] buffer;
1789 		DWORD actuallyRead;
1790 			// FIXME: ReadConsoleInputW
1791 		auto success = ReadConsoleInputA(inputHandle, buffer.ptr, buffer.length, &actuallyRead);
1792 		if(success == 0)
1793 			throw new Exception("ReadConsoleInput");
1794 
1795 		InputEvent[] newEvents;
1796 		input_loop: foreach(record; buffer[0 .. actuallyRead]) {
1797 			switch(record.EventType) {
1798 				case KEY_EVENT:
1799 					auto ev = record.KeyEvent;
1800 					KeyboardEvent ke;
1801 					CharacterEvent e;
1802 					NonCharacterKeyEvent ne;
1803 
1804 					e.eventType = ev.bKeyDown ? CharacterEvent.Type.Pressed : CharacterEvent.Type.Released;
1805 					ne.eventType = ev.bKeyDown ? NonCharacterKeyEvent.Type.Pressed : NonCharacterKeyEvent.Type.Released;
1806 
1807 					ke.pressed = ev.bKeyDown ? true : false;
1808 
1809 					// only send released events when specifically requested
1810 					if(!(flags & ConsoleInputFlags.releasedKeys) && !ev.bKeyDown)
1811 						break;
1812 
1813 					e.modifierState = ev.dwControlKeyState;
1814 					ne.modifierState = ev.dwControlKeyState;
1815 					ke.modifierState = ev.dwControlKeyState;
1816 
1817 					if(ev.UnicodeChar) {
1818 						// new style event goes first
1819 						ke.which = cast(dchar) cast(wchar) ev.UnicodeChar;
1820 						newEvents ~= InputEvent(ke, terminal);
1821 
1822 						// old style event then follows as the fallback
1823 						e.character = cast(dchar) cast(wchar) ev.UnicodeChar;
1824 						newEvents ~= InputEvent(e, terminal);
1825 					} else {
1826 						// old style event
1827 						ne.key = cast(NonCharacterKeyEvent.Key) ev.wVirtualKeyCode;
1828 
1829 						// new style event. See comment on KeyboardEvent.Key
1830 						ke.which = cast(KeyboardEvent.Key) (ev.wVirtualKeyCode + 0xF0000);
1831 
1832 						// FIXME: make this better. the goal is to make sure the key code is a valid enum member
1833 						// Windows sends more keys than Unix and we're doing lowest common denominator here
1834 						foreach(member; __traits(allMembers, NonCharacterKeyEvent.Key))
1835 							if(__traits(getMember, NonCharacterKeyEvent.Key, member) == ne.key) {
1836 								newEvents ~= InputEvent(ke, terminal);
1837 								newEvents ~= InputEvent(ne, terminal);
1838 								break;
1839 							}
1840 					}
1841 				break;
1842 				case MOUSE_EVENT:
1843 					auto ev = record.MouseEvent;
1844 					MouseEvent e;
1845 
1846 					e.modifierState = ev.dwControlKeyState;
1847 					e.x = ev.dwMousePosition.X;
1848 					e.y = ev.dwMousePosition.Y;
1849 
1850 					switch(ev.dwEventFlags) {
1851 						case 0:
1852 							//press or release
1853 							e.eventType = MouseEvent.Type.Pressed;
1854 							static DWORD lastButtonState;
1855 							auto lastButtonState2 = lastButtonState;
1856 							e.buttons = ev.dwButtonState;
1857 							lastButtonState = e.buttons;
1858 
1859 							// this is sent on state change. if fewer buttons are pressed, it must mean released
1860 							if(cast(DWORD) e.buttons < lastButtonState2) {
1861 								e.eventType = MouseEvent.Type.Released;
1862 								// if last was 101 and now it is 100, then button far right was released
1863 								// so we flip the bits, ~100 == 011, then and them: 101 & 011 == 001, the
1864 								// button that was released
1865 								e.buttons = lastButtonState2 & ~e.buttons;
1866 							}
1867 						break;
1868 						case MOUSE_MOVED:
1869 							e.eventType = MouseEvent.Type.Moved;
1870 							e.buttons = ev.dwButtonState;
1871 						break;
1872 						case 0x0004/*MOUSE_WHEELED*/:
1873 							e.eventType = MouseEvent.Type.Pressed;
1874 							if(ev.dwButtonState > 0)
1875 								e.buttons = MouseEvent.Button.ScrollDown;
1876 							else
1877 								e.buttons = MouseEvent.Button.ScrollUp;
1878 						break;
1879 						default:
1880 							continue input_loop;
1881 					}
1882 
1883 					newEvents ~= InputEvent(e, terminal);
1884 				break;
1885 				case WINDOW_BUFFER_SIZE_EVENT:
1886 					auto ev = record.WindowBufferSizeEvent;
1887 					auto oldWidth = terminal.width;
1888 					auto oldHeight = terminal.height;
1889 					terminal._width = ev.dwSize.X;
1890 					terminal._height = ev.dwSize.Y;
1891 					newEvents ~= InputEvent(SizeChangedEvent(oldWidth, oldHeight, terminal.width, terminal.height), terminal);
1892 				break;
1893 				// FIXME: can we catch ctrl+c here too?
1894 				default:
1895 					// ignore
1896 			}
1897 		}
1898 
1899 		return newEvents;
1900 	}
1901 
1902 	version(Posix)
1903 	InputEvent[] readNextEvents() {
1904 		terminal.flush(); // make sure all output is sent out before we try to get input
1905 
1906 		// we want to starve the read, especially if we're called from an edge-triggered
1907 		// epoll (which might happen in version=with_eventloop.. impl detail there subject
1908 		// to change).
1909 		auto initial = readNextEventsHelper();
1910 
1911 		// lol this calls select() inside a function prolly called from epoll but meh,
1912 		// it is the simplest thing that can possibly work. The alternative would be
1913 		// doing non-blocking reads and buffering in the nextRaw function (not a bad idea
1914 		// btw, just a bit more of a hassle).
1915 		while(timedCheckForInput(0)) {
1916 			auto ne = readNextEventsHelper();
1917 			initial ~= ne;
1918 			foreach(n; ne)
1919 				if(n.type == InputEvent.Type.EndOfFileEvent)
1920 					return initial; // hit end of file, get out of here lest we infinite loop
1921 					// (select still returns info available even after we read end of file)
1922 		}
1923 		return initial;
1924 	}
1925 
1926 	// The helper reads just one actual event from the pipe...
1927 	version(Posix)
1928 	InputEvent[] readNextEventsHelper() {
1929 		InputEvent[] charPressAndRelease(dchar character) {
1930 			if((flags & ConsoleInputFlags.releasedKeys))
1931 				return [
1932 					// new style event
1933 					InputEvent(KeyboardEvent(true, character, 0), terminal),
1934 					InputEvent(KeyboardEvent(false, character, 0), terminal),
1935 					// old style event
1936 					InputEvent(CharacterEvent(CharacterEvent.Type.Pressed, character, 0), terminal),
1937 					InputEvent(CharacterEvent(CharacterEvent.Type.Released, character, 0), terminal),
1938 				];
1939 			else return [
1940 				// new style event
1941 				InputEvent(KeyboardEvent(true, character, 0), terminal),
1942 				// old style event
1943 				InputEvent(CharacterEvent(CharacterEvent.Type.Pressed, character, 0), terminal)
1944 			];
1945 		}
1946 		InputEvent[] keyPressAndRelease(NonCharacterKeyEvent.Key key, uint modifiers = 0) {
1947 			if((flags & ConsoleInputFlags.releasedKeys))
1948 				return [
1949 					// new style event FIXME: when the old events are removed, kill the +0xF0000 from here!
1950 					InputEvent(KeyboardEvent(true, cast(dchar)(key) + 0xF0000, modifiers), terminal),
1951 					InputEvent(KeyboardEvent(false, cast(dchar)(key) + 0xF0000, modifiers), terminal),
1952 					// old style event
1953 					InputEvent(NonCharacterKeyEvent(NonCharacterKeyEvent.Type.Pressed, key, modifiers), terminal),
1954 					InputEvent(NonCharacterKeyEvent(NonCharacterKeyEvent.Type.Released, key, modifiers), terminal),
1955 				];
1956 			else return [
1957 				// new style event FIXME: when the old events are removed, kill the +0xF0000 from here!
1958 				InputEvent(KeyboardEvent(true, cast(dchar)(key) + 0xF0000, modifiers), terminal),
1959 				// old style event
1960 				InputEvent(NonCharacterKeyEvent(NonCharacterKeyEvent.Type.Pressed, key, modifiers), terminal)
1961 			];
1962 		}
1963 
1964 		char[30] sequenceBuffer;
1965 
1966 		// this assumes you just read "\033["
1967 		char[] readEscapeSequence(char[] sequence) {
1968 			int sequenceLength = 2;
1969 			sequence[0] = '\033';
1970 			sequence[1] = '[';
1971 
1972 			while(sequenceLength < sequence.length) {
1973 				auto n = nextRaw();
1974 				sequence[sequenceLength++] = cast(char) n;
1975 				// I think a [ is supposed to termiate a CSI sequence
1976 				// but the Linux console sends CSI[A for F1, so I'm
1977 				// hacking it to accept that too
1978 				if(n >= 0x40 && !(sequenceLength == 3 && n == '['))
1979 					break;
1980 			}
1981 
1982 			return sequence[0 .. sequenceLength];
1983 		}
1984 
1985 		InputEvent[] translateTermcapName(string cap) {
1986 			switch(cap) {
1987 				//case "k0":
1988 					//return keyPressAndRelease(NonCharacterKeyEvent.Key.F1);
1989 				case "k1":
1990 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F1);
1991 				case "k2":
1992 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F2);
1993 				case "k3":
1994 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F3);
1995 				case "k4":
1996 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F4);
1997 				case "k5":
1998 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F5);
1999 				case "k6":
2000 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F6);
2001 				case "k7":
2002 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F7);
2003 				case "k8":
2004 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F8);
2005 				case "k9":
2006 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F9);
2007 				case "k;":
2008 				case "k0":
2009 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F10);
2010 				case "F1":
2011 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F11);
2012 				case "F2":
2013 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F12);
2014 
2015 
2016 				case "kb":
2017 					return charPressAndRelease('\b');
2018 				case "kD":
2019 					return keyPressAndRelease(NonCharacterKeyEvent.Key.Delete);
2020 
2021 				case "kd":
2022 				case "do":
2023 					return keyPressAndRelease(NonCharacterKeyEvent.Key.DownArrow);
2024 				case "ku":
2025 				case "up":
2026 					return keyPressAndRelease(NonCharacterKeyEvent.Key.UpArrow);
2027 				case "kl":
2028 					return keyPressAndRelease(NonCharacterKeyEvent.Key.LeftArrow);
2029 				case "kr":
2030 				case "nd":
2031 					return keyPressAndRelease(NonCharacterKeyEvent.Key.RightArrow);
2032 
2033 				case "kN":
2034 				case "K5":
2035 					return keyPressAndRelease(NonCharacterKeyEvent.Key.PageDown);
2036 				case "kP":
2037 				case "K2":
2038 					return keyPressAndRelease(NonCharacterKeyEvent.Key.PageUp);
2039 
2040 				case "ho": // this might not be a key but my thing sometimes returns it... weird...
2041 				case "kh":
2042 				case "K1":
2043 					return keyPressAndRelease(NonCharacterKeyEvent.Key.Home);
2044 				case "kH":
2045 					return keyPressAndRelease(NonCharacterKeyEvent.Key.End);
2046 				case "kI":
2047 					return keyPressAndRelease(NonCharacterKeyEvent.Key.Insert);
2048 				default:
2049 					// don't know it, just ignore
2050 					//import std.stdio;
2051 					//writeln(cap);
2052 			}
2053 
2054 			return null;
2055 		}
2056 
2057 
2058 		InputEvent[] doEscapeSequence(in char[] sequence) {
2059 			switch(sequence) {
2060 				case "\033[200~":
2061 					// bracketed paste begin
2062 					// we want to keep reading until
2063 					// "\033[201~":
2064 					// and build a paste event out of it
2065 
2066 
2067 					string data;
2068 					for(;;) {
2069 						auto n = nextRaw();
2070 						if(n == '\033') {
2071 							n = nextRaw();
2072 							if(n == '[') {
2073 								auto esc = readEscapeSequence(sequenceBuffer);
2074 								if(esc == "\033[201~") {
2075 									// complete!
2076 									break;
2077 								} else {
2078 									// was something else apparently, but it is pasted, so keep it
2079 									data ~= esc;
2080 								}
2081 							} else {
2082 								data ~= '\033';
2083 								data ~= cast(char) n;
2084 							}
2085 						} else {
2086 							data ~= cast(char) n;
2087 						}
2088 					}
2089 					return [InputEvent(PasteEvent(data), terminal)];
2090 				case "\033[M":
2091 					// mouse event
2092 					auto buttonCode = nextRaw() - 32;
2093 						// nextChar is commented because i'm not using UTF-8 mouse mode
2094 						// cuz i don't think it is as widely supported
2095 					auto x = cast(int) (/*nextChar*/(nextRaw())) - 33; /* they encode value + 32, but make upper left 1,1. I want it to be 0,0 */
2096 					auto y = cast(int) (/*nextChar*/(nextRaw())) - 33; /* ditto */
2097 
2098 
2099 					bool isRelease = (buttonCode & 0b11) == 3;
2100 					int buttonNumber;
2101 					if(!isRelease) {
2102 						buttonNumber = (buttonCode & 0b11);
2103 						if(buttonCode & 64)
2104 							buttonNumber += 3; // button 4 and 5 are sent as like button 1 and 2, but code | 64
2105 							// so button 1 == button 4 here
2106 
2107 						// note: buttonNumber == 0 means button 1 at this point
2108 						buttonNumber++; // hence this
2109 
2110 
2111 						// apparently this considers middle to be button 2. but i want middle to be button 3.
2112 						if(buttonNumber == 2)
2113 							buttonNumber = 3;
2114 						else if(buttonNumber == 3)
2115 							buttonNumber = 2;
2116 					}
2117 
2118 					auto modifiers = buttonCode & (0b0001_1100);
2119 						// 4 == shift
2120 						// 8 == meta
2121 						// 16 == control
2122 
2123 					MouseEvent m;
2124 
2125 					if(buttonCode & 32)
2126 						m.eventType = MouseEvent.Type.Moved;
2127 					else
2128 						m.eventType = isRelease ? MouseEvent.Type.Released : MouseEvent.Type.Pressed;
2129 
2130 					// ugh, if no buttons are pressed, released and moved are indistinguishable...
2131 					// so we'll count the buttons down, and if we get a release
2132 					static int buttonsDown = 0;
2133 					if(!isRelease && buttonNumber <= 3) // exclude wheel "presses"...
2134 						buttonsDown++;
2135 
2136 					if(isRelease && m.eventType != MouseEvent.Type.Moved) {
2137 						if(buttonsDown)
2138 							buttonsDown--;
2139 						else // no buttons down, so this should be a motion instead..
2140 							m.eventType = MouseEvent.Type.Moved;
2141 					}
2142 
2143 
2144 					if(buttonNumber == 0)
2145 						m.buttons = 0; // we don't actually know :(
2146 					else
2147 						m.buttons = 1 << (buttonNumber - 1); // I prefer flags so that's how we do it
2148 					m.x = x;
2149 					m.y = y;
2150 					m.modifierState = modifiers;
2151 
2152 					return [InputEvent(m, terminal)];
2153 				default:
2154 					// look it up in the termcap key database
2155 					auto cap = terminal.findSequenceInTermcap(sequence);
2156 					if(cap !is null) {
2157 						return translateTermcapName(cap);
2158 					} else {
2159 						if(terminal.terminalInFamily("xterm")) {
2160 							import std.conv, std..string;
2161 							auto terminator = sequence[$ - 1];
2162 							auto parts = sequence[2 .. $ - 1].split(";");
2163 							// parts[0] and terminator tells us the key
2164 							// parts[1] tells us the modifierState
2165 
2166 							uint modifierState;
2167 
2168 							int modGot;
2169 							if(parts.length > 1)
2170 								modGot = to!int(parts[1]);
2171 							mod_switch: switch(modGot) {
2172 								case 2: modifierState |= ModifierState.shift; break;
2173 								case 3: modifierState |= ModifierState.alt; break;
2174 								case 4: modifierState |= ModifierState.shift | ModifierState.alt; break;
2175 								case 5: modifierState |= ModifierState.control; break;
2176 								case 6: modifierState |= ModifierState.shift | ModifierState.control; break;
2177 								case 7: modifierState |= ModifierState.alt | ModifierState.control; break;
2178 								case 8: modifierState |= ModifierState.shift | ModifierState.alt | ModifierState.control; break;
2179 								case 9:
2180 								..
2181 								case 16:
2182 									modifierState |= ModifierState.meta;
2183 									if(modGot != 9) {
2184 										modGot -= 8;
2185 										goto mod_switch;
2186 									}
2187 								break;
2188 
2189 								// this is an extension in my own terminal emulator
2190 								case 20:
2191 								..
2192 								case 36:
2193 									modifierState |= ModifierState.windows;
2194 									modGot -= 20;
2195 									goto mod_switch;
2196 								default:
2197 							}
2198 
2199 							switch(terminator) {
2200 								case 'A': return keyPressAndRelease(NonCharacterKeyEvent.Key.UpArrow, modifierState);
2201 								case 'B': return keyPressAndRelease(NonCharacterKeyEvent.Key.DownArrow, modifierState);
2202 								case 'C': return keyPressAndRelease(NonCharacterKeyEvent.Key.RightArrow, modifierState);
2203 								case 'D': return keyPressAndRelease(NonCharacterKeyEvent.Key.LeftArrow, modifierState);
2204 
2205 								case 'H': return keyPressAndRelease(NonCharacterKeyEvent.Key.Home, modifierState);
2206 								case 'F': return keyPressAndRelease(NonCharacterKeyEvent.Key.End, modifierState);
2207 
2208 								case 'P': return keyPressAndRelease(NonCharacterKeyEvent.Key.F1, modifierState);
2209 								case 'Q': return keyPressAndRelease(NonCharacterKeyEvent.Key.F2, modifierState);
2210 								case 'R': return keyPressAndRelease(NonCharacterKeyEvent.Key.F3, modifierState);
2211 								case 'S': return keyPressAndRelease(NonCharacterKeyEvent.Key.F4, modifierState);
2212 
2213 								case '~': // others
2214 									switch(parts[0]) {
2215 										case "5": return keyPressAndRelease(NonCharacterKeyEvent.Key.PageUp, modifierState);
2216 										case "6": return keyPressAndRelease(NonCharacterKeyEvent.Key.PageDown, modifierState);
2217 										case "2": return keyPressAndRelease(NonCharacterKeyEvent.Key.Insert, modifierState);
2218 										case "3": return keyPressAndRelease(NonCharacterKeyEvent.Key.Delete, modifierState);
2219 
2220 										case "15": return keyPressAndRelease(NonCharacterKeyEvent.Key.F5, modifierState);
2221 										case "17": return keyPressAndRelease(NonCharacterKeyEvent.Key.F6, modifierState);
2222 										case "18": return keyPressAndRelease(NonCharacterKeyEvent.Key.F7, modifierState);
2223 										case "19": return keyPressAndRelease(NonCharacterKeyEvent.Key.F8, modifierState);
2224 										case "20": return keyPressAndRelease(NonCharacterKeyEvent.Key.F9, modifierState);
2225 										case "21": return keyPressAndRelease(NonCharacterKeyEvent.Key.F10, modifierState);
2226 										case "23": return keyPressAndRelease(NonCharacterKeyEvent.Key.F11, modifierState);
2227 										case "24": return keyPressAndRelease(NonCharacterKeyEvent.Key.F12, modifierState);
2228 										default:
2229 									}
2230 								break;
2231 
2232 								default:
2233 							}
2234 						} else if(terminal.terminalInFamily("rxvt")) {
2235 							// FIXME: figure these out. rxvt seems to just change the terminator while keeping the rest the same
2236 							// though it isn't consistent. ugh.
2237 						} else {
2238 							// maybe we could do more terminals, but linux doesn't even send it and screen just seems to pass through, so i don't think so; xterm prolly covers most them anyway
2239 							// so this space is semi-intentionally left blank
2240 						}
2241 					}
2242 			}
2243 
2244 			return null;
2245 		}
2246 
2247 		auto c = nextRaw(true);
2248 		if(c == -1)
2249 			return null; // interrupted; give back nothing so the other level can recheck signal flags
2250 		if(c == 0)
2251 			return [InputEvent(EndOfFileEvent(), terminal)];
2252 		if(c == '\033') {
2253 			if(timedCheckForInput(50)) {
2254 				// escape sequence
2255 				c = nextRaw();
2256 				if(c == '[') { // CSI, ends on anything >= 'A'
2257 					return doEscapeSequence(readEscapeSequence(sequenceBuffer));
2258 				} else if(c == 'O') {
2259 					// could be xterm function key
2260 					auto n = nextRaw();
2261 
2262 					char[3] thing;
2263 					thing[0] = '\033';
2264 					thing[1] = 'O';
2265 					thing[2] = cast(char) n;
2266 
2267 					auto cap = terminal.findSequenceInTermcap(thing);
2268 					if(cap is null) {
2269 						return keyPressAndRelease(NonCharacterKeyEvent.Key.escape) ~
2270 							charPressAndRelease('O') ~
2271 							charPressAndRelease(thing[2]);
2272 					} else {
2273 						return translateTermcapName(cap);
2274 					}
2275 				} else {
2276 					// I don't know, probably unsupported terminal or just quick user input or something
2277 					return keyPressAndRelease(NonCharacterKeyEvent.Key.escape) ~ charPressAndRelease(nextChar(c));
2278 				}
2279 			} else {
2280 				// user hit escape (or super slow escape sequence, but meh)
2281 				return keyPressAndRelease(NonCharacterKeyEvent.Key.escape);
2282 			}
2283 		} else {
2284 			// FIXME: what if it is neither? we should check the termcap
2285 			auto next = nextChar(c);
2286 			if(next == 127) // some terminals send 127 on the backspace. Let's normalize that.
2287 				next = '\b';
2288 			return charPressAndRelease(next);
2289 		}
2290 	}
2291 }
2292 
2293 /// The new style of keyboard event
2294 struct KeyboardEvent {
2295 	bool pressed; ///
2296 	dchar which; ///
2297 	uint modifierState; ///
2298 
2299 	///
2300 	bool isCharacter() {
2301 		return !(which >= Key.min && which <= Key.max);
2302 	}
2303 
2304 	// these match Windows virtual key codes numerically for simplicity of translation there
2305 	// but are plus a unicode private use area offset so i can cram them in the dchar
2306 	// http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731%28v=vs.85%29.aspx
2307 	/// .
2308 	enum Key : dchar {
2309 		escape = 0x1b + 0xF0000, /// .
2310 		F1 = 0x70 + 0xF0000, /// .
2311 		F2 = 0x71 + 0xF0000, /// .
2312 		F3 = 0x72 + 0xF0000, /// .
2313 		F4 = 0x73 + 0xF0000, /// .
2314 		F5 = 0x74 + 0xF0000, /// .
2315 		F6 = 0x75 + 0xF0000, /// .
2316 		F7 = 0x76 + 0xF0000, /// .
2317 		F8 = 0x77 + 0xF0000, /// .
2318 		F9 = 0x78 + 0xF0000, /// .
2319 		F10 = 0x79 + 0xF0000, /// .
2320 		F11 = 0x7A + 0xF0000, /// .
2321 		F12 = 0x7B + 0xF0000, /// .
2322 		LeftArrow = 0x25 + 0xF0000, /// .
2323 		RightArrow = 0x27 + 0xF0000, /// .
2324 		UpArrow = 0x26 + 0xF0000, /// .
2325 		DownArrow = 0x28 + 0xF0000, /// .
2326 		Insert = 0x2d + 0xF0000, /// .
2327 		Delete = 0x2e + 0xF0000, /// .
2328 		Home = 0x24 + 0xF0000, /// .
2329 		End = 0x23 + 0xF0000, /// .
2330 		PageUp = 0x21 + 0xF0000, /// .
2331 		PageDown = 0x22 + 0xF0000, /// .
2332 	}
2333 
2334 
2335 }
2336 
2337 /// Deprecated: use KeyboardEvent instead in new programs
2338 /// Input event for characters
2339 struct CharacterEvent {
2340 	/// .
2341 	enum Type {
2342 		Released, /// .
2343 		Pressed /// .
2344 	}
2345 
2346 	Type eventType; /// .
2347 	dchar character; /// .
2348 	uint modifierState; /// Don't depend on this to be available for character events
2349 }
2350 
2351 /// Deprecated: use KeyboardEvent instead in new programs
2352 struct NonCharacterKeyEvent {
2353 	/// .
2354 	enum Type {
2355 		Released, /// .
2356 		Pressed /// .
2357 	}
2358 	Type eventType; /// .
2359 
2360 	// these match Windows virtual key codes numerically for simplicity of translation there
2361 	//http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731%28v=vs.85%29.aspx
2362 	/// .
2363 	enum Key : int {
2364 		escape = 0x1b, /// .
2365 		F1 = 0x70, /// .
2366 		F2 = 0x71, /// .
2367 		F3 = 0x72, /// .
2368 		F4 = 0x73, /// .
2369 		F5 = 0x74, /// .
2370 		F6 = 0x75, /// .
2371 		F7 = 0x76, /// .
2372 		F8 = 0x77, /// .
2373 		F9 = 0x78, /// .
2374 		F10 = 0x79, /// .
2375 		F11 = 0x7A, /// .
2376 		F12 = 0x7B, /// .
2377 		LeftArrow = 0x25, /// .
2378 		RightArrow = 0x27, /// .
2379 		UpArrow = 0x26, /// .
2380 		DownArrow = 0x28, /// .
2381 		Insert = 0x2d, /// .
2382 		Delete = 0x2e, /// .
2383 		Home = 0x24, /// .
2384 		End = 0x23, /// .
2385 		PageUp = 0x21, /// .
2386 		PageDown = 0x22, /// .
2387 		}
2388 	Key key; /// .
2389 
2390 	uint modifierState; /// A mask of ModifierState. Always use by checking modifierState & ModifierState.something, the actual value differs across platforms
2391 
2392 }
2393 
2394 /// .
2395 struct PasteEvent {
2396 	string pastedText; /// .
2397 }
2398 
2399 /// .
2400 struct MouseEvent {
2401 	// these match simpledisplay.d numerically as well
2402 	/// .
2403 	enum Type {
2404 		Moved = 0, /// .
2405 		Pressed = 1, /// .
2406 		Released = 2, /// .
2407 		Clicked, /// .
2408 	}
2409 
2410 	Type eventType; /// .
2411 
2412 	// note: these should numerically match simpledisplay.d for maximum beauty in my other code
2413 	/// .
2414 	enum Button : uint {
2415 		None = 0, /// .
2416 		Left = 1, /// .
2417 		Middle = 4, /// .
2418 		Right = 2, /// .
2419 		ScrollUp = 8, /// .
2420 		ScrollDown = 16 /// .
2421 	}
2422 	uint buttons; /// A mask of Button
2423 	int x; /// 0 == left side
2424 	int y; /// 0 == top
2425 	uint modifierState; /// shift, ctrl, alt, meta, altgr. Not always available. Always check by using modifierState & ModifierState.something
2426 }
2427 
2428 /// When you get this, check terminal.width and terminal.height to see the new size and react accordingly.
2429 struct SizeChangedEvent {
2430 	int oldWidth;
2431 	int oldHeight;
2432 	int newWidth;
2433 	int newHeight;
2434 }
2435 
2436 /// the user hitting ctrl+c will send this
2437 /// You should drop what you're doing and perhaps exit when this happens.
2438 struct UserInterruptionEvent {}
2439 
2440 /// If the user hangs up (for example, closes the terminal emulator without exiting the app), this is sent.
2441 /// If you receive it, you should generally cleanly exit.
2442 struct HangupEvent {}
2443 
2444 /// Sent upon receiving end-of-file from stdin.
2445 struct EndOfFileEvent {}
2446 
2447 interface CustomEvent {}
2448 
2449 version(Windows)
2450 enum ModifierState : uint {
2451 	shift = 0x10,
2452 	control = 0x8 | 0x4, // 8 == left ctrl, 4 == right ctrl
2453 
2454 	// i'm not sure if the next two are available
2455 	alt = 2 | 1, //2 ==left alt, 1 == right alt
2456 
2457 	// FIXME: I don't think these are actually available
2458 	windows = 512,
2459 	meta = 4096, // FIXME sanity
2460 
2461 	// I don't think this is available on Linux....
2462 	scrollLock = 0x40,
2463 }
2464 else
2465 enum ModifierState : uint {
2466 	shift = 4,
2467 	alt = 2,
2468 	control = 16,
2469 	meta = 8,
2470 
2471 	windows = 512 // only available if you are using my terminal emulator; it isn't actually offered on standard linux ones
2472 }
2473 
2474 version(DDoc)
2475 ///
2476 enum ModifierState : uint {
2477 	///
2478 	shift = 4,
2479 	///
2480 	alt = 2,
2481 	///
2482 	control = 16,
2483 
2484 }
2485 
2486 /++
2487 	[RealTimeConsoleInput.nextEvent] returns one of these. Check the type, then use the [InputEvent.get|get] method to get the more detailed information about the event.
2488 ++/
2489 struct InputEvent {
2490 	/// .
2491 	enum Type {
2492 		KeyboardEvent, /// Keyboard key pressed (or released, where supported)
2493 		CharacterEvent, /// Do not use this in new programs, use KeyboardEvent instead
2494 		NonCharacterKeyEvent, /// Do not use this in new programs, use KeyboardEvent instead
2495 		PasteEvent, /// The user pasted some text. Not always available, the pasted text might come as a series of character events instead.
2496 		MouseEvent, /// only sent if you subscribed to mouse events
2497 		SizeChangedEvent, /// only sent if you subscribed to size events
2498 		UserInterruptionEvent, /// the user hit ctrl+c
2499 		EndOfFileEvent, /// stdin has received an end of file
2500 		HangupEvent, /// the terminal hanged up - for example, if the user closed a terminal emulator
2501 		CustomEvent /// .
2502 	}
2503 
2504 	/// .
2505 	@property Type type() { return t; }
2506 
2507 	/// Returns a pointer to the terminal associated with this event.
2508 	/// (You can usually just ignore this as there's only one terminal typically.)
2509 	///
2510 	/// It may be null in the case of program-generated events;
2511 	@property Terminal* terminal() { return term; }
2512 
2513 	/++
2514 		Gets the specific event instance. First, check the type (such as in a `switch` statement), then extract the correct one from here. Note that the template argument is a $(B value type of the enum above), not a type argument. So to use it, do $(D event.get!(InputEvent.Type.KeyboardEvent)), for example.
2515 
2516 		See_Also:
2517 
2518 		The event types:
2519 			[KeyboardEvent], [MouseEvent], [SizeChangedEvent],
2520 			[PasteEvent], [UserInterruptionEvent],
2521 			[EndOfFileEvent], [HangupEvent], [CustomEvent]
2522 
2523 		And associated functions:
2524 			[RealTimeConsoleInput], [ConsoleInputFlags]
2525 	++/
2526 	@property auto get(Type T)() {
2527 		if(type != T)
2528 			throw new Exception("Wrong event type");
2529 		static if(T == Type.CharacterEvent)
2530 			return characterEvent;
2531 		else static if(T == Type.KeyboardEvent)
2532 			return keyboardEvent;
2533 		else static if(T == Type.NonCharacterKeyEvent)
2534 			return nonCharacterKeyEvent;
2535 		else static if(T == Type.PasteEvent)
2536 			return pasteEvent;
2537 		else static if(T == Type.MouseEvent)
2538 			return mouseEvent;
2539 		else static if(T == Type.SizeChangedEvent)
2540 			return sizeChangedEvent;
2541 		else static if(T == Type.UserInterruptionEvent)
2542 			return userInterruptionEvent;
2543 		else static if(T == Type.EndOfFileEvent)
2544 			return endOfFileEvent;
2545 		else static if(T == Type.HangupEvent)
2546 			return hangupEvent;
2547 		else static if(T == Type.CustomEvent)
2548 			return customEvent;
2549 		else static assert(0, "Type " ~ T.stringof ~ " not added to the get function");
2550 	}
2551 
2552 	/// custom event is public because otherwise there's no point at all
2553 	this(CustomEvent c, Terminal* p = null) {
2554 		t = Type.CustomEvent;
2555 		customEvent = c;
2556 	}
2557 
2558 	private {
2559 		this(CharacterEvent c, Terminal* p) {
2560 			t = Type.CharacterEvent;
2561 			characterEvent = c;
2562 		}
2563 		this(KeyboardEvent c, Terminal* p) {
2564 			t = Type.KeyboardEvent;
2565 			keyboardEvent = c;
2566 		}
2567 		this(NonCharacterKeyEvent c, Terminal* p) {
2568 			t = Type.NonCharacterKeyEvent;
2569 			nonCharacterKeyEvent = c;
2570 		}
2571 		this(PasteEvent c, Terminal* p) {
2572 			t = Type.PasteEvent;
2573 			pasteEvent = c;
2574 		}
2575 		this(MouseEvent c, Terminal* p) {
2576 			t = Type.MouseEvent;
2577 			mouseEvent = c;
2578 		}
2579 		this(SizeChangedEvent c, Terminal* p) {
2580 			t = Type.SizeChangedEvent;
2581 			sizeChangedEvent = c;
2582 		}
2583 		this(UserInterruptionEvent c, Terminal* p) {
2584 			t = Type.UserInterruptionEvent;
2585 			userInterruptionEvent = c;
2586 		}
2587 		this(HangupEvent c, Terminal* p) {
2588 			t = Type.HangupEvent;
2589 			hangupEvent = c;
2590 		}
2591 		this(EndOfFileEvent c, Terminal* p) {
2592 			t = Type.EndOfFileEvent;
2593 			endOfFileEvent = c;
2594 		}
2595 
2596 		Type t;
2597 		Terminal* term;
2598 
2599 		union {
2600 			KeyboardEvent keyboardEvent;
2601 			CharacterEvent characterEvent;
2602 			NonCharacterKeyEvent nonCharacterKeyEvent;
2603 			PasteEvent pasteEvent;
2604 			MouseEvent mouseEvent;
2605 			SizeChangedEvent sizeChangedEvent;
2606 			UserInterruptionEvent userInterruptionEvent;
2607 			HangupEvent hangupEvent;
2608 			EndOfFileEvent endOfFileEvent;
2609 			CustomEvent customEvent;
2610 		}
2611 	}
2612 }
2613 
2614 /**
2615 	FIXME: support lines that wrap
2616 	FIXME: better controls maybe
2617 
2618 	FIXME: support multi-line "lines" and some form of line continuation, both
2619 	       from the user (if permitted) and from the application, so like the user
2620 	       hits "class foo { \n" and the app says "that line needs continuation" automatically.
2621 
2622 	FIXME: fix lengths on prompt and suggestion
2623 
2624 	A note on history:
2625 
2626 	To save history, you must call LineGetter.dispose() when you're done with it.
2627 	History will not be automatically saved without that call!
2628 
2629 	The history saving and loading as a trivially encountered race condition: if you
2630 	open two programs that use the same one at the same time, the one that closes second
2631 	will overwrite any history changes the first closer saved.
2632 
2633 	GNU Getline does this too... and it actually kinda drives me nuts. But I don't know
2634 	what a good fix is except for doing a transactional commit straight to the file every
2635 	time and that seems like hitting the disk way too often.
2636 
2637 	We could also do like a history server like a database daemon that keeps the order
2638 	correct but I don't actually like that either because I kinda like different bashes
2639 	to have different history, I just don't like it all to get lost.
2640 
2641 	Regardless though, this isn't even used in bash anyway, so I don't think I care enough
2642 	to put that much effort into it. Just using separate files for separate tasks is good
2643 	enough I think.
2644 */
2645 class LineGetter {
2646 	/* A note on the assumeSafeAppends in here: since these buffers are private, we can be
2647 	   pretty sure that stomping isn't an issue, so I'm using this liberally to keep the
2648 	   append/realloc code simple and hopefully reasonably fast. */
2649 
2650 	// saved to file
2651 	string[] history;
2652 
2653 	// not saved
2654 	Terminal* terminal;
2655 	string historyFilename;
2656 
2657 	/// Make sure that the parent terminal struct remains in scope for the duration
2658 	/// of LineGetter's lifetime, as it does hold on to and use the passed pointer
2659 	/// throughout.
2660 	///
2661 	/// historyFilename will load and save an input history log to a particular folder.
2662 	/// Leaving it null will mean no file will be used and history will not be saved across sessions.
2663 	this(Terminal* tty, string historyFilename = null) {
2664 		this.terminal = tty;
2665 		this.historyFilename = historyFilename;
2666 
2667 		line.reserve(128);
2668 
2669 		if(historyFilename.length)
2670 			loadSettingsAndHistoryFromFile();
2671 
2672 		regularForeground = cast(Color) terminal._currentForeground;
2673 		background = cast(Color) terminal._currentBackground;
2674 		suggestionForeground = Color.blue;
2675 	}
2676 
2677 	/// Call this before letting LineGetter die so it can do any necessary
2678 	/// cleanup and save the updated history to a file.
2679 	void dispose() {
2680 		if(historyFilename.length)
2681 			saveSettingsAndHistoryToFile();
2682 	}
2683 
2684 	/// Override this to change the directory where history files are stored
2685 	///
2686 	/// Default is $HOME/.arsd-getline on linux and %APPDATA%/arsd-getline/ on Windows.
2687 	/* virtual */ string historyFileDirectory() {
2688 		version(Windows) {
2689 			char[1024] path;
2690 			// FIXME: this doesn't link because the crappy dmd lib doesn't have it
2691 			if(0) { // SHGetFolderPathA(null, CSIDL_APPDATA, null, 0, path.ptr) >= 0) {
2692 				import core.stdc..string;
2693 				return cast(string) path[0 .. strlen(path.ptr)] ~ "\\arsd-getline";
2694 			} else {
2695 				import std.process;
2696 				return environment["APPDATA"] ~ "\\arsd-getline";
2697 			}
2698 		} else version(Posix) {
2699 			import std.process;
2700 			return environment["HOME"] ~ "/.arsd-getline";
2701 		}
2702 	}
2703 
2704 	/// You can customize the colors here. You should set these after construction, but before
2705 	/// calling startGettingLine or getline.
2706 	Color suggestionForeground;
2707 	Color regularForeground; /// .
2708 	Color background; /// .
2709 	//bool reverseVideo;
2710 
2711 	/// Set this if you want a prompt to be drawn with the line. It does NOT support color in string.
2712 	string prompt;
2713 
2714 	/// Turn on auto suggest if you want a greyed thing of what tab
2715 	/// would be able to fill in as you type.
2716 	///
2717 	/// You might want to turn it off if generating a completion list is slow.
2718 	bool autoSuggest = true;
2719 
2720 
2721 	/// Override this if you don't want all lines added to the history.
2722 	/// You can return null to not add it at all, or you can transform it.
2723 	/* virtual */ string historyFilter(string candidate) {
2724 		return candidate;
2725 	}
2726 
2727 	/// You may override this to do nothing
2728 	/* virtual */ void saveSettingsAndHistoryToFile() {
2729 		import std.file;
2730 		if(!exists(historyFileDirectory))
2731 			mkdir(historyFileDirectory);
2732 		auto fn = historyPath();
2733 		import std.stdio;
2734 		auto file = File(fn, "wt");
2735 		foreach(item; history)
2736 			file.writeln(item);
2737 	}
2738 
2739 	private string historyPath() {
2740 		import std.path;
2741 		auto filename = historyFileDirectory() ~ dirSeparator ~ historyFilename ~ ".history";
2742 		return filename;
2743 	}
2744 
2745 	/// You may override this to do nothing
2746 	/* virtual */ void loadSettingsAndHistoryFromFile() {
2747 		import std.file;
2748 		history = null;
2749 		auto fn = historyPath();
2750 		if(exists(fn)) {
2751 			import std.stdio;
2752 			foreach(line; File(fn, "rt").byLine)
2753 				history ~= line.idup;
2754 
2755 		}
2756 	}
2757 
2758 	/**
2759 		Override this to provide tab completion. You may use the candidate
2760 		argument to filter the list, but you don't have to (LineGetter will
2761 		do it for you on the values you return).
2762 
2763 		Ideally, you wouldn't return more than about ten items since the list
2764 		gets difficult to use if it is too long.
2765 
2766 		Default is to provide recent command history as autocomplete.
2767 	*/
2768 	/* virtual */ protected string[] tabComplete(in dchar[] candidate) {
2769 		return history.length > 20 ? history[0 .. 20] : history;
2770 	}
2771 
2772 	private string[] filterTabCompleteList(string[] list) {
2773 		if(list.length == 0)
2774 			return list;
2775 
2776 		string[] f;
2777 		f.reserve(list.length);
2778 
2779 		foreach(item; list) {
2780 			import std.algorithm;
2781 			if(startsWith(item, line[0 .. cursorPosition]))
2782 				f ~= item;
2783 		}
2784 
2785 		return f;
2786 	}
2787 
2788 	/// Override this to provide a custom display of the tab completion list
2789 	protected void showTabCompleteList(string[] list) {
2790 		if(list.length) {
2791 			// FIXME: allow mouse clicking of an item, that would be cool
2792 
2793 			// FIXME: scroll
2794 			//if(terminal.type == ConsoleOutputType.linear) {
2795 				terminal.writeln();
2796 				foreach(item; list) {
2797 					terminal.color(suggestionForeground, background);
2798 					import std.utf;
2799 					auto idx = codeLength!char(line[0 .. cursorPosition]);
2800 					terminal.write("  ", item[0 .. idx]);
2801 					terminal.color(regularForeground, background);
2802 					terminal.writeln(item[idx .. $]);
2803 				}
2804 				updateCursorPosition();
2805 				redraw();
2806 			//}
2807 		}
2808 	}
2809 
2810 	/// One-call shop for the main workhorse
2811 	/// If you already have a RealTimeConsoleInput ready to go, you
2812 	/// should pass a pointer to yours here. Otherwise, LineGetter will
2813 	/// make its own.
2814 	public string getline(RealTimeConsoleInput* input = null) {
2815 		startGettingLine();
2816 		if(input is null) {
2817 			auto i = RealTimeConsoleInput(terminal, ConsoleInputFlags.raw | ConsoleInputFlags.allInputEvents);
2818 			while(workOnLine(i.nextEvent())) {}
2819 		} else
2820 			while(workOnLine(input.nextEvent())) {}
2821 		return finishGettingLine();
2822 	}
2823 
2824 	private int currentHistoryViewPosition = 0;
2825 	private dchar[] uncommittedHistoryCandidate;
2826 	void loadFromHistory(int howFarBack) {
2827 		if(howFarBack < 0)
2828 			howFarBack = 0;
2829 		if(howFarBack > history.length) // lol signed/unsigned comparison here means if i did this first, before howFarBack < 0, it would totally cycle around.
2830 			howFarBack = cast(int) history.length;
2831 		if(howFarBack == currentHistoryViewPosition)
2832 			return;
2833 		if(currentHistoryViewPosition == 0) {
2834 			// save the current line so we can down arrow back to it later
2835 			if(uncommittedHistoryCandidate.length < line.length) {
2836 				uncommittedHistoryCandidate.length = line.length;
2837 			}
2838 
2839 			uncommittedHistoryCandidate[0 .. line.length] = line[];
2840 			uncommittedHistoryCandidate = uncommittedHistoryCandidate[0 .. line.length];
2841 			uncommittedHistoryCandidate.assumeSafeAppend();
2842 		}
2843 
2844 		currentHistoryViewPosition = howFarBack;
2845 
2846 		if(howFarBack == 0) {
2847 			line.length = uncommittedHistoryCandidate.length;
2848 			line.assumeSafeAppend();
2849 			line[] = uncommittedHistoryCandidate[];
2850 		} else {
2851 			line = line[0 .. 0];
2852 			line.assumeSafeAppend();
2853 			foreach(dchar ch; history[$ - howFarBack])
2854 				line ~= ch;
2855 		}
2856 
2857 		cursorPosition = cast(int) line.length;
2858 		scrollToEnd();
2859 	}
2860 
2861 	bool insertMode = true;
2862 	bool multiLineMode = false;
2863 
2864 	private dchar[] line;
2865 	private int cursorPosition = 0;
2866 	private int horizontalScrollPosition = 0;
2867 
2868 	private void scrollToEnd() {
2869 		horizontalScrollPosition = (cast(int) line.length);
2870 		horizontalScrollPosition -= availableLineLength();
2871 		if(horizontalScrollPosition < 0)
2872 			horizontalScrollPosition = 0;
2873 	}
2874 
2875 	// used for redrawing the line in the right place
2876 	// and detecting mouse events on our line.
2877 	private int startOfLineX;
2878 	private int startOfLineY;
2879 
2880 	// private string[] cachedCompletionList;
2881 
2882 	// FIXME
2883 	// /// Note that this assumes the tab complete list won't change between actual
2884 	// /// presses of tab by the user. If you pass it a list, it will use it, but
2885 	// /// otherwise it will keep track of the last one to avoid calls to tabComplete.
2886 	private string suggestion(string[] list = null) {
2887 		import std.algorithm, std.utf;
2888 		auto relevantLineSection = line[0 .. cursorPosition];
2889 		// FIXME: see about caching the list if we easily can
2890 		if(list is null)
2891 			list = filterTabCompleteList(tabComplete(relevantLineSection));
2892 
2893 		if(list.length) {
2894 			string commonality = list[0];
2895 			foreach(item; list[1 .. $]) {
2896 				commonality = commonPrefix(commonality, item);
2897 			}
2898 
2899 			if(commonality.length) {
2900 				return commonality[codeLength!char(relevantLineSection) .. $];
2901 			}
2902 		}
2903 
2904 		return null;
2905 	}
2906 
2907 	/// Adds a character at the current position in the line. You can call this too if you hook events for hotkeys or something.
2908 	/// You'll probably want to call redraw() after adding chars.
2909 	void addChar(dchar ch) {
2910 		assert(cursorPosition >= 0 && cursorPosition <= line.length);
2911 		if(cursorPosition == line.length)
2912 			line ~= ch;
2913 		else {
2914 			assert(line.length);
2915 			if(insertMode) {
2916 				line ~= ' ';
2917 				for(int i = cast(int) line.length - 2; i >= cursorPosition; i --)
2918 					line[i + 1] = line[i];
2919 			}
2920 			line[cursorPosition] = ch;
2921 		}
2922 		cursorPosition++;
2923 
2924 		if(cursorPosition >= horizontalScrollPosition + availableLineLength())
2925 			horizontalScrollPosition++;
2926 	}
2927 
2928 	/// .
2929 	void addString(string s) {
2930 		// FIXME: this could be more efficient
2931 		// but does it matter? these lines aren't super long anyway. But then again a paste could be excessively long (prolly accidental, but still)
2932 		foreach(dchar ch; s)
2933 			addChar(ch);
2934 	}
2935 
2936 	/// Deletes the character at the current position in the line.
2937 	/// You'll probably want to call redraw() after deleting chars.
2938 	void deleteChar() {
2939 		if(cursorPosition == line.length)
2940 			return;
2941 		for(int i = cursorPosition; i < line.length - 1; i++)
2942 			line[i] = line[i + 1];
2943 		line = line[0 .. $-1];
2944 		line.assumeSafeAppend();
2945 	}
2946 
2947 	///
2948 	void deleteToEndOfLine() {
2949 		while(cursorPosition < line.length)
2950 			deleteChar();
2951 	}
2952 
2953 	int availableLineLength() {
2954 		return terminal.width - startOfLineX - cast(int) prompt.length - 1;
2955 	}
2956 
2957 	private int lastDrawLength = 0;
2958 	void redraw() {
2959 		terminal.moveTo(startOfLineX, startOfLineY);
2960 
2961 		auto lineLength = availableLineLength();
2962 		if(lineLength < 0)
2963 			throw new Exception("too narrow terminal to draw");
2964 
2965 		terminal.write(prompt);
2966 
2967 		auto towrite = line[horizontalScrollPosition .. $];
2968 		auto cursorPositionToDrawX = cursorPosition - horizontalScrollPosition;
2969 		auto cursorPositionToDrawY = 0;
2970 
2971 		if(towrite.length > lineLength) {
2972 			towrite = towrite[0 .. lineLength];
2973 		}
2974 
2975 		terminal.write(towrite);
2976 
2977 		lineLength -= towrite.length;
2978 
2979 		string suggestion;
2980 
2981 		if(lineLength >= 0) {
2982 			suggestion = ((cursorPosition == towrite.length) && autoSuggest) ? this.suggestion() : null;
2983 			if(suggestion.length) {
2984 				terminal.color(suggestionForeground, background);
2985 				terminal.write(suggestion);
2986 				terminal.color(regularForeground, background);
2987 			}
2988 		}
2989 
2990 		// FIXME: graphemes and utf-8 on suggestion/prompt
2991 		auto written = cast(int) (towrite.length + suggestion.length + prompt.length);
2992 
2993 		if(written < lastDrawLength)
2994 		foreach(i; written .. lastDrawLength)
2995 			terminal.write(" ");
2996 		lastDrawLength = written;
2997 
2998 		terminal.moveTo(startOfLineX + cursorPositionToDrawX + cast(int) prompt.length, startOfLineY + cursorPositionToDrawY);
2999 	}
3000 
3001 	/// Starts getting a new line. Call workOnLine and finishGettingLine afterward.
3002 	///
3003 	/// Make sure that you've flushed your input and output before calling this
3004 	/// function or else you might lose events or get exceptions from this.
3005 	void startGettingLine() {
3006 		// reset from any previous call first
3007 		cursorPosition = 0;
3008 		horizontalScrollPosition = 0;
3009 		justHitTab = false;
3010 		currentHistoryViewPosition = 0;
3011 		if(line.length) {
3012 			line = line[0 .. 0];
3013 			line.assumeSafeAppend();
3014 		}
3015 
3016 		updateCursorPosition();
3017 		terminal.showCursor();
3018 
3019 		lastDrawLength = availableLineLength();
3020 		redraw();
3021 	}
3022 
3023 	private void updateCursorPosition() {
3024 		terminal.flush();
3025 
3026 		// then get the current cursor position to start fresh
3027 		version(Windows) {
3028 			CONSOLE_SCREEN_BUFFER_INFO info;
3029 			GetConsoleScreenBufferInfo(terminal.hConsole, &info);
3030 			startOfLineX = info.dwCursorPosition.X;
3031 			startOfLineY = info.dwCursorPosition.Y;
3032 		} else {
3033 			// request current cursor position
3034 
3035 			// we have to turn off cooked mode to get this answer, otherwise it will all
3036 			// be messed up. (I hate unix terminals, the Windows way is so much easer.)
3037 
3038 			// We also can't use RealTimeConsoleInput here because it also does event loop stuff
3039 			// which would be broken by the child destructor :( (maybe that should be a FIXME)
3040 
3041 			ubyte[128] hack2;
3042 			termios old;
3043 			ubyte[128] hack;
3044 			tcgetattr(terminal.fdIn, &old);
3045 			auto n = old;
3046 			n.c_lflag &= ~(ICANON | ECHO);
3047 			tcsetattr(terminal.fdIn, TCSANOW, &n);
3048 			scope(exit)
3049 				tcsetattr(terminal.fdIn, TCSANOW, &old);
3050 
3051 
3052 			terminal.writeStringRaw("\033[6n");
3053 			terminal.flush();
3054 
3055 			import core.sys.posix.unistd;
3056 			// reading directly to bypass any buffering
3057 			ubyte[16] buffer;
3058 			auto len = read(terminal.fdIn, buffer.ptr, buffer.length);
3059 			if(len <= 0)
3060 				throw new Exception("Couldn't get cursor position to initialize get line");
3061 			auto got = buffer[0 .. len];
3062 			if(got.length < 6)
3063 				throw new Exception("not enough cursor reply answer");
3064 			if(got[0] != '\033' || got[1] != '[' || got[$-1] != 'R')
3065 				throw new Exception("wrong answer for cursor position");
3066 			auto gots = cast(char[]) got[2 .. $-1];
3067 
3068 			import std.conv;
3069 			import std..string;
3070 
3071 			auto pieces = split(gots, ";");
3072 			if(pieces.length != 2) throw new Exception("wtf wrong answer on cursor position");
3073 
3074 			startOfLineX = to!int(pieces[1]) - 1;
3075 			startOfLineY = to!int(pieces[0]) - 1;
3076 		}
3077 
3078 		// updating these too because I can with the more accurate info from above
3079 		terminal._cursorX = startOfLineX;
3080 		terminal._cursorY = startOfLineY;
3081 	}
3082 
3083 	private bool justHitTab;
3084 
3085 	/// for integrating into another event loop
3086 	/// you can pass individual events to this and
3087 	/// the line getter will work on it
3088 	///
3089 	/// returns false when there's nothing more to do
3090 	bool workOnLine(InputEvent e) {
3091 		switch(e.type) {
3092 			case InputEvent.Type.EndOfFileEvent:
3093 				justHitTab = false;
3094 				// FIXME: this should be distinct from an empty line when hit at the beginning
3095 				return false;
3096 			//break;
3097 			case InputEvent.Type.KeyboardEvent:
3098 				auto ev = e.keyboardEvent;
3099 				if(ev.pressed == false)
3100 					return true;
3101 				/* Insert the character (unless it is backspace, tab, or some other control char) */
3102 				auto ch = ev.which;
3103 				switch(ch) {
3104 					case 4: // ctrl+d will also send a newline-equivalent
3105 					case '\r':
3106 					case '\n':
3107 						justHitTab = false;
3108 						return false;
3109 					case '\t':
3110 						auto relevantLineSection = line[0 .. cursorPosition];
3111 						auto possibilities = filterTabCompleteList(tabComplete(relevantLineSection));
3112 						import std.utf;
3113 
3114 						if(possibilities.length == 1) {
3115 							auto toFill = possibilities[0][codeLength!char(relevantLineSection) .. $];
3116 							if(toFill.length) {
3117 								addString(toFill);
3118 								redraw();
3119 							}
3120 							justHitTab = false;
3121 						} else {
3122 							if(justHitTab) {
3123 								justHitTab = false;
3124 								showTabCompleteList(possibilities);
3125 							} else {
3126 								justHitTab = true;
3127 								/* fill it in with as much commonality as there is amongst all the suggestions */
3128 								auto suggestion = this.suggestion(possibilities);
3129 								if(suggestion.length) {
3130 									addString(suggestion);
3131 									redraw();
3132 								}
3133 							}
3134 						}
3135 					break;
3136 					case '\b':
3137 						justHitTab = false;
3138 						if(cursorPosition) {
3139 							cursorPosition--;
3140 							for(int i = cursorPosition; i < line.length - 1; i++)
3141 								line[i] = line[i + 1];
3142 							line = line[0 .. $ - 1];
3143 							line.assumeSafeAppend();
3144 
3145 							if(!multiLineMode) {
3146 								if(horizontalScrollPosition > cursorPosition - 1)
3147 									horizontalScrollPosition = cursorPosition - 1 - availableLineLength();
3148 								if(horizontalScrollPosition < 0)
3149 									horizontalScrollPosition = 0;
3150 							}
3151 
3152 							redraw();
3153 						}
3154 					break;
3155 					case KeyboardEvent.Key.LeftArrow:
3156 						justHitTab = false;
3157 						if(cursorPosition)
3158 							cursorPosition--;
3159 						if(!multiLineMode) {
3160 							if(cursorPosition < horizontalScrollPosition)
3161 								horizontalScrollPosition--;
3162 						}
3163 
3164 						redraw();
3165 					break;
3166 					case KeyboardEvent.Key.RightArrow:
3167 						justHitTab = false;
3168 						if(cursorPosition < line.length)
3169 							cursorPosition++;
3170 						if(!multiLineMode) {
3171 							if(cursorPosition >= horizontalScrollPosition + availableLineLength())
3172 								horizontalScrollPosition++;
3173 						}
3174 
3175 						redraw();
3176 					break;
3177 					case KeyboardEvent.Key.UpArrow:
3178 						justHitTab = false;
3179 						loadFromHistory(currentHistoryViewPosition + 1);
3180 						redraw();
3181 					break;
3182 					case KeyboardEvent.Key.DownArrow:
3183 						justHitTab = false;
3184 						loadFromHistory(currentHistoryViewPosition - 1);
3185 						redraw();
3186 					break;
3187 					case KeyboardEvent.Key.PageUp:
3188 						justHitTab = false;
3189 						loadFromHistory(cast(int) history.length);
3190 						redraw();
3191 					break;
3192 					case KeyboardEvent.Key.PageDown:
3193 						justHitTab = false;
3194 						loadFromHistory(0);
3195 						redraw();
3196 					break;
3197 					case 1: // ctrl+a does home too in the emacs keybindings
3198 					case KeyboardEvent.Key.Home:
3199 						justHitTab = false;
3200 						cursorPosition = 0;
3201 						horizontalScrollPosition = 0;
3202 						redraw();
3203 					break;
3204 					case 5: // ctrl+e from emacs
3205 					case KeyboardEvent.Key.End:
3206 						justHitTab = false;
3207 						cursorPosition = cast(int) line.length;
3208 						scrollToEnd();
3209 						redraw();
3210 					break;
3211 					case KeyboardEvent.Key.Insert:
3212 						justHitTab = false;
3213 						insertMode = !insertMode;
3214 						// FIXME: indicate this on the UI somehow
3215 						// like change the cursor or something
3216 					break;
3217 					case KeyboardEvent.Key.Delete:
3218 						justHitTab = false;
3219 						if(ev.modifierState & ModifierState.control)
3220 							deleteToEndOfLine();
3221 						else
3222 							deleteChar();
3223 						redraw();
3224 					break;
3225 					case 11: // ctrl+k is delete to end of line from emacs
3226 						justHitTab = false;
3227 						deleteToEndOfLine();
3228 						redraw();
3229 					break;
3230 					default:
3231 						justHitTab = false;
3232 						if(e.keyboardEvent.isCharacter)
3233 							addChar(ch);
3234 						redraw();
3235 				}
3236 			break;
3237 			case InputEvent.Type.PasteEvent:
3238 				justHitTab = false;
3239 				addString(e.pasteEvent.pastedText);
3240 				redraw();
3241 			break;
3242 			case InputEvent.Type.MouseEvent:
3243 				/* Clicking with the mouse to move the cursor is so much easier than arrowing
3244 				   or even emacs/vi style movements much of the time, so I'ma support it. */
3245 
3246 				auto me = e.mouseEvent;
3247 				if(me.eventType == MouseEvent.Type.Pressed) {
3248 					if(me.buttons & MouseEvent.Button.Left) {
3249 						if(me.y == startOfLineY) {
3250 							// FIXME: prompt.length should be graphemes or at least code poitns
3251 							int p = me.x - startOfLineX - cast(int) prompt.length + horizontalScrollPosition;
3252 							if(p >= 0 && p < line.length) {
3253 								justHitTab = false;
3254 								cursorPosition = p;
3255 								redraw();
3256 							}
3257 						}
3258 					}
3259 				}
3260 			break;
3261 			case InputEvent.Type.SizeChangedEvent:
3262 				/* We'll adjust the bounding box. If you don't like this, handle SizeChangedEvent
3263 				   yourself and then don't pass it to this function. */
3264 				// FIXME
3265 			break;
3266 			case InputEvent.Type.UserInterruptionEvent:
3267 				/* I'll take this as canceling the line. */
3268 				throw new UserInterruptionException();
3269 			//break;
3270 			case InputEvent.Type.HangupEvent:
3271 				/* I'll take this as canceling the line. */
3272 				throw new HangupException();
3273 			//break;
3274 			default:
3275 				/* ignore. ideally it wouldn't be passed to us anyway! */
3276 		}
3277 
3278 		return true;
3279 	}
3280 
3281 	string finishGettingLine() {
3282 		import std.conv;
3283 		auto f = to!string(line);
3284 		auto history = historyFilter(f);
3285 		if(history !is null)
3286 			this.history ~= history;
3287 
3288 		// FIXME: we should hide the cursor if it was hidden in the call to startGettingLine
3289 		return f;
3290 	}
3291 }
3292 
3293 /// Adds default constructors that just forward to the superclass
3294 mixin template LineGetterConstructors() {
3295 	this(Terminal* tty, string historyFilename = null) {
3296 		super(tty, historyFilename);
3297 	}
3298 }
3299 
3300 /// This is a line getter that customizes the tab completion to
3301 /// fill in file names separated by spaces, like a command line thing.
3302 class FileLineGetter : LineGetter {
3303 	mixin LineGetterConstructors;
3304 
3305 	/// You can set this property to tell it where to search for the files
3306 	/// to complete.
3307 	string searchDirectory = ".";
3308 
3309 	override protected string[] tabComplete(in dchar[] candidate) {
3310 		import std.file, std.conv, std.algorithm, std..string;
3311 		const(dchar)[] soFar = candidate;
3312 		auto idx = candidate.lastIndexOf(" ");
3313 		if(idx != -1)
3314 			soFar = candidate[idx + 1 .. $];
3315 
3316 		string[] list;
3317 		foreach(string name; dirEntries(searchDirectory, SpanMode.breadth)) {
3318 			// try without the ./
3319 			if(startsWith(name[2..$], soFar))
3320 				list ~= text(candidate, name[searchDirectory.length + 1 + soFar.length .. $]);
3321 			else // and with
3322 			if(startsWith(name, soFar))
3323 				list ~= text(candidate, name[soFar.length .. $]);
3324 		}
3325 
3326 		return list;
3327 	}
3328 }
3329 
3330 version(Windows) {
3331 	// to get the directory for saving history in the line things
3332 	enum CSIDL_APPDATA = 26;
3333 	extern(Windows) HRESULT SHGetFolderPathA(HWND, int, HANDLE, DWORD, LPSTR);
3334 }
3335 
3336 
3337 
3338 
3339 
3340 /* Like getting a line, printing a lot of lines is kinda important too, so I'm including
3341    that widget here too. */
3342 
3343 
3344 struct ScrollbackBuffer {
3345 
3346 	bool demandsAttention;
3347 
3348 	this(string name) {
3349 		this.name = name;
3350 	}
3351 
3352 	void write(T...)(T t) {
3353 		import std.conv : text;
3354 		addComponent(text(t), foreground_, background_, null);
3355 	}
3356 
3357 	void writeln(T...)(T t) {
3358 		write(t, "\n");
3359 	}
3360 
3361 	void writef(T...)(string fmt, T t) {
3362 		import std.format: format;
3363 		write(format(fmt, t));
3364 	}
3365 
3366 	void writefln(T...)(string fmt, T t) {
3367 		writef(fmt, t, "\n");
3368 	}
3369 
3370 	void clear() {
3371 		lines = null;
3372 		clickRegions = null;
3373 		scrollbackPosition = 0;
3374 	}
3375 
3376 	int foreground_ = Color.DEFAULT, background_ = Color.DEFAULT;
3377 	void color(int foreground, int background) {
3378 		this.foreground_ = foreground;
3379 		this.background_ = background;
3380 	}
3381 
3382 	void addComponent(string text, int foreground, int background, bool delegate() onclick) {
3383 		if(lines.length == 0) {
3384 			addLine();
3385 		}
3386 		bool first = true;
3387 		import std.algorithm;
3388 		foreach(t; splitter(text, "\n")) {
3389 			if(!first) addLine();
3390 			first = false;
3391 			lines[$-1].components ~= LineComponent(t, foreground, background, onclick);
3392 		}
3393 	}
3394 
3395 	void addLine() {
3396 		lines ~= Line();
3397 		if(scrollbackPosition) // if the user is scrolling back, we want to keep them basically centered where they are
3398 			scrollbackPosition++;
3399 	}
3400 
3401 	void addLine(string line) {
3402 		lines ~= Line([LineComponent(line)]);
3403 		if(scrollbackPosition) // if the user is scrolling back, we want to keep them basically centered where they are
3404 			scrollbackPosition++;
3405 	}
3406 
3407 	void scrollUp(int lines = 1) {
3408 		scrollbackPosition += lines;
3409 		//if(scrollbackPosition >= this.lines.length)
3410 		//	scrollbackPosition = cast(int) this.lines.length - 1;
3411 	}
3412 
3413 	void scrollDown(int lines = 1) {
3414 		scrollbackPosition -= lines;
3415 		if(scrollbackPosition < 0)
3416 			scrollbackPosition = 0;
3417 	}
3418 
3419 	void scrollToBottom() {
3420 		scrollbackPosition = 0;
3421 	}
3422 
3423 	// this needs width and height to know how to word wrap it
3424 	void scrollToTop(int width, int height) {
3425 		scrollbackPosition = scrollTopPosition(width, height);
3426 	}
3427 
3428 
3429 
3430 
3431 	struct LineComponent {
3432 		string text;
3433 		bool isRgb;
3434 		union {
3435 			int color;
3436 			RGB colorRgb;
3437 		}
3438 		union {
3439 			int background;
3440 			RGB backgroundRgb;
3441 		}
3442 		bool delegate() onclick; // return true if you need to redraw
3443 
3444 		// 16 color ctor
3445 		this(string text, int color = Color.DEFAULT, int background = Color.DEFAULT, bool delegate() onclick = null) {
3446 			this.text = text;
3447 			this.color = color;
3448 			this.background = background;
3449 			this.onclick = onclick;
3450 			this.isRgb = false;
3451 		}
3452 
3453 		// true color ctor
3454 		this(string text, RGB colorRgb, RGB backgroundRgb = RGB(0, 0, 0), bool delegate() onclick = null) {
3455 			this.text = text;
3456 			this.colorRgb = colorRgb;
3457 			this.backgroundRgb = backgroundRgb;
3458 			this.onclick = onclick;
3459 			this.isRgb = true;
3460 		}
3461 	}
3462 
3463 	struct Line {
3464 		LineComponent[] components;
3465 		int length() {
3466 			int l = 0;
3467 			foreach(c; components)
3468 				l += c.text.length;
3469 			return l;
3470 		}
3471 	}
3472 
3473 	// FIXME: limit scrollback lines.length
3474 
3475 	Line[] lines;
3476 	string name;
3477 
3478 	int x, y, width, height;
3479 
3480 	int scrollbackPosition;
3481 
3482 
3483 	int scrollTopPosition(int width, int height) {
3484 		int lineCount;
3485 
3486 		foreach_reverse(line; lines) {
3487 			int written = 0;
3488 			comp_loop: foreach(cidx, component; line.components) {
3489 				auto towrite = component.text;
3490 				foreach(idx, dchar ch; towrite) {
3491 					if(written >= width) {
3492 						lineCount++;
3493 						written = 0;
3494 					}
3495 
3496 					if(ch == '\t')
3497 						written += 8; // FIXME
3498 					else
3499 						written++;
3500 				}
3501 			}
3502 			lineCount++;
3503 		}
3504 
3505 		//if(lineCount > height)
3506 			return lineCount - height;
3507 		//return 0;
3508 	}
3509 
3510 	void drawInto(Terminal* terminal, in int x = 0, in int y = 0, int width = 0, int height = 0) {
3511 		if(lines.length == 0)
3512 			return;
3513 
3514 		if(width == 0)
3515 			width = terminal.width;
3516 		if(height == 0)
3517 			height = terminal.height;
3518 
3519 		this.x = x;
3520 		this.y = y;
3521 		this.width = width;
3522 		this.height = height;
3523 
3524 		/* We need to figure out how much is going to fit
3525 		   in a first pass, so we can figure out where to
3526 		   start drawing */
3527 
3528 		int remaining = height + scrollbackPosition;
3529 		int start = cast(int) lines.length;
3530 		int howMany = 0;
3531 
3532 		bool firstPartial = false;
3533 
3534 		static struct Idx {
3535 			size_t cidx;
3536 			size_t idx;
3537 		}
3538 
3539 		Idx firstPartialStartIndex;
3540 
3541 		// this is private so I know we can safe append
3542 		clickRegions.length = 0;
3543 		clickRegions.assumeSafeAppend();
3544 
3545 		// FIXME: should prolly handle \n and \r in here too.
3546 
3547 		// we'll work backwards to figure out how much will fit...
3548 		// this will give accurate per-line things even with changing width and wrapping
3549 		// while being generally efficient - we usually want to show the end of the list
3550 		// anyway; actually using the scrollback is a bit of an exceptional case.
3551 
3552 		// It could probably do this instead of on each redraw, on each resize or insertion.
3553 		// or at least cache between redraws until one of those invalidates it.
3554 		foreach_reverse(line; lines) {
3555 			int written = 0;
3556 			int brokenLineCount;
3557 			Idx[16] lineBreaksBuffer;
3558 			Idx[] lineBreaks = lineBreaksBuffer[];
3559 			comp_loop: foreach(cidx, component; line.components) {
3560 				auto towrite = component.text;
3561 				foreach(idx, dchar ch; towrite) {
3562 					if(written >= width) {
3563 						if(brokenLineCount == lineBreaks.length)
3564 							lineBreaks ~= Idx(cidx, idx);
3565 						else
3566 							lineBreaks[brokenLineCount] = Idx(cidx, idx);
3567 
3568 						brokenLineCount++;
3569 
3570 						written = 0;
3571 					}
3572 
3573 					if(ch == '\t')
3574 						written += 8; // FIXME
3575 					else
3576 						written++;
3577 				}
3578 			}
3579 
3580 			lineBreaks = lineBreaks[0 .. brokenLineCount];
3581 
3582 			foreach_reverse(lineBreak; lineBreaks) {
3583 				if(remaining == 1) {
3584 					firstPartial = true;
3585 					firstPartialStartIndex = lineBreak;
3586 					break;
3587 				} else {
3588 					remaining--;
3589 				}
3590 				if(remaining <= 0)
3591 					break;
3592 			}
3593 
3594 			remaining--;
3595 
3596 			start--;
3597 			howMany++;
3598 			if(remaining <= 0)
3599 				break;
3600 		}
3601 
3602 		// second pass: actually draw it
3603 		int linePos = remaining;
3604 
3605 		foreach(idx, line; lines[start .. start + howMany]) {
3606 			int written = 0;
3607 
3608 			if(linePos < 0) {
3609 				linePos++;
3610 				continue;
3611 			}
3612 
3613 			terminal.moveTo(x, y + ((linePos >= 0) ? linePos : 0));
3614 
3615 			auto todo = line.components;
3616 
3617 			if(firstPartial) {
3618 				todo = todo[firstPartialStartIndex.cidx .. $];
3619 			}
3620 
3621 			foreach(ref component; todo) {
3622 				if(component.isRgb)
3623 					terminal.setTrueColor(component.colorRgb, component.backgroundRgb);
3624 				else
3625 					terminal.color(component.color, component.background);
3626 				auto towrite = component.text;
3627 
3628 				again:
3629 
3630 				if(linePos >= height)
3631 					break;
3632 
3633 				if(firstPartial) {
3634 					towrite = towrite[firstPartialStartIndex.idx .. $];
3635 					firstPartial = false;
3636 				}
3637 
3638 				foreach(chIdx, dchar ch; towrite) {
3639 					if(written >= width) {
3640 						clickRegions ~= ClickRegion(&component, terminal.cursorX, terminal.cursorY, written);
3641 						terminal.write(towrite[0 .. chIdx]);
3642 						towrite = towrite[chIdx .. $];
3643 						linePos++;
3644 						written = 0;
3645 						terminal.moveTo(x, y + linePos);
3646 						goto again;
3647 					}
3648 
3649 					if(ch == '\t')
3650 						written += 8; // FIXME
3651 					else
3652 						written++;
3653 				}
3654 
3655 				if(towrite.length) {
3656 					clickRegions ~= ClickRegion(&component, terminal.cursorX, terminal.cursorY, written);
3657 					terminal.write(towrite);
3658 				}
3659 			}
3660 
3661 			if(written < width) {
3662 				terminal.color(Color.DEFAULT, Color.DEFAULT);
3663 				foreach(i; written .. width)
3664 					terminal.write(" ");
3665 			}
3666 
3667 			linePos++;
3668 
3669 			if(linePos >= height)
3670 				break;
3671 		}
3672 
3673 		if(linePos < height) {
3674 			terminal.color(Color.DEFAULT, Color.DEFAULT);
3675 			foreach(i; linePos .. height) {
3676 				if(i >= 0 && i < height) {
3677 					terminal.moveTo(x, y + i);
3678 					foreach(w; 0 .. width)
3679 						terminal.write(" ");
3680 				}
3681 			}
3682 		}
3683 	}
3684 
3685 	private struct ClickRegion {
3686 		LineComponent* component;
3687 		int xStart;
3688 		int yStart;
3689 		int length;
3690 	}
3691 	private ClickRegion[] clickRegions;
3692 
3693 	/// Default event handling for this widget. Call this only after drawing it into a rectangle
3694 	/// and only if the event ought to be dispatched to it (which you determine however you want;
3695 	/// you could dispatch all events to it, or perhaps filter some out too)
3696 	///
3697 	/// Returns true if it should be redrawn
3698 	bool handleEvent(InputEvent e) {
3699 		final switch(e.type) {
3700 			case InputEvent.Type.KeyboardEvent:
3701 				auto ev = e.keyboardEvent;
3702 
3703 				demandsAttention = false;
3704 
3705 				switch(ev.which) {
3706 					case KeyboardEvent.Key.UpArrow:
3707 						scrollUp();
3708 						return true;
3709 					case KeyboardEvent.Key.DownArrow:
3710 						scrollDown();
3711 						return true;
3712 					case KeyboardEvent.Key.PageUp:
3713 						scrollUp(height);
3714 						return true;
3715 					case KeyboardEvent.Key.PageDown:
3716 						scrollDown(height);
3717 						return true;
3718 					default:
3719 						// ignore
3720 				}
3721 			break;
3722 			case InputEvent.Type.MouseEvent:
3723 				auto ev = e.mouseEvent;
3724 				if(ev.x >= x && ev.x < x + width && ev.y >= y && ev.y < y + height) {
3725 					demandsAttention = false;
3726 					// it is inside our box, so do something with it
3727 					auto mx = ev.x - x;
3728 					auto my = ev.y - y;
3729 
3730 					if(ev.eventType == MouseEvent.Type.Pressed) {
3731 						if(ev.buttons & MouseEvent.Button.Left) {
3732 							foreach(region; clickRegions)
3733 								if(ev.x >= region.xStart && ev.x < region.xStart + region.length && ev.y == region.yStart)
3734 									if(region.component.onclick !is null)
3735 										return region.component.onclick();
3736 						}
3737 						if(ev.buttons & MouseEvent.Button.ScrollUp) {
3738 							scrollUp();
3739 							return true;
3740 						}
3741 						if(ev.buttons & MouseEvent.Button.ScrollDown) {
3742 							scrollDown();
3743 							return true;
3744 						}
3745 					}
3746 				} else {
3747 					// outside our area, free to ignore
3748 				}
3749 			break;
3750 			case InputEvent.Type.SizeChangedEvent:
3751 				// (size changed might be but it needs to be handled at a higher level really anyway)
3752 				// though it will return true because it probably needs redrawing anyway.
3753 				return true;
3754 			case InputEvent.Type.UserInterruptionEvent:
3755 				throw new UserInterruptionException();
3756 			case InputEvent.Type.HangupEvent:
3757 				throw new HangupException();
3758 			case InputEvent.Type.EndOfFileEvent:
3759 				// ignore, not relevant to this
3760 			break;
3761 			case InputEvent.Type.CharacterEvent:
3762 			case InputEvent.Type.NonCharacterKeyEvent:
3763 				// obsolete, ignore them until they are removed
3764 			break;
3765 			case InputEvent.Type.CustomEvent:
3766 			case InputEvent.Type.PasteEvent:
3767 				// ignored, not relevant to us
3768 			break;
3769 		}
3770 
3771 		return false;
3772 	}
3773 }
3774 
3775 
3776 class UserInterruptionException : Exception {
3777 	this() { super("Ctrl+C"); }
3778 }
3779 class HangupException : Exception {
3780 	this() { super("Hup"); }
3781 }
3782 
3783 
3784 
3785 /*
3786 
3787 	// more efficient scrolling
3788 	http://msdn.microsoft.com/en-us/library/windows/desktop/ms685113%28v=vs.85%29.aspx
3789 	// and the unix sequences
3790 
3791 
3792 	rxvt documentation:
3793 	use this to finish the input magic for that
3794 
3795 
3796        For the keypad, use Shift to temporarily override Application-Keypad
3797        setting use Num_Lock to toggle Application-Keypad setting if Num_Lock
3798        is off, toggle Application-Keypad setting. Also note that values of
3799        Home, End, Delete may have been compiled differently on your system.
3800 
3801                          Normal       Shift         Control      Ctrl+Shift
3802        Tab               ^I           ESC [ Z       ^I           ESC [ Z
3803        BackSpace         ^H           ^?            ^?           ^?
3804        Find              ESC [ 1 ~    ESC [ 1 $     ESC [ 1 ^    ESC [ 1 @
3805        Insert            ESC [ 2 ~    paste         ESC [ 2 ^    ESC [ 2 @
3806        Execute           ESC [ 3 ~    ESC [ 3 $     ESC [ 3 ^    ESC [ 3 @
3807        Select            ESC [ 4 ~    ESC [ 4 $     ESC [ 4 ^    ESC [ 4 @
3808        Prior             ESC [ 5 ~    scroll-up     ESC [ 5 ^    ESC [ 5 @
3809        Next              ESC [ 6 ~    scroll-down   ESC [ 6 ^    ESC [ 6 @
3810        Home              ESC [ 7 ~    ESC [ 7 $     ESC [ 7 ^    ESC [ 7 @
3811        End               ESC [ 8 ~    ESC [ 8 $     ESC [ 8 ^    ESC [ 8 @
3812        Delete            ESC [ 3 ~    ESC [ 3 $     ESC [ 3 ^    ESC [ 3 @
3813        F1                ESC [ 11 ~   ESC [ 23 ~    ESC [ 11 ^   ESC [ 23 ^
3814        F2                ESC [ 12 ~   ESC [ 24 ~    ESC [ 12 ^   ESC [ 24 ^
3815        F3                ESC [ 13 ~   ESC [ 25 ~    ESC [ 13 ^   ESC [ 25 ^
3816        F4                ESC [ 14 ~   ESC [ 26 ~    ESC [ 14 ^   ESC [ 26 ^
3817        F5                ESC [ 15 ~   ESC [ 28 ~    ESC [ 15 ^   ESC [ 28 ^
3818        F6                ESC [ 17 ~   ESC [ 29 ~    ESC [ 17 ^   ESC [ 29 ^
3819        F7                ESC [ 18 ~   ESC [ 31 ~    ESC [ 18 ^   ESC [ 31 ^
3820        F8                ESC [ 19 ~   ESC [ 32 ~    ESC [ 19 ^   ESC [ 32 ^
3821        F9                ESC [ 20 ~   ESC [ 33 ~    ESC [ 20 ^   ESC [ 33 ^
3822        F10               ESC [ 21 ~   ESC [ 34 ~    ESC [ 21 ^   ESC [ 34 ^
3823        F11               ESC [ 23 ~   ESC [ 23 $    ESC [ 23 ^   ESC [ 23 @
3824        F12               ESC [ 24 ~   ESC [ 24 $    ESC [ 24 ^   ESC [ 24 @
3825        F13               ESC [ 25 ~   ESC [ 25 $    ESC [ 25 ^   ESC [ 25 @
3826        F14               ESC [ 26 ~   ESC [ 26 $    ESC [ 26 ^   ESC [ 26 @
3827        F15 (Help)        ESC [ 28 ~   ESC [ 28 $    ESC [ 28 ^   ESC [ 28 @
3828        F16 (Menu)        ESC [ 29 ~   ESC [ 29 $    ESC [ 29 ^   ESC [ 29 @
3829 
3830        F17               ESC [ 31 ~   ESC [ 31 $    ESC [ 31 ^   ESC [ 31 @
3831        F18               ESC [ 32 ~   ESC [ 32 $    ESC [ 32 ^   ESC [ 32 @
3832        F19               ESC [ 33 ~   ESC [ 33 $    ESC [ 33 ^   ESC [ 33 @
3833        F20               ESC [ 34 ~   ESC [ 34 $    ESC [ 34 ^   ESC [ 34 @
3834                                                                  Application
3835        Up                ESC [ A      ESC [ a       ESC O a      ESC O A
3836        Down              ESC [ B      ESC [ b       ESC O b      ESC O B
3837        Right             ESC [ C      ESC [ c       ESC O c      ESC O C
3838        Left              ESC [ D      ESC [ d       ESC O d      ESC O D
3839        KP_Enter          ^M                                      ESC O M
3840        KP_F1             ESC O P                                 ESC O P
3841        KP_F2             ESC O Q                                 ESC O Q
3842        KP_F3             ESC O R                                 ESC O R
3843        KP_F4             ESC O S                                 ESC O S
3844        XK_KP_Multiply    *                                       ESC O j
3845        XK_KP_Add         +                                       ESC O k
3846        XK_KP_Separator   ,                                       ESC O l
3847        XK_KP_Subtract    -                                       ESC O m
3848        XK_KP_Decimal     .                                       ESC O n
3849        XK_KP_Divide      /                                       ESC O o
3850        XK_KP_0           0                                       ESC O p
3851        XK_KP_1           1                                       ESC O q
3852        XK_KP_2           2                                       ESC O r
3853        XK_KP_3           3                                       ESC O s
3854        XK_KP_4           4                                       ESC O t
3855        XK_KP_5           5                                       ESC O u
3856        XK_KP_6           6                                       ESC O v
3857        XK_KP_7           7                                       ESC O w
3858        XK_KP_8           8                                       ESC O x
3859        XK_KP_9           9                                       ESC O y
3860 */
3861 
3862 /*
3863 	The Xterm palette progression is:
3864 	[0, 95, 135, 175, 215, 255]
3865 
3866 	So if I take the color and subtract 55, then div 40, I get
3867 	it into one of these areas. If I add 20, I get a reasonable
3868 	rounding.
3869 */
3870 
3871 ubyte colorToXTermPaletteIndex(RGB color) {
3872 	/*
3873 		Here, I will round off to the color ramp or the
3874 		greyscale. I will NOT use the bottom 16 colors because
3875 		there's duplicates (or very close enough) to them in here
3876 	*/
3877 
3878 	if(color.r == color.g && color.g == color.b) {
3879 		// grey - find one of them:
3880 		if(color.r == 0) return 0;
3881 		// meh don't need those two, let's simplify branche
3882 		//if(color.r == 0xc0) return 7;
3883 		//if(color.r == 0x80) return 8;
3884 		// it isn't == 255 because it wants to catch anything
3885 		// that would wrap the simple algorithm below back to 0.
3886 		if(color.r >= 248) return 15;
3887 
3888 		// there's greys in the color ramp too, but these
3889 		// are all close enough as-is, no need to complicate
3890 		// algorithm for approximation anyway
3891 
3892 		return cast(ubyte) (232 + ((color.r - 8) / 10));
3893 	}
3894 
3895 	// if it isn't grey, it is color
3896 
3897 	// the ramp goes blue, green, red, with 6 of each,
3898 	// so just multiplying will give something good enough
3899 
3900 	// will give something between 0 and 5, with some rounding
3901 	auto r = (cast(int) color.r - 35) / 40;
3902 	auto g = (cast(int) color.g - 35) / 40;
3903 	auto b = (cast(int) color.b - 35) / 40;
3904 
3905 	return cast(ubyte) (16 + b + g*6 + r*36);
3906 }
3907 
3908 /++
3909 	Represents a 24-bit color.
3910 
3911 
3912 	$(TIP You can convert these to and from [arsd.color.Color] using
3913 	      `.tupleof`:
3914 
3915 		---
3916 	      	RGB rgb;
3917 		Color c = Color(rgb.tupleof);
3918 		---
3919 	)
3920 +/
3921 struct RGB {
3922 	ubyte r; ///
3923 	ubyte g; ///
3924 	ubyte b; ///
3925 	// terminal can't actually use this but I want the value
3926 	// there for assignment to an arsd.color.Color
3927 	private ubyte a = 255;
3928 }
3929 
3930 // This is an approximation too for a few entries, but a very close one.
3931 RGB xtermPaletteIndexToColor(int paletteIdx) {
3932 	RGB color;
3933 
3934 	if(paletteIdx < 16) {
3935 		if(paletteIdx == 7)
3936 			return RGB(0xc0, 0xc0, 0xc0);
3937 		else if(paletteIdx == 8)
3938 			return RGB(0x80, 0x80, 0x80);
3939 
3940 		color.r = (paletteIdx & 0b001) ? ((paletteIdx & 0b1000) ? 0xff : 0x80) : 0x00;
3941 		color.g = (paletteIdx & 0b010) ? ((paletteIdx & 0b1000) ? 0xff : 0x80) : 0x00;
3942 		color.b = (paletteIdx & 0b100) ? ((paletteIdx & 0b1000) ? 0xff : 0x80) : 0x00;
3943 
3944 	} else if(paletteIdx < 232) {
3945 		// color ramp, 6x6x6 cube
3946 		color.r = cast(ubyte) ((paletteIdx - 16) / 36 * 40 + 55);
3947 		color.g = cast(ubyte) (((paletteIdx - 16) % 36) / 6 * 40 + 55);
3948 		color.b = cast(ubyte) ((paletteIdx - 16) % 6 * 40 + 55);
3949 
3950 		if(color.r == 55) color.r = 0;
3951 		if(color.g == 55) color.g = 0;
3952 		if(color.b == 55) color.b = 0;
3953 	} else {
3954 		// greyscale ramp, from 0x8 to 0xee
3955 		color.r = cast(ubyte) (8 + (paletteIdx - 232) * 10);
3956 		color.g = color.r;
3957 		color.b = color.g;
3958 	}
3959 
3960 	return color;
3961 }
3962 
3963 int approximate16Color(RGB color) {
3964 	int c;
3965 	c |= color.r > 64 ? RED_BIT : 0;
3966 	c |= color.g > 64 ? GREEN_BIT : 0;
3967 	c |= color.b > 64 ? BLUE_BIT : 0;
3968 
3969 	c |= (((color.r + color.g + color.b) / 3) > 80) ? Bright : 0;
3970 
3971 	return c;
3972 }