1 /**
2  * Module for supporting cursor and color manipulation on the console.
3  *
4  * The main interface for this module is the Terminal struct, which
5  * encapsulates the functions of the terminal. Creating an instance of
6  * this struct will perform console initialization; when the struct
7  * goes out of scope, any changes in console settings will be automatically
8  * reverted.
9  *
10  * Note: on Posix, it traps SIGINT and translates it into an input event. You should
11  * keep your event loop moving and keep an eye open for this to exit cleanly; simply break
12  * your event loop upon receiving a UserInterruptionEvent. (Without
13  * the signal handler, ctrl+c can leave your terminal in a bizarre state.)
14  *
15  * As a user, if you have to forcibly kill your program and the event doesn't work, there's still ctrl+\
16  */
17 module terminal;
18 
19 // FIXME: ctrl+d eof on stdin
20 
21 // FIXME: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686016%28v=vs.85%29.aspx
22 
23 version(linux)
24 	enum SIGWINCH = 28; // FIXME: confirm this is correct on other posix
25 
26 version(Posix) {
27 	__gshared bool windowSizeChanged = false;
28 	__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
29 	__gshared bool hangedUp = false; /// similar to interrupted.
30 
31 	version(with_eventloop)
32 		struct SignalFired {}
33 
34 	extern(C)
35 	void sizeSignalHandler(int sigNumber) nothrow {
36 		windowSizeChanged = true;
37 		version(with_eventloop) {
38 			import arsd.eventloop;
39 			try
40 				send(SignalFired());
41 			catch(Exception) {}
42 		}
43 	}
44 	extern(C)
45 	void interruptSignalHandler(int sigNumber) nothrow {
46 		interrupted = true;
47 		version(with_eventloop) {
48 			import arsd.eventloop;
49 			try
50 				send(SignalFired());
51 			catch(Exception) {}
52 		}
53 	}
54 	extern(C)
55 	void hangupSignalHandler(int sigNumber) nothrow {
56 		hangedUp = true;
57 		version(with_eventloop) {
58 			import arsd.eventloop;
59 			try
60 				send(SignalFired());
61 			catch(Exception) {}
62 		}
63 	}
64 
65 }
66 
67 // parts of this were taken from Robik's ConsoleD
68 // https://github.com/robik/ConsoleD/blob/master/consoled.d
69 
70 // Uncomment this line to get a main() to demonstrate this module's
71 // capabilities.
72 //version = Demo
73 
74 version(Windows) {
75 	import core.sys.windows.windows;
76 	import std.string : toStringz;
77 	private {
78 		enum RED_BIT = 4;
79 		enum GREEN_BIT = 2;
80 		enum BLUE_BIT = 1;
81 	}
82 }
83 
84 version(Posix) {
85 	import core.sys.posix.termios;
86 	import core.sys.posix.unistd;
87 	import unix = core.sys.posix.unistd;
88 	import core.sys.posix.sys.types;
89 	import core.sys.posix.sys.time;
90 	import core.stdc.stdio;
91 	private {
92 		enum RED_BIT = 1;
93 		enum GREEN_BIT = 2;
94 		enum BLUE_BIT = 4;
95 	}
96 
97 	extern(C) int ioctl(int, int, ...);
98 	enum int TIOCGWINSZ = 0x5413;
99 	struct winsize {
100 		ushort ws_row;
101 		ushort ws_col;
102 		ushort ws_xpixel;
103 		ushort ws_ypixel;
104 	}
105 
106 	// 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).
107 
108 	// this way we'll have some definitions for 99% of typical PC cases even without any help from the local operating system
109 
110 	enum string builtinTermcap = `
111 # Generic VT entry.
112 vg|vt-generic|Generic VT entries:\
113 	:bs:mi:ms:pt:xn:xo:it#8:\
114 	:RA=\E[?7l:SA=\E?7h:\
115 	:bl=^G:cr=^M:ta=^I:\
116 	:cm=\E[%i%d;%dH:\
117 	:le=^H:up=\E[A:do=\E[B:nd=\E[C:\
118 	:LE=\E[%dD:RI=\E[%dC:UP=\E[%dA:DO=\E[%dB:\
119 	:ho=\E[H:cl=\E[H\E[2J:ce=\E[K:cb=\E[1K:cd=\E[J:sf=\ED:sr=\EM:\
120 	:ct=\E[3g:st=\EH:\
121 	:cs=\E[%i%d;%dr:sc=\E7:rc=\E8:\
122 	:ei=\E[4l:ic=\E[@:IC=\E[%d@:al=\E[L:AL=\E[%dL:\
123 	:dc=\E[P:DC=\E[%dP:dl=\E[M:DL=\E[%dM:\
124 	:so=\E[7m:se=\E[m:us=\E[4m:ue=\E[m:\
125 	:mb=\E[5m:mh=\E[2m:md=\E[1m:mr=\E[7m:me=\E[m:\
126 	:sc=\E7:rc=\E8:kb=\177:\
127 	:ku=\E[A:kd=\E[B:kr=\E[C:kl=\E[D:
128 
129 
130 # Slackware 3.1 linux termcap entry (Sat Apr 27 23:03:58 CDT 1996):
131 lx|linux|console|con80x25|LINUX System Console:\
132         :do=^J:co#80:li#25:cl=\E[H\E[J:sf=\ED:sb=\EM:\
133         :le=^H:bs:am:cm=\E[%i%d;%dH:nd=\E[C:up=\E[A:\
134         :ce=\E[K:cd=\E[J:so=\E[7m:se=\E[27m:us=\E[36m:ue=\E[m:\
135         :md=\E[1m:mr=\E[7m:mb=\E[5m:me=\E[m:is=\E[1;25r\E[25;1H:\
136         :ll=\E[1;25r\E[25;1H:al=\E[L:dc=\E[P:dl=\E[M:\
137         :it#8:ku=\E[A:kd=\E[B:kr=\E[C:kl=\E[D:kb=^H:ti=\E[r\E[H:\
138         :ho=\E[H:kP=\E[5~:kN=\E[6~:kH=\E[4~:kh=\E[1~:kD=\E[3~:kI=\E[2~:\
139         :k1=\E[[A:k2=\E[[B:k3=\E[[C:k4=\E[[D:k5=\E[[E:k6=\E[17~:\
140 	:F1=\E[23~:F2=\E[24~:\
141         :k7=\E[18~:k8=\E[19~:k9=\E[20~:k0=\E[21~:K1=\E[1~:K2=\E[5~:\
142         :K4=\E[4~:K5=\E[6~:\
143         :pt:sr=\EM:vt#3:xn:km:bl=^G:vi=\E[?25l:ve=\E[?25h:vs=\E[?25h:\
144         :sc=\E7:rc=\E8:cs=\E[%i%d;%dr:\
145         :r1=\Ec:r2=\Ec:r3=\Ec:
146 
147 # Some other, commonly used linux console entries.
148 lx|con80x28:co#80:li#28:tc=linux:
149 lx|con80x43:co#80:li#43:tc=linux:
150 lx|con80x50:co#80:li#50:tc=linux:
151 lx|con100x37:co#100:li#37:tc=linux:
152 lx|con100x40:co#100:li#40:tc=linux:
153 lx|con132x43:co#132:li#43:tc=linux:
154 
155 # vt102 - vt100 + insert line etc. VT102 does not have insert character.
156 v2|vt102|DEC vt102 compatible:\
157 	:co#80:li#24:\
158 	:ic@:IC@:\
159 	:is=\E[m\E[?1l\E>:\
160 	:rs=\E[m\E[?1l\E>:\
161 	:eA=\E)0:as=^N:ae=^O:ac=aaffggjjkkllmmnnooqqssttuuvvwwxx:\
162 	:ks=:ke=:\
163 	:k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:\
164 	:tc=vt-generic:
165 
166 # vt100 - really vt102 without insert line, insert char etc.
167 vt|vt100|DEC vt100 compatible:\
168 	:im@:mi@:al@:dl@:ic@:dc@:AL@:DL@:IC@:DC@:\
169 	:tc=vt102:
170 
171 
172 # Entry for an xterm. Insert mode has been disabled.
173 vs|xterm|xterm-color|vs100|xterm terminal emulator (X Window System):\
174 	:am:bs:mi@:km:co#80:li#55:\
175 	:im@:ei@:\
176 	:ct=\E[3k:ue=\E[m:\
177 	:is=\E[m\E[?1l\E>:\
178 	:rs=\E[m\E[?1l\E>:\
179 	:vi=\E[?25l:ve=\E[?25h:\
180 	:eA=\E)0:as=^N:ae=^O:ac=aaffggjjkkllmmnnooqqssttuuvvwwxx:\
181 	:kI=\E[2~:kD=\E[3~:kP=\E[5~:kN=\E[6~:\
182 	:k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:k5=\E[15~:\
183 	:k6=\E[17~:k7=\E[18~:k8=\E[19~:k9=\E[20~:k0=\E[21~:\
184 	:F1=\E[23~:F2=\E[24~:\
185 	:kh=\E[H:kH=\E[F:\
186 	:ks=:ke=:\
187 	:te=\E[2J\E[?47l\E8:ti=\E7\E[?47h:\
188 	:tc=vt-generic:
189 
190 
191 #rxvt, added by me
192 rxvt|rxvt-unicode:\
193 	:am:bs:mi@:km:co#80:li#55:\
194 	:im@:ei@:\
195 	:ct=\E[3k:ue=\E[m:\
196 	:is=\E[m\E[?1l\E>:\
197 	:rs=\E[m\E[?1l\E>:\
198 	:vi=\E[?25l:\
199 	:ve=\E[?25h:\
200 	:eA=\E)0:as=^N:ae=^O:ac=aaffggjjkkllmmnnooqqssttuuvvwwxx:\
201 	:kI=\E[2~:kD=\E[3~:kP=\E[5~:kN=\E[6~:\
202 	:k1=\E[11~:k2=\E[12~:k3=\E[13~:k4=\E[14~:k5=\E[15~:\
203 	:k6=\E[17~:k7=\E[18~:k8=\E[19~:k9=\E[20~:k0=\E[21~:\
204 	:F1=\E[23~:F2=\E[24~:\
205 	:kh=\E[7~:kH=\E[8~:\
206 	:ks=:ke=:\
207 	:te=\E[2J\E[?47l\E8:ti=\E7\E[?47h:\
208 	:tc=vt-generic:
209 
210 
211 # Some other entries for the same xterm.
212 v2|xterms|vs100s|xterm small window:\
213 	:co#80:li#24:tc=xterm:
214 vb|xterm-bold|xterm with bold instead of underline:\
215 	:us=\E[1m:tc=xterm:
216 vi|xterm-ins|xterm with insert mode:\
217 	:mi:im=\E[4h:ei=\E[4l:tc=xterm:
218 
219 Eterm|Eterm Terminal Emulator (X11 Window System):\
220         :am:bw:eo:km:mi:ms:xn:xo:\
221         :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:\
222         :AL=\E[%dL:DC=\E[%dP:DL=\E[%dM:DO=\E[%dB:IC=\E[%d@:\
223         :K1=\E[7~:K2=\EOu:K3=\E[5~:K4=\E[8~:K5=\E[6~:LE=\E[%dD:\
224         :RI=\E[%dC:UP=\E[%dA:ae=^O:al=\E[L:as=^N:bl=^G:cd=\E[J:\
225         :ce=\E[K:cl=\E[H\E[2J:cm=\E[%i%d;%dH:cr=^M:\
226         :cs=\E[%i%d;%dr:ct=\E[3g:dc=\E[P:dl=\E[M:do=\E[B:\
227         :ec=\E[%dX:ei=\E[4l:ho=\E[H:i1=\E[?47l\E>\E[?1l:ic=\E[@:\
228         :im=\E[4h:is=\E[r\E[m\E[2J\E[H\E[?7h\E[?1;3;4;6l\E[4l:\
229         :k1=\E[11~:k2=\E[12~:k3=\E[13~:k4=\E[14~:k5=\E[15~:\
230         :k6=\E[17~:k7=\E[18~:k8=\E[19~:k9=\E[20~:kD=\E[3~:\
231         :kI=\E[2~:kN=\E[6~:kP=\E[5~:kb=^H:kd=\E[B:ke=:kh=\E[7~:\
232         :kl=\E[D:kr=\E[C:ks=:ku=\E[A:le=^H:mb=\E[5m:md=\E[1m:\
233         :me=\E[m\017:mr=\E[7m:nd=\E[C:rc=\E8:\
234         :sc=\E7:se=\E[27m:sf=^J:so=\E[7m:sr=\EM:st=\EH:ta=^I:\
235         :te=\E[2J\E[?47l\E8:ti=\E7\E[?47h:ue=\E[24m:up=\E[A:\
236         :us=\E[4m:vb=\E[?5h\E[?5l:ve=\E[?25h:vi=\E[?25l:\
237         :ac=``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~:
238 
239 # DOS terminal emulator such as Telix or TeleMate.
240 # This probably also works for the SCO console, though it's incomplete.
241 an|ansi|ansi-bbs|ANSI terminals (emulators):\
242 	:co#80:li#24:am:\
243 	:is=:rs=\Ec:kb=^H:\
244 	:as=\E[m:ae=:eA=:\
245 	:ac=0\333+\257,\256.\031-\030a\261f\370g\361j\331k\277l\332m\300n\305q\304t\264u\303v\301w\302x\263~\025:\
246 	:kD=\177:kH=\E[Y:kN=\E[U:kP=\E[V:kh=\E[H:\
247 	:k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:k5=\EOT:\
248 	:k6=\EOU:k7=\EOV:k8=\EOW:k9=\EOX:k0=\EOY:\
249 	:tc=vt-generic:
250 
251 	`;
252 }
253 
254 enum Bright = 0x08;
255 
256 /// Defines the list of standard colors understood by Terminal.
257 enum Color : ushort {
258 	black = 0, /// .
259 	red = RED_BIT, /// .
260 	green = GREEN_BIT, /// .
261 	yellow = red | green, /// .
262 	blue = BLUE_BIT, /// .
263 	magenta = red | blue, /// .
264 	cyan = blue | green, /// .
265 	white = red | green | blue, /// .
266 	DEFAULT = 256,
267 }
268 
269 /// When capturing input, what events are you interested in?
270 ///
271 /// Note: these flags can be OR'd together to select more than one option at a time.
272 ///
273 /// Ctrl+C and other keyboard input is always captured, though it may be line buffered if you don't use raw.
274 /// 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.
275 enum ConsoleInputFlags {
276 	raw = 0, /// raw input returns keystrokes immediately, without line buffering
277 	echo = 1, /// do you want to automatically echo input back to the user?
278 	mouse = 2, /// capture mouse events
279 	paste = 4, /// capture paste events (note: without this, paste can come through as keystrokes)
280 	size = 8, /// window resize events
281 
282 	releasedKeys = 64, /// key release events. Not reliable on Posix.
283 
284 	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.
285 	allInputEventsWithRelease = allInputEvents|releasedKeys, /// subscribe to all input events, including (unreliable on Posix) key release events.
286 }
287 
288 /// Defines how terminal output should be handled.
289 enum ConsoleOutputType {
290 	linear = 0, /// do you want output to work one line at a time?
291 	cellular = 1, /// or do you want access to the terminal screen as a grid of characters?
292 	//truncatedCellular = 3, /// cellular, but instead of wrapping output to the next line automatically, it will truncate at the edges
293 
294 	minimalProcessing = 255, /// do the least possible work, skips most construction and desturction tasks. Only use if you know what you're doing here
295 }
296 
297 /// Some methods will try not to send unnecessary commands to the screen. You can override their judgement using a ForceOption parameter, if present
298 enum ForceOption {
299 	automatic = 0, /// automatically decide what to do (best, unless you know for sure it isn't right)
300 	neverSend = -1, /// never send the data. This will only update Terminal's internal state. Use with caution.
301 	alwaysSend = 1, /// always send the data, even if it doesn't seem necessary
302 }
303 
304 // we could do it with termcap too, getenv("TERMCAP") then split on : and replace \E with \033 and get the pieces
305 
306 /// Encapsulates the I/O capabilities of a terminal.
307 ///
308 /// Warning: do not write out escape sequences to the terminal. This won't work
309 /// on Windows and will confuse Terminal's internal state on Posix.
310 struct Terminal {
311 	@disable this();
312 	@disable this(this);
313 	private ConsoleOutputType type;
314 
315 	version(Posix) {
316 		private int fdOut;
317 		private int fdIn;
318 		private int[] delegate() getSizeOverride;
319 	}
320 
321 	version(Posix) {
322 		bool terminalInFamily(string[] terms...) {
323 			import std.process;
324 			import std.string;
325 			auto term = environment.get("TERM");
326 			foreach(t; terms)
327 				if(indexOf(term, t) != -1)
328 					return true;
329 
330 			return false;
331 		}
332 
333 		static string[string] termcapDatabase;
334 		static void readTermcapFile(bool useBuiltinTermcap = false) {
335 			import std.file;
336 			import std.stdio;
337 			import std.string;
338 
339 			if(!exists("/etc/termcap"))
340 				useBuiltinTermcap = true;
341 
342 			string current;
343 
344 			void commitCurrentEntry() {
345 				if(current is null)
346 					return;
347 
348 				string names = current;
349 				auto idx = indexOf(names, ":");
350 				if(idx != -1)
351 					names = names[0 .. idx];
352 
353 				foreach(name; split(names, "|"))
354 					termcapDatabase[name] = current;
355 
356 				current = null;
357 			}
358 
359 			void handleTermcapLine(in char[] line) {
360 				if(line.length == 0) { // blank
361 					commitCurrentEntry();
362 					return; // continue
363 				}
364 				if(line[0] == '#') // comment
365 					return; // continue
366 				size_t termination = line.length;
367 				if(line[$-1] == '\\')
368 					termination--; // cut off the \\
369 				current ~= strip(line[0 .. termination]);
370 				// termcap entries must be on one logical line, so if it isn't continued, we know we're done
371 				if(line[$-1] != '\\')
372 					commitCurrentEntry();
373 			}
374 
375 			if(useBuiltinTermcap) {
376 				foreach(line; splitLines(builtinTermcap)) {
377 					handleTermcapLine(line);
378 				}
379 			} else {
380 				foreach(line; File("/etc/termcap").byLine()) {
381 					handleTermcapLine(line);
382 				}
383 			}
384 		}
385 
386 		static string getTermcapDatabase(string terminal) {
387 			import std.string;
388 
389 			if(termcapDatabase is null)
390 				readTermcapFile();
391 
392 			auto data = terminal in termcapDatabase;
393 			if(data is null)
394 				return null;
395 
396 			auto tc = *data;
397 			auto more = indexOf(tc, ":tc=");
398 			if(more != -1) {
399 				auto tcKey = tc[more + ":tc=".length .. $];
400 				auto end = indexOf(tcKey, ":");
401 				if(end != -1)
402 					tcKey = tcKey[0 .. end];
403 				tc = getTermcapDatabase(tcKey) ~ tc;
404 			}
405 
406 			return tc;
407 		}
408 
409 		string[string] termcap;
410 		void readTermcap() {
411 			import std.process;
412 			import std.string;
413 			import std.array;
414 
415 			string termcapData = environment.get("TERMCAP");
416 			if(termcapData.length == 0) {
417 				termcapData = getTermcapDatabase(environment.get("TERM"));
418 			}
419 
420 			auto e = replace(termcapData, "\\\n", "\n");
421 			termcap = null;
422 
423 			foreach(part; split(e, ":")) {
424 				// FIXME: handle numeric things too
425 
426 				auto things = split(part, "=");
427 				if(things.length)
428 					termcap[things[0]] =
429 						things.length > 1 ? things[1] : null;
430 			}
431 		}
432 
433 		string findSequenceInTermcap(in char[] sequenceIn) {
434 			char[10] sequenceBuffer;
435 			char[] sequence;
436 			if(sequenceIn.length > 0 && sequenceIn[0] == '\033') {
437 				if(!(sequenceIn.length < sequenceBuffer.length - 1))
438 					return null;
439 				sequenceBuffer[1 .. sequenceIn.length + 1] = sequenceIn[];
440 				sequenceBuffer[0] = '\\';
441 				sequenceBuffer[1] = 'E';
442 				sequence = sequenceBuffer[0 .. sequenceIn.length + 1];
443 			} else {
444 				sequence = sequenceBuffer[1 .. sequenceIn.length + 1];
445 			}
446 
447 			import std.array;
448 			foreach(k, v; termcap)
449 				if(v == sequence)
450 					return k;
451 			return null;
452 		}
453 
454 		string getTermcap(string key) {
455 			auto k = key in termcap;
456 			if(k !is null) return *k;
457 			return null;
458 		}
459 
460 		// Looks up a termcap item and tries to execute it. Returns false on failure
461 		bool doTermcap(T...)(string key, T t) {
462 			import std.conv;
463 			auto fs = getTermcap(key);
464 			if(fs is null)
465 				return false;
466 
467 			int swapNextTwo = 0;
468 
469 			R getArg(R)(int idx) {
470 				if(swapNextTwo == 2) {
471 					idx ++;
472 					swapNextTwo--;
473 				} else if(swapNextTwo == 1) {
474 					idx --;
475 					swapNextTwo--;
476 				}
477 
478 				foreach(i, arg; t) {
479 					if(i == idx)
480 						return to!R(arg);
481 				}
482 				assert(0, to!string(idx) ~ " is out of bounds working " ~ fs);
483 			}
484 
485 			char[256] buffer;
486 			int bufferPos = 0;
487 
488 			void addChar(char c) {
489 				import std.exception;
490 				enforce(bufferPos < buffer.length);
491 				buffer[bufferPos++] = c;
492 			}
493 
494 			void addString(in char[] c) {
495 				import std.exception;
496 				enforce(bufferPos + c.length < buffer.length);
497 				buffer[bufferPos .. bufferPos + c.length] = c[];
498 				bufferPos += c.length;
499 			}
500 
501 			void addInt(int c, int minSize) {
502 				import std.string;
503 				auto str = format("%0"~(minSize ? to!string(minSize) : "")~"d", c);
504 				addString(str);
505 			}
506 
507 			bool inPercent;
508 			int argPosition = 0;
509 			int incrementParams = 0;
510 			bool skipNext;
511 			bool nextIsChar;
512 			bool inBackslash;
513 
514 			foreach(char c; fs) {
515 				if(inBackslash) {
516 					if(c == 'E')
517 						addChar('\033');
518 					else
519 						addChar(c);
520 					inBackslash = false;
521 				} else if(nextIsChar) {
522 					if(skipNext)
523 						skipNext = false;
524 					else
525 						addChar(cast(char) (c + getArg!int(argPosition) + (incrementParams ? 1 : 0)));
526 					if(incrementParams) incrementParams--;
527 					argPosition++;
528 					inPercent = false;
529 				} else if(inPercent) {
530 					switch(c) {
531 						case '%':
532 							addChar('%');
533 							inPercent = false;
534 						break;
535 						case '2':
536 						case '3':
537 						case 'd':
538 							if(skipNext)
539 								skipNext = false;
540 							else
541 								addInt(getArg!int(argPosition) + (incrementParams ? 1 : 0),
542 									c == 'd' ? 0 : (c - '0')
543 								);
544 							if(incrementParams) incrementParams--;
545 							argPosition++;
546 							inPercent = false;
547 						break;
548 						case '.':
549 							if(skipNext)
550 								skipNext = false;
551 							else
552 								addChar(cast(char) (getArg!int(argPosition) + (incrementParams ? 1 : 0)));
553 							if(incrementParams) incrementParams--;
554 							argPosition++;
555 						break;
556 						case '+':
557 							nextIsChar = true;
558 							inPercent = false;
559 						break;
560 						case 'i':
561 							incrementParams = 2;
562 							inPercent = false;
563 						break;
564 						case 's':
565 							skipNext = true;
566 							inPercent = false;
567 						break;
568 						case 'b':
569 							argPosition--;
570 							inPercent = false;
571 						break;
572 						case 'r':
573 							swapNextTwo = 2;
574 							inPercent = false;
575 						break;
576 						// FIXME: there's more
577 						// http://www.gnu.org/software/termutils/manual/termcap-1.3/html_mono/termcap.html
578 
579 						default:
580 							assert(0, "not supported " ~ c);
581 					}
582 				} else {
583 					if(c == '%')
584 						inPercent = true;
585 					else if(c == '\\')
586 						inBackslash = true;
587 					else
588 						addChar(c);
589 				}
590 			}
591 
592 			writeStringRaw(buffer[0 .. bufferPos]);
593 			return true;
594 		}
595 	}
596 
597 	version(Posix)
598 	/**
599 	 * Constructs an instance of Terminal representing the capabilities of
600 	 * the current terminal.
601 	 *
602 	 * While it is possible to override the stdin+stdout file descriptors, remember
603 	 * that is not portable across platforms and be sure you know what you're doing.
604 	 *
605 	 * ditto on getSizeOverride. That's there so you can do something instead of ioctl.
606 	 */
607 	this(ConsoleOutputType type, int fdIn = 0, int fdOut = 1, int[] delegate() getSizeOverride = null) {
608 		this.fdIn = fdIn;
609 		this.fdOut = fdOut;
610 		this.getSizeOverride = getSizeOverride;
611 		this.type = type;
612 
613 		readTermcap();
614 
615 		if(type == ConsoleOutputType.minimalProcessing) {
616 			_suppressDestruction = true;
617 			return;
618 		}
619 
620 		if(type == ConsoleOutputType.cellular) {
621 			doTermcap("ti");
622 			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
623 		}
624 
625 		if(terminalInFamily("xterm", "rxvt", "screen")) {
626 			writeStringRaw("\033[22;0t"); // save window title on a stack (support seems spotty, but it doesn't hurt to have it)
627 		}
628 	}
629 
630 	version(Windows)
631 		HANDLE hConsole;
632 
633 	version(Windows)
634 	/// ditto
635 	this(ConsoleOutputType type) {
636 		hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
637 		if(type == ConsoleOutputType.cellular) {
638 			/*
639 http://msdn.microsoft.com/en-us/library/windows/desktop/ms686125%28v=vs.85%29.aspx
640 http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.aspx
641 			*/
642 			COORD size;
643 			/*
644 			CONSOLE_SCREEN_BUFFER_INFO sbi;
645 			GetConsoleScreenBufferInfo(hConsole, &sbi);
646 			size.X = cast(short) GetSystemMetrics(SM_CXMIN);
647 			size.Y = cast(short) GetSystemMetrics(SM_CYMIN);
648 			*/
649 
650 			// FIXME: this sucks, maybe i should just revert it. but there shouldn't be scrollbars in cellular mode
651 			size.X = 80;
652 			size.Y = 24;
653 			SetConsoleScreenBufferSize(hConsole, size);
654 			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
655 		}
656 	}
657 
658 	// 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...
659 	bool _suppressDestruction;
660 
661 	version(Posix)
662 	~this() {
663 		if(_suppressDestruction) {
664 			flush();
665 			return;
666 		}
667 		if(type == ConsoleOutputType.cellular) {
668 			doTermcap("te");
669 		}
670 		if(terminalInFamily("xterm", "rxvt", "screen")) {
671 			writeStringRaw("\033[23;0t"); // restore window title from the stack
672 		}
673 		showCursor();
674 		reset();
675 		flush();
676 
677 		if(lineGetter !is null)
678 			lineGetter.dispose();
679 	}
680 
681 	version(Windows)
682 	~this() {
683 		reset();
684 		flush();
685 		showCursor();
686 
687 		if(lineGetter !is null)
688 			lineGetter.dispose();
689 	}
690 
691 	// lazily initialized and preserved between calls to getline for a bit of efficiency (only a bit)
692 	// and some history storage.
693 	LineGetter lineGetter;
694 
695 	int _currentForeground = Color.DEFAULT;
696 	int _currentBackground = Color.DEFAULT;
697 	bool reverseVideo = false;
698 
699 	/// Changes the current color. See enum Color for the values.
700 	void color(int foreground, int background, ForceOption force = ForceOption.automatic, bool reverseVideo = false) {
701 		if(force != ForceOption.neverSend) {
702 			version(Windows) {
703 				// assuming a dark background on windows, so LowContrast == dark which means the bit is NOT set on hardware
704 				/*
705 				foreground ^= LowContrast;
706 				background ^= LowContrast;
707 				*/
708 
709 				ushort setTof = cast(ushort) foreground;
710 				ushort setTob = cast(ushort) background;
711 
712 				// this isn't necessarily right but meh
713 				if(background == Color.DEFAULT)
714 					setTob = Color.black;
715 				if(foreground == Color.DEFAULT)
716 					setTof = Color.white;
717 
718 				if(force == ForceOption.alwaysSend || reverseVideo != this.reverseVideo || foreground != _currentForeground || background != _currentBackground) {
719 					flush(); // if we don't do this now, the buffering can screw up the colors...
720 					if(reverseVideo) {
721 						if(background == Color.DEFAULT)
722 							setTof = Color.black;
723 						else
724 							setTof = cast(ushort) background | (foreground & Bright);
725 
726 						if(background == Color.DEFAULT)
727 							setTob = Color.white;
728 						else
729 							setTob = cast(ushort) (foreground & ~Bright);
730 					}
731 					SetConsoleTextAttribute(
732 						GetStdHandle(STD_OUTPUT_HANDLE),
733 						cast(ushort)((setTob << 4) | setTof));
734 				}
735 			} else {
736 				import std.process;
737 				// I started using this envvar for my text editor, but now use it elsewhere too
738 				// if we aren't set to dark, assume light
739 				/*
740 				if(getenv("ELVISBG") == "dark") {
741 					// LowContrast on dark bg menas
742 				} else {
743 					foreground ^= LowContrast;
744 					background ^= LowContrast;
745 				}
746 				*/
747 
748 				ushort setTof = cast(ushort) foreground & ~Bright;
749 				ushort setTob = cast(ushort) background & ~Bright;
750 
751 				if(foreground & Color.DEFAULT)
752 					setTof = 9; // ansi sequence for reset
753 				if(background == Color.DEFAULT)
754 					setTob = 9;
755 
756 				import std.string;
757 
758 				if(force == ForceOption.alwaysSend || reverseVideo != this.reverseVideo || foreground != _currentForeground || background != _currentBackground) {
759 					writeStringRaw(format("\033[%dm\033[3%dm\033[4%dm\033[%dm",
760 						(foreground != Color.DEFAULT && (foreground & Bright)) ? 1 : 0,
761 						cast(int) setTof,
762 						cast(int) setTob,
763 						reverseVideo ? 7 : 27
764 					));
765 				}
766 			}
767 		}
768 
769 		_currentForeground = foreground;
770 		_currentBackground = background;
771 		this.reverseVideo = reverseVideo;
772 	}
773 
774 	private bool _underlined = false;
775 
776 	/// Note: the Windows console does not support underlining
777 	void underline(bool set, ForceOption force = ForceOption.automatic) {
778 		if(set == _underlined && force != ForceOption.alwaysSend)
779 			return;
780 		version(Posix) {
781 			if(set)
782 				writeStringRaw("\033[4m");
783 			else
784 				writeStringRaw("\033[24m");
785 		}
786 		_underlined = set;
787 	}
788 	// FIXME: do I want to do bold and italic?
789 
790 	/// Returns the terminal to normal output colors
791 	void reset() {
792 		version(Windows)
793 			SetConsoleTextAttribute(
794 				GetStdHandle(STD_OUTPUT_HANDLE),
795 				cast(ushort)((Color.black << 4) | Color.white));
796 		else
797 			writeStringRaw("\033[0m");
798 
799 		_underlined = false;
800 		_currentForeground = Color.DEFAULT;
801 		_currentBackground = Color.DEFAULT;
802 		reverseVideo = false;
803 	}
804 
805 	// FIXME: add moveRelative
806 
807 	/// The current x position of the output cursor. 0 == leftmost column
808 	@property int cursorX() {
809 		return _cursorX;
810 	}
811 
812 	/// The current y position of the output cursor. 0 == topmost row
813 	@property int cursorY() {
814 		return _cursorY;
815 	}
816 
817 	private int _cursorX;
818 	private int _cursorY;
819 
820 	/// 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
821 	void moveTo(int x, int y, ForceOption force = ForceOption.automatic) {
822 		if(force != ForceOption.neverSend && (force == ForceOption.alwaysSend || x != _cursorX || y != _cursorY)) {
823 			executeAutoHideCursor();
824 			version(Posix)
825 				doTermcap("cm", y, x);
826 			else version(Windows) {
827 
828 				flush(); // if we don't do this now, the buffering can screw up the position
829 				COORD coord = {cast(short) x, cast(short) y};
830 				SetConsoleCursorPosition(hConsole, coord);
831 			} else static assert(0);
832 		}
833 
834 		_cursorX = x;
835 		_cursorY = y;
836 	}
837 
838 	/// shows the cursor
839 	void showCursor() {
840 		version(Posix)
841 			doTermcap("ve");
842 		else {
843 			CONSOLE_CURSOR_INFO info;
844 			GetConsoleCursorInfo(hConsole, &info);
845 			info.bVisible = true;
846 			SetConsoleCursorInfo(hConsole, &info);
847 		}
848 	}
849 
850 	/// hides the cursor
851 	void hideCursor() {
852 		version(Posix) {
853 			doTermcap("vi");
854 		} else {
855 			CONSOLE_CURSOR_INFO info;
856 			GetConsoleCursorInfo(hConsole, &info);
857 			info.bVisible = false;
858 			SetConsoleCursorInfo(hConsole, &info);
859 		}
860 
861 	}
862 
863 	private bool autoHidingCursor;
864 	private bool autoHiddenCursor;
865 	// explicitly not publicly documented
866 	// Sets the cursor to automatically insert a hide command at the front of the output buffer iff it is moved.
867 	// Call autoShowCursor when you are done with the batch update.
868 	void autoHideCursor() {
869 		autoHidingCursor = true;
870 	}
871 
872 	private void executeAutoHideCursor() {
873 		if(autoHidingCursor) {
874 			version(Windows)
875 				hideCursor();
876 			else version(Posix) {
877 				// prepend the hide cursor command so it is the first thing flushed
878 				writeBuffer = "\033[?25l" ~ writeBuffer;
879 			}
880 
881 			autoHiddenCursor = true;
882 			autoHidingCursor = false; // already been done, don't insert the command again
883 		}
884 	}
885 
886 	// explicitly not publicly documented
887 	// Shows the cursor if it was automatically hidden by autoHideCursor and resets the internal auto hide state.
888 	void autoShowCursor() {
889 		if(autoHiddenCursor)
890 			showCursor();
891 
892 		autoHidingCursor = false;
893 		autoHiddenCursor = false;
894 	}
895 
896 	/*
897 	// alas this doesn't work due to a bunch of delegate context pointer and postblit problems
898 	// instead of using: auto input = terminal.captureInput(flags)
899 	// use: auto input = RealTimeConsoleInput(&terminal, flags);
900 	/// Gets real time input, disabling line buffering
901 	RealTimeConsoleInput captureInput(ConsoleInputFlags flags) {
902 		return RealTimeConsoleInput(&this, flags);
903 	}
904 	*/
905 
906 	/// Changes the terminal's title
907 	void setTitle(string t) {
908 		version(Windows) {
909 			SetConsoleTitleA(toStringz(t));
910 		} else {
911 			import std.string;
912 			if(terminalInFamily("xterm", "rxvt", "screen"))
913 				writeStringRaw(format("\033]0;%s\007", t));
914 		}
915 	}
916 
917 	/// Flushes your updates to the terminal.
918 	/// It is important to call this when you are finished writing for now if you are using the version=with_eventloop
919 	void flush() {
920 		version(Posix) {
921 			ssize_t written;
922 
923 			while(writeBuffer.length) {
924 				written = unix.write(this.fdOut, writeBuffer.ptr, writeBuffer.length);
925 				if(written < 0)
926 					throw new Exception("write failed for some reason");
927 				writeBuffer = writeBuffer[written .. $];
928 			}
929 		} else version(Windows) {
930 			while(writeBuffer.length) {
931 				DWORD written;
932 				/* FIXME: WriteConsoleW */
933 				WriteConsoleA(hConsole, writeBuffer.ptr, writeBuffer.length, &written, null);
934 				writeBuffer = writeBuffer[written .. $];
935 			}
936 		}
937 		// not buffering right now on Windows, since it probably isn't on ssh anyway
938 	}
939 
940 	int[] getSize() {
941 		version(Windows) {
942 			CONSOLE_SCREEN_BUFFER_INFO info;
943 			GetConsoleScreenBufferInfo( hConsole, &info );
944 
945 			int cols, rows;
946 
947 			cols = (info.srWindow.Right - info.srWindow.Left + 1);
948 			rows = (info.srWindow.Bottom - info.srWindow.Top + 1);
949 
950 			return [cols, rows];
951 		} else {
952 			if(getSizeOverride is null) {
953 				winsize w;
954 				ioctl(0, TIOCGWINSZ, &w);
955 				return [w.ws_col, w.ws_row];
956 			} else return getSizeOverride();
957 		}
958 	}
959 
960 	void updateSize() {
961 		auto size = getSize();
962 		_width = size[0];
963 		_height = size[1];
964 	}
965 
966 	private int _width;
967 	private int _height;
968 
969 	/// The current width of the terminal (the number of columns)
970 	@property int width() {
971 		if(_width == 0 || _height == 0)
972 			updateSize();
973 		return _width;
974 	}
975 
976 	/// The current height of the terminal (the number of rows)
977 	@property int height() {
978 		if(_width == 0 || _height == 0)
979 			updateSize();
980 		return _height;
981 	}
982 
983 	/*
984 	void write(T...)(T t) {
985 		foreach(arg; t) {
986 			writeStringRaw(to!string(arg));
987 		}
988 	}
989 	*/
990 
991 	/// Writes to the terminal at the current cursor position.
992 	void writef(T...)(string f, T t) {
993 		import std.string;
994 		writePrintableString(format(f, t));
995 	}
996 
997 	/// ditto
998 	void writefln(T...)(string f, T t) {
999 		writef(f ~ "\n", t);
1000 	}
1001 
1002 	/// ditto
1003 	void write(T...)(T t) {
1004 		import std.conv;
1005 		string data;
1006 		foreach(arg; t) {
1007 			data ~= to!string(arg);
1008 		}
1009 
1010 		writePrintableString(data);
1011 	}
1012 
1013 	/// ditto
1014 	void writeln(T...)(T t) {
1015 		write(t, "\n");
1016 	}
1017 
1018 	/+
1019 	/// A combined moveTo and writef that puts the cursor back where it was before when it finishes the write.
1020 	/// Only works in cellular mode.
1021 	/// 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)
1022 	void writefAt(T...)(int x, int y, string f, T t) {
1023 		import std.string;
1024 		auto toWrite = format(f, t);
1025 
1026 		auto oldX = _cursorX;
1027 		auto oldY = _cursorY;
1028 
1029 		writeAtWithoutReturn(x, y, toWrite);
1030 
1031 		moveTo(oldX, oldY);
1032 	}
1033 
1034 	void writeAtWithoutReturn(int x, int y, in char[] data) {
1035 		moveTo(x, y);
1036 		writeStringRaw(toWrite, ForceOption.alwaysSend);
1037 	}
1038 	+/
1039 
1040 	void writePrintableString(in char[] s, ForceOption force = ForceOption.automatic) {
1041 		// an escape character is going to mess things up. Actually any non-printable character could, but meh
1042 		// assert(s.indexOf("\033") == -1);
1043 
1044 		// tracking cursor position
1045 		foreach(ch; s) {
1046 			switch(ch) {
1047 				case '\n':
1048 					_cursorX = 0;
1049 					_cursorY++;
1050 				break;
1051 				case '\r':
1052 					_cursorX = 0;
1053 				break;
1054 				case '\t':
1055 					_cursorX ++;
1056 					_cursorX += _cursorX % 8; // FIXME: get the actual tabstop, if possible
1057 				break;
1058 				default:
1059 					if(ch <= 127) // way of only advancing once per dchar instead of per code unit
1060 						_cursorX++;
1061 			}
1062 
1063 			if(_wrapAround && _cursorX > width) {
1064 				_cursorX = 0;
1065 				_cursorY++;
1066 			}
1067 
1068 			if(_cursorY == height)
1069 				_cursorY--;
1070 
1071 			/+
1072 			auto index = getIndex(_cursorX, _cursorY);
1073 			if(data[index] != ch) {
1074 				data[index] = ch;
1075 			}
1076 			+/
1077 		}
1078 
1079 		writeStringRaw(s);
1080 	}
1081 
1082 	/* private */ bool _wrapAround = true;
1083 
1084 	deprecated alias writePrintableString writeString; /// use write() or writePrintableString instead
1085 
1086 	private string writeBuffer;
1087 
1088 	// you really, really shouldn't use this unless you know what you are doing
1089 	/*private*/ void writeStringRaw(in char[] s) {
1090 		// FIXME: make sure all the data is sent, check for errors
1091 		version(Posix) {
1092 			writeBuffer ~= s; // buffer it to do everything at once in flush() calls
1093 		} else version(Windows) {
1094 			writeBuffer ~= s;
1095 		} else static assert(0);
1096 	}
1097 
1098 	/// Clears the screen.
1099 	void clear() {
1100 		version(Posix) {
1101 			doTermcap("cl");
1102 		} else version(Windows) {
1103 			// http://support.microsoft.com/kb/99261
1104 
1105 			DWORD c;
1106 			CONSOLE_SCREEN_BUFFER_INFO csbi;
1107 			DWORD conSize;
1108 			GetConsoleScreenBufferInfo(hConsole, &csbi);
1109 			COORD coordScreen;
1110 			FillConsoleOutputCharacterA(hConsole, ' ', conSize, coordScreen, &c);
1111 			FillConsoleOutputAttribute(hConsole, csbi.wAttributes, conSize, coordScreen, &c);
1112 			moveTo(0, 0);
1113 		}
1114 
1115 		_cursorX = 0;
1116 		_cursorY = 0;
1117 	}
1118 
1119 	/// gets a line, including user editing. Convenience method around the LineGetter class and RealTimeConsoleInput facilities - use them if you need more control.
1120 	/// 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.
1121 	// FIXME: add a method to make it easy to check if stdin is actually a tty and use other methods there.
1122 	string getline(string prompt = null) {
1123 		if(lineGetter is null)
1124 			lineGetter = new LineGetter(&this);
1125 		// since the struct might move (it shouldn't, this should be unmovable!) but since
1126 		// it technically might, I'm updating the pointer before using it just in case.
1127 		lineGetter.terminal = &this;
1128 
1129 		lineGetter.prompt = prompt;
1130 
1131 		auto line = lineGetter.getline();
1132 
1133 		// lineGetter leaves us exactly where it was when the user hit enter, giving best
1134 		// flexibility to real-time input and cellular programs. The convenience function,
1135 		// however, wants to do what is right in most the simple cases, which is to actually
1136 		// print the line (echo would be enabled without RealTimeConsoleInput anyway and they
1137 		// did hit enter), so we'll do that here too.
1138 		writePrintableString("\n");
1139 
1140 		return line;
1141 	}
1142 
1143 }
1144 
1145 /+
1146 struct ConsoleBuffer {
1147 	int cursorX;
1148 	int cursorY;
1149 	int width;
1150 	int height;
1151 	dchar[] data;
1152 
1153 	void actualize(Terminal* t) {
1154 		auto writer = t.getBufferedWriter();
1155 
1156 		this.copyTo(&(t.onScreen));
1157 	}
1158 
1159 	void copyTo(ConsoleBuffer* buffer) {
1160 		buffer.cursorX = this.cursorX;
1161 		buffer.cursorY = this.cursorY;
1162 		buffer.width = this.width;
1163 		buffer.height = this.height;
1164 		buffer.data[] = this.data[];
1165 	}
1166 }
1167 +/
1168 
1169 /**
1170  * Encapsulates the stream of input events received from the terminal input.
1171  */
1172 struct RealTimeConsoleInput {
1173 	@disable this();
1174 	@disable this(this);
1175 
1176 	version(Posix) {
1177 		private int fdOut;
1178 		private int fdIn;
1179 		private sigaction_t oldSigWinch;
1180 		private sigaction_t oldSigIntr;
1181 		private sigaction_t oldHupIntr;
1182 		private termios old;
1183 		ubyte[128] hack;
1184 		// apparently termios isn't the size druntime thinks it is (at least on 32 bit, sometimes)....
1185 		// tcgetattr smashed other variables in here too that could create random problems
1186 		// so this hack is just to give some room for that to happen without destroying the rest of the world
1187 	}
1188 
1189 	version(Windows) {
1190 		private DWORD oldInput;
1191 		private DWORD oldOutput;
1192 		HANDLE inputHandle;
1193 	}
1194 
1195 	private ConsoleInputFlags flags;
1196 	private Terminal* terminal;
1197 	private void delegate()[] destructor;
1198 
1199 	/// To capture input, you need to provide a terminal and some flags.
1200 	public this(Terminal* terminal, ConsoleInputFlags flags) {
1201 		this.flags = flags;
1202 		this.terminal = terminal;
1203 
1204 		version(Windows) {
1205 			inputHandle = GetStdHandle(STD_INPUT_HANDLE);
1206 
1207 			GetConsoleMode(inputHandle, &oldInput);
1208 
1209 			DWORD mode = 0;
1210 			mode |= ENABLE_PROCESSED_INPUT /* 0x01 */; // this gives Ctrl+C which we probably want to be similar to linux
1211 			//if(flags & ConsoleInputFlags.size)
1212 			mode |= ENABLE_WINDOW_INPUT /* 0208 */; // gives size etc
1213 			if(flags & ConsoleInputFlags.echo)
1214 				mode |= ENABLE_ECHO_INPUT; // 0x4
1215 			if(flags & ConsoleInputFlags.mouse)
1216 				mode |= ENABLE_MOUSE_INPUT; // 0x10
1217 			// if(flags & ConsoleInputFlags.raw) // FIXME: maybe that should be a separate flag for ENABLE_LINE_INPUT
1218 
1219 			SetConsoleMode(inputHandle, mode);
1220 			destructor ~= { SetConsoleMode(inputHandle, oldInput); };
1221 
1222 
1223 			GetConsoleMode(terminal.hConsole, &oldOutput);
1224 			mode = 0;
1225 			// we want this to match linux too
1226 			mode |= ENABLE_PROCESSED_OUTPUT; /* 0x01 */
1227 			mode |= ENABLE_WRAP_AT_EOL_OUTPUT; /* 0x02 */
1228 			SetConsoleMode(terminal.hConsole, mode);
1229 			destructor ~= { SetConsoleMode(terminal.hConsole, oldOutput); };
1230 
1231 			// FIXME: change to UTF8 as well
1232 		}
1233 
1234 		version(Posix) {
1235 			this.fdIn = terminal.fdIn;
1236 			this.fdOut = terminal.fdOut;
1237 
1238 			if(fdIn != -1) {
1239 				tcgetattr(fdIn, &old);
1240 				auto n = old;
1241 
1242 				auto f = ICANON;
1243 				if(!(flags & ConsoleInputFlags.echo))
1244 					f |= ECHO;
1245 
1246 				n.c_lflag &= ~f;
1247 				tcsetattr(fdIn, TCSANOW, &n);
1248 			}
1249 
1250 			// some weird bug breaks this, https://github.com/robik/ConsoleD/issues/3
1251 			//destructor ~= { tcsetattr(fdIn, TCSANOW, &old); };
1252 
1253 			if(flags & ConsoleInputFlags.size) {
1254 				import core.sys.posix.signal;
1255 				sigaction_t n;
1256 				n.sa_handler = &sizeSignalHandler;
1257 				n.sa_mask = cast(sigset_t) 0;
1258 				n.sa_flags = 0;
1259 				sigaction(SIGWINCH, &n, &oldSigWinch);
1260 			}
1261 
1262 			{
1263 				import core.sys.posix.signal;
1264 				sigaction_t n;
1265 				n.sa_handler = &interruptSignalHandler;
1266 				n.sa_mask = cast(sigset_t) 0;
1267 				n.sa_flags = 0;
1268 				sigaction(SIGINT, &n, &oldSigIntr);
1269 			}
1270 
1271 			{
1272 				import core.sys.posix.signal;
1273 				sigaction_t n;
1274 				n.sa_handler = &hangupSignalHandler;
1275 				n.sa_mask = cast(sigset_t) 0;
1276 				n.sa_flags = 0;
1277 				sigaction(SIGHUP, &n, &oldHupIntr);
1278 			}
1279 
1280 
1281 
1282 			if(flags & ConsoleInputFlags.mouse) {
1283 				// basic button press+release notification
1284 
1285 				// FIXME: try to get maximum capabilities from all terminals
1286 				// right now this works well on xterm but rxvt isn't sending movements...
1287 
1288 				terminal.writeStringRaw("\033[?1000h");
1289 				destructor ~= { terminal.writeStringRaw("\033[?1000l"); };
1290 				if(terminal.terminalInFamily("xterm")) {
1291 					// this is vt200 mouse with full motion tracking, supported by xterm
1292 					terminal.writeStringRaw("\033[?1003h");
1293 					destructor ~= { terminal.writeStringRaw("\033[?1003l"); };
1294 				} else if(terminal.terminalInFamily("rxvt", "screen")) {
1295 					terminal.writeStringRaw("\033[?1002h"); // this is vt200 mouse with press/release and motion notification iff buttons are pressed
1296 					destructor ~= { terminal.writeStringRaw("\033[?1002l"); };
1297 				}
1298 			}
1299 			if(flags & ConsoleInputFlags.paste) {
1300 				if(terminal.terminalInFamily("xterm", "rxvt", "screen")) {
1301 					terminal.writeStringRaw("\033[?2004h"); // bracketed paste mode
1302 					destructor ~= { terminal.writeStringRaw("\033[?2004l"); };
1303 				}
1304 			}
1305 
1306 			// try to ensure the terminal is in UTF-8 mode
1307 			if(terminal.terminalInFamily("xterm", "screen", "linux")) {
1308 				terminal.writeStringRaw("\033%G");
1309 			}
1310 
1311 			terminal.flush();
1312 		}
1313 
1314 
1315 		version(with_eventloop) {
1316 			import arsd.eventloop;
1317 			version(Windows)
1318 				auto listenTo = inputHandle;
1319 			else version(Posix)
1320 				auto listenTo = this.fdIn;
1321 			else static assert(0, "idk about this OS");
1322 
1323 			version(Posix)
1324 			addListener(&signalFired);
1325 
1326 			if(listenTo != -1) {
1327 				addFileEventListeners(listenTo, &eventListener, null, null);
1328 				destructor ~= { removeFileEventListeners(listenTo); };
1329 			}
1330 			addOnIdle(&terminal.flush);
1331 			destructor ~= { removeOnIdle(&terminal.flush); };
1332 		}
1333 	}
1334 
1335 	version(with_eventloop) {
1336 		version(Posix)
1337 		void signalFired(SignalFired) {
1338 			if(interrupted) {
1339 				interrupted = false;
1340 				send(InputEvent(UserInterruptionEvent()));
1341 			}
1342 			if(windowSizeChanged)
1343 				send(checkWindowSizeChanged());
1344 			if(hangedUp) {
1345 				hangedUp = false;
1346 				send(InputEvent(HangupEvent()));
1347 			}
1348 		}
1349 
1350 		import arsd.eventloop;
1351 		void eventListener(OsFileHandle fd) {
1352 			auto queue = readNextEvents();
1353 			foreach(event; queue)
1354 				send(event);
1355 		}
1356 	}
1357 
1358 	~this() {
1359 		// the delegate thing doesn't actually work for this... for some reason
1360 		version(Posix)
1361 			if(fdIn != -1)
1362 				tcsetattr(fdIn, TCSANOW, &old);
1363 
1364 		version(Posix) {
1365 			if(flags & ConsoleInputFlags.size) {
1366 				// restoration
1367 				sigaction(SIGWINCH, &oldSigWinch, null);
1368 			}
1369 			sigaction(SIGINT, &oldSigIntr, null);
1370 			sigaction(SIGHUP, &oldHupIntr, null);
1371 		}
1372 
1373 		// we're just undoing everything the constructor did, in reverse order, same criteria
1374 		foreach_reverse(d; destructor)
1375 			d();
1376 	}
1377 
1378 	/// Returns true if there is input available now
1379 	bool kbhit() {
1380 		return timedCheckForInput(0);
1381 	}
1382 
1383 	/// Check for input, waiting no longer than the number of milliseconds
1384 	bool timedCheckForInput(int milliseconds) {
1385 		version(Windows) {
1386 			auto response = WaitForSingleObject(terminal.hConsole, milliseconds);
1387 			if(response  == 0)
1388 				return true; // the object is ready
1389 			return false;
1390 		} else version(Posix) {
1391 			if(fdIn == -1)
1392 				return false;
1393 
1394 			timeval tv;
1395 			tv.tv_sec = 0;
1396 			tv.tv_usec = milliseconds * 1000;
1397 
1398 			fd_set fs;
1399 			FD_ZERO(&fs);
1400 
1401 			FD_SET(fdIn, &fs);
1402 			select(fdIn + 1, &fs, null, null, &tv);
1403 
1404 			return FD_ISSET(fdIn, &fs);
1405 		}
1406 	}
1407 
1408 	/// Get one character from the terminal, discarding other
1409 	/// events in the process. Returns dchar.init upon receiving end-of-file.
1410 	dchar getch() {
1411 		auto event = nextEvent();
1412 		while(event.type != InputEvent.Type.CharacterEvent || event.characterEvent.eventType == CharacterEvent.Type.Released) {
1413 			if(event.type == InputEvent.Type.UserInterruptionEvent)
1414 				throw new Exception("Ctrl+c");
1415 			if(event.type == InputEvent.Type.HangupEvent)
1416 				throw new Exception("Hangup");
1417 			if(event.type == InputEvent.Type.EndOfFileEvent)
1418 				return dchar.init;
1419 			event = nextEvent();
1420 		}
1421 		return event.characterEvent.character;
1422 	}
1423 
1424 	//char[128] inputBuffer;
1425 	//int inputBufferPosition;
1426 	version(Posix)
1427 	int nextRaw(bool interruptable = false) {
1428 		if(fdIn == -1)
1429 			return 0;
1430 
1431 		char[1] buf;
1432 		try_again:
1433 		auto ret = read(fdIn, buf.ptr, buf.length);
1434 		if(ret == 0)
1435 			return 0; // input closed
1436 		if(ret == -1) {
1437 			import core.stdc.errno;
1438 			if(errno == EINTR)
1439 				// interrupted by signal call, quite possibly resize or ctrl+c which we want to check for in the event loop
1440 				if(interruptable)
1441 					return -1;
1442 				else
1443 					goto try_again;
1444 			else
1445 				throw new Exception("read failed");
1446 		}
1447 
1448 		//terminal.writef("RAW READ: %d\n", buf[0]);
1449 
1450 		if(ret == 1)
1451 			return inputPrefilter ? inputPrefilter(buf[0]) : buf[0];
1452 		else
1453 			assert(0); // read too much, should be impossible
1454 	}
1455 
1456 	version(Posix)
1457 		int delegate(char) inputPrefilter;
1458 
1459 	version(Posix)
1460 	dchar nextChar(int starting) {
1461 		if(starting <= 127)
1462 			return cast(dchar) starting;
1463 		char[6] buffer;
1464 		int pos = 0;
1465 		buffer[pos++] = cast(char) starting;
1466 
1467 		// see the utf-8 encoding for details
1468 		int remaining = 0;
1469 		ubyte magic = starting & 0xff;
1470 		while(magic & 0b1000_000) {
1471 			remaining++;
1472 			magic <<= 1;
1473 		}
1474 
1475 		while(remaining && pos < buffer.length) {
1476 			buffer[pos++] = cast(char) nextRaw();
1477 			remaining--;
1478 		}
1479 
1480 		import std.utf;
1481 		size_t throwAway; // it insists on the index but we don't care
1482 		return decode(cast(string)buffer, throwAway);
1483 	}
1484 
1485 	InputEvent checkWindowSizeChanged() {
1486 		auto oldWidth = terminal.width;
1487 		auto oldHeight = terminal.height;
1488 		terminal.updateSize();
1489 		version(Posix)
1490 		windowSizeChanged = false;
1491 		return InputEvent(SizeChangedEvent(oldWidth, oldHeight, terminal.width, terminal.height));
1492 	}
1493 
1494 
1495 	// character event
1496 	// non-character key event
1497 	// paste event
1498 	// mouse event
1499 	// size event maybe, and if appropriate focus events
1500 
1501 	/// Returns the next event.
1502 	///
1503 	/// Experimental: It is also possible to integrate this into
1504 	/// a generic event loop, currently under -version=with_eventloop and it will
1505 	/// require the module arsd.eventloop (Linux only at this point)
1506 	InputEvent nextEvent() {
1507 		terminal.flush();
1508 		if(inputQueue.length) {
1509 			auto e = inputQueue[0];
1510 			inputQueue = inputQueue[1 .. $];
1511 			return e;
1512 		}
1513 
1514 		wait_for_more:
1515 		version(Posix)
1516 		if(interrupted) {
1517 			interrupted = false;
1518 			return InputEvent(UserInterruptionEvent());
1519 		}
1520 
1521 		version(Posix)
1522 		if(hangedUp) {
1523 			hangedUp = false;
1524 			return InputEvent(HangupEvent());
1525 		}
1526 
1527 		version(Posix)
1528 		if(windowSizeChanged) {
1529 			return checkWindowSizeChanged();
1530 		}
1531 
1532 		auto more = readNextEvents();
1533 		if(!more.length)
1534 			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
1535 
1536 		assert(more.length);
1537 
1538 		auto e = more[0];
1539 		inputQueue = more[1 .. $];
1540 		return e;
1541 	}
1542 
1543 	InputEvent* peekNextEvent() {
1544 		if(inputQueue.length)
1545 			return &(inputQueue[0]);
1546 		return null;
1547 	}
1548 
1549 	enum InjectionPosition { head, tail }
1550 	void injectEvent(InputEvent ev, InjectionPosition where) {
1551 		final switch(where) {
1552 			case InjectionPosition.head:
1553 				inputQueue = ev ~ inputQueue;
1554 			break;
1555 			case InjectionPosition.tail:
1556 				inputQueue ~= ev;
1557 			break;
1558 		}
1559 	}
1560 
1561 	InputEvent[] inputQueue;
1562 
1563 	version(Windows)
1564 	InputEvent[] readNextEvents() {
1565 		terminal.flush(); // make sure all output is sent out before waiting for anything
1566 
1567 		INPUT_RECORD[32] buffer;
1568 		DWORD actuallyRead;
1569 			// FIXME: ReadConsoleInputW
1570 		auto success = ReadConsoleInputA(inputHandle, buffer.ptr, buffer.length, &actuallyRead);
1571 		if(success == 0)
1572 			throw new Exception("ReadConsoleInput");
1573 
1574 		InputEvent[] newEvents;
1575 		input_loop: foreach(record; buffer[0 .. actuallyRead]) {
1576 			switch(record.EventType) {
1577 				case KEY_EVENT:
1578 					auto ev = record.KeyEvent;
1579 					CharacterEvent e;
1580 					NonCharacterKeyEvent ne;
1581 
1582 					e.eventType = ev.bKeyDown ? CharacterEvent.Type.Pressed : CharacterEvent.Type.Released;
1583 					ne.eventType = ev.bKeyDown ? NonCharacterKeyEvent.Type.Pressed : NonCharacterKeyEvent.Type.Released;
1584 
1585 					// only send released events when specifically requested
1586 					if(!(flags & ConsoleInputFlags.releasedKeys) && !ev.bKeyDown)
1587 						break;
1588 
1589 					e.modifierState = ev.dwControlKeyState;
1590 					ne.modifierState = ev.dwControlKeyState;
1591 
1592 					if(ev.UnicodeChar) {
1593 						e.character = cast(dchar) cast(wchar) ev.UnicodeChar;
1594 						newEvents ~= InputEvent(e);
1595 					} else {
1596 						ne.key = cast(NonCharacterKeyEvent.Key) ev.wVirtualKeyCode;
1597 
1598 						// FIXME: make this better. the goal is to make sure the key code is a valid enum member
1599 						// Windows sends more keys than Unix and we're doing lowest common denominator here
1600 						foreach(member; __traits(allMembers, NonCharacterKeyEvent.Key))
1601 							if(__traits(getMember, NonCharacterKeyEvent.Key, member) == ne.key) {
1602 								newEvents ~= InputEvent(ne);
1603 								break;
1604 							}
1605 					}
1606 				break;
1607 				case MOUSE_EVENT:
1608 					auto ev = record.MouseEvent;
1609 					MouseEvent e;
1610 
1611 					e.modifierState = ev.dwControlKeyState;
1612 					e.x = ev.dwMousePosition.X;
1613 					e.y = ev.dwMousePosition.Y;
1614 
1615 					switch(ev.dwEventFlags) {
1616 						case 0:
1617 							//press or release
1618 							e.eventType = MouseEvent.Type.Pressed;
1619 							static DWORD lastButtonState;
1620 							auto lastButtonState2 = lastButtonState;
1621 							e.buttons = ev.dwButtonState;
1622 							lastButtonState = e.buttons;
1623 
1624 							// this is sent on state change. if fewer buttons are pressed, it must mean released
1625 							if(cast(DWORD) e.buttons < lastButtonState2) {
1626 								e.eventType = MouseEvent.Type.Released;
1627 								// if last was 101 and now it is 100, then button far right was released
1628 								// so we flip the bits, ~100 == 011, then and them: 101 & 011 == 001, the
1629 								// button that was released
1630 								e.buttons = lastButtonState2 & ~e.buttons;
1631 							}
1632 						break;
1633 						case MOUSE_MOVED:
1634 							e.eventType = MouseEvent.Type.Moved;
1635 							e.buttons = ev.dwButtonState;
1636 						break;
1637 						case 0x0004/*MOUSE_WHEELED*/:
1638 							e.eventType = MouseEvent.Type.Pressed;
1639 							if(ev.dwButtonState > 0)
1640 								e.buttons = MouseEvent.Button.ScrollDown;
1641 							else
1642 								e.buttons = MouseEvent.Button.ScrollUp;
1643 						break;
1644 						default:
1645 							continue input_loop;
1646 					}
1647 
1648 					newEvents ~= InputEvent(e);
1649 				break;
1650 				case WINDOW_BUFFER_SIZE_EVENT:
1651 					auto ev = record.WindowBufferSizeEvent;
1652 					auto oldWidth = terminal.width;
1653 					auto oldHeight = terminal.height;
1654 					terminal._width = ev.dwSize.X;
1655 					terminal._height = ev.dwSize.Y;
1656 					newEvents ~= InputEvent(SizeChangedEvent(oldWidth, oldHeight, terminal.width, terminal.height));
1657 				break;
1658 				// FIXME: can we catch ctrl+c here too?
1659 				default:
1660 					// ignore
1661 			}
1662 		}
1663 
1664 		return newEvents;
1665 	}
1666 
1667 	version(Posix)
1668 	InputEvent[] readNextEvents() {
1669 		terminal.flush(); // make sure all output is sent out before we try to get input
1670 
1671 		// we want to starve the read, especially if we're called from an edge-triggered
1672 		// epoll (which might happen in version=with_eventloop.. impl detail there subject
1673 		// to change).
1674 		auto initial = readNextEventsHelper();
1675 
1676 		// lol this calls select() inside a function prolly called from epoll but meh,
1677 		// it is the simplest thing that can possibly work. The alternative would be
1678 		// doing non-blocking reads and buffering in the nextRaw function (not a bad idea
1679 		// btw, just a bit more of a hassle).
1680 		while(timedCheckForInput(0))
1681 			initial ~= readNextEventsHelper();
1682 		return initial;
1683 	}
1684 
1685 	// The helper reads just one actual event from the pipe...
1686 	version(Posix)
1687 	InputEvent[] readNextEventsHelper() {
1688 		InputEvent[] charPressAndRelease(dchar character) {
1689 			if((flags & ConsoleInputFlags.releasedKeys))
1690 				return [
1691 					InputEvent(CharacterEvent(CharacterEvent.Type.Pressed, character, 0)),
1692 					InputEvent(CharacterEvent(CharacterEvent.Type.Released, character, 0)),
1693 				];
1694 			else return [ InputEvent(CharacterEvent(CharacterEvent.Type.Pressed, character, 0)) ];
1695 		}
1696 		InputEvent[] keyPressAndRelease(NonCharacterKeyEvent.Key key, uint modifiers = 0) {
1697 			if((flags & ConsoleInputFlags.releasedKeys))
1698 				return [
1699 					InputEvent(NonCharacterKeyEvent(NonCharacterKeyEvent.Type.Pressed, key, modifiers)),
1700 					InputEvent(NonCharacterKeyEvent(NonCharacterKeyEvent.Type.Released, key, modifiers)),
1701 				];
1702 			else return [ InputEvent(NonCharacterKeyEvent(NonCharacterKeyEvent.Type.Pressed, key, modifiers)) ];
1703 		}
1704 
1705 		char[30] sequenceBuffer;
1706 
1707 		// this assumes you just read "\033["
1708 		char[] readEscapeSequence(char[] sequence) {
1709 			int sequenceLength = 2;
1710 			sequence[0] = '\033';
1711 			sequence[1] = '[';
1712 
1713 			while(sequenceLength < sequence.length) {
1714 				auto n = nextRaw();
1715 				sequence[sequenceLength++] = cast(char) n;
1716 				// I think a [ is supposed to termiate a CSI sequence
1717 				// but the Linux console sends CSI[A for F1, so I'm
1718 				// hacking it to accept that too
1719 				if(n >= 0x40 && !(sequenceLength == 3 && n == '['))
1720 					break;
1721 			}
1722 
1723 			return sequence[0 .. sequenceLength];
1724 		}
1725 
1726 		InputEvent[] translateTermcapName(string cap) {
1727 			switch(cap) {
1728 				//case "k0":
1729 					//return keyPressAndRelease(NonCharacterKeyEvent.Key.F1);
1730 				case "k1":
1731 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F1);
1732 				case "k2":
1733 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F2);
1734 				case "k3":
1735 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F3);
1736 				case "k4":
1737 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F4);
1738 				case "k5":
1739 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F5);
1740 				case "k6":
1741 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F6);
1742 				case "k7":
1743 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F7);
1744 				case "k8":
1745 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F8);
1746 				case "k9":
1747 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F9);
1748 				case "k;":
1749 				case "k0":
1750 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F10);
1751 				case "F1":
1752 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F11);
1753 				case "F2":
1754 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F12);
1755 
1756 
1757 				case "kb":
1758 					return charPressAndRelease('\b');
1759 				case "kD":
1760 					return keyPressAndRelease(NonCharacterKeyEvent.Key.Delete);
1761 
1762 				case "kd":
1763 				case "do":
1764 					return keyPressAndRelease(NonCharacterKeyEvent.Key.DownArrow);
1765 				case "ku":
1766 				case "up":
1767 					return keyPressAndRelease(NonCharacterKeyEvent.Key.UpArrow);
1768 				case "kl":
1769 					return keyPressAndRelease(NonCharacterKeyEvent.Key.LeftArrow);
1770 				case "kr":
1771 				case "nd":
1772 					return keyPressAndRelease(NonCharacterKeyEvent.Key.RightArrow);
1773 
1774 				case "kN":
1775 				case "K5":
1776 					return keyPressAndRelease(NonCharacterKeyEvent.Key.PageDown);
1777 				case "kP":
1778 				case "K2":
1779 					return keyPressAndRelease(NonCharacterKeyEvent.Key.PageUp);
1780 
1781 				case "kh":
1782 				case "K1":
1783 					return keyPressAndRelease(NonCharacterKeyEvent.Key.Home);
1784 				case "kH":
1785 					return keyPressAndRelease(NonCharacterKeyEvent.Key.End);
1786 				case "kI":
1787 					return keyPressAndRelease(NonCharacterKeyEvent.Key.Insert);
1788 				default:
1789 					// don't know it, just ignore
1790 					//import std.stdio;
1791 					//writeln(cap);
1792 			}
1793 
1794 			return null;
1795 		}
1796 
1797 
1798 		InputEvent[] doEscapeSequence(in char[] sequence) {
1799 			switch(sequence) {
1800 				case "\033[200~":
1801 					// bracketed paste begin
1802 					// we want to keep reading until
1803 					// "\033[201~":
1804 					// and build a paste event out of it
1805 
1806 
1807 					string data;
1808 					for(;;) {
1809 						auto n = nextRaw();
1810 						if(n == '\033') {
1811 							n = nextRaw();
1812 							if(n == '[') {
1813 								auto esc = readEscapeSequence(sequenceBuffer);
1814 								if(esc == "\033[201~") {
1815 									// complete!
1816 									break;
1817 								} else {
1818 									// was something else apparently, but it is pasted, so keep it
1819 									data ~= esc;
1820 								}
1821 							} else {
1822 								data ~= '\033';
1823 								data ~= cast(char) n;
1824 							}
1825 						} else {
1826 							data ~= cast(char) n;
1827 						}
1828 					}
1829 					return [InputEvent(PasteEvent(data))];
1830 				case "\033[M":
1831 					// mouse event
1832 					auto buttonCode = nextRaw() - 32;
1833 						// nextChar is commented because i'm not using UTF-8 mouse mode
1834 						// cuz i don't think it is as widely supported
1835 					auto x = cast(int) (/*nextChar*/(nextRaw())) - 33; /* they encode value + 32, but make upper left 1,1. I want it to be 0,0 */
1836 					auto y = cast(int) (/*nextChar*/(nextRaw())) - 33; /* ditto */
1837 
1838 
1839 					bool isRelease = (buttonCode & 0b11) == 3;
1840 					int buttonNumber;
1841 					if(!isRelease) {
1842 						buttonNumber = (buttonCode & 0b11);
1843 						if(buttonCode & 64)
1844 							buttonNumber += 3; // button 4 and 5 are sent as like button 1 and 2, but code | 64
1845 							// so button 1 == button 4 here
1846 
1847 						// note: buttonNumber == 0 means button 1 at this point
1848 						buttonNumber++; // hence this
1849 
1850 
1851 						// apparently this considers middle to be button 2. but i want middle to be button 3.
1852 						if(buttonNumber == 2)
1853 							buttonNumber = 3;
1854 						else if(buttonNumber == 3)
1855 							buttonNumber = 2;
1856 					}
1857 
1858 					auto modifiers = buttonCode & (0b0001_1100);
1859 						// 4 == shift
1860 						// 8 == meta
1861 						// 16 == control
1862 
1863 					MouseEvent m;
1864 
1865 					if(buttonCode & 32)
1866 						m.eventType = MouseEvent.Type.Moved;
1867 					else
1868 						m.eventType = isRelease ? MouseEvent.Type.Released : MouseEvent.Type.Pressed;
1869 
1870 					// ugh, if no buttons are pressed, released and moved are indistinguishable...
1871 					// so we'll count the buttons down, and if we get a release
1872 					static int buttonsDown = 0;
1873 					if(!isRelease && buttonNumber <= 3) // exclude wheel "presses"...
1874 						buttonsDown++;
1875 
1876 					if(isRelease && m.eventType != MouseEvent.Type.Moved) {
1877 						if(buttonsDown)
1878 							buttonsDown--;
1879 						else // no buttons down, so this should be a motion instead..
1880 							m.eventType = MouseEvent.Type.Moved;
1881 					}
1882 
1883 
1884 					if(buttonNumber == 0)
1885 						m.buttons = 0; // we don't actually know :(
1886 					else
1887 						m.buttons = 1 << (buttonNumber - 1); // I prefer flags so that's how we do it
1888 					m.x = x;
1889 					m.y = y;
1890 					m.modifierState = modifiers;
1891 
1892 					return [InputEvent(m)];
1893 				default:
1894 					// look it up in the termcap key database
1895 					auto cap = terminal.findSequenceInTermcap(sequence);
1896 					if(cap !is null) {
1897 						return translateTermcapName(cap);
1898 					} else {
1899 						if(terminal.terminalInFamily("xterm")) {
1900 							import std.conv, std.string;
1901 							auto terminator = sequence[$ - 1];
1902 							auto parts = sequence[2 .. $ - 1].split(";");
1903 							// parts[0] and terminator tells us the key
1904 							// parts[1] tells us the modifierState
1905 
1906 							uint modifierState;
1907 
1908 							int modGot;
1909 							if(parts.length > 1)
1910 								modGot = to!int(parts[1]);
1911 							mod_switch: switch(modGot) {
1912 								case 2: modifierState |= ModifierState.shift; break;
1913 								case 3: modifierState |= ModifierState.alt; break;
1914 								case 4: modifierState |= ModifierState.shift | ModifierState.alt; break;
1915 								case 5: modifierState |= ModifierState.control; break;
1916 								case 6: modifierState |= ModifierState.shift | ModifierState.control; break;
1917 								case 7: modifierState |= ModifierState.alt | ModifierState.control; break;
1918 								case 8: modifierState |= ModifierState.shift | ModifierState.alt | ModifierState.control; break;
1919 								case 9:
1920 								..
1921 								case 16:
1922 									modifierState |= ModifierState.meta;
1923 									if(modGot != 9) {
1924 										modGot -= 8;
1925 										goto mod_switch;
1926 									}
1927 								break;
1928 
1929 								// this is an extension in my own terminal emulator
1930 								case 20:
1931 								..
1932 								case 36:
1933 									modifierState |= ModifierState.windows;
1934 									modGot -= 20;
1935 									goto mod_switch;
1936 								default:
1937 							}
1938 
1939 							switch(terminator) {
1940 								case 'A': return keyPressAndRelease(NonCharacterKeyEvent.Key.UpArrow, modifierState);
1941 								case 'B': return keyPressAndRelease(NonCharacterKeyEvent.Key.DownArrow, modifierState);
1942 								case 'C': return keyPressAndRelease(NonCharacterKeyEvent.Key.RightArrow, modifierState);
1943 								case 'D': return keyPressAndRelease(NonCharacterKeyEvent.Key.LeftArrow, modifierState);
1944 
1945 								case 'H': return keyPressAndRelease(NonCharacterKeyEvent.Key.Home, modifierState);
1946 								case 'F': return keyPressAndRelease(NonCharacterKeyEvent.Key.End, modifierState);
1947 
1948 								case 'P': return keyPressAndRelease(NonCharacterKeyEvent.Key.F1, modifierState);
1949 								case 'Q': return keyPressAndRelease(NonCharacterKeyEvent.Key.F2, modifierState);
1950 								case 'R': return keyPressAndRelease(NonCharacterKeyEvent.Key.F3, modifierState);
1951 								case 'S': return keyPressAndRelease(NonCharacterKeyEvent.Key.F4, modifierState);
1952 
1953 								case '~': // others
1954 									switch(parts[0]) {
1955 										case "5": return keyPressAndRelease(NonCharacterKeyEvent.Key.PageUp, modifierState);
1956 										case "6": return keyPressAndRelease(NonCharacterKeyEvent.Key.PageDown, modifierState);
1957 										case "2": return keyPressAndRelease(NonCharacterKeyEvent.Key.Insert, modifierState);
1958 										case "3": return keyPressAndRelease(NonCharacterKeyEvent.Key.Delete, modifierState);
1959 
1960 										case "15": return keyPressAndRelease(NonCharacterKeyEvent.Key.F5, modifierState);
1961 										case "17": return keyPressAndRelease(NonCharacterKeyEvent.Key.F6, modifierState);
1962 										case "18": return keyPressAndRelease(NonCharacterKeyEvent.Key.F7, modifierState);
1963 										case "19": return keyPressAndRelease(NonCharacterKeyEvent.Key.F8, modifierState);
1964 										case "20": return keyPressAndRelease(NonCharacterKeyEvent.Key.F9, modifierState);
1965 										case "21": return keyPressAndRelease(NonCharacterKeyEvent.Key.F10, modifierState);
1966 										case "23": return keyPressAndRelease(NonCharacterKeyEvent.Key.F11, modifierState);
1967 										case "24": return keyPressAndRelease(NonCharacterKeyEvent.Key.F12, modifierState);
1968 										default:
1969 									}
1970 								break;
1971 
1972 								default:
1973 							}
1974 						} else if(terminal.terminalInFamily("rxvt")) {
1975 							// FIXME: figure these out. rxvt seems to just change the terminator while keeping the rest the same
1976 							// though it isn't consistent. ugh.
1977 						} else {
1978 							// 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
1979 							// so this space is semi-intentionally left blank
1980 						}
1981 					}
1982 			}
1983 
1984 			return null;
1985 		}
1986 
1987 		auto c = nextRaw(true);
1988 		if(c == -1)
1989 			return null; // interrupted; give back nothing so the other level can recheck signal flags
1990 		if(c == 0)
1991 			return [InputEvent(EndOfFileEvent())];
1992 		if(c == '\033') {
1993 			if(timedCheckForInput(50)) {
1994 				// escape sequence
1995 				c = nextRaw();
1996 				if(c == '[') { // CSI, ends on anything >= 'A'
1997 					return doEscapeSequence(readEscapeSequence(sequenceBuffer));
1998 				} else if(c == 'O') {
1999 					// could be xterm function key
2000 					auto n = nextRaw();
2001 
2002 					char[3] thing;
2003 					thing[0] = '\033';
2004 					thing[1] = 'O';
2005 					thing[2] = cast(char) n;
2006 
2007 					auto cap = terminal.findSequenceInTermcap(thing);
2008 					if(cap is null) {
2009 						return charPressAndRelease('\033') ~
2010 							charPressAndRelease('O') ~
2011 							charPressAndRelease(thing[2]);
2012 					} else {
2013 						return translateTermcapName(cap);
2014 					}
2015 				} else {
2016 					// I don't know, probably unsupported terminal or just quick user input or something
2017 					return charPressAndRelease('\033') ~ charPressAndRelease(nextChar(c));
2018 				}
2019 			} else {
2020 				// user hit escape (or super slow escape sequence, but meh)
2021 				return keyPressAndRelease(NonCharacterKeyEvent.Key.escape);
2022 			}
2023 		} else {
2024 			// FIXME: what if it is neither? we should check the termcap
2025 			auto next = nextChar(c);
2026 			if(next == 127) // some terminals send 127 on the backspace. Let's normalize that.
2027 				next = '\b';
2028 			return charPressAndRelease(next);
2029 		}
2030 	}
2031 }
2032 
2033 /// Input event for characters
2034 struct CharacterEvent {
2035 	/// .
2036 	enum Type {
2037 		Released, /// .
2038 		Pressed /// .
2039 	}
2040 
2041 	Type eventType; /// .
2042 	dchar character; /// .
2043 	uint modifierState; /// Don't depend on this to be available for character events
2044 }
2045 
2046 struct NonCharacterKeyEvent {
2047 	/// .
2048 	enum Type {
2049 		Released, /// .
2050 		Pressed /// .
2051 	}
2052 	Type eventType; /// .
2053 
2054 	// these match Windows virtual key codes numerically for simplicity of translation there
2055 	//http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731%28v=vs.85%29.aspx
2056 	/// .
2057 	enum Key : int {
2058 		escape = 0x1b, /// .
2059 		F1 = 0x70, /// .
2060 		F2 = 0x71, /// .
2061 		F3 = 0x72, /// .
2062 		F4 = 0x73, /// .
2063 		F5 = 0x74, /// .
2064 		F6 = 0x75, /// .
2065 		F7 = 0x76, /// .
2066 		F8 = 0x77, /// .
2067 		F9 = 0x78, /// .
2068 		F10 = 0x79, /// .
2069 		F11 = 0x7A, /// .
2070 		F12 = 0x7B, /// .
2071 		LeftArrow = 0x25, /// .
2072 		RightArrow = 0x27, /// .
2073 		UpArrow = 0x26, /// .
2074 		DownArrow = 0x28, /// .
2075 		Insert = 0x2d, /// .
2076 		Delete = 0x2e, /// .
2077 		Home = 0x24, /// .
2078 		End = 0x23, /// .
2079 		PageUp = 0x21, /// .
2080 		PageDown = 0x22, /// .
2081 		}
2082 	Key key; /// .
2083 
2084 	uint modifierState; /// A mask of ModifierState. Always use by checking modifierState & ModifierState.something, the actual value differs across platforms
2085 
2086 }
2087 
2088 /// .
2089 struct PasteEvent {
2090 	string pastedText; /// .
2091 }
2092 
2093 /// .
2094 struct MouseEvent {
2095 	// these match simpledisplay.d numerically as well
2096 	/// .
2097 	enum Type {
2098 		Moved = 0, /// .
2099 		Pressed = 1, /// .
2100 		Released = 2, /// .
2101 		Clicked, /// .
2102 	}
2103 
2104 	Type eventType; /// .
2105 
2106 	// note: these should numerically match simpledisplay.d for maximum beauty in my other code
2107 	/// .
2108 	enum Button : uint {
2109 		None = 0, /// .
2110 		Left = 1, /// .
2111 		Middle = 4, /// .
2112 		Right = 2, /// .
2113 		ScrollUp = 8, /// .
2114 		ScrollDown = 16 /// .
2115 	}
2116 	uint buttons; /// A mask of Button
2117 	int x; /// 0 == left side
2118 	int y; /// 0 == top
2119 	uint modifierState; /// shift, ctrl, alt, meta, altgr. Not always available. Always check by using modifierState & ModifierState.something
2120 }
2121 
2122 /// .
2123 struct SizeChangedEvent {
2124 	int oldWidth;
2125 	int oldHeight;
2126 	int newWidth;
2127 	int newHeight;
2128 }
2129 
2130 /// the user hitting ctrl+c will send this
2131 /// You should drop what you're doing and perhaps exit when this happens.
2132 struct UserInterruptionEvent {}
2133 
2134 /// If the user hangs up (for example, closes the terminal emulator without exiting the app), this is sent.
2135 /// If you receive it, you should generally cleanly exit.
2136 struct HangupEvent {}
2137 
2138 /// Sent upon receiving end-of-file from stdin.
2139 struct EndOfFileEvent {}
2140 
2141 interface CustomEvent {}
2142 
2143 version(Windows)
2144 enum ModifierState : uint {
2145 	shift = 0x10,
2146 	control = 0x8 | 0x4, // 8 == left ctrl, 4 == right ctrl
2147 
2148 	// i'm not sure if the next two are available
2149 	alt = 2 | 1, //2 ==left alt, 1 == right alt
2150 
2151 	// FIXME: I don't think these are actually available
2152 	windows = 512,
2153 	meta = 4096, // FIXME sanity
2154 
2155 	// I don't think this is available on Linux....
2156 	scrollLock = 0x40,
2157 }
2158 else
2159 enum ModifierState : uint {
2160 	shift = 4,
2161 	alt = 2,
2162 	control = 16,
2163 	meta = 8,
2164 
2165 	windows = 512 // only available if you are using my terminal emulator; it isn't actually offered on standard linux ones
2166 }
2167 
2168 /// GetNextEvent returns this. Check the type, then use get to get the more detailed input
2169 struct InputEvent {
2170 	/// .
2171 	enum Type {
2172 		CharacterEvent, ///.
2173 		NonCharacterKeyEvent, /// .
2174 		PasteEvent, /// The user pasted some text. Not always available, the pasted text might come as a series of character events instead.
2175 		MouseEvent, /// only sent if you subscribed to mouse events
2176 		SizeChangedEvent, /// only sent if you subscribed to size events
2177 		UserInterruptionEvent, /// the user hit ctrl+c
2178 		EndOfFileEvent, /// stdin has received an end of file
2179 		HangupEvent, /// the terminal hanged up - for example, if the user closed a terminal emulator
2180 		CustomEvent /// .
2181 	}
2182 
2183 	/// .
2184 	@property Type type() { return t; }
2185 
2186 	/// .
2187 	@property auto get(Type T)() {
2188 		if(type != T)
2189 			throw new Exception("Wrong event type");
2190 		static if(T == Type.CharacterEvent)
2191 			return characterEvent;
2192 		else static if(T == Type.NonCharacterKeyEvent)
2193 			return nonCharacterKeyEvent;
2194 		else static if(T == Type.PasteEvent)
2195 			return pasteEvent;
2196 		else static if(T == Type.MouseEvent)
2197 			return mouseEvent;
2198 		else static if(T == Type.SizeChangedEvent)
2199 			return sizeChangedEvent;
2200 		else static if(T == Type.UserInterruptionEvent)
2201 			return userInterruptionEvent;
2202 		else static if(T == Type.EndOfFileEvent)
2203 			return endOfFileEvent;
2204 		else static if(T == Type.HangupEvent)
2205 			return hangupEvent;
2206 		else static if(T == Type.CustomEvent)
2207 			return customEvent;
2208 		else static assert(0, "Type " ~ T.stringof ~ " not added to the get function");
2209 	}
2210 
2211 	private {
2212 		this(CharacterEvent c) {
2213 			t = Type.CharacterEvent;
2214 			characterEvent = c;
2215 		}
2216 		this(NonCharacterKeyEvent c) {
2217 			t = Type.NonCharacterKeyEvent;
2218 			nonCharacterKeyEvent = c;
2219 		}
2220 		this(PasteEvent c) {
2221 			t = Type.PasteEvent;
2222 			pasteEvent = c;
2223 		}
2224 		this(MouseEvent c) {
2225 			t = Type.MouseEvent;
2226 			mouseEvent = c;
2227 		}
2228 		this(SizeChangedEvent c) {
2229 			t = Type.SizeChangedEvent;
2230 			sizeChangedEvent = c;
2231 		}
2232 		this(UserInterruptionEvent c) {
2233 			t = Type.UserInterruptionEvent;
2234 			userInterruptionEvent = c;
2235 		}
2236 		this(HangupEvent c) {
2237 			t = Type.HangupEvent;
2238 			hangupEvent = c;
2239 		}
2240 		this(EndOfFileEvent c) {
2241 			t = Type.EndOfFileEvent;
2242 			endOfFileEvent = c;
2243 		}
2244 		this(CustomEvent c) {
2245 			t = Type.CustomEvent;
2246 			customEvent = c;
2247 		}
2248 
2249 		Type t;
2250 
2251 		union {
2252 			CharacterEvent characterEvent;
2253 			NonCharacterKeyEvent nonCharacterKeyEvent;
2254 			PasteEvent pasteEvent;
2255 			MouseEvent mouseEvent;
2256 			SizeChangedEvent sizeChangedEvent;
2257 			UserInterruptionEvent userInterruptionEvent;
2258 			HangupEvent hangupEvent;
2259 			EndOfFileEvent endOfFileEvent;
2260 			CustomEvent customEvent;
2261 		}
2262 	}
2263 }
2264 
2265 version(Demo)
2266 void main() {
2267 	auto terminal = Terminal(ConsoleOutputType.cellular);
2268 
2269 	//terminal.color(Color.DEFAULT, Color.DEFAULT);
2270 
2271 	//
2272 	/*
2273 	auto getter = new LineGetter(&terminal, "test");
2274 	getter.prompt = "> ";
2275 	terminal.writeln("\n" ~ getter.getline());
2276 	terminal.writeln("\n" ~ getter.getline());
2277 	terminal.writeln("\n" ~ getter.getline());
2278 	getter.dispose();
2279 	*/
2280 
2281 	terminal.writeln(terminal.getline());
2282 	terminal.writeln(terminal.getline());
2283 	terminal.writeln(terminal.getline());
2284 
2285 	//input.getch();
2286 
2287 	// return;
2288 	//
2289 
2290 	terminal.setTitle("Basic I/O");
2291 	auto input = RealTimeConsoleInput(&terminal, ConsoleInputFlags.raw | ConsoleInputFlags.allInputEvents);
2292 	terminal.color(Color.green | Bright, Color.black);
2293 
2294 	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");
2295 	terminal.writefln("%d %d", terminal.cursorX, terminal.cursorY);
2296 
2297 	int centerX = terminal.width / 2;
2298 	int centerY = terminal.height / 2;
2299 
2300 	bool timeToBreak = false;
2301 
2302 	void handleEvent(InputEvent event) {
2303 		terminal.writef("%s\n", event.type);
2304 		final switch(event.type) {
2305 			case InputEvent.Type.UserInterruptionEvent:
2306 			case InputEvent.Type.HangupEvent:
2307 			case InputEvent.Type.EndOfFileEvent:
2308 				timeToBreak = true;
2309 				version(with_eventloop) {
2310 					import arsd.eventloop;
2311 					exit();
2312 				}
2313 			break;
2314 			case InputEvent.Type.SizeChangedEvent:
2315 				auto ev = event.get!(InputEvent.Type.SizeChangedEvent);
2316 				terminal.writeln(ev);
2317 			break;
2318 			case InputEvent.Type.CharacterEvent:
2319 				auto ev = event.get!(InputEvent.Type.CharacterEvent);
2320 				terminal.writef("\t%s\n", ev);
2321 				if(ev.character == 'Q') {
2322 					timeToBreak = true;
2323 					version(with_eventloop) {
2324 						import arsd.eventloop;
2325 						exit();
2326 					}
2327 				}
2328 
2329 				if(ev.character == 'C')
2330 					terminal.clear();
2331 			break;
2332 			case InputEvent.Type.NonCharacterKeyEvent:
2333 				terminal.writef("\t%s\n", event.get!(InputEvent.Type.NonCharacterKeyEvent));
2334 			break;
2335 			case InputEvent.Type.PasteEvent:
2336 				terminal.writef("\t%s\n", event.get!(InputEvent.Type.PasteEvent));
2337 			break;
2338 			case InputEvent.Type.MouseEvent:
2339 				terminal.writef("\t%s\n", event.get!(InputEvent.Type.MouseEvent));
2340 			break;
2341 			case InputEvent.Type.CustomEvent:
2342 			break;
2343 		}
2344 
2345 		terminal.writefln("%d %d", terminal.cursorX, terminal.cursorY);
2346 
2347 		/*
2348 		if(input.kbhit()) {
2349 			auto c = input.getch();
2350 			if(c == 'q' || c == 'Q')
2351 				break;
2352 			terminal.moveTo(centerX, centerY);
2353 			terminal.writef("%c", c);
2354 			terminal.flush();
2355 		}
2356 		usleep(10000);
2357 		*/
2358 	}
2359 
2360 	version(with_eventloop) {
2361 		import arsd.eventloop;
2362 		addListener(&handleEvent);
2363 		loop();
2364 	} else {
2365 		loop: while(true) {
2366 			auto event = input.nextEvent();
2367 			handleEvent(event);
2368 			if(timeToBreak)
2369 				break loop;
2370 		}
2371 	}
2372 }
2373 
2374 /**
2375 	FIXME: support lines that wrap
2376 	FIXME: better controls maybe
2377 
2378 	FIXME: support multi-line "lines" and some form of line continuation, both
2379 	       from the user (if permitted) and from the application, so like the user
2380 	       hits "class foo { \n" and the app says "that line needs continuation" automatically.
2381 
2382 	FIXME: fix lengths on prompt and suggestion
2383 
2384 	A note on history:
2385 
2386 	To save history, you must call LineGetter.dispose() when you're done with it.
2387 	History will not be automatically saved without that call!
2388 
2389 	The history saving and loading as a trivially encountered race condition: if you
2390 	open two programs that use the same one at the same time, the one that closes second
2391 	will overwrite any history changes the first closer saved.
2392 
2393 	GNU Getline does this too... and it actually kinda drives me nuts. But I don't know
2394 	what a good fix is except for doing a transactional commit straight to the file every
2395 	time and that seems like hitting the disk way too often.
2396 
2397 	We could also do like a history server like a database daemon that keeps the order
2398 	correct but I don't actually like that either because I kinda like different bashes
2399 	to have different history, I just don't like it all to get lost.
2400 
2401 	Regardless though, this isn't even used in bash anyway, so I don't think I care enough
2402 	to put that much effort into it. Just using separate files for separate tasks is good
2403 	enough I think.
2404 */
2405 class LineGetter {
2406 	/* A note on the assumeSafeAppends in here: since these buffers are private, we can be
2407 	   pretty sure that stomping isn't an issue, so I'm using this liberally to keep the
2408 	   append/realloc code simple and hopefully reasonably fast. */
2409 
2410 	// saved to file
2411 	string[] history;
2412 
2413 	// not saved
2414 	Terminal* terminal;
2415 	string historyFilename;
2416 
2417 	/// Make sure that the parent terminal struct remains in scope for the duration
2418 	/// of LineGetter's lifetime, as it does hold on to and use the passed pointer
2419 	/// throughout.
2420 	///
2421 	/// historyFilename will load and save an input history log to a particular folder.
2422 	/// Leaving it null will mean no file will be used and history will not be saved across sessions.
2423 	this(Terminal* tty, string historyFilename = null) {
2424 		this.terminal = tty;
2425 		this.historyFilename = historyFilename;
2426 
2427 		line.reserve(128);
2428 
2429 		if(historyFilename.length)
2430 			loadSettingsAndHistoryFromFile();
2431 
2432 		regularForeground = cast(Color) terminal._currentForeground;
2433 		background = cast(Color) terminal._currentBackground;
2434 		suggestionForeground = Color.blue;
2435 	}
2436 
2437 	/// Call this before letting LineGetter die so it can do any necessary
2438 	/// cleanup and save the updated history to a file.
2439 	void dispose() {
2440 		if(historyFilename.length)
2441 			saveSettingsAndHistoryToFile();
2442 	}
2443 
2444 	/// Override this to change the directory where history files are stored
2445 	///
2446 	/// Default is $HOME/.arsd-getline on linux and %APPDATA%/arsd-getline/ on Windows.
2447 	string historyFileDirectory() {
2448 		version(Windows) {
2449 			char[1024] path;
2450 			// FIXME: this doesn't link because the crappy dmd lib doesn't have it
2451 			if(0) { // SHGetFolderPathA(null, CSIDL_APPDATA, null, 0, path.ptr) >= 0) {
2452 				import core.stdc.string;
2453 				return cast(string) path[0 .. strlen(path.ptr)] ~ "\\arsd-getline";
2454 			} else {
2455 				import std.process;
2456 				return environment["APPDATA"] ~ "\\arsd-getline";
2457 			}
2458 		} else version(Posix) {
2459 			import std.process;
2460 			return environment["HOME"] ~ "/.arsd-getline";
2461 		}
2462 	}
2463 
2464 	/// You can customize the colors here. You should set these after construction, but before
2465 	/// calling startGettingLine or getline.
2466 	Color suggestionForeground;
2467 	Color regularForeground; /// .
2468 	Color background; /// .
2469 	//bool reverseVideo;
2470 
2471 	/// Set this if you want a prompt to be drawn with the line. It does NOT support color in string.
2472 	string prompt;
2473 
2474 	/// Turn on auto suggest if you want a greyed thing of what tab
2475 	/// would be able to fill in as you type.
2476 	///
2477 	/// You might want to turn it off if generating a completion list is slow.
2478 	bool autoSuggest = true;
2479 
2480 
2481 	/// Override this if you don't want all lines added to the history.
2482 	/// You can return null to not add it at all, or you can transform it.
2483 	string historyFilter(string candidate) {
2484 		return candidate;
2485 	}
2486 
2487 	/// You may override this to do nothing
2488 	void saveSettingsAndHistoryToFile() {
2489 		import std.file;
2490 		if(!exists(historyFileDirectory()))
2491 			mkdir(historyFileDirectory());
2492 		auto fn = historyPath();
2493 		import std.stdio;
2494 		auto file = File(fn, "wt");
2495 		foreach(item; history)
2496 			file.writeln(item);
2497 	}
2498 
2499 	private string historyPath() {
2500 		import std.path;
2501 		auto filename = historyFileDirectory() ~ dirSeparator ~ historyFilename ~ ".history";
2502 		return filename;
2503 	}
2504 
2505 	/// You may override this to do nothing
2506 	void loadSettingsAndHistoryFromFile() {
2507 		import std.file;
2508 		history = null;
2509 		auto fn = historyPath();
2510 		if(exists(fn)) {
2511 			import std.stdio;
2512 			foreach(line; File(fn, "rt").byLine())
2513 				history ~= line.idup;
2514 
2515 		}
2516 	}
2517 
2518 	/**
2519 		Override this to provide tab completion. You may use the candidate
2520 		argument to filter the list, but you don't have to (LineGetter will
2521 		do it for you on the values you return).
2522 
2523 		Ideally, you wouldn't return more than about ten items since the list
2524 		gets difficult to use if it is too long.
2525 
2526 		Default is to provide recent command history as autocomplete.
2527 	*/
2528 	protected string[] tabComplete(in dchar[] candidate) {
2529 		return history.length > 20 ? history[0 .. 20] : history;
2530 	}
2531 
2532 	private string[] filterTabCompleteList(string[] list) {
2533 		if(list.length == 0)
2534 			return list;
2535 
2536 		string[] f;
2537 		f.reserve(list.length);
2538 
2539 		foreach(item; list) {
2540 			import std.algorithm;
2541 			if(startsWith(item, line[0 .. cursorPosition]))
2542 				f ~= item;
2543 		}
2544 
2545 		return f;
2546 	}
2547 
2548 	/// Override this to provide a custom display of the tab completion list
2549 	protected void showTabCompleteList(string[] list) {
2550 		if(list.length) {
2551 			// FIXME: allow mouse clicking of an item, that would be cool
2552 
2553 			//if(terminal.type == ConsoleOutputType.linear) {
2554 				terminal.writeln();
2555 				foreach(item; list) {
2556 					terminal.color(suggestionForeground, background);
2557 					import std.utf;
2558 					auto idx = codeLength!char(line[0 .. cursorPosition]);
2559 					terminal.write("  ", item[0 .. idx]);
2560 					terminal.color(regularForeground, background);
2561 					terminal.writeln(item[idx .. $]);
2562 				}
2563 				updateCursorPosition();
2564 				redraw();
2565 			//}
2566 		}
2567 	}
2568 
2569 	/// One-call shop for the main workhorse
2570 	/// If you already have a RealTimeConsoleInput ready to go, you
2571 	/// should pass a pointer to yours here. Otherwise, LineGetter will
2572 	/// make its own.
2573 	public string getline(RealTimeConsoleInput* input = null) {
2574 		startGettingLine();
2575 		if(input is null) {
2576 			auto i = RealTimeConsoleInput(terminal, ConsoleInputFlags.raw | ConsoleInputFlags.allInputEvents);
2577 			while(workOnLine(i.nextEvent())) {}
2578 		} else
2579 			while(workOnLine(input.nextEvent())) {}
2580 		return finishGettingLine();
2581 	}
2582 
2583 	private int currentHistoryViewPosition = 0;
2584 	private dchar[] uncommittedHistoryCandidate;
2585 	void loadFromHistory(int howFarBack) {
2586 		if(howFarBack < 0)
2587 			howFarBack = 0;
2588 		if(howFarBack > history.length) // lol signed/unsigned comparison here means if i did this first, before howFarBack < 0, it would totally cycle around.
2589 			howFarBack = cast(int) history.length;
2590 		if(howFarBack == currentHistoryViewPosition)
2591 			return;
2592 		if(currentHistoryViewPosition == 0) {
2593 			// save the current line so we can down arrow back to it later
2594 			if(uncommittedHistoryCandidate.length < line.length) {
2595 				uncommittedHistoryCandidate.length = line.length;
2596 			}
2597 
2598 			uncommittedHistoryCandidate[0 .. line.length] = line[];
2599 			uncommittedHistoryCandidate = uncommittedHistoryCandidate[0 .. line.length];
2600 			uncommittedHistoryCandidate.assumeSafeAppend();
2601 		}
2602 
2603 		currentHistoryViewPosition = howFarBack;
2604 
2605 		if(howFarBack == 0) {
2606 			line.length = uncommittedHistoryCandidate.length;
2607 			line.assumeSafeAppend();
2608 			line[] = uncommittedHistoryCandidate[];
2609 		} else {
2610 			line = line[0 .. 0];
2611 			line.assumeSafeAppend();
2612 			foreach(dchar ch; history[$ - howFarBack])
2613 				line ~= ch;
2614 		}
2615 
2616 		cursorPosition = cast(int) line.length;
2617 	}
2618 
2619 	bool insertMode = true;
2620 
2621 	private dchar[] line;
2622 	private int cursorPosition = 0;
2623 
2624 	// used for redrawing the line in the right place
2625 	// and detecting mouse events on our line.
2626 	private int startOfLineX;
2627 	private int startOfLineY;
2628 
2629 	private string suggestion(string[] list = null) {
2630 		import std.algorithm, std.utf;
2631 		auto relevantLineSection = line[0 .. cursorPosition];
2632 		// FIXME: see about caching the list if we easily can
2633 		if(list is null)
2634 			list = filterTabCompleteList(tabComplete(relevantLineSection));
2635 
2636 		if(list.length) {
2637 			string commonality = list[0];
2638 			foreach(item; list[1 .. $]) {
2639 				commonality = commonPrefix(commonality, item);
2640 			}
2641 
2642 			if(commonality.length) {
2643 				return commonality[codeLength!char(relevantLineSection) .. $];
2644 			}
2645 		}
2646 
2647 		return null;
2648 	}
2649 
2650 	/// Adds a character at the current position in the line. You can call this too if you hook events for hotkeys or something.
2651 	/// You'll probably want to call redraw() after adding chars.
2652 	void addChar(dchar ch) {
2653 		assert(cursorPosition >= 0 && cursorPosition <= line.length);
2654 		if(cursorPosition == line.length)
2655 			line ~= ch;
2656 		else {
2657 			assert(line.length);
2658 			if(insertMode) {
2659 				line ~= ' ';
2660 				for(int i = cast(int) line.length - 2; i >= cursorPosition; i --)
2661 					line[i + 1] = line[i];
2662 			}
2663 			line[cursorPosition] = ch;
2664 		}
2665 		cursorPosition++;
2666 	}
2667 
2668 	/// .
2669 	void addString(string s) {
2670 		// FIXME: this could be more efficient
2671 		// but does it matter? these lines aren't super long anyway. But then again a paste could be excessively long (prolly accidental, but still)
2672 		foreach(dchar ch; s)
2673 			addChar(ch);
2674 	}
2675 
2676 	/// Deletes the character at the current position in the line.
2677 	/// You'll probably want to call redraw() after deleting chars.
2678 	void deleteChar() {
2679 		if(cursorPosition == line.length)
2680 			return;
2681 		for(int i = cursorPosition; i < line.length - 1; i++)
2682 			line[i] = line[i + 1];
2683 		line = line[0 .. $-1];
2684 		line.assumeSafeAppend();
2685 	}
2686 
2687 	int lastDrawLength = 0;
2688 	void redraw() {
2689 		terminal.moveTo(startOfLineX, startOfLineY);
2690 
2691 		terminal.write(prompt);
2692 
2693 		terminal.write(line);
2694 		auto suggestion = ((cursorPosition == line.length) && autoSuggest) ? this.suggestion() : null;
2695 		if(suggestion.length) {
2696 			terminal.color(suggestionForeground, background);
2697 			terminal.write(suggestion);
2698 			terminal.color(regularForeground, background);
2699 		}
2700 		if(line.length < lastDrawLength)
2701 		foreach(i; line.length + suggestion.length + prompt.length .. lastDrawLength)
2702 			terminal.write(" ");
2703 		lastDrawLength = cast(int) (line.length + suggestion.length + prompt.length); // FIXME: graphemes and utf-8 on suggestion/prompt
2704 
2705 		// FIXME: wrapping
2706 		terminal.moveTo(startOfLineX + cursorPosition + cast(int) prompt.length, startOfLineY);
2707 	}
2708 
2709 	/// Starts getting a new line. Call workOnLine and finishGettingLine afterward.
2710 	///
2711 	/// Make sure that you've flushed your input and output before calling this
2712 	/// function or else you might lose events or get exceptions from this.
2713 	void startGettingLine() {
2714 		// reset from any previous call first
2715 		cursorPosition = 0;
2716 		lastDrawLength = 0;
2717 		justHitTab = false;
2718 		currentHistoryViewPosition = 0;
2719 		if(line.length) {
2720 			line = line[0 .. 0];
2721 			line.assumeSafeAppend();
2722 		}
2723 
2724 		updateCursorPosition();
2725 		terminal.showCursor();
2726 
2727 		redraw();
2728 	}
2729 
2730 	private void updateCursorPosition() {
2731 		terminal.flush();
2732 
2733 		// then get the current cursor position to start fresh
2734 		version(Windows) {
2735 			CONSOLE_SCREEN_BUFFER_INFO info;
2736 			GetConsoleScreenBufferInfo(terminal.hConsole, &info);
2737 			startOfLineX = info.dwCursorPosition.X;
2738 			startOfLineY = info.dwCursorPosition.Y;
2739 		} else {
2740 			// request current cursor position
2741 
2742 			// we have to turn off cooked mode to get this answer, otherwise it will all
2743 			// be messed up. (I hate unix terminals, the Windows way is so much easer.)
2744 			RealTimeConsoleInput input = RealTimeConsoleInput(terminal, ConsoleInputFlags.raw);
2745 
2746 			terminal.writeStringRaw("\033[6n");
2747 			terminal.flush();
2748 
2749 			import core.sys.posix.unistd;
2750 			// reading directly to bypass any buffering
2751 			ubyte[16] buffer;
2752 			auto len = read(terminal.fdIn, buffer.ptr, buffer.length);
2753 			if(len <= 0)
2754 				throw new Exception("Couldn't get cursor position to initialize get line");
2755 			auto got = buffer[0 .. len];
2756 			if(got.length < 6)
2757 				throw new Exception("not enough cursor reply answer");
2758 			if(got[0] != '\033' || got[1] != '[' || got[$-1] != 'R')
2759 				throw new Exception("wrong answer for cursor position");
2760 			auto gots = cast(char[]) got[2 .. $-1];
2761 
2762 			import std.conv;
2763 			import std.string;
2764 
2765 			auto pieces = split(gots, ";");
2766 			if(pieces.length != 2) throw new Exception("wtf wrong answer on cursor position");
2767 
2768 			startOfLineX = to!int(pieces[1]) - 1;
2769 			startOfLineY = to!int(pieces[0]) - 1;
2770 		}
2771 
2772 		// updating these too because I can with the more accurate info from above
2773 		terminal._cursorX = startOfLineX;
2774 		terminal._cursorY = startOfLineY;
2775 	}
2776 
2777 	private bool justHitTab;
2778 
2779 	/// for integrating into another event loop
2780 	/// you can pass individual events to this and
2781 	/// the line getter will work on it
2782 	///
2783 	/// returns false when there's nothing more to do
2784 	bool workOnLine(InputEvent e) {
2785 		switch(e.type) {
2786 			case InputEvent.Type.EndOfFileEvent:
2787 				justHitTab = false;
2788 				// FIXME: this should be distinct from an empty line when hit at the beginning
2789 				return false;
2790 			//break;
2791 			case InputEvent.Type.CharacterEvent:
2792 				if(e.characterEvent.eventType == CharacterEvent.Type.Released)
2793 					return true;
2794 				/* Insert the character (unless it is backspace, tab, or some other control char) */
2795 				auto ch = e.characterEvent.character;
2796 				switch(ch) {
2797 					case 4: // ctrl+d will also send a newline-equivalent 
2798 					case '\r':
2799 					case '\n':
2800 						justHitTab = false;
2801 						return false;
2802 					case '\t':
2803 						auto relevantLineSection = line[0 .. cursorPosition];
2804 						auto possibilities = filterTabCompleteList(tabComplete(relevantLineSection));
2805 						import std.utf;
2806 
2807 						if(possibilities.length == 1) {
2808 							auto toFill = possibilities[0][codeLength!char(relevantLineSection) .. $];
2809 							if(toFill.length) {
2810 								addString(toFill);
2811 								redraw();
2812 							}
2813 							justHitTab = false;
2814 						} else {
2815 							if(justHitTab) {
2816 								justHitTab = false;
2817 								showTabCompleteList(possibilities);
2818 							} else {
2819 								justHitTab = true;
2820 								/* fill it in with as much commonality as there is amongst all the suggestions */
2821 								auto suggestion = this.suggestion(possibilities);
2822 								if(suggestion.length) {
2823 									addString(suggestion);
2824 									redraw();
2825 								}
2826 							}
2827 						}
2828 					break;
2829 					case '\b':
2830 						justHitTab = false;
2831 						if(cursorPosition) {
2832 							cursorPosition--;
2833 							for(int i = cursorPosition; i < line.length - 1; i++)
2834 								line[i] = line[i + 1];
2835 							line = line[0 .. $ - 1];
2836 							line.assumeSafeAppend();
2837 							redraw();
2838 						}
2839 					break;
2840 					default:
2841 						justHitTab = false;
2842 						addChar(ch);
2843 						redraw();
2844 				}
2845 			break;
2846 			case InputEvent.Type.NonCharacterKeyEvent:
2847 				if(e.nonCharacterKeyEvent.eventType == NonCharacterKeyEvent.Type.Released)
2848 					return true;
2849 				justHitTab = false;
2850 				/* Navigation */
2851 				auto key = e.nonCharacterKeyEvent.key;
2852 				switch(key) {
2853 					case NonCharacterKeyEvent.Key.LeftArrow:
2854 						if(cursorPosition)
2855 							cursorPosition--;
2856 						redraw();
2857 					break;
2858 					case NonCharacterKeyEvent.Key.RightArrow:
2859 						if(cursorPosition < line.length)
2860 							cursorPosition++;
2861 						redraw();
2862 					break;
2863 					case NonCharacterKeyEvent.Key.UpArrow:
2864 						loadFromHistory(currentHistoryViewPosition + 1);
2865 						redraw();
2866 					break;
2867 					case NonCharacterKeyEvent.Key.DownArrow:
2868 						loadFromHistory(currentHistoryViewPosition - 1);
2869 						redraw();
2870 					break;
2871 					case NonCharacterKeyEvent.Key.PageUp:
2872 						loadFromHistory(cast(int) history.length);
2873 						redraw();
2874 					break;
2875 					case NonCharacterKeyEvent.Key.PageDown:
2876 						loadFromHistory(0);
2877 						redraw();
2878 					break;
2879 					case NonCharacterKeyEvent.Key.Home:
2880 						cursorPosition = 0;
2881 						redraw();
2882 					break;
2883 					case NonCharacterKeyEvent.Key.End:
2884 						cursorPosition = cast(int) line.length;
2885 						redraw();
2886 					break;
2887 					case NonCharacterKeyEvent.Key.Insert:
2888 						insertMode = !insertMode;
2889 						// FIXME: indicate this on the UI somehow
2890 						// like change the cursor or something
2891 					break;
2892 					case NonCharacterKeyEvent.Key.Delete:
2893 						deleteChar();
2894 						redraw();
2895 					break;
2896 					default:
2897 						/* ignore */
2898 				}
2899 			break;
2900 			case InputEvent.Type.PasteEvent:
2901 				justHitTab = false;
2902 				addString(e.pasteEvent.pastedText);
2903 				redraw();
2904 			break;
2905 			case InputEvent.Type.MouseEvent:
2906 				/* Clicking with the mouse to move the cursor is so much easier than arrowing
2907 				   or even emacs/vi style movements much of the time, so I'ma support it. */
2908 
2909 				auto me = e.mouseEvent;
2910 				if(me.eventType == MouseEvent.Type.Pressed) {
2911 					if(me.buttons & MouseEvent.Button.Left) {
2912 						if(me.y == startOfLineY) {
2913 							// FIXME: prompt.length should be graphemes or at least code poitns
2914 							int p = me.x - startOfLineX - cast(int) prompt.length;
2915 							if(p >= 0 && p < line.length) {
2916 								justHitTab = false;
2917 								cursorPosition = p;
2918 								redraw();
2919 							}
2920 						}
2921 					}
2922 				}
2923 			break;
2924 			case InputEvent.Type.SizeChangedEvent:
2925 				/* We'll adjust the bounding box. If you don't like this, handle SizeChangedEvent
2926 				   yourself and then don't pass it to this function. */
2927 				// FIXME
2928 			break;
2929 			case InputEvent.Type.UserInterruptionEvent:
2930 				/* I'll take this as canceling the line. */
2931 				throw new Exception("user canceled"); // FIXME
2932 			//break;
2933 			case InputEvent.Type.HangupEvent:
2934 				/* I'll take this as canceling the line. */
2935 				throw new Exception("user hanged up"); // FIXME
2936 			//break;
2937 			default:
2938 				/* ignore. ideally it wouldn't be passed to us anyway! */
2939 		}
2940 
2941 		return true;
2942 	}
2943 
2944 	string finishGettingLine() {
2945 		import std.conv;
2946 		auto f = to!string(line);
2947 		auto history = historyFilter(f);
2948 		if(history !is null)
2949 			this.history ~= history;
2950 
2951 		// FIXME: we should hide the cursor if it was hidden in the call to startGettingLine
2952 		return f;
2953 	}
2954 }
2955 
2956 version(Windows) {
2957 	// to get the directory for saving history in the line things
2958 	enum CSIDL_APPDATA = 26;
2959 	extern(Windows) HRESULT SHGetFolderPathA(HWND, int, HANDLE, DWORD, LPSTR);
2960 }
2961 
2962 /*
2963 
2964 	// more efficient scrolling
2965 	http://msdn.microsoft.com/en-us/library/windows/desktop/ms685113%28v=vs.85%29.aspx
2966 	// and the unix sequences
2967 
2968 
2969 	rxvt documentation:
2970 	use this to finish the input magic for that
2971 
2972 
2973        For the keypad, use Shift to temporarily override Application-Keypad
2974        setting use Num_Lock to toggle Application-Keypad setting if Num_Lock
2975        is off, toggle Application-Keypad setting. Also note that values of
2976        Home, End, Delete may have been compiled differently on your system.
2977 
2978                          Normal       Shift         Control      Ctrl+Shift
2979        Tab               ^I           ESC [ Z       ^I           ESC [ Z
2980        BackSpace         ^H           ^?            ^?           ^?
2981        Find              ESC [ 1 ~    ESC [ 1 $     ESC [ 1 ^    ESC [ 1 @
2982        Insert            ESC [ 2 ~    paste         ESC [ 2 ^    ESC [ 2 @
2983        Execute           ESC [ 3 ~    ESC [ 3 $     ESC [ 3 ^    ESC [ 3 @
2984        Select            ESC [ 4 ~    ESC [ 4 $     ESC [ 4 ^    ESC [ 4 @
2985        Prior             ESC [ 5 ~    scroll-up     ESC [ 5 ^    ESC [ 5 @
2986        Next              ESC [ 6 ~    scroll-down   ESC [ 6 ^    ESC [ 6 @
2987        Home              ESC [ 7 ~    ESC [ 7 $     ESC [ 7 ^    ESC [ 7 @
2988        End               ESC [ 8 ~    ESC [ 8 $     ESC [ 8 ^    ESC [ 8 @
2989        Delete            ESC [ 3 ~    ESC [ 3 $     ESC [ 3 ^    ESC [ 3 @
2990        F1                ESC [ 11 ~   ESC [ 23 ~    ESC [ 11 ^   ESC [ 23 ^
2991        F2                ESC [ 12 ~   ESC [ 24 ~    ESC [ 12 ^   ESC [ 24 ^
2992        F3                ESC [ 13 ~   ESC [ 25 ~    ESC [ 13 ^   ESC [ 25 ^
2993        F4                ESC [ 14 ~   ESC [ 26 ~    ESC [ 14 ^   ESC [ 26 ^
2994        F5                ESC [ 15 ~   ESC [ 28 ~    ESC [ 15 ^   ESC [ 28 ^
2995        F6                ESC [ 17 ~   ESC [ 29 ~    ESC [ 17 ^   ESC [ 29 ^
2996        F7                ESC [ 18 ~   ESC [ 31 ~    ESC [ 18 ^   ESC [ 31 ^
2997        F8                ESC [ 19 ~   ESC [ 32 ~    ESC [ 19 ^   ESC [ 32 ^
2998        F9                ESC [ 20 ~   ESC [ 33 ~    ESC [ 20 ^   ESC [ 33 ^
2999        F10               ESC [ 21 ~   ESC [ 34 ~    ESC [ 21 ^   ESC [ 34 ^
3000        F11               ESC [ 23 ~   ESC [ 23 $    ESC [ 23 ^   ESC [ 23 @
3001        F12               ESC [ 24 ~   ESC [ 24 $    ESC [ 24 ^   ESC [ 24 @
3002        F13               ESC [ 25 ~   ESC [ 25 $    ESC [ 25 ^   ESC [ 25 @
3003        F14               ESC [ 26 ~   ESC [ 26 $    ESC [ 26 ^   ESC [ 26 @
3004        F15 (Help)        ESC [ 28 ~   ESC [ 28 $    ESC [ 28 ^   ESC [ 28 @
3005        F16 (Menu)        ESC [ 29 ~   ESC [ 29 $    ESC [ 29 ^   ESC [ 29 @
3006 
3007        F17               ESC [ 31 ~   ESC [ 31 $    ESC [ 31 ^   ESC [ 31 @
3008        F18               ESC [ 32 ~   ESC [ 32 $    ESC [ 32 ^   ESC [ 32 @
3009        F19               ESC [ 33 ~   ESC [ 33 $    ESC [ 33 ^   ESC [ 33 @
3010        F20               ESC [ 34 ~   ESC [ 34 $    ESC [ 34 ^   ESC [ 34 @
3011                                                                  Application
3012        Up                ESC [ A      ESC [ a       ESC O a      ESC O A
3013        Down              ESC [ B      ESC [ b       ESC O b      ESC O B
3014        Right             ESC [ C      ESC [ c       ESC O c      ESC O C
3015        Left              ESC [ D      ESC [ d       ESC O d      ESC O D
3016        KP_Enter          ^M                                      ESC O M
3017        KP_F1             ESC O P                                 ESC O P
3018        KP_F2             ESC O Q                                 ESC O Q
3019        KP_F3             ESC O R                                 ESC O R
3020        KP_F4             ESC O S                                 ESC O S
3021        XK_KP_Multiply    *                                       ESC O j
3022        XK_KP_Add         +                                       ESC O k
3023        XK_KP_Separator   ,                                       ESC O l
3024        XK_KP_Subtract    -                                       ESC O m
3025        XK_KP_Decimal     .                                       ESC O n
3026        XK_KP_Divide      /                                       ESC O o
3027        XK_KP_0           0                                       ESC O p
3028        XK_KP_1           1                                       ESC O q
3029        XK_KP_2           2                                       ESC O r
3030        XK_KP_3           3                                       ESC O s
3031        XK_KP_4           4                                       ESC O t
3032        XK_KP_5           5                                       ESC O u
3033        XK_KP_6           6                                       ESC O v
3034        XK_KP_7           7                                       ESC O w
3035        XK_KP_8           8                                       ESC O x
3036        XK_KP_9           9                                       ESC O y
3037 */