2007-04-18 Jeffrey Stedfast <fejj@novell.com>
[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 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30 #if NET_2_0
31 //#define DEBUG
32 using System.Collections;
33 using System.IO;
34 using System.Text;
35 using System.Runtime.InteropServices;
36 namespace System {
37         class TermInfoDriver : IConsoleDriver {
38                 /* Do not rename this field, its looked up from the runtime */
39                 static bool need_window_dimensions = true;
40                 
41                 static string [] locations = { "/etc/terminfo", "/usr/share/terminfo", "/usr/lib/terminfo" };
42
43                 TermInfoReader reader;
44                 int cursorLeft;
45                 int cursorTop;
46                 string title = String.Empty;
47                 string titleFormat = String.Empty;
48                 bool cursorVisible = true;
49                 string csrVisible;
50                 string csrInvisible;
51                 string clear;
52                 string bell;
53                 string term;
54                 Stream stdin;
55                 internal byte verase;
56                 byte vsusp;
57                 byte intr;
58
59                 int windowWidth;
60                 int windowHeight;
61                 //int windowTop;
62                 //int windowLeft;
63                 int bufferHeight;
64                 int bufferWidth;
65
66                 byte [] buffer;
67                 int readpos;
68                 int writepos;
69                 string keypadXmit, keypadLocal;
70                 bool controlCAsInput;
71                 bool inited;
72                 bool initKeys;
73                 string origPair;
74                 string origColors;
75                 string cursorAddress;
76                 ConsoleColor fgcolor = ConsoleColor.White;
77                 ConsoleColor bgcolor = ConsoleColor.Black;
78                 string setafcolor; // TODO: use setforeground/setbackground if available for better
79                 string setabcolor; // color mapping.
80                 bool noGetPosition;
81                 Hashtable keymap;
82                 ByteMatcher rootmap;
83                 bool home_1_1; // if true, we have to add 1 to x and y when using cursorAddress
84                 int rl_startx = -1, rl_starty = -1;
85 #if DEBUG
86                 StreamWriter logger;
87 #endif
88
89                 static string SearchTerminfo (string term)
90                 {
91                         if (term == null || term == String.Empty)
92                                 return null;
93
94                         // Ignore TERMINFO and TERMINFO_DIRS by now
95                         //string terminfo = Environment.GetEnvironmentVariable ("TERMINFO");
96                         //string terminfoDirs = Environment.GetEnvironmentVariable ("TERMINFO_DIRS");
97
98                         foreach (string dir in locations) {
99                                 if (!Directory.Exists (dir))
100                                         continue;
101
102                                 string path = Path.Combine (dir, term.Substring (0, 1));
103                                 if (!Directory.Exists (dir))
104                                         continue;
105
106                                 path = Path.Combine (path, term);
107                                 if (!File.Exists (path))
108                                         continue;
109
110                                 return path;
111                         }
112
113                         return null;
114                 }
115
116                 static void WriteConsole (string str)
117                 {
118                         if (str == null)
119                                 return;
120
121                         ((CStreamWriter) Console.stdout).InternalWriteString (str);
122                 }
123
124                 public TermInfoDriver ()
125                         : this (Environment.GetEnvironmentVariable ("TERM"))
126                 {
127                 }
128
129                 public TermInfoDriver (string term)
130                 {
131 #if DEBUG
132                         //File.Delete ("console.log");
133                         logger = new StreamWriter (File.OpenWrite ("console.log"));
134 #endif
135                         this.term = term;
136
137                         if (term == "xterm") {
138                                 reader = new TermInfoReader (term, KnownTerminals.xterm);
139                         } else if (term == "linux") {
140                                 reader = new TermInfoReader (term, KnownTerminals.linux);
141                         } else {
142                                 string filename = SearchTerminfo (term);
143                                 if (filename != null)
144                                         reader = new TermInfoReader (term, filename);
145                         }
146
147                         if (reader == null)
148                                 reader = new TermInfoReader (term, KnownTerminals.ansi);
149                 }
150
151                 public bool Initialized {
152                         get { return inited; }
153                 }
154
155                 public void Init ()
156                 {
157                         if (inited)
158                                 return;
159                         
160                         /* This should not happen any more, since it is checked for in Console */
161                         if (!ConsoleDriver.IsConsole)
162                                 throw new IOException ("Not a tty.");
163
164                         inited = true;
165
166                         ConsoleDriver.SetEcho (false);
167
168                         string endString = null;
169                         keypadXmit = reader.Get (TermInfoStrings.KeypadXmit);
170                         keypadLocal = reader.Get (TermInfoStrings.KeypadLocal);
171                         if (keypadXmit != null) {
172                                 WriteConsole (keypadXmit); // Needed to get the arrows working
173                                 if (keypadLocal != null)
174                                         endString += keypadLocal;
175                         }
176
177                         origPair = reader.Get (TermInfoStrings.OrigPair);
178                         origColors = reader.Get (TermInfoStrings.OrigColors);
179                         setafcolor = MangleParameters (reader.Get (TermInfoStrings.SetAForeground));
180                         setabcolor = MangleParameters (reader.Get (TermInfoStrings.SetABackground));
181                         string resetColors = (origColors == null) ? origPair : origColors;
182                         if (resetColors != null)
183                                 endString += resetColors;
184
185                         if (!ConsoleDriver.TtySetup (endString, out verase, out vsusp, out intr))
186                                 throw new IOException ("Error initializing terminal.");
187
188                         stdin = Console.OpenStandardInput (0);
189                         clear = reader.Get (TermInfoStrings.ClearScreen);
190                         bell = reader.Get (TermInfoStrings.Bell);
191                         if (clear == null) {
192                                 clear = reader.Get (TermInfoStrings.CursorHome);
193                                 clear += reader.Get (TermInfoStrings.ClrEos);
194                         }
195
196                         csrVisible = reader.Get (TermInfoStrings.CursorNormal);
197                         if (csrVisible == null)
198                                 csrVisible = reader.Get (TermInfoStrings.CursorVisible);
199
200                         csrInvisible = reader.Get (TermInfoStrings.CursorInvisible);
201                         if (term == "cygwin" || term == "linux" || (term != null && term.StartsWith ("xterm")) ||
202                                 term == "rxvt" || term == "dtterm") {
203                                 titleFormat = "\x1b]0;{0}\x7"; // icon + window title
204                         } else if (term == "iris-ansi") {
205                                 titleFormat = "\x1bP1.y{0}\x1b\\"; // not tested
206                         } else if (term == "sun-cmd") {
207                                 titleFormat = "\x1b]l{0}\x1b\\"; // not tested
208                         }
209
210                         cursorAddress = reader.Get (TermInfoStrings.CursorAddress);
211                         if (cursorAddress != null) {
212                                 string result = cursorAddress.Replace ("%i", String.Empty);
213                                 home_1_1 = (cursorAddress != result);
214                                 cursorAddress = MangleParameters (result);
215                         }
216
217                         GetCursorPosition ();
218 #if DEBUG
219                         logger.WriteLine ("noGetPosition: {0} left: {1} top: {2}", noGetPosition, cursorLeft, cursorTop);
220                         logger.Flush ();
221 #endif
222                         if (noGetPosition) {
223                                 WriteConsole (clear);
224                                 cursorLeft = 0;
225                                 cursorTop = 0;
226                         }
227                 }
228
229                 static string MangleParameters (string str)
230                 {
231                         if (str == null)
232                                 return null;
233
234                         str = str.Replace ("{", "{{");
235                         str = str.Replace ("}", "}}");
236                         str = str.Replace ("%p1%d", "{0}");
237                         return str.Replace ("%p2%d", "{1}");
238                 }
239
240                 static int TranslateColor (ConsoleColor desired)
241                 {
242                         switch (desired) {
243                         case ConsoleColor.Black:
244                         case ConsoleColor.DarkGray:
245                                 return 0;
246                         case ConsoleColor.DarkBlue:
247                         case ConsoleColor.Blue:
248                                 return 4;
249                         case ConsoleColor.DarkGreen:
250                         case ConsoleColor.Green:
251                                 return 2;
252                         case ConsoleColor.DarkCyan:
253                         case ConsoleColor.Cyan:
254                                 return 6;
255                         case ConsoleColor.DarkRed:
256                         case ConsoleColor.Red:
257                                 return 1;
258                         case ConsoleColor.DarkMagenta:
259                         case ConsoleColor.Magenta:
260                                 return 5;
261                         case ConsoleColor.DarkYellow:
262                         case ConsoleColor.Yellow:
263                                 return 3;
264                         case ConsoleColor.Gray:
265                         case ConsoleColor.White:
266                                 return 7;
267                         }
268
269                         return 0;
270                 }
271
272                 void IncrementX ()
273                 {
274                         cursorLeft++;
275                         if (cursorLeft >= WindowWidth) {
276                                 cursorTop++;
277                                 cursorLeft = 0;
278                                 if (cursorTop >= WindowHeight) {
279                                         // Writing beyond the initial screen
280                                         if (rl_starty != -1) rl_starty--;
281                                         cursorTop--;
282                                 }
283                         }
284                 }
285
286                 // Should never get called unless inited
287                 public void WriteSpecialKey (ConsoleKeyInfo key)
288                 {
289                         switch (key.Key) {
290                         case ConsoleKey.Backspace:
291                                 if (cursorLeft > 0) {
292                                         if (cursorLeft <= rl_startx && cursorTop == rl_starty)
293                                                 break;
294                                         cursorLeft--;
295                                         SetCursorPosition (cursorLeft, cursorTop);
296                                         WriteConsole (" ");
297                                         SetCursorPosition (cursorLeft, cursorTop);
298                                 }
299 #if DEBUG
300                                 logger.WriteLine ("BS left: {0} top: {1}", cursorLeft, cursorTop);
301                                 logger.Flush ();
302 #endif
303                                 break;
304                         case ConsoleKey.Tab:
305                                 int n = 8 - (cursorLeft % 8);
306                                 for (int i = 0; i < n; i++)
307                                         IncrementX ();
308                                 break;
309                         case ConsoleKey.Clear:
310                                 break;
311                         case ConsoleKey.Enter:
312                                 break;
313                         default:
314                                 break;
315                         }
316 #if DEBUG
317                         logger.WriteLine ("left: {0} top: {1}", cursorLeft, cursorTop);
318                         logger.Flush ();
319 #endif
320                 }
321
322                 // Should never get called unless inited
323                 public void WriteSpecialKey (char c)
324                 {
325                         WriteSpecialKey (CreateKeyInfoFromInt (c));
326                 }
327
328                 public bool IsSpecialKey (ConsoleKeyInfo key)
329                 {
330                         if (!inited)
331                                 return false;
332
333                         switch (key.Key) {
334                         case ConsoleKey.Backspace:
335                                 return true;
336                         case ConsoleKey.Tab:
337                                 return true;
338                         case ConsoleKey.Clear:
339                                 return true;
340                         case ConsoleKey.Enter:
341                                 cursorLeft = 0;
342                                 cursorTop++;
343                                 if (cursorTop >= WindowHeight) {
344                                         cursorTop--;
345                                         //TODO: scroll up
346                                 }
347                                 return false;
348                         default:
349                                 // CStreamWriter will handle writing this key
350                                 IncrementX ();
351                                 return false;
352                         }
353                 }
354
355                 public bool IsSpecialKey (char c)
356                 {
357                         return IsSpecialKey (CreateKeyInfoFromInt (c));
358                 }
359
360                 public ConsoleColor BackgroundColor {
361                         get {
362                                 if (!inited) {
363                                         Init ();
364                                 }
365
366                                 return bgcolor;
367                         }
368                         set {
369                                 if (!inited) {
370                                         Init ();
371                                 }
372
373                                 bgcolor = value;
374                                 WriteConsole (String.Format (setabcolor, TranslateColor (value)));
375                         }
376                 }
377
378                 public ConsoleColor ForegroundColor {
379                         get {
380                                 if (!inited) {
381                                         Init ();
382                                 }
383
384                                 return fgcolor;
385                         }
386                         set {
387                                 if (!inited) {
388                                         Init ();
389                                 }
390
391                                 fgcolor = value;
392                                 WriteConsole (String.Format (setafcolor, TranslateColor (value)));
393                         }
394                 }
395
396                 void GetCursorPosition ()
397                 {
398                         int row = 0, col = 0;
399
400                         WriteConsole ("\x1b[6n");
401                         if (ConsoleDriver.InternalKeyAvailable (1000) <= 0) {
402                                 noGetPosition = true;
403                                 return;
404                         }
405
406                         int b = stdin.ReadByte ();
407                         while (b != '\x1b') {
408                                 AddToBuffer (b);
409                                 if (ConsoleDriver.InternalKeyAvailable (100) <= 0)
410                                         return;
411                                 b = stdin.ReadByte ();
412                         }
413
414                         b = stdin.ReadByte ();
415                         if (b != '[') {
416                                 AddToBuffer ('\x1b');
417                                 AddToBuffer (b);
418                                 return;
419                         }
420
421                         b = stdin.ReadByte ();
422                         if (b != ';') {
423                                 row = b - '0';
424                                 b = stdin.ReadByte ();
425                                 while ((b >= '0') && (b <= '9')) {
426                                         row = row * 10 + b - '0';
427                                         b = stdin.ReadByte ();
428                                 }
429                                 // Row/col is 0 based
430                                 row --;
431                         }
432
433                         b = stdin.ReadByte ();
434                         if (b != 'R') {
435                                 col = b - '0';
436                                 b = stdin.ReadByte ();
437                                 while ((b >= '0') && (b <= '9')) {
438                                         col = col * 10 + b - '0';
439                                         b = stdin.ReadByte ();
440                                 }
441                                 // Row/col is 0 based
442                                 col --;
443                         }
444
445 #if DEBUG
446                         logger.WriteLine ("GetCursorPosition: {0}, {1}", col, row);
447                         logger.Flush ();
448 #endif
449
450                         cursorLeft = col;
451                         cursorTop = row;
452                 }
453
454                 public int BufferHeight {
455                         get {
456                                 if (!inited) {
457                                         Init ();
458                                 }
459
460                                 return bufferHeight;
461                         }
462                         set {
463                                 if (!inited) {
464                                         Init ();
465                                 }
466
467                                 throw new NotSupportedException ();
468                         }
469                 }
470
471                 public int BufferWidth {
472                         get {
473                                 if (!inited) {
474                                         Init ();
475                                 }
476
477                                 return bufferWidth;
478                         }
479                         set {
480                                 if (!inited) {
481                                         Init ();
482                                 }
483
484                                 throw new NotSupportedException ();
485                         }
486                 }
487
488                 public bool CapsLock {
489                         get {
490                                 if (!inited) {
491                                         Init ();
492                                 }
493
494                                 throw new NotSupportedException ();
495                         }
496                 }
497
498                 public int CursorLeft {
499                         get {
500                                 if (!inited) {
501                                         Init ();
502                                 }
503
504                                 return cursorLeft;
505                         }
506                         set {
507                                 if (!inited) {
508                                         Init ();
509                                 }
510
511                                 SetCursorPosition (value, CursorTop);
512                         }
513                 }
514
515                 public int CursorTop {
516                         get {
517                                 if (!inited) {
518                                         Init ();
519                                 }
520
521                                 return cursorTop;
522                         }
523                         set {
524                                 if (!inited) {
525                                         Init ();
526                                 }
527
528                                 SetCursorPosition (CursorLeft, value);
529                         }
530                 }
531
532                 public bool CursorVisible {
533                         get {
534                                 if (!inited) {
535                                         Init ();
536                                 }
537
538                                 return cursorVisible;
539                         }
540                         set {
541                                 if (!inited) {
542                                         Init ();
543                                 }
544
545                                 cursorVisible = value;
546                                 WriteConsole ((value ? csrVisible : csrInvisible));
547                         }
548                 }
549
550                 // we have CursorNormal vs. CursorVisible...
551                 [MonoTODO]
552                 public int CursorSize {
553                         get {
554                                 if (!inited) {
555                                         Init ();
556                                 }
557                                 return 1;
558                         }
559                         set {
560                                 if (!inited) {
561                                         Init ();
562                                 }
563                         }
564
565                 }
566
567                 public bool KeyAvailable {
568                         get {
569                                 if (!inited) {
570                                         Init ();
571                                 }
572
573                                 return (writepos > readpos || ConsoleDriver.InternalKeyAvailable (0) > 0);
574                         }
575                 }
576
577                 // We don't know these next 2 values, so return something reasonable
578                 public int LargestWindowHeight {
579                         get { return WindowHeight; }
580                 }
581
582                 public int LargestWindowWidth {
583                         get { return WindowWidth; }
584                 }
585
586                 public bool NumberLock {
587                         get {
588                                 if (!inited) {
589                                         Init ();
590                                 }
591
592                                 throw new NotSupportedException ();
593                         }
594                 }
595
596                 public string Title {
597                         get {
598                                 if (!inited) {
599                                         Init ();
600                                 }
601                                 return title;
602                         }
603                         
604                         set {
605                                 if (!inited) {
606                                         Init ();
607                                 }
608
609                                 title = value;
610                                 WriteConsole (String.Format (titleFormat, value));
611                         }
612                 }
613
614                 public bool TreatControlCAsInput {
615                         get {
616                                 if (!inited) {
617                                         Init ();
618                                 }
619                                 return controlCAsInput;
620                         }
621                         set {
622                                 if (!inited) {
623                                         Init ();
624                                 }
625
626                                 if (controlCAsInput == value)
627                                         return;
628
629                                 ConsoleDriver.SetBreak (value);
630                                 controlCAsInput = value;
631                         }
632                 }
633
634                 void GetWindowDimensions ()
635                 {
636                         /* Try the ioctl first */
637                         if (!ConsoleDriver.GetTtySize (MonoIO.ConsoleOutput, out windowWidth, out windowHeight)) {
638                                 windowWidth = reader.Get (TermInfoNumbers.Columns);
639                                 string env = Environment.GetEnvironmentVariable ("COLUMNS");
640                                 if (env != null) {
641                                         try {
642                                                 windowWidth = (int) UInt32.Parse (env);
643                                         } catch {
644                                         }
645                                 }
646
647                                 windowHeight = reader.Get (TermInfoNumbers.Lines);
648                                 env = Environment.GetEnvironmentVariable ("LINES");
649                                 if (env != null) {
650                                         try {
651                                                 windowHeight = (int) UInt32.Parse (env);
652                                         } catch {
653                                         }
654                                 }
655                         }
656
657                         bufferHeight = windowHeight;
658                         bufferWidth = windowWidth;
659                         need_window_dimensions = false;
660                 }
661
662                 public int WindowHeight {
663                         get {
664                                 if (!inited) {
665                                         Init ();
666                                 }
667
668                                 if (need_window_dimensions)
669                                         GetWindowDimensions ();
670                                 return windowHeight;
671                         }
672                         set {
673                                 if (!inited) {
674                                         Init ();
675                                 }
676
677                                 throw new NotSupportedException ();
678                         }
679                 }
680
681                 public int WindowLeft {
682                         get {
683                                 if (!inited) {
684                                         Init ();
685                                 }
686
687                                 //GetWindowDimensions ();
688                                 return 0;
689                         }
690                         set {
691                                 if (!inited) {
692                                         Init ();
693                                 }
694
695                                 throw new NotSupportedException ();
696                         }
697                 }
698
699                 public int WindowTop {
700                         get {
701                                 if (!inited) {
702                                         Init ();
703                                 }
704
705                                 //GetWindowDimensions ();
706                                 return 0;
707                         }
708                         set {
709                                 if (!inited) {
710                                         Init ();
711                                 }
712
713                                 throw new NotSupportedException ();
714                         }
715                 }
716
717                 public int WindowWidth {
718                         get {
719                                 if (!inited) {
720                                         Init ();
721                                 }
722
723                                 if (need_window_dimensions)
724                                         GetWindowDimensions ();
725                                 return windowWidth;
726                         }
727                         set {
728                                 if (!inited) {
729                                         Init ();
730                                 }
731
732                                 throw new NotSupportedException ();
733                         }
734                 }
735
736                 public void Clear ()
737                 {
738                         if (!inited) {
739                                 Init ();
740                         }
741
742                         WriteConsole (clear);
743                 }
744
745                 public void Beep (int frequency, int duration)
746                 {
747                         if (!inited) {
748                                 Init ();
749                         }
750
751                         WriteConsole (bell);
752                 }
753
754                 public void MoveBufferArea (int sourceLeft, int sourceTop, int sourceWidth, int sourceHeight,
755                                         int targetLeft, int targetTop, Char sourceChar,
756                                         ConsoleColor sourceForeColor, ConsoleColor sourceBackColor)
757                 {
758                         if (!inited) {
759                                 Init ();
760                         }
761
762                         throw new NotImplementedException ();
763                 }
764
765                 void AddToBuffer (int b)
766                 {
767                         if (buffer == null) {
768                                 buffer = new byte [1024];
769                         } else if (writepos >= buffer.Length) {
770                                 byte [] newbuf = new byte [buffer.Length * 2];
771                                 Buffer.BlockCopy (buffer, 0, newbuf, 0, buffer.Length);
772                                 buffer = newbuf;
773                         }
774
775                         buffer [writepos++] = (byte) b;
776                 }
777
778                 void AdjustBuffer ()
779                 {
780                         if (readpos >= writepos) {
781                                 readpos = writepos = 0;
782                         }
783                 }
784
785                 ConsoleKeyInfo CreateKeyInfoFromInt (int n)
786                 {
787                         char c = (char) n;
788                         ConsoleKey key = (ConsoleKey)n;
789                         bool shift = false;
790                         bool ctrl = false;
791                         bool alt = false;
792
793                         if (n == 10) {
794                                 key = ConsoleKey.Enter;
795                         } else if (n == 8 || n == 9 || n == 12 || n == 13 || n == 19) {
796                                 /* Values in ConsoleKey */
797                         } else if (n >= 1 && n <= 26) {
798                                 // For Ctrl-a to Ctrl-z.
799                                 ctrl = true;
800                                 key = ConsoleKey.A + n - 1;
801                         } else if (n == 27) {
802                                 key = ConsoleKey.Escape;
803                         } else if (n >= 'a' && n <= 'z') {
804                                 key = ConsoleKey.A - 'a' + n;
805                         } else if (n >= 'A' && n <= 'Z') {
806                                 shift = true;
807                         } else if (n >= '0' && n <= '9') {
808                         } else {
809                                 key = 0;
810                         }
811
812                         return new ConsoleKeyInfo (c, key, shift, alt, ctrl);
813                 }
814
815                 object GetKeyFromBuffer (bool cooked)
816                 {
817                         if (readpos >= writepos)
818                                 return null;
819
820                         int next = buffer [readpos];
821                         if (!cooked || !rootmap.StartsWith (next)) {
822                                 readpos++;
823                                 AdjustBuffer ();
824                                 return CreateKeyInfoFromInt (next);
825                         }
826
827                         int used;
828                         TermInfoStrings str = rootmap.Match (buffer, readpos, writepos - readpos, out used);
829                         if ((int) str == -1)
830                                 return null;
831
832                         ConsoleKeyInfo key;
833                         if (keymap [str] != null) {
834                                 key = (ConsoleKeyInfo) keymap [str];
835                         } else {
836                                 readpos++;
837                                 AdjustBuffer ();
838                                 return CreateKeyInfoFromInt (next);
839                         }
840
841                         readpos += used;
842                         AdjustBuffer ();
843                         return key;
844                 }
845
846                 ConsoleKeyInfo ReadKeyInternal (out bool fresh)
847                 {
848                         if (!inited)
849                                 Init ();
850
851                         InitKeys ();
852
853                         object o;
854
855                         if ((o = GetKeyFromBuffer (true)) == null) {
856                                 do {
857                                         if (ConsoleDriver.InternalKeyAvailable (150) > 0) {
858                                                 do {
859                                                         AddToBuffer (stdin.ReadByte ());
860                                                 } while (ConsoleDriver.InternalKeyAvailable (0) > 0);
861                                         } else {
862                                                 if ((o = GetKeyFromBuffer (false)) != null)
863                                                         break;
864                                                 
865                                                 AddToBuffer (stdin.ReadByte ());
866                                         }
867                                         
868                                         o = GetKeyFromBuffer (true);
869                                 } while (o == null);
870
871                                 // freshly read character
872                                 fresh = true;
873                         } else {
874                                 // this char was pre-buffered (e.g. not fresh)
875                                 fresh = false;
876                         }
877
878                         return (ConsoleKeyInfo) o;
879                 }
880
881                 public int Read ([In, Out] char [] dest, int index, int count)
882                 {
883                         CStreamWriter writer = Console.stdout as CStreamWriter;
884                         StringBuilder sbuf;
885                         ConsoleKeyInfo key;
886                         int BoL = 0;  // Beginning-of-Line marker (can't backspace beyond this)
887                         bool fresh;
888                         object o;
889                         char c;
890
891                         sbuf = new StringBuilder ();
892
893                         // consume buffered keys first (do not echo, these have already been echo'd)
894                         while (true) {
895                                 if ((o = GetKeyFromBuffer (true)) == null)
896                                         break;
897
898                                 key = (ConsoleKeyInfo) o;
899                                 c = key.KeyChar;
900
901                                 if (key.Key != ConsoleKey.Backspace) {
902                                         if (key.Key == ConsoleKey.Enter)
903                                                 BoL = sbuf.Length;
904
905                                         sbuf.Append (c);
906                                 } else if (sbuf.Length > BoL) {
907                                         sbuf.Length--;
908                                 }
909                         }
910
911                         // continue reading until Enter is hit
912                         rl_startx = cursorLeft;
913                         rl_starty = cursorTop;
914
915                         do {
916                                 key = ReadKeyInternal (out fresh);
917                                 c = key.KeyChar;
918
919                                 if (key.Key != ConsoleKey.Backspace) {
920                                         if (key.Key == ConsoleKey.Enter)
921                                                 BoL = sbuf.Length;
922
923                                         sbuf.Append (c);
924                                 } else if (sbuf.Length > BoL) {
925                                         sbuf.Length--;
926                                 } else {
927                                         continue;
928                                 }
929
930                                 if (fresh) {
931                                         // echo fresh keys back to the console
932                                         if (writer != null)
933                                                 writer.WriteKey (key);
934                                         else
935                                                 Console.stdout.Write (c);
936                                 }
937                         } while (key.Key != ConsoleKey.Enter);
938
939                         rl_startx = -1;
940                         rl_starty = -1;
941
942                         // copy up to count chars into dest
943                         int nread = 0;
944                         while (count > 0 && nread < sbuf.Length) {
945                                 dest[index + nread] = sbuf[nread];
946                                 nread++;
947                                 count--;
948                         }
949
950                         // put the rest back into our key buffer
951                         for (int i = nread; i < sbuf.Length; i++)
952                                 AddToBuffer (sbuf[i]);
953
954                         return nread;
955                 }
956
957                 public ConsoleKeyInfo ReadKey (bool intercept)
958                 {
959                         bool fresh;
960
961                         ConsoleKeyInfo key = ReadKeyInternal (out fresh);
962
963                         if (!intercept && fresh) {
964                                 // echo the fresh key back to the console
965                                 CStreamWriter writer = Console.stdout as CStreamWriter;
966
967                                 if (writer != null)
968                                         writer.WriteKey (key);
969                                 else
970                                         Console.stdout.Write (key.KeyChar);
971                         }
972
973                         return key;
974                 }
975
976                 public string ReadLine ()
977                 {
978                         if (!inited)
979                                 Init ();
980
981                         // Hack to make Iron Python work (since it goes behind our backs
982                         // when writing to the console thus preventing us from keeping
983                         // cursor state normally).
984                         GetCursorPosition ();
985
986                         CStreamWriter writer = Console.stdout as CStreamWriter;
987                         StringBuilder builder = new StringBuilder ();
988                         ConsoleKeyInfo key;
989                         bool fresh;
990                         char c;
991
992                         rl_startx = cursorLeft;
993                         rl_starty = cursorTop;
994
995                         do {
996                                 key = ReadKeyInternal (out fresh);
997                                 c = key.KeyChar;
998                                 if (key.Key != ConsoleKey.Enter) {
999                                         if (key.Key != ConsoleKey.Backspace)
1000                                                 builder.Append (c);
1001                                         else if (builder.Length > 0)
1002                                                 builder.Length--;
1003                                         else {
1004                                                 // skips over echoing the key to the console
1005                                                 continue;
1006                                         }
1007                                 }
1008
1009                                 if (fresh) {
1010                                         // echo fresh keys back to the console
1011                                         if (writer != null)
1012                                                 writer.WriteKey (key);
1013                                         else
1014                                                 Console.stdout.Write (c);
1015                                 }
1016                         } while (key.Key != ConsoleKey.Enter);
1017
1018                         rl_startx = -1;
1019                         rl_starty = -1;
1020
1021                         return builder.ToString ();
1022                 }
1023
1024                 public void ResetColor ()
1025                 {
1026                         if (!inited) {
1027                                 Init ();
1028                         }
1029
1030                         string str = (origPair != null) ? origPair : origColors;
1031                         WriteConsole (str);
1032                 }
1033
1034                 public void SetBufferSize (int width, int height)
1035                 {
1036                         if (!inited) {
1037                                 Init ();
1038                         }
1039
1040                         throw new NotImplementedException (String.Empty);
1041                 }
1042
1043                 public void SetCursorPosition (int left, int top)
1044                 {
1045                         if (!inited) {
1046                                 Init ();
1047                         }
1048
1049                         if (bufferWidth == 0 && need_window_dimensions)
1050                                 GetWindowDimensions ();
1051
1052                         if (left < 0 || left >= bufferWidth)
1053                                 throw new ArgumentOutOfRangeException ("left", "Value must be positive and below the buffer width.");
1054
1055                         if (top < 0 || top >= bufferHeight)
1056                                 throw new ArgumentOutOfRangeException ("top", "Value must be positive and below the buffer height.");
1057
1058                         // Either CursorAddress or nothing.
1059                         // We might want to play with up/down/left/right/home when ca is not available.
1060                         if (cursorAddress == null)
1061                                 throw new NotSupportedException ("This terminal does not suport setting the cursor position.");
1062
1063                         int one = (home_1_1 ? 1 : 0);
1064                         WriteConsole (String.Format (cursorAddress, top + one, left + one));
1065                         cursorLeft = left;
1066                         cursorTop = top;
1067                 }
1068
1069                 public void SetWindowPosition (int left, int top)
1070                 {
1071                         if (!inited) {
1072                                 Init ();
1073                         }
1074
1075                         // No need to throw exceptions here.
1076                         //throw new NotSupportedException ();
1077                 }
1078
1079                 public void SetWindowSize (int width, int height)
1080                 {
1081                         if (!inited) {
1082                                 Init ();
1083                         }
1084
1085                         // No need to throw exceptions here.
1086                         //throw new NotSupportedException ();
1087                 }
1088
1089
1090                 void CreateKeyMap ()
1091                 {
1092                         keymap = new Hashtable ();
1093                         keymap [TermInfoStrings.KeyBackspace] = new ConsoleKeyInfo ('\0', ConsoleKey.Backspace, false, false, false);
1094                         keymap [TermInfoStrings.KeyClear] = new ConsoleKeyInfo ('\0', ConsoleKey.Clear, false, false, false);
1095                         // Delete character...
1096                         keymap [TermInfoStrings.KeyDown] = new ConsoleKeyInfo ('\0', ConsoleKey.DownArrow, false, false, false);
1097                         keymap [TermInfoStrings.KeyF1] = new ConsoleKeyInfo ('\0', ConsoleKey.F1, false, false, false);
1098                         keymap [TermInfoStrings.KeyF10] = new ConsoleKeyInfo ('\0', ConsoleKey.F10, false, false, false);
1099                         keymap [TermInfoStrings.KeyF2] = new ConsoleKeyInfo ('\0', ConsoleKey.F2, false, false, false);
1100                         keymap [TermInfoStrings.KeyF3] = new ConsoleKeyInfo ('\0', ConsoleKey.F3, false, false, false);
1101                         keymap [TermInfoStrings.KeyF4] = new ConsoleKeyInfo ('\0', ConsoleKey.F4, false, false, false);
1102                         keymap [TermInfoStrings.KeyF5] = new ConsoleKeyInfo ('\0', ConsoleKey.F5, false, false, false);
1103                         keymap [TermInfoStrings.KeyF6] = new ConsoleKeyInfo ('\0', ConsoleKey.F6, false, false, false);
1104                         keymap [TermInfoStrings.KeyF7] = new ConsoleKeyInfo ('\0', ConsoleKey.F7, false, false, false);
1105                         keymap [TermInfoStrings.KeyF8] = new ConsoleKeyInfo ('\0', ConsoleKey.F8, false, false, false);
1106                         keymap [TermInfoStrings.KeyF9] = new ConsoleKeyInfo ('\0', ConsoleKey.F9, false, false, false);
1107                         keymap [TermInfoStrings.KeyHome] = new ConsoleKeyInfo ('\0', ConsoleKey.Home, false, false, false);
1108
1109                         keymap [TermInfoStrings.KeyLeft] = new ConsoleKeyInfo ('\0', ConsoleKey.LeftArrow, false, false, false);
1110                         keymap [TermInfoStrings.KeyLl] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad1, false, false, false);
1111                         keymap [TermInfoStrings.KeyNpage] = new ConsoleKeyInfo ('\0', ConsoleKey.PageDown, false, false, false);
1112                         keymap [TermInfoStrings.KeyPpage] = new ConsoleKeyInfo ('\0', ConsoleKey.PageUp, false, false, false);
1113                         keymap [TermInfoStrings.KeyRight] = new ConsoleKeyInfo ('\0', ConsoleKey.RightArrow, false, false, false);
1114                         keymap [TermInfoStrings.KeySf] = new ConsoleKeyInfo ('\0', ConsoleKey.PageDown, false, false, false);
1115                         keymap [TermInfoStrings.KeySr] = new ConsoleKeyInfo ('\0', ConsoleKey.PageUp, false, false, false);
1116                         keymap [TermInfoStrings.KeyUp] = new ConsoleKeyInfo ('\0', ConsoleKey.UpArrow, false, false, false);
1117                         keymap [TermInfoStrings.KeyA1] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad7, false, false, false);
1118                         keymap [TermInfoStrings.KeyA3] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad9, false, false, false);
1119                         keymap [TermInfoStrings.KeyB2] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad5, false, false, false);
1120                         keymap [TermInfoStrings.KeyC1] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad1, false, false, false);
1121                         keymap [TermInfoStrings.KeyC3] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad3, false, false, false);
1122                         keymap [TermInfoStrings.KeyBtab] = new ConsoleKeyInfo ('\0', ConsoleKey.Tab, true, false, false);
1123                         keymap [TermInfoStrings.KeyBeg] = new ConsoleKeyInfo ('\0', ConsoleKey.Home, false, false, false);
1124                         keymap [TermInfoStrings.KeyCopy] = new ConsoleKeyInfo ('C', ConsoleKey.C, false, true, false);
1125                         keymap [TermInfoStrings.KeyEnd] = new ConsoleKeyInfo ('\0', ConsoleKey.End, false, false, false);
1126                         keymap [TermInfoStrings.KeyEnter] = new ConsoleKeyInfo ('\n', ConsoleKey.Enter, false, false, false);
1127                         keymap [TermInfoStrings.KeyHelp] = new ConsoleKeyInfo ('\0', ConsoleKey.Help, false, false, false);
1128                         keymap [TermInfoStrings.KeyPrint] = new ConsoleKeyInfo ('\0', ConsoleKey.Print, false, false, false);
1129                         keymap [TermInfoStrings.KeyUndo] = new ConsoleKeyInfo ('Z', ConsoleKey.Z , false, true, false);
1130                         keymap [TermInfoStrings.KeySbeg] = new ConsoleKeyInfo ('\0', ConsoleKey.Home, true, false, false);
1131                         keymap [TermInfoStrings.KeyScopy] = new ConsoleKeyInfo ('C', ConsoleKey.C , true, true, false);
1132                         keymap [TermInfoStrings.KeySdc] = new ConsoleKeyInfo ('\x9', ConsoleKey.Delete, true, false, false);
1133                         keymap [TermInfoStrings.KeyShelp] = new ConsoleKeyInfo ('\0', ConsoleKey.Help, true, false, false);
1134                         keymap [TermInfoStrings.KeyShome] = new ConsoleKeyInfo ('\0', ConsoleKey.Home, true, false, false);
1135                         keymap [TermInfoStrings.KeySleft] = new ConsoleKeyInfo ('\0', ConsoleKey.LeftArrow, true, false, false);
1136                         keymap [TermInfoStrings.KeySprint] = new ConsoleKeyInfo ('\0', ConsoleKey.Print, true, false, false);
1137                         keymap [TermInfoStrings.KeySright] = new ConsoleKeyInfo ('\0', ConsoleKey.RightArrow, true, false, false);
1138                         keymap [TermInfoStrings.KeySundo] = new ConsoleKeyInfo ('Z', ConsoleKey.Z, true, false, false);
1139                         keymap [TermInfoStrings.KeyF11] = new ConsoleKeyInfo ('\0', ConsoleKey.F11, false, false, false);
1140                         keymap [TermInfoStrings.KeyF12] = new ConsoleKeyInfo ('\0', ConsoleKey.F12 , false, false, false);
1141                         keymap [TermInfoStrings.KeyF13] = new ConsoleKeyInfo ('\0', ConsoleKey.F13, false, false, false);
1142                         keymap [TermInfoStrings.KeyF14] = new ConsoleKeyInfo ('\0', ConsoleKey.F14, false, false, false);
1143                         keymap [TermInfoStrings.KeyF15] = new ConsoleKeyInfo ('\0', ConsoleKey.F15, false, false, false);
1144                         keymap [TermInfoStrings.KeyF16] = new ConsoleKeyInfo ('\0', ConsoleKey.F16, false, false, false);
1145                         keymap [TermInfoStrings.KeyF17] = new ConsoleKeyInfo ('\0', ConsoleKey.F17, false, false, false);
1146                         keymap [TermInfoStrings.KeyF18] = new ConsoleKeyInfo ('\0', ConsoleKey.F18, false, false, false);
1147                         keymap [TermInfoStrings.KeyF19] = new ConsoleKeyInfo ('\0', ConsoleKey.F19, false, false, false);
1148                         keymap [TermInfoStrings.KeyF20] = new ConsoleKeyInfo ('\0', ConsoleKey.F20, false, false, false);
1149                         keymap [TermInfoStrings.KeyF21] = new ConsoleKeyInfo ('\0', ConsoleKey.F21, false, false, false);
1150                         keymap [TermInfoStrings.KeyF22] = new ConsoleKeyInfo ('\0', ConsoleKey.F22, false, false, false);
1151                         keymap [TermInfoStrings.KeyF23] = new ConsoleKeyInfo ('\0', ConsoleKey.F23, false, false, false);
1152                         keymap [TermInfoStrings.KeyF24] = new ConsoleKeyInfo ('\0', ConsoleKey.F24, false, false, false);
1153                 }
1154
1155                 void InitKeys ()
1156                 {
1157                         if (initKeys)
1158                                 return;
1159
1160                         CreateKeyMap ();
1161                         rootmap = new ByteMatcher ();
1162                         AddStringMapping (TermInfoStrings.KeyBackspace);
1163                         AddStringMapping (TermInfoStrings.KeyClear);
1164                         AddStringMapping (TermInfoStrings.KeyDown);
1165                         AddStringMapping (TermInfoStrings.KeyF1);
1166                         AddStringMapping (TermInfoStrings.KeyF10);
1167                         AddStringMapping (TermInfoStrings.KeyF2);
1168                         AddStringMapping (TermInfoStrings.KeyF3);
1169                         AddStringMapping (TermInfoStrings.KeyF4);
1170                         AddStringMapping (TermInfoStrings.KeyF5);
1171                         AddStringMapping (TermInfoStrings.KeyF6);
1172                         AddStringMapping (TermInfoStrings.KeyF7);
1173                         AddStringMapping (TermInfoStrings.KeyF8);
1174                         AddStringMapping (TermInfoStrings.KeyF9);
1175                         AddStringMapping (TermInfoStrings.KeyHome);
1176                         AddStringMapping (TermInfoStrings.KeyLeft);
1177                         AddStringMapping (TermInfoStrings.KeyLl);
1178                         AddStringMapping (TermInfoStrings.KeyNpage);
1179                         AddStringMapping (TermInfoStrings.KeyPpage);
1180                         AddStringMapping (TermInfoStrings.KeyRight);
1181                         AddStringMapping (TermInfoStrings.KeySf);
1182                         AddStringMapping (TermInfoStrings.KeySr);
1183                         AddStringMapping (TermInfoStrings.KeyUp);
1184                         AddStringMapping (TermInfoStrings.KeyA1);
1185                         AddStringMapping (TermInfoStrings.KeyA3);
1186                         AddStringMapping (TermInfoStrings.KeyB2);
1187                         AddStringMapping (TermInfoStrings.KeyC1);
1188                         AddStringMapping (TermInfoStrings.KeyC3);
1189                         AddStringMapping (TermInfoStrings.KeyBtab);
1190                         AddStringMapping (TermInfoStrings.KeyBeg);
1191                         AddStringMapping (TermInfoStrings.KeyCopy);
1192                         AddStringMapping (TermInfoStrings.KeyEnd);
1193                         AddStringMapping (TermInfoStrings.KeyEnter);
1194                         AddStringMapping (TermInfoStrings.KeyHelp);
1195                         AddStringMapping (TermInfoStrings.KeyPrint);
1196                         AddStringMapping (TermInfoStrings.KeyUndo);
1197                         AddStringMapping (TermInfoStrings.KeySbeg);
1198                         AddStringMapping (TermInfoStrings.KeyScopy);
1199                         AddStringMapping (TermInfoStrings.KeySdc);
1200                         AddStringMapping (TermInfoStrings.KeyShelp);
1201                         AddStringMapping (TermInfoStrings.KeyShome);
1202                         AddStringMapping (TermInfoStrings.KeySleft);
1203                         AddStringMapping (TermInfoStrings.KeySprint);
1204                         AddStringMapping (TermInfoStrings.KeySright);
1205                         AddStringMapping (TermInfoStrings.KeySundo);
1206                         AddStringMapping (TermInfoStrings.KeyF11);
1207                         AddStringMapping (TermInfoStrings.KeyF12);
1208                         AddStringMapping (TermInfoStrings.KeyF13);
1209                         AddStringMapping (TermInfoStrings.KeyF14);
1210                         AddStringMapping (TermInfoStrings.KeyF15);
1211                         AddStringMapping (TermInfoStrings.KeyF16);
1212                         AddStringMapping (TermInfoStrings.KeyF17);
1213                         AddStringMapping (TermInfoStrings.KeyF18);
1214                         AddStringMapping (TermInfoStrings.KeyF19);
1215                         AddStringMapping (TermInfoStrings.KeyF20);
1216                         AddStringMapping (TermInfoStrings.KeyF21);
1217                         AddStringMapping (TermInfoStrings.KeyF22);
1218                         AddStringMapping (TermInfoStrings.KeyF23);
1219                         AddStringMapping (TermInfoStrings.KeyF24);
1220                         rootmap.Sort ();
1221                         initKeys = true;
1222                 }
1223
1224                 void AddStringMapping (TermInfoStrings s)
1225                 {
1226                         byte [] bytes = reader.GetStringBytes (s);
1227                         if (bytes == null)
1228                                 return;
1229
1230                         rootmap.AddMapping (s, bytes);
1231                 }
1232         }
1233
1234         class ByteMatcher {
1235                 Hashtable map = new Hashtable ();
1236                 Hashtable starts = new Hashtable ();
1237
1238                 public void AddMapping (TermInfoStrings key, byte [] val)
1239                 {
1240                         if (val.Length == 0)
1241                                 return;
1242
1243                         map [val] = key;
1244                         starts [(int) val [0]] = true;
1245                 }
1246
1247                 public void Sort ()
1248                 {
1249                 }
1250
1251                 public bool StartsWith (int c)
1252                 {
1253                         return (starts [c] != null);
1254                 }
1255
1256                 public TermInfoStrings Match (byte [] buffer, int offset, int length, out int used)
1257                 {
1258                         foreach (byte [] bytes in map.Keys) {
1259                                 for (int i = 0; i < bytes.Length && i < length; i++) {
1260                                         if (bytes [i] != buffer [offset + i])
1261                                                 break;
1262
1263                                         if (bytes.Length - 1 == i) {
1264                                                 used = bytes.Length;
1265                                                 return (TermInfoStrings) map [bytes];
1266                                         }
1267                                 }
1268                         }
1269
1270                         used = 0;
1271                         return (TermInfoStrings) (-1);
1272                 }
1273         }
1274 }
1275 #endif
1276