1 // for optional dependency
2 // for VT on Windows P s = 1 8 → Report the size of the text area in characters as CSI 8 ; height ; width t
3 // could be used to have the TE volunteer the size
4 /++
5 	Module for interacting with the user's terminal, including color output, cursor manipulation, and full-featured real-time mouse and keyboard input. Also includes high-level convenience methods, like [Terminal.getline], which gives the user a line editor with history, completion, etc. See the [#examples].
6 
7 
8 	The main interface for this module is the Terminal struct, which
9 	encapsulates the output functions and line-buffered input of the terminal, and
10 	RealTimeConsoleInput, which gives real time input.
11 	
12 	Creating an instance of these structs will perform console initialization. When the struct
13 	goes out of scope, any changes in console settings will be automatically reverted.
14 
15 	Note: on Posix, it traps SIGINT and translates it into an input event. You should
16 	keep your event loop moving and keep an eye open for this to exit cleanly; simply break
17 	your event loop upon receiving a UserInterruptionEvent. (Without
18 	the signal handler, ctrl+c can leave your terminal in a bizarre state.)
19 
20 	As a user, if you have to forcibly kill your program and the event doesn't work, there's still ctrl+\
21 
22 	On old Mac Terminal btw, a lot of hacks are needed and mouse support doesn't work. Most functions basically
23 	work now with newer Mac OS versions though.
24 
25 	Future_Roadmap:
26 	$(LIST
27 		* The CharacterEvent and NonCharacterKeyEvent types will be removed. Instead, use KeyboardEvent
28 		  on new programs.
29 
30 		* The ScrollbackBuffer will be expanded to be easier to use to partition your screen. It might even
31 		  handle input events of some sort. Its API may change.
32 
33 		* getline I want to be really easy to use both for code and end users. It will need multi-line support
34 		  eventually.
35 
36 		* I might add an expandable event loop and base level widget classes. This may be Linux-specific in places and may overlap with similar functionality in simpledisplay.d. If I can pull it off without a third module, I want them to be compatible with each other too so the two modules can be combined easily. (Currently, they are both compatible with my eventloop.d and can be easily combined through it, but that is a third module.)
37 
38 		* More advanced terminal features as functions, where available, like cursor changing and full-color functions.
39 
40 		* More documentation.
41 	)
42 
43 	WHAT I WON'T DO:
44 	$(LIST
45 		* support everything under the sun. If it isn't default-installed on an OS I or significant number of other people
46 		  might actually use, and isn't written by me, I don't really care about it. This means the only supported terminals are:
47 		  $(LIST
48 
49 		  * xterm (and decently xterm compatible emulators like Konsole)
50 		  * Windows console
51 		  * rxvt (to a lesser extent)
52 		  * Linux console
53 		  * My terminal emulator family of applications https://github.com/adamdruppe/terminal-emulator
54 		  )
55 
56 		  Anything else is cool if it does work, but I don't want to go out of my way for it.
57 
58 		* Use other libraries, unless strictly optional. terminal.d is a stand-alone module by default and
59 		  always will be.
60 
61 		* Do a full TUI widget set. I might do some basics and lay a little groundwork, but a full TUI
62 		  is outside the scope of this module (unless I can do it really small.)
63 	)
64 +/
65 module arsd.terminal;
66 
67 // FIXME: needs to support VT output on Windows too in certain situations
68 // detect VT on windows by trying to set the flag. if this succeeds, ask it for caps. if this replies with my code we good to do extended output.
69 
70 /++
71 	$(H3 Get Line)
72 
73 	This example will demonstrate the high-level getline interface.
74 
75 	The user will be able to type a line and navigate around it with cursor keys and even the mouse on some systems, as well as perform editing as they expect (e.g. the backspace and delete keys work normally) until they press enter.  Then, the final line will be returned to your program, which the example will simply print back to the user.
76 +/
77 version(demos) unittest {
78 	import arsd.terminal;
79 
80 	void main() {
81 		auto terminal = Terminal(ConsoleOutputType.linear);
82 		string line = terminal.getline();
83 		terminal.writeln("You wrote: ", line);
84 	}
85 
86 	main; // exclude from docs
87 }
88 
89 /++
90 	$(H3 Color)
91 
92 	This example demonstrates color output, using [Terminal.color]
93 	and the output functions like [Terminal.writeln].
94 +/
95 version(demos) unittest {
96 	import arsd.terminal;
97 
98 	void main() {
99 		auto terminal = Terminal(ConsoleOutputType.linear);
100 		terminal.color(Color.green, Color.black);
101 		terminal.writeln("Hello world, in green on black!");
102 		terminal.color(Color.DEFAULT, Color.DEFAULT);
103 		terminal.writeln("And back to normal.");
104 	}
105 
106 	main; // exclude from docs
107 }
108 
109 /++
110 	$(H3 Single Key)
111 
112 	This shows how to get one single character press using
113 	the [RealTimeConsoleInput] structure.
114 +/
115 version(demos) unittest {
116 	import arsd.terminal;
117 
118 	void main() {
119 		auto terminal = Terminal(ConsoleOutputType.linear);
120 		auto input = RealTimeConsoleInput(&terminal, ConsoleInputFlags.raw);
121 
122 		terminal.writeln("Press any key to continue...");
123 		auto ch = input.getch();
124 		terminal.writeln("You pressed ", ch);
125 	}
126 
127 	main; // exclude from docs
128 }
129 
130 /*
131 	Widgets:
132 		tab widget
133 		scrollback buffer
134 		partitioned canvas
135 */
136 
137 // FIXME: ctrl+d eof on stdin
138 
139 // FIXME: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686016%28v=vs.85%29.aspx
140 
141 
142 /++
143 	A function the sigint handler will call (if overridden - which is the
144 	case when [RealTimeConsoleInput] is active on Posix or if you compile with
145 	`TerminalDirectToEmulator` version on any platform at this time) in addition
146 	to the library's default handling, which is to set a flag for the event loop
147 	to inform you.
148 
149 	Remember, this is called from a signal handler and/or from a separate thread,
150 	so you are not allowed to do much with it and need care when setting TLS variables.
151 
152 	I suggest you only set a `__gshared bool` flag as many other operations will risk
153 	undefined behavior.
154 
155 	$(WARNING
156 		This function is never called on the default Windows console
157 		configuration in the current implementation. You can use
158 		`-version=TerminalDirectToEmulator` to guarantee it is called there
159 		too by causing the library to pop up a gui window for your application.
160 	)
161 
162 	History:
163 		Added March 30, 2020. Included in release v7.1.0.
164 
165 +/
166 __gshared void delegate() nothrow @nogc sigIntExtension;
167 
168 
169 version(TerminalDirectToEmulator) {
170 	version=WithEncapsulatedSignals;
171 } else version(Posix) {
172 	enum SIGWINCH = 28;
173 	__gshared bool windowSizeChanged = false;
174 	__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
175 	__gshared bool hangedUp = false; /// similar to interrupted.
176 	version=WithSignals;
177 
178 	version(with_eventloop)
179 		struct SignalFired {}
180 
181 	extern(C)
182 	void sizeSignalHandler(int sigNumber) nothrow {
183 		windowSizeChanged = true;
184 		version(with_eventloop) {
185 			import arsd.eventloop;
186 			try
187 				send(SignalFired());
188 			catch(Exception) {}
189 		}
190 	}
191 	extern(C)
192 	void interruptSignalHandler(int sigNumber) nothrow {
193 		interrupted = true;
194 		version(with_eventloop) {
195 			import arsd.eventloop;
196 			try
197 				send(SignalFired());
198 			catch(Exception) {}
199 		}
200 
201 		if(sigIntExtension)
202 			sigIntExtension();
203 	}
204 	extern(C)
205 	void hangupSignalHandler(int sigNumber) nothrow {
206 		hangedUp = true;
207 		version(with_eventloop) {
208 			import arsd.eventloop;
209 			try
210 				send(SignalFired());
211 			catch(Exception) {}
212 		}
213 	}
214 }
215 
216 // parts of this were taken from Robik's ConsoleD
217 // https://github.com/robik/ConsoleD/blob/master/consoled.d
218 
219 // Uncomment this line to get a main() to demonstrate this module's
220 // capabilities.
221 //version = Demo
222 
223 version(TerminalDirectToEmulator) {
224 	version=VtEscapeCodes;
225 } else version(Windows) {
226 	version(VtEscapeCodes) {} // cool
227 	version=Win32Console;
228 }
229 
230 version(Windows)
231 	import core.sys.windows.windows;
232 
233 version(Win32Console) {
234 	private {
235 		enum RED_BIT = 4;
236 		enum GREEN_BIT = 2;
237 		enum BLUE_BIT = 1;
238 	}
239 }
240 
241 version(Posix) {
242 
243 	version=VtEscapeCodes;
244 
245 	import core.sys.posix.termios;
246 	import core.sys.posix.unistd;
247 	import unix = core.sys.posix.unistd;
248 	import core.sys.posix.sys.types;
249 	import core.sys.posix.sys.time;
250 	import core.stdc.stdio;
251 
252 	import core.sys.posix.sys.ioctl;
253 }
254 
255 version(VtEscapeCodes) {
256 
257 	enum UseVtSequences = true;
258 
259 	version(TerminalDirectToEmulator) {
260 		private {
261 			enum RED_BIT = 1;
262 			enum GREEN_BIT = 2;
263 			enum BLUE_BIT = 4;
264 		}
265 	} else version(Windows) {} else
266 	private {
267 		enum RED_BIT = 1;
268 		enum GREEN_BIT = 2;
269 		enum BLUE_BIT = 4;
270 	}
271 
272 	struct winsize {
273 		ushort ws_row;
274 		ushort ws_col;
275 		ushort ws_xpixel;
276 		ushort ws_ypixel;
277 	}
278 
279 	// I'm taking this from the minimal termcap from my Slackware box (which I use as my /etc/termcap) and just taking the most commonly used ones (for me anyway).
280 
281 	// this way we'll have some definitions for 99% of typical PC cases even without any help from the local operating system
282 
283 	enum string builtinTermcap = `
284 # Generic VT entry.
285 vg|vt-generic|Generic VT entries:\
286 	:bs:mi:ms:pt:xn:xo:it#8:\
287 	:RA=\E[?7l:SA=\E?7h:\
288 	:bl=^G:cr=^M:ta=^I:\
289 	:cm=\E[%i%d;%dH:\
290 	:le=^H:up=\E[A:do=\E[B:nd=\E[C:\
291 	:LE=\E[%dD:RI=\E[%dC:UP=\E[%dA:DO=\E[%dB:\
292 	:ho=\E[H:cl=\E[H\E[2J:ce=\E[K:cb=\E[1K:cd=\E[J:sf=\ED:sr=\EM:\
293 	:ct=\E[3g:st=\EH:\
294 	:cs=\E[%i%d;%dr:sc=\E7:rc=\E8:\
295 	:ei=\E[4l:ic=\E[@:IC=\E[%d@:al=\E[L:AL=\E[%dL:\
296 	:dc=\E[P:DC=\E[%dP:dl=\E[M:DL=\E[%dM:\
297 	:so=\E[7m:se=\E[m:us=\E[4m:ue=\E[m:\
298 	:mb=\E[5m:mh=\E[2m:md=\E[1m:mr=\E[7m:me=\E[m:\
299 	:sc=\E7:rc=\E8:kb=\177:\
300 	:ku=\E[A:kd=\E[B:kr=\E[C:kl=\E[D:
301 
302 
303 # Slackware 3.1 linux termcap entry (Sat Apr 27 23:03:58 CDT 1996):
304 lx|linux|console|con80x25|LINUX System Console:\
305         :do=^J:co#80:li#25:cl=\E[H\E[J:sf=\ED:sb=\EM:\
306         :le=^H:bs:am:cm=\E[%i%d;%dH:nd=\E[C:up=\E[A:\
307         :ce=\E[K:cd=\E[J:so=\E[7m:se=\E[27m:us=\E[36m:ue=\E[m:\
308         :md=\E[1m:mr=\E[7m:mb=\E[5m:me=\E[m:is=\E[1;25r\E[25;1H:\
309         :ll=\E[1;25r\E[25;1H:al=\E[L:dc=\E[P:dl=\E[M:\
310         :it#8:ku=\E[A:kd=\E[B:kr=\E[C:kl=\E[D:kb=^H:ti=\E[r\E[H:\
311         :ho=\E[H:kP=\E[5~:kN=\E[6~:kH=\E[4~:kh=\E[1~:kD=\E[3~:kI=\E[2~:\
312         :k1=\E[[A:k2=\E[[B:k3=\E[[C:k4=\E[[D:k5=\E[[E:k6=\E[17~:\
313 	:F1=\E[23~:F2=\E[24~:\
314         :k7=\E[18~:k8=\E[19~:k9=\E[20~:k0=\E[21~:K1=\E[1~:K2=\E[5~:\
315         :K4=\E[4~:K5=\E[6~:\
316         :pt:sr=\EM:vt#3:xn:km:bl=^G:vi=\E[?25l:ve=\E[?25h:vs=\E[?25h:\
317         :sc=\E7:rc=\E8:cs=\E[%i%d;%dr:\
318         :r1=\Ec:r2=\Ec:r3=\Ec:
319 
320 # Some other, commonly used linux console entries.
321 lx|con80x28:co#80:li#28:tc=linux:
322 lx|con80x43:co#80:li#43:tc=linux:
323 lx|con80x50:co#80:li#50:tc=linux:
324 lx|con100x37:co#100:li#37:tc=linux:
325 lx|con100x40:co#100:li#40:tc=linux:
326 lx|con132x43:co#132:li#43:tc=linux:
327 
328 # vt102 - vt100 + insert line etc. VT102 does not have insert character.
329 v2|vt102|DEC vt102 compatible:\
330 	:co#80:li#24:\
331 	:ic@:IC@:\
332 	:is=\E[m\E[?1l\E>:\
333 	:rs=\E[m\E[?1l\E>:\
334 	:eA=\E)0:as=^N:ae=^O:ac=aaffggjjkkllmmnnooqqssttuuvvwwxx:\
335 	:ks=:ke=:\
336 	:k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:\
337 	:tc=vt-generic:
338 
339 # vt100 - really vt102 without insert line, insert char etc.
340 vt|vt100|DEC vt100 compatible:\
341 	:im@:mi@:al@:dl@:ic@:dc@:AL@:DL@:IC@:DC@:\
342 	:tc=vt102:
343 
344 
345 # Entry for an xterm. Insert mode has been disabled.
346 vs|xterm|tmux|tmux-256color|xterm-kitty|screen|screen.xterm|screen-256color|screen.xterm-256color|xterm-color|xterm-256color|vs100|xterm terminal emulator (X Window System):\
347 	:am:bs:mi@:km:co#80:li#55:\
348 	:im@:ei@:\
349 	:cl=\E[H\E[J:\
350 	:ct=\E[3k:ue=\E[m:\
351 	:is=\E[m\E[?1l\E>:\
352 	:rs=\E[m\E[?1l\E>:\
353 	:vi=\E[?25l:ve=\E[?25h:\
354 	:eA=\E)0:as=^N:ae=^O:ac=aaffggjjkkllmmnnooqqssttuuvvwwxx:\
355 	:kI=\E[2~:kD=\E[3~:kP=\E[5~:kN=\E[6~:\
356 	:k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:k5=\E[15~:\
357 	:k6=\E[17~:k7=\E[18~:k8=\E[19~:k9=\E[20~:k0=\E[21~:\
358 	:F1=\E[23~:F2=\E[24~:\
359 	:kh=\E[H:kH=\E[F:\
360 	:ks=:ke=:\
361 	:te=\E[2J\E[?47l\E8:ti=\E7\E[?47h:\
362 	:tc=vt-generic:
363 
364 
365 #rxvt, added by me
366 rxvt|rxvt-unicode|rxvt-unicode-256color:\
367 	:am:bs:mi@:km:co#80:li#55:\
368 	:im@:ei@:\
369 	:ct=\E[3k:ue=\E[m:\
370 	:is=\E[m\E[?1l\E>:\
371 	:rs=\E[m\E[?1l\E>:\
372 	:vi=\E[?25l:\
373 	:ve=\E[?25h:\
374 	:eA=\E)0:as=^N:ae=^O:ac=aaffggjjkkllmmnnooqqssttuuvvwwxx:\
375 	:kI=\E[2~:kD=\E[3~:kP=\E[5~:kN=\E[6~:\
376 	:k1=\E[11~:k2=\E[12~:k3=\E[13~:k4=\E[14~:k5=\E[15~:\
377 	:k6=\E[17~:k7=\E[18~:k8=\E[19~:k9=\E[20~:k0=\E[21~:\
378 	:F1=\E[23~:F2=\E[24~:\
379 	:kh=\E[7~:kH=\E[8~:\
380 	:ks=:ke=:\
381 	:te=\E[2J\E[?47l\E8:ti=\E7\E[?47h:\
382 	:tc=vt-generic:
383 
384 
385 # Some other entries for the same xterm.
386 v2|xterms|vs100s|xterm small window:\
387 	:co#80:li#24:tc=xterm:
388 vb|xterm-bold|xterm with bold instead of underline:\
389 	:us=\E[1m:tc=xterm:
390 vi|xterm-ins|xterm with insert mode:\
391 	:mi:im=\E[4h:ei=\E[4l:tc=xterm:
392 
393 Eterm|Eterm Terminal Emulator (X11 Window System):\
394         :am:bw:eo:km:mi:ms:xn:xo:\
395         :co#80:it#8:li#24:lm#0:pa#64:Co#8:AF=\E[3%dm:AB=\E[4%dm:op=\E[39m\E[49m:\
396         :AL=\E[%dL:DC=\E[%dP:DL=\E[%dM:DO=\E[%dB:IC=\E[%d@:\
397         :K1=\E[7~:K2=\EOu:K3=\E[5~:K4=\E[8~:K5=\E[6~:LE=\E[%dD:\
398         :RI=\E[%dC:UP=\E[%dA:ae=^O:al=\E[L:as=^N:bl=^G:cd=\E[J:\
399         :ce=\E[K:cl=\E[H\E[2J:cm=\E[%i%d;%dH:cr=^M:\
400         :cs=\E[%i%d;%dr:ct=\E[3g:dc=\E[P:dl=\E[M:do=\E[B:\
401         :ec=\E[%dX:ei=\E[4l:ho=\E[H:i1=\E[?47l\E>\E[?1l:ic=\E[@:\
402         :im=\E[4h:is=\E[r\E[m\E[2J\E[H\E[?7h\E[?1;3;4;6l\E[4l:\
403         :k1=\E[11~:k2=\E[12~:k3=\E[13~:k4=\E[14~:k5=\E[15~:\
404         :k6=\E[17~:k7=\E[18~:k8=\E[19~:k9=\E[20~:kD=\E[3~:\
405         :kI=\E[2~:kN=\E[6~:kP=\E[5~:kb=^H:kd=\E[B:ke=:kh=\E[7~:\
406         :kl=\E[D:kr=\E[C:ks=:ku=\E[A:le=^H:mb=\E[5m:md=\E[1m:\
407         :me=\E[m\017:mr=\E[7m:nd=\E[C:rc=\E8:\
408         :sc=\E7:se=\E[27m:sf=^J:so=\E[7m:sr=\EM:st=\EH:ta=^I:\
409         :te=\E[2J\E[?47l\E8:ti=\E7\E[?47h:ue=\E[24m:up=\E[A:\
410         :us=\E[4m:vb=\E[?5h\E[?5l:ve=\E[?25h:vi=\E[?25l:\
411         :ac=aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~:
412 
413 # DOS terminal emulator such as Telix or TeleMate.
414 # This probably also works for the SCO console, though it's incomplete.
415 an|ansi|ansi-bbs|ANSI terminals (emulators):\
416 	:co#80:li#24:am:\
417 	:is=:rs=\Ec:kb=^H:\
418 	:as=\E[m:ae=:eA=:\
419 	:ac=0\333+\257,\256.\031-\030a\261f\370g\361j\331k\277l\332m\300n\305q\304t\264u\303v\301w\302x\263~\025:\
420 	:kD=\177:kH=\E[Y:kN=\E[U:kP=\E[V:kh=\E[H:\
421 	:k1=\EOP:k2=\EOQ:k3=\EOR:k4=\EOS:k5=\EOT:\
422 	:k6=\EOU:k7=\EOV:k8=\EOW:k9=\EOX:k0=\EOY:\
423 	:tc=vt-generic:
424 
425 	`;
426 } else {
427 	enum UseVtSequences = false;
428 }
429 
430 /// A modifier for [Color]
431 enum Bright = 0x08;
432 
433 /// Defines the list of standard colors understood by Terminal.
434 /// See also: [Bright]
435 enum Color : ushort {
436 	black = 0, /// .
437 	red = RED_BIT, /// .
438 	green = GREEN_BIT, /// .
439 	yellow = red | green, /// .
440 	blue = BLUE_BIT, /// .
441 	magenta = red | blue, /// .
442 	cyan = blue | green, /// .
443 	white = red | green | blue, /// .
444 	DEFAULT = 256,
445 }
446 
447 /// When capturing input, what events are you interested in?
448 ///
449 /// Note: these flags can be OR'd together to select more than one option at a time.
450 ///
451 /// Ctrl+C and other keyboard input is always captured, though it may be line buffered if you don't use raw.
452 /// 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.
453 enum ConsoleInputFlags {
454 	raw = 0, /// raw input returns keystrokes immediately, without line buffering
455 	echo = 1, /// do you want to automatically echo input back to the user?
456 	mouse = 2, /// capture mouse events
457 	paste = 4, /// capture paste events (note: without this, paste can come through as keystrokes)
458 	size = 8, /// window resize events
459 
460 	releasedKeys = 64, /// key release events. Not reliable on Posix.
461 
462 	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.
463 	allInputEventsWithRelease = allInputEvents|releasedKeys, /// subscribe to all input events, including (unreliable on Posix) key release events.
464 
465 	noEolWrap = 128,
466 }
467 
468 /// Defines how terminal output should be handled.
469 enum ConsoleOutputType {
470 	linear = 0, /// do you want output to work one line at a time?
471 	cellular = 1, /// or do you want access to the terminal screen as a grid of characters?
472 	//truncatedCellular = 3, /// cellular, but instead of wrapping output to the next line automatically, it will truncate at the edges
473 
474 	minimalProcessing = 255, /// do the least possible work, skips most construction and desturction tasks. Only use if you know what you're doing here
475 }
476 
477 alias ConsoleOutputMode = ConsoleOutputType;
478 
479 /// Some methods will try not to send unnecessary commands to the screen. You can override their judgement using a ForceOption parameter, if present
480 enum ForceOption {
481 	automatic = 0, /// automatically decide what to do (best, unless you know for sure it isn't right)
482 	neverSend = -1, /// never send the data. This will only update Terminal's internal state. Use with caution.
483 	alwaysSend = 1, /// always send the data, even if it doesn't seem necessary
484 }
485 
486 ///
487 enum TerminalCursor {
488 	DEFAULT = 0, ///
489 	insert = 1, ///
490 	block = 2 ///
491 }
492 
493 // we could do it with termcap too, getenv("TERMCAP") then split on : and replace \E with \033 and get the pieces
494 
495 /// Encapsulates the I/O capabilities of a terminal.
496 ///
497 /// Warning: do not write out escape sequences to the terminal. This won't work
498 /// on Windows and will confuse Terminal's internal state on Posix.
499 struct Terminal {
500 	///
501 	@disable this();
502 	@disable this(this);
503 	private ConsoleOutputType type;
504 
505 	version(TerminalDirectToEmulator) {
506 		private bool windowSizeChanged = false;
507 		private 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
508 		private bool hangedUp = false; /// similar to interrupted.
509 	}
510 
511 	private TerminalCursor currentCursor_;
512 	version(Windows) private CONSOLE_CURSOR_INFO originalCursorInfo;
513 
514 	/++
515 		Changes the current cursor.
516 	+/
517 	void cursor(TerminalCursor what, ForceOption force = ForceOption.automatic) {
518 		if(force == ForceOption.neverSend) {
519 			currentCursor_ = what;
520 			return;
521 		} else {
522 			if(what != currentCursor_ || force == ForceOption.alwaysSend) {
523 				currentCursor_ = what;
524 				version(Win32Console) {
525 					final switch(what) {
526 						case TerminalCursor.DEFAULT:
527 							SetConsoleCursorInfo(hConsole, &originalCursorInfo);
528 						break;
529 						case TerminalCursor.insert:
530 						case TerminalCursor.block:
531 							CONSOLE_CURSOR_INFO info;
532 							GetConsoleCursorInfo(hConsole, &info);
533 							info.dwSize = what == TerminalCursor.insert ? 1 : 100;
534 							SetConsoleCursorInfo(hConsole, &info);
535 						break;
536 					}
537 				} else {
538 					final switch(what) {
539 						case TerminalCursor.DEFAULT:
540 							if(terminalInFamily("linux"))
541 								writeStringRaw("\033[?0c");
542 							else
543 								writeStringRaw("\033[0 q");
544 						break;
545 						case TerminalCursor.insert:
546 							if(terminalInFamily("linux"))
547 								writeStringRaw("\033[?2c");
548 							else if(terminalInFamily("xterm"))
549 								writeStringRaw("\033[6 q");
550 							else
551 								writeStringRaw("\033[4 q");
552 						break;
553 						case TerminalCursor.block:
554 							if(terminalInFamily("linux"))
555 								writeStringRaw("\033[?6c");
556 							else
557 								writeStringRaw("\033[2 q");
558 						break;
559 					}
560 				}
561 			}
562 		}
563 	}
564 
565 	/++
566 		Terminal is only valid to use on an actual console device or terminal
567 		handle. You should not attempt to construct a Terminal instance if this
568 		returns false. Real time input is similarly impossible if `!stdinIsTerminal`.
569 	+/
570 	static bool stdoutIsTerminal() {
571 		version(TerminalDirectToEmulator) {
572 			version(Windows) {
573 				// if it is null, it was a gui subsystem exe. But otherwise, it
574 				// might be explicitly redirected and we should respect that for
575 				// compatibility with normal console expectations (even though like
576 				// we COULD pop up a gui and do both, really that isn't the normal
577 				// use of this library so don't wanna go too nuts)
578 				auto hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
579 				return hConsole is null || GetFileType(hConsole) == FILE_TYPE_CHAR;
580 			} else version(Posix) {
581 				// same as normal here since thee is no gui subsystem really
582 				import core.sys.posix.unistd;
583 				return cast(bool) isatty(1);
584 			} else static assert(0);
585 		} else version(Posix) {
586 			import core.sys.posix.unistd;
587 			return cast(bool) isatty(1);
588 		} else version(Win32Console) {
589 			auto hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
590 			return GetFileType(hConsole) == FILE_TYPE_CHAR;
591 			/+
592 			auto hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
593 			CONSOLE_SCREEN_BUFFER_INFO originalSbi;
594 			if(GetConsoleScreenBufferInfo(hConsole, &originalSbi) == 0)
595 				return false;
596 			else
597 				return true;
598 			+/
599 		} else static assert(0);
600 	}
601 
602 	///
603 	static bool stdinIsTerminal() {
604 		version(TerminalDirectToEmulator) {
605 			version(Windows) {
606 				auto hConsole = GetStdHandle(STD_INPUT_HANDLE);
607 				return hConsole is null || GetFileType(hConsole) == FILE_TYPE_CHAR;
608 			} else version(Posix) {
609 				// same as normal here since thee is no gui subsystem really
610 				import core.sys.posix.unistd;
611 				return cast(bool) isatty(0);
612 			} else static assert(0);
613 		} else version(Posix) {
614 			import core.sys.posix.unistd;
615 			return cast(bool) isatty(0);
616 		} else version(Win32Console) {
617 			auto hConsole = GetStdHandle(STD_INPUT_HANDLE);
618 			return GetFileType(hConsole) == FILE_TYPE_CHAR;
619 		} else static assert(0);
620 	}
621 
622 	version(Posix) {
623 		private int fdOut;
624 		private int fdIn;
625 		private int[] delegate() getSizeOverride;
626 		void delegate(in void[]) _writeDelegate; // used to override the unix write() system call, set it magically
627 	}
628 
629 	bool terminalInFamily(string[] terms...) {
630 		import std.process;
631 		import std..string;
632 		version(TerminalDirectToEmulator)
633 			auto term = "xterm";
634 		else
635 			auto term = environment.get("TERM");
636 		foreach(t; terms)
637 			if(indexOf(term, t) != -1)
638 				return true;
639 
640 		return false;
641 	}
642 
643 	version(Posix) {
644 		// This is a filthy hack because Terminal.app and OS X are garbage who don't
645 		// work the way they're advertised. I just have to best-guess hack and hope it
646 		// doesn't break anything else. (If you know a better way, let me know!)
647 		bool isMacTerminal() {
648 			// it gives 1,2 in getTerminalCapabilities...
649 			// FIXME
650 			import std.process;
651 			import std..string;
652 			auto term = environment.get("TERM");
653 			return term == "xterm-256color";
654 		}
655 	} else
656 		bool isMacTerminal() { return false; }
657 
658 	static string[string] termcapDatabase;
659 	static void readTermcapFile(bool useBuiltinTermcap = false) {
660 		import std.file;
661 		import std.stdio;
662 		import std..string;
663 
664 		//if(!exists("/etc/termcap"))
665 			useBuiltinTermcap = true;
666 
667 		string current;
668 
669 		void commitCurrentEntry() {
670 			if(current is null)
671 				return;
672 
673 			string names = current;
674 			auto idx = indexOf(names, ":");
675 			if(idx != -1)
676 				names = names[0 .. idx];
677 
678 			foreach(name; split(names, "|"))
679 				termcapDatabase[name] = current;
680 
681 			current = null;
682 		}
683 
684 		void handleTermcapLine(in char[] line) {
685 			if(line.length == 0) { // blank
686 				commitCurrentEntry();
687 				return; // continue
688 			}
689 			if(line[0] == '#') // comment
690 				return; // continue
691 			size_t termination = line.length;
692 			if(line[$-1] == '\\')
693 				termination--; // cut off the \\
694 			current ~= strip(line[0 .. termination]);
695 			// termcap entries must be on one logical line, so if it isn't continued, we know we're done
696 			if(line[$-1] != '\\')
697 				commitCurrentEntry();
698 		}
699 
700 		if(useBuiltinTermcap) {
701 			version(VtEscapeCodes)
702 			foreach(line; splitLines(builtinTermcap)) {
703 				handleTermcapLine(line);
704 			}
705 		} else {
706 			foreach(line; File("/etc/termcap").byLine()) {
707 				handleTermcapLine(line);
708 			}
709 		}
710 	}
711 
712 	static string getTermcapDatabase(string terminal) {
713 		import std..string;
714 
715 		if(termcapDatabase is null)
716 			readTermcapFile();
717 
718 		auto data = terminal in termcapDatabase;
719 		if(data is null)
720 			return null;
721 
722 		auto tc = *data;
723 		auto more = indexOf(tc, ":tc=");
724 		if(more != -1) {
725 			auto tcKey = tc[more + ":tc=".length .. $];
726 			auto end = indexOf(tcKey, ":");
727 			if(end != -1)
728 				tcKey = tcKey[0 .. end];
729 			tc = getTermcapDatabase(tcKey) ~ tc;
730 		}
731 
732 		return tc;
733 	}
734 
735 	string[string] termcap;
736 	void readTermcap(string t = null) {
737 		version(TerminalDirectToEmulator)
738 			t = "xterm";
739 		import std.process;
740 		import std..string;
741 		import std.array;
742 
743 		string termcapData = environment.get("TERMCAP");
744 		if(termcapData.length == 0) {
745 			if(t is null) {
746 				t = environment.get("TERM");
747 			}
748 
749 			// loosen the check so any xterm variety gets
750 			// the same termcap. odds are this is right
751 			// almost always
752 			if(t.indexOf("xterm") != -1)
753 				t = "xterm";
754 			if(t.indexOf("putty") != -1)
755 				t = "xterm";
756 			if(t.indexOf("tmux") != -1)
757 				t = "tmux";
758 			if(t.indexOf("screen") != -1)
759 				t = "screen";
760 
761 			termcapData = getTermcapDatabase(t);
762 		}
763 
764 		auto e = replace(termcapData, "\\\n", "\n");
765 		termcap = null;
766 
767 		foreach(part; split(e, ":")) {
768 			// FIXME: handle numeric things too
769 
770 			auto things = split(part, "=");
771 			if(things.length)
772 				termcap[things[0]] =
773 					things.length > 1 ? things[1] : null;
774 		}
775 	}
776 
777 	string findSequenceInTermcap(in char[] sequenceIn) {
778 		char[10] sequenceBuffer;
779 		char[] sequence;
780 		if(sequenceIn.length > 0 && sequenceIn[0] == '\033') {
781 			if(!(sequenceIn.length < sequenceBuffer.length - 1))
782 				return null;
783 			sequenceBuffer[1 .. sequenceIn.length + 1] = sequenceIn[];
784 			sequenceBuffer[0] = '\\';
785 			sequenceBuffer[1] = 'E';
786 			sequence = sequenceBuffer[0 .. sequenceIn.length + 1];
787 		} else {
788 			sequence = sequenceBuffer[1 .. sequenceIn.length + 1];
789 		}
790 
791 		import std.array;
792 		foreach(k, v; termcap)
793 			if(v == sequence)
794 				return k;
795 		return null;
796 	}
797 
798 	string getTermcap(string key) {
799 		auto k = key in termcap;
800 		if(k !is null) return *k;
801 		return null;
802 	}
803 
804 	// Looks up a termcap item and tries to execute it. Returns false on failure
805 	bool doTermcap(T...)(string key, T t) {
806 		import std.conv;
807 		auto fs = getTermcap(key);
808 		if(fs is null)
809 			return false;
810 
811 		int swapNextTwo = 0;
812 
813 		R getArg(R)(int idx) {
814 			if(swapNextTwo == 2) {
815 				idx ++;
816 				swapNextTwo--;
817 			} else if(swapNextTwo == 1) {
818 				idx --;
819 				swapNextTwo--;
820 			}
821 
822 			foreach(i, arg; t) {
823 				if(i == idx)
824 					return to!R(arg);
825 			}
826 			assert(0, to!string(idx) ~ " is out of bounds working " ~ fs);
827 		}
828 
829 		char[256] buffer;
830 		int bufferPos = 0;
831 
832 		void addChar(char c) {
833 			import std.exception;
834 			enforce(bufferPos < buffer.length);
835 			buffer[bufferPos++] = c;
836 		}
837 
838 		void addString(in char[] c) {
839 			import std.exception;
840 			enforce(bufferPos + c.length < buffer.length);
841 			buffer[bufferPos .. bufferPos + c.length] = c[];
842 			bufferPos += c.length;
843 		}
844 
845 		void addInt(int c, int minSize) {
846 			import std..string;
847 			auto str = format("%0"~(minSize ? to!string(minSize) : "")~"d", c);
848 			addString(str);
849 		}
850 
851 		bool inPercent;
852 		int argPosition = 0;
853 		int incrementParams = 0;
854 		bool skipNext;
855 		bool nextIsChar;
856 		bool inBackslash;
857 
858 		foreach(char c; fs) {
859 			if(inBackslash) {
860 				if(c == 'E')
861 					addChar('\033');
862 				else
863 					addChar(c);
864 				inBackslash = false;
865 			} else if(nextIsChar) {
866 				if(skipNext)
867 					skipNext = false;
868 				else
869 					addChar(cast(char) (c + getArg!int(argPosition) + (incrementParams ? 1 : 0)));
870 				if(incrementParams) incrementParams--;
871 				argPosition++;
872 				inPercent = false;
873 			} else if(inPercent) {
874 				switch(c) {
875 					case '%':
876 						addChar('%');
877 						inPercent = false;
878 					break;
879 					case '2':
880 					case '3':
881 					case 'd':
882 						if(skipNext)
883 							skipNext = false;
884 						else
885 							addInt(getArg!int(argPosition) + (incrementParams ? 1 : 0),
886 								c == 'd' ? 0 : (c - '0')
887 							);
888 						if(incrementParams) incrementParams--;
889 						argPosition++;
890 						inPercent = false;
891 					break;
892 					case '.':
893 						if(skipNext)
894 							skipNext = false;
895 						else
896 							addChar(cast(char) (getArg!int(argPosition) + (incrementParams ? 1 : 0)));
897 						if(incrementParams) incrementParams--;
898 						argPosition++;
899 					break;
900 					case '+':
901 						nextIsChar = true;
902 						inPercent = false;
903 					break;
904 					case 'i':
905 						incrementParams = 2;
906 						inPercent = false;
907 					break;
908 					case 's':
909 						skipNext = true;
910 						inPercent = false;
911 					break;
912 					case 'b':
913 						argPosition--;
914 						inPercent = false;
915 					break;
916 					case 'r':
917 						swapNextTwo = 2;
918 						inPercent = false;
919 					break;
920 					// FIXME: there's more
921 					// http://www.gnu.org/software/termutils/manual/termcap-1.3/html_mono/termcap.html
922 
923 					default:
924 						assert(0, "not supported " ~ c);
925 				}
926 			} else {
927 				if(c == '%')
928 					inPercent = true;
929 				else if(c == '\\')
930 					inBackslash = true;
931 				else
932 					addChar(c);
933 			}
934 		}
935 
936 		writeStringRaw(buffer[0 .. bufferPos]);
937 		return true;
938 	}
939 
940 	uint tcaps;
941 
942 	bool inlineImagesSupported() {
943 		return (tcaps & TerminalCapabilities.arsdImage) ? true : false;
944 	}
945 	bool clipboardSupported() {
946 		version(Win32Console) return true;
947 		else return (tcaps & TerminalCapabilities.arsdImage) ? true : false;
948 	}
949 
950 	// only supported on my custom terminal emulator. guarded behind if(inlineImagesSupported)
951 	// though that isn't even 100% accurate but meh
952 	void changeWindowIcon()(string filename) {
953 		if(inlineImagesSupported()) {
954 		        import arsd.png;
955 			auto image = readPng(filename);
956 			auto ii = cast(IndexedImage) image;
957 			assert(ii !is null);
958 
959 			// copy/pasted from my terminalemulator.d
960 			string encodeSmallTextImage(IndexedImage ii) {
961 				char encodeNumeric(int c) {
962 					if(c < 10)
963 						return cast(char)(c + '0');
964 					if(c < 10 + 26)
965 						return cast(char)(c - 10 + 'a');
966 					assert(0);
967 				}
968 
969 				string s;
970 				s ~= encodeNumeric(ii.width);
971 				s ~= encodeNumeric(ii.height);
972 
973 				foreach(entry; ii.palette)
974 					s ~= entry.toRgbaHexString();
975 				s ~= "Z";
976 
977 				ubyte rleByte;
978 				int rleCount;
979 
980 				void rleCommit() {
981 					if(rleByte >= 26)
982 						assert(0); // too many colors for us to handle
983 					if(rleCount == 0)
984 						goto finish;
985 					if(rleCount == 1) {
986 						s ~= rleByte + 'a';
987 						goto finish;
988 					}
989 
990 					import std.conv;
991 					s ~= to!string(rleCount);
992 					s ~= rleByte + 'a';
993 
994 					finish:
995 						rleByte = 0;
996 						rleCount = 0;
997 				}
998 
999 				foreach(b; ii.data) {
1000 					if(b == rleByte)
1001 						rleCount++;
1002 					else {
1003 						rleCommit();
1004 						rleByte = b;
1005 						rleCount = 1;
1006 					}
1007 				}
1008 
1009 				rleCommit();
1010 
1011 				return s;
1012 			}
1013 
1014 			this.writeStringRaw("\033]5000;"~encodeSmallTextImage(ii)~"\007");
1015 		}
1016 	}
1017 
1018 	// dependent on tcaps...
1019 	void displayInlineImage()(ubyte[] imageData) {
1020 		if(inlineImagesSupported) {
1021 			import std.base64;
1022 
1023 			// I might change this protocol later!
1024 			enum extensionMagicIdentifier = "ARSD Terminal Emulator binary extension data follows:";
1025 
1026 			this.writeStringRaw("\000");
1027 			this.writeStringRaw(extensionMagicIdentifier);
1028 			this.writeStringRaw(Base64.encode(imageData));
1029 			this.writeStringRaw("\000");
1030 		}
1031 	}
1032 
1033 	void demandUserAttention() {
1034 		if(UseVtSequences) {
1035 			if(!terminalInFamily("linux"))
1036 				writeStringRaw("\033]5001;1\007");
1037 		}
1038 	}
1039 
1040 	void requestCopyToClipboard(string text) {
1041 		if(clipboardSupported) {
1042 			import std.base64;
1043 			writeStringRaw("\033]52;c;"~Base64.encode(cast(ubyte[])text)~"\007");
1044 		}
1045 	}
1046 
1047 	void requestCopyToPrimary(string text) {
1048 		if(clipboardSupported) {
1049 			import std.base64;
1050 			writeStringRaw("\033]52;p;"~Base64.encode(cast(ubyte[])text)~"\007");
1051 		}
1052 	}
1053 
1054 	bool hasDefaultDarkBackground() {
1055 		version(Win32Console) {
1056 			return !(defaultBackgroundColor & 0xf);
1057 		} else version(TerminalDirectToEmulator) {
1058 			return integratedTerminalEmulatorConfiguration.defaultBackground.g < 100;
1059 		} else {
1060 			// FIXME: there is probably a better way to do this
1061 			// but like idk how reliable it is.
1062 			if(terminalInFamily("linux"))
1063 				return true;
1064 			else
1065 				return false;
1066 		}
1067 	}
1068 
1069 	version(TerminalDirectToEmulator) {
1070 		TerminalEmulatorWidget tew;
1071 		private __gshared Window mainWindow;
1072 		import core.thread;
1073 		version(Posix)
1074 			ThreadID threadId;
1075 		else version(Windows)
1076 			HANDLE threadId;
1077 		private __gshared Thread guiThread;
1078 
1079 		private static class NewTerminalEvent {
1080 			Terminal* t;
1081 			this(Terminal* t) {
1082 				this.t = t;
1083 			}
1084 		}
1085 	}
1086 
1087 	version(TerminalDirectToEmulator)
1088 	/++
1089 	+/
1090 	this(ConsoleOutputType type) {
1091 		this.type = type;
1092 
1093 		if(type == ConsoleOutputType.minimalProcessing) {
1094 			readTermcap("xterm");
1095 			_suppressDestruction = true;
1096 			return;
1097 		}
1098 
1099 		tcaps = uint.max; // all capabilities
1100 		import core.thread;
1101 
1102 		version(Posix)
1103 			threadId = Thread.getThis.id;
1104 		else version(Windows)
1105 			threadId = GetCurrentThread();
1106 
1107 		if(guiThread is null) {
1108 			guiThread = new Thread( {
1109 				auto window = new TerminalEmulatorWindow(&this, null);
1110 				mainWindow = window;
1111 				mainWindow.win.addEventListener((NewTerminalEvent t) {
1112 					auto nw = new TerminalEmulatorWindow(t.t, null);
1113 					t.t.tew = nw.tew;
1114 					t.t = null;
1115 					nw.show();
1116 				});
1117 				tew = window.tew;
1118 				//try
1119 					window.loop();
1120 				/*
1121 				catch(Throwable t) {
1122 					import std.stdio;
1123 					stdout.writeln(t);
1124 					stdout.flush();
1125 				}
1126 				*/
1127 			});
1128 			guiThread.start();
1129 			guiThread.priority = Thread.PRIORITY_MAX; // gui thread needs responsiveness
1130 		} else {
1131 			// FIXME: 64 bit builds on linux segfault with multiple terminals
1132 			// so that isn't really supported as of yet.
1133 			while(cast(shared) mainWindow is null) {
1134 				import core.thread;
1135 				Thread.sleep(5.msecs);
1136 			}
1137 			mainWindow.win.postEvent(new NewTerminalEvent(&this));
1138 		}
1139 
1140 		// need to wait until it is properly initialized
1141 		while(cast(shared) tew is null) {
1142 			import core.thread;
1143 			Thread.sleep(5.msecs);
1144 		}
1145 
1146 		initializeVt();
1147 
1148 	}
1149 	else
1150 
1151 	version(Posix)
1152 	/**
1153 	 * Constructs an instance of Terminal representing the capabilities of
1154 	 * the current terminal.
1155 	 *
1156 	 * While it is possible to override the stdin+stdout file descriptors, remember
1157 	 * that is not portable across platforms and be sure you know what you're doing.
1158 	 *
1159 	 * ditto on getSizeOverride. That's there so you can do something instead of ioctl.
1160 	 */
1161 	this(ConsoleOutputType type, int fdIn = 0, int fdOut = 1, int[] delegate() getSizeOverride = null) {
1162 		this.fdIn = fdIn;
1163 		this.fdOut = fdOut;
1164 		this.getSizeOverride = getSizeOverride;
1165 		this.type = type;
1166 
1167 		if(type == ConsoleOutputType.minimalProcessing) {
1168 			readTermcap();
1169 			_suppressDestruction = true;
1170 			return;
1171 		}
1172 
1173 		tcaps = getTerminalCapabilities(fdIn, fdOut);
1174 		//writeln(tcaps);
1175 
1176 		initializeVt();
1177 	}
1178 
1179 	void initializeVt() {
1180 		readTermcap();
1181 
1182 		if(type == ConsoleOutputType.cellular) {
1183 			doTermcap("ti");
1184 			clear();
1185 			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
1186 		}
1187 
1188 		if(terminalInFamily("xterm", "rxvt", "screen", "tmux")) {
1189 			writeStringRaw("\033[22;0t"); // save window title on a stack (support seems spotty, but it doesn't hurt to have it)
1190 		}
1191 
1192 	}
1193 
1194 	// EXPERIMENTAL do not use yet
1195 	Terminal alternateScreen() {
1196 		assert(this.type != ConsoleOutputType.cellular);
1197 
1198 		this.flush();
1199 		return Terminal(ConsoleOutputType.cellular);
1200 	}
1201 
1202 	version(Win32Console) {
1203 		HANDLE hConsole;
1204 		CONSOLE_SCREEN_BUFFER_INFO originalSbi;
1205 	}
1206 
1207 	version(Win32Console)
1208 	/// ditto
1209 	this(ConsoleOutputType type) {
1210 		if(UseVtSequences) {
1211 			hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
1212 			initializeVt();
1213 		} else {
1214 			if(type == ConsoleOutputType.cellular) {
1215 				hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, null, CONSOLE_TEXTMODE_BUFFER, null);
1216 				if(hConsole == INVALID_HANDLE_VALUE) {
1217 					import std.conv;
1218 					throw new Exception(to!string(GetLastError()));
1219 				}
1220 
1221 				SetConsoleActiveScreenBuffer(hConsole);
1222 				/*
1223 	http://msdn.microsoft.com/en-us/library/windows/desktop/ms686125%28v=vs.85%29.aspx
1224 	http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.aspx
1225 				*/
1226 				COORD size;
1227 				/*
1228 				CONSOLE_SCREEN_BUFFER_INFO sbi;
1229 				GetConsoleScreenBufferInfo(hConsole, &sbi);
1230 				size.X = cast(short) GetSystemMetrics(SM_CXMIN);
1231 				size.Y = cast(short) GetSystemMetrics(SM_CYMIN);
1232 				*/
1233 
1234 				// FIXME: this sucks, maybe i should just revert it. but there shouldn't be scrollbars in cellular mode
1235 				//size.X = 80;
1236 				//size.Y = 24;
1237 				//SetConsoleScreenBufferSize(hConsole, size);
1238 
1239 				GetConsoleCursorInfo(hConsole, &originalCursorInfo);
1240 
1241 				clear();
1242 			} else {
1243 				hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
1244 			}
1245 
1246 			if(GetConsoleScreenBufferInfo(hConsole, &originalSbi) == 0)
1247 				throw new Exception("not a user-interactive terminal");
1248 
1249 			defaultForegroundColor = cast(Color) (originalSbi.wAttributes & 0x0f);
1250 			defaultBackgroundColor = cast(Color) ((originalSbi.wAttributes >> 4) & 0x0f);
1251 
1252 			// this is unnecessary since I use the W versions of other functions
1253 			// and can cause weird font bugs, so I'm commenting unless some other
1254 			// need comes up.
1255 			/*
1256 			oldCp = GetConsoleOutputCP();
1257 			SetConsoleOutputCP(65001); // UTF-8
1258 
1259 			oldCpIn = GetConsoleCP();
1260 			SetConsoleCP(65001); // UTF-8
1261 			*/
1262 		}
1263 	}
1264 
1265 	version(Win32Console) {
1266 		private Color defaultBackgroundColor = Color.black;
1267 		private Color defaultForegroundColor = Color.white;
1268 		UINT oldCp;
1269 		UINT oldCpIn;
1270 	}
1271 
1272 	// 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...
1273 	bool _suppressDestruction;
1274 
1275 	~this() {
1276 		if(_suppressDestruction) {
1277 			flush();
1278 			return;
1279 		}
1280 
1281 		if(UseVtSequences) {
1282 			if(type == ConsoleOutputType.cellular) {
1283 				doTermcap("te");
1284 			}
1285 			version(TerminalDirectToEmulator) {
1286 				writeln("\n\n<exited>");
1287 				setTitle(tew.terminalEmulator.currentTitle ~ " <exited>");
1288 				tew.term = null;
1289 
1290 				if(integratedTerminalEmulatorConfiguration.closeOnExit)
1291 					tew.parentWindow.close();
1292 			} else
1293 			if(terminalInFamily("xterm", "rxvt", "screen", "tmux")) {
1294 				writeStringRaw("\033[23;0t"); // restore window title from the stack
1295 			}
1296 			cursor = TerminalCursor.DEFAULT;
1297 			showCursor();
1298 			reset();
1299 			flush();
1300 
1301 			if(lineGetter !is null)
1302 				lineGetter.dispose();
1303 		} else version(Win32Console) {
1304 			flush(); // make sure user data is all flushed before resetting
1305 			reset();
1306 			showCursor();
1307 
1308 			if(lineGetter !is null)
1309 				lineGetter.dispose();
1310 
1311 
1312 			SetConsoleOutputCP(oldCp);
1313 			SetConsoleCP(oldCpIn);
1314 
1315 			auto stdo = GetStdHandle(STD_OUTPUT_HANDLE);
1316 			SetConsoleActiveScreenBuffer(stdo);
1317 			if(hConsole !is stdo)
1318 				CloseHandle(hConsole);
1319 		}
1320 	}
1321 
1322 	// lazily initialized and preserved between calls to getline for a bit of efficiency (only a bit)
1323 	// and some history storage.
1324 	LineGetter lineGetter;
1325 
1326 	int _currentForeground = Color.DEFAULT;
1327 	int _currentBackground = Color.DEFAULT;
1328 	RGB _currentForegroundRGB;
1329 	RGB _currentBackgroundRGB;
1330 	bool reverseVideo = false;
1331 
1332 	/++
1333 		Attempts to set color according to a 24 bit value (r, g, b, each >= 0 and < 256).
1334 
1335 
1336 		This is not supported on all terminals. It will attempt to fall back to a 256-color
1337 		or 8-color palette in those cases automatically.
1338 
1339 		Returns: true if it believes it was successful (note that it cannot be completely sure),
1340 		false if it had to use a fallback.
1341 	+/
1342 	bool setTrueColor(RGB foreground, RGB background, ForceOption force = ForceOption.automatic) {
1343 		if(force == ForceOption.neverSend) {
1344 			_currentForeground = -1;
1345 			_currentBackground = -1;
1346 			_currentForegroundRGB = foreground;
1347 			_currentBackgroundRGB = background;
1348 			return true;
1349 		}
1350 
1351 		if(force == ForceOption.automatic && _currentForeground == -1 && _currentBackground == -1 && (_currentForegroundRGB == foreground && _currentBackgroundRGB == background))
1352 			return true;
1353 
1354 		_currentForeground = -1;
1355 		_currentBackground = -1;
1356 		_currentForegroundRGB = foreground;
1357 		_currentBackgroundRGB = background;
1358 
1359 		version(Win32Console) {
1360 			flush();
1361 			ushort setTob = cast(ushort) approximate16Color(background);
1362 			ushort setTof = cast(ushort) approximate16Color(foreground);
1363 			SetConsoleTextAttribute(
1364 				hConsole,
1365 				cast(ushort)((setTob << 4) | setTof));
1366 			return false;
1367 		} else {
1368 			// FIXME: if the terminal reliably does support 24 bit color, use it
1369 			// instead of the round off. But idk how to detect that yet...
1370 
1371 			// fallback to 16 color for term that i know don't take it well
1372 			import std.process;
1373 			import std..string;
1374 			version(TerminalDirectToEmulator) {} else
1375 			if(environment.get("TERM") == "rxvt" || environment.get("TERM") == "linux") {
1376 				// not likely supported, use 16 color fallback
1377 				auto setTof = approximate16Color(foreground);
1378 				auto setTob = approximate16Color(background);
1379 
1380 				writeStringRaw(format("\033[%dm\033[3%dm\033[4%dm",
1381 					(setTof & Bright) ? 1 : 0,
1382 					cast(int) (setTof & ~Bright),
1383 					cast(int) (setTob & ~Bright)
1384 				));
1385 
1386 				return false;
1387 			}
1388 
1389 			// otherwise, assume it is probably supported and give it a try
1390 			writeStringRaw(format("\033[38;5;%dm\033[48;5;%dm",
1391 				colorToXTermPaletteIndex(foreground),
1392 				colorToXTermPaletteIndex(background)
1393 			));
1394 
1395 			/+ // this is the full 24 bit color sequence
1396 			writeStringRaw(format("\033[38;2;%d;%d;%dm", foreground.r, foreground.g, foreground.b));
1397 			writeStringRaw(format("\033[48;2;%d;%d;%dm", background.r, background.g, background.b));
1398 			+/
1399 
1400 			return true;
1401 		}
1402 	}
1403 
1404 	/// Changes the current color. See enum Color for the values.
1405 	void color(int foreground, int background, ForceOption force = ForceOption.automatic, bool reverseVideo = false) {
1406 		if(force != ForceOption.neverSend) {
1407 			version(Win32Console) {
1408 				// assuming a dark background on windows, so LowContrast == dark which means the bit is NOT set on hardware
1409 				/*
1410 				foreground ^= LowContrast;
1411 				background ^= LowContrast;
1412 				*/
1413 
1414 				ushort setTof = cast(ushort) foreground;
1415 				ushort setTob = cast(ushort) background;
1416 
1417 				// this isn't necessarily right but meh
1418 				if(background == Color.DEFAULT)
1419 					setTob = defaultBackgroundColor;
1420 				if(foreground == Color.DEFAULT)
1421 					setTof = defaultForegroundColor;
1422 
1423 				if(force == ForceOption.alwaysSend || reverseVideo != this.reverseVideo || foreground != _currentForeground || background != _currentBackground) {
1424 					flush(); // if we don't do this now, the buffering can screw up the colors...
1425 					if(reverseVideo) {
1426 						if(background == Color.DEFAULT)
1427 							setTof = defaultBackgroundColor;
1428 						else
1429 							setTof = cast(ushort) background | (foreground & Bright);
1430 
1431 						if(background == Color.DEFAULT)
1432 							setTob = defaultForegroundColor;
1433 						else
1434 							setTob = cast(ushort) (foreground & ~Bright);
1435 					}
1436 					SetConsoleTextAttribute(
1437 						hConsole,
1438 						cast(ushort)((setTob << 4) | setTof));
1439 				}
1440 			} else {
1441 				import std.process;
1442 				// I started using this envvar for my text editor, but now use it elsewhere too
1443 				// if we aren't set to dark, assume light
1444 				/*
1445 				if(getenv("ELVISBG") == "dark") {
1446 					// LowContrast on dark bg menas
1447 				} else {
1448 					foreground ^= LowContrast;
1449 					background ^= LowContrast;
1450 				}
1451 				*/
1452 
1453 				ushort setTof = cast(ushort) foreground & ~Bright;
1454 				ushort setTob = cast(ushort) background & ~Bright;
1455 
1456 				if(foreground & Color.DEFAULT)
1457 					setTof = 9; // ansi sequence for reset
1458 				if(background == Color.DEFAULT)
1459 					setTob = 9;
1460 
1461 				import std..string;
1462 
1463 				if(force == ForceOption.alwaysSend || reverseVideo != this.reverseVideo || foreground != _currentForeground || background != _currentBackground) {
1464 					writeStringRaw(format("\033[%dm\033[3%dm\033[4%dm\033[%dm",
1465 						(foreground != Color.DEFAULT && (foreground & Bright)) ? 1 : 0,
1466 						cast(int) setTof,
1467 						cast(int) setTob,
1468 						reverseVideo ? 7 : 27
1469 					));
1470 				}
1471 			}
1472 		}
1473 
1474 		_currentForeground = foreground;
1475 		_currentBackground = background;
1476 		this.reverseVideo = reverseVideo;
1477 	}
1478 
1479 	private bool _underlined = false;
1480 
1481 	/++
1482 		Outputs a hyperlink to my custom terminal (v0.0.7 or later) or to version
1483 		`TerminalDirectToEmulator`.  The way it works is a bit strange...
1484 
1485 
1486 		If using a terminal that supports it, it outputs the given text with the
1487 		given identifier attached (one bit of identifier per grapheme of text!). When
1488 		the user clicks on it, it will send a [LinkEvent] with the text and the identifier
1489 		for you to respond, if in real-time input mode, or a simple paste event with the
1490 		text if not (you will not be able to distinguish this from a user pasting the
1491 		same text).
1492 
1493 		If the user's terminal does not support my feature, it writes plain text instead.
1494 
1495 		It is important that you make sure your program still works even if the hyperlinks
1496 		never work - ideally, make them out of text the user can type manually or copy/paste
1497 		into your command line somehow too.
1498 
1499 		Hyperlinks may not work correctly after your program exits or if you are capturing
1500 		mouse input (the user will have to hold shift in that case). It is really designed
1501 		for linear mode with direct to emulator mode. If you are using cellular mode with
1502 		full input capturing, you should manage the clicks yourself.
1503 
1504 		Similarly, if it horizontally scrolls off the screen, it can be corrupted since it
1505 		packs your text and identifier into free bits in the screen buffer itself. I may be
1506 		able to fix that later.
1507 
1508 		Params:
1509 			text = text displayed in the terminal
1510 			identifier = an additional number attached to the text and returned to you in a [LinkEvent]
1511 			autoStyle = set to `false` to suppress the automatic color and underlining of the text.
1512 
1513 		Bugs:
1514 			there's no keyboard interaction with it at all right now. i might make the terminal
1515 			emulator offer the ids or something through a hold ctrl or something interface. idk.
1516 			or tap ctrl twice to turn that on.
1517 
1518 		History:
1519 			Added March 18, 2020
1520 	+/
1521 	void hyperlink(string text, ushort identifier = 0, bool autoStyle = true) {
1522 		if((tcaps & TerminalCapabilities.arsdHyperlinks)) {
1523 			bool previouslyUnderlined = _underlined;
1524 			int fg = _currentForeground, bg = _currentBackground;
1525 			if(autoStyle) {
1526 				color(Color.blue, Color.white);
1527 				underline = true;
1528 			}
1529 
1530 			import std.conv;
1531 			writeStringRaw("\033[?" ~ to!string(65536 + identifier) ~ "h");
1532 			write(text);
1533 			writeStringRaw("\033[?65536l");
1534 
1535 			if(autoStyle) {
1536 				underline = previouslyUnderlined;
1537 				color(fg, bg);
1538 			}
1539 		} else {
1540 			write(text); // graceful degrade  
1541 		}
1542 	}
1543 
1544 	/// Note: the Windows console does not support underlining
1545 	void underline(bool set, ForceOption force = ForceOption.automatic) {
1546 		if(set == _underlined && force != ForceOption.alwaysSend)
1547 			return;
1548 		if(UseVtSequences) {
1549 			if(set)
1550 				writeStringRaw("\033[4m");
1551 			else
1552 				writeStringRaw("\033[24m");
1553 		}
1554 		_underlined = set;
1555 	}
1556 	// FIXME: do I want to do bold and italic?
1557 
1558 	/// Returns the terminal to normal output colors
1559 	void reset() {
1560 		version(Win32Console)
1561 			SetConsoleTextAttribute(
1562 				hConsole,
1563 				originalSbi.wAttributes);
1564 		else
1565 			writeStringRaw("\033[0m");
1566 
1567 		_underlined = false;
1568 		_currentForeground = Color.DEFAULT;
1569 		_currentBackground = Color.DEFAULT;
1570 		reverseVideo = false;
1571 	}
1572 
1573 	// FIXME: add moveRelative
1574 
1575 	/// The current x position of the output cursor. 0 == leftmost column
1576 	@property int cursorX() {
1577 		return _cursorX;
1578 	}
1579 
1580 	/// The current y position of the output cursor. 0 == topmost row
1581 	@property int cursorY() {
1582 		return _cursorY;
1583 	}
1584 
1585 	private int _cursorX;
1586 	private int _cursorY;
1587 
1588 	/// 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
1589 	void moveTo(int x, int y, ForceOption force = ForceOption.automatic) {
1590 		if(force != ForceOption.neverSend && (force == ForceOption.alwaysSend || x != _cursorX || y != _cursorY)) {
1591 			executeAutoHideCursor();
1592 			if(UseVtSequences) {
1593 				doTermcap("cm", y, x);
1594 			} else version(Win32Console) {
1595 
1596 				flush(); // if we don't do this now, the buffering can screw up the position
1597 				COORD coord = {cast(short) x, cast(short) y};
1598 				SetConsoleCursorPosition(hConsole, coord);
1599 			}
1600 		}
1601 
1602 		_cursorX = x;
1603 		_cursorY = y;
1604 	}
1605 
1606 	/// shows the cursor
1607 	void showCursor() {
1608 		if(UseVtSequences)
1609 			doTermcap("ve");
1610 		else version(Win32Console) {
1611 			CONSOLE_CURSOR_INFO info;
1612 			GetConsoleCursorInfo(hConsole, &info);
1613 			info.bVisible = true;
1614 			SetConsoleCursorInfo(hConsole, &info);
1615 		}
1616 	}
1617 
1618 	/// hides the cursor
1619 	void hideCursor() {
1620 		if(UseVtSequences) {
1621 			doTermcap("vi");
1622 		} else version(Win32Console) {
1623 			CONSOLE_CURSOR_INFO info;
1624 			GetConsoleCursorInfo(hConsole, &info);
1625 			info.bVisible = false;
1626 			SetConsoleCursorInfo(hConsole, &info);
1627 		}
1628 
1629 	}
1630 
1631 	private bool autoHidingCursor;
1632 	private bool autoHiddenCursor;
1633 	// explicitly not publicly documented
1634 	// Sets the cursor to automatically insert a hide command at the front of the output buffer iff it is moved.
1635 	// Call autoShowCursor when you are done with the batch update.
1636 	void autoHideCursor() {
1637 		autoHidingCursor = true;
1638 	}
1639 
1640 	private void executeAutoHideCursor() {
1641 		if(autoHidingCursor) {
1642 			version(Win32Console)
1643 				hideCursor();
1644 			else if(UseVtSequences) {
1645 				// prepend the hide cursor command so it is the first thing flushed
1646 				writeBuffer = "\033[?25l" ~ writeBuffer;
1647 			}
1648 
1649 			autoHiddenCursor = true;
1650 			autoHidingCursor = false; // already been done, don't insert the command again
1651 		}
1652 	}
1653 
1654 	// explicitly not publicly documented
1655 	// Shows the cursor if it was automatically hidden by autoHideCursor and resets the internal auto hide state.
1656 	void autoShowCursor() {
1657 		if(autoHiddenCursor)
1658 			showCursor();
1659 
1660 		autoHidingCursor = false;
1661 		autoHiddenCursor = false;
1662 	}
1663 
1664 	/*
1665 	// alas this doesn't work due to a bunch of delegate context pointer and postblit problems
1666 	// instead of using: auto input = terminal.captureInput(flags)
1667 	// use: auto input = RealTimeConsoleInput(&terminal, flags);
1668 	/// Gets real time input, disabling line buffering
1669 	RealTimeConsoleInput captureInput(ConsoleInputFlags flags) {
1670 		return RealTimeConsoleInput(&this, flags);
1671 	}
1672 	*/
1673 
1674 	/// Changes the terminal's title
1675 	void setTitle(string t) {
1676 		version(Win32Console) {
1677 			wchar[256] buffer;
1678 			size_t bufferLength;
1679 			foreach(wchar ch; t)
1680 				if(bufferLength < buffer.length)
1681 					buffer[bufferLength++] = ch;
1682 			if(bufferLength < buffer.length)
1683 				buffer[bufferLength++] = 0;
1684 			else
1685 				buffer[$-1] = 0;
1686 			SetConsoleTitleW(buffer.ptr);
1687 		} else {
1688 			import std..string;
1689 			if(terminalInFamily("xterm", "rxvt", "screen", "tmux"))
1690 				writeStringRaw(format("\033]0;%s\007", t));
1691 		}
1692 	}
1693 
1694 	/// Flushes your updates to the terminal.
1695 	/// It is important to call this when you are finished writing for now if you are using the version=with_eventloop
1696 	void flush() {
1697 		if(writeBuffer.length == 0)
1698 			return;
1699 
1700 		version(TerminalDirectToEmulator) {
1701 			tew.sendRawInput(cast(ubyte[]) writeBuffer);
1702 			writeBuffer = null;
1703 		} else version(Posix) {
1704 			if(_writeDelegate !is null) {
1705 				_writeDelegate(writeBuffer);
1706 			} else {
1707 				ssize_t written;
1708 
1709 				while(writeBuffer.length) {
1710 					written = unix.write(this.fdOut, writeBuffer.ptr, writeBuffer.length);
1711 					if(written < 0)
1712 						throw new Exception("write failed for some reason");
1713 					writeBuffer = writeBuffer[written .. $];
1714 				}
1715 			}
1716 		} else version(Win32Console) {
1717 			import std.conv;
1718 			// FIXME: I'm not sure I'm actually happy with this allocation but
1719 			// it probably isn't a big deal. At least it has unicode support now.
1720 			wstring writeBufferw = to!wstring(writeBuffer);
1721 			while(writeBufferw.length) {
1722 				DWORD written;
1723 				WriteConsoleW(hConsole, writeBufferw.ptr, cast(DWORD)writeBufferw.length, &written, null);
1724 				writeBufferw = writeBufferw[written .. $];
1725 			}
1726 
1727 			writeBuffer = null;
1728 		}
1729 	}
1730 
1731 	int[] getSize() {
1732 		version(TerminalDirectToEmulator) {
1733 			return [tew.terminalEmulator.width, tew.terminalEmulator.height];
1734 		} else version(Windows) {
1735 			CONSOLE_SCREEN_BUFFER_INFO info;
1736 			GetConsoleScreenBufferInfo( hConsole, &info );
1737         
1738 			int cols, rows;
1739         
1740 			cols = (info.srWindow.Right - info.srWindow.Left + 1);
1741 			rows = (info.srWindow.Bottom - info.srWindow.Top + 1);
1742 
1743 			return [cols, rows];
1744 		} else {
1745 			if(getSizeOverride is null) {
1746 				winsize w;
1747 				ioctl(0, TIOCGWINSZ, &w);
1748 				return [w.ws_col, w.ws_row];
1749 			} else return getSizeOverride();
1750 		}
1751 	}
1752 
1753 	void updateSize() {
1754 		auto size = getSize();
1755 		_width = size[0];
1756 		_height = size[1];
1757 	}
1758 
1759 	private int _width;
1760 	private int _height;
1761 
1762 	/// The current width of the terminal (the number of columns)
1763 	@property int width() {
1764 		if(_width == 0 || _height == 0)
1765 			updateSize();
1766 		return _width;
1767 	}
1768 
1769 	/// The current height of the terminal (the number of rows)
1770 	@property int height() {
1771 		if(_width == 0 || _height == 0)
1772 			updateSize();
1773 		return _height;
1774 	}
1775 
1776 	/*
1777 	void write(T...)(T t) {
1778 		foreach(arg; t) {
1779 			writeStringRaw(to!string(arg));
1780 		}
1781 	}
1782 	*/
1783 
1784 	/// Writes to the terminal at the current cursor position.
1785 	void writef(T...)(string f, T t) {
1786 		import std..string;
1787 		writePrintableString(format(f, t));
1788 	}
1789 
1790 	/// ditto
1791 	void writefln(T...)(string f, T t) {
1792 		writef(f ~ "\n", t);
1793 	}
1794 
1795 	/// ditto
1796 	void write(T...)(T t) {
1797 		import std.conv;
1798 		string data;
1799 		foreach(arg; t) {
1800 			data ~= to!string(arg);
1801 		}
1802 
1803 		writePrintableString(data);
1804 	}
1805 
1806 	/// ditto
1807 	void writeln(T...)(T t) {
1808 		write(t, "\n");
1809 	}
1810 
1811 	/+
1812 	/// A combined moveTo and writef that puts the cursor back where it was before when it finishes the write.
1813 	/// Only works in cellular mode. 
1814 	/// 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)
1815 	void writefAt(T...)(int x, int y, string f, T t) {
1816 		import std.string;
1817 		auto toWrite = format(f, t);
1818 
1819 		auto oldX = _cursorX;
1820 		auto oldY = _cursorY;
1821 
1822 		writeAtWithoutReturn(x, y, toWrite);
1823 
1824 		moveTo(oldX, oldY);
1825 	}
1826 
1827 	void writeAtWithoutReturn(int x, int y, in char[] data) {
1828 		moveTo(x, y);
1829 		writeStringRaw(toWrite, ForceOption.alwaysSend);
1830 	}
1831 	+/
1832 
1833 	void writePrintableString(const(char)[] s, ForceOption force = ForceOption.automatic) {
1834 		// an escape character is going to mess things up. Actually any non-printable character could, but meh
1835 		// assert(s.indexOf("\033") == -1);
1836 
1837 		if(s.length == 0)
1838 			return;
1839 
1840 		// tracking cursor position
1841 		// FIXME: by grapheme?
1842 		foreach(dchar ch; s) {
1843 			switch(ch) {
1844 				case '\n':
1845 					_cursorX = 0;
1846 					_cursorY++;
1847 				break;
1848 				case '\r':
1849 					_cursorX = 0;
1850 				break;
1851 				case '\t':
1852 					_cursorX ++;
1853 					_cursorX += _cursorX % 8; // FIXME: get the actual tabstop, if possible
1854 				break;
1855 				default:
1856 					_cursorX++;
1857 			}
1858 
1859 			if(_wrapAround && _cursorX > width) {
1860 				_cursorX = 0;
1861 				_cursorY++;
1862 			}
1863 
1864 			if(_cursorY == height)
1865 				_cursorY--;
1866 
1867 			/+
1868 			auto index = getIndex(_cursorX, _cursorY);
1869 			if(data[index] != ch) {
1870 				data[index] = ch;
1871 			}
1872 			+/
1873 		}
1874 
1875 		version(TerminalDirectToEmulator) {
1876 			// this breaks up extremely long output a little as an aid to the
1877 			// gui thread; by breaking it up, it helps to avoid monopolizing the
1878 			// event loop. Easier to do here than in the thread itself because
1879 			// this one doesn't have escape sequences to break up so it avoids work.
1880 			while(s.length) {
1881 				auto len = s.length;
1882 				if(len > 1024 * 32) {
1883 					len = 1024 * 32;
1884 					// get to the start of a utf-8 sequence. kidna sorta.
1885 					while(len && (s[len] & 0x1000_0000))
1886 						len--;
1887 				}
1888 				auto next = s[0 .. len];
1889 				s = s[len .. $];
1890 				writeStringRaw(next);
1891 			}
1892 		} else {
1893 			writeStringRaw(s);
1894 		}
1895 	}
1896 
1897 	/* private */ bool _wrapAround = true;
1898 
1899 	deprecated alias writePrintableString writeString; /// use write() or writePrintableString instead
1900 
1901 	private string writeBuffer;
1902 
1903 	// you really, really shouldn't use this unless you know what you are doing
1904 	/*private*/ void writeStringRaw(in char[] s) {
1905 		writeBuffer ~= s; // buffer it to do everything at once in flush() calls
1906 		if(writeBuffer.length >  1024 * 32)
1907 			flush();
1908 	}
1909 
1910 	/// Clears the screen.
1911 	void clear() {
1912 		if(UseVtSequences) {
1913 			doTermcap("cl");
1914 		} else version(Win32Console) {
1915 			// http://support.microsoft.com/kb/99261
1916 			flush();
1917 
1918 			DWORD c;
1919 			CONSOLE_SCREEN_BUFFER_INFO csbi;
1920 			DWORD conSize;
1921 			GetConsoleScreenBufferInfo(hConsole, &csbi);
1922 			conSize = csbi.dwSize.X * csbi.dwSize.Y;
1923 			COORD coordScreen;
1924 			FillConsoleOutputCharacterA(hConsole, ' ', conSize, coordScreen, &c);
1925 			FillConsoleOutputAttribute(hConsole, csbi.wAttributes, conSize, coordScreen, &c);
1926 			moveTo(0, 0, ForceOption.alwaysSend);
1927 		}
1928 
1929 		_cursorX = 0;
1930 		_cursorY = 0;
1931 	}
1932 
1933 	/// gets a line, including user editing. Convenience method around the LineGetter class and RealTimeConsoleInput facilities - use them if you need more control.
1934 	/// 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.
1935 	// FIXME: add a method to make it easy to check if stdin is actually a tty and use other methods there.
1936 	string getline(string prompt = null) {
1937 		if(lineGetter is null)
1938 			lineGetter = new LineGetter(&this);
1939 		// since the struct might move (it shouldn't, this should be unmovable!) but since
1940 		// it technically might, I'm updating the pointer before using it just in case.
1941 		lineGetter.terminal = &this;
1942 
1943 		if(prompt !is null)
1944 			lineGetter.prompt = prompt;
1945 
1946 		auto input = RealTimeConsoleInput(&this, ConsoleInputFlags.raw);
1947 		auto line = lineGetter.getline(&input);
1948 
1949 		// lineGetter leaves us exactly where it was when the user hit enter, giving best
1950 		// flexibility to real-time input and cellular programs. The convenience function,
1951 		// however, wants to do what is right in most the simple cases, which is to actually
1952 		// print the line (echo would be enabled without RealTimeConsoleInput anyway and they
1953 		// did hit enter), so we'll do that here too.
1954 		writePrintableString("\n");
1955 
1956 		return line;
1957 	}
1958 
1959 }
1960 
1961 /++
1962 	Removes terminal color, bold, etc. sequences from a string,
1963 	making it plain text suitable for output to a normal .txt
1964 	file.
1965 +/
1966 inout(char)[] removeTerminalGraphicsSequences(inout(char)[] s) {
1967 	import std..string;
1968 
1969 	auto at = s.indexOf("\033[");
1970 	if(at == -1)
1971 		return s;
1972 
1973 	inout(char)[] ret;
1974 
1975 	do {
1976 		ret ~= s[0 .. at];
1977 		s = s[at + 2 .. $];
1978 		while(s.length && !((s[0] >= 'a' && s[0] <= 'z') || s[0] >= 'A' && s[0] <= 'Z')) {
1979 			s = s[1 .. $];
1980 		}
1981 		if(s.length)
1982 			s = s[1 .. $]; // skip the terminator
1983 		at = s.indexOf("\033[");
1984 	} while(at != -1);
1985 
1986 	ret ~= s;
1987 
1988 	return ret;
1989 }
1990 
1991 unittest {
1992 	assert("foo".removeTerminalGraphicsSequences == "foo");
1993 	assert("\033[34mfoo".removeTerminalGraphicsSequences == "foo");
1994 	assert("\033[34mfoo\033[39m".removeTerminalGraphicsSequences == "foo");
1995 	assert("\033[34m\033[45mfoo\033[39mbar\033[49m".removeTerminalGraphicsSequences == "foobar");
1996 }
1997 
1998 
1999 /+
2000 struct ConsoleBuffer {
2001 	int cursorX;
2002 	int cursorY;
2003 	int width;
2004 	int height;
2005 	dchar[] data;
2006 
2007 	void actualize(Terminal* t) {
2008 		auto writer = t.getBufferedWriter();
2009 
2010 		this.copyTo(&(t.onScreen));
2011 	}
2012 
2013 	void copyTo(ConsoleBuffer* buffer) {
2014 		buffer.cursorX = this.cursorX;
2015 		buffer.cursorY = this.cursorY;
2016 		buffer.width = this.width;
2017 		buffer.height = this.height;
2018 		buffer.data[] = this.data[];
2019 	}
2020 }
2021 +/
2022 
2023 /**
2024  * Encapsulates the stream of input events received from the terminal input.
2025  */
2026 struct RealTimeConsoleInput {
2027 	@disable this();
2028 	@disable this(this);
2029 
2030 	/++
2031 		Requests the system to send paste data as a [PasteEvent] to this stream, if possible.
2032 
2033 		See_Also:
2034 			[Terminal.requestCopyToPrimary]
2035 			[Terminal.requestCopyToClipboard]
2036 			[Terminal.clipboardSupported]
2037 
2038 		History:
2039 			Added February 17, 2020.
2040 
2041 			It was in Terminal briefly during an undocumented period, but it had to be moved here to have the context needed to send the real time paste event.
2042 	+/
2043 	void requestPasteFromClipboard() {
2044 		version(Win32Console) {
2045 			HWND hwndOwner = null;
2046 			if(OpenClipboard(hwndOwner) == 0)
2047 				throw new Exception("OpenClipboard");
2048 			scope(exit)
2049 				CloseClipboard();
2050 			if(auto dataHandle = GetClipboardData(CF_UNICODETEXT)) {
2051 
2052 				if(auto data = cast(wchar*) GlobalLock(dataHandle)) {
2053 					scope(exit)
2054 						GlobalUnlock(dataHandle);
2055 
2056 					int len = 0;
2057 					auto d = data;
2058 					while(*d) {
2059 						d++;
2060 						len++;
2061 					}
2062 					string s;
2063 					s.reserve(len);
2064 					foreach(idx, dchar ch; data[0 .. len]) {
2065 						// CR/LF -> LF
2066 						if(ch == '\r' && idx + 1 < len && data[idx + 1] == '\n')
2067 							continue;
2068 						s ~= ch;
2069 					}
2070 
2071 					injectEvent(InputEvent(PasteEvent(s), terminal), InjectionPosition.tail);
2072 				}
2073 			}
2074 		} else
2075 		if(terminal.clipboardSupported) {
2076 			if(UseVtSequences)
2077 				terminal.writeStringRaw("\033]52;c;?\007");
2078 		}
2079 	}
2080 
2081 	/// ditto
2082 	void requestPasteFromPrimary() {
2083 		if(terminal.clipboardSupported) {
2084 			if(UseVtSequences)
2085 				terminal.writeStringRaw("\033]52;p;?\007");
2086 		}
2087 	}
2088 
2089 
2090 	version(Posix) {
2091 		private int fdOut;
2092 		private int fdIn;
2093 		private sigaction_t oldSigWinch;
2094 		private sigaction_t oldSigIntr;
2095 		private sigaction_t oldHupIntr;
2096 		private termios old;
2097 		ubyte[128] hack;
2098 		// apparently termios isn't the size druntime thinks it is (at least on 32 bit, sometimes)....
2099 		// tcgetattr smashed other variables in here too that could create random problems
2100 		// so this hack is just to give some room for that to happen without destroying the rest of the world
2101 	}
2102 
2103 	version(Win32Console) {
2104 		private DWORD oldInput;
2105 		private DWORD oldOutput;
2106 		HANDLE inputHandle;
2107 	}
2108 
2109 	private ConsoleInputFlags flags;
2110 	private Terminal* terminal;
2111 	private void delegate()[] destructor;
2112 
2113 	/// To capture input, you need to provide a terminal and some flags.
2114 	public this(Terminal* terminal, ConsoleInputFlags flags) {
2115 		this.flags = flags;
2116 		this.terminal = terminal;
2117 
2118 		version(Win32Console) {
2119 			inputHandle = GetStdHandle(STD_INPUT_HANDLE);
2120 
2121 			GetConsoleMode(inputHandle, &oldInput);
2122 
2123 			DWORD mode = 0;
2124 			//mode |= ENABLE_PROCESSED_INPUT /* 0x01 */; // this gives Ctrl+C and automatic paste... which we probably want to be similar to linux
2125 			//if(flags & ConsoleInputFlags.size)
2126 			mode |= ENABLE_WINDOW_INPUT /* 0208 */; // gives size etc
2127 			if(flags & ConsoleInputFlags.echo)
2128 				mode |= ENABLE_ECHO_INPUT; // 0x4
2129 			if(flags & ConsoleInputFlags.mouse)
2130 				mode |= ENABLE_MOUSE_INPUT; // 0x10
2131 			// if(flags & ConsoleInputFlags.raw) // FIXME: maybe that should be a separate flag for ENABLE_LINE_INPUT
2132 
2133 			SetConsoleMode(inputHandle, mode);
2134 			destructor ~= { SetConsoleMode(inputHandle, oldInput); };
2135 
2136 
2137 			GetConsoleMode(terminal.hConsole, &oldOutput);
2138 			mode = 0;
2139 			// we want this to match linux too
2140 			mode |= ENABLE_PROCESSED_OUTPUT; /* 0x01 */
2141 			if(!(flags & ConsoleInputFlags.noEolWrap))
2142 				mode |= ENABLE_WRAP_AT_EOL_OUTPUT; /* 0x02 */
2143 			SetConsoleMode(terminal.hConsole, mode);
2144 			destructor ~= { SetConsoleMode(terminal.hConsole, oldOutput); };
2145 		}
2146 
2147 		version(TerminalDirectToEmulator) {
2148 			terminal.tew.terminalEmulator.echo = (flags & ConsoleInputFlags.echo) ? true : false;
2149 		} else version(Posix) {
2150 			this.fdIn = terminal.fdIn;
2151 			this.fdOut = terminal.fdOut;
2152 
2153 			if(fdIn != -1) {
2154 				tcgetattr(fdIn, &old);
2155 				auto n = old;
2156 
2157 				auto f = ICANON;
2158 				if(!(flags & ConsoleInputFlags.echo))
2159 					f |= ECHO;
2160 
2161 				// \033Z or \033[c
2162 
2163 				n.c_lflag &= ~f;
2164 				tcsetattr(fdIn, TCSANOW, &n);
2165 			}
2166 
2167 			// some weird bug breaks this, https://github.com/robik/ConsoleD/issues/3
2168 			//destructor ~= { tcsetattr(fdIn, TCSANOW, &old); };
2169 
2170 			if(flags & ConsoleInputFlags.size) {
2171 				import core.sys.posix.signal;
2172 				sigaction_t n;
2173 				n.sa_handler = &sizeSignalHandler;
2174 				n.sa_mask = cast(sigset_t) 0;
2175 				n.sa_flags = 0;
2176 				sigaction(SIGWINCH, &n, &oldSigWinch);
2177 			}
2178 
2179 			{
2180 				import core.sys.posix.signal;
2181 				sigaction_t n;
2182 				n.sa_handler = &interruptSignalHandler;
2183 				n.sa_mask = cast(sigset_t) 0;
2184 				n.sa_flags = 0;
2185 				sigaction(SIGINT, &n, &oldSigIntr);
2186 			}
2187 
2188 			{
2189 				import core.sys.posix.signal;
2190 				sigaction_t n;
2191 				n.sa_handler = &hangupSignalHandler;
2192 				n.sa_mask = cast(sigset_t) 0;
2193 				n.sa_flags = 0;
2194 				sigaction(SIGHUP, &n, &oldHupIntr);
2195 			}
2196 		}
2197 
2198 		if(UseVtSequences) {
2199 			if(flags & ConsoleInputFlags.mouse) {
2200 				// basic button press+release notification
2201 
2202 				// FIXME: try to get maximum capabilities from all terminals
2203 				// right now this works well on xterm but rxvt isn't sending movements...
2204 
2205 				terminal.writeStringRaw("\033[?1000h");
2206 				destructor ~= { terminal.writeStringRaw("\033[?1000l"); };
2207 				// the MOUSE_HACK env var is for the case where I run screen
2208 				// but set TERM=xterm (which I do from putty). The 1003 mouse mode
2209 				// doesn't work there, breaking mouse support entirely. So by setting
2210 				// MOUSE_HACK=1002 it tells us to use the other mode for a fallback.
2211 				import std.process : environment;
2212 				if(terminal.terminalInFamily("xterm") && environment.get("MOUSE_HACK") != "1002") {
2213 					// this is vt200 mouse with full motion tracking, supported by xterm
2214 					terminal.writeStringRaw("\033[?1003h");
2215 					destructor ~= { terminal.writeStringRaw("\033[?1003l"); };
2216 				} else if(terminal.terminalInFamily("rxvt", "screen", "tmux") || environment.get("MOUSE_HACK") == "1002") {
2217 					terminal.writeStringRaw("\033[?1002h"); // this is vt200 mouse with press/release and motion notification iff buttons are pressed
2218 					destructor ~= { terminal.writeStringRaw("\033[?1002l"); };
2219 				}
2220 			}
2221 			if(flags & ConsoleInputFlags.paste) {
2222 				if(terminal.terminalInFamily("xterm", "rxvt", "screen", "tmux")) {
2223 					terminal.writeStringRaw("\033[?2004h"); // bracketed paste mode
2224 					destructor ~= { terminal.writeStringRaw("\033[?2004l"); };
2225 				}
2226 			}
2227 
2228 			if(terminal.tcaps & TerminalCapabilities.arsdHyperlinks) {
2229 				terminal.writeStringRaw("\033[?3004h"); // bracketed link mode
2230 				destructor ~= { terminal.writeStringRaw("\033[?3004l"); };
2231 			}
2232 
2233 			// try to ensure the terminal is in UTF-8 mode
2234 			if(terminal.terminalInFamily("xterm", "screen", "linux", "tmux") && !terminal.isMacTerminal()) {
2235 				terminal.writeStringRaw("\033%G");
2236 			}
2237 
2238 			terminal.flush();
2239 		}
2240 
2241 
2242 		version(with_eventloop) {
2243 			import arsd.eventloop;
2244 			version(Win32Console)
2245 				auto listenTo = inputHandle;
2246 			else version(Posix)
2247 				auto listenTo = this.fdIn;
2248 			else static assert(0, "idk about this OS");
2249 
2250 			version(Posix)
2251 			addListener(&signalFired);
2252 
2253 			if(listenTo != -1) {
2254 				addFileEventListeners(listenTo, &eventListener, null, null);
2255 				destructor ~= { removeFileEventListeners(listenTo); };
2256 			}
2257 			addOnIdle(&terminal.flush);
2258 			destructor ~= { removeOnIdle(&terminal.flush); };
2259 		}
2260 	}
2261 
2262 	void fdReadyReader() {
2263 		auto queue = readNextEvents();
2264 		foreach(event; queue)
2265 			userEventHandler(event);
2266 	}
2267 
2268 	void delegate(InputEvent) userEventHandler;
2269 
2270 	/++
2271 		If you are using [arsd.simpledisplay] and want terminal interop too, you can call
2272 		this function to add it to the sdpy event loop and get the callback called on new
2273 		input.
2274 
2275 		Note that you will probably need to call `terminal.flush()` when you are doing doing
2276 		output, as the sdpy event loop doesn't know to do that (yet). I will probably change
2277 		that in a future version, but it doesn't hurt to call it twice anyway, so I recommend
2278 		calling flush yourself in any code you write using this.
2279 	+/
2280 	void integrateWithSimpleDisplayEventLoop()(void delegate(InputEvent) userEventHandler) {
2281 		this.userEventHandler = userEventHandler;
2282 		import arsd.simpledisplay;
2283 		version(Win32Console)
2284 			auto listener = new WindowsHandleReader(&fdReadyReader, terminal.hConsole);
2285 		else version(linux)
2286 			auto listener = new PosixFdReader(&fdReadyReader, fdIn);
2287 		else static assert(0, "sdpy event loop integration not implemented on this platform");
2288 	}
2289 
2290 	version(with_eventloop) {
2291 		version(Posix)
2292 		void signalFired(SignalFired) {
2293 			if(interrupted) {
2294 				interrupted = false;
2295 				send(InputEvent(UserInterruptionEvent(), terminal));
2296 			}
2297 			if(windowSizeChanged)
2298 				send(checkWindowSizeChanged());
2299 			if(hangedUp) {
2300 				hangedUp = false;
2301 				send(InputEvent(HangupEvent(), terminal));
2302 			}
2303 		}
2304 
2305 		import arsd.eventloop;
2306 		void eventListener(OsFileHandle fd) {
2307 			auto queue = readNextEvents();
2308 			foreach(event; queue)
2309 				send(event);
2310 		}
2311 	}
2312 
2313 	bool _suppressDestruction;
2314 
2315 	~this() {
2316 		if(_suppressDestruction)
2317 			return;
2318 
2319 		// the delegate thing doesn't actually work for this... for some reason
2320 
2321 		version(TerminalDirectToEmulator) { } else
2322 		version(Posix)
2323 			if(fdIn != -1)
2324 				tcsetattr(fdIn, TCSANOW, &old);
2325 
2326 		version(TerminalDirectToEmulator) { } else
2327 		version(Posix) {
2328 			if(flags & ConsoleInputFlags.size) {
2329 				// restoration
2330 				sigaction(SIGWINCH, &oldSigWinch, null);
2331 			}
2332 			sigaction(SIGINT, &oldSigIntr, null);
2333 			sigaction(SIGHUP, &oldHupIntr, null);
2334 		}
2335 
2336 		// we're just undoing everything the constructor did, in reverse order, same criteria
2337 		foreach_reverse(d; destructor)
2338 			d();
2339 	}
2340 
2341 	/**
2342 		Returns true if there iff getch() would not block.
2343 
2344 		WARNING: kbhit might consume input that would be ignored by getch. This
2345 		function is really only meant to be used in conjunction with getch. Typically,
2346 		you should use a full-fledged event loop if you want all kinds of input. kbhit+getch
2347 		are just for simple keyboard driven applications.
2348 	*/
2349 	bool kbhit() {
2350 		auto got = getch(true);
2351 
2352 		if(got == dchar.init)
2353 			return false;
2354 
2355 		getchBuffer = got;
2356 		return true;
2357 	}
2358 
2359 	/// Check for input, waiting no longer than the number of milliseconds
2360 	bool timedCheckForInput(int milliseconds) {
2361 		if(inputQueue.length || timedCheckForInput_bypassingBuffer(milliseconds))
2362 			return true;
2363 		version(WithEncapsulatedSignals)
2364 			if(terminal.interrupted || terminal.windowSizeChanged || terminal.hangedUp)
2365 				return true;
2366 		version(WithSignals)
2367 			if(interrupted || windowSizeChanged || hangedUp)
2368 				return true;
2369 		return false;
2370 	}
2371 
2372 	/* private */ bool anyInput_internal(int timeout = 0) {
2373 		return timedCheckForInput(timeout);
2374 	}
2375 
2376 	bool timedCheckForInput_bypassingBuffer(int milliseconds) {
2377 		version(TerminalDirectToEmulator) {
2378 			import core.time;
2379 			if(terminal.tew.terminalEmulator.pendingForApplication.length)
2380 				return true;
2381 			if(terminal.tew.terminalEmulator.outgoingSignal.wait(milliseconds.msecs))
2382 				// it was notified, but it could be left over from stuff we
2383 				// already processed... so gonna check the blocking conditions here too
2384 				// (FIXME: this sucks and is surely a race condition of pain)
2385 				return terminal.tew.terminalEmulator.pendingForApplication.length || terminal.interrupted || terminal.windowSizeChanged || terminal.hangedUp;
2386 			else
2387 				return false;
2388 		} else version(Win32Console) {
2389 			auto response = WaitForSingleObject(inputHandle, milliseconds);
2390 			if(response  == 0)
2391 				return true; // the object is ready
2392 			return false;
2393 		} else version(Posix) {
2394 			if(fdIn == -1)
2395 				return false;
2396 
2397 			timeval tv;
2398 			tv.tv_sec = 0;
2399 			tv.tv_usec = milliseconds * 1000;
2400 
2401 			fd_set fs;
2402 			FD_ZERO(&fs);
2403 
2404 			FD_SET(fdIn, &fs);
2405 			int tries = 0;
2406 			try_again:
2407 			auto ret = select(fdIn + 1, &fs, null, null, &tv);
2408 			if(ret == -1) {
2409 				import core.stdc.errno;
2410 				if(errno == EINTR) {
2411 					tries++;
2412 					if(tries < 3)
2413 						goto try_again;
2414 				}
2415 				return false;
2416 			}
2417 			if(ret == 0)
2418 				return false;
2419 
2420 			return FD_ISSET(fdIn, &fs);
2421 		}
2422 	}
2423 
2424 	private dchar getchBuffer;
2425 
2426 	/// Get one key press from the terminal, discarding other
2427 	/// events in the process. Returns dchar.init upon receiving end-of-file.
2428 	///
2429 	/// Be aware that this may return non-character key events, like F1, F2, arrow keys, etc., as private use Unicode characters. Check them against KeyboardEvent.Key if you like.
2430 	dchar getch(bool nonblocking = false) {
2431 		if(getchBuffer != dchar.init) {
2432 			auto a = getchBuffer;
2433 			getchBuffer = dchar.init;
2434 			return a;
2435 		}
2436 
2437 		if(nonblocking && !anyInput_internal())
2438 			return dchar.init;
2439 
2440 		auto event = nextEvent();
2441 		while(event.type != InputEvent.Type.KeyboardEvent || event.keyboardEvent.pressed == false) {
2442 			if(event.type == InputEvent.Type.UserInterruptionEvent)
2443 				throw new UserInterruptionException();
2444 			if(event.type == InputEvent.Type.HangupEvent)
2445 				throw new HangupException();
2446 			if(event.type == InputEvent.Type.EndOfFileEvent)
2447 				return dchar.init;
2448 
2449 			if(nonblocking && !anyInput_internal())
2450 				return dchar.init;
2451 
2452 			event = nextEvent();
2453 		}
2454 		return event.keyboardEvent.which;
2455 	}
2456 
2457 	//char[128] inputBuffer;
2458 	//int inputBufferPosition;
2459 	int nextRaw(bool interruptable = false) {
2460 		version(TerminalDirectToEmulator) {
2461 			moar:
2462 			//if(interruptable && inputQueue.length)
2463 				//return -1;
2464 			if(terminal.tew.terminalEmulator.pendingForApplication.length == 0)
2465 				terminal.tew.terminalEmulator.outgoingSignal.wait();
2466 			synchronized(terminal.tew.terminalEmulator) {
2467 				if(terminal.tew.terminalEmulator.pendingForApplication.length == 0) {
2468 					if(interruptable)
2469 						return -1;
2470 					else
2471 						goto moar;
2472 				}
2473 				auto a = terminal.tew.terminalEmulator.pendingForApplication[0];
2474 				terminal.tew.terminalEmulator.pendingForApplication = terminal.tew.terminalEmulator.pendingForApplication[1 .. $];
2475 				return a;
2476 			}
2477 		} else version(Posix) {
2478 			if(fdIn == -1)
2479 				return 0;
2480 
2481 			char[1] buf;
2482 			try_again:
2483 			auto ret = read(fdIn, buf.ptr, buf.length);
2484 			if(ret == 0)
2485 				return 0; // input closed
2486 			if(ret == -1) {
2487 				import core.stdc.errno;
2488 				if(errno == EINTR)
2489 					// interrupted by signal call, quite possibly resize or ctrl+c which we want to check for in the event loop
2490 					if(interruptable)
2491 						return -1;
2492 					else
2493 						goto try_again;
2494 				else
2495 					throw new Exception("read failed");
2496 			}
2497 
2498 			//terminal.writef("RAW READ: %d\n", buf[0]);
2499 
2500 			if(ret == 1)
2501 				return inputPrefilter ? inputPrefilter(buf[0]) : buf[0];
2502 			else
2503 				assert(0); // read too much, should be impossible
2504 		} else version(Windows) {
2505 			char[1] buf;
2506 			DWORD d;
2507 			import std.conv;
2508 			if(!ReadFile(inputHandle, buf.ptr, cast(int) buf.length, &d, null))
2509 				throw new Exception("ReadFile " ~ to!string(GetLastError()));
2510 			return buf[0];
2511 		}
2512 	}
2513 
2514 	version(Posix)
2515 		int delegate(char) inputPrefilter;
2516 
2517 	// for VT
2518 	dchar nextChar(int starting) {
2519 		if(starting <= 127)
2520 			return cast(dchar) starting;
2521 		char[6] buffer;
2522 		int pos = 0;
2523 		buffer[pos++] = cast(char) starting;
2524 
2525 		// see the utf-8 encoding for details
2526 		int remaining = 0;
2527 		ubyte magic = starting & 0xff;
2528 		while(magic & 0b1000_000) {
2529 			remaining++;
2530 			magic <<= 1;
2531 		}
2532 
2533 		while(remaining && pos < buffer.length) {
2534 			buffer[pos++] = cast(char) nextRaw();
2535 			remaining--;
2536 		}
2537 
2538 		import std.utf;
2539 		size_t throwAway; // it insists on the index but we don't care
2540 		return decode(buffer[], throwAway);
2541 	}
2542 
2543 	InputEvent checkWindowSizeChanged() {
2544 		auto oldWidth = terminal.width;
2545 		auto oldHeight = terminal.height;
2546 		terminal.updateSize();
2547 		version(WithSignals)
2548 			windowSizeChanged = false;
2549 		version(WithEncapsulatedSignals)
2550 			terminal.windowSizeChanged = false;
2551 		return InputEvent(SizeChangedEvent(oldWidth, oldHeight, terminal.width, terminal.height), terminal);
2552 	}
2553 
2554 
2555 	// character event
2556 	// non-character key event
2557 	// paste event
2558 	// mouse event
2559 	// size event maybe, and if appropriate focus events
2560 
2561 	/// Returns the next event.
2562 	///
2563 	/// Experimental: It is also possible to integrate this into
2564 	/// a generic event loop, currently under -version=with_eventloop and it will
2565 	/// require the module arsd.eventloop (Linux only at this point)
2566 	InputEvent nextEvent() {
2567 		terminal.flush();
2568 
2569 		wait_for_more:
2570 		version(WithSignals) {
2571 			if(interrupted) {
2572 				interrupted = false;
2573 				return InputEvent(UserInterruptionEvent(), terminal);
2574 			}
2575 
2576 			if(hangedUp) {
2577 				hangedUp = false;
2578 				return InputEvent(HangupEvent(), terminal);
2579 			}
2580 
2581 			if(windowSizeChanged) {
2582 				return checkWindowSizeChanged();
2583 			}
2584 		}
2585 
2586 		version(WithEncapsulatedSignals) {
2587 			if(terminal.interrupted) {
2588 				terminal.interrupted = false;
2589 				return InputEvent(UserInterruptionEvent(), terminal);
2590 			}
2591 
2592 			if(terminal.hangedUp) {
2593 				terminal.hangedUp = false;
2594 				return InputEvent(HangupEvent(), terminal);
2595 			}
2596 
2597 			if(terminal.windowSizeChanged) {
2598 				return checkWindowSizeChanged();
2599 			}
2600 		}
2601 
2602 		if(inputQueue.length) {
2603 			auto e = inputQueue[0];
2604 			inputQueue = inputQueue[1 .. $];
2605 			return e;
2606 		}
2607 
2608 		auto more = readNextEvents();
2609 		if(!more.length)
2610 			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
2611 
2612 		assert(more.length);
2613 
2614 		auto e = more[0];
2615 		inputQueue = more[1 .. $];
2616 		return e;
2617 	}
2618 
2619 	InputEvent* peekNextEvent() {
2620 		if(inputQueue.length)
2621 			return &(inputQueue[0]);
2622 		return null;
2623 	}
2624 
2625 	enum InjectionPosition { head, tail }
2626 	void injectEvent(InputEvent ev, InjectionPosition where) {
2627 		final switch(where) {
2628 			case InjectionPosition.head:
2629 				inputQueue = ev ~ inputQueue;
2630 			break;
2631 			case InjectionPosition.tail:
2632 				inputQueue ~= ev;
2633 			break;
2634 		}
2635 	}
2636 
2637 	InputEvent[] inputQueue;
2638 
2639 	InputEvent[] readNextEvents() {
2640 		if(UseVtSequences)
2641 			return readNextEventsVt();
2642 		else version(Win32Console)
2643 			return readNextEventsWin32();
2644 		else
2645 			assert(0);
2646 	}
2647 
2648 	version(Win32Console)
2649 	InputEvent[] readNextEventsWin32() {
2650 		terminal.flush(); // make sure all output is sent out before waiting for anything
2651 
2652 		INPUT_RECORD[32] buffer;
2653 		DWORD actuallyRead;
2654 			// FIXME: ReadConsoleInputW
2655 		auto success = ReadConsoleInputW(inputHandle, buffer.ptr, buffer.length, &actuallyRead);
2656 		if(success == 0)
2657 			throw new Exception("ReadConsoleInput");
2658 
2659 		InputEvent[] newEvents;
2660 		input_loop: foreach(record; buffer[0 .. actuallyRead]) {
2661 			switch(record.EventType) {
2662 				case KEY_EVENT:
2663 					auto ev = record.KeyEvent;
2664 					KeyboardEvent ke;
2665 					CharacterEvent e;
2666 					NonCharacterKeyEvent ne;
2667 
2668 					ke.pressed = ev.bKeyDown ? true : false;
2669 
2670 					// only send released events when specifically requested
2671 					// terminal.writefln("got %s %s", ev.UnicodeChar, ev.bKeyDown);
2672 					if(ev.UnicodeChar && ev.wVirtualKeyCode == VK_MENU && ev.bKeyDown == 0) {
2673 						// this indicates Windows is actually sending us
2674 						// an alt+xxx key sequence, may also be a unicode paste.
2675 						// either way, it cool.
2676 						ke.pressed = true;
2677 					} else {
2678 						if(!(flags & ConsoleInputFlags.releasedKeys) && !ev.bKeyDown)
2679 							break;
2680 					}
2681 
2682 					e.eventType = ke.pressed ? CharacterEvent.Type.Pressed : CharacterEvent.Type.Released;
2683 					ne.eventType = ke.pressed ? NonCharacterKeyEvent.Type.Pressed : NonCharacterKeyEvent.Type.Released;
2684 
2685 					e.modifierState = ev.dwControlKeyState;
2686 					ne.modifierState = ev.dwControlKeyState;
2687 					ke.modifierState = ev.dwControlKeyState;
2688 
2689 					if(ev.UnicodeChar) {
2690 						// new style event goes first
2691 
2692 						if(ev.UnicodeChar == 3) {
2693 							// handling this internally for linux compat too
2694 							newEvents ~= InputEvent(UserInterruptionEvent(), terminal);
2695 						} else if(ev.UnicodeChar == '\r') {
2696 							// translating \r to \n for same result as linux...
2697 							ke.which = cast(dchar) cast(wchar) '\n';
2698 							newEvents ~= InputEvent(ke, terminal);
2699 
2700 							// old style event then follows as the fallback
2701 							e.character = cast(dchar) cast(wchar) '\n';
2702 							newEvents ~= InputEvent(e, terminal);
2703 						} else if(ev.wVirtualKeyCode == 0x1b) {
2704 							ke.which = cast(KeyboardEvent.Key) (ev.wVirtualKeyCode + 0xF0000);
2705 							newEvents ~= InputEvent(ke, terminal);
2706 
2707 							ne.key = cast(NonCharacterKeyEvent.Key) ev.wVirtualKeyCode;
2708 							newEvents ~= InputEvent(ne, terminal);
2709 						} else {
2710 							ke.which = cast(dchar) cast(wchar) ev.UnicodeChar;
2711 							newEvents ~= InputEvent(ke, terminal);
2712 
2713 							// old style event then follows as the fallback
2714 							e.character = cast(dchar) cast(wchar) ev.UnicodeChar;
2715 							newEvents ~= InputEvent(e, terminal);
2716 						}
2717 					} else {
2718 						// old style event
2719 						ne.key = cast(NonCharacterKeyEvent.Key) ev.wVirtualKeyCode;
2720 
2721 						// new style event. See comment on KeyboardEvent.Key
2722 						ke.which = cast(KeyboardEvent.Key) (ev.wVirtualKeyCode + 0xF0000);
2723 
2724 						// FIXME: make this better. the goal is to make sure the key code is a valid enum member
2725 						// Windows sends more keys than Unix and we're doing lowest common denominator here
2726 						foreach(member; __traits(allMembers, NonCharacterKeyEvent.Key))
2727 							if(__traits(getMember, NonCharacterKeyEvent.Key, member) == ne.key) {
2728 								newEvents ~= InputEvent(ke, terminal);
2729 								newEvents ~= InputEvent(ne, terminal);
2730 								break;
2731 							}
2732 					}
2733 				break;
2734 				case MOUSE_EVENT:
2735 					auto ev = record.MouseEvent;
2736 					MouseEvent e;
2737 
2738 					e.modifierState = ev.dwControlKeyState;
2739 					e.x = ev.dwMousePosition.X;
2740 					e.y = ev.dwMousePosition.Y;
2741 
2742 					switch(ev.dwEventFlags) {
2743 						case 0:
2744 							//press or release
2745 							e.eventType = MouseEvent.Type.Pressed;
2746 							static DWORD lastButtonState;
2747 							auto lastButtonState2 = lastButtonState;
2748 							e.buttons = ev.dwButtonState;
2749 							lastButtonState = e.buttons;
2750 
2751 							// this is sent on state change. if fewer buttons are pressed, it must mean released
2752 							if(cast(DWORD) e.buttons < lastButtonState2) {
2753 								e.eventType = MouseEvent.Type.Released;
2754 								// if last was 101 and now it is 100, then button far right was released
2755 								// so we flip the bits, ~100 == 011, then and them: 101 & 011 == 001, the
2756 								// button that was released
2757 								e.buttons = lastButtonState2 & ~e.buttons;
2758 							}
2759 						break;
2760 						case MOUSE_MOVED:
2761 							e.eventType = MouseEvent.Type.Moved;
2762 							e.buttons = ev.dwButtonState;
2763 						break;
2764 						case 0x0004/*MOUSE_WHEELED*/:
2765 							e.eventType = MouseEvent.Type.Pressed;
2766 							if(ev.dwButtonState > 0)
2767 								e.buttons = MouseEvent.Button.ScrollDown;
2768 							else
2769 								e.buttons = MouseEvent.Button.ScrollUp;
2770 						break;
2771 						default:
2772 							continue input_loop;
2773 					}
2774 
2775 					newEvents ~= InputEvent(e, terminal);
2776 				break;
2777 				case WINDOW_BUFFER_SIZE_EVENT:
2778 					auto ev = record.WindowBufferSizeEvent;
2779 					auto oldWidth = terminal.width;
2780 					auto oldHeight = terminal.height;
2781 					terminal._width = ev.dwSize.X;
2782 					terminal._height = ev.dwSize.Y;
2783 					newEvents ~= InputEvent(SizeChangedEvent(oldWidth, oldHeight, terminal.width, terminal.height), terminal);
2784 				break;
2785 				// FIXME: can we catch ctrl+c here too?
2786 				default:
2787 					// ignore
2788 			}
2789 		}
2790 
2791 		return newEvents;
2792 	}
2793 
2794 	// for UseVtSequences....
2795 	InputEvent[] readNextEventsVt() {
2796 		terminal.flush(); // make sure all output is sent out before we try to get input
2797 
2798 		// we want to starve the read, especially if we're called from an edge-triggered
2799 		// epoll (which might happen in version=with_eventloop.. impl detail there subject
2800 		// to change).
2801 		auto initial = readNextEventsHelper();
2802 
2803 		// lol this calls select() inside a function prolly called from epoll but meh,
2804 		// it is the simplest thing that can possibly work. The alternative would be
2805 		// doing non-blocking reads and buffering in the nextRaw function (not a bad idea
2806 		// btw, just a bit more of a hassle).
2807 		while(timedCheckForInput_bypassingBuffer(0)) {
2808 			auto ne = readNextEventsHelper();
2809 			initial ~= ne;
2810 			foreach(n; ne)
2811 				if(n.type == InputEvent.Type.EndOfFileEvent)
2812 					return initial; // hit end of file, get out of here lest we infinite loop
2813 					// (select still returns info available even after we read end of file)
2814 		}
2815 		return initial;
2816 	}
2817 
2818 	// The helper reads just one actual event from the pipe...
2819 	// for UseVtSequences....
2820 	InputEvent[] readNextEventsHelper(int remainingFromLastTime = int.max) {
2821 		InputEvent[] charPressAndRelease(dchar character) {
2822 			if((flags & ConsoleInputFlags.releasedKeys))
2823 				return [
2824 					// new style event
2825 					InputEvent(KeyboardEvent(true, character, 0), terminal),
2826 					InputEvent(KeyboardEvent(false, character, 0), terminal),
2827 					// old style event
2828 					InputEvent(CharacterEvent(CharacterEvent.Type.Pressed, character, 0), terminal),
2829 					InputEvent(CharacterEvent(CharacterEvent.Type.Released, character, 0), terminal),
2830 				];
2831 			else return [
2832 				// new style event
2833 				InputEvent(KeyboardEvent(true, character, 0), terminal),
2834 				// old style event
2835 				InputEvent(CharacterEvent(CharacterEvent.Type.Pressed, character, 0), terminal)
2836 			];
2837 		}
2838 		InputEvent[] keyPressAndRelease(NonCharacterKeyEvent.Key key, uint modifiers = 0) {
2839 			if((flags & ConsoleInputFlags.releasedKeys))
2840 				return [
2841 					// new style event FIXME: when the old events are removed, kill the +0xF0000 from here!
2842 					InputEvent(KeyboardEvent(true, cast(dchar)(key) + 0xF0000, modifiers), terminal),
2843 					InputEvent(KeyboardEvent(false, cast(dchar)(key) + 0xF0000, modifiers), terminal),
2844 					// old style event
2845 					InputEvent(NonCharacterKeyEvent(NonCharacterKeyEvent.Type.Pressed, key, modifiers), terminal),
2846 					InputEvent(NonCharacterKeyEvent(NonCharacterKeyEvent.Type.Released, key, modifiers), terminal),
2847 				];
2848 			else return [
2849 				// new style event FIXME: when the old events are removed, kill the +0xF0000 from here!
2850 				InputEvent(KeyboardEvent(true, cast(dchar)(key) + 0xF0000, modifiers), terminal),
2851 				// old style event
2852 				InputEvent(NonCharacterKeyEvent(NonCharacterKeyEvent.Type.Pressed, key, modifiers), terminal)
2853 			];
2854 		}
2855 
2856 		InputEvent[] keyPressAndRelease2(dchar c, uint modifiers = 0) {
2857 			if((flags & ConsoleInputFlags.releasedKeys))
2858 				return [
2859 					InputEvent(KeyboardEvent(true, c, modifiers), terminal),
2860 					InputEvent(KeyboardEvent(false, c, modifiers), terminal),
2861 					// old style event
2862 					InputEvent(CharacterEvent(CharacterEvent.Type.Pressed, c, modifiers), terminal),
2863 					InputEvent(CharacterEvent(CharacterEvent.Type.Released, c, modifiers), terminal),
2864 				];
2865 			else return [
2866 				InputEvent(KeyboardEvent(true, c, modifiers), terminal),
2867 				// old style event
2868 				InputEvent(CharacterEvent(CharacterEvent.Type.Pressed, c, modifiers), terminal)
2869 			];
2870 
2871 		}
2872 
2873 		char[30] sequenceBuffer;
2874 
2875 		// this assumes you just read "\033["
2876 		char[] readEscapeSequence(char[] sequence) {
2877 			int sequenceLength = 2;
2878 			sequence[0] = '\033';
2879 			sequence[1] = '[';
2880 
2881 			while(sequenceLength < sequence.length) {
2882 				auto n = nextRaw();
2883 				sequence[sequenceLength++] = cast(char) n;
2884 				// I think a [ is supposed to termiate a CSI sequence
2885 				// but the Linux console sends CSI[A for F1, so I'm
2886 				// hacking it to accept that too
2887 				if(n >= 0x40 && !(sequenceLength == 3 && n == '['))
2888 					break;
2889 			}
2890 
2891 			return sequence[0 .. sequenceLength];
2892 		}
2893 
2894 		InputEvent[] translateTermcapName(string cap) {
2895 			switch(cap) {
2896 				//case "k0":
2897 					//return keyPressAndRelease(NonCharacterKeyEvent.Key.F1);
2898 				case "k1":
2899 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F1);
2900 				case "k2":
2901 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F2);
2902 				case "k3":
2903 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F3);
2904 				case "k4":
2905 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F4);
2906 				case "k5":
2907 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F5);
2908 				case "k6":
2909 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F6);
2910 				case "k7":
2911 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F7);
2912 				case "k8":
2913 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F8);
2914 				case "k9":
2915 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F9);
2916 				case "k;":
2917 				case "k0":
2918 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F10);
2919 				case "F1":
2920 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F11);
2921 				case "F2":
2922 					return keyPressAndRelease(NonCharacterKeyEvent.Key.F12);
2923 
2924 
2925 				case "kb":
2926 					return charPressAndRelease('\b');
2927 				case "kD":
2928 					return keyPressAndRelease(NonCharacterKeyEvent.Key.Delete);
2929 
2930 				case "kd":
2931 				case "do":
2932 					return keyPressAndRelease(NonCharacterKeyEvent.Key.DownArrow);
2933 				case "ku":
2934 				case "up":
2935 					return keyPressAndRelease(NonCharacterKeyEvent.Key.UpArrow);
2936 				case "kl":
2937 					return keyPressAndRelease(NonCharacterKeyEvent.Key.LeftArrow);
2938 				case "kr":
2939 				case "nd":
2940 					return keyPressAndRelease(NonCharacterKeyEvent.Key.RightArrow);
2941 
2942 				case "kN":
2943 				case "K5":
2944 					return keyPressAndRelease(NonCharacterKeyEvent.Key.PageDown);
2945 				case "kP":
2946 				case "K2":
2947 					return keyPressAndRelease(NonCharacterKeyEvent.Key.PageUp);
2948 
2949 				case "ho": // this might not be a key but my thing sometimes returns it... weird...
2950 				case "kh":
2951 				case "K1":
2952 					return keyPressAndRelease(NonCharacterKeyEvent.Key.Home);
2953 				case "kH":
2954 					return keyPressAndRelease(NonCharacterKeyEvent.Key.End);
2955 				case "kI":
2956 					return keyPressAndRelease(NonCharacterKeyEvent.Key.Insert);
2957 				default:
2958 					// don't know it, just ignore
2959 					//import std.stdio;
2960 					//terminal.writeln(cap);
2961 			}
2962 
2963 			return null;
2964 		}
2965 
2966 
2967 		InputEvent[] doEscapeSequence(in char[] sequence) {
2968 			switch(sequence) {
2969 				case "\033[200~":
2970 					// bracketed paste begin
2971 					// we want to keep reading until
2972 					// "\033[201~":
2973 					// and build a paste event out of it
2974 
2975 
2976 					string data;
2977 					for(;;) {
2978 						auto n = nextRaw();
2979 						if(n == '\033') {
2980 							n = nextRaw();
2981 							if(n == '[') {
2982 								auto esc = readEscapeSequence(sequenceBuffer);
2983 								if(esc == "\033[201~") {
2984 									// complete!
2985 									break;
2986 								} else {
2987 									// was something else apparently, but it is pasted, so keep it
2988 									data ~= esc;
2989 								}
2990 							} else {
2991 								data ~= '\033';
2992 								data ~= cast(char) n;
2993 							}
2994 						} else {
2995 							data ~= cast(char) n;
2996 						}
2997 					}
2998 					return [InputEvent(PasteEvent(data), terminal)];
2999 				case "\033[220~":
3000 					// bracketed hyperlink begin (arsd extension)
3001 
3002 					string data;
3003 					for(;;) {
3004 						auto n = nextRaw();
3005 						if(n == '\033') {
3006 							n = nextRaw();
3007 							if(n == '[') {
3008 								auto esc = readEscapeSequence(sequenceBuffer);
3009 								if(esc == "\033[221~") {
3010 									// complete!
3011 									break;
3012 								} else {
3013 									// was something else apparently, but it is pasted, so keep it
3014 									data ~= esc;
3015 								}
3016 							} else {
3017 								data ~= '\033';
3018 								data ~= cast(char) n;
3019 							}
3020 						} else {
3021 							data ~= cast(char) n;
3022 						}
3023 					}
3024 
3025 					import std..string, std.conv;
3026 					auto idx = data.indexOf(";");
3027 					auto id = data[0 .. idx].to!ushort;
3028 					data = data[idx + 1 .. $];
3029 					idx = data.indexOf(";");
3030 					auto cmd = data[0 .. idx].to!ushort;
3031 					data = data[idx + 1 .. $];
3032 
3033 					return [InputEvent(LinkEvent(data, id, cmd), terminal)];
3034 				case "\033[M":
3035 					// mouse event
3036 					auto buttonCode = nextRaw() - 32;
3037 						// nextChar is commented because i'm not using UTF-8 mouse mode
3038 						// cuz i don't think it is as widely supported
3039 					auto x = cast(int) (/*nextChar*/(nextRaw())) - 33; /* they encode value + 32, but make upper left 1,1. I want it to be 0,0 */
3040 					auto y = cast(int) (/*nextChar*/(nextRaw())) - 33; /* ditto */
3041 
3042 
3043 					bool isRelease = (buttonCode & 0b11) == 3;
3044 					int buttonNumber;
3045 					if(!isRelease) {
3046 						buttonNumber = (buttonCode & 0b11);
3047 						if(buttonCode & 64)
3048 							buttonNumber += 3; // button 4 and 5 are sent as like button 1 and 2, but code | 64
3049 							// so button 1 == button 4 here
3050 
3051 						// note: buttonNumber == 0 means button 1 at this point
3052 						buttonNumber++; // hence this
3053 
3054 
3055 						// apparently this considers middle to be button 2. but i want middle to be button 3.
3056 						if(buttonNumber == 2)
3057 							buttonNumber = 3;
3058 						else if(buttonNumber == 3)
3059 							buttonNumber = 2;
3060 					}
3061 
3062 					auto modifiers = buttonCode & (0b0001_1100);
3063 						// 4 == shift
3064 						// 8 == meta
3065 						// 16 == control
3066 
3067 					MouseEvent m;
3068 
3069 					if(buttonCode & 32)
3070 						m.eventType = MouseEvent.Type.Moved;
3071 					else
3072 						m.eventType = isRelease ? MouseEvent.Type.Released : MouseEvent.Type.Pressed;
3073 
3074 					// ugh, if no buttons are pressed, released and moved are indistinguishable...
3075 					// so we'll count the buttons down, and if we get a release
3076 					static int buttonsDown = 0;
3077 					if(!isRelease && buttonNumber <= 3) // exclude wheel "presses"...
3078 						buttonsDown++;
3079 
3080 					if(isRelease && m.eventType != MouseEvent.Type.Moved) {
3081 						if(buttonsDown)
3082 							buttonsDown--;
3083 						else // no buttons down, so this should be a motion instead..
3084 							m.eventType = MouseEvent.Type.Moved;
3085 					}
3086 
3087 
3088 					if(buttonNumber == 0)
3089 						m.buttons = 0; // we don't actually know :(
3090 					else
3091 						m.buttons = 1 << (buttonNumber - 1); // I prefer flags so that's how we do it
3092 					m.x = x;
3093 					m.y = y;
3094 					m.modifierState = modifiers;
3095 
3096 					return [InputEvent(m, terminal)];
3097 				default:
3098 					// screen doesn't actually do the modifiers, but
3099 					// it uses the same format so this branch still works fine.
3100 					if(terminal.terminalInFamily("xterm", "screen", "tmux")) {
3101 						import std.conv, std..string;
3102 						auto terminator = sequence[$ - 1];
3103 						auto parts = sequence[2 .. $ - 1].split(";");
3104 						// parts[0] and terminator tells us the key
3105 						// parts[1] tells us the modifierState
3106 
3107 						uint modifierState;
3108 
3109 						int modGot;
3110 						if(parts.length > 1)
3111 							modGot = to!int(parts[1]);
3112 						mod_switch: switch(modGot) {
3113 							case 2: modifierState |= ModifierState.shift; break;
3114 							case 3: modifierState |= ModifierState.alt; break;
3115 							case 4: modifierState |= ModifierState.shift | ModifierState.alt; break;
3116 							case 5: modifierState |= ModifierState.control; break;
3117 							case 6: modifierState |= ModifierState.shift | ModifierState.control; break;
3118 							case 7: modifierState |= ModifierState.alt | ModifierState.control; break;
3119 							case 8: modifierState |= ModifierState.shift | ModifierState.alt | ModifierState.control; break;
3120 							case 9:
3121 							..
3122 							case 16:
3123 								modifierState |= ModifierState.meta;
3124 								if(modGot != 9) {
3125 									modGot -= 8;
3126 									goto mod_switch;
3127 								}
3128 							break;
3129 
3130 							// this is an extension in my own terminal emulator
3131 							case 20:
3132 							..
3133 							case 36:
3134 								modifierState |= ModifierState.windows;
3135 								modGot -= 20;
3136 								goto mod_switch;
3137 							default:
3138 						}
3139 
3140 						switch(terminator) {
3141 							case 'A': return keyPressAndRelease(NonCharacterKeyEvent.Key.UpArrow, modifierState);
3142 							case 'B': return keyPressAndRelease(NonCharacterKeyEvent.Key.DownArrow, modifierState);
3143 							case 'C': return keyPressAndRelease(NonCharacterKeyEvent.Key.RightArrow, modifierState);
3144 							case 'D': return keyPressAndRelease(NonCharacterKeyEvent.Key.LeftArrow, modifierState);
3145 
3146 							case 'H': return keyPressAndRelease(NonCharacterKeyEvent.Key.Home, modifierState);
3147 							case 'F': return keyPressAndRelease(NonCharacterKeyEvent.Key.End, modifierState);
3148 
3149 							case 'P': return keyPressAndRelease(NonCharacterKeyEvent.Key.F1, modifierState);
3150 							case 'Q': return keyPressAndRelease(NonCharacterKeyEvent.Key.F2, modifierState);
3151 							case 'R': return keyPressAndRelease(NonCharacterKeyEvent.Key.F3, modifierState);
3152 							case 'S': return keyPressAndRelease(NonCharacterKeyEvent.Key.F4, modifierState);
3153 
3154 							case '~': // others
3155 								switch(parts[0]) {
3156 									case "1": return keyPressAndRelease(NonCharacterKeyEvent.Key.Home, modifierState);
3157 									case "4": return keyPressAndRelease(NonCharacterKeyEvent.Key.End, modifierState);
3158 									case "5": return keyPressAndRelease(NonCharacterKeyEvent.Key.PageUp, modifierState);
3159 									case "6": return keyPressAndRelease(NonCharacterKeyEvent.Key.PageDown, modifierState);
3160 									case "2": return keyPressAndRelease(NonCharacterKeyEvent.Key.Insert, modifierState);
3161 									case "3": return keyPressAndRelease(NonCharacterKeyEvent.Key.Delete, modifierState);
3162 
3163 									case "15": return keyPressAndRelease(NonCharacterKeyEvent.Key.F5, modifierState);
3164 									case "17": return keyPressAndRelease(NonCharacterKeyEvent.Key.F6, modifierState);
3165 									case "18": return keyPressAndRelease(NonCharacterKeyEvent.Key.F7, modifierState);
3166 									case "19": return keyPressAndRelease(NonCharacterKeyEvent.Key.F8, modifierState);
3167 									case "20": return keyPressAndRelease(NonCharacterKeyEvent.Key.F9, modifierState);
3168 									case "21": return keyPressAndRelease(NonCharacterKeyEvent.Key.F10, modifierState);
3169 									case "23": return keyPressAndRelease(NonCharacterKeyEvent.Key.F11, modifierState);
3170 									case "24": return keyPressAndRelease(NonCharacterKeyEvent.Key.F12, modifierState);
3171 
3172 									// starting at 70 i do some magic for like shift+enter etc.
3173 									// this only happens on my own terminal emulator.
3174 									case "70": return keyPressAndRelease(NonCharacterKeyEvent.Key.ScrollLock, modifierState);
3175 									case "78": return keyPressAndRelease2('\b', modifierState);
3176 									case "79": return keyPressAndRelease2('\t', modifierState);
3177 									case "83": return keyPressAndRelease2('\n', modifierState);
3178 									default:
3179 								}
3180 							break;
3181 
3182 							default:
3183 						}
3184 					} else if(terminal.terminalInFamily("rxvt")) {
3185 						// look it up in the termcap key database
3186 						string cap = terminal.findSequenceInTermcap(sequence);
3187 						if(cap !is null) {
3188 						//terminal.writeln("found in termcap " ~ cap);
3189 							return translateTermcapName(cap);
3190 						}
3191 						// FIXME: figure these out. rxvt seems to just change the terminator while keeping the rest the same
3192 						// though it isn't consistent. ugh.
3193 					} else {
3194 						// 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
3195 						// so this space is semi-intentionally left blank
3196 						//terminal.writeln("wtf ", sequence[1..$]);
3197 
3198 						// look it up in the termcap key database
3199 						string cap = terminal.findSequenceInTermcap(sequence);
3200 						if(cap !is null) {
3201 						//terminal.writeln("found in termcap " ~ cap);
3202 							return translateTermcapName(cap);
3203 						}
3204 					}
3205 			}
3206 
3207 			return null;
3208 		}
3209 
3210 		auto c = remainingFromLastTime == int.max ? nextRaw(true) : remainingFromLastTime;
3211 		if(c == -1)
3212 			return null; // interrupted; give back nothing so the other level can recheck signal flags
3213 		if(c == 0)
3214 			return [InputEvent(EndOfFileEvent(), terminal)];
3215 		if(c == '\033') {
3216 			if(timedCheckForInput_bypassingBuffer(50)) {
3217 				// escape sequence
3218 				c = nextRaw();
3219 				if(c == '[') { // CSI, ends on anything >= 'A'
3220 					return doEscapeSequence(readEscapeSequence(sequenceBuffer));
3221 				} else if(c == 'O') {
3222 					// could be xterm function key
3223 					auto n = nextRaw();
3224 
3225 					char[3] thing;
3226 					thing[0] = '\033';
3227 					thing[1] = 'O';
3228 					thing[2] = cast(char) n;
3229 
3230 					auto cap = terminal.findSequenceInTermcap(thing);
3231 					if(cap is null) {
3232 						return keyPressAndRelease(NonCharacterKeyEvent.Key.escape) ~
3233 							charPressAndRelease('O') ~
3234 							charPressAndRelease(thing[2]);
3235 					} else {
3236 						return translateTermcapName(cap);
3237 					}
3238 				} else if(c == '\033') {
3239 					// could be escape followed by an escape sequence!
3240 					return keyPressAndRelease(NonCharacterKeyEvent.Key.escape) ~ readNextEventsHelper(c);
3241 				} else {
3242 					// I don't know, probably unsupported terminal or just quick user input or something
3243 					return keyPressAndRelease(NonCharacterKeyEvent.Key.escape) ~ charPressAndRelease(nextChar(c));
3244 				}
3245 			} else {
3246 				// user hit escape (or super slow escape sequence, but meh)
3247 				return keyPressAndRelease(NonCharacterKeyEvent.Key.escape);
3248 			}
3249 		} else {
3250 			// FIXME: what if it is neither? we should check the termcap
3251 			auto next = nextChar(c);
3252 			if(next == 127) // some terminals send 127 on the backspace. Let's normalize that.
3253 				next = '\b';
3254 			return charPressAndRelease(next);
3255 		}
3256 	}
3257 }
3258 
3259 /// The new style of keyboard event
3260 struct KeyboardEvent {
3261 	bool pressed; ///
3262 	dchar which; ///
3263 	alias key = which; /// I often use this when porting old to new so i took it
3264 	alias character = which; /// I often use this when porting old to new so i took it
3265 	uint modifierState; ///
3266 
3267 	///
3268 	bool isCharacter() {
3269 		return !(which >= Key.min && which <= Key.max);
3270 	}
3271 
3272 	// these match Windows virtual key codes numerically for simplicity of translation there
3273 	// but are plus a unicode private use area offset so i can cram them in the dchar
3274 	// http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731%28v=vs.85%29.aspx
3275 	/// .
3276 	enum Key : dchar {
3277 		escape = 0x1b + 0xF0000, /// .
3278 		F1 = 0x70 + 0xF0000, /// .
3279 		F2 = 0x71 + 0xF0000, /// .
3280 		F3 = 0x72 + 0xF0000, /// .
3281 		F4 = 0x73 + 0xF0000, /// .
3282 		F5 = 0x74 + 0xF0000, /// .
3283 		F6 = 0x75 + 0xF0000, /// .
3284 		F7 = 0x76 + 0xF0000, /// .
3285 		F8 = 0x77 + 0xF0000, /// .
3286 		F9 = 0x78 + 0xF0000, /// .
3287 		F10 = 0x79 + 0xF0000, /// .
3288 		F11 = 0x7A + 0xF0000, /// .
3289 		F12 = 0x7B + 0xF0000, /// .
3290 		LeftArrow = 0x25 + 0xF0000, /// .
3291 		RightArrow = 0x27 + 0xF0000, /// .
3292 		UpArrow = 0x26 + 0xF0000, /// .
3293 		DownArrow = 0x28 + 0xF0000, /// .
3294 		Insert = 0x2d + 0xF0000, /// .
3295 		Delete = 0x2e + 0xF0000, /// .
3296 		Home = 0x24 + 0xF0000, /// .
3297 		End = 0x23 + 0xF0000, /// .
3298 		PageUp = 0x21 + 0xF0000, /// .
3299 		PageDown = 0x22 + 0xF0000, /// .
3300 		ScrollLock = 0x91 + 0xF0000, /// unlikely to work outside my custom terminal emulator
3301 	}
3302 
3303 
3304 }
3305 
3306 /// Deprecated: use KeyboardEvent instead in new programs
3307 /// Input event for characters
3308 struct CharacterEvent {
3309 	/// .
3310 	enum Type {
3311 		Released, /// .
3312 		Pressed /// .
3313 	}
3314 
3315 	Type eventType; /// .
3316 	dchar character; /// .
3317 	uint modifierState; /// Don't depend on this to be available for character events
3318 }
3319 
3320 /// Deprecated: use KeyboardEvent instead in new programs
3321 struct NonCharacterKeyEvent {
3322 	/// .
3323 	enum Type {
3324 		Released, /// .
3325 		Pressed /// .
3326 	}
3327 	Type eventType; /// .
3328 
3329 	// these match Windows virtual key codes numerically for simplicity of translation there
3330 	//http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731%28v=vs.85%29.aspx
3331 	/// .
3332 	enum Key : int {
3333 		escape = 0x1b, /// .
3334 		F1 = 0x70, /// .
3335 		F2 = 0x71, /// .
3336 		F3 = 0x72, /// .
3337 		F4 = 0x73, /// .
3338 		F5 = 0x74, /// .
3339 		F6 = 0x75, /// .
3340 		F7 = 0x76, /// .
3341 		F8 = 0x77, /// .
3342 		F9 = 0x78, /// .
3343 		F10 = 0x79, /// .
3344 		F11 = 0x7A, /// .
3345 		F12 = 0x7B, /// .
3346 		LeftArrow = 0x25, /// .
3347 		RightArrow = 0x27, /// .
3348 		UpArrow = 0x26, /// .
3349 		DownArrow = 0x28, /// .
3350 		Insert = 0x2d, /// .
3351 		Delete = 0x2e, /// .
3352 		Home = 0x24, /// .
3353 		End = 0x23, /// .
3354 		PageUp = 0x21, /// .
3355 		PageDown = 0x22, /// .
3356 		ScrollLock = 0x91, /// unlikely to work outside my terminal emulator
3357 		}
3358 	Key key; /// .
3359 
3360 	uint modifierState; /// A mask of ModifierState. Always use by checking modifierState & ModifierState.something, the actual value differs across platforms
3361 
3362 }
3363 
3364 /// .
3365 struct PasteEvent {
3366 	string pastedText; /// .
3367 }
3368 
3369 /++
3370 	Indicates a hyperlink was clicked in my custom terminal emulator
3371 	or with version `TerminalDirectToEmulator`.
3372 
3373 	You can simply ignore this event in a `final switch` if you aren't
3374 	using the feature.
3375 
3376 	History:
3377 		Added March 18, 2020
3378 +/
3379 struct LinkEvent {
3380 	string text; ///
3381 	ushort identifier; ///
3382 	ushort command; /// set by the terminal to indicate how it was clicked. values tbd
3383 }
3384 
3385 /// .
3386 struct MouseEvent {
3387 	// these match simpledisplay.d numerically as well
3388 	/// .
3389 	enum Type {
3390 		Moved = 0, /// .
3391 		Pressed = 1, /// .
3392 		Released = 2, /// .
3393 		Clicked, /// .
3394 	}
3395 
3396 	Type eventType; /// .
3397 
3398 	// note: these should numerically match simpledisplay.d for maximum beauty in my other code
3399 	/// .
3400 	enum Button : uint {
3401 		None = 0, /// .
3402 		Left = 1, /// .
3403 		Middle = 4, /// .
3404 		Right = 2, /// .
3405 		ScrollUp = 8, /// .
3406 		ScrollDown = 16 /// .
3407 	}
3408 	uint buttons; /// A mask of Button
3409 	int x; /// 0 == left side
3410 	int y; /// 0 == top
3411 	uint modifierState; /// shift, ctrl, alt, meta, altgr. Not always available. Always check by using modifierState & ModifierState.something
3412 }
3413 
3414 /// When you get this, check terminal.width and terminal.height to see the new size and react accordingly.
3415 struct SizeChangedEvent {
3416 	int oldWidth;
3417 	int oldHeight;
3418 	int newWidth;
3419 	int newHeight;
3420 }
3421 
3422 /// the user hitting ctrl+c will send this
3423 /// You should drop what you're doing and perhaps exit when this happens.
3424 struct UserInterruptionEvent {}
3425 
3426 /// If the user hangs up (for example, closes the terminal emulator without exiting the app), this is sent.
3427 /// If you receive it, you should generally cleanly exit.
3428 struct HangupEvent {}
3429 
3430 /// Sent upon receiving end-of-file from stdin.
3431 struct EndOfFileEvent {}
3432 
3433 interface CustomEvent {}
3434 
3435 version(Win32Console)
3436 enum ModifierState : uint {
3437 	shift = 0x10,
3438 	control = 0x8 | 0x4, // 8 == left ctrl, 4 == right ctrl
3439 
3440 	// i'm not sure if the next two are available
3441 	alt = 2 | 1, //2 ==left alt, 1 == right alt
3442 
3443 	// FIXME: I don't think these are actually available
3444 	windows = 512,
3445 	meta = 4096, // FIXME sanity
3446 
3447 	// I don't think this is available on Linux....
3448 	scrollLock = 0x40,
3449 }
3450 else
3451 enum ModifierState : uint {
3452 	shift = 4,
3453 	alt = 2,
3454 	control = 16,
3455 	meta = 8,
3456 
3457 	windows = 512 // only available if you are using my terminal emulator; it isn't actually offered on standard linux ones
3458 }
3459 
3460 version(DDoc)
3461 ///
3462 enum ModifierState : uint {
3463 	///
3464 	shift = 4,
3465 	///
3466 	alt = 2,
3467 	///
3468 	control = 16,
3469 
3470 }
3471 
3472 /++
3473 	[RealTimeConsoleInput.nextEvent] returns one of these. Check the type, then use the [InputEvent.get|get] method to get the more detailed information about the event.
3474 ++/
3475 struct InputEvent {
3476 	/// .
3477 	enum Type {
3478 		KeyboardEvent, /// Keyboard key pressed (or released, where supported)
3479 		CharacterEvent, /// Do not use this in new programs, use KeyboardEvent instead
3480 		NonCharacterKeyEvent, /// Do not use this in new programs, use KeyboardEvent instead
3481 		PasteEvent, /// The user pasted some text. Not always available, the pasted text might come as a series of character events instead.
3482 		LinkEvent, /// User clicked a hyperlink you created. Simply ignore if you are not using that feature.
3483 		MouseEvent, /// only sent if you subscribed to mouse events
3484 		SizeChangedEvent, /// only sent if you subscribed to size events
3485 		UserInterruptionEvent, /// the user hit ctrl+c
3486 		EndOfFileEvent, /// stdin has received an end of file
3487 		HangupEvent, /// the terminal hanged up - for example, if the user closed a terminal emulator
3488 		CustomEvent /// .
3489 	}
3490 
3491 	/// If this event is deprecated, you should filter it out in new programs
3492 	bool isDeprecated() {
3493 		return type == Type.CharacterEvent || type == Type.NonCharacterKeyEvent;
3494 	}
3495 
3496 	/// .
3497 	@property Type type() { return t; }
3498 
3499 	/// Returns a pointer to the terminal associated with this event.
3500 	/// (You can usually just ignore this as there's only one terminal typically.)
3501 	///
3502 	/// It may be null in the case of program-generated events;
3503 	@property Terminal* terminal() { return term; }
3504 
3505 	/++
3506 		Gets the specific event instance. First, check the type (such as in a `switch` statement), then extract the correct one from here. Note that the template argument is a $(B value type of the enum above), not a type argument. So to use it, do $(D event.get!(InputEvent.Type.KeyboardEvent)), for example.
3507 
3508 		See_Also:
3509 
3510 		The event types:
3511 			[KeyboardEvent], [MouseEvent], [SizeChangedEvent],
3512 			[PasteEvent], [UserInterruptionEvent], 
3513 			[EndOfFileEvent], [HangupEvent], [CustomEvent]
3514 
3515 		And associated functions:
3516 			[RealTimeConsoleInput], [ConsoleInputFlags]
3517 	++/
3518 	@property auto get(Type T)() {
3519 		if(type != T)
3520 			throw new Exception("Wrong event type");
3521 		static if(T == Type.CharacterEvent)
3522 			return characterEvent;
3523 		else static if(T == Type.KeyboardEvent)
3524 			return keyboardEvent;
3525 		else static if(T == Type.NonCharacterKeyEvent)
3526 			return nonCharacterKeyEvent;
3527 		else static if(T == Type.PasteEvent)
3528 			return pasteEvent;
3529 		else static if(T == Type.LinkEvent)
3530 			return linkEvent;
3531 		else static if(T == Type.MouseEvent)
3532 			return mouseEvent;
3533 		else static if(T == Type.SizeChangedEvent)
3534 			return sizeChangedEvent;
3535 		else static if(T == Type.UserInterruptionEvent)
3536 			return userInterruptionEvent;
3537 		else static if(T == Type.EndOfFileEvent)
3538 			return endOfFileEvent;
3539 		else static if(T == Type.HangupEvent)
3540 			return hangupEvent;
3541 		else static if(T == Type.CustomEvent)
3542 			return customEvent;
3543 		else static assert(0, "Type " ~ T.stringof ~ " not added to the get function");
3544 	}
3545 
3546 	/// custom event is public because otherwise there's no point at all
3547 	this(CustomEvent c, Terminal* p = null) {
3548 		t = Type.CustomEvent;
3549 		customEvent = c;
3550 	}
3551 
3552 	private {
3553 		this(CharacterEvent c, Terminal* p) {
3554 			t = Type.CharacterEvent;
3555 			characterEvent = c;
3556 		}
3557 		this(KeyboardEvent c, Terminal* p) {
3558 			t = Type.KeyboardEvent;
3559 			keyboardEvent = c;
3560 		}
3561 		this(NonCharacterKeyEvent c, Terminal* p) {
3562 			t = Type.NonCharacterKeyEvent;
3563 			nonCharacterKeyEvent = c;
3564 		}
3565 		this(PasteEvent c, Terminal* p) {
3566 			t = Type.PasteEvent;
3567 			pasteEvent = c;
3568 		}
3569 		this(LinkEvent c, Terminal* p) {
3570 			t = Type.LinkEvent;
3571 			linkEvent = c;
3572 		}
3573 		this(MouseEvent c, Terminal* p) {
3574 			t = Type.MouseEvent;
3575 			mouseEvent = c;
3576 		}
3577 		this(SizeChangedEvent c, Terminal* p) {
3578 			t = Type.SizeChangedEvent;
3579 			sizeChangedEvent = c;
3580 		}
3581 		this(UserInterruptionEvent c, Terminal* p) {
3582 			t = Type.UserInterruptionEvent;
3583 			userInterruptionEvent = c;
3584 		}
3585 		this(HangupEvent c, Terminal* p) {
3586 			t = Type.HangupEvent;
3587 			hangupEvent = c;
3588 		}
3589 		this(EndOfFileEvent c, Terminal* p) {
3590 			t = Type.EndOfFileEvent;
3591 			endOfFileEvent = c;
3592 		}
3593 
3594 		Type t;
3595 		Terminal* term;
3596 
3597 		union {
3598 			KeyboardEvent keyboardEvent;
3599 			CharacterEvent characterEvent;
3600 			NonCharacterKeyEvent nonCharacterKeyEvent;
3601 			PasteEvent pasteEvent;
3602 			MouseEvent mouseEvent;
3603 			SizeChangedEvent sizeChangedEvent;
3604 			UserInterruptionEvent userInterruptionEvent;
3605 			HangupEvent hangupEvent;
3606 			EndOfFileEvent endOfFileEvent;
3607 			LinkEvent linkEvent;
3608 			CustomEvent customEvent;
3609 		}
3610 	}
3611 }
3612 
3613 version(Demo)
3614 /// View the source of this!
3615 void main() {
3616 	auto terminal = Terminal(ConsoleOutputType.cellular);
3617 
3618 	//terminal.color(Color.DEFAULT, Color.DEFAULT);
3619 
3620 	//
3621 	///*
3622 	auto getter = new FileLineGetter(&terminal, "test");
3623 	getter.prompt = "> ";
3624 	getter.history = ["abcdefghijklmnopqrstuvwzyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"];
3625 	terminal.writeln("\n" ~ getter.getline());
3626 	terminal.writeln("\n" ~ getter.getline());
3627 	terminal.writeln("\n" ~ getter.getline());
3628 	getter.dispose();
3629 	//*/
3630 
3631 	terminal.writeln(terminal.getline());
3632 	terminal.writeln(terminal.getline());
3633 	terminal.writeln(terminal.getline());
3634 
3635 	//input.getch();
3636 
3637 	// return;
3638 	//
3639 
3640 	terminal.setTitle("Basic I/O");
3641 	auto input = RealTimeConsoleInput(&terminal, ConsoleInputFlags.raw | ConsoleInputFlags.allInputEventsWithRelease);
3642 	terminal.color(Color.green | Bright, Color.black);
3643 
3644 	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");
3645 	terminal.writefln("%d %d", terminal.cursorX, terminal.cursorY);
3646 
3647 	terminal.color(Color.DEFAULT, Color.DEFAULT);
3648 
3649 	int centerX = terminal.width / 2;
3650 	int centerY = terminal.height / 2;
3651 
3652 	bool timeToBreak = false;
3653 
3654 	terminal.hyperlink("test", 4);
3655 	terminal.hyperlink("another", 7);
3656 
3657 	void handleEvent(InputEvent event) {
3658 		//terminal.writef("%s\n", event.type);
3659 		final switch(event.type) {
3660 			case InputEvent.Type.LinkEvent:
3661 				auto ev = event.get!(InputEvent.Type.LinkEvent);
3662 				terminal.writeln(ev);
3663 			break;
3664 			case InputEvent.Type.UserInterruptionEvent:
3665 			case InputEvent.Type.HangupEvent:
3666 			case InputEvent.Type.EndOfFileEvent:
3667 				timeToBreak = true;
3668 				version(with_eventloop) {
3669 					import arsd.eventloop;
3670 					exit();
3671 				}
3672 			break;
3673 			case InputEvent.Type.SizeChangedEvent:
3674 				auto ev = event.get!(InputEvent.Type.SizeChangedEvent);
3675 				terminal.writeln(ev);
3676 			break;
3677 			case InputEvent.Type.KeyboardEvent:
3678 				auto ev = event.get!(InputEvent.Type.KeyboardEvent);
3679 					terminal.writef("\t%s", ev);
3680 				terminal.writef(" (%s)", cast(KeyboardEvent.Key) ev.which);
3681 				terminal.writeln();
3682 				if(ev.which == 'Q') {
3683 					timeToBreak = true;
3684 					version(with_eventloop) {
3685 						import arsd.eventloop;
3686 						exit();
3687 					}
3688 				}
3689 
3690 				if(ev.which == 'C')
3691 					terminal.clear();
3692 			break;
3693 			case InputEvent.Type.CharacterEvent: // obsolete
3694 				auto ev = event.get!(InputEvent.Type.CharacterEvent);
3695 				terminal.writef("\t%s\n", ev);
3696 			break;
3697 			case InputEvent.Type.NonCharacterKeyEvent: // obsolete
3698 				terminal.writef("\t%s\n", event.get!(InputEvent.Type.NonCharacterKeyEvent));
3699 			break;
3700 			case InputEvent.Type.PasteEvent:
3701 				terminal.writef("\t%s\n", event.get!(InputEvent.Type.PasteEvent));
3702 			break;
3703 			case InputEvent.Type.MouseEvent:
3704 				//terminal.writef("\t%s\n", event.get!(InputEvent.Type.MouseEvent));
3705 			break;
3706 			case InputEvent.Type.CustomEvent:
3707 			break;
3708 		}
3709 
3710 		//terminal.writefln("%d %d", terminal.cursorX, terminal.cursorY);
3711 
3712 		/*
3713 		if(input.kbhit()) {
3714 			auto c = input.getch();
3715 			if(c == 'q' || c == 'Q')
3716 				break;
3717 			terminal.moveTo(centerX, centerY);
3718 			terminal.writef("%c", c);
3719 			terminal.flush();
3720 		}
3721 		usleep(10000);
3722 		*/
3723 	}
3724 
3725 	version(with_eventloop) {
3726 		import arsd.eventloop;
3727 		addListener(&handleEvent);
3728 		loop();
3729 	} else {
3730 		loop: while(true) {
3731 			auto event = input.nextEvent();
3732 			handleEvent(event);
3733 			if(timeToBreak)
3734 				break loop;
3735 		}
3736 	}
3737 }
3738 
3739 enum TerminalCapabilities : uint {
3740 	minimal = 0,
3741 	vt100 = 1 << 0,
3742 
3743 	// my special terminal emulator extensions
3744 	arsdClipboard = 1 << 15, // 90 in caps
3745 	arsdImage = 1 << 16, // 91 in caps
3746 	arsdHyperlinks = 1 << 17, // 92 in caps
3747 }
3748 
3749 version(Posix)
3750 private uint /* TerminalCapabilities bitmask */ getTerminalCapabilities(int fdIn, int fdOut) {
3751 	if(fdIn == -1 || fdOut == -1)
3752 		return TerminalCapabilities.minimal;
3753 
3754 	import std.conv;
3755 	import core.stdc.errno;
3756 	import core.sys.posix.unistd;
3757 
3758 	ubyte[128] hack2;
3759 	termios old;
3760 	ubyte[128] hack;
3761 	tcgetattr(fdIn, &old);
3762 	auto n = old;
3763 	n.c_lflag &= ~(ICANON | ECHO);
3764 	tcsetattr(fdIn, TCSANOW, &n);
3765 	scope(exit)
3766 		tcsetattr(fdIn, TCSANOW, &old);
3767 
3768 	// drain the buffer? meh
3769 
3770 	string cmd = "\033[c";
3771 	auto err = write(fdOut, cmd.ptr, cmd.length);
3772 	if(err != cmd.length) {
3773 		throw new Exception("couldn't ask terminal for ID");
3774 	}
3775 
3776 	// reading directly to bypass any buffering
3777 	int retries = 16;
3778 	int len;
3779 	ubyte[96] buffer;
3780 	try_again:
3781 
3782 
3783 	timeval tv;
3784 	tv.tv_sec = 0;
3785 	tv.tv_usec = 250 * 1000; // 250 ms
3786 
3787 	fd_set fs;
3788 	FD_ZERO(&fs);
3789 
3790 	FD_SET(fdIn, &fs);
3791 	if(select(fdIn + 1, &fs, null, null, &tv) == -1) {
3792 		goto try_again;
3793 	}
3794 
3795 	if(FD_ISSET(fdIn, &fs)) {
3796 		auto len2 = read(fdIn, &buffer[len], buffer.length - len);
3797 		if(len2 <= 0) {
3798 			retries--;
3799 			if(retries > 0)
3800 				goto try_again;
3801 			throw new Exception("can't get terminal id");
3802 		} else {
3803 			len += len2;
3804 		}
3805 	} else {
3806 		// no data... assume terminal doesn't support giving an answer
3807 		return TerminalCapabilities.minimal;
3808 	}
3809 
3810 	ubyte[] answer;
3811 	bool hasAnswer(ubyte[] data) {
3812 		if(data.length < 4)
3813 			return false;
3814 		answer = null;
3815 		size_t start;
3816 		int position = 0;
3817 		foreach(idx, ch; data) {
3818 			switch(position) {
3819 				case 0:
3820 					if(ch == '\033') {
3821 						start = idx;
3822 						position++;
3823 					}
3824 				break;
3825 				case 1:
3826 					if(ch == '[')
3827 						position++;
3828 					else
3829 						position = 0;
3830 				break;
3831 				case 2:
3832 					if(ch == '?')
3833 						position++;
3834 					else
3835 						position = 0;
3836 				break;
3837 				case 3:
3838 					// body
3839 					if(ch == 'c') {
3840 						answer = data[start .. idx + 1];
3841 						return true;
3842 					} else if(ch == ';' || (ch >= '0' && ch <= '9')) {
3843 						// good, keep going
3844 					} else {
3845 						// invalid, drop it
3846 						position = 0;
3847 					}
3848 				break;
3849 				default: assert(0);
3850 			}
3851 		}
3852 		return false;
3853 	}
3854 
3855 	auto got = buffer[0 .. len];
3856 	if(!hasAnswer(got)) {
3857 		goto try_again;
3858 	}
3859 	auto gots = cast(char[]) answer[3 .. $-1];
3860 
3861 	import std..string;
3862 
3863 	auto pieces = split(gots, ";");
3864 	uint ret = TerminalCapabilities.vt100;
3865 	foreach(p; pieces)
3866 		switch(p) {
3867 			case "90":
3868 				ret |= TerminalCapabilities.arsdClipboard;
3869 			break;
3870 			case "91":
3871 				ret |= TerminalCapabilities.arsdImage;
3872 			break;
3873 			case "92":
3874 				ret |= TerminalCapabilities.arsdHyperlinks;
3875 			break;
3876 			default:
3877 		}
3878 	return ret;
3879 }
3880 
3881 private extern(C) int mkstemp(char *templ);
3882 
3883 /**
3884 	FIXME: support lines that wrap
3885 	FIXME: better controls maybe
3886 
3887 	FIXME: support multi-line "lines" and some form of line continuation, both
3888 	       from the user (if permitted) and from the application, so like the user
3889 	       hits "class foo { \n" and the app says "that line needs continuation" automatically.
3890 
3891 	FIXME: fix lengths on prompt and suggestion
3892 
3893 	A note on history:
3894 
3895 	To save history, you must call LineGetter.dispose() when you're done with it.
3896 	History will not be automatically saved without that call!
3897 
3898 	The history saving and loading as a trivially encountered race condition: if you
3899 	open two programs that use the same one at the same time, the one that closes second
3900 	will overwrite any history changes the first closer saved.
3901 
3902 	GNU Getline does this too... and it actually kinda drives me nuts. But I don't know
3903 	what a good fix is except for doing a transactional commit straight to the file every
3904 	time and that seems like hitting the disk way too often.
3905 
3906 	We could also do like a history server like a database daemon that keeps the order
3907 	correct but I don't actually like that either because I kinda like different bashes
3908 	to have different history, I just don't like it all to get lost.
3909 
3910 	Regardless though, this isn't even used in bash anyway, so I don't think I care enough
3911 	to put that much effort into it. Just using separate files for separate tasks is good
3912 	enough I think.
3913 */
3914 class LineGetter {
3915 	/* A note on the assumeSafeAppends in here: since these buffers are private, we can be
3916 	   pretty sure that stomping isn't an issue, so I'm using this liberally to keep the
3917 	   append/realloc code simple and hopefully reasonably fast. */
3918 
3919 	// saved to file
3920 	string[] history;
3921 
3922 	// not saved
3923 	Terminal* terminal;
3924 	string historyFilename;
3925 
3926 	/// Make sure that the parent terminal struct remains in scope for the duration
3927 	/// of LineGetter's lifetime, as it does hold on to and use the passed pointer
3928 	/// throughout.
3929 	///
3930 	/// historyFilename will load and save an input history log to a particular folder.
3931 	/// Leaving it null will mean no file will be used and history will not be saved across sessions.
3932 	this(Terminal* tty, string historyFilename = null) {
3933 		this.terminal = tty;
3934 		this.historyFilename = historyFilename;
3935 
3936 		line.reserve(128);
3937 
3938 		if(historyFilename.length)
3939 			loadSettingsAndHistoryFromFile();
3940 
3941 		regularForeground = cast(Color) terminal._currentForeground;
3942 		background = cast(Color) terminal._currentBackground;
3943 		suggestionForeground = Color.blue;
3944 	}
3945 
3946 	/// Call this before letting LineGetter die so it can do any necessary
3947 	/// cleanup and save the updated history to a file.
3948 	void dispose() {
3949 		if(historyFilename.length)
3950 			saveSettingsAndHistoryToFile();
3951 	}
3952 
3953 	/// Override this to change the directory where history files are stored
3954 	///
3955 	/// Default is $HOME/.arsd-getline on linux and %APPDATA%/arsd-getline/ on Windows.
3956 	/* virtual */ string historyFileDirectory() {
3957 		version(Windows) {
3958 			char[1024] path;
3959 			// FIXME: this doesn't link because the crappy dmd lib doesn't have it
3960 			if(0) { // SHGetFolderPathA(null, CSIDL_APPDATA, null, 0, path.ptr) >= 0) {
3961 				import core.stdc..string;
3962 				return cast(string) path[0 .. strlen(path.ptr)] ~ "\\arsd-getline";
3963 			} else {
3964 				import std.process;
3965 				return environment["APPDATA"] ~ "\\arsd-getline";
3966 			}
3967 		} else version(Posix) {
3968 			import std.process;
3969 			return environment["HOME"] ~ "/.arsd-getline";
3970 		}
3971 	}
3972 
3973 	/// You can customize the colors here. You should set these after construction, but before
3974 	/// calling startGettingLine or getline.
3975 	Color suggestionForeground = Color.blue;
3976 	Color regularForeground = Color.DEFAULT; /// ditto
3977 	Color background = Color.DEFAULT; /// ditto
3978 	Color promptColor = Color.DEFAULT; /// ditto
3979 	Color specialCharBackground = Color.green; /// ditto
3980 	//bool reverseVideo;
3981 
3982 	/// Set this if you want a prompt to be drawn with the line. It does NOT support color in string.
3983 	@property void prompt(string p) {
3984 		this.prompt_ = p;
3985 
3986 		promptLength = 0;
3987 		foreach(dchar c; p)
3988 			promptLength++;
3989 	}
3990 
3991 	/// ditto
3992 	@property string prompt() {
3993 		return this.prompt_;
3994 	}
3995 
3996 	private string prompt_;
3997 	private int promptLength;
3998 
3999 	/++
4000 		Turn on auto suggest if you want a greyed thing of what tab
4001 		would be able to fill in as you type.
4002 
4003 		You might want to turn it off if generating a completion list is slow.
4004 
4005 		Or if you know you want it, be sure to turn it on explicitly in your
4006 		code because I reserve the right to change the default without advance notice.
4007 
4008 		History:
4009 			On March 4, 2020, I changed the default to `false` because it
4010 			is kinda slow and not useful in all cases.
4011 	+/
4012 	bool autoSuggest = false;
4013 
4014 	/++
4015 		Returns true if there was any input in the buffer. Can be
4016 		checked in the case of a [UserInterruptionException].
4017 	+/
4018 	bool hadInput() {
4019 		return line.length > 0;
4020 	}
4021 
4022 	/// Override this if you don't want all lines added to the history.
4023 	/// You can return null to not add it at all, or you can transform it.
4024 	/* virtual */ string historyFilter(string candidate) {
4025 		return candidate;
4026 	}
4027 
4028 	/// You may override this to do nothing
4029 	/* virtual */ void saveSettingsAndHistoryToFile() {
4030 		import std.file;
4031 		if(!exists(historyFileDirectory))
4032 			mkdir(historyFileDirectory);
4033 		auto fn = historyPath();
4034 		import std.stdio;
4035 		auto file = File(fn, "wt");
4036 		foreach(item; history)
4037 			file.writeln(item);
4038 	}
4039 
4040 	/++
4041 		History:
4042 			Introduced on January 31, 2020
4043 	+/
4044 	/* virtual */ string historyFileExtension() {
4045 		return ".history";
4046 	}
4047 
4048 	private string historyPath() {
4049 		import std.path;
4050 		auto filename = historyFileDirectory() ~ dirSeparator ~ historyFilename ~ historyFileExtension();
4051 		return filename;
4052 	}
4053 
4054 	/// You may override this to do nothing
4055 	/* virtual */ void loadSettingsAndHistoryFromFile() {
4056 		import std.file;
4057 		history = null;
4058 		auto fn = historyPath();
4059 		if(exists(fn)) {
4060 			import std.stdio;
4061 			foreach(line; File(fn, "rt").byLine)
4062 				history ~= line.idup;
4063 
4064 		}
4065 	}
4066 
4067 	/++
4068 		Override this to provide tab completion. You may use the candidate
4069 		argument to filter the list, but you don't have to (LineGetter will
4070 		do it for you on the values you return). This means you can ignore
4071 		the arguments if you like.
4072 
4073 		Ideally, you wouldn't return more than about ten items since the list
4074 		gets difficult to use if it is too long.
4075 
4076 		Tab complete cannot modify text before or after the cursor at this time.
4077 		I *might* change that later to allow tab complete to fuzzy search and spell
4078 		check fix before. But right now it ONLY inserts.
4079 
4080 		Default is to provide recent command history as autocomplete.
4081 
4082 		Returns:
4083 			This function should return the full string to replace
4084 			`candidate[tabCompleteStartPoint(args) .. $]`.
4085 			For example, if your user wrote `wri<tab>` and you want to complete
4086 			it to `write` or `writeln`, you should return `["write", "writeln"]`.
4087 
4088 			If you offer different tab complete in different places, you still
4089 			need to return the whole string. For example, a file competition of
4090 			a second argument, when the user writes `terminal.d term<tab>` and you
4091 			want it to complete to an additional `terminal.d`, you should return
4092 			`["terminal.d terminal.d"]`; in other words, `candidate ~ completion`
4093 			for each completion.
4094 
4095 			It does this so you can simply return an array of words without having
4096 			to rebuild that array for each combination.
4097 
4098 			To choose the word separator, override [tabCompleteStartPoint].
4099 
4100 		Params:
4101 			candidate = the text of the line up to the text cursor, after
4102 			which the completed text would be inserted
4103 
4104 			afterCursor = the remaining text after the cursor. You can inspect
4105 			this, but cannot change it - this will be appended to the line
4106 			after completion, keeping the cursor in the same relative location.
4107 
4108 		History:
4109 			Prior to January 30, 2020, this method took only one argument,
4110 			`candidate`. It now takes `afterCursor` as well, to allow you to
4111 			make more intelligent completions with full context.
4112 	+/
4113 	/* virtual */ protected string[] tabComplete(in dchar[] candidate, in dchar[] afterCursor) {
4114 		return history.length > 20 ? history[0 .. 20] : history;
4115 	}
4116 
4117 	/++
4118 		Override this to provide a different tab competition starting point. The default
4119 		is `0`, always completing the complete line, but you may return the index of another
4120 		character of `candidate` to provide a new split.
4121 
4122 		Returns:
4123 			The index of `candidate` where we should start the slice to keep in [tabComplete].
4124 			It must be `>= 0 && <= candidate.length`.
4125 
4126 		History:
4127 			Added on February 1, 2020. Initial default is to return 0 to maintain
4128 			old behavior.
4129 	+/
4130 	/* virtual */ protected size_t tabCompleteStartPoint(in dchar[] candidate, in dchar[] afterCursor) {
4131 		return 0;
4132 	}
4133 
4134 	/++
4135 		This gives extra information for an item when displaying tab competition details.
4136 
4137 		History:
4138 			Added January 31, 2020.
4139 
4140 	+/
4141 	/* virtual */ protected string tabCompleteHelp(string candidate) {
4142 		return null;
4143 	}
4144 
4145 	private string[] filterTabCompleteList(string[] list, size_t start) {
4146 		if(list.length == 0)
4147 			return list;
4148 
4149 		string[] f;
4150 		f.reserve(list.length);
4151 
4152 		foreach(item; list) {
4153 			import std.algorithm;
4154 			if(startsWith(item, line[start .. cursorPosition]))
4155 				f ~= item;
4156 		}
4157 
4158 		/+
4159 		// if it is excessively long, let's trim it down by trying to
4160 		// group common sub-sequences together.
4161 		if(f.length > terminal.height * 3 / 4) {
4162 			import std.algorithm;
4163 			f.sort();
4164 
4165 			// see how many can be saved by just keeping going until there is
4166 			// no more common prefix. then commit that and keep on down the list.
4167 			// since it is sorted, if there is a commonality, it should appear quickly
4168 			string[] n;
4169 			string commonality = f[0];
4170 			size_t idx = 1;
4171 			while(idx < f.length) {
4172 				auto c = commonPrefix(commonality, f[idx]);
4173 				if(c.length > cursorPosition - start) {
4174 					commonality = c;
4175 				} else {
4176 					n ~= commonality;
4177 					commonality = f[idx];
4178 				}
4179 				idx++;
4180 			}
4181 			if(commonality.length)
4182 				n ~= commonality;
4183 
4184 			if(n.length)
4185 				f = n;
4186 		}
4187 		+/
4188 
4189 		return f;
4190 	}
4191 
4192 	/++
4193 		Override this to provide a custom display of the tab completion list.
4194 
4195 		History:
4196 			Prior to January 31, 2020, it only displayed the list. After
4197 			that, it would call [tabCompleteHelp] for each candidate and display
4198 			that string (if present) as well.
4199 	+/
4200 	protected void showTabCompleteList(string[] list) {
4201 		if(list.length) {
4202 			// FIXME: allow mouse clicking of an item, that would be cool
4203 
4204 			auto start = tabCompleteStartPoint(line[0 .. cursorPosition], line[cursorPosition .. $]);
4205 
4206 			// FIXME: scroll
4207 			//if(terminal.type == ConsoleOutputType.linear) {
4208 				terminal.writeln();
4209 				foreach(item; list) {
4210 					terminal.color(suggestionForeground, background);
4211 					import std.utf;
4212 					auto idx = codeLength!char(line[start .. cursorPosition]);
4213 					terminal.write("  ", item[0 .. idx]);
4214 					terminal.color(regularForeground, background);
4215 					terminal.write(item[idx .. $]);
4216 					auto help = tabCompleteHelp(item);
4217 					if(help !is null) {
4218 						import std..string;
4219 						help = help.replace("\t", " ").replace("\n", " ").replace("\r", " ");
4220 						terminal.write("\t\t");
4221 						int remaining;
4222 						if(terminal.cursorX + 2 < terminal.width) {
4223 							remaining = terminal.width - terminal.cursorX - 2;
4224 						}
4225 						if(remaining > 8)
4226 							terminal.write(remaining < help.length ? help[0 .. remaining] : help);
4227 					}
4228 					terminal.writeln();
4229 
4230 				}
4231 				updateCursorPosition();
4232 				redraw();
4233 			//}
4234 		}
4235 	}
4236 
4237 	/++
4238 		Called by the default event loop when the user presses F1. Override
4239 		`showHelp` to change the UI, override [helpMessage] if you just want
4240 		to change the message.
4241 
4242 		History:
4243 			Introduced on January 30, 2020
4244 	+/
4245 	protected void showHelp() {
4246 		terminal.writeln();
4247 		terminal.writeln(helpMessage);
4248 		updateCursorPosition();
4249 		redraw();
4250 	}
4251 
4252 	/++
4253 		History:
4254 			Introduced on January 30, 2020
4255 	+/
4256 	protected string helpMessage() {
4257 		return "Press F2 to edit current line in your editor. F3 searches. F9 runs current line while maintaining current edit state.";
4258 	}
4259 
4260 	/++
4261 		History:
4262 			Introduced on January 30, 2020
4263 	+/
4264 	protected dchar[] editLineInEditor(in dchar[] line, in size_t cursorPosition) {
4265 		import std.conv;
4266 		import std.process;
4267 		import std.file;
4268 
4269 		char[] tmpName;
4270 
4271 		version(Windows) {
4272 			import core.stdc..string;
4273 			char[280] path;
4274 			auto l = GetTempPathA(cast(DWORD) path.length, path.ptr);
4275 			if(l == 0) throw new Exception("GetTempPathA");
4276 			path[l] = 0;
4277 			char[280] name;
4278 			auto r = GetTempFileNameA(path.ptr, "adr", 0, name.ptr);
4279 			if(r == 0) throw new Exception("GetTempFileNameA");
4280 			tmpName = name[0 .. strlen(name.ptr)];
4281 			scope(exit)
4282 				std.file.remove(tmpName);
4283 			std.file.write(tmpName, to!string(line));
4284 
4285 			string editor = environment.get("EDITOR", "notepad.exe");
4286 		} else {
4287 			import core.stdc.stdlib;
4288 			import core.sys.posix.unistd;
4289 			char[120] name;
4290 			string p = "/tmp/adrXXXXXX";
4291 			name[0 .. p.length] = p[];
4292 			name[p.length] = 0;
4293 			auto fd = mkstemp(name.ptr);
4294 			tmpName = name[0 .. p.length];
4295 			if(fd == -1) throw new Exception("mkstemp");
4296 			scope(exit)
4297 				close(fd);
4298 			scope(exit)
4299 				std.file.remove(tmpName);
4300 
4301 			string s = to!string(line);
4302 			while(s.length) {
4303 				auto x = write(fd, s.ptr, s.length);
4304 				if(x == -1) throw new Exception("write");
4305 				s = s[x .. $];
4306 			}
4307 			string editor = environment.get("EDITOR", "vi");
4308 		}
4309 
4310 		// FIXME the spawned process changes terminal state!
4311 
4312 		spawnProcess([editor, tmpName]).wait;
4313 		import std..string;
4314 		return to!(dchar[])(cast(char[]) std.file.read(tmpName)).chomp;
4315 	}
4316 
4317 	//private RealTimeConsoleInput* rtci;
4318 
4319 	/// One-call shop for the main workhorse
4320 	/// If you already have a RealTimeConsoleInput ready to go, you
4321 	/// should pass a pointer to yours here. Otherwise, LineGetter will
4322 	/// make its own.
4323 	public string getline(RealTimeConsoleInput* input = null) {
4324 		startGettingLine();
4325 		if(input is null) {
4326 			auto i = RealTimeConsoleInput(terminal, ConsoleInputFlags.raw | ConsoleInputFlags.allInputEvents | ConsoleInputFlags.noEolWrap);
4327 			//rtci = &i;
4328 			//scope(exit) rtci = null;
4329 			while(workOnLine(i.nextEvent(), &i)) {}
4330 		} else {
4331 			//rtci = input;
4332 			//scope(exit) rtci = null;
4333 			while(workOnLine(input.nextEvent(), input)) {}
4334 		}
4335 		return finishGettingLine();
4336 	}
4337 
4338 	private int currentHistoryViewPosition = 0;
4339 	private dchar[] uncommittedHistoryCandidate;
4340 	void loadFromHistory(int howFarBack) {
4341 		if(howFarBack < 0)
4342 			howFarBack = 0;
4343 		if(howFarBack > history.length) // lol signed/unsigned comparison here means if i did this first, before howFarBack < 0, it would totally cycle around.
4344 			howFarBack = cast(int) history.length;
4345 		if(howFarBack == currentHistoryViewPosition)
4346 			return;
4347 		if(currentHistoryViewPosition == 0) {
4348 			// save the current line so we can down arrow back to it later
4349 			if(uncommittedHistoryCandidate.length < line.length) {
4350 				uncommittedHistoryCandidate.length = line.length;
4351 			}
4352 
4353 			uncommittedHistoryCandidate[0 .. line.length] = line[];
4354 			uncommittedHistoryCandidate = uncommittedHistoryCandidate[0 .. line.length];
4355 			uncommittedHistoryCandidate.assumeSafeAppend();
4356 		}
4357 
4358 		currentHistoryViewPosition = howFarBack;
4359 
4360 		if(howFarBack == 0) {
4361 			line.length = uncommittedHistoryCandidate.length;
4362 			line.assumeSafeAppend();
4363 			line[] = uncommittedHistoryCandidate[];
4364 		} else {
4365 			line = line[0 .. 0];
4366 			line.assumeSafeAppend();
4367 			foreach(dchar ch; history[$ - howFarBack])
4368 				line ~= ch;
4369 		}
4370 
4371 		cursorPosition = cast(int) line.length;
4372 		scrollToEnd();
4373 	}
4374 
4375 	bool insertMode = true;
4376 	bool multiLineMode = false;
4377 
4378 	private dchar[] line;
4379 	private int cursorPosition = 0;
4380 	private int horizontalScrollPosition = 0;
4381 
4382 	private void scrollToEnd() {
4383 		horizontalScrollPosition = (cast(int) line.length);
4384 		horizontalScrollPosition -= availableLineLength();
4385 		if(horizontalScrollPosition < 0)
4386 			horizontalScrollPosition = 0;
4387 	}
4388 
4389 	// used for redrawing the line in the right place
4390 	// and detecting mouse events on our line.
4391 	private int startOfLineX;
4392 	private int startOfLineY;
4393 
4394 	// private string[] cachedCompletionList;
4395 
4396 	// FIXME
4397 	// /// Note that this assumes the tab complete list won't change between actual
4398 	// /// presses of tab by the user. If you pass it a list, it will use it, but
4399 	// /// otherwise it will keep track of the last one to avoid calls to tabComplete.
4400 	private string suggestion(string[] list = null) {
4401 		import std.algorithm, std.utf;
4402 		auto relevantLineSection = line[0 .. cursorPosition];
4403 		auto start = tabCompleteStartPoint(relevantLineSection, line[cursorPosition .. $]);
4404 		relevantLineSection = relevantLineSection[start .. $];
4405 		// FIXME: see about caching the list if we easily can
4406 		if(list is null)
4407 			list = filterTabCompleteList(tabComplete(relevantLineSection, line[cursorPosition .. $]), start);
4408 
4409 		if(list.length) {
4410 			string commonality = list[0];
4411 			foreach(item; list[1 .. $]) {
4412 				commonality = commonPrefix(commonality, item);
4413 			}
4414 
4415 			if(commonality.length) {
4416 				return commonality[codeLength!char(relevantLineSection) .. $];
4417 			}
4418 		}
4419 
4420 		return null;
4421 	}
4422 
4423 	/// Adds a character at the current position in the line. You can call this too if you hook events for hotkeys or something.
4424 	/// You'll probably want to call redraw() after adding chars.
4425 	void addChar(dchar ch) {
4426 		assert(cursorPosition >= 0 && cursorPosition <= line.length);
4427 		if(cursorPosition == line.length)
4428 			line ~= ch;
4429 		else {
4430 			assert(line.length);
4431 			if(insertMode) {
4432 				line ~= ' ';
4433 				for(int i = cast(int) line.length - 2; i >= cursorPosition; i --)
4434 					line[i + 1] = line[i];
4435 			}
4436 			line[cursorPosition] = ch;
4437 		}
4438 		cursorPosition++;
4439 
4440 		if(cursorPosition >= horizontalScrollPosition + availableLineLength())
4441 			horizontalScrollPosition++;
4442 	}
4443 
4444 	/// .
4445 	void addString(string s) {
4446 		// FIXME: this could be more efficient
4447 		// but does it matter? these lines aren't super long anyway. But then again a paste could be excessively long (prolly accidental, but still)
4448 		foreach(dchar ch; s)
4449 			addChar(ch);
4450 	}
4451 
4452 	/// Deletes the character at the current position in the line.
4453 	/// You'll probably want to call redraw() after deleting chars.
4454 	void deleteChar() {
4455 		if(cursorPosition == line.length)
4456 			return;
4457 		for(int i = cursorPosition; i < line.length - 1; i++)
4458 			line[i] = line[i + 1];
4459 		line = line[0 .. $-1];
4460 		line.assumeSafeAppend();
4461 	}
4462 
4463 	///
4464 	void deleteToEndOfLine() {
4465 		line = line[0 .. cursorPosition];
4466 		line.assumeSafeAppend();
4467 		//while(cursorPosition < line.length)
4468 			//deleteChar();
4469 	}
4470 
4471 	int availableLineLength() {
4472 		return terminal.width - startOfLineX - promptLength - 1;
4473 	}
4474 
4475 	private int lastDrawLength = 0;
4476 	void redraw() {
4477 		terminal.hideCursor();
4478 		scope(exit) {
4479 			version(Win32Console) {
4480 				// on Windows, we want to make sure all
4481 				// is displayed before the cursor jumps around
4482 				terminal.flush();
4483 				terminal.showCursor();
4484 			} else {
4485 				// but elsewhere, the showCursor is itself buffered,
4486 				// so we can do it all at once for a slight speed boost
4487 				terminal.showCursor();
4488 				//import std.string; import std.stdio; writeln(terminal.writeBuffer.replace("\033", "\\e"));
4489 				terminal.flush();
4490 			}
4491 		}
4492 		terminal.moveTo(startOfLineX, startOfLineY);
4493 
4494 		auto lineLength = availableLineLength();
4495 		if(lineLength < 0)
4496 			throw new Exception("too narrow terminal to draw");
4497 
4498 		terminal.color(promptColor, background);
4499 		terminal.write(prompt);
4500 		terminal.color(regularForeground, background);
4501 
4502 		auto towrite = line[horizontalScrollPosition .. $];
4503 		auto cursorPositionToDrawX = cursorPosition - horizontalScrollPosition;
4504 		auto cursorPositionToDrawY = 0;
4505 
4506 		int written = promptLength;
4507 
4508 		void specialChar(char c) {
4509 			terminal.color(regularForeground, specialCharBackground);
4510 			terminal.write(c);
4511 			terminal.color(regularForeground, background);
4512 
4513 			written++;
4514 			lineLength--;
4515 		}
4516 
4517 		void regularChar(dchar ch) {
4518 			import std.utf;
4519 			char[4] buffer;
4520 			auto l = encode(buffer, ch);
4521 			// note the Terminal buffers it so meh
4522 			terminal.write(buffer[0 .. l]);
4523 
4524 			written++;
4525 			lineLength--;
4526 		}
4527 
4528 		// FIXME: if there is a color at the end of the line it messes up as you scroll
4529 		// FIXME: need a way to go to multi-line editing
4530 
4531 		foreach(dchar ch; towrite) {
4532 			if(lineLength == 0)
4533 				break;
4534 			switch(ch) {
4535 				case '\n': specialChar('n'); break;
4536 				case '\r': specialChar('r'); break;
4537 				case '\a': specialChar('a'); break;
4538 				case '\t': specialChar('t'); break;
4539 				case '\b': specialChar('b'); break;
4540 				case '\033': specialChar('e'); break;
4541 				default:
4542 					regularChar(ch);
4543 			}
4544 		}
4545 
4546 		string suggestion;
4547 
4548 		if(lineLength >= 0) {
4549 			suggestion = ((cursorPosition == towrite.length) && autoSuggest) ? this.suggestion() : null;
4550 			if(suggestion.length) {
4551 				terminal.color(suggestionForeground, background);
4552 				foreach(dchar ch; suggestion) {
4553 					if(lineLength == 0)
4554 						break;
4555 					regularChar(ch);
4556 				}
4557 				terminal.color(regularForeground, background);
4558 			}
4559 		}
4560 
4561 		// FIXME: graphemes
4562 
4563 		if(written < lastDrawLength)
4564 		foreach(i; written .. lastDrawLength)
4565 			terminal.write(" ");
4566 		lastDrawLength = written;
4567 
4568 		terminal.moveTo(startOfLineX + cursorPositionToDrawX + promptLength, startOfLineY + cursorPositionToDrawY);
4569 	}
4570 
4571 	/// Starts getting a new line. Call workOnLine and finishGettingLine afterward.
4572 	///
4573 	/// Make sure that you've flushed your input and output before calling this
4574 	/// function or else you might lose events or get exceptions from this.
4575 	void startGettingLine() {
4576 		// reset from any previous call first
4577 		if(!maintainBuffer) {
4578 			cursorPosition = 0;
4579 			horizontalScrollPosition = 0;
4580 			justHitTab = false;
4581 			currentHistoryViewPosition = 0;
4582 			if(line.length) {
4583 				line = line[0 .. 0];
4584 				line.assumeSafeAppend();
4585 			}
4586 		}
4587 
4588 		maintainBuffer = false;
4589 
4590 		initializeWithSize(true);
4591 
4592 		terminal.cursor = TerminalCursor.insert;
4593 		terminal.showCursor();
4594 	}
4595 
4596 	private void positionCursor() {
4597 		if(cursorPosition == 0)
4598 			horizontalScrollPosition = 0;
4599 		else if(cursorPosition == line.length)
4600 			scrollToEnd();
4601 		else {
4602 			// otherwise just try to center it in the screen
4603 			horizontalScrollPosition = cursorPosition;
4604 			horizontalScrollPosition -= terminal.width / 2;
4605 			// align on a code point boundary
4606 			aligned(horizontalScrollPosition, -1);
4607 			if(horizontalScrollPosition < 0)
4608 				horizontalScrollPosition = 0;
4609 		}
4610 	}
4611 
4612 	private void aligned(ref int what, int direction) {
4613 		// whereas line is right now dchar[] no need for this
4614 		// at least until we go by grapheme...
4615 		/*
4616 		while(what > 0 && what < line.length && ((line[what] & 0b1100_0000) == 0b1000_0000))
4617 			what += direction;
4618 		*/
4619 	}
4620 
4621 	private void initializeWithSize(bool firstEver = false) {
4622 		auto x = startOfLineX;
4623 
4624 		updateCursorPosition();
4625 
4626 		if(!firstEver) {
4627 			startOfLineX = x;
4628 			positionCursor();
4629 		}
4630 
4631 		lastDrawLength = terminal.width - terminal.cursorX;
4632 		version(Win32Console)
4633 			lastDrawLength -= 1; // I don't like this but Windows resizing is different anyway and it is liable to scroll if i go over..
4634 
4635 		redraw();
4636 	}
4637 
4638 	private void updateCursorPosition() {
4639 		terminal.flush();
4640 
4641 		// then get the current cursor position to start fresh
4642 		version(TerminalDirectToEmulator) {
4643 			startOfLineX = terminal.tew.terminalEmulator.cursorX;
4644 			startOfLineY = terminal.tew.terminalEmulator.cursorY;
4645 		} else version(Win32Console) {
4646 			CONSOLE_SCREEN_BUFFER_INFO info;
4647 			GetConsoleScreenBufferInfo(terminal.hConsole, &info);
4648 			startOfLineX = info.dwCursorPosition.X;
4649 			startOfLineY = info.dwCursorPosition.Y;
4650 		} else {
4651 			// request current cursor position
4652 
4653 			// we have to turn off cooked mode to get this answer, otherwise it will all
4654 			// be messed up. (I hate unix terminals, the Windows way is so much easer.)
4655 
4656 			// We also can't use RealTimeConsoleInput here because it also does event loop stuff
4657 			// which would be broken by the child destructor :( (maybe that should be a FIXME)
4658 
4659 			/+
4660 			if(rtci !is null) {
4661 				while(rtci.timedCheckForInput_bypassingBuffer(1000))
4662 					rtci.inputQueue ~= rtci.readNextEvents();
4663 			}
4664 			+/
4665 
4666 			ubyte[128] hack2;
4667 			termios old;
4668 			ubyte[128] hack;
4669 			tcgetattr(terminal.fdIn, &old);
4670 			auto n = old;
4671 			n.c_lflag &= ~(ICANON | ECHO);
4672 			tcsetattr(terminal.fdIn, TCSANOW, &n);
4673 			scope(exit)
4674 				tcsetattr(terminal.fdIn, TCSANOW, &old);
4675 
4676 
4677 			terminal.writeStringRaw("\033[6n");
4678 			terminal.flush();
4679 
4680 			import std.conv;
4681 			import core.stdc.errno;
4682 
4683 			import core.sys.posix.unistd;
4684 
4685 			ubyte readOne() {
4686 				ubyte[1] buffer;
4687 				int tries = 0;
4688 				try_again:
4689 				if(tries > 30)
4690 					throw new Exception("terminal reply timed out");
4691 				auto len = read(terminal.fdIn, buffer.ptr, buffer.length);
4692 				if(len == -1) {
4693 					if(errno == EINTR)
4694 						goto try_again;
4695 					if(errno == EAGAIN || errno == EWOULDBLOCK) {
4696 						import core.thread;
4697 						Thread.sleep(10.msecs);
4698 						tries++;
4699 						goto try_again;
4700 					}
4701 				} else if(len == 0) {
4702 					throw new Exception("Couldn't get cursor position to initialize get line " ~ to!string(len) ~ " " ~ to!string(errno));
4703 				}
4704 
4705 				return buffer[0];
4706 			}
4707 
4708 			nextEscape:
4709 			while(readOne() != '\033') {}
4710 			if(readOne() != '[')
4711 				goto nextEscape;
4712 
4713 			int x, y;
4714 
4715 			// now we should have some numbers being like yyy;xxxR
4716 			// but there may be a ? in there too; DEC private mode format
4717 			// of the very same data.
4718 
4719 			x = 0;
4720 			y = 0;
4721 
4722 			auto b = readOne();
4723 
4724 			if(b == '?')
4725 				b = readOne(); // no big deal, just ignore and continue
4726 
4727 			nextNumberY:
4728 			if(b >= '0' || b <= '9') {
4729 				y *= 10;
4730 				y += b - '0';
4731 			} else goto nextEscape;
4732 
4733 			b = readOne();
4734 			if(b != ';')
4735 				goto nextNumberY;
4736 
4737 			nextNumberX:
4738 			b = readOne();
4739 			if(b >= '0' || b <= '9') {
4740 				x *= 10;
4741 				x += b - '0';
4742 			} else goto nextEscape;
4743 
4744 			b = readOne();
4745 			if(b != 'R')
4746 				goto nextEscape; // it wasn't the right thing it after all
4747 
4748 			startOfLineX = x - 1;
4749 			startOfLineY = y - 1;
4750 		}
4751 
4752 		// updating these too because I can with the more accurate info from above
4753 		terminal._cursorX = startOfLineX;
4754 		terminal._cursorY = startOfLineY;
4755 	}
4756 
4757 	private bool justHitTab;
4758 	private bool eof;
4759 
4760 	///
4761 	string delegate(string s) pastePreprocessor;
4762 
4763 	string defaultPastePreprocessor(string s) {
4764 		return s;
4765 	}
4766 
4767 	void showIndividualHelp(string help) {
4768 		terminal.writeln();
4769 		terminal.writeln(help);
4770 	}
4771 
4772 	private bool maintainBuffer;
4773 
4774 	/++
4775 		for integrating into another event loop
4776 		you can pass individual events to this and
4777 		the line getter will work on it
4778 
4779 		returns false when there's nothing more to do
4780 
4781 		History:
4782 			On February 17, 2020, it was changed to take
4783 			a new argument which should be the input source
4784 			where the event came from.
4785 	+/
4786 	bool workOnLine(InputEvent e, RealTimeConsoleInput* rtti = null) {
4787 		switch(e.type) {
4788 			case InputEvent.Type.EndOfFileEvent:
4789 				justHitTab = false;
4790 				eof = true;
4791 				// FIXME: this should be distinct from an empty line when hit at the beginning
4792 				return false;
4793 			//break;
4794 			case InputEvent.Type.KeyboardEvent:
4795 				auto ev = e.keyboardEvent;
4796 				if(ev.pressed == false)
4797 					return true;
4798 				/* Insert the character (unless it is backspace, tab, or some other control char) */
4799 				auto ch = ev.which;
4800 				switch(ch) {
4801 					version(Windows) case 26: // and this is really for Windows
4802 						goto case;
4803 					case 4: // ctrl+d will also send a newline-equivalent 
4804 						if(line.length == 0)
4805 							eof = true;
4806 						goto case;
4807 					case '\r':
4808 					case '\n':
4809 						justHitTab = false;
4810 						return false;
4811 					case '\t':
4812 						auto relevantLineSection = line[0 .. cursorPosition];
4813 						auto start = tabCompleteStartPoint(relevantLineSection, line[cursorPosition .. $]);
4814 						relevantLineSection = relevantLineSection[start .. $];
4815 						auto possibilities = filterTabCompleteList(tabComplete(relevantLineSection, line[cursorPosition .. $]), start);
4816 						import std.utf;
4817 
4818 						if(possibilities.length == 1) {
4819 							auto toFill = possibilities[0][codeLength!char(relevantLineSection) .. $];
4820 							if(toFill.length) {
4821 								addString(toFill);
4822 								redraw();
4823 							} else {
4824 								auto help = this.tabCompleteHelp(possibilities[0]);
4825 								if(help.length) {
4826 									showIndividualHelp(help);
4827 									updateCursorPosition();
4828 									redraw();
4829 								}
4830 							}
4831 							justHitTab = false;
4832 						} else {
4833 							if(justHitTab) {
4834 								justHitTab = false;
4835 								showTabCompleteList(possibilities);
4836 							} else {
4837 								justHitTab = true;
4838 								/* fill it in with as much commonality as there is amongst all the suggestions */
4839 								auto suggestion = this.suggestion(possibilities);
4840 								if(suggestion.length) {
4841 									addString(suggestion);
4842 									redraw();
4843 								}
4844 							}
4845 						}
4846 					break;
4847 					case '\b':
4848 						justHitTab = false;
4849 						if(cursorPosition) {
4850 							cursorPosition--;
4851 							for(int i = cursorPosition; i < line.length - 1; i++)
4852 								line[i] = line[i + 1];
4853 							line = line[0 .. $ - 1];
4854 							line.assumeSafeAppend();
4855 
4856 							if(!multiLineMode) {
4857 								if(horizontalScrollPosition > cursorPosition - 1)
4858 									horizontalScrollPosition = cursorPosition - 1 - availableLineLength();
4859 								if(horizontalScrollPosition < 0)
4860 									horizontalScrollPosition = 0;
4861 							}
4862 
4863 							redraw();
4864 						}
4865 					break;
4866 					case KeyboardEvent.Key.escape:
4867 						justHitTab = false;
4868 						cursorPosition = 0;
4869 						horizontalScrollPosition = 0;
4870 						line = line[0 .. 0];
4871 						line.assumeSafeAppend();
4872 						redraw();
4873 					break;
4874 					case KeyboardEvent.Key.F1:
4875 						justHitTab = false;
4876 						showHelp();
4877 					break;
4878 					case KeyboardEvent.Key.F2:
4879 						justHitTab = false;
4880 						line = editLineInEditor(line, cursorPosition);
4881 						if(cursorPosition > line.length)
4882 							cursorPosition = cast(int) line.length;
4883 						if(horizontalScrollPosition > line.length)
4884 							horizontalScrollPosition = cast(int) line.length;
4885 						positionCursor();
4886 						redraw();
4887 					break;
4888 					case KeyboardEvent.Key.F3:
4889 					// case 'r' - 'a' + 1: // ctrl+r
4890 						justHitTab = false;
4891 						// search in history
4892 						// FIXME: what about search in completion too?
4893 					break;
4894 					case KeyboardEvent.Key.F4:
4895 						justHitTab = false;
4896 						// FIXME: clear line
4897 					break;
4898 					case KeyboardEvent.Key.F9:
4899 						justHitTab = false;
4900 						// compile and run analog; return the current string
4901 						// but keep the buffer the same
4902 						maintainBuffer = true;
4903 						return false;
4904 					case 0x1d: // ctrl+5, because of vim % shortcut
4905 						justHitTab = false;
4906 						// FIXME: find matching delimiter
4907 					break;
4908 					case KeyboardEvent.Key.LeftArrow:
4909 						justHitTab = false;
4910 						if(cursorPosition)
4911 							cursorPosition--;
4912 						if(ev.modifierState & ModifierState.control) {
4913 							while(cursorPosition && line[cursorPosition - 1] != ' ')
4914 								cursorPosition--;
4915 						}
4916 						aligned(cursorPosition, -1);
4917 
4918 						if(cursorPosition < horizontalScrollPosition)
4919 							positionCursor();
4920 
4921 						redraw();
4922 					break;
4923 					case KeyboardEvent.Key.RightArrow:
4924 						justHitTab = false;
4925 						if(cursorPosition < line.length)
4926 							cursorPosition++;
4927 
4928 						if(ev.modifierState & ModifierState.control) {
4929 							while(cursorPosition + 1 < line.length && line[cursorPosition + 1] != ' ')
4930 								cursorPosition++;
4931 							cursorPosition += 2;
4932 							if(cursorPosition > line.length)
4933 								cursorPosition = cast(int) line.length;
4934 						}
4935 						aligned(cursorPosition, 1);
4936 
4937 						if(cursorPosition > horizontalScrollPosition + availableLineLength())
4938 							positionCursor();
4939 
4940 						redraw();
4941 					break;
4942 					case KeyboardEvent.Key.UpArrow:
4943 						justHitTab = false;
4944 						loadFromHistory(currentHistoryViewPosition + 1);
4945 						redraw();
4946 					break;
4947 					case KeyboardEvent.Key.DownArrow:
4948 						justHitTab = false;
4949 						loadFromHistory(currentHistoryViewPosition - 1);
4950 						redraw();
4951 					break;
4952 					case KeyboardEvent.Key.PageUp:
4953 						justHitTab = false;
4954 						loadFromHistory(cast(int) history.length);
4955 						redraw();
4956 					break;
4957 					case KeyboardEvent.Key.PageDown:
4958 						justHitTab = false;
4959 						loadFromHistory(0);
4960 						redraw();
4961 					break;
4962 					case 1: // ctrl+a does home too in the emacs keybindings
4963 					case KeyboardEvent.Key.Home:
4964 						justHitTab = false;
4965 						cursorPosition = 0;
4966 						horizontalScrollPosition = 0;
4967 						redraw();
4968 					break;
4969 					case 5: // ctrl+e from emacs
4970 					case KeyboardEvent.Key.End:
4971 						justHitTab = false;
4972 						cursorPosition = cast(int) line.length;
4973 						scrollToEnd();
4974 						redraw();
4975 					break;
4976 					case ('v' - 'a' + 1):
4977 						if(rtti)
4978 							rtti.requestPasteFromClipboard();
4979 					break;
4980 					case KeyboardEvent.Key.Insert:
4981 						justHitTab = false;
4982 						if(ev.modifierState & ModifierState.shift) {
4983 							// paste
4984 
4985 							// shift+insert = request paste
4986 							// ctrl+insert = request copy. but that needs a selection
4987 
4988 							// those work on Windows!!!! and many linux TEs too.
4989 							// but if it does make it here, we'll attempt it at this level
4990 							if(rtti)
4991 								rtti.requestPasteFromClipboard();
4992 						} else if(ev.modifierState & ModifierState.control) {
4993 							// copy
4994 							// FIXME
4995 						} else {
4996 							insertMode = !insertMode;
4997 
4998 							if(insertMode)
4999 								terminal.cursor = TerminalCursor.insert;
5000 							else
5001 								terminal.cursor = TerminalCursor.block;
5002 						}
5003 					break;
5004 					case KeyboardEvent.Key.Delete:
5005 						justHitTab = false;
5006 						if(ev.modifierState & ModifierState.control)
5007 							deleteToEndOfLine();
5008 						else
5009 							deleteChar();
5010 						redraw();
5011 					break;
5012 					case 11: // ctrl+k is delete to end of line from emacs
5013 						justHitTab = false;
5014 						deleteToEndOfLine();
5015 						redraw();
5016 					break;
5017 					default:
5018 						justHitTab = false;
5019 						if(e.keyboardEvent.isCharacter)
5020 							addChar(ch);
5021 						redraw();
5022 				}
5023 			break;
5024 			case InputEvent.Type.PasteEvent:
5025 				justHitTab = false;
5026 				if(pastePreprocessor)
5027 					addString(pastePreprocessor(e.pasteEvent.pastedText));
5028 				else
5029 					addString(defaultPastePreprocessor(e.pasteEvent.pastedText));
5030 				redraw();
5031 			break;
5032 			case InputEvent.Type.MouseEvent:
5033 				/* Clicking with the mouse to move the cursor is so much easier than arrowing
5034 				   or even emacs/vi style movements much of the time, so I'ma support it. */
5035 
5036 				auto me = e.mouseEvent;
5037 				if(me.eventType == MouseEvent.Type.Pressed) {
5038 					if(me.buttons & MouseEvent.Button.Left) {
5039 						if(me.y == startOfLineY) {
5040 							int p = me.x - startOfLineX - promptLength + horizontalScrollPosition;
5041 							if(p >= 0 && p < line.length) {
5042 								justHitTab = false;
5043 								cursorPosition = p;
5044 								redraw();
5045 							}
5046 						}
5047 					}
5048 					if(me.buttons & MouseEvent.Button.Middle) {
5049 						if(rtti)
5050 							rtti.requestPasteFromPrimary();
5051 					}
5052 				}
5053 			break;
5054 			case InputEvent.Type.SizeChangedEvent:
5055 				/* We'll adjust the bounding box. If you don't like this, handle SizeChangedEvent
5056 				   yourself and then don't pass it to this function. */
5057 				// FIXME
5058 				initializeWithSize();
5059 			break;
5060 			case InputEvent.Type.UserInterruptionEvent:
5061 				/* I'll take this as canceling the line. */
5062 				throw new UserInterruptionException();
5063 			//break;
5064 			case InputEvent.Type.HangupEvent:
5065 				/* I'll take this as canceling the line. */
5066 				throw new HangupException();
5067 			//break;
5068 			default:
5069 				/* ignore. ideally it wouldn't be passed to us anyway! */
5070 		}
5071 
5072 		return true;
5073 	}
5074 
5075 	string finishGettingLine() {
5076 		import std.conv;
5077 		auto f = to!string(line);
5078 		auto history = historyFilter(f);
5079 		if(history !is null)
5080 			this.history ~= history;
5081 
5082 		// FIXME: we should hide the cursor if it was hidden in the call to startGettingLine
5083 		return eof ? null : f.length ? f : "";
5084 	}
5085 }
5086 
5087 /// Adds default constructors that just forward to the superclass
5088 mixin template LineGetterConstructors() {
5089 	this(Terminal* tty, string historyFilename = null) {
5090 		super(tty, historyFilename);
5091 	}
5092 }
5093 
5094 /// This is a line getter that customizes the tab completion to
5095 /// fill in file names separated by spaces, like a command line thing.
5096 class FileLineGetter : LineGetter {
5097 	mixin LineGetterConstructors;
5098 
5099 	/// You can set this property to tell it where to search for the files
5100 	/// to complete.
5101 	string searchDirectory = ".";
5102 
5103 	override size_t tabCompleteStartPoint(in dchar[] candidate, in dchar[] afterCursor) {
5104 		import std..string;
5105 		return candidate.lastIndexOf(" ") + 1;
5106 	}
5107 
5108 	override protected string[] tabComplete(in dchar[] candidate, in dchar[] afterCursor) {
5109 		import std.file, std.conv, std.algorithm, std..string;
5110 
5111 		string[] list;
5112 		foreach(string name; dirEntries(searchDirectory, SpanMode.breadth)) {
5113 			// both with and without the (searchDirectory ~ "/")
5114 			list ~= name[searchDirectory.length + 1 .. $];
5115 			list ~= name[0 .. $];
5116 		}
5117 
5118 		return list;
5119 	}
5120 }
5121 
5122 version(Windows) {
5123 	// to get the directory for saving history in the line things
5124 	enum CSIDL_APPDATA = 26;
5125 	extern(Windows) HRESULT SHGetFolderPathA(HWND, int, HANDLE, DWORD, LPSTR);
5126 }
5127 
5128 
5129 
5130 
5131 
5132 /* Like getting a line, printing a lot of lines is kinda important too, so I'm including
5133    that widget here too. */
5134 
5135 
5136 struct ScrollbackBuffer {
5137 
5138 	bool demandsAttention;
5139 
5140 	this(string name) {
5141 		this.name = name;
5142 	}
5143 
5144 	void write(T...)(T t) {
5145 		import std.conv : text;
5146 		addComponent(text(t), foreground_, background_, null);
5147 	}
5148 
5149 	void writeln(T...)(T t) {
5150 		write(t, "\n");
5151 	}
5152 
5153 	void writef(T...)(string fmt, T t) {
5154 		import std.format: format;
5155 		write(format(fmt, t));
5156 	}
5157 
5158 	void writefln(T...)(string fmt, T t) {
5159 		writef(fmt, t, "\n");
5160 	}
5161 
5162 	void clear() {
5163 		lines.clear();
5164 		clickRegions = null;
5165 		scrollbackPosition = 0;
5166 	}
5167 
5168 	int foreground_ = Color.DEFAULT, background_ = Color.DEFAULT;
5169 	void color(int foreground, int background) {
5170 		this.foreground_ = foreground;
5171 		this.background_ = background;
5172 	}
5173 
5174 	void addComponent(string text, int foreground, int background, bool delegate() onclick) {
5175 		if(lines.length == 0) {
5176 			addLine();
5177 		}
5178 		bool first = true;
5179 		import std.algorithm;
5180 		foreach(t; splitter(text, "\n")) {
5181 			if(!first) addLine();
5182 			first = false;
5183 			lines[$-1].components ~= LineComponent(t, foreground, background, onclick);
5184 		}
5185 	}
5186 
5187 	void addLine() {
5188 		lines ~= Line();
5189 		if(scrollbackPosition) // if the user is scrolling back, we want to keep them basically centered where they are
5190 			scrollbackPosition++;
5191 	}
5192 
5193 	void addLine(string line) {
5194 		lines ~= Line([LineComponent(line)]);
5195 		if(scrollbackPosition) // if the user is scrolling back, we want to keep them basically centered where they are
5196 			scrollbackPosition++;
5197 	}
5198 
5199 	void scrollUp(int lines = 1) {
5200 		scrollbackPosition += lines;
5201 		//if(scrollbackPosition >= this.lines.length)
5202 		//	scrollbackPosition = cast(int) this.lines.length - 1;
5203 	}
5204 
5205 	void scrollDown(int lines = 1) {
5206 		scrollbackPosition -= lines;
5207 		if(scrollbackPosition < 0)
5208 			scrollbackPosition = 0;
5209 	}
5210 
5211 	void scrollToBottom() {
5212 		scrollbackPosition = 0;
5213 	}
5214 
5215 	// this needs width and height to know how to word wrap it
5216 	void scrollToTop(int width, int height) {
5217 		scrollbackPosition = scrollTopPosition(width, height);
5218 	}
5219 
5220 
5221 
5222 
5223 	struct LineComponent {
5224 		string text;
5225 		bool isRgb;
5226 		union {
5227 			int color;
5228 			RGB colorRgb;
5229 		}
5230 		union {
5231 			int background;
5232 			RGB backgroundRgb;
5233 		}
5234 		bool delegate() onclick; // return true if you need to redraw
5235 
5236 		// 16 color ctor
5237 		this(string text, int color = Color.DEFAULT, int background = Color.DEFAULT, bool delegate() onclick = null) {
5238 			this.text = text;
5239 			this.color = color;
5240 			this.background = background;
5241 			this.onclick = onclick;
5242 			this.isRgb = false;
5243 		}
5244 
5245 		// true color ctor
5246 		this(string text, RGB colorRgb, RGB backgroundRgb = RGB(0, 0, 0), bool delegate() onclick = null) {
5247 			this.text = text;
5248 			this.colorRgb = colorRgb;
5249 			this.backgroundRgb = backgroundRgb;
5250 			this.onclick = onclick;
5251 			this.isRgb = true;
5252 		}
5253 	}
5254 
5255 	struct Line {
5256 		LineComponent[] components;
5257 		int length() {
5258 			int l = 0;
5259 			foreach(c; components)
5260 				l += c.text.length;
5261 			return l;
5262 		}
5263 	}
5264 
5265 	static struct CircularBuffer(T) {
5266 		T[] backing;
5267 
5268 		enum maxScrollback = 8192; // as a power of 2, i hope the compiler optimizes the % below to a simple bit mask...
5269 
5270 		int start;
5271 		int length_;
5272 
5273 		void clear() {
5274 			backing = null;
5275 			start = 0;
5276 			length_ = 0;
5277 		}
5278 
5279 		size_t length() {
5280 			return length_;
5281 		}
5282 
5283 		void opOpAssign(string op : "~")(T line) {
5284 			if(length_ < maxScrollback) {
5285 				backing.assumeSafeAppend();
5286 				backing ~= line;
5287 				length_++;
5288 			} else {
5289 				backing[start] = line;
5290 				start++;
5291 				if(start == maxScrollback)
5292 					start = 0;
5293 			}
5294 		}
5295 
5296 		ref T opIndex(int idx) {
5297 			return backing[(start + idx) % maxScrollback];
5298 		}
5299 		ref T opIndex(Dollar idx) {
5300 			return backing[(start + (length + idx.offsetFromEnd)) % maxScrollback];
5301 		}
5302 
5303 		CircularBufferRange opSlice(int startOfIteration, Dollar end) {
5304 			return CircularBufferRange(&this, startOfIteration, cast(int) length - startOfIteration + end.offsetFromEnd);
5305 		}
5306 		CircularBufferRange opSlice(int startOfIteration, int end) {
5307 			return CircularBufferRange(&this, startOfIteration, end - startOfIteration);
5308 		}
5309 		CircularBufferRange opSlice() {
5310 			return CircularBufferRange(&this, 0, cast(int) length);
5311 		}
5312 
5313 		static struct CircularBufferRange {
5314 			CircularBuffer* item;
5315 			int position;
5316 			int remaining;
5317 			this(CircularBuffer* item, int startOfIteration, int count) {
5318 				this.item = item;
5319 				position = startOfIteration;
5320 				remaining = count;
5321 			}
5322 
5323 			ref T front() { return (*item)[position]; }
5324 			bool empty() { return remaining <= 0; }
5325 			void popFront() {
5326 				position++;
5327 				remaining--;
5328 			}
5329 
5330 			ref T back() { return (*item)[remaining - 1 - position]; }
5331 			void popBack() {
5332 				remaining--;
5333 			}
5334 		}
5335 
5336 		static struct Dollar {
5337 			int offsetFromEnd;
5338 			Dollar opBinary(string op : "-")(int rhs) {
5339 				return Dollar(offsetFromEnd - rhs);
5340 			}
5341 		}
5342 		Dollar opDollar() { return Dollar(0); }
5343 	}
5344 
5345 	CircularBuffer!Line lines;
5346 	string name;
5347 
5348 	int x, y, width, height;
5349 
5350 	int scrollbackPosition;
5351 
5352 
5353 	int scrollTopPosition(int width, int height) {
5354 		int lineCount;
5355 
5356 		foreach_reverse(line; lines) {
5357 			int written = 0;
5358 			comp_loop: foreach(cidx, component; line.components) {
5359 				auto towrite = component.text;
5360 				foreach(idx, dchar ch; towrite) {
5361 					if(written >= width) {
5362 						lineCount++;
5363 						written = 0;
5364 					}
5365 
5366 					if(ch == '\t')
5367 						written += 8; // FIXME
5368 					else
5369 						written++;
5370 				}
5371 			}
5372 			lineCount++;
5373 		}
5374 
5375 		//if(lineCount > height)
5376 			return lineCount - height;
5377 		//return 0;
5378 	}
5379 
5380 	void drawInto(Terminal* terminal, in int x = 0, in int y = 0, int width = 0, int height = 0) {
5381 		if(lines.length == 0)
5382 			return;
5383 
5384 		if(width == 0)
5385 			width = terminal.width;
5386 		if(height == 0)
5387 			height = terminal.height;
5388 
5389 		this.x = x;
5390 		this.y = y;
5391 		this.width = width;
5392 		this.height = height;
5393 
5394 		/* We need to figure out how much is going to fit
5395 		   in a first pass, so we can figure out where to
5396 		   start drawing */
5397 
5398 		int remaining = height + scrollbackPosition;
5399 		int start = cast(int) lines.length;
5400 		int howMany = 0;
5401 
5402 		bool firstPartial = false;
5403 
5404 		static struct Idx {
5405 			size_t cidx;
5406 			size_t idx;
5407 		}
5408 
5409 		Idx firstPartialStartIndex;
5410 
5411 		// this is private so I know we can safe append
5412 		clickRegions.length = 0;
5413 		clickRegions.assumeSafeAppend();
5414 
5415 		// FIXME: should prolly handle \n and \r in here too.
5416 
5417 		// we'll work backwards to figure out how much will fit...
5418 		// this will give accurate per-line things even with changing width and wrapping
5419 		// while being generally efficient - we usually want to show the end of the list
5420 		// anyway; actually using the scrollback is a bit of an exceptional case.
5421 
5422 		// It could probably do this instead of on each redraw, on each resize or insertion.
5423 		// or at least cache between redraws until one of those invalidates it.
5424 		foreach_reverse(line; lines) {
5425 			int written = 0;
5426 			int brokenLineCount;
5427 			Idx[16] lineBreaksBuffer;
5428 			Idx[] lineBreaks = lineBreaksBuffer[];
5429 			comp_loop: foreach(cidx, component; line.components) {
5430 				auto towrite = component.text;
5431 				foreach(idx, dchar ch; towrite) {
5432 					if(written >= width) {
5433 						if(brokenLineCount == lineBreaks.length)
5434 							lineBreaks ~= Idx(cidx, idx);
5435 						else
5436 							lineBreaks[brokenLineCount] = Idx(cidx, idx);
5437 
5438 						brokenLineCount++;
5439 
5440 						written = 0;
5441 					}
5442 
5443 					if(ch == '\t')
5444 						written += 8; // FIXME
5445 					else
5446 						written++;
5447 				}
5448 			}
5449 
5450 			lineBreaks = lineBreaks[0 .. brokenLineCount];
5451 
5452 			foreach_reverse(lineBreak; lineBreaks) {
5453 				if(remaining == 1) {
5454 					firstPartial = true;
5455 					firstPartialStartIndex = lineBreak;
5456 					break;
5457 				} else {
5458 					remaining--;
5459 				}
5460 				if(remaining <= 0)
5461 					break;
5462 			}
5463 
5464 			remaining--;
5465 
5466 			start--;
5467 			howMany++;
5468 			if(remaining <= 0)
5469 				break;
5470 		}
5471 
5472 		// second pass: actually draw it
5473 		int linePos = remaining;
5474 
5475 		foreach(line; lines[start .. start + howMany]) {
5476 			int written = 0;
5477 
5478 			if(linePos < 0) {
5479 				linePos++;
5480 				continue;
5481 			}
5482 		
5483 			terminal.moveTo(x, y + ((linePos >= 0) ? linePos : 0));
5484 
5485 			auto todo = line.components;
5486 
5487 			if(firstPartial) {
5488 				todo = todo[firstPartialStartIndex.cidx .. $];
5489 			}
5490 
5491 			foreach(ref component; todo) {
5492 				if(component.isRgb)
5493 					terminal.setTrueColor(component.colorRgb, component.backgroundRgb);
5494 				else
5495 					terminal.color(component.color, component.background);
5496 				auto towrite = component.text;
5497 
5498 				again:
5499 
5500 				if(linePos >= height)
5501 					break;
5502 
5503 				if(firstPartial) {
5504 					towrite = towrite[firstPartialStartIndex.idx .. $];
5505 					firstPartial = false;
5506 				}
5507 
5508 				foreach(idx, dchar ch; towrite) {
5509 					if(written >= width) {
5510 						clickRegions ~= ClickRegion(&component, terminal.cursorX, terminal.cursorY, written);
5511 						terminal.write(towrite[0 .. idx]);
5512 						towrite = towrite[idx .. $];
5513 						linePos++;
5514 						written = 0;
5515 						terminal.moveTo(x, y + linePos);
5516 						goto again;
5517 					}
5518 
5519 					if(ch == '\t')
5520 						written += 8; // FIXME
5521 					else
5522 						written++;
5523 				}
5524 
5525 				if(towrite.length) {
5526 					clickRegions ~= ClickRegion(&component, terminal.cursorX, terminal.cursorY, written);
5527 					terminal.write(towrite);
5528 				}
5529 			}
5530 
5531 			if(written < width) {
5532 				terminal.color(Color.DEFAULT, Color.DEFAULT);
5533 				foreach(i; written .. width)
5534 					terminal.write(" ");
5535 			}
5536 
5537 			linePos++;
5538 
5539 			if(linePos >= height)
5540 				break;
5541 		}
5542 
5543 		if(linePos < height) {
5544 			terminal.color(Color.DEFAULT, Color.DEFAULT);
5545 			foreach(i; linePos .. height) {
5546 				if(i >= 0 && i < height) {
5547 					terminal.moveTo(x, y + i);
5548 					foreach(w; 0 .. width)
5549 						terminal.write(" ");
5550 				}
5551 			}
5552 		}
5553 	}
5554 
5555 	private struct ClickRegion {
5556 		LineComponent* component;
5557 		int xStart;
5558 		int yStart;
5559 		int length;
5560 	}
5561 	private ClickRegion[] clickRegions;
5562 
5563 	/// Default event handling for this widget. Call this only after drawing it into a rectangle
5564 	/// and only if the event ought to be dispatched to it (which you determine however you want;
5565 	/// you could dispatch all events to it, or perhaps filter some out too)
5566 	///
5567 	/// Returns true if it should be redrawn
5568 	bool handleEvent(InputEvent e) {
5569 		final switch(e.type) {
5570 			case InputEvent.Type.LinkEvent:
5571 				// meh
5572 			break;
5573 			case InputEvent.Type.KeyboardEvent:
5574 				auto ev = e.keyboardEvent;
5575 
5576 				demandsAttention = false;
5577 
5578 				switch(ev.which) {
5579 					case KeyboardEvent.Key.UpArrow:
5580 						scrollUp();
5581 						return true;
5582 					case KeyboardEvent.Key.DownArrow:
5583 						scrollDown();
5584 						return true;
5585 					case KeyboardEvent.Key.PageUp:
5586 						scrollUp(height);
5587 						return true;
5588 					case KeyboardEvent.Key.PageDown:
5589 						scrollDown(height);
5590 						return true;
5591 					default:
5592 						// ignore
5593 				}
5594 			break;
5595 			case InputEvent.Type.MouseEvent:
5596 				auto ev = e.mouseEvent;
5597 				if(ev.x >= x && ev.x < x + width && ev.y >= y && ev.y < y + height) {
5598 					demandsAttention = false;
5599 					// it is inside our box, so do something with it
5600 					auto mx = ev.x - x;
5601 					auto my = ev.y - y;
5602 
5603 					if(ev.eventType == MouseEvent.Type.Pressed) {
5604 						if(ev.buttons & MouseEvent.Button.Left) {
5605 							foreach(region; clickRegions)
5606 								if(ev.x >= region.xStart && ev.x < region.xStart + region.length && ev.y == region.yStart)
5607 									if(region.component.onclick !is null)
5608 										return region.component.onclick();
5609 						}
5610 						if(ev.buttons & MouseEvent.Button.ScrollUp) {
5611 							scrollUp();
5612 							return true;
5613 						}
5614 						if(ev.buttons & MouseEvent.Button.ScrollDown) {
5615 							scrollDown();
5616 							return true;
5617 						}
5618 					}
5619 				} else {
5620 					// outside our area, free to ignore
5621 				}
5622 			break;
5623 			case InputEvent.Type.SizeChangedEvent:
5624 				// (size changed might be but it needs to be handled at a higher level really anyway)
5625 				// though it will return true because it probably needs redrawing anyway.
5626 				return true;
5627 			case InputEvent.Type.UserInterruptionEvent:
5628 				throw new UserInterruptionException();
5629 			case InputEvent.Type.HangupEvent:
5630 				throw new HangupException();
5631 			case InputEvent.Type.EndOfFileEvent:
5632 				// ignore, not relevant to this
5633 			break;
5634 			case InputEvent.Type.CharacterEvent:
5635 			case InputEvent.Type.NonCharacterKeyEvent:
5636 				// obsolete, ignore them until they are removed
5637 			break;
5638 			case InputEvent.Type.CustomEvent:
5639 			case InputEvent.Type.PasteEvent:
5640 				// ignored, not relevant to us
5641 			break;
5642 		}
5643 
5644 		return false;
5645 	}
5646 }
5647 
5648 
5649 class UserInterruptionException : Exception {
5650 	this() { super("Ctrl+C"); }
5651 }
5652 class HangupException : Exception {
5653 	this() { super("Hup"); }
5654 }
5655 
5656 
5657 
5658 /*
5659 
5660 	// more efficient scrolling
5661 	http://msdn.microsoft.com/en-us/library/windows/desktop/ms685113%28v=vs.85%29.aspx
5662 	// and the unix sequences
5663 
5664 
5665 	rxvt documentation:
5666 	use this to finish the input magic for that
5667 
5668 
5669        For the keypad, use Shift to temporarily override Application-Keypad
5670        setting use Num_Lock to toggle Application-Keypad setting if Num_Lock
5671        is off, toggle Application-Keypad setting. Also note that values of
5672        Home, End, Delete may have been compiled differently on your system.
5673 
5674                          Normal       Shift         Control      Ctrl+Shift
5675        Tab               ^I           ESC [ Z       ^I           ESC [ Z
5676        BackSpace         ^H           ^?            ^?           ^?
5677        Find              ESC [ 1 ~    ESC [ 1 $     ESC [ 1 ^    ESC [ 1 @
5678        Insert            ESC [ 2 ~    paste         ESC [ 2 ^    ESC [ 2 @
5679        Execute           ESC [ 3 ~    ESC [ 3 $     ESC [ 3 ^    ESC [ 3 @
5680        Select            ESC [ 4 ~    ESC [ 4 $     ESC [ 4 ^    ESC [ 4 @
5681        Prior             ESC [ 5 ~    scroll-up     ESC [ 5 ^    ESC [ 5 @
5682        Next              ESC [ 6 ~    scroll-down   ESC [ 6 ^    ESC [ 6 @
5683        Home              ESC [ 7 ~    ESC [ 7 $     ESC [ 7 ^    ESC [ 7 @
5684        End               ESC [ 8 ~    ESC [ 8 $     ESC [ 8 ^    ESC [ 8 @
5685        Delete            ESC [ 3 ~    ESC [ 3 $     ESC [ 3 ^    ESC [ 3 @
5686        F1                ESC [ 11 ~   ESC [ 23 ~    ESC [ 11 ^   ESC [ 23 ^
5687        F2                ESC [ 12 ~   ESC [ 24 ~    ESC [ 12 ^   ESC [ 24 ^
5688        F3                ESC [ 13 ~   ESC [ 25 ~    ESC [ 13 ^   ESC [ 25 ^
5689        F4                ESC [ 14 ~   ESC [ 26 ~    ESC [ 14 ^   ESC [ 26 ^
5690        F5                ESC [ 15 ~   ESC [ 28 ~    ESC [ 15 ^   ESC [ 28 ^
5691        F6                ESC [ 17 ~   ESC [ 29 ~    ESC [ 17 ^   ESC [ 29 ^
5692        F7                ESC [ 18 ~   ESC [ 31 ~    ESC [ 18 ^   ESC [ 31 ^
5693        F8                ESC [ 19 ~   ESC [ 32 ~    ESC [ 19 ^   ESC [ 32 ^
5694        F9                ESC [ 20 ~   ESC [ 33 ~    ESC [ 20 ^   ESC [ 33 ^
5695        F10               ESC [ 21 ~   ESC [ 34 ~    ESC [ 21 ^   ESC [ 34 ^
5696        F11               ESC [ 23 ~   ESC [ 23 $    ESC [ 23 ^   ESC [ 23 @
5697        F12               ESC [ 24 ~   ESC [ 24 $    ESC [ 24 ^   ESC [ 24 @
5698        F13               ESC [ 25 ~   ESC [ 25 $    ESC [ 25 ^   ESC [ 25 @
5699        F14               ESC [ 26 ~   ESC [ 26 $    ESC [ 26 ^   ESC [ 26 @
5700        F15 (Help)        ESC [ 28 ~   ESC [ 28 $    ESC [ 28 ^   ESC [ 28 @
5701        F16 (Menu)        ESC [ 29 ~   ESC [ 29 $    ESC [ 29 ^   ESC [ 29 @
5702 
5703        F17               ESC [ 31 ~   ESC [ 31 $    ESC [ 31 ^   ESC [ 31 @
5704        F18               ESC [ 32 ~   ESC [ 32 $    ESC [ 32 ^   ESC [ 32 @
5705        F19               ESC [ 33 ~   ESC [ 33 $    ESC [ 33 ^   ESC [ 33 @
5706        F20               ESC [ 34 ~   ESC [ 34 $    ESC [ 34 ^   ESC [ 34 @
5707                                                                  Application
5708        Up                ESC [ A      ESC [ a       ESC O a      ESC O A
5709        Down              ESC [ B      ESC [ b       ESC O b      ESC O B
5710        Right             ESC [ C      ESC [ c       ESC O c      ESC O C
5711        Left              ESC [ D      ESC [ d       ESC O d      ESC O D
5712        KP_Enter          ^M                                      ESC O M
5713        KP_F1             ESC O P                                 ESC O P
5714        KP_F2             ESC O Q                                 ESC O Q
5715        KP_F3             ESC O R                                 ESC O R
5716        KP_F4             ESC O S                                 ESC O S
5717        XK_KP_Multiply    *                                       ESC O j
5718        XK_KP_Add         +                                       ESC O k
5719        XK_KP_Separator   ,                                       ESC O l
5720        XK_KP_Subtract    -                                       ESC O m
5721        XK_KP_Decimal     .                                       ESC O n
5722        XK_KP_Divide      /                                       ESC O o
5723        XK_KP_0           0                                       ESC O p
5724        XK_KP_1           1                                       ESC O q
5725        XK_KP_2           2                                       ESC O r
5726        XK_KP_3           3                                       ESC O s
5727        XK_KP_4           4                                       ESC O t
5728        XK_KP_5           5                                       ESC O u
5729        XK_KP_6           6                                       ESC O v
5730        XK_KP_7           7                                       ESC O w
5731        XK_KP_8           8                                       ESC O x
5732        XK_KP_9           9                                       ESC O y
5733 */
5734 
5735 version(Demo_kbhit)
5736 void main() {
5737 	auto terminal = Terminal(ConsoleOutputType.linear);
5738 	auto input = RealTimeConsoleInput(&terminal, ConsoleInputFlags.raw);
5739 
5740 	int a;
5741 	char ch = '.';
5742 	while(a < 1000) {
5743 		a++;
5744 		if(a % terminal.width == 0) {
5745 			terminal.write("\r");
5746 			if(ch == '.')
5747 				ch = ' ';
5748 			else
5749 				ch = '.';
5750 		}
5751 
5752 		if(input.kbhit())
5753 			terminal.write(input.getch());
5754 		else
5755 			terminal.write(ch);
5756 
5757 		terminal.flush();
5758 
5759 		import core.thread;
5760 		Thread.sleep(50.msecs);
5761 	}
5762 }
5763 
5764 /*
5765 	The Xterm palette progression is:
5766 	[0, 95, 135, 175, 215, 255]
5767 
5768 	So if I take the color and subtract 55, then div 40, I get
5769 	it into one of these areas. If I add 20, I get a reasonable
5770 	rounding.
5771 */
5772 
5773 ubyte colorToXTermPaletteIndex(RGB color) {
5774 	/*
5775 		Here, I will round off to the color ramp or the
5776 		greyscale. I will NOT use the bottom 16 colors because
5777 		there's duplicates (or very close enough) to them in here
5778 	*/
5779 
5780 	if(color.r == color.g && color.g == color.b) {
5781 		// grey - find one of them:
5782 		if(color.r == 0) return 0;
5783 		// meh don't need those two, let's simplify branche
5784 		//if(color.r == 0xc0) return 7;
5785 		//if(color.r == 0x80) return 8;
5786 		// it isn't == 255 because it wants to catch anything
5787 		// that would wrap the simple algorithm below back to 0.
5788 		if(color.r >= 248) return 15;
5789 
5790 		// there's greys in the color ramp too, but these
5791 		// are all close enough as-is, no need to complicate
5792 		// algorithm for approximation anyway
5793 
5794 		return cast(ubyte) (232 + ((color.r - 8) / 10));
5795 	}
5796 
5797 	// if it isn't grey, it is color
5798 
5799 	// the ramp goes blue, green, red, with 6 of each,
5800 	// so just multiplying will give something good enough
5801 
5802 	// will give something between 0 and 5, with some rounding
5803 	auto r = (cast(int) color.r - 35) / 40;
5804 	auto g = (cast(int) color.g - 35) / 40;
5805 	auto b = (cast(int) color.b - 35) / 40;
5806 
5807 	return cast(ubyte) (16 + b + g*6 + r*36);
5808 }
5809 
5810 /++
5811 	Represents a 24-bit color.
5812 
5813 
5814 	$(TIP You can convert these to and from [arsd.color.Color] using
5815 	      `.tupleof`:
5816 
5817 		---
5818 	      	RGB rgb;
5819 		Color c = Color(rgb.tupleof);
5820 		---
5821 	)
5822 +/
5823 struct RGB {
5824 	ubyte r; ///
5825 	ubyte g; ///
5826 	ubyte b; ///
5827 	// terminal can't actually use this but I want the value
5828 	// there for assignment to an arsd.color.Color
5829 	private ubyte a = 255;
5830 }
5831 
5832 // This is an approximation too for a few entries, but a very close one.
5833 RGB xtermPaletteIndexToColor(int paletteIdx) {
5834 	RGB color;
5835 
5836 	if(paletteIdx < 16) {
5837 		if(paletteIdx == 7)
5838 			return RGB(0xc0, 0xc0, 0xc0);
5839 		else if(paletteIdx == 8)
5840 			return RGB(0x80, 0x80, 0x80);
5841 
5842 		color.r = (paletteIdx & 0b001) ? ((paletteIdx & 0b1000) ? 0xff : 0x80) : 0x00;
5843 		color.g = (paletteIdx & 0b010) ? ((paletteIdx & 0b1000) ? 0xff : 0x80) : 0x00;
5844 		color.b = (paletteIdx & 0b100) ? ((paletteIdx & 0b1000) ? 0xff : 0x80) : 0x00;
5845 
5846 	} else if(paletteIdx < 232) {
5847 		// color ramp, 6x6x6 cube
5848 		color.r = cast(ubyte) ((paletteIdx - 16) / 36 * 40 + 55);
5849 		color.g = cast(ubyte) (((paletteIdx - 16) % 36) / 6 * 40 + 55);
5850 		color.b = cast(ubyte) ((paletteIdx - 16) % 6 * 40 + 55);
5851 
5852 		if(color.r == 55) color.r = 0;
5853 		if(color.g == 55) color.g = 0;
5854 		if(color.b == 55) color.b = 0;
5855 	} else {
5856 		// greyscale ramp, from 0x8 to 0xee
5857 		color.r = cast(ubyte) (8 + (paletteIdx - 232) * 10);
5858 		color.g = color.r;
5859 		color.b = color.g;
5860 	}
5861 
5862 	return color;
5863 }
5864 
5865 int approximate16Color(RGB color) {
5866 	int c;
5867 	c |= color.r > 64 ? RED_BIT : 0;
5868 	c |= color.g > 64 ? GREEN_BIT : 0;
5869 	c |= color.b > 64 ? BLUE_BIT : 0;
5870 
5871 	c |= (((color.r + color.g + color.b) / 3) > 80) ? Bright : 0;
5872 
5873 	return c;
5874 }
5875 
5876 version(TerminalDirectToEmulator) {
5877 
5878 	/++
5879 		Indicates the TerminalDirectToEmulator features
5880 		are present. You can check this with `static if`.
5881 
5882 		$(WARNING
5883 			This will cause the [Terminal] constructor to spawn a GUI thread with [arsd.minigui]/[arsd.simpledisplay].
5884 
5885 			This means you can NOT use those libraries in your
5886 			own thing without using the [arsd.simpledisplay.runInGuiThread] helper since otherwise the main thread is inaccessible, since having two different threads creating event loops or windows is undefined behavior with those libraries.
5887 		)
5888 	+/
5889 	enum IntegratedEmulator = true;
5890 
5891 	/++
5892 		Allows customization of the integrated emulator window.
5893 		You may change the default colors, font, and other aspects
5894 		of GUI integration.
5895 
5896 		Test for its presence before using with `static if(arsd.terminal.IntegratedEmulator)`.
5897 
5898 		All settings here must be set BEFORE you construct any [Terminal] instances.
5899 
5900 		History:
5901 			Added March 7, 2020.
5902 	+/
5903 	struct IntegratedTerminalEmulatorConfiguration {
5904 		/// Note that all Colors in here are 24 bit colors.
5905 		alias Color = arsd.color.Color;
5906 
5907 		/// Default foreground color of the terminal.
5908 		Color defaultForeground = Color.black;
5909 		/// Default background color of the terminal.
5910 		Color defaultBackground = Color.white;
5911 
5912 		/++
5913 			Font to use in the window. It should be a monospace font,
5914 			and your selection may not actually be used if not available on
5915 			the user's system, in which case it will fallback to one.
5916 
5917 			History:
5918 				Implemented March 26, 2020
5919 		+/
5920 		string fontName;
5921 		/// ditto
5922 		int fontSize = 14;
5923 
5924 		/++
5925 			Requested initial terminal size in character cells. You may not actually get exactly this.
5926 		+/
5927 		int initialWidth = 80;
5928 		/// ditto
5929 		int initialHeight = 40;
5930 
5931 		/++
5932 			If `true`, the window will close automatically when the main thread exits.
5933 			Otherwise, the window will remain open so the user can work with output before
5934 			it disappears.
5935 
5936 			History:
5937 				Added April 10, 2020 (v7.2.0)
5938 		+/
5939 		bool closeOnExit = false;
5940 
5941 		/++
5942 			Gives you a chance to modify the window as it is constructed. Intended
5943 			to let you add custom menu options.
5944 
5945 			---
5946 			import arsd.terminal;
5947 			integratedTerminalEmulatorConfiguration.menuExtensionsConstructor = (TerminalEmulatorWindow window) {
5948 				import arsd.minigui; // for the menu related UDAs
5949 				class Commands {
5950 					@menu("Help") {
5951 						void Topics() {
5952 							auto window = new Window(); // make a help window of some sort
5953 							window.show();
5954 						}
5955 
5956 						@separator
5957 
5958 						void About() {
5959 							messageBox("My Application v 1.0");
5960 						}
5961 					}
5962 				}
5963 				window.setMenuAndToolbarFromAnnotatedCode(new Commands());
5964 			};
5965 			---
5966 
5967 			History:
5968 				Added March 29, 2020. Included in release v7.1.0.
5969 		+/
5970 		void delegate(TerminalEmulatorWindow) menuExtensionsConstructor;
5971 	}
5972 
5973 	/+
5974 		status bar should probably tell
5975 		if scroll lock is on...
5976 	+/
5977 
5978 	/// You can set this in a static module constructor. (`shared static this() {}`)
5979 	__gshared IntegratedTerminalEmulatorConfiguration integratedTerminalEmulatorConfiguration;
5980 
5981 	import arsd.terminalemulator;
5982 	import arsd.minigui;
5983 
5984 	/++
5985 		Represents the window that the library pops up for you.
5986 	+/
5987 	final class TerminalEmulatorWindow : MainWindow {
5988 
5989 		/++
5990 			Gives access to the underlying terminal emulation object.
5991 		+/
5992 		TerminalEmulator terminalEmulator() {
5993 			return tew.terminalEmulator;
5994 		}
5995 
5996 		private TerminalEmulatorWindow parent;
5997 		private TerminalEmulatorWindow[] children;
5998 		private void childClosing(TerminalEmulatorWindow t) {
5999 			foreach(idx, c; children)
6000 				if(c is t)
6001 					children = children[0 .. idx] ~ children[idx + 1 .. $];
6002 		}
6003 		private void registerChild(TerminalEmulatorWindow t) {
6004 			children ~= t;
6005 		}
6006 
6007 		private this(Terminal* term, TerminalEmulatorWindow parent) {
6008 
6009 			this.parent = parent;
6010 			scope(success) if(parent) parent.registerChild(this);
6011 
6012 			super("Terminal Application", integratedTerminalEmulatorConfiguration.initialWidth * integratedTerminalEmulatorConfiguration.fontSize / 2, integratedTerminalEmulatorConfiguration.initialHeight * integratedTerminalEmulatorConfiguration.fontSize);
6013 
6014 			smw = new ScrollMessageWidget(this);
6015 			tew = new TerminalEmulatorWidget(term, smw);
6016 
6017 			smw.addEventListener("scroll", () {
6018 				tew.terminalEmulator.scrollbackTo(smw.position.x, smw.position.y + tew.terminalEmulator.height);
6019 				redraw();
6020 			});
6021 
6022 			smw.setTotalArea(1, 1);
6023 
6024 			setMenuAndToolbarFromAnnotatedCode(this);
6025 			if(integratedTerminalEmulatorConfiguration.menuExtensionsConstructor)
6026 				integratedTerminalEmulatorConfiguration.menuExtensionsConstructor(this);
6027 		}
6028 
6029 		TerminalEmulator.TerminalCell[] delegate(TerminalEmulator.TerminalCell[] i) parentFilter;
6030 
6031 		private void addScrollbackLineFromParent(TerminalEmulator.TerminalCell[] lineIn) {
6032 			if(parentFilter is null)
6033 				return;
6034 
6035 			auto line = parentFilter(lineIn);
6036 			if(line is null) return;
6037 
6038 			if(tew && tew.terminalEmulator) {
6039 				bool atBottom = smw.verticalScrollBar.atEnd && smw.horizontalScrollBar.atStart;
6040 				tew.terminalEmulator.addScrollbackLine(line);
6041 				tew.terminalEmulator.notifyScrollbackAdded();
6042 				if(atBottom) {
6043 					tew.terminalEmulator.notifyScrollbarPosition(0, int.max);
6044 					tew.terminalEmulator.scrollbackTo(0, int.max);
6045 					tew.redraw();
6046 				}
6047 			}
6048 		}
6049 
6050 		private TerminalEmulatorWidget tew;
6051 		private ScrollMessageWidget smw;
6052 
6053 		@menu("&History") {
6054 			@tip("Saves the currently visible content to a file")
6055 			void Save() {
6056 				getSaveFileName((string name) {
6057 					tew.terminalEmulator.writeScrollbackToFile(name);
6058 				});
6059 			}
6060 
6061 			// FIXME
6062 			version(FIXME)
6063 			void Save_HTML() {
6064 
6065 			}
6066 
6067 			@separator
6068 			/*
6069 			void Find() {
6070 				// FIXME
6071 				// jump to the previous instance in the scrollback
6072 
6073 			}
6074 			*/
6075 
6076 			void Filter() {
6077 				// open a new window that just shows items that pass the filter
6078 
6079 				static struct FilterParams {
6080 					string searchTerm;
6081 					bool caseSensitive;
6082 				}
6083 
6084 				dialog((FilterParams p) {
6085 					auto nw = new TerminalEmulatorWindow(null, this);
6086 
6087 					nw.parentFilter = (TerminalEmulator.TerminalCell[] line) {
6088 						import std.algorithm;
6089 						import std.uni;
6090 						// omg autodecoding being kinda useful for once LOL
6091 						if(line.map!(c => c.hasNonCharacterData ? dchar(0) : (p.caseSensitive ? c.ch : c.ch.toLower)).
6092 							canFind(p.searchTerm))
6093 						{
6094 							// I might highlight the match too, but meh for now
6095 							return line;
6096 						}
6097 						return null;
6098 					};
6099 
6100 					foreach(line; tew.terminalEmulator.sbb[0 .. $]) {
6101 						if(auto l = nw.parentFilter(line))
6102 							nw.tew.terminalEmulator.addScrollbackLine(l);
6103 					}
6104 					nw.tew.terminalEmulator.toggleScrollLock();
6105 					nw.tew.terminalEmulator.drawScrollback();
6106 					nw.title = "Filter Display";
6107 					nw.show();
6108 				});
6109 
6110 			}
6111 
6112 			@separator
6113 			void Clear() {
6114 				tew.terminalEmulator.clearScrollbackHistory();
6115 				tew.terminalEmulator.cls();
6116 				tew.terminalEmulator.moveCursor(0, 0);
6117 				if(tew.term) {
6118 					tew.term.windowSizeChanged = true;
6119 					tew.terminalEmulator.outgoingSignal.notify();
6120 				}
6121 				tew.redraw();
6122 			}
6123 
6124 			@separator
6125 			void Exit() @accelerator("Alt+F4") @hotkey('x') {
6126 				this.close();
6127 			}
6128 		}
6129 
6130 		@menu("&Edit") {
6131 			void Copy() {
6132 				tew.terminalEmulator.copyToClipboard(tew.terminalEmulator.getSelectedText());
6133 			}
6134 
6135 			void Paste() {
6136 				tew.terminalEmulator.pasteFromClipboard(&tew.terminalEmulator.sendPasteData);
6137 			}
6138 		}
6139 	}
6140 
6141 	private class InputEventInternal {
6142 		const(ubyte)[] data;
6143 		this(in ubyte[] data) {
6144 			this.data = data;
6145 		}
6146 	}
6147 
6148 	private class TerminalEmulatorWidget : Widget {
6149 
6150 		Menu ctx;
6151 
6152 		override Menu contextMenu(int x, int y) {
6153 			if(ctx is null) {
6154 				ctx = new Menu("");
6155 				ctx.addItem(new MenuItem(new Action("Copy", 0, {
6156 					terminalEmulator.copyToClipboard(terminalEmulator.getSelectedText());
6157 				})));
6158 				 ctx.addItem(new MenuItem(new Action("Paste", 0, {
6159 					terminalEmulator.pasteFromClipboard(&terminalEmulator.sendPasteData);
6160 				})));
6161 				 ctx.addItem(new MenuItem(new Action("Toggle Scroll Lock", 0, {
6162 				 	terminalEmulator.toggleScrollLock();
6163 				})));
6164 			}
6165 			return ctx;
6166 		}
6167 
6168 		this(Terminal* term, ScrollMessageWidget parent) {
6169 			this.smw = parent;
6170 			this.term = term;
6171 			terminalEmulator = new TerminalEmulatorInsideWidget(this);
6172 			super(parent);
6173 			this.parentWindow.win.onClosing = {
6174 				if(term)
6175 					term.hangedUp = true;
6176 
6177 				if(auto wi = cast(TerminalEmulatorWindow) this.parentWindow) {
6178 					if(wi.parent)
6179 						wi.parent.childClosing(wi);
6180 				}
6181 
6182 				// try to get it to terminate slightly more forcibly too, if possible
6183 				if(sigIntExtension)
6184 					sigIntExtension();
6185 
6186 				terminalEmulator.outgoingSignal.notify();
6187 				terminalEmulator.incomingSignal.notify();
6188 			};
6189 
6190 			this.parentWindow.win.addEventListener((InputEventInternal ie) {
6191 				terminalEmulator.sendRawInput(ie.data);
6192 				this.redraw();
6193 				terminalEmulator.incomingSignal.notify();
6194 			});
6195 		}
6196 
6197 		ScrollMessageWidget smw;
6198 		Terminal* term;
6199 
6200 		void sendRawInput(const(ubyte)[] data) {
6201 			if(this.parentWindow) {
6202 				this.parentWindow.win.postEvent(new InputEventInternal(data));
6203 				terminalEmulator.incomingSignal.wait(); // blocking write basically, wait until the TE confirms the receipt of it
6204 			}
6205 		}
6206 
6207 		TerminalEmulatorInsideWidget terminalEmulator;
6208 
6209 		override void registerMovement() {
6210 			super.registerMovement();
6211 			terminalEmulator.resized(width, height);
6212 		}
6213 
6214 		override void focus() {
6215 			super.focus();
6216 			terminalEmulator.attentionReceived();
6217 		}
6218 
6219 		override MouseCursor cursor() { return GenericCursor.Text; }
6220 
6221 		override void erase(WidgetPainter painter) { /* intentionally blank, paint does it better */ }
6222 
6223 		override void paint(WidgetPainter painter) {
6224 			bool forceRedraw = false;
6225 			if(terminalEmulator.invalidateAll || terminalEmulator.clearScreenRequested) {
6226 				auto clearColor = terminalEmulator.defaultBackground;
6227 				painter.outlineColor = clearColor;
6228 				painter.fillColor = clearColor;
6229 				painter.drawRectangle(Point(0, 0), this.width, this.height);
6230 				terminalEmulator.clearScreenRequested = false;
6231 				forceRedraw = true;
6232 			}
6233 
6234 			terminalEmulator.redrawPainter(painter, forceRedraw);
6235 		}
6236 	}
6237 
6238 	private class TerminalEmulatorInsideWidget : TerminalEmulator {
6239 
6240 		private ScrollbackBuffer sbb() { return scrollbackBuffer; }
6241 
6242 		void resized(int w, int h) {
6243 			this.resizeTerminal(w / fontWidth, h / fontHeight);
6244 			if(widget && widget.smw) {
6245 				widget.smw.setViewableArea(this.width, this.height);
6246 				widget.smw.setPageSize(this.width / 2, this.height / 2);
6247 			}
6248 			clearScreenRequested = true;
6249 			if(widget && widget.term)
6250 				widget.term.windowSizeChanged = true;
6251 			outgoingSignal.notify();
6252 			redraw();
6253 		}
6254 
6255 		override void addScrollbackLine(TerminalCell[] line) {
6256 			super.addScrollbackLine(line);
6257 			if(widget)
6258 			if(auto p = cast(TerminalEmulatorWindow) widget.parentWindow) {
6259 				foreach(child; p.children)
6260 					child.addScrollbackLineFromParent(line);
6261 			}
6262 		}
6263 
6264 		override void notifyScrollbackAdded() {
6265 			widget.smw.setTotalArea(this.scrollbackWidth > this.width ? this.scrollbackWidth : this.width, this.scrollbackLength > this.height ? this.scrollbackLength : this.height);
6266 		}
6267 
6268 		override void notifyScrollbarPosition(int x, int y) {
6269 			widget.smw.setPosition(x, y);
6270 			widget.redraw();
6271 		}
6272 
6273 		override void notifyScrollbarRelevant(bool isRelevantHorizontally, bool isRelevantVertically) {
6274 			if(isRelevantVertically)
6275 				notifyScrollbackAdded();
6276 			else
6277 				widget.smw.setTotalArea(width, height);
6278 		}
6279 
6280 		override @property public int cursorX() { return super.cursorX; }
6281 		override @property public int cursorY() { return super.cursorY; }
6282 
6283 		protected override void changeCursorStyle(CursorStyle s) { }
6284 
6285 		string currentTitle;
6286 		protected override void changeWindowTitle(string t) {
6287 			if(widget && widget.parentWindow && t.length) {
6288 				widget.parentWindow.win.title = t;
6289 				currentTitle = t;
6290 			}
6291 		}
6292 		protected override void changeWindowIcon(IndexedImage t) {
6293 			if(widget && widget.parentWindow && t)
6294 				widget.parentWindow.win.icon = t;
6295 		}
6296 
6297 		protected override void changeIconTitle(string) {}
6298 		protected override void changeTextAttributes(TextAttributes) {}
6299 		protected override void soundBell() {
6300 			static if(UsingSimpledisplayX11)
6301 				XBell(XDisplayConnection.get(), 50);
6302 		}
6303 
6304 		protected override void demandAttention() {
6305 			if(widget && widget.parentWindow)
6306 				widget.parentWindow.win.requestAttention();
6307 		}
6308 
6309 		protected override void copyToClipboard(string text) {
6310 			setClipboardText(widget.parentWindow.win, text);
6311 		}
6312 
6313 		override int maxScrollbackLength() const {
6314 			return int.max; // no scrollback limit for custom programs
6315 		}
6316 
6317 		protected override void pasteFromClipboard(void delegate(in char[]) dg) {
6318 			static if(UsingSimpledisplayX11)
6319 				getPrimarySelection(widget.parentWindow.win, dg);
6320 			else
6321 				getClipboardText(widget.parentWindow.win, (in char[] dataIn) {
6322 					char[] data;
6323 					// change Windows \r\n to plain \n
6324 					foreach(char ch; dataIn)
6325 						if(ch != 13)
6326 							data ~= ch;
6327 					dg(data);
6328 				});
6329 		}
6330 
6331 		protected override void copyToPrimary(string text) {
6332 			static if(UsingSimpledisplayX11)
6333 				setPrimarySelection(widget.parentWindow.win, text);
6334 			else
6335 				{}
6336 		}
6337 		protected override void pasteFromPrimary(void delegate(in char[]) dg) {
6338 			static if(UsingSimpledisplayX11)
6339 				getPrimarySelection(widget.parentWindow.win, dg);
6340 		}
6341 
6342 		override void requestExit() {
6343 			widget.parentWindow.close();
6344 		}
6345 
6346 		bool echo = false;
6347 
6348 		override void sendRawInput(in ubyte[] data) {
6349 			void send(in ubyte[] data) {
6350 				if(data.length == 0)
6351 					return;
6352 				super.sendRawInput(data);
6353 				if(echo)
6354 				sendToApplication(data);
6355 			}
6356 
6357 			// need to echo, translate 10 to 13/10 cr-lf
6358 			size_t last = 0;
6359 			const ubyte[2] crlf = [13, 10];
6360 			foreach(idx, ch; data) {
6361 				if(ch == 10) {
6362 					send(data[last .. idx]);
6363 					send(crlf[]);
6364 					last = idx + 1;
6365 				}
6366 			}
6367 
6368 			if(last < data.length)
6369 				send(data[last .. $]);
6370 		}
6371 
6372 		bool focused;
6373 
6374 		TerminalEmulatorWidget widget;
6375 
6376 		import arsd.simpledisplay;
6377 		import arsd.color;
6378 		import core.sync.semaphore;
6379 		alias ModifierState = arsd.simpledisplay.ModifierState;
6380 		alias Color = arsd.color.Color;
6381 		alias fromHsl = arsd.color.fromHsl;
6382 
6383 		const(ubyte)[] pendingForApplication;
6384 		Semaphore outgoingSignal;
6385 		Semaphore incomingSignal;
6386 
6387 		override void sendToApplication(scope const(void)[] what) {
6388 			synchronized(this) {
6389 				pendingForApplication ~= cast(const(ubyte)[]) what;
6390 			}
6391 			outgoingSignal.notify();
6392 		}
6393 
6394 		@property int width() { return screenWidth; }
6395 		@property int height() { return screenHeight; }
6396 
6397 		@property bool invalidateAll() { return super.invalidateAll; }
6398 
6399 		private this(TerminalEmulatorWidget widget) {
6400 
6401 			this.outgoingSignal = new Semaphore();
6402 			this.incomingSignal = new Semaphore();
6403 
6404 			this.widget = widget;
6405 
6406 			if(integratedTerminalEmulatorConfiguration.fontName.length) {
6407 				this.font = new OperatingSystemFont(integratedTerminalEmulatorConfiguration.fontName, integratedTerminalEmulatorConfiguration.fontSize, FontWeight.medium);
6408 				this.fontWidth = font.averageWidth;
6409 				this.fontHeight = font.height;
6410 			}
6411 
6412 
6413 			if(this.font is null || this.font.isNull)
6414 				loadDefaultFont(integratedTerminalEmulatorConfiguration.fontSize);
6415 
6416 			super(integratedTerminalEmulatorConfiguration.initialWidth, integratedTerminalEmulatorConfiguration.initialHeight);
6417 
6418 			defaultForeground = integratedTerminalEmulatorConfiguration.defaultForeground;
6419 			defaultBackground = integratedTerminalEmulatorConfiguration.defaultBackground;
6420 
6421 			bool skipNextChar = false;
6422 
6423 			widget.addEventListener("mousedown", (Event ev) {
6424 				int termX = (ev.clientX - paddingLeft) / fontWidth;
6425 				int termY = (ev.clientY - paddingTop) / fontHeight;
6426 
6427 				if((!mouseButtonTracking || (ev.state & ModifierState.shift)) && ev.button == MouseButton.right)
6428 					widget.showContextMenu(ev.clientX, ev.clientY);
6429 				else
6430 					if(sendMouseInputToApplication(termX, termY,
6431 						arsd.terminalemulator.MouseEventType.buttonPressed,
6432 						cast(arsd.terminalemulator.MouseButton) ev.button,
6433 						(ev.state & ModifierState.shift) ? true : false,
6434 						(ev.state & ModifierState.ctrl) ? true : false,
6435 						(ev.state & ModifierState.alt) ? true : false
6436 					))
6437 						redraw();
6438 			});
6439 
6440 			widget.addEventListener("mouseup", (Event ev) {
6441 				int termX = (ev.clientX - paddingLeft) / fontWidth;
6442 				int termY = (ev.clientY - paddingTop) / fontHeight;
6443 
6444 				if(sendMouseInputToApplication(termX, termY,
6445 					arsd.terminalemulator.MouseEventType.buttonReleased,
6446 					cast(arsd.terminalemulator.MouseButton) ev.button,
6447 					(ev.state & ModifierState.shift) ? true : false,
6448 					(ev.state & ModifierState.ctrl) ? true : false,
6449 					(ev.state & ModifierState.alt) ? true : false
6450 				))
6451 					redraw();
6452 			});
6453 
6454 			widget.addEventListener("mousemove", (Event ev) {
6455 				int termX = (ev.clientX - paddingLeft) / fontWidth;
6456 				int termY = (ev.clientY - paddingTop) / fontHeight;
6457 
6458 				if(sendMouseInputToApplication(termX, termY,
6459 					arsd.terminalemulator.MouseEventType.motion,
6460 					cast(arsd.terminalemulator.MouseButton) ev.button,
6461 					(ev.state & ModifierState.shift) ? true : false,
6462 					(ev.state & ModifierState.ctrl) ? true : false,
6463 					(ev.state & ModifierState.alt) ? true : false
6464 				))
6465 					redraw();
6466 			});
6467 
6468 			widget.addEventListener("keydown", (Event ev) {
6469 				static string magic() {
6470 					string code;
6471 					foreach(member; __traits(allMembers, TerminalKey))
6472 						if(member != "Escape")
6473 							code ~= "case Key." ~ member ~ ": if(sendKeyToApplication(TerminalKey." ~ member ~ "
6474 								, (ev.state & ModifierState.shift)?true:false
6475 								, (ev.state & ModifierState.alt)?true:false
6476 								, (ev.state & ModifierState.ctrl)?true:false
6477 								, (ev.state & ModifierState.windows)?true:false
6478 							)) redraw(); break;";
6479 					return code;
6480 				}
6481 
6482 
6483 				switch(ev.key) {
6484 					mixin(magic());
6485 					default:
6486 						// keep going, not special
6487 				}
6488 
6489 				return; // the character event handler will do others
6490 			});
6491 
6492 			widget.addEventListener("char", (Event ev) {
6493 				dchar c = ev.character;
6494 				if(skipNextChar) {
6495 					skipNextChar = false;
6496 					return;
6497 				}
6498 
6499 				endScrollback();
6500 				char[4] str;
6501 				import std.utf;
6502 				if(c == '\n') c = '\r'; // terminal seem to expect enter to send 13 instead of 10
6503 				auto data = str[0 .. encode(str, c)];
6504 
6505 
6506 				if(c == 0x1c) /* ctrl+\, force quit */ {
6507 					version(Posix) {
6508 						import core.sys.posix.signal;
6509 						pthread_kill(widget.term.threadId, SIGQUIT); // or SIGKILL even?
6510 
6511 						assert(0);
6512 						//import core.sys.posix.pthread;
6513 						//pthread_cancel(widget.term.threadId);
6514 						//widget.term = null;
6515 					} else version(Windows) {
6516 						import core.sys.windows.windows;
6517 						auto hnd = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, TRUE, GetCurrentProcessId());
6518 						TerminateProcess(hnd, -1);
6519 						assert(0);
6520 					}
6521 				} else if(c == 3) /* ctrl+c, interrupt */ {
6522 					if(sigIntExtension)
6523 						sigIntExtension();
6524 
6525 					if(widget && widget.term) {
6526 						widget.term.interrupted = true;
6527 						outgoingSignal.notify();
6528 					}
6529 				} else if(c != 127) {
6530 					// on X11, the delete key can send a 127 character too, but that shouldn't be sent to the terminal since xterm shoots \033[3~ instead, which we handle in the KeyEvent handler.
6531 					sendToApplication(data);
6532 				}
6533 			});
6534 		}
6535 
6536 		bool clearScreenRequested = true;
6537 		void redraw() {
6538 			if(widget.parentWindow is null || widget.parentWindow.win is null || widget.parentWindow.win.closed)
6539 				return;
6540 
6541 			widget.redraw();
6542 		}
6543 
6544 		mixin SdpyDraw;
6545 	}
6546 } else {
6547 	///
6548 	enum IntegratedEmulator = false;
6549 }
6550 
6551 /*
6552 void main() {
6553 	auto terminal = Terminal(ConsoleOutputType.linear);
6554 	terminal.setTrueColor(RGB(255, 0, 255), RGB(255, 255, 255));
6555 	terminal.writeln("Hello, world!");
6556 }
6557 */
6558 
6559 
6560 /*
6561 	ONLY SUPPORTED ON MY TERMINAL EMULATOR IN GENERAL
6562 
6563 	bracketed section can collapse and scroll independently in the TE. may also pop out into a window (possibly with a comparison window)
6564 
6565 	hyperlink can either just indicate something to the TE to handle externally
6566 	OR
6567 	indicate a certain input sequence be triggered when it is clicked (prolly wrapped up as a paste event). this MAY also be a custom event.
6568 
6569 	internally it can set two bits: one indicates it is a hyperlink, the other just flips each use to separate consecutive sequences.
6570 
6571 	it might require the content of the paste event to be the visible word but it would bne kinda cool if it could be some secret thing elsewhere.
6572 
6573 
6574 	I could spread a unique id number across bits, one bit per char so the memory isn't too bad.
6575 	so it would set a number and a word. this is sent back to the application to handle internally.
6576 
6577 	1) turn on special input
6578 	2) turn off special input
6579 	3) special input sends a paste event with a number and the text
6580 	4) to make a link, you write out the begin sequence, the text, and the end sequence. including the magic number somewhere.
6581 		magic number is allowed to have one bit per char. the terminal discards anything else. terminal.d api will enforce.
6582 
6583 	if magic number is zero, it is not sent in the paste event. maybe.
6584 
6585 	or if it is like 255, it is handled as a url and opened externally
6586 		tho tbh a url could just be detected by regex pattern
6587 
6588 
6589 	NOTE: if your program requests mouse input, the TE does not process it! Thus the user will have to shift+click for it.
6590 
6591 	mode 3004 for bracketed hyperlink
6592 
6593 	hyperlink sequence: \033[?220hnum;text\033[?220l~
6594 
6595 */