Implement mono_gc_alloc_fixed on Boehm to be uncollectable. This matches SGen behavio...
[mono.git] / mcs / class / corlib / System / TermInfoDriver.cs
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
10 //
11 //
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.
30 //
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
35 //
36 #if !MOBILE
37
38 //
39 // Defining this writes the output to console.log
40 //#define DEBUG
41
42 using System.Collections;
43 using System.IO;
44 using System.Text;
45 using System.Runtime.InteropServices;
46 namespace System {
47         class TermInfoDriver : IConsoleDriver {
48
49                 // This points to a variable that is updated by unmanage code on window size changes.
50                 unsafe static int *native_terminal_size;
51
52                 // The current size that we believe we have
53                 static int terminal_size;
54                 
55                 //static uint flag = 0xdeadbeef;
56                 readonly static string [] locations = { "/usr/share/terminfo", "/etc/terminfo", "/usr/lib/terminfo", "/lib/terminfo" };
57
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;
71
72                 int windowWidth;
73                 int windowHeight;
74                 //int windowTop;
75                 //int windowLeft;
76                 int bufferHeight;
77                 int bufferWidth;
78
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
103
104                 static string TryTermInfoDir (string dir, string term )
105                 {
106                         string path = String.Format ("{0}/{1:x}/{2}", dir, (int)(term [0]), term);
107                         if (File.Exists (path))
108                                 return path;
109                                 
110                         path = Path.Combine (dir, term.Substring (0, 1), term);
111                         if (File.Exists (path))
112                                 return path;
113                         return null;
114                 }
115
116                 static string SearchTerminfo (string term)
117                 {
118                         if (term == null || term == String.Empty)
119                                 return null;
120
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;
127                         }
128                                     
129                         foreach (string dir in locations) {
130                                 if (!Directory.Exists (dir))
131                                         continue;
132
133                                 path = TryTermInfoDir (dir, term);
134                                 if (path != null)
135                                         return path;
136                         }
137
138                         return null;
139                 }
140
141                 void WriteConsole (string str)
142                 {
143                         if (str == null)
144                                 return;
145                         
146                         stdout.InternalWriteString (str);
147                 }
148
149                 public TermInfoDriver ()
150                         : this (Environment.GetEnvironmentVariable ("TERM"))
151                 {
152                 }
153
154                 public TermInfoDriver (string term)
155                 {
156 #if DEBUG
157                         File.Delete ("console.log");
158                         logger = new StreamWriter (File.OpenWrite ("console.log"));
159 #endif
160                         this.term = term;
161
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);
171                                 }
172                         }
173
174                         if (reader == null)
175                                 reader = new TermInfoReader (term, KnownTerminals.ansi);
176
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;
183                         }
184                 }
185
186                 public bool Initialized {
187                         get { return inited; }
188                 }
189
190                 public void Init ()
191                 {
192                         if (inited)
193                                 return;
194
195                         lock (initLock){
196                                 if (inited)
197                                         return;
198                                 inited = true;
199                                 
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.");
203                                 
204                                 ConsoleDriver.SetEcho (false);
205                                 
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;
213                                 }
214                                 
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);
221                                 
222                                 string resetColors = (origColors == null) ? origPair : origColors;
223                                 if (resetColors != null)
224                                         endString += resetColors;
225                                 
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.");
231                                         }
232                                 }
233                                 
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);
240                                 }
241                                 
242                                 csrVisible = reader.Get (TermInfoStrings.CursorNormal);
243                                 if (csrVisible == null)
244                                         csrVisible = reader.Get (TermInfoStrings.CursorVisible);
245                                 
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
254                                 }
255                                 
256                                 cursorAddress = reader.Get (TermInfoStrings.CursorAddress);
257                                 
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;
267                                 }
268                         }
269                 }
270
271                 void IncrementX ()
272                 {
273                         cursorLeft++;
274                         if (cursorLeft >= WindowWidth) {
275                                 cursorTop++;
276                                 cursorLeft = 0;
277                                 if (cursorTop >= WindowHeight) {
278                                         // Writing beyond the initial screen
279                                         if (rl_starty != -1) rl_starty--;
280                                         cursorTop--;
281                                 }
282                         }
283                 }
284
285                 // Should never get called unless inited
286                 public void WriteSpecialKey (ConsoleKeyInfo key)
287                 {
288                         switch (key.Key) {
289                         case ConsoleKey.Backspace:
290                                 if (cursorLeft > 0) {
291                                         if (cursorLeft <= rl_startx && cursorTop == rl_starty)
292                                                 break;
293                                         cursorLeft--;
294                                         SetCursorPosition (cursorLeft, cursorTop);
295                                         WriteConsole (" ");
296                                         SetCursorPosition (cursorLeft, cursorTop);
297                                 }
298 #if DEBUG
299                                 logger.WriteLine ("BS left: {0} top: {1}", cursorLeft, cursorTop);
300                                 logger.Flush ();
301 #endif
302                                 break;
303                         case ConsoleKey.Tab:
304                                 int n = 8 - (cursorLeft % 8);
305                                 for (int i = 0; i < n; i++){
306                                         IncrementX ();
307                                 }
308                                 WriteConsole ("\t");
309                                 break;
310                         case ConsoleKey.Clear:
311                                 WriteConsole (clear);
312                                 cursorLeft = 0;
313                                 cursorTop = 0;
314                                 break;
315                         case ConsoleKey.Enter:
316                                 break;
317                         default:
318                                 break;
319                         }
320 #if DEBUG
321                         logger.WriteLine ("left: {0} top: {1}", cursorLeft, cursorTop);
322                         logger.Flush ();
323 #endif
324                 }
325
326                 // Should never get called unless inited
327                 public void WriteSpecialKey (char c)
328                 {
329                         WriteSpecialKey (CreateKeyInfoFromInt (c, false));
330                 }
331
332                 public bool IsSpecialKey (ConsoleKeyInfo key)
333                 {
334                         if (!inited)
335                                 return false;
336
337                         switch (key.Key) {
338                         case ConsoleKey.Backspace:
339                                 return true;
340                         case ConsoleKey.Tab:
341                                 return true;
342                         case ConsoleKey.Clear:
343                                 return true;
344                         case ConsoleKey.Enter:
345                                 cursorLeft = 0;
346                                 cursorTop++;
347                                 if (cursorTop >= WindowHeight) {
348                                         cursorTop--;
349                                         //TODO: scroll up
350                                 }
351                                 return false;
352                         default:
353                                 // CStreamWriter will handle writing this key
354                                 IncrementX ();
355                                 return false;
356                         }
357                 }
358
359                 public bool IsSpecialKey (char c)
360                 {
361                         return IsSpecialKey (CreateKeyInfoFromInt (c, false));
362                 }
363
364                 /// <summary>
365                 /// The values of the ConsoleColor enums unfortunately don't map to the 
366                 /// corresponding ANSI values.  We need to do the mapping manually.
367                 /// See http://en.wikipedia.org/wiki/ANSI_escape_code#Colors
368                 /// </summary>
369                 private static readonly int[] _consoleColorToAnsiCode = new int[]
370                 {
371                         // Dark/Normal colors
372                         0, // Black,
373                         4, // DarkBlue,
374                         2, // DarkGreen,
375                         6, // DarkCyan,
376                         1, // DarkRed,
377                         5, // DarkMagenta,
378                         3, // DarkYellow,
379                         7, // Gray,
380         
381                         // Bright colors
382                         8,  // DarkGray,
383                         12, // Blue,
384                         10, // Green,
385                         14, // Cyan,
386                         9,  // Red,
387                         13, // Magenta,
388                         11, // Yellow,
389                         15  // White
390                 };
391
392                 void ChangeColor (string format, ConsoleColor color)
393                 {
394                         int ccValue = (int)color;
395                         if ((ccValue & ~0xF) != 0)
396                                 throw new ArgumentException("Invalid Console Color");
397
398                         int ansiCode = _consoleColorToAnsiCode[ccValue] % maxColors;
399
400                         WriteConsole (ParameterizedStrings.Evaluate (format, ansiCode));
401                 }
402                 
403                 public ConsoleColor BackgroundColor {
404                         get {
405                                 if (!inited) {
406                                         Init ();
407                                 }
408
409                                 return bgcolor;
410                         }
411                         set {
412                                 if (!inited) {
413                                         Init ();
414                                 }
415                                 ChangeColor (setbgcolor, value);
416                                 bgcolor = value;
417                         }
418                 }
419
420                 public ConsoleColor ForegroundColor {
421                         get {
422                                 if (!inited) {
423                                         Init ();
424                                 }
425
426                                 return fgcolor;
427                         }
428                         set {
429                                 if (!inited) {
430                                         Init ();
431                                 }
432                                 ChangeColor (setfgcolor, value);
433                                 fgcolor = value;
434                         }
435                 }
436
437                 void GetCursorPosition ()
438                 {
439                         int row = 0, col = 0;
440                         int b;
441
442                         // First, get any data in the input buffer.  Merely reduces the likelyhood of getting an error
443                         int inqueue = ConsoleDriver.InternalKeyAvailable (0);
444                         while (inqueue-- > 0){
445                                 b = stdin.Read ();
446                                 AddToBuffer (b);
447                         }
448
449                         // Then try to probe for the cursor coordinates
450                         WriteConsole ("\x1b[6n");
451                         if (ConsoleDriver.InternalKeyAvailable (1000) <= 0) {
452                                 noGetPosition = true;
453                                 return;
454                         }
455
456                         b = stdin.Read ();
457                         while (b != '\x1b') {
458                                 AddToBuffer (b);
459                                 if (ConsoleDriver.InternalKeyAvailable (100) <= 0)
460                                         return;
461                                 b = stdin.Read ();
462                         }
463
464                         b = stdin.Read ();
465                         if (b != '[') {
466                                 AddToBuffer ('\x1b');
467                                 AddToBuffer (b);
468                                 return;
469                         }
470
471                         b = stdin.Read ();
472                         if (b != ';') {
473                                 row = b - '0';
474                                 b = stdin.Read ();
475                                 while ((b >= '0') && (b <= '9')) {
476                                         row = row * 10 + b - '0';
477                                         b = stdin.Read ();
478                                 }
479                                 // Row/col is 0 based
480                                 row --;
481                         }
482
483                         b = stdin.Read ();
484                         if (b != 'R') {
485                                 col = b - '0';
486                                 b = stdin.Read ();
487                                 while ((b >= '0') && (b <= '9')) {
488                                         col = col * 10 + b - '0';
489                                         b = stdin.Read ();
490                                 }
491                                 // Row/col is 0 based
492                                 col --;
493                         }
494
495 #if DEBUG
496                         logger.WriteLine ("GetCursorPosition: {0}, {1}", col, row);
497                         logger.Flush ();
498 #endif
499
500                         cursorLeft = col;
501                         cursorTop = row;
502                 }
503
504                 public int BufferHeight {
505                         get {
506                                 if (!inited) {
507                                         Init ();
508                                 }
509
510                                 CheckWindowDimensions ();
511                                 return bufferHeight;
512                         }
513                         set {
514                                 if (!inited) {
515                                         Init ();
516                                 }
517
518                                 throw new NotSupportedException ();
519                         }
520                 }
521
522                 public int BufferWidth {
523                         get {
524                                 if (!inited) {
525                                         Init ();
526                                 }
527
528                                 CheckWindowDimensions ();
529                                 return bufferWidth;
530                         }
531                         set {
532                                 if (!inited) {
533                                         Init ();
534                                 }
535
536                                 throw new NotSupportedException ();
537                         }
538                 }
539
540                 public bool CapsLock {
541                         get {
542                                 if (!inited) {
543                                         Init ();
544                                 }
545                                 return false;
546                         }
547                 }
548
549                 public int CursorLeft {
550                         get {
551                                 if (!inited) {
552                                         Init ();
553                                 }
554
555                                 return cursorLeft;
556                         }
557                         set {
558                                 if (!inited) {
559                                         Init ();
560                                 }
561
562                                 SetCursorPosition (value, CursorTop);
563                         }
564                 }
565
566                 public int CursorTop {
567                         get {
568                                 if (!inited) {
569                                         Init ();
570                                 }
571
572                                 return cursorTop;
573                         }
574                         set {
575                                 if (!inited) {
576                                         Init ();
577                                 }
578
579                                 SetCursorPosition (CursorLeft, value);
580                         }
581                 }
582
583                 public bool CursorVisible {
584                         get {
585                                 if (!inited) {
586                                         Init ();
587                                 }
588
589                                 return cursorVisible;
590                         }
591                         set {
592                                 if (!inited) {
593                                         Init ();
594                                 }
595
596                                 cursorVisible = value;
597                                 WriteConsole ((value ? csrVisible : csrInvisible));
598                         }
599                 }
600
601                 // we have CursorNormal vs. CursorVisible...
602                 [MonoTODO]
603                 public int CursorSize {
604                         get {
605                                 if (!inited) {
606                                         Init ();
607                                 }
608                                 return 1;
609                         }
610                         set {
611                                 if (!inited) {
612                                         Init ();
613                                 }
614                         }
615
616                 }
617
618                 public bool KeyAvailable {
619                         get {
620                                 if (!inited) {
621                                         Init ();
622                                 }
623
624                                 return (writepos > readpos || ConsoleDriver.InternalKeyAvailable (0) > 0);
625                         }
626                 }
627
628                 // We don't know these next 2 values, so return something reasonable
629                 public int LargestWindowHeight {
630                         get { return WindowHeight; }
631                 }
632
633                 public int LargestWindowWidth {
634                         get { return WindowWidth; }
635                 }
636
637                 public bool NumberLock {
638                         get {
639                                 if (!inited) {
640                                         Init ();
641                                 }
642
643                                 return false;
644                         }
645                 }
646
647                 public string Title {
648                         get {
649                                 if (!inited) {
650                                         Init ();
651                                 }
652                                 return title;
653                         }
654                         
655                         set {
656                                 if (!inited) {
657                                         Init ();
658                                 }
659
660                                 title = value;
661                                 WriteConsole (String.Format (titleFormat, value));
662                         }
663                 }
664
665                 public bool TreatControlCAsInput {
666                         get {
667                                 if (!inited) {
668                                         Init ();
669                                 }
670                                 return controlCAsInput;
671                         }
672                         set {
673                                 if (!inited) {
674                                         Init ();
675                                 }
676
677                                 if (controlCAsInput == value)
678                                         return;
679
680                                 ConsoleDriver.SetBreak (value);
681                                 controlCAsInput = value;
682                         }
683                 }
684
685                 //
686                 // Requries that caller calls Init () if not !inited.
687                 //
688                 unsafe void CheckWindowDimensions ()
689                 {
690                         if (native_terminal_size == null || terminal_size == *native_terminal_size)
691                                 return;
692
693                         if (*native_terminal_size == -1){
694                                 int c = reader.Get (TermInfoNumbers.Columns);
695                                 if (c != 0)
696                                         windowWidth = c;
697                                 
698                                 c = reader.Get (TermInfoNumbers.Lines);
699                                 if (c != 0)
700                                         windowHeight = c;
701                         } else {
702                                 terminal_size = *native_terminal_size;
703                                 windowWidth = terminal_size >> 16;
704                                 windowHeight = terminal_size & 0xffff;
705                         }
706                         bufferHeight = windowHeight;
707                         bufferWidth = windowWidth;
708                 }
709
710                 
711                 public int WindowHeight {
712                         get {
713                                 if (!inited) {
714                                         Init ();
715                                 }
716
717                                 CheckWindowDimensions ();
718                                 return windowHeight;
719                         }
720                         set {
721                                 if (!inited) {
722                                         Init ();
723                                 }
724
725                                 throw new NotSupportedException ();
726                         }
727                 }
728
729                 public int WindowLeft {
730                         get {
731                                 if (!inited) {
732                                         Init ();
733                                 }
734
735                                 //CheckWindowDimensions ();
736                                 return 0;
737                         }
738                         set {
739                                 if (!inited) {
740                                         Init ();
741                                 }
742
743                                 throw new NotSupportedException ();
744                         }
745                 }
746
747                 public int WindowTop {
748                         get {
749                                 if (!inited) {
750                                         Init ();
751                                 }
752
753                                 //CheckWindowDimensions ();
754                                 return 0;
755                         }
756                         set {
757                                 if (!inited) {
758                                         Init ();
759                                 }
760
761                                 throw new NotSupportedException ();
762                         }
763                 }
764
765                 public int WindowWidth {
766                         get {
767                                 if (!inited) {
768                                         Init ();
769                                 }
770
771                                 CheckWindowDimensions ();
772                                 return windowWidth;
773                         }
774                         set {
775                                 if (!inited) {
776                                         Init ();
777                                 }
778
779                                 throw new NotSupportedException ();
780                         }
781                 }
782
783                 public void Clear ()
784                 {
785                         if (!inited) {
786                                 Init ();
787                         }
788
789                         WriteConsole (clear);
790                         cursorLeft = 0;
791                         cursorTop = 0;
792                 }
793
794                 public void Beep (int frequency, int duration)
795                 {
796                         if (!inited) {
797                                 Init ();
798                         }
799
800                         WriteConsole (bell);
801                 }
802
803                 public void MoveBufferArea (int sourceLeft, int sourceTop, int sourceWidth, int sourceHeight,
804                                         int targetLeft, int targetTop, Char sourceChar,
805                                         ConsoleColor sourceForeColor, ConsoleColor sourceBackColor)
806                 {
807                         if (!inited) {
808                                 Init ();
809                         }
810
811                         throw new NotImplementedException ();
812                 }
813
814                 void AddToBuffer (int b)
815                 {
816                         if (buffer == null) {
817                                 buffer = new char [1024];
818                         } else if (writepos >= buffer.Length) {
819                                 char [] newbuf = new char [buffer.Length * 2];
820                                 Buffer.BlockCopy (buffer, 0, newbuf, 0, buffer.Length);
821                                 buffer = newbuf;
822                         }
823
824                         buffer [writepos++] = (char) b;
825                 }
826
827                 void AdjustBuffer ()
828                 {
829                         if (readpos >= writepos) {
830                                 readpos = writepos = 0;
831                         }
832                 }
833
834                 ConsoleKeyInfo CreateKeyInfoFromInt (int n, bool alt)
835                 {
836                         char c = (char) n;
837                         ConsoleKey key = (ConsoleKey)n;
838                         bool shift = false;
839                         bool ctrl = false;
840
841                         switch (n){
842                         case 10:
843                                 key = ConsoleKey.Enter;
844                                 break;
845                         case 0x20:
846                                 key = ConsoleKey.Spacebar;
847                                 break;
848                         case 45:
849                                 key = ConsoleKey.Subtract;
850                                 break;
851                         case 43:
852                                 key = ConsoleKey.Add;
853                                 break;
854                         case 47:
855                                 key = ConsoleKey.Divide;
856                                 break;
857                         case 42:
858                                 key = ConsoleKey.Multiply;
859                                 break;
860                         case 8: case 9: case 12: case 13: case 19:
861                                 /* Values in ConsoleKey */
862                                 break;
863                         case 27:
864                                 key = ConsoleKey.Escape;
865                                 break;
866                                 
867                         default:
868                                 if (n >= 1 && n <= 26) {
869                                         // For Ctrl-a to Ctrl-z.
870                                         ctrl = true;
871                                         key = ConsoleKey.A + n - 1;
872                                 } else if (n >= 'a' && n <= 'z') {
873                                         key = ConsoleKey.A - 'a' + n;
874                                 } else if (n >= 'A' && n <= 'Z') {
875                                         shift = true;
876                                 } else if (n >= '0' && n <= '9') {
877                                 } else
878                                         key = 0;
879                                 break;
880                         }
881
882                         return new ConsoleKeyInfo (c, key, shift, alt, ctrl);
883                 }
884
885                 object GetKeyFromBuffer (bool cooked)
886                 {
887                         if (readpos >= writepos)
888                                 return null;
889
890                         int next = buffer [readpos];
891                         if (!cooked || !rootmap.StartsWith (next)) {
892                                 readpos++;
893                                 AdjustBuffer ();
894                                 return CreateKeyInfoFromInt (next, false);
895                         }
896
897                         int used;
898                         TermInfoStrings str = rootmap.Match (buffer, readpos, writepos - readpos, out used);
899                         if ((int) str == -1){
900                                 // Escape sequences: alt keys are sent as ESC-key
901                                 if (buffer [readpos] == 27 && (writepos - readpos) >= 2){
902                                         readpos += 2;
903                                         AdjustBuffer ();
904                                         if (buffer [readpos+1] == 127)
905                                                 return new ConsoleKeyInfo ((char)8, ConsoleKey.Backspace, false, true, false);
906                                         return CreateKeyInfoFromInt (buffer [readpos+1], true);
907                                 } else
908                                         return null;
909                         }
910
911                         ConsoleKeyInfo key;
912                         if (keymap [str] != null) {
913                                 key = (ConsoleKeyInfo) keymap [str];
914                         } else {
915                                 readpos++;
916                                 AdjustBuffer ();
917                                 return CreateKeyInfoFromInt (next, false);
918                         }
919
920                         readpos += used;
921                         AdjustBuffer ();
922                         return key;
923                 }
924
925                 ConsoleKeyInfo ReadKeyInternal (out bool fresh)
926                 {
927                         if (!inited)
928                                 Init ();
929
930                         InitKeys ();
931
932                         object o;
933
934                         if ((o = GetKeyFromBuffer (true)) == null) {
935                                 do {
936                                         if (ConsoleDriver.InternalKeyAvailable (150) > 0) {
937                                                 do {
938                                                         AddToBuffer (stdin.Read ());
939                                                 } while (ConsoleDriver.InternalKeyAvailable (0) > 0);
940                                         } else if (stdin.DataAvailable ()) {
941                                                 do {
942                                                         AddToBuffer (stdin.Read ());
943                                                 } while (stdin.DataAvailable ());
944                                         } else {
945                                                 if ((o = GetKeyFromBuffer (false)) != null)
946                                                         break;
947
948                                                 AddToBuffer (stdin.Read ());
949                                         }
950                                         
951                                         o = GetKeyFromBuffer (true);
952                                 } while (o == null);
953
954                                 // freshly read character
955                                 fresh = true;
956                         } else {
957                                 // this char was pre-buffered (e.g. not fresh)
958                                 fresh = false;
959                         }
960
961                         return (ConsoleKeyInfo) o;
962                 }
963
964 #region Input echoing optimization
965                 bool InputPending ()
966                 {
967                         // check if we've got pending input we can read immediately
968                         return readpos < writepos || stdin.DataAvailable ();
969                 }
970
971                 char [] echobuf = null;
972                 int echon = 0;
973
974                 // Queues a character to be echo'd back to the console
975                 void QueueEcho (char c)
976                 {
977                         if (echobuf == null)
978                                 echobuf = new char [1024];
979
980                         echobuf[echon++] = c;
981
982                         if (echon == echobuf.Length || !InputPending ()) {
983                                 // blit our echo buffer to the console
984                                 stdout.InternalWriteChars (echobuf, echon);
985                                 echon = 0;
986                         }
987                 }
988
989                 // Queues a key to be echo'd back to the console
990                 void Echo (ConsoleKeyInfo key)
991                 {
992                         if (!IsSpecialKey (key)) {
993                                 QueueEcho (key.KeyChar);
994                                 return;
995                         }
996
997                         // flush pending echo's
998                         EchoFlush ();
999
1000                         WriteSpecialKey (key);
1001                 }
1002
1003                 // Flush the pending echo queue
1004                 void EchoFlush ()
1005                 {
1006                         if (echon == 0)
1007                                 return;
1008
1009                         // flush our echo buffer to the console
1010                         stdout.InternalWriteChars (echobuf, echon);
1011                         echon = 0;
1012                 }
1013 #endregion
1014
1015                 public int Read ([In, Out] char [] dest, int index, int count)
1016                 {
1017                         bool fresh, echo = false;
1018                         StringBuilder sbuf;
1019                         ConsoleKeyInfo key;
1020                         int BoL = 0;  // Beginning-of-Line marker (can't backspace beyond this)
1021                         object o;
1022                         char c;
1023
1024                         sbuf = new StringBuilder ();
1025
1026                         // consume buffered keys first (do not echo, these have already been echo'd)
1027                         while (true) {
1028                                 if ((o = GetKeyFromBuffer (true)) == null)
1029                                         break;
1030
1031                                 key = (ConsoleKeyInfo) o;
1032                                 c = key.KeyChar;
1033
1034                                 if (key.Key != ConsoleKey.Backspace) {
1035                                         if (key.Key == ConsoleKey.Enter)
1036                                                 BoL = sbuf.Length;
1037
1038                                         sbuf.Append (c);
1039                                 } else if (sbuf.Length > BoL) {
1040                                         sbuf.Length--;
1041                                 }
1042                         }
1043
1044                         // continue reading until Enter is hit
1045                         rl_startx = cursorLeft;
1046                         rl_starty = cursorTop;
1047
1048                         do {
1049                                 key = ReadKeyInternal (out fresh);
1050                                 echo = echo || fresh;
1051                                 c = key.KeyChar;
1052
1053                                 if (key.Key != ConsoleKey.Backspace) {
1054                                         if (key.Key == ConsoleKey.Enter)
1055                                                 BoL = sbuf.Length;
1056
1057                                         sbuf.Append (c);
1058                                 } else if (sbuf.Length > BoL) {
1059                                         sbuf.Length--;
1060                                 } else {
1061                                         continue;
1062                                 }
1063
1064                                 // echo fresh keys back to the console
1065                                 if (echo)
1066                                         Echo (key);
1067                         } while (key.Key != ConsoleKey.Enter);
1068
1069                         EchoFlush ();
1070
1071                         rl_startx = -1;
1072                         rl_starty = -1;
1073
1074                         // copy up to count chars into dest
1075                         int nread = 0;
1076                         while (count > 0 && nread < sbuf.Length) {
1077                                 dest[index + nread] = sbuf[nread];
1078                                 nread++;
1079                                 count--;
1080                         }
1081
1082                         // put the rest back into our key buffer
1083                         for (int i = nread; i < sbuf.Length; i++)
1084                                 AddToBuffer (sbuf[i]);
1085
1086                         return nread;
1087                 }
1088
1089                 public ConsoleKeyInfo ReadKey (bool intercept)
1090                 {
1091                         bool fresh;
1092
1093                         ConsoleKeyInfo key = ReadKeyInternal (out fresh);
1094
1095                         if (!intercept && fresh) {
1096                                 // echo the fresh key back to the console
1097                                 Echo (key);
1098                                 EchoFlush ();
1099                         }
1100
1101                         return key;
1102                 }
1103
1104                 public string ReadLine ()
1105                 {
1106                         if (!inited)
1107                                 Init ();
1108
1109                         // Hack to make Iron Python work (since it goes behind our backs
1110                         // when writing to the console thus preventing us from keeping
1111                         // cursor state normally).
1112                         GetCursorPosition ();
1113
1114                         StringBuilder builder = new StringBuilder ();
1115                         bool fresh, echo = false;
1116                         ConsoleKeyInfo key;
1117                         char c;
1118
1119                         rl_startx = cursorLeft;
1120                         rl_starty = cursorTop;
1121                         char eof = (char) control_characters [ControlCharacters.EOF];
1122
1123                         do {
1124                                 key = ReadKeyInternal (out fresh);
1125                                 echo = echo || fresh;
1126                                 c = key.KeyChar;
1127                                 // EOF -> Ctrl-D (EOT) pressed.
1128                                 if (c == eof && c != 0 && builder.Length == 0)
1129                                         return null;
1130
1131                                 if (key.Key != ConsoleKey.Enter) {
1132                                         if (key.Key != ConsoleKey.Backspace) {
1133                                                 builder.Append (c);
1134                                         } else if (builder.Length > 0) {
1135                                                 builder.Length--;
1136                                         } else {
1137                                                 // skips over echoing the key to the console
1138                                                 continue;
1139                                         }
1140                                 }
1141
1142                                 // echo fresh keys back to the console
1143                                 if (echo)
1144                                         Echo (key);
1145                         } while (key.Key != ConsoleKey.Enter);
1146
1147                         EchoFlush ();
1148
1149                         rl_startx = -1;
1150                         rl_starty = -1;
1151
1152                         return builder.ToString ();
1153                 }
1154
1155                 public void ResetColor ()
1156                 {
1157                         if (!inited) {
1158                                 Init ();
1159                         }
1160
1161                         string str = (origPair != null) ? origPair : origColors;
1162                         WriteConsole (str);
1163                 }
1164
1165                 public void SetBufferSize (int width, int height)
1166                 {
1167                         if (!inited) {
1168                                 Init ();
1169                         }
1170
1171                         throw new NotImplementedException (String.Empty);
1172                 }
1173
1174                 public void SetCursorPosition (int left, int top)
1175                 {
1176                         if (!inited) {
1177                                 Init ();
1178                         }
1179
1180                         CheckWindowDimensions ();
1181                         if (left < 0 || left >= bufferWidth)
1182                                 throw new ArgumentOutOfRangeException ("left", "Value must be positive and below the buffer width.");
1183
1184                         if (top < 0 || top >= bufferHeight)
1185                                 throw new ArgumentOutOfRangeException ("top", "Value must be positive and below the buffer height.");
1186
1187                         // Either CursorAddress or nothing.
1188                         // We might want to play with up/down/left/right/home when ca is not available.
1189                         if (cursorAddress == null)
1190                                 throw new NotSupportedException ("This terminal does not suport setting the cursor position.");
1191
1192                         WriteConsole (ParameterizedStrings.Evaluate (cursorAddress, top, left));
1193                         cursorLeft = left;
1194                         cursorTop = top;
1195                 }
1196
1197                 public void SetWindowPosition (int left, int top)
1198                 {
1199                         if (!inited) {
1200                                 Init ();
1201                         }
1202
1203                         // No need to throw exceptions here.
1204                         //throw new NotSupportedException ();
1205                 }
1206
1207                 public void SetWindowSize (int width, int height)
1208                 {
1209                         if (!inited) {
1210                                 Init ();
1211                         }
1212
1213                         // No need to throw exceptions here.
1214                         //throw new NotSupportedException ();
1215                 }
1216
1217
1218                 void CreateKeyMap ()
1219                 {
1220                         keymap = new Hashtable ();
1221                         
1222                         keymap [TermInfoStrings.KeyBackspace] = new ConsoleKeyInfo ('\0', ConsoleKey.Backspace, false, false, false);
1223                         keymap [TermInfoStrings.KeyClear] = new ConsoleKeyInfo ('\0', ConsoleKey.Clear, false, false, false);
1224                         // Delete character...
1225                         keymap [TermInfoStrings.KeyDown] = new ConsoleKeyInfo ('\0', ConsoleKey.DownArrow, false, false, false);
1226                         keymap [TermInfoStrings.KeyF1] = new ConsoleKeyInfo ('\0', ConsoleKey.F1, false, false, false);
1227                         keymap [TermInfoStrings.KeyF10] = new ConsoleKeyInfo ('\0', ConsoleKey.F10, false, false, false);
1228                         keymap [TermInfoStrings.KeyF2] = new ConsoleKeyInfo ('\0', ConsoleKey.F2, false, false, false);
1229                         keymap [TermInfoStrings.KeyF3] = new ConsoleKeyInfo ('\0', ConsoleKey.F3, false, false, false);
1230                         keymap [TermInfoStrings.KeyF4] = new ConsoleKeyInfo ('\0', ConsoleKey.F4, false, false, false);
1231                         keymap [TermInfoStrings.KeyF5] = new ConsoleKeyInfo ('\0', ConsoleKey.F5, false, false, false);
1232                         keymap [TermInfoStrings.KeyF6] = new ConsoleKeyInfo ('\0', ConsoleKey.F6, false, false, false);
1233                         keymap [TermInfoStrings.KeyF7] = new ConsoleKeyInfo ('\0', ConsoleKey.F7, false, false, false);
1234                         keymap [TermInfoStrings.KeyF8] = new ConsoleKeyInfo ('\0', ConsoleKey.F8, false, false, false);
1235                         keymap [TermInfoStrings.KeyF9] = new ConsoleKeyInfo ('\0', ConsoleKey.F9, false, false, false);
1236                         keymap [TermInfoStrings.KeyHome] = new ConsoleKeyInfo ('\0', ConsoleKey.Home, false, false, false);
1237                         keymap [TermInfoStrings.KeyLeft] = new ConsoleKeyInfo ('\0', ConsoleKey.LeftArrow, false, false, false);
1238                         keymap [TermInfoStrings.KeyLl] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad1, false, false, false);
1239                         keymap [TermInfoStrings.KeyNpage] = new ConsoleKeyInfo ('\0', ConsoleKey.PageDown, false, false, false);
1240                         keymap [TermInfoStrings.KeyPpage] = new ConsoleKeyInfo ('\0', ConsoleKey.PageUp, false, false, false);
1241                         keymap [TermInfoStrings.KeyRight] = new ConsoleKeyInfo ('\0', ConsoleKey.RightArrow, false, false, false);
1242                         keymap [TermInfoStrings.KeySf] = new ConsoleKeyInfo ('\0', ConsoleKey.PageDown, false, false, false);
1243                         keymap [TermInfoStrings.KeySr] = new ConsoleKeyInfo ('\0', ConsoleKey.PageUp, false, false, false);
1244                         keymap [TermInfoStrings.KeyUp] = new ConsoleKeyInfo ('\0', ConsoleKey.UpArrow, false, false, false);
1245                         keymap [TermInfoStrings.KeyA1] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad7, false, false, false);
1246                         keymap [TermInfoStrings.KeyA3] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad9, false, false, false);
1247                         keymap [TermInfoStrings.KeyB2] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad5, false, false, false);
1248                         keymap [TermInfoStrings.KeyC1] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad1, false, false, false);
1249                         keymap [TermInfoStrings.KeyC3] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad3, false, false, false);
1250                         keymap [TermInfoStrings.KeyBtab] = new ConsoleKeyInfo ('\0', ConsoleKey.Tab, true, false, false);
1251                         keymap [TermInfoStrings.KeyBeg] = new ConsoleKeyInfo ('\0', ConsoleKey.Home, false, false, false);
1252                         keymap [TermInfoStrings.KeyCopy] = new ConsoleKeyInfo ('C', ConsoleKey.C, false, true, false);
1253                         keymap [TermInfoStrings.KeyEnd] = new ConsoleKeyInfo ('\0', ConsoleKey.End, false, false, false);
1254                         keymap [TermInfoStrings.KeyEnter] = new ConsoleKeyInfo ('\n', ConsoleKey.Enter, false, false, false);
1255                         keymap [TermInfoStrings.KeyHelp] = new ConsoleKeyInfo ('\0', ConsoleKey.Help, false, false, false);
1256                         keymap [TermInfoStrings.KeyPrint] = new ConsoleKeyInfo ('\0', ConsoleKey.Print, false, false, false);
1257                         keymap [TermInfoStrings.KeyUndo] = new ConsoleKeyInfo ('Z', ConsoleKey.Z , false, true, false);
1258                         keymap [TermInfoStrings.KeySbeg] = new ConsoleKeyInfo ('\0', ConsoleKey.Home, true, false, false);
1259                         keymap [TermInfoStrings.KeyScopy] = new ConsoleKeyInfo ('C', ConsoleKey.C , true, true, false);
1260                         keymap [TermInfoStrings.KeySdc] = new ConsoleKeyInfo ('\x9', ConsoleKey.Delete, true, false, false);
1261                         keymap [TermInfoStrings.KeyShelp] = new ConsoleKeyInfo ('\0', ConsoleKey.Help, true, false, false);
1262                         keymap [TermInfoStrings.KeyShome] = new ConsoleKeyInfo ('\0', ConsoleKey.Home, true, false, false);
1263                         keymap [TermInfoStrings.KeySleft] = new ConsoleKeyInfo ('\0', ConsoleKey.LeftArrow, true, false, false);
1264                         keymap [TermInfoStrings.KeySprint] = new ConsoleKeyInfo ('\0', ConsoleKey.Print, true, false, false);
1265                         keymap [TermInfoStrings.KeySright] = new ConsoleKeyInfo ('\0', ConsoleKey.RightArrow, true, false, false);
1266                         keymap [TermInfoStrings.KeySundo] = new ConsoleKeyInfo ('Z', ConsoleKey.Z, true, false, false);
1267                         keymap [TermInfoStrings.KeyF11] = new ConsoleKeyInfo ('\0', ConsoleKey.F11, false, false, false);
1268                         keymap [TermInfoStrings.KeyF12] = new ConsoleKeyInfo ('\0', ConsoleKey.F12 , false, false, false);
1269                         keymap [TermInfoStrings.KeyF13] = new ConsoleKeyInfo ('\0', ConsoleKey.F13, false, false, false);
1270                         keymap [TermInfoStrings.KeyF14] = new ConsoleKeyInfo ('\0', ConsoleKey.F14, false, false, false);
1271                         keymap [TermInfoStrings.KeyF15] = new ConsoleKeyInfo ('\0', ConsoleKey.F15, false, false, false);
1272                         keymap [TermInfoStrings.KeyF16] = new ConsoleKeyInfo ('\0', ConsoleKey.F16, false, false, false);
1273                         keymap [TermInfoStrings.KeyF17] = new ConsoleKeyInfo ('\0', ConsoleKey.F17, false, false, false);
1274                         keymap [TermInfoStrings.KeyF18] = new ConsoleKeyInfo ('\0', ConsoleKey.F18, false, false, false);
1275                         keymap [TermInfoStrings.KeyF19] = new ConsoleKeyInfo ('\0', ConsoleKey.F19, false, false, false);
1276                         keymap [TermInfoStrings.KeyF20] = new ConsoleKeyInfo ('\0', ConsoleKey.F20, false, false, false);
1277                         keymap [TermInfoStrings.KeyF21] = new ConsoleKeyInfo ('\0', ConsoleKey.F21, false, false, false);
1278                         keymap [TermInfoStrings.KeyF22] = new ConsoleKeyInfo ('\0', ConsoleKey.F22, false, false, false);
1279                         keymap [TermInfoStrings.KeyF23] = new ConsoleKeyInfo ('\0', ConsoleKey.F23, false, false, false);
1280                         keymap [TermInfoStrings.KeyF24] = new ConsoleKeyInfo ('\0', ConsoleKey.F24, false, false, false);
1281                         // These were previously missing:
1282                         keymap [TermInfoStrings.KeyDc] = new ConsoleKeyInfo ('\0', ConsoleKey.Delete, false, false, false);
1283                         keymap [TermInfoStrings.KeyIc] = new ConsoleKeyInfo ('\0', ConsoleKey.Insert, false, false, false);
1284                 }
1285
1286                 void InitKeys ()
1287                 {
1288                         if (initKeys)
1289                                 return;
1290
1291                         CreateKeyMap ();
1292                         rootmap = new ByteMatcher ();
1293
1294                         //
1295                         // The keys that we know about and use
1296                         //
1297                         var UsedKeys = new [] {
1298                                 TermInfoStrings.KeyBackspace,
1299                                 TermInfoStrings.KeyClear,
1300                                 TermInfoStrings.KeyDown,
1301                                 TermInfoStrings.KeyF1,
1302                                 TermInfoStrings.KeyF10,
1303                                 TermInfoStrings.KeyF2,
1304                                 TermInfoStrings.KeyF3,
1305                                 TermInfoStrings.KeyF4,
1306                                 TermInfoStrings.KeyF5,
1307                                 TermInfoStrings.KeyF6,
1308                                 TermInfoStrings.KeyF7,
1309                                 TermInfoStrings.KeyF8,
1310                                 TermInfoStrings.KeyF9,
1311                                 TermInfoStrings.KeyHome,
1312                                 TermInfoStrings.KeyLeft,
1313                                 TermInfoStrings.KeyLl,
1314                                 TermInfoStrings.KeyNpage,
1315                                 TermInfoStrings.KeyPpage,
1316                                 TermInfoStrings.KeyRight,
1317                                 TermInfoStrings.KeySf,
1318                                 TermInfoStrings.KeySr,
1319                                 TermInfoStrings.KeyUp,
1320                                 TermInfoStrings.KeyA1,
1321                                 TermInfoStrings.KeyA3,
1322                                 TermInfoStrings.KeyB2,
1323                                 TermInfoStrings.KeyC1,
1324                                 TermInfoStrings.KeyC3,
1325                                 TermInfoStrings.KeyBtab,
1326                                 TermInfoStrings.KeyBeg,
1327                                 TermInfoStrings.KeyCopy,
1328                                 TermInfoStrings.KeyEnd,
1329                                 TermInfoStrings.KeyEnter,
1330                                 TermInfoStrings.KeyHelp,
1331                                 TermInfoStrings.KeyPrint,
1332                                 TermInfoStrings.KeyUndo,
1333                                 TermInfoStrings.KeySbeg,
1334                                 TermInfoStrings.KeyScopy,
1335                                 TermInfoStrings.KeySdc,
1336                                 TermInfoStrings.KeyShelp,
1337                                 TermInfoStrings.KeyShome,
1338                                 TermInfoStrings.KeySleft,
1339                                 TermInfoStrings.KeySprint,
1340                                 TermInfoStrings.KeySright,
1341                                 TermInfoStrings.KeySundo,
1342                                 TermInfoStrings.KeyF11,
1343                                 TermInfoStrings.KeyF12,
1344                                 TermInfoStrings.KeyF13,
1345                                 TermInfoStrings.KeyF14,
1346                                 TermInfoStrings.KeyF15,
1347                                 TermInfoStrings.KeyF16,
1348                                 TermInfoStrings.KeyF17,
1349                                 TermInfoStrings.KeyF18,
1350                                 TermInfoStrings.KeyF19,
1351                                 TermInfoStrings.KeyF20,
1352                                 TermInfoStrings.KeyF21,
1353                                 TermInfoStrings.KeyF22,
1354                                 TermInfoStrings.KeyF23,
1355                                 TermInfoStrings.KeyF24,
1356
1357                                 // These were missing
1358                                 TermInfoStrings.KeyDc,
1359                                 TermInfoStrings.KeyIc
1360                         };
1361                         
1362                         foreach (TermInfoStrings tis in UsedKeys)
1363                                 AddStringMapping (tis);
1364                         
1365                         rootmap.AddMapping (TermInfoStrings.KeyBackspace, new byte [] { control_characters [ControlCharacters.Erase] });
1366                         rootmap.Sort ();
1367                         initKeys = true;
1368                 }
1369
1370                 void AddStringMapping (TermInfoStrings s)
1371                 {
1372                         byte [] bytes = reader.GetStringBytes (s);
1373                         if (bytes == null)
1374                                 return;
1375
1376                         rootmap.AddMapping (s, bytes);
1377                 }
1378         }
1379
1380         /// <summary>Provides support for evaluating parameterized terminfo database format strings.</summary>
1381         internal static class ParameterizedStrings
1382         {
1383                 /// <summary>A cached stack to use to avoid allocating a new stack object for every evaluation.</summary>
1384                 [ThreadStatic]
1385                 private static LowLevelStack _cachedStack;
1386
1387                 /// <summary>Evaluates a terminfo formatting string, using the supplied arguments.</summary>
1388                 /// <param name="format">The format string.</param>
1389                 /// <param name="args">The arguments to the format string.</param>
1390                 /// <returns>The formatted string.</returns>
1391                 public static string Evaluate(string format, params FormatParam[] args)
1392                 {
1393                         if (format == null)
1394                                 throw new ArgumentNullException("format");
1395                         if (args == null)
1396                                 throw new ArgumentNullException("args");
1397
1398                         // Initialize the stack to use for processing.
1399                         LowLevelStack stack = _cachedStack;
1400                         if (stack == null)
1401                                 _cachedStack = stack = new LowLevelStack();
1402                         else
1403                                 stack.Clear();
1404
1405                         // "dynamic" and "static" variables are much less often used (the "dynamic" and "static"
1406                         // terminology appears to just refer to two different collections rather than to any semantic
1407                         // meaning).  As such, we'll only initialize them if we really need them.
1408                         FormatParam[] dynamicVars = null, staticVars = null;
1409                         
1410                         int pos = 0;
1411                         return EvaluateInternal(format, ref pos, args, stack, ref dynamicVars, ref staticVars);
1412                         
1413                         // EvaluateInternal may throw IndexOutOfRangeException and InvalidOperationException
1414                         // if the format string is malformed or if it's inconsistent with the parameters provided.
1415                 }
1416                 
1417                 /// <summary>Evaluates a terminfo formatting string, using the supplied arguments and processing data structures.</summary>
1418                 /// <param name="format">The format string.</param>
1419                 /// <param name="pos">The position in <paramref name="format"/> to start processing.</param>
1420                 /// <param name="args">The arguments to the format string.</param>
1421                 /// <param name="stack">The stack to use as the format string is evaluated.</param>
1422                 /// <param name="dynamicVars">A lazily-initialized collection of variables.</param>
1423                 /// <param name="staticVars">A lazily-initialized collection of variables.</param>
1424                 /// <returns>
1425                 /// The formatted string; this may be empty if the evaluation didn't yield any output.
1426                 /// The evaluation stack will have a 1 at the top if all processing was completed at invoked level
1427                 /// of recursion, and a 0 at the top if we're still inside of a conditional that requires more processing.
1428                 /// </returns>
1429                 private static string EvaluateInternal(
1430                         string format, ref int pos, FormatParam[] args, LowLevelStack stack,
1431                         ref FormatParam[] dynamicVars, ref FormatParam[] staticVars)
1432                 {
1433                         // Create a StringBuilder to store the output of this processing.  We use the format's length as an 
1434                         // approximation of an upper-bound for how large the output will be, though with parameter processing,
1435                         // this is just an estimate, sometimes way over, sometimes under.
1436                         StringBuilder output = new StringBuilder(format.Length);
1437
1438                         // Format strings support conditionals, including the equivalent of "if ... then ..." and
1439                         // "if ... then ... else ...", as well as "if ... then ... else ... then ..."
1440                         // and so on, where an else clause can not only be evaluated for string output but also
1441                         // as a conditional used to determine whether to evaluate a subsequent then clause.
1442                         // We use recursion to process these subsequent parts, and we track whether we're processing
1443                         // at the same level of the initial if clause (or whether we're nested).
1444                         bool sawIfConditional = false;
1445
1446                         // Process each character in the format string, starting from the position passed in.
1447                         for (; pos < format.Length; pos++){
1448                                 // '%' is the escape character for a special sequence to be evaluated.
1449                                 // Anything else just gets pushed to output.
1450                                 if (format[pos] != '%') {
1451                                         output.Append(format[pos]);
1452                                         continue;
1453                                 }
1454                                 // We have a special parameter sequence to process.  Now we need
1455                                 // to look at what comes after the '%'.
1456                                 ++pos;
1457                                 switch (format[pos]) {
1458                                 // Output appending operations
1459                                 case '%': // Output the escaped '%'
1460                                         output.Append('%');
1461                                         break;
1462                                 case 'c': // Pop the stack and output it as a char
1463                                         output.Append((char)stack.Pop().Int32);
1464                                         break;
1465                                 case 's': // Pop the stack and output it as a string
1466                                         output.Append(stack.Pop().String);
1467                                         break;
1468                                 case 'd': // Pop the stack and output it as an integer
1469                                         output.Append(stack.Pop().Int32);
1470                                         break;
1471                                 case 'o':
1472                                 case 'X':
1473                                 case 'x':
1474                                 case ':':
1475                                 case '0':
1476                                 case '1':
1477                                 case '2':
1478                                 case '3':
1479                                 case '4':
1480                                 case '5':
1481                                 case '6':
1482                                 case '7':
1483                                 case '8':
1484                                 case '9':
1485                                         // printf strings of the format "%[[:]flags][width[.precision]][doxXs]" are allowed
1486                                         // (with a ':' used in front of flags to help differentiate from binary operations, as flags can
1487                                         // include '-' and '+').  While above we've special-cased common usage (e.g. %d, %s),
1488                                         // for more complicated expressions we delegate to printf.
1489                                         int printfEnd = pos;
1490                                         for (; printfEnd < format.Length; printfEnd++) // find the end of the printf format string
1491                                         {
1492                                                 char ec = format[printfEnd];
1493                                                 if (ec == 'd' || ec == 'o' || ec == 'x' || ec == 'X' || ec == 's')
1494                                                 {
1495                                                         break;
1496                                                 }
1497                                         }
1498                                         if (printfEnd >= format.Length)
1499                                                 throw new InvalidOperationException("Terminfo database contains invalid values");
1500                                         string printfFormat = format.Substring(pos - 1, printfEnd - pos + 2); // extract the format string
1501                                         if (printfFormat.Length > 1 && printfFormat[1] == ':')
1502                                                 printfFormat = printfFormat.Remove(1, 1);
1503                                         output.Append(FormatPrintF(printfFormat, stack.Pop().Object)); // do the printf formatting and append its output
1504                                         break;
1505
1506                                         // Stack pushing operations
1507                                 case 'p': // Push the specified parameter (1-based) onto the stack
1508                                         pos++;
1509                                         stack.Push(args[format[pos] - '1']);
1510                                         break;
1511                                 case 'l': // Pop a string and push its length
1512                                         stack.Push(stack.Pop().String.Length);
1513                                         break;
1514                                 case '{': // Push integer literal, enclosed between braces
1515                                         pos++;
1516                                         int intLit = 0;
1517                                         while (format[pos] != '}')
1518                                         {
1519                                                 intLit = (intLit * 10) + (format[pos] - '0');
1520                                                 pos++;
1521                                         }
1522                                         stack.Push(intLit);
1523                                         break;
1524                                 case '\'': // Push literal character, enclosed between single quotes
1525                                         stack.Push((int)format[pos + 1]);
1526                                         pos += 2;
1527                                         break;
1528
1529                                         // Storing and retrieving "static" and "dynamic" variables
1530                                 case 'P': // Pop a value and store it into either static or dynamic variables based on whether the a-z variable is capitalized
1531                                         pos++;
1532                                         int setIndex;
1533                                         FormatParam[] targetVars = GetDynamicOrStaticVariables(format[pos], ref dynamicVars, ref staticVars, out setIndex);
1534                                         targetVars[setIndex] = stack.Pop();
1535                                         break;
1536                                 case 'g': // Push a static or dynamic variable; which is based on whether the a-z variable is capitalized
1537                                         pos++;
1538                                         int getIndex;
1539                                         FormatParam[] sourceVars = GetDynamicOrStaticVariables(format[pos], ref dynamicVars, ref staticVars, out getIndex);
1540                                         stack.Push(sourceVars[getIndex]);
1541                                         break;
1542
1543                                         // Binary operations
1544                                 case '+':
1545                                 case '-':
1546                                 case '*':
1547                                 case '/':
1548                                 case 'm':
1549                                 case '^': // arithmetic
1550                                 case '&':
1551                                 case '|':                                         // bitwise
1552                                 case '=':
1553                                 case '>':
1554                                 case '<':                               // comparison
1555                                 case 'A':
1556                                 case 'O':                                         // logical
1557                                         int second = stack.Pop().Int32; // it's a stack... the second value was pushed last
1558                                         int first = stack.Pop().Int32;
1559                                         int res;
1560                                         switch (format[pos]) {
1561                                         case '+':
1562                                                 res = first + second;
1563                                                 break;
1564                                         case '-':
1565                                                 res = first - second;
1566                                                 break;
1567                                         case '*':
1568                                                 res = first * second;
1569                                                 break;
1570                                         case '/':
1571                                                 res = first / second;
1572                                                 break;
1573                                         case 'm':
1574                                                 res = first % second;
1575                                                 break;
1576                                         case '^':
1577                                                 res = first ^ second;
1578                                                 break;
1579                                         case '&':
1580                                                 res = first & second;
1581                                                 break;
1582                                         case '|':
1583                                                 res = first | second;
1584                                                 break;
1585                                         case '=':
1586                                                 res = AsInt(first == second);
1587                                                 break;
1588                                         case '>':
1589                                                 res = AsInt(first > second);
1590                                                 break;
1591                                         case '<':
1592                                                 res = AsInt(first < second);
1593                                                 break;
1594                                         case 'A':
1595                                                 res = AsInt(AsBool(first) && AsBool(second));
1596                                                 break;
1597                                         case 'O':
1598                                                 res = AsInt(AsBool(first) || AsBool(second));
1599                                                 break;
1600                                         default:
1601                                                 res = 0;
1602                                                 break;
1603                                         }
1604                                         stack.Push(res);
1605                                         break;
1606
1607                                         // Unary operations
1608                                 case '!':
1609                                 case '~':
1610                                         int value = stack.Pop().Int32;
1611                                         stack.Push(
1612                                                 format[pos] == '!' ? AsInt(!AsBool(value)) :
1613                                                 ~value);
1614                                         break;
1615
1616                                         // Augment first two parameters by 1
1617                                 case 'i':
1618                                         args[0] = 1 + args[0].Int32;
1619                                         args[1] = 1 + args[1].Int32;
1620                                         break;
1621
1622                                         // Conditional of the form %? if-part %t then-part %e else-part %;
1623                                         // The "%e else-part" is optional.
1624                                 case '?':
1625                                         sawIfConditional = true;
1626                                         break;
1627                                 case 't':
1628                                         // We hit the end of the if-part and are about to start the then-part.
1629                                         // The if-part left its result on the stack; pop and evaluate.
1630                                         bool conditionalResult = AsBool(stack.Pop().Int32);
1631
1632                                         // Regardless of whether it's true, run the then-part to get past it.
1633                                         // If the conditional was true, output the then results.
1634                                         pos++;
1635                                         string thenResult = EvaluateInternal(format, ref pos, args, stack, ref dynamicVars, ref staticVars);
1636                                         if (conditionalResult)
1637                                         {
1638                                                 output.Append(thenResult);
1639                                         }
1640
1641                                         // We're past the then; the top of the stack should now be a Boolean
1642                                         // indicating whether this conditional has more to be processed (an else clause).
1643                                         if (!AsBool(stack.Pop().Int32))
1644                                         {
1645                                                 // Process the else clause, and if the conditional was false, output the else results.
1646                                                 pos++;
1647                                                 string elseResult = EvaluateInternal(format, ref pos, args, stack, ref dynamicVars, ref staticVars);
1648                                                 if (!conditionalResult)
1649                                                 {
1650                                                         output.Append(elseResult);
1651                                                 }
1652                                                 // Now we should be done (any subsequent elseif logic will have bene handled in the recursive call).
1653                                                 if (!AsBool(stack.Pop().Int32))
1654                                                 {
1655                                                         throw new InvalidOperationException("Terminfo database contains invalid values");
1656                                                 }
1657                                         }
1658
1659                                         // If we're in a nested processing, return to our parent.
1660                                         if (!sawIfConditional)
1661                                         {
1662                                                 stack.Push(1);
1663                                                 return output.ToString();
1664                                         }
1665                                         // Otherwise, we're done processing the conditional in its entirety.
1666                                         sawIfConditional = false;
1667                                         break;
1668                                 case 'e':
1669                                 case ';':
1670                                         // Let our caller know why we're exiting, whether due to the end of the conditional or an else branch.
1671                                         stack.Push(AsInt(format[pos] == ';'));
1672                                         return output.ToString();
1673
1674                                         // Anything else is an error
1675                                 default:
1676                                         throw new InvalidOperationException("Terminfo database contains invalid values");
1677                                 }
1678                         }
1679                         stack.Push(1);
1680                         return output.ToString();
1681                 }
1682                 
1683                 /// <summary>Converts an Int32 to a Boolean, with 0 meaning false and all non-zero values meaning true.</summary>
1684                 /// <param name="i">The integer value to convert.</param>
1685                 /// <returns>true if the integer was non-zero; otherwise, false.</returns>
1686                 static bool AsBool(Int32 i) { return i != 0; }
1687
1688                 /// <summary>Converts a Boolean to an Int32, with true meaning 1 and false meaning 0.</summary>
1689                 /// <param name="b">The Boolean value to convert.</param>
1690                 /// <returns>1 if the Boolean is true; otherwise, 0.</returns>
1691                 static int AsInt(bool b) { return b ? 1 : 0; }
1692
1693                 static string StringFromAsciiBytes(byte[] buffer, int offset, int length)
1694                 {
1695                         // Special-case for empty strings
1696                         if (length == 0)
1697                                 return string.Empty;
1698
1699                         // new string(sbyte*, ...) doesn't exist in the targeted reference assembly,
1700                         // so we first copy to an array of chars, and then create a string from that.
1701                         char[] chars = new char[length];
1702                         for (int i = 0, j = offset; i < length; i++, j++)
1703                                 chars[i] = (char)buffer[j];
1704                         return new string(chars);
1705                 }
1706
1707                 [DllImport("libc")]
1708                 static extern unsafe int snprintf(byte* str, IntPtr size, string format, string arg1);
1709
1710                 [DllImport("libc")]
1711                 static extern unsafe int snprintf(byte* str, IntPtr size, string format, int arg1);
1712                 
1713                 /// <summary>Formats an argument into a printf-style format string.</summary>
1714                 /// <param name="format">The printf-style format string.</param>
1715                 /// <param name="arg">The argument to format.  This must be an Int32 or a String.</param>
1716                 /// <returns>The formatted string.</returns>
1717                 static unsafe string FormatPrintF(string format, object arg)
1718                 {
1719                         // Determine how much space is needed to store the formatted string.
1720                         string stringArg = arg as string;
1721                         int neededLength = stringArg != null ?
1722                                 snprintf(null, IntPtr.Zero, format, stringArg) :
1723                                 snprintf(null, IntPtr.Zero, format, (int)arg);
1724                         if (neededLength == 0)
1725                                 return string.Empty;
1726                         if (neededLength < 0)
1727                                 throw new InvalidOperationException("The printf operation failed");
1728                         
1729                         // Allocate the needed space, format into it, and return the data as a string.
1730                         byte[] bytes = new byte[neededLength + 1]; // extra byte for the null terminator
1731                         fixed (byte* ptr = bytes){
1732                                 int length = stringArg != null ?
1733                                         snprintf(ptr, (IntPtr)bytes.Length, format, stringArg) :
1734                                         snprintf(ptr, (IntPtr)bytes.Length, format, (int)arg);
1735                                 if (length != neededLength)
1736                                 {
1737                                         throw new InvalidOperationException("Invalid printf operation");
1738                                 }
1739                         }
1740                         return StringFromAsciiBytes(bytes, 0, neededLength);
1741                 }
1742                 
1743                 /// <summary>Gets the lazily-initialized dynamic or static variables collection, based on the supplied variable name.</summary>
1744                 /// <param name="c">The name of the variable.</param>
1745                 /// <param name="dynamicVars">The lazily-initialized dynamic variables collection.</param>
1746                 /// <param name="staticVars">The lazily-initialized static variables collection.</param>
1747                 /// <param name="index">The index to use to index into the variables.</param>
1748                 /// <returns>The variables collection.</returns>
1749                 private static FormatParam[] GetDynamicOrStaticVariables(
1750                         char c, ref FormatParam[] dynamicVars, ref FormatParam[] staticVars, out int index)
1751                 {
1752                         if (c >= 'A' && c <= 'Z'){
1753                                 index = c - 'A';
1754                                 return staticVars ?? (staticVars = new FormatParam[26]); // one slot for each letter of alphabet
1755                         } else if (c >= 'a' && c <= 'z') {
1756                                 index = c - 'a';
1757                                 return dynamicVars ?? (dynamicVars = new FormatParam[26]); // one slot for each letter of alphabet
1758                         }
1759                         else throw new InvalidOperationException("Terminfo database contains invalid values");
1760                 }
1761
1762                 /// <summary>
1763                 /// Represents a parameter to a terminfo formatting string.
1764                 /// It is a discriminated union of either an integer or a string, 
1765                 /// with characters represented as integers.
1766                 /// </summary>
1767                 public struct FormatParam
1768                 {
1769                         /// <summary>The integer stored in the parameter.</summary>
1770                         private readonly int _int32;
1771                         /// <summary>The string stored in the parameter.</summary>
1772                         private readonly string _string; // null means an Int32 is stored
1773                         
1774                         /// <summary>Initializes the parameter with an integer value.</summary>
1775                         /// <param name="value">The value to be stored in the parameter.</param>
1776                         public FormatParam(Int32 value) : this(value, null) { }
1777                         
1778                         /// <summary>Initializes the parameter with a string value.</summary>
1779                         /// <param name="value">The value to be stored in the parameter.</param>
1780                         public FormatParam(String value) : this(0, value ?? string.Empty) { }
1781                         
1782                         /// <summary>Initializes the parameter.</summary>
1783                         /// <param name="intValue">The integer value.</param>
1784                         /// <param name="stringValue">The string value.</param>
1785                         private FormatParam(Int32 intValue, String stringValue)
1786                         {
1787                                 _int32 = intValue;
1788                                 _string = stringValue;
1789                         }
1790                         
1791                         /// <summary>Implicit converts an integer into a parameter.</summary>
1792                         public static implicit operator FormatParam(int value)
1793                         {
1794                                 return new FormatParam(value);
1795                         }
1796                         
1797                         /// <summary>Implicit converts a string into a parameter.</summary>
1798                         public static implicit operator FormatParam(string value)
1799                         {
1800                                 return new FormatParam(value);
1801                         }
1802                         
1803                         /// <summary>Gets the integer value of the parameter. If a string was stored, 0 is returned.</summary>
1804                         public int Int32 { get { return _int32; } }
1805                         
1806                         /// <summary>Gets the string value of the parameter.  If an Int32 or a null String were stored, an empty string is returned.</summary>
1807                         public string String { get { return _string ?? string.Empty; } }
1808                         
1809                         /// <summary>Gets the string or the integer value as an object.</summary>
1810                         public object Object { get { return _string ?? (object)_int32; } }
1811                 }
1812                 
1813                 /// <summary>Provides a basic stack data structure.</summary>
1814                 /// <typeparam name="T">Specifies the type of data in the stack.</typeparam>
1815                 private sealed class LowLevelStack
1816                 {
1817                         private const int DefaultSize = 4;
1818                         private FormatParam[] _arr;
1819                         private int _count;
1820                         
1821                         public LowLevelStack() { _arr = new FormatParam[DefaultSize]; }
1822                         
1823                         public FormatParam Pop()
1824                         {
1825                                 if (_count == 0)
1826                                         throw new InvalidOperationException("Terminfo: Invalid Stack");
1827
1828                                 var item = _arr[--_count];
1829                                 _arr[_count] = default(FormatParam);
1830                                 return item;
1831                         }
1832                         
1833                         public void Push(FormatParam item)
1834                         {
1835                                 if (_arr.Length == _count){
1836                                         var newArr = new FormatParam[_arr.Length * 2];
1837                                         Array.Copy(_arr, 0, newArr, 0, _arr.Length);
1838                                         _arr = newArr;
1839                                 }
1840                                 _arr[_count++] = item;
1841                         }
1842                         
1843                         public void Clear()
1844                         {
1845                                 Array.Clear(_arr, 0, _count);
1846                                 _count = 0;
1847                         }
1848                 }
1849         }
1850                
1851         class ByteMatcher {
1852                 Hashtable map = new Hashtable ();
1853                 Hashtable starts = new Hashtable ();
1854
1855                 public void AddMapping (TermInfoStrings key, byte [] val)
1856                 {
1857                         if (val.Length == 0)
1858                                 return;
1859
1860                         map [val] = key;
1861                         starts [(int) val [0]] = true;
1862                 }
1863
1864                 public void Sort ()
1865                 {
1866                 }
1867
1868                 public bool StartsWith (int c)
1869                 {
1870                         return (starts [c] != null);
1871                 }
1872
1873                 public TermInfoStrings Match (char [] buffer, int offset, int length, out int used)
1874                 {
1875                         foreach (byte [] bytes in map.Keys) {
1876                                 for (int i = 0; i < bytes.Length && i < length; i++) {
1877                                         if ((char) bytes [i] != buffer [offset + i])
1878                                                 break;
1879
1880                                         if (bytes.Length - 1 == i) {
1881                                                 used = bytes.Length;
1882                                                 return (TermInfoStrings) map [bytes];
1883                                         }
1884                                 }
1885                         }
1886
1887                         used = 0;
1888                         return (TermInfoStrings) (-1);
1889                 }
1890         }
1891 }
1892 #endif
1893