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