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