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