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