Set inited flag _after_ TermInfoDriver initialisation completes (#18318)
[mono-project.git] / mcs / class / corlib / System / TermInfoDriver.cs
blobc16b7367279d6681d2c00a852214b633accae6e1
1 //
2 // System.ConsoleDriver
3 //
4 // Authors:
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 //
7 // (C) 2005,2006 Novell, Inc (http://www.novell.com)
8 // Copyright (c) Microsoft.
9 // Copyright 2014 Xamarin Inc
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 //
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 //
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 // This code contains the ParameterizedStrings implementation from .NET's
32 // Core System.Console:
33 // https://github.com/dotnet/corefx
34 // src/System.Console/src/System/ConsolePal.Unix.cs
36 #if MONO_FEATURE_CONSOLE
39 // Defining this writes the output to console.log
40 //#define DEBUG
42 using System.Collections;
43 using System.IO;
44 using System.Text;
45 using System.Runtime.InteropServices;
46 namespace System {
47 class TermInfoDriver : IConsoleDriver {
49 // This points to a variable that is updated by unmanage code on window size changes.
50 unsafe static int *native_terminal_size;
52 // The current size that we believe we have
53 static int terminal_size;
55 //static uint flag = 0xdeadbeef;
56 readonly static string [] locations = { "/usr/share/terminfo", "/etc/terminfo", "/usr/lib/terminfo", "/lib/terminfo" };
58 TermInfoReader reader;
59 int cursorLeft;
60 int cursorTop;
61 string title = String.Empty;
62 string titleFormat = String.Empty;
63 bool cursorVisible = true;
64 string csrVisible;
65 string csrInvisible;
66 string clear;
67 string bell;
68 string term;
69 StreamReader stdin;
70 CStreamWriter stdout;
72 int windowWidth;
73 int windowHeight;
74 //int windowTop;
75 //int windowLeft;
76 int bufferHeight;
77 int bufferWidth;
79 char [] buffer;
80 int readpos;
81 int writepos;
82 string keypadXmit, keypadLocal;
83 bool controlCAsInput;
84 bool inited;
85 object initLock = new object ();
86 bool initKeys;
87 string origPair;
88 string origColors;
89 string cursorAddress;
90 ConsoleColor fgcolor = ConsoleColor.White;
91 ConsoleColor bgcolor = ConsoleColor.Black;
92 string setfgcolor;
93 string setbgcolor;
94 int maxColors;
95 bool noGetPosition;
96 Hashtable keymap;
97 ByteMatcher rootmap;
98 int rl_startx = -1, rl_starty = -1;
99 byte [] control_characters; // Indexed by ControlCharacters.XXXXXX
100 #if DEBUG
101 StreamWriter logger;
102 #endif
104 static string TryTermInfoDir (string dir, string term )
106 string path = String.Format ("{0}/{1:x}/{2}", dir, (int)(term [0]), term);
107 if (File.Exists (path))
108 return path;
110 path = Path.Combine (dir, term.Substring (0, 1), term);
111 if (File.Exists (path))
112 return path;
113 return null;
116 static string SearchTerminfo (string term)
118 if (term == null || term == String.Empty)
119 return null;
121 string path;
122 string terminfo = Environment.GetEnvironmentVariable ("TERMINFO");
123 if (terminfo != null && Directory.Exists (terminfo)){
124 path = TryTermInfoDir (terminfo, term);
125 if (path != null)
126 return path;
129 foreach (string dir in locations) {
130 if (!Directory.Exists (dir))
131 continue;
133 path = TryTermInfoDir (dir, term);
134 if (path != null)
135 return path;
138 return null;
141 void WriteConsole (string str)
143 if (str == null)
144 return;
146 stdout.InternalWriteString (str);
149 public TermInfoDriver ()
150 : this (Environment.GetEnvironmentVariable ("TERM"))
154 public TermInfoDriver (string term)
156 #if DEBUG
157 File.Delete ("console.log");
158 logger = new StreamWriter (File.OpenWrite ("console.log"));
159 #endif
160 this.term = term;
162 string filename = SearchTerminfo (term);
163 if (filename != null)
164 reader = new TermInfoReader (term, filename);
165 else {
166 // fallbacks
167 if (term == "xterm") {
168 reader = new TermInfoReader (term, KnownTerminals.xterm);
169 } else if (term == "linux") {
170 reader = new TermInfoReader (term, KnownTerminals.linux);
174 if (reader == null)
175 reader = new TermInfoReader (term, KnownTerminals.ansi);
177 if (!(Console.stdout is CStreamWriter)) {
178 // Application set its own stdout, we need a reference to the real stdout
179 stdout = new CStreamWriter (Console.OpenStandardOutput (0), Console.OutputEncoding, false);
180 ((StreamWriter) stdout).AutoFlush = true;
181 } else {
182 stdout = (CStreamWriter) Console.stdout;
186 public bool Initialized {
187 get { return inited; }
190 public void Init ()
192 if (inited)
193 return;
195 lock (initLock){
196 if (inited)
197 return;
199 try {
200 /* This should not happen any more, since it is checked for in Console */
201 if (!ConsoleDriver.IsConsole)
202 throw new IOException ("Not a tty.");
204 ConsoleDriver.SetEcho (false);
206 string endString = null;
207 keypadXmit = reader.Get (TermInfoStrings.KeypadXmit);
208 keypadLocal = reader.Get (TermInfoStrings.KeypadLocal);
209 if (keypadXmit != null) {
210 WriteConsole (keypadXmit); // Needed to get the arrows working
211 if (keypadLocal != null)
212 endString += keypadLocal;
215 origPair = reader.Get (TermInfoStrings.OrigPair);
216 origColors = reader.Get (TermInfoStrings.OrigColors);
217 setfgcolor = reader.Get (TermInfoStrings.SetAForeground);
218 setbgcolor = reader.Get (TermInfoStrings.SetABackground);
219 maxColors = reader.Get (TermInfoNumbers.MaxColors);
220 maxColors = Math.Max (Math.Min (maxColors, 16), 1);
222 string resetColors = (origColors == null) ? origPair : origColors;
223 if (resetColors != null)
224 endString += resetColors;
226 unsafe {
227 if (!ConsoleDriver.TtySetup (keypadXmit, endString, out control_characters, out native_terminal_size)){
228 control_characters = new byte [17];
229 native_terminal_size = null;
230 //throw new IOException ("Error initializing terminal.");
234 stdin = new StreamReader (Console.OpenStandardInput (0), Console.InputEncoding);
235 clear = reader.Get (TermInfoStrings.ClearScreen);
236 bell = reader.Get (TermInfoStrings.Bell);
237 if (clear == null) {
238 clear = reader.Get (TermInfoStrings.CursorHome);
239 clear += reader.Get (TermInfoStrings.ClrEos);
242 csrVisible = reader.Get (TermInfoStrings.CursorNormal);
243 if (csrVisible == null)
244 csrVisible = reader.Get (TermInfoStrings.CursorVisible);
246 csrInvisible = reader.Get (TermInfoStrings.CursorInvisible);
247 if (term == "cygwin" || term == "linux" || (term != null && term.StartsWith ("xterm")) ||
248 term == "rxvt" || term == "dtterm") {
249 titleFormat = "\x1b]0;{0}\x7"; // icon + window title
250 } else if (term == "iris-ansi") {
251 titleFormat = "\x1bP1.y{0}\x1b\\"; // not tested
252 } else if (term == "sun-cmd") {
253 titleFormat = "\x1b]l{0}\x1b\\"; // not tested
256 cursorAddress = reader.Get (TermInfoStrings.CursorAddress);
258 GetCursorPosition ();
259 #if DEBUG
260 logger.WriteLine ("noGetPosition: {0} left: {1} top: {2}", noGetPosition, cursorLeft, cursorTop);
261 logger.Flush ();
262 #endif
263 if (noGetPosition) {
264 WriteConsole (clear);
265 cursorLeft = 0;
266 cursorTop = 0;
269 } finally {
270 inited = true;
276 void IncrementX ()
278 cursorLeft++;
279 if (cursorLeft >= WindowWidth) {
280 cursorTop++;
281 cursorLeft = 0;
282 if (cursorTop >= WindowHeight) {
283 // Writing beyond the initial screen
284 if (rl_starty != -1) rl_starty--;
285 cursorTop--;
290 // Should never get called unless inited
291 public void WriteSpecialKey (ConsoleKeyInfo key)
293 switch (key.Key) {
294 case ConsoleKey.Backspace:
295 if (cursorLeft > 0) {
296 if (cursorLeft <= rl_startx && cursorTop == rl_starty)
297 break;
298 cursorLeft--;
299 SetCursorPosition (cursorLeft, cursorTop);
300 WriteConsole (" ");
301 SetCursorPosition (cursorLeft, cursorTop);
303 #if DEBUG
304 logger.WriteLine ("BS left: {0} top: {1}", cursorLeft, cursorTop);
305 logger.Flush ();
306 #endif
307 break;
308 case ConsoleKey.Tab:
309 int n = 8 - (cursorLeft % 8);
310 for (int i = 0; i < n; i++){
311 IncrementX ();
313 WriteConsole ("\t");
314 break;
315 case ConsoleKey.Clear:
316 WriteConsole (clear);
317 cursorLeft = 0;
318 cursorTop = 0;
319 break;
320 case ConsoleKey.Enter:
321 break;
322 default:
323 break;
325 #if DEBUG
326 logger.WriteLine ("left: {0} top: {1}", cursorLeft, cursorTop);
327 logger.Flush ();
328 #endif
331 // Should never get called unless inited
332 public void WriteSpecialKey (char c)
334 WriteSpecialKey (CreateKeyInfoFromInt (c, false));
337 public bool IsSpecialKey (ConsoleKeyInfo key)
339 if (!inited)
340 return false;
342 switch (key.Key) {
343 case ConsoleKey.Backspace:
344 return true;
345 case ConsoleKey.Tab:
346 return true;
347 case ConsoleKey.Clear:
348 return true;
349 case ConsoleKey.Enter:
350 cursorLeft = 0;
351 cursorTop++;
352 if (cursorTop >= WindowHeight) {
353 cursorTop--;
354 //TODO: scroll up
356 return false;
357 default:
358 // CStreamWriter will handle writing this key
359 IncrementX ();
360 return false;
364 public bool IsSpecialKey (char c)
366 return IsSpecialKey (CreateKeyInfoFromInt (c, false));
369 /// <summary>
370 /// The values of the ConsoleColor enums unfortunately don't map to the
371 /// corresponding ANSI values. We need to do the mapping manually.
372 /// See http://en.wikipedia.org/wiki/ANSI_escape_code#Colors
373 /// </summary>
374 private static readonly int[] _consoleColorToAnsiCode = new int[]
376 // Dark/Normal colors
377 0, // Black,
378 4, // DarkBlue,
379 2, // DarkGreen,
380 6, // DarkCyan,
381 1, // DarkRed,
382 5, // DarkMagenta,
383 3, // DarkYellow,
384 7, // Gray,
386 // Bright colors
387 8, // DarkGray,
388 12, // Blue,
389 10, // Green,
390 14, // Cyan,
391 9, // Red,
392 13, // Magenta,
393 11, // Yellow,
394 15 // White
397 void ChangeColor (string format, ConsoleColor color)
399 if (String.IsNullOrEmpty (format))
400 // the terminal doesn't support colors
401 return;
403 int ccValue = (int)color;
404 if ((ccValue & ~0xF) != 0)
405 throw new ArgumentException("Invalid Console Color");
407 int ansiCode = _consoleColorToAnsiCode[ccValue] % maxColors;
409 WriteConsole (ParameterizedStrings.Evaluate (format, ansiCode));
412 public ConsoleColor BackgroundColor {
413 get {
414 if (!inited) {
415 Init ();
418 return bgcolor;
420 set {
421 if (!inited) {
422 Init ();
424 ChangeColor (setbgcolor, value);
425 bgcolor = value;
429 public ConsoleColor ForegroundColor {
430 get {
431 if (!inited) {
432 Init ();
435 return fgcolor;
437 set {
438 if (!inited) {
439 Init ();
441 ChangeColor (setfgcolor, value);
442 fgcolor = value;
446 void GetCursorPosition ()
448 int row = 0, col = 0;
449 int b;
451 // First, get any data in the input buffer. Merely reduces the likelyhood of getting an error
452 int inqueue = ConsoleDriver.InternalKeyAvailable (0);
453 while (inqueue-- > 0){
454 b = stdin.Read ();
455 AddToBuffer (b);
458 // Then try to probe for the cursor coordinates
459 WriteConsole ("\x1b[6n");
460 if (ConsoleDriver.InternalKeyAvailable (1000) <= 0) {
461 noGetPosition = true;
462 return;
465 b = stdin.Read ();
466 while (b != '\x1b') {
467 AddToBuffer (b);
468 if (ConsoleDriver.InternalKeyAvailable (100) <= 0)
469 return;
470 b = stdin.Read ();
473 b = stdin.Read ();
474 if (b != '[') {
475 AddToBuffer ('\x1b');
476 AddToBuffer (b);
477 return;
480 b = stdin.Read ();
481 if (b != ';') {
482 row = b - '0';
483 b = stdin.Read ();
484 while ((b >= '0') && (b <= '9')) {
485 row = row * 10 + b - '0';
486 b = stdin.Read ();
488 // Row/col is 0 based
489 row --;
492 b = stdin.Read ();
493 if (b != 'R') {
494 col = b - '0';
495 b = stdin.Read ();
496 while ((b >= '0') && (b <= '9')) {
497 col = col * 10 + b - '0';
498 b = stdin.Read ();
500 // Row/col is 0 based
501 col --;
504 #if DEBUG
505 logger.WriteLine ("GetCursorPosition: {0}, {1}", col, row);
506 logger.Flush ();
507 #endif
509 cursorLeft = col;
510 cursorTop = row;
513 public int BufferHeight {
514 get {
515 if (!inited) {
516 Init ();
519 CheckWindowDimensions ();
520 return bufferHeight;
522 set {
523 if (!inited) {
524 Init ();
527 throw new NotSupportedException ();
531 public int BufferWidth {
532 get {
533 if (!inited) {
534 Init ();
537 CheckWindowDimensions ();
538 return bufferWidth;
540 set {
541 if (!inited) {
542 Init ();
545 throw new NotSupportedException ();
549 public bool CapsLock {
550 get {
551 if (!inited) {
552 Init ();
554 return false;
558 public int CursorLeft {
559 get {
560 if (!inited) {
561 Init ();
564 return cursorLeft;
566 set {
567 if (!inited) {
568 Init ();
571 SetCursorPosition (value, CursorTop);
575 public int CursorTop {
576 get {
577 if (!inited) {
578 Init ();
581 return cursorTop;
583 set {
584 if (!inited) {
585 Init ();
588 SetCursorPosition (CursorLeft, value);
592 public bool CursorVisible {
593 get {
594 if (!inited) {
595 Init ();
598 return cursorVisible;
600 set {
601 if (!inited) {
602 Init ();
605 cursorVisible = value;
606 WriteConsole ((value ? csrVisible : csrInvisible));
610 // we have CursorNormal vs. CursorVisible...
611 [MonoTODO]
612 public int CursorSize {
613 get {
614 if (!inited) {
615 Init ();
617 return 1;
619 set {
620 if (!inited) {
621 Init ();
627 public bool KeyAvailable {
628 get {
629 if (!inited) {
630 Init ();
633 return (writepos > readpos || ConsoleDriver.InternalKeyAvailable (0) > 0);
637 // We don't know these next 2 values, so return something reasonable
638 public int LargestWindowHeight {
639 get { return WindowHeight; }
642 public int LargestWindowWidth {
643 get { return WindowWidth; }
646 public bool NumberLock {
647 get {
648 if (!inited) {
649 Init ();
652 return false;
656 public string Title {
657 get {
658 if (!inited) {
659 Init ();
661 return title;
664 set {
665 if (!inited) {
666 Init ();
669 title = value;
670 WriteConsole (String.Format (titleFormat, value));
674 public bool TreatControlCAsInput {
675 get {
676 if (!inited) {
677 Init ();
679 return controlCAsInput;
681 set {
682 if (!inited) {
683 Init ();
686 if (controlCAsInput == value)
687 return;
689 ConsoleDriver.SetBreak (value);
690 controlCAsInput = value;
695 // Requries that caller calls Init () if not !inited.
697 unsafe void CheckWindowDimensions ()
699 if (native_terminal_size == null || terminal_size == *native_terminal_size)
700 return;
702 if (*native_terminal_size == -1){
703 int c = reader.Get (TermInfoNumbers.Columns);
704 if (c != 0)
705 windowWidth = c;
707 c = reader.Get (TermInfoNumbers.Lines);
708 if (c != 0)
709 windowHeight = c;
710 } else {
711 terminal_size = *native_terminal_size;
712 windowWidth = terminal_size >> 16;
713 windowHeight = terminal_size & 0xffff;
715 bufferHeight = windowHeight;
716 bufferWidth = windowWidth;
720 public int WindowHeight {
721 get {
722 if (!inited) {
723 Init ();
726 CheckWindowDimensions ();
727 return windowHeight;
729 set {
730 if (!inited) {
731 Init ();
734 throw new NotSupportedException ();
738 public int WindowLeft {
739 get {
740 if (!inited) {
741 Init ();
744 //CheckWindowDimensions ();
745 return 0;
747 set {
748 if (!inited) {
749 Init ();
752 throw new NotSupportedException ();
756 public int WindowTop {
757 get {
758 if (!inited) {
759 Init ();
762 //CheckWindowDimensions ();
763 return 0;
765 set {
766 if (!inited) {
767 Init ();
770 throw new NotSupportedException ();
774 public int WindowWidth {
775 get {
776 if (!inited) {
777 Init ();
780 CheckWindowDimensions ();
781 return windowWidth;
783 set {
784 if (!inited) {
785 Init ();
788 throw new NotSupportedException ();
792 public void Clear ()
794 if (!inited) {
795 Init ();
798 WriteConsole (clear);
799 cursorLeft = 0;
800 cursorTop = 0;
803 public void Beep (int frequency, int duration)
805 if (!inited) {
806 Init ();
809 WriteConsole (bell);
812 public void MoveBufferArea (int sourceLeft, int sourceTop, int sourceWidth, int sourceHeight,
813 int targetLeft, int targetTop, Char sourceChar,
814 ConsoleColor sourceForeColor, ConsoleColor sourceBackColor)
816 if (!inited) {
817 Init ();
820 throw new NotImplementedException ();
823 void AddToBuffer (int b)
825 if (buffer == null) {
826 buffer = new char [1024];
827 } else if (writepos >= buffer.Length) {
828 char [] newbuf = new char [buffer.Length * 2];
829 Buffer.BlockCopy (buffer, 0, newbuf, 0, buffer.Length);
830 buffer = newbuf;
833 buffer [writepos++] = (char) b;
836 void AdjustBuffer ()
838 if (readpos >= writepos) {
839 readpos = writepos = 0;
843 ConsoleKeyInfo CreateKeyInfoFromInt (int n, bool alt)
845 char c = (char) n;
846 ConsoleKey key = (ConsoleKey)n;
847 bool shift = false;
848 bool ctrl = false;
850 switch (n){
851 case 10:
852 key = ConsoleKey.Enter;
853 break;
854 case 0x20:
855 key = ConsoleKey.Spacebar;
856 break;
857 case 45:
858 key = ConsoleKey.Subtract;
859 break;
860 case 43:
861 key = ConsoleKey.Add;
862 break;
863 case 47:
864 key = ConsoleKey.Divide;
865 break;
866 case 42:
867 key = ConsoleKey.Multiply;
868 break;
869 case 8: case 9: case 12: case 13: case 19:
870 /* Values in ConsoleKey */
871 break;
872 case 27:
873 key = ConsoleKey.Escape;
874 break;
876 default:
877 if (n >= 1 && n <= 26) {
878 // For Ctrl-a to Ctrl-z.
879 ctrl = true;
880 key = ConsoleKey.A + n - 1;
881 } else if (n >= 'a' && n <= 'z') {
882 key = ConsoleKey.A - 'a' + n;
883 } else if (n >= 'A' && n <= 'Z') {
884 shift = true;
885 } else if (n >= '0' && n <= '9') {
886 } else
887 key = 0;
888 break;
891 return new ConsoleKeyInfo (c, key, shift, alt, ctrl);
894 object GetKeyFromBuffer (bool cooked)
896 if (readpos >= writepos)
897 return null;
899 int next = buffer [readpos];
900 if (!cooked || !rootmap.StartsWith (next)) {
901 readpos++;
902 AdjustBuffer ();
903 return CreateKeyInfoFromInt (next, false);
906 int used;
907 TermInfoStrings str = rootmap.Match (buffer, readpos, writepos - readpos, out used);
908 if ((int) str == -1){
909 // Escape sequences: alt keys are sent as ESC-key
910 if (buffer [readpos] == 27 && (writepos - readpos) >= 2){
911 readpos += 2;
912 AdjustBuffer ();
913 if (buffer [readpos+1] == 127)
914 return new ConsoleKeyInfo ((char)8, ConsoleKey.Backspace, false, true, false);
915 return CreateKeyInfoFromInt (buffer [readpos+1], true);
916 } else
917 return null;
920 ConsoleKeyInfo key;
921 if (keymap [str] != null) {
922 key = (ConsoleKeyInfo) keymap [str];
923 } else {
924 readpos++;
925 AdjustBuffer ();
926 return CreateKeyInfoFromInt (next, false);
929 readpos += used;
930 AdjustBuffer ();
931 return key;
934 ConsoleKeyInfo ReadKeyInternal (out bool fresh)
936 if (!inited)
937 Init ();
939 InitKeys ();
941 object o;
943 if ((o = GetKeyFromBuffer (true)) == null) {
944 do {
945 if (ConsoleDriver.InternalKeyAvailable (150) > 0) {
946 do {
947 AddToBuffer (stdin.Read ());
948 } while (ConsoleDriver.InternalKeyAvailable (0) > 0);
949 } else if (stdin.DataAvailable ()) {
950 do {
951 AddToBuffer (stdin.Read ());
952 } while (stdin.DataAvailable ());
953 } else {
954 if ((o = GetKeyFromBuffer (false)) != null)
955 break;
957 AddToBuffer (stdin.Read ());
960 o = GetKeyFromBuffer (true);
961 } while (o == null);
963 // freshly read character
964 fresh = true;
965 } else {
966 // this char was pre-buffered (e.g. not fresh)
967 fresh = false;
970 return (ConsoleKeyInfo) o;
973 #region Input echoing optimization
974 bool InputPending ()
976 // check if we've got pending input we can read immediately
977 return readpos < writepos || stdin.DataAvailable ();
980 char [] echobuf = null;
981 int echon = 0;
983 // Queues a character to be echo'd back to the console
984 void QueueEcho (char c)
986 if (echobuf == null)
987 echobuf = new char [1024];
989 echobuf[echon++] = c;
991 if (echon == echobuf.Length || !InputPending ()) {
992 // blit our echo buffer to the console
993 stdout.InternalWriteChars (echobuf, echon);
994 echon = 0;
998 // Queues a key to be echo'd back to the console
999 void Echo (ConsoleKeyInfo key)
1001 if (!IsSpecialKey (key)) {
1002 QueueEcho (key.KeyChar);
1003 return;
1006 // flush pending echo's
1007 EchoFlush ();
1009 WriteSpecialKey (key);
1012 // Flush the pending echo queue
1013 void EchoFlush ()
1015 if (echon == 0)
1016 return;
1018 // flush our echo buffer to the console
1019 stdout.InternalWriteChars (echobuf, echon);
1020 echon = 0;
1022 #endregion
1024 public int Read ([In, Out] char [] dest, int index, int count)
1026 bool fresh, echo = false;
1027 StringBuilder sbuf;
1028 ConsoleKeyInfo key;
1029 int BoL = 0; // Beginning-of-Line marker (can't backspace beyond this)
1030 object o;
1031 char c;
1033 sbuf = new StringBuilder ();
1035 // consume buffered keys first (do not echo, these have already been echo'd)
1036 while (true) {
1037 if ((o = GetKeyFromBuffer (true)) == null)
1038 break;
1040 key = (ConsoleKeyInfo) o;
1041 c = key.KeyChar;
1043 if (key.Key != ConsoleKey.Backspace) {
1044 if (key.Key == ConsoleKey.Enter)
1045 BoL = sbuf.Length;
1047 sbuf.Append (c);
1048 } else if (sbuf.Length > BoL) {
1049 sbuf.Length--;
1053 // continue reading until Enter is hit
1054 rl_startx = cursorLeft;
1055 rl_starty = cursorTop;
1057 do {
1058 key = ReadKeyInternal (out fresh);
1059 echo = echo || fresh;
1060 c = key.KeyChar;
1062 if (key.Key != ConsoleKey.Backspace) {
1063 if (key.Key == ConsoleKey.Enter)
1064 BoL = sbuf.Length;
1066 sbuf.Append (c);
1067 } else if (sbuf.Length > BoL) {
1068 sbuf.Length--;
1069 } else {
1070 continue;
1073 // echo fresh keys back to the console
1074 if (echo)
1075 Echo (key);
1076 } while (key.Key != ConsoleKey.Enter);
1078 EchoFlush ();
1080 rl_startx = -1;
1081 rl_starty = -1;
1083 // copy up to count chars into dest
1084 int nread = 0;
1085 while (count > 0 && nread < sbuf.Length) {
1086 dest[index + nread] = sbuf[nread];
1087 nread++;
1088 count--;
1091 // put the rest back into our key buffer
1092 for (int i = nread; i < sbuf.Length; i++)
1093 AddToBuffer (sbuf[i]);
1095 return nread;
1098 public ConsoleKeyInfo ReadKey (bool intercept)
1100 bool fresh;
1102 ConsoleKeyInfo key = ReadKeyInternal (out fresh);
1104 if (!intercept && fresh) {
1105 // echo the fresh key back to the console
1106 Echo (key);
1107 EchoFlush ();
1110 return key;
1113 public string ReadLine ()
1115 return ReadUntilConditionInternal (true);
1118 public string ReadToEnd ()
1120 return ReadUntilConditionInternal (false);
1123 private string ReadUntilConditionInternal (bool haltOnNewLine)
1125 if (!inited)
1126 Init ();
1128 // Hack to make Iron Python work (since it goes behind our backs
1129 // when writing to the console thus preventing us from keeping
1130 // cursor state normally).
1131 GetCursorPosition ();
1133 StringBuilder builder = new StringBuilder ();
1134 bool fresh, echo = false;
1135 ConsoleKeyInfo key;
1136 char c;
1138 rl_startx = cursorLeft;
1139 rl_starty = cursorTop;
1140 char eof = (char) control_characters [ControlCharacters.EOF];
1142 bool treatAsEnterKey;
1144 do {
1145 key = ReadKeyInternal (out fresh);
1146 echo = echo || fresh;
1147 c = key.KeyChar;
1148 // EOF -> Ctrl-D (EOT) pressed.
1149 if (c == eof && c != 0 && builder.Length == 0)
1150 return null;
1152 treatAsEnterKey = haltOnNewLine && (key.Key == ConsoleKey.Enter);
1154 if (!treatAsEnterKey) {
1155 if (key.Key != ConsoleKey.Backspace) {
1156 builder.Append (c);
1157 } else if (builder.Length > 0) {
1158 builder.Length--;
1159 } else {
1160 // skips over echoing the key to the console
1161 continue;
1165 // echo fresh keys back to the console
1166 if (echo)
1167 Echo (key);
1168 } while (!treatAsEnterKey);
1170 EchoFlush ();
1172 rl_startx = -1;
1173 rl_starty = -1;
1175 return builder.ToString ();
1178 public void ResetColor ()
1180 if (!inited) {
1181 Init ();
1184 string str = (origPair != null) ? origPair : origColors;
1185 WriteConsole (str);
1188 public void SetBufferSize (int width, int height)
1190 if (!inited) {
1191 Init ();
1194 throw new NotImplementedException (String.Empty);
1197 public void SetCursorPosition (int left, int top)
1199 if (!inited) {
1200 Init ();
1203 CheckWindowDimensions ();
1204 if (left < 0 || left >= bufferWidth)
1205 throw new ArgumentOutOfRangeException ("left", "Value must be positive and below the buffer width.");
1207 if (top < 0 || top >= bufferHeight)
1208 throw new ArgumentOutOfRangeException ("top", "Value must be positive and below the buffer height.");
1210 // Either CursorAddress or nothing.
1211 // We might want to play with up/down/left/right/home when ca is not available.
1212 if (cursorAddress == null)
1213 throw new NotSupportedException ("This terminal does not suport setting the cursor position.");
1215 WriteConsole (ParameterizedStrings.Evaluate (cursorAddress, top, left));
1216 cursorLeft = left;
1217 cursorTop = top;
1220 public void SetWindowPosition (int left, int top)
1222 if (!inited) {
1223 Init ();
1226 // No need to throw exceptions here.
1227 //throw new NotSupportedException ();
1230 public void SetWindowSize (int width, int height)
1232 if (!inited) {
1233 Init ();
1236 // No need to throw exceptions here.
1237 //throw new NotSupportedException ();
1241 void CreateKeyMap ()
1243 keymap = new Hashtable ();
1245 keymap [TermInfoStrings.KeyBackspace] = new ConsoleKeyInfo ('\0', ConsoleKey.Backspace, false, false, false);
1246 keymap [TermInfoStrings.KeyClear] = new ConsoleKeyInfo ('\0', ConsoleKey.Clear, false, false, false);
1247 // Delete character...
1248 keymap [TermInfoStrings.KeyDown] = new ConsoleKeyInfo ('\0', ConsoleKey.DownArrow, false, false, false);
1249 keymap [TermInfoStrings.KeyF1] = new ConsoleKeyInfo ('\0', ConsoleKey.F1, false, false, false);
1250 keymap [TermInfoStrings.KeyF10] = new ConsoleKeyInfo ('\0', ConsoleKey.F10, false, false, false);
1251 keymap [TermInfoStrings.KeyF2] = new ConsoleKeyInfo ('\0', ConsoleKey.F2, false, false, false);
1252 keymap [TermInfoStrings.KeyF3] = new ConsoleKeyInfo ('\0', ConsoleKey.F3, false, false, false);
1253 keymap [TermInfoStrings.KeyF4] = new ConsoleKeyInfo ('\0', ConsoleKey.F4, false, false, false);
1254 keymap [TermInfoStrings.KeyF5] = new ConsoleKeyInfo ('\0', ConsoleKey.F5, false, false, false);
1255 keymap [TermInfoStrings.KeyF6] = new ConsoleKeyInfo ('\0', ConsoleKey.F6, false, false, false);
1256 keymap [TermInfoStrings.KeyF7] = new ConsoleKeyInfo ('\0', ConsoleKey.F7, false, false, false);
1257 keymap [TermInfoStrings.KeyF8] = new ConsoleKeyInfo ('\0', ConsoleKey.F8, false, false, false);
1258 keymap [TermInfoStrings.KeyF9] = new ConsoleKeyInfo ('\0', ConsoleKey.F9, false, false, false);
1259 keymap [TermInfoStrings.KeyHome] = new ConsoleKeyInfo ('\0', ConsoleKey.Home, false, false, false);
1260 keymap [TermInfoStrings.KeyLeft] = new ConsoleKeyInfo ('\0', ConsoleKey.LeftArrow, false, false, false);
1261 keymap [TermInfoStrings.KeyLl] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad1, false, false, false);
1262 keymap [TermInfoStrings.KeyNpage] = new ConsoleKeyInfo ('\0', ConsoleKey.PageDown, false, false, false);
1263 keymap [TermInfoStrings.KeyPpage] = new ConsoleKeyInfo ('\0', ConsoleKey.PageUp, false, false, false);
1264 keymap [TermInfoStrings.KeyRight] = new ConsoleKeyInfo ('\0', ConsoleKey.RightArrow, false, false, false);
1265 keymap [TermInfoStrings.KeySf] = new ConsoleKeyInfo ('\0', ConsoleKey.PageDown, false, false, false);
1266 keymap [TermInfoStrings.KeySr] = new ConsoleKeyInfo ('\0', ConsoleKey.PageUp, false, false, false);
1267 keymap [TermInfoStrings.KeyUp] = new ConsoleKeyInfo ('\0', ConsoleKey.UpArrow, false, false, false);
1268 keymap [TermInfoStrings.KeyA1] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad7, false, false, false);
1269 keymap [TermInfoStrings.KeyA3] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad9, false, false, false);
1270 keymap [TermInfoStrings.KeyB2] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad5, false, false, false);
1271 keymap [TermInfoStrings.KeyC1] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad1, false, false, false);
1272 keymap [TermInfoStrings.KeyC3] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad3, false, false, false);
1273 keymap [TermInfoStrings.KeyBtab] = new ConsoleKeyInfo ('\0', ConsoleKey.Tab, true, false, false);
1274 keymap [TermInfoStrings.KeyBeg] = new ConsoleKeyInfo ('\0', ConsoleKey.Home, false, false, false);
1275 keymap [TermInfoStrings.KeyCopy] = new ConsoleKeyInfo ('C', ConsoleKey.C, false, true, false);
1276 keymap [TermInfoStrings.KeyEnd] = new ConsoleKeyInfo ('\0', ConsoleKey.End, false, false, false);
1277 keymap [TermInfoStrings.KeyEnter] = new ConsoleKeyInfo ('\n', ConsoleKey.Enter, false, false, false);
1278 keymap [TermInfoStrings.KeyHelp] = new ConsoleKeyInfo ('\0', ConsoleKey.Help, false, false, false);
1279 keymap [TermInfoStrings.KeyPrint] = new ConsoleKeyInfo ('\0', ConsoleKey.Print, false, false, false);
1280 keymap [TermInfoStrings.KeyUndo] = new ConsoleKeyInfo ('Z', ConsoleKey.Z , false, true, false);
1281 keymap [TermInfoStrings.KeySbeg] = new ConsoleKeyInfo ('\0', ConsoleKey.Home, true, false, false);
1282 keymap [TermInfoStrings.KeyScopy] = new ConsoleKeyInfo ('C', ConsoleKey.C , true, true, false);
1283 keymap [TermInfoStrings.KeySdc] = new ConsoleKeyInfo ('\x9', ConsoleKey.Delete, true, false, false);
1284 keymap [TermInfoStrings.KeyShelp] = new ConsoleKeyInfo ('\0', ConsoleKey.Help, true, false, false);
1285 keymap [TermInfoStrings.KeyShome] = new ConsoleKeyInfo ('\0', ConsoleKey.Home, true, false, false);
1286 keymap [TermInfoStrings.KeySleft] = new ConsoleKeyInfo ('\0', ConsoleKey.LeftArrow, true, false, false);
1287 keymap [TermInfoStrings.KeySprint] = new ConsoleKeyInfo ('\0', ConsoleKey.Print, true, false, false);
1288 keymap [TermInfoStrings.KeySright] = new ConsoleKeyInfo ('\0', ConsoleKey.RightArrow, true, false, false);
1289 keymap [TermInfoStrings.KeySundo] = new ConsoleKeyInfo ('Z', ConsoleKey.Z, true, false, false);
1290 keymap [TermInfoStrings.KeyF11] = new ConsoleKeyInfo ('\0', ConsoleKey.F11, false, false, false);
1291 keymap [TermInfoStrings.KeyF12] = new ConsoleKeyInfo ('\0', ConsoleKey.F12 , false, false, false);
1292 keymap [TermInfoStrings.KeyF13] = new ConsoleKeyInfo ('\0', ConsoleKey.F13, false, false, false);
1293 keymap [TermInfoStrings.KeyF14] = new ConsoleKeyInfo ('\0', ConsoleKey.F14, false, false, false);
1294 keymap [TermInfoStrings.KeyF15] = new ConsoleKeyInfo ('\0', ConsoleKey.F15, false, false, false);
1295 keymap [TermInfoStrings.KeyF16] = new ConsoleKeyInfo ('\0', ConsoleKey.F16, false, false, false);
1296 keymap [TermInfoStrings.KeyF17] = new ConsoleKeyInfo ('\0', ConsoleKey.F17, false, false, false);
1297 keymap [TermInfoStrings.KeyF18] = new ConsoleKeyInfo ('\0', ConsoleKey.F18, false, false, false);
1298 keymap [TermInfoStrings.KeyF19] = new ConsoleKeyInfo ('\0', ConsoleKey.F19, false, false, false);
1299 keymap [TermInfoStrings.KeyF20] = new ConsoleKeyInfo ('\0', ConsoleKey.F20, false, false, false);
1300 keymap [TermInfoStrings.KeyF21] = new ConsoleKeyInfo ('\0', ConsoleKey.F21, false, false, false);
1301 keymap [TermInfoStrings.KeyF22] = new ConsoleKeyInfo ('\0', ConsoleKey.F22, false, false, false);
1302 keymap [TermInfoStrings.KeyF23] = new ConsoleKeyInfo ('\0', ConsoleKey.F23, false, false, false);
1303 keymap [TermInfoStrings.KeyF24] = new ConsoleKeyInfo ('\0', ConsoleKey.F24, false, false, false);
1304 // These were previously missing:
1305 keymap [TermInfoStrings.KeyDc] = new ConsoleKeyInfo ('\0', ConsoleKey.Delete, false, false, false);
1306 keymap [TermInfoStrings.KeyIc] = new ConsoleKeyInfo ('\0', ConsoleKey.Insert, false, false, false);
1309 void InitKeys ()
1311 if (initKeys)
1312 return;
1314 CreateKeyMap ();
1315 rootmap = new ByteMatcher ();
1318 // The keys that we know about and use
1320 var UsedKeys = new [] {
1321 TermInfoStrings.KeyBackspace,
1322 TermInfoStrings.KeyClear,
1323 TermInfoStrings.KeyDown,
1324 TermInfoStrings.KeyF1,
1325 TermInfoStrings.KeyF10,
1326 TermInfoStrings.KeyF2,
1327 TermInfoStrings.KeyF3,
1328 TermInfoStrings.KeyF4,
1329 TermInfoStrings.KeyF5,
1330 TermInfoStrings.KeyF6,
1331 TermInfoStrings.KeyF7,
1332 TermInfoStrings.KeyF8,
1333 TermInfoStrings.KeyF9,
1334 TermInfoStrings.KeyHome,
1335 TermInfoStrings.KeyLeft,
1336 TermInfoStrings.KeyLl,
1337 TermInfoStrings.KeyNpage,
1338 TermInfoStrings.KeyPpage,
1339 TermInfoStrings.KeyRight,
1340 TermInfoStrings.KeySf,
1341 TermInfoStrings.KeySr,
1342 TermInfoStrings.KeyUp,
1343 TermInfoStrings.KeyA1,
1344 TermInfoStrings.KeyA3,
1345 TermInfoStrings.KeyB2,
1346 TermInfoStrings.KeyC1,
1347 TermInfoStrings.KeyC3,
1348 TermInfoStrings.KeyBtab,
1349 TermInfoStrings.KeyBeg,
1350 TermInfoStrings.KeyCopy,
1351 TermInfoStrings.KeyEnd,
1352 TermInfoStrings.KeyEnter,
1353 TermInfoStrings.KeyHelp,
1354 TermInfoStrings.KeyPrint,
1355 TermInfoStrings.KeyUndo,
1356 TermInfoStrings.KeySbeg,
1357 TermInfoStrings.KeyScopy,
1358 TermInfoStrings.KeySdc,
1359 TermInfoStrings.KeyShelp,
1360 TermInfoStrings.KeyShome,
1361 TermInfoStrings.KeySleft,
1362 TermInfoStrings.KeySprint,
1363 TermInfoStrings.KeySright,
1364 TermInfoStrings.KeySundo,
1365 TermInfoStrings.KeyF11,
1366 TermInfoStrings.KeyF12,
1367 TermInfoStrings.KeyF13,
1368 TermInfoStrings.KeyF14,
1369 TermInfoStrings.KeyF15,
1370 TermInfoStrings.KeyF16,
1371 TermInfoStrings.KeyF17,
1372 TermInfoStrings.KeyF18,
1373 TermInfoStrings.KeyF19,
1374 TermInfoStrings.KeyF20,
1375 TermInfoStrings.KeyF21,
1376 TermInfoStrings.KeyF22,
1377 TermInfoStrings.KeyF23,
1378 TermInfoStrings.KeyF24,
1380 // These were missing
1381 TermInfoStrings.KeyDc,
1382 TermInfoStrings.KeyIc
1385 foreach (TermInfoStrings tis in UsedKeys)
1386 AddStringMapping (tis);
1388 rootmap.AddMapping (TermInfoStrings.KeyBackspace, new byte [] { control_characters [ControlCharacters.Erase] });
1389 rootmap.Sort ();
1390 initKeys = true;
1393 void AddStringMapping (TermInfoStrings s)
1395 byte [] bytes = reader.GetStringBytes (s);
1396 if (bytes == null)
1397 return;
1399 rootmap.AddMapping (s, bytes);
1403 /// <summary>Provides support for evaluating parameterized terminfo database format strings.</summary>
1404 internal static class ParameterizedStrings
1406 /// <summary>A cached stack to use to avoid allocating a new stack object for every evaluation.</summary>
1407 [ThreadStatic]
1408 private static LowLevelStack _cachedStack;
1410 /// <summary>Evaluates a terminfo formatting string, using the supplied arguments.</summary>
1411 /// <param name="format">The format string.</param>
1412 /// <param name="args">The arguments to the format string.</param>
1413 /// <returns>The formatted string.</returns>
1414 public static string Evaluate(string format, params FormatParam[] args)
1416 if (format == null)
1417 throw new ArgumentNullException("format");
1418 if (args == null)
1419 throw new ArgumentNullException("args");
1421 // Initialize the stack to use for processing.
1422 LowLevelStack stack = _cachedStack;
1423 if (stack == null)
1424 _cachedStack = stack = new LowLevelStack();
1425 else
1426 stack.Clear();
1428 // "dynamic" and "static" variables are much less often used (the "dynamic" and "static"
1429 // terminology appears to just refer to two different collections rather than to any semantic
1430 // meaning). As such, we'll only initialize them if we really need them.
1431 FormatParam[] dynamicVars = null, staticVars = null;
1433 int pos = 0;
1434 return EvaluateInternal(format, ref pos, args, stack, ref dynamicVars, ref staticVars);
1436 // EvaluateInternal may throw IndexOutOfRangeException and InvalidOperationException
1437 // if the format string is malformed or if it's inconsistent with the parameters provided.
1440 /// <summary>Evaluates a terminfo formatting string, using the supplied arguments and processing data structures.</summary>
1441 /// <param name="format">The format string.</param>
1442 /// <param name="pos">The position in <paramref name="format"/> to start processing.</param>
1443 /// <param name="args">The arguments to the format string.</param>
1444 /// <param name="stack">The stack to use as the format string is evaluated.</param>
1445 /// <param name="dynamicVars">A lazily-initialized collection of variables.</param>
1446 /// <param name="staticVars">A lazily-initialized collection of variables.</param>
1447 /// <returns>
1448 /// The formatted string; this may be empty if the evaluation didn't yield any output.
1449 /// The evaluation stack will have a 1 at the top if all processing was completed at invoked level
1450 /// of recursion, and a 0 at the top if we're still inside of a conditional that requires more processing.
1451 /// </returns>
1452 private static string EvaluateInternal(
1453 string format, ref int pos, FormatParam[] args, LowLevelStack stack,
1454 ref FormatParam[] dynamicVars, ref FormatParam[] staticVars)
1456 // Create a StringBuilder to store the output of this processing. We use the format's length as an
1457 // approximation of an upper-bound for how large the output will be, though with parameter processing,
1458 // this is just an estimate, sometimes way over, sometimes under.
1459 StringBuilder output = new StringBuilder(format.Length);
1461 // Format strings support conditionals, including the equivalent of "if ... then ..." and
1462 // "if ... then ... else ...", as well as "if ... then ... else ... then ..."
1463 // and so on, where an else clause can not only be evaluated for string output but also
1464 // as a conditional used to determine whether to evaluate a subsequent then clause.
1465 // We use recursion to process these subsequent parts, and we track whether we're processing
1466 // at the same level of the initial if clause (or whether we're nested).
1467 bool sawIfConditional = false;
1469 // Process each character in the format string, starting from the position passed in.
1470 for (; pos < format.Length; pos++){
1471 // '%' is the escape character for a special sequence to be evaluated.
1472 // Anything else just gets pushed to output.
1473 if (format[pos] != '%') {
1474 output.Append(format[pos]);
1475 continue;
1477 // We have a special parameter sequence to process. Now we need
1478 // to look at what comes after the '%'.
1479 ++pos;
1480 switch (format[pos]) {
1481 // Output appending operations
1482 case '%': // Output the escaped '%'
1483 output.Append('%');
1484 break;
1485 case 'c': // Pop the stack and output it as a char
1486 output.Append((char)stack.Pop().Int32);
1487 break;
1488 case 's': // Pop the stack and output it as a string
1489 output.Append(stack.Pop().String);
1490 break;
1491 case 'd': // Pop the stack and output it as an integer
1492 output.Append(stack.Pop().Int32);
1493 break;
1494 case 'o':
1495 case 'X':
1496 case 'x':
1497 case ':':
1498 case '0':
1499 case '1':
1500 case '2':
1501 case '3':
1502 case '4':
1503 case '5':
1504 case '6':
1505 case '7':
1506 case '8':
1507 case '9':
1508 // printf strings of the format "%[[:]flags][width[.precision]][doxXs]" are allowed
1509 // (with a ':' used in front of flags to help differentiate from binary operations, as flags can
1510 // include '-' and '+'). While above we've special-cased common usage (e.g. %d, %s),
1511 // for more complicated expressions we delegate to printf.
1512 int printfEnd = pos;
1513 for (; printfEnd < format.Length; printfEnd++) // find the end of the printf format string
1515 char ec = format[printfEnd];
1516 if (ec == 'd' || ec == 'o' || ec == 'x' || ec == 'X' || ec == 's')
1518 break;
1521 if (printfEnd >= format.Length)
1522 throw new InvalidOperationException("Terminfo database contains invalid values");
1523 string printfFormat = format.Substring(pos - 1, printfEnd - pos + 2); // extract the format string
1524 if (printfFormat.Length > 1 && printfFormat[1] == ':')
1525 printfFormat = printfFormat.Remove(1, 1);
1526 output.Append(FormatPrintF(printfFormat, stack.Pop().Object)); // do the printf formatting and append its output
1527 break;
1529 // Stack pushing operations
1530 case 'p': // Push the specified parameter (1-based) onto the stack
1531 pos++;
1532 stack.Push(args[format[pos] - '1']);
1533 break;
1534 case 'l': // Pop a string and push its length
1535 stack.Push(stack.Pop().String.Length);
1536 break;
1537 case '{': // Push integer literal, enclosed between braces
1538 pos++;
1539 int intLit = 0;
1540 while (format[pos] != '}')
1542 intLit = (intLit * 10) + (format[pos] - '0');
1543 pos++;
1545 stack.Push(intLit);
1546 break;
1547 case '\'': // Push literal character, enclosed between single quotes
1548 stack.Push((int)format[pos + 1]);
1549 pos += 2;
1550 break;
1552 // Storing and retrieving "static" and "dynamic" variables
1553 case 'P': // Pop a value and store it into either static or dynamic variables based on whether the a-z variable is capitalized
1554 pos++;
1555 int setIndex;
1556 FormatParam[] targetVars = GetDynamicOrStaticVariables(format[pos], ref dynamicVars, ref staticVars, out setIndex);
1557 targetVars[setIndex] = stack.Pop();
1558 break;
1559 case 'g': // Push a static or dynamic variable; which is based on whether the a-z variable is capitalized
1560 pos++;
1561 int getIndex;
1562 FormatParam[] sourceVars = GetDynamicOrStaticVariables(format[pos], ref dynamicVars, ref staticVars, out getIndex);
1563 stack.Push(sourceVars[getIndex]);
1564 break;
1566 // Binary operations
1567 case '+':
1568 case '-':
1569 case '*':
1570 case '/':
1571 case 'm':
1572 case '^': // arithmetic
1573 case '&':
1574 case '|': // bitwise
1575 case '=':
1576 case '>':
1577 case '<': // comparison
1578 case 'A':
1579 case 'O': // logical
1580 int second = stack.Pop().Int32; // it's a stack... the second value was pushed last
1581 int first = stack.Pop().Int32;
1582 int res;
1583 switch (format[pos]) {
1584 case '+':
1585 res = first + second;
1586 break;
1587 case '-':
1588 res = first - second;
1589 break;
1590 case '*':
1591 res = first * second;
1592 break;
1593 case '/':
1594 res = first / second;
1595 break;
1596 case 'm':
1597 res = first % second;
1598 break;
1599 case '^':
1600 res = first ^ second;
1601 break;
1602 case '&':
1603 res = first & second;
1604 break;
1605 case '|':
1606 res = first | second;
1607 break;
1608 case '=':
1609 res = AsInt(first == second);
1610 break;
1611 case '>':
1612 res = AsInt(first > second);
1613 break;
1614 case '<':
1615 res = AsInt(first < second);
1616 break;
1617 case 'A':
1618 res = AsInt(AsBool(first) && AsBool(second));
1619 break;
1620 case 'O':
1621 res = AsInt(AsBool(first) || AsBool(second));
1622 break;
1623 default:
1624 res = 0;
1625 break;
1627 stack.Push(res);
1628 break;
1630 // Unary operations
1631 case '!':
1632 case '~':
1633 int value = stack.Pop().Int32;
1634 stack.Push(
1635 format[pos] == '!' ? AsInt(!AsBool(value)) :
1636 ~value);
1637 break;
1639 // Augment first two parameters by 1
1640 case 'i':
1641 args[0] = 1 + args[0].Int32;
1642 args[1] = 1 + args[1].Int32;
1643 break;
1645 // Conditional of the form %? if-part %t then-part %e else-part %;
1646 // The "%e else-part" is optional.
1647 case '?':
1648 sawIfConditional = true;
1649 break;
1650 case 't':
1651 // We hit the end of the if-part and are about to start the then-part.
1652 // The if-part left its result on the stack; pop and evaluate.
1653 bool conditionalResult = AsBool(stack.Pop().Int32);
1655 // Regardless of whether it's true, run the then-part to get past it.
1656 // If the conditional was true, output the then results.
1657 pos++;
1658 string thenResult = EvaluateInternal(format, ref pos, args, stack, ref dynamicVars, ref staticVars);
1659 if (conditionalResult)
1661 output.Append(thenResult);
1664 // We're past the then; the top of the stack should now be a Boolean
1665 // indicating whether this conditional has more to be processed (an else clause).
1666 if (!AsBool(stack.Pop().Int32))
1668 // Process the else clause, and if the conditional was false, output the else results.
1669 pos++;
1670 string elseResult = EvaluateInternal(format, ref pos, args, stack, ref dynamicVars, ref staticVars);
1671 if (!conditionalResult)
1673 output.Append(elseResult);
1675 // Now we should be done (any subsequent elseif logic will have bene handled in the recursive call).
1676 if (!AsBool(stack.Pop().Int32))
1678 throw new InvalidOperationException("Terminfo database contains invalid values");
1682 // If we're in a nested processing, return to our parent.
1683 if (!sawIfConditional)
1685 stack.Push(1);
1686 return output.ToString();
1688 // Otherwise, we're done processing the conditional in its entirety.
1689 sawIfConditional = false;
1690 break;
1691 case 'e':
1692 case ';':
1693 // Let our caller know why we're exiting, whether due to the end of the conditional or an else branch.
1694 stack.Push(AsInt(format[pos] == ';'));
1695 return output.ToString();
1697 // Anything else is an error
1698 default:
1699 throw new InvalidOperationException("Terminfo database contains invalid values");
1702 stack.Push(1);
1703 return output.ToString();
1706 /// <summary>Converts an Int32 to a Boolean, with 0 meaning false and all non-zero values meaning true.</summary>
1707 /// <param name="i">The integer value to convert.</param>
1708 /// <returns>true if the integer was non-zero; otherwise, false.</returns>
1709 static bool AsBool(Int32 i) { return i != 0; }
1711 /// <summary>Converts a Boolean to an Int32, with true meaning 1 and false meaning 0.</summary>
1712 /// <param name="b">The Boolean value to convert.</param>
1713 /// <returns>1 if the Boolean is true; otherwise, 0.</returns>
1714 static int AsInt(bool b) { return b ? 1 : 0; }
1716 static string StringFromAsciiBytes(byte[] buffer, int offset, int length)
1718 // Special-case for empty strings
1719 if (length == 0)
1720 return string.Empty;
1722 // new string(sbyte*, ...) doesn't exist in the targeted reference assembly,
1723 // so we first copy to an array of chars, and then create a string from that.
1724 char[] chars = new char[length];
1725 for (int i = 0, j = offset; i < length; i++, j++)
1726 chars[i] = (char)buffer[j];
1727 return new string(chars);
1730 [DllImport("libc")]
1731 static extern unsafe int snprintf(byte* str, IntPtr size, string format, string arg1);
1733 [DllImport("libc")]
1734 static extern unsafe int snprintf(byte* str, IntPtr size, string format, int arg1);
1736 /// <summary>Formats an argument into a printf-style format string.</summary>
1737 /// <param name="format">The printf-style format string.</param>
1738 /// <param name="arg">The argument to format. This must be an Int32 or a String.</param>
1739 /// <returns>The formatted string.</returns>
1740 static unsafe string FormatPrintF(string format, object arg)
1742 // Determine how much space is needed to store the formatted string.
1743 string stringArg = arg as string;
1744 int neededLength = stringArg != null ?
1745 snprintf(null, IntPtr.Zero, format, stringArg) :
1746 snprintf(null, IntPtr.Zero, format, (int)arg);
1747 if (neededLength == 0)
1748 return string.Empty;
1749 if (neededLength < 0)
1750 throw new InvalidOperationException("The printf operation failed");
1752 // Allocate the needed space, format into it, and return the data as a string.
1753 byte[] bytes = new byte[neededLength + 1]; // extra byte for the null terminator
1754 fixed (byte* ptr = bytes){
1755 int length = stringArg != null ?
1756 snprintf(ptr, (IntPtr)bytes.Length, format, stringArg) :
1757 snprintf(ptr, (IntPtr)bytes.Length, format, (int)arg);
1758 if (length != neededLength)
1760 throw new InvalidOperationException("Invalid printf operation");
1763 return StringFromAsciiBytes(bytes, 0, neededLength);
1766 /// <summary>Gets the lazily-initialized dynamic or static variables collection, based on the supplied variable name.</summary>
1767 /// <param name="c">The name of the variable.</param>
1768 /// <param name="dynamicVars">The lazily-initialized dynamic variables collection.</param>
1769 /// <param name="staticVars">The lazily-initialized static variables collection.</param>
1770 /// <param name="index">The index to use to index into the variables.</param>
1771 /// <returns>The variables collection.</returns>
1772 private static FormatParam[] GetDynamicOrStaticVariables(
1773 char c, ref FormatParam[] dynamicVars, ref FormatParam[] staticVars, out int index)
1775 if (c >= 'A' && c <= 'Z'){
1776 index = c - 'A';
1777 return staticVars ?? (staticVars = new FormatParam[26]); // one slot for each letter of alphabet
1778 } else if (c >= 'a' && c <= 'z') {
1779 index = c - 'a';
1780 return dynamicVars ?? (dynamicVars = new FormatParam[26]); // one slot for each letter of alphabet
1782 else throw new InvalidOperationException("Terminfo database contains invalid values");
1785 /// <summary>
1786 /// Represents a parameter to a terminfo formatting string.
1787 /// It is a discriminated union of either an integer or a string,
1788 /// with characters represented as integers.
1789 /// </summary>
1790 public struct FormatParam
1792 /// <summary>The integer stored in the parameter.</summary>
1793 private readonly int _int32;
1794 /// <summary>The string stored in the parameter.</summary>
1795 private readonly string _string; // null means an Int32 is stored
1797 /// <summary>Initializes the parameter with an integer value.</summary>
1798 /// <param name="value">The value to be stored in the parameter.</param>
1799 public FormatParam(Int32 value) : this(value, null) { }
1801 /// <summary>Initializes the parameter with a string value.</summary>
1802 /// <param name="value">The value to be stored in the parameter.</param>
1803 public FormatParam(String value) : this(0, value ?? string.Empty) { }
1805 /// <summary>Initializes the parameter.</summary>
1806 /// <param name="intValue">The integer value.</param>
1807 /// <param name="stringValue">The string value.</param>
1808 private FormatParam(Int32 intValue, String stringValue)
1810 _int32 = intValue;
1811 _string = stringValue;
1814 /// <summary>Implicit converts an integer into a parameter.</summary>
1815 public static implicit operator FormatParam(int value)
1817 return new FormatParam(value);
1820 /// <summary>Implicit converts a string into a parameter.</summary>
1821 public static implicit operator FormatParam(string value)
1823 return new FormatParam(value);
1826 /// <summary>Gets the integer value of the parameter. If a string was stored, 0 is returned.</summary>
1827 public int Int32 { get { return _int32; } }
1829 /// <summary>Gets the string value of the parameter. If an Int32 or a null String were stored, an empty string is returned.</summary>
1830 public string String { get { return _string ?? string.Empty; } }
1832 /// <summary>Gets the string or the integer value as an object.</summary>
1833 public object Object { get { return _string ?? (object)_int32; } }
1836 /// <summary>Provides a basic stack data structure.</summary>
1837 /// <typeparam name="T">Specifies the type of data in the stack.</typeparam>
1838 private sealed class LowLevelStack
1840 private const int DefaultSize = 4;
1841 private FormatParam[] _arr;
1842 private int _count;
1844 public LowLevelStack() { _arr = new FormatParam[DefaultSize]; }
1846 public FormatParam Pop()
1848 if (_count == 0)
1849 throw new InvalidOperationException("Terminfo: Invalid Stack");
1851 var item = _arr[--_count];
1852 _arr[_count] = default(FormatParam);
1853 return item;
1856 public void Push(FormatParam item)
1858 if (_arr.Length == _count){
1859 var newArr = new FormatParam[_arr.Length * 2];
1860 Array.Copy(_arr, 0, newArr, 0, _arr.Length);
1861 _arr = newArr;
1863 _arr[_count++] = item;
1866 public void Clear()
1868 Array.Clear(_arr, 0, _count);
1869 _count = 0;
1874 class ByteMatcher {
1875 Hashtable map = new Hashtable ();
1876 Hashtable starts = new Hashtable ();
1878 public void AddMapping (TermInfoStrings key, byte [] val)
1880 if (val.Length == 0)
1881 return;
1883 map [val] = key;
1884 starts [(int) val [0]] = true;
1887 public void Sort ()
1891 public bool StartsWith (int c)
1893 return (starts [c] != null);
1896 public TermInfoStrings Match (char [] buffer, int offset, int length, out int used)
1898 foreach (byte [] bytes in map.Keys) {
1899 for (int i = 0; i < bytes.Length && i < length; i++) {
1900 if ((char) bytes [i] != buffer [offset + i])
1901 break;
1903 if (bytes.Length - 1 == i) {
1904 used = bytes.Length;
1905 return (TermInfoStrings) map [bytes];
1910 used = 0;
1911 return (TermInfoStrings) (-1);
1915 #endif