2 // System.ConsoleDriver
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 // (C) 2005,2006 Novell, Inc (http://www.novell.com)
8 // Copyright (c) Microsoft.
9 // Copyright 2014 Xamarin Inc
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 // This code contains the ParameterizedStrings implementation from .NET's
32 // Core System.Console:
33 // https://github.com/dotnet/corefx
34 // src/System.Console/src/System/ConsolePal.Unix.cs
39 // Defining this writes the output to console.log
42 using System.Collections;
45 using System.Runtime.InteropServices;
47 class TermInfoDriver : IConsoleDriver {
49 // This points to a variable that is updated by unmanage code on window size changes.
50 unsafe static int *native_terminal_size;
52 // The current size that we believe we have
53 static int terminal_size;
55 //static uint flag = 0xdeadbeef;
56 readonly static string [] locations = { "/usr/share/terminfo", "/etc/terminfo", "/usr/lib/terminfo", "/lib/terminfo" };
58 TermInfoReader reader;
61 string title = String.Empty;
62 string titleFormat = String.Empty;
63 bool cursorVisible = true;
82 string keypadXmit, keypadLocal;
85 object initLock = new object ();
90 ConsoleColor fgcolor = ConsoleColor.White;
91 ConsoleColor bgcolor = ConsoleColor.Black;
98 int rl_startx = -1, rl_starty = -1;
99 byte [] control_characters; // Indexed by ControlCharacters.XXXXXX
104 static string TryTermInfoDir (string dir, string term )
106 string path = String.Format ("{0}/{1:x}/{2}", dir, (int)(term [0]), term);
107 if (File.Exists (path))
110 path = Path.Combine (dir, term.Substring (0, 1), term);
111 if (File.Exists (path))
116 static string SearchTerminfo (string term)
118 if (term == null || term == String.Empty)
122 string terminfo = Environment.GetEnvironmentVariable ("TERMINFO");
123 if (terminfo != null && Directory.Exists (terminfo)){
124 path = TryTermInfoDir (terminfo, term);
129 foreach (string dir in locations) {
130 if (!Directory.Exists (dir))
133 path = TryTermInfoDir (dir, term);
141 void WriteConsole (string str)
146 stdout.InternalWriteString (str);
149 public TermInfoDriver ()
150 : this (Environment.GetEnvironmentVariable ("TERM"))
154 public TermInfoDriver (string term)
157 File.Delete ("console.log");
158 logger = new StreamWriter (File.OpenWrite ("console.log"));
162 string filename = SearchTerminfo (term);
163 if (filename != null)
164 reader = new TermInfoReader (term, filename);
167 if (term == "xterm") {
168 reader = new TermInfoReader (term, KnownTerminals.xterm);
169 } else if (term == "linux") {
170 reader = new TermInfoReader (term, KnownTerminals.linux);
175 reader = new TermInfoReader (term, KnownTerminals.ansi);
177 if (!(Console.stdout is CStreamWriter)) {
178 // Application set its own stdout, we need a reference to the real stdout
179 stdout = new CStreamWriter (Console.OpenStandardOutput (0), Console.OutputEncoding, false);
180 ((StreamWriter) stdout).AutoFlush = true;
182 stdout = (CStreamWriter) Console.stdout;
186 public bool Initialized {
187 get { return inited; }
200 /* This should not happen any more, since it is checked for in Console */
201 if (!ConsoleDriver.IsConsole)
202 throw new IOException ("Not a tty.");
204 ConsoleDriver.SetEcho (false);
206 string endString = null;
207 keypadXmit = reader.Get (TermInfoStrings.KeypadXmit);
208 keypadLocal = reader.Get (TermInfoStrings.KeypadLocal);
209 if (keypadXmit != null) {
210 WriteConsole (keypadXmit); // Needed to get the arrows working
211 if (keypadLocal != null)
212 endString += keypadLocal;
215 origPair = reader.Get (TermInfoStrings.OrigPair);
216 origColors = reader.Get (TermInfoStrings.OrigColors);
217 setfgcolor = reader.Get (TermInfoStrings.SetAForeground);
218 setbgcolor = reader.Get (TermInfoStrings.SetABackground);
219 maxColors = reader.Get (TermInfoNumbers.MaxColors);
220 maxColors = Math.Max (Math.Min (maxColors, 16), 1);
222 string resetColors = (origColors == null) ? origPair : origColors;
223 if (resetColors != null)
224 endString += resetColors;
227 if (!ConsoleDriver.TtySetup (keypadXmit, endString, out control_characters, out native_terminal_size)){
228 control_characters = new byte [17];
229 native_terminal_size = null;
230 //throw new IOException ("Error initializing terminal.");
234 stdin = new StreamReader (Console.OpenStandardInput (0), Console.InputEncoding);
235 clear = reader.Get (TermInfoStrings.ClearScreen);
236 bell = reader.Get (TermInfoStrings.Bell);
238 clear = reader.Get (TermInfoStrings.CursorHome);
239 clear += reader.Get (TermInfoStrings.ClrEos);
242 csrVisible = reader.Get (TermInfoStrings.CursorNormal);
243 if (csrVisible == null)
244 csrVisible = reader.Get (TermInfoStrings.CursorVisible);
246 csrInvisible = reader.Get (TermInfoStrings.CursorInvisible);
247 if (term == "cygwin" || term == "linux" || (term != null && term.StartsWith ("xterm")) ||
248 term == "rxvt" || term == "dtterm") {
249 titleFormat = "\x1b]0;{0}\x7"; // icon + window title
250 } else if (term == "iris-ansi") {
251 titleFormat = "\x1bP1.y{0}\x1b\\"; // not tested
252 } else if (term == "sun-cmd") {
253 titleFormat = "\x1b]l{0}\x1b\\"; // not tested
256 cursorAddress = reader.Get (TermInfoStrings.CursorAddress);
258 GetCursorPosition ();
260 logger.WriteLine ("noGetPosition: {0} left: {1} top: {2}", noGetPosition, cursorLeft, cursorTop);
264 WriteConsole (clear);
274 if (cursorLeft >= WindowWidth) {
277 if (cursorTop >= WindowHeight) {
278 // Writing beyond the initial screen
279 if (rl_starty != -1) rl_starty--;
285 // Should never get called unless inited
286 public void WriteSpecialKey (ConsoleKeyInfo key)
289 case ConsoleKey.Backspace:
290 if (cursorLeft > 0) {
291 if (cursorLeft <= rl_startx && cursorTop == rl_starty)
294 SetCursorPosition (cursorLeft, cursorTop);
296 SetCursorPosition (cursorLeft, cursorTop);
299 logger.WriteLine ("BS left: {0} top: {1}", cursorLeft, cursorTop);
304 int n = 8 - (cursorLeft % 8);
305 for (int i = 0; i < n; i++){
310 case ConsoleKey.Clear:
311 WriteConsole (clear);
315 case ConsoleKey.Enter:
321 logger.WriteLine ("left: {0} top: {1}", cursorLeft, cursorTop);
326 // Should never get called unless inited
327 public void WriteSpecialKey (char c)
329 WriteSpecialKey (CreateKeyInfoFromInt (c, false));
332 public bool IsSpecialKey (ConsoleKeyInfo key)
338 case ConsoleKey.Backspace:
342 case ConsoleKey.Clear:
344 case ConsoleKey.Enter:
347 if (cursorTop >= WindowHeight) {
353 // CStreamWriter will handle writing this key
359 public bool IsSpecialKey (char c)
361 return IsSpecialKey (CreateKeyInfoFromInt (c, false));
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
369 private static readonly int[] _consoleColorToAnsiCode = new int[]
371 // Dark/Normal colors
392 void ChangeColor (string format, ConsoleColor color)
394 int ccValue = (int)color;
395 if ((ccValue & ~0xF) != 0)
396 throw new ArgumentException("Invalid Console Color");
398 int ansiCode = _consoleColorToAnsiCode[ccValue] % maxColors;
400 WriteConsole (ParameterizedStrings.Evaluate (format, ansiCode));
403 public ConsoleColor BackgroundColor {
415 ChangeColor (setbgcolor, value);
420 public ConsoleColor ForegroundColor {
432 ChangeColor (setfgcolor, value);
437 void GetCursorPosition ()
439 int row = 0, col = 0;
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){
449 // Then try to probe for the cursor coordinates
450 WriteConsole ("\x1b[6n");
451 if (ConsoleDriver.InternalKeyAvailable (1000) <= 0) {
452 noGetPosition = true;
457 while (b != '\x1b') {
459 if (ConsoleDriver.InternalKeyAvailable (100) <= 0)
466 AddToBuffer ('\x1b');
475 while ((b >= '0') && (b <= '9')) {
476 row = row * 10 + b - '0';
479 // Row/col is 0 based
487 while ((b >= '0') && (b <= '9')) {
488 col = col * 10 + b - '0';
491 // Row/col is 0 based
496 logger.WriteLine ("GetCursorPosition: {0}, {1}", col, row);
504 public int BufferHeight {
510 CheckWindowDimensions ();
518 throw new NotSupportedException ();
522 public int BufferWidth {
528 CheckWindowDimensions ();
536 throw new NotSupportedException ();
540 public bool CapsLock {
549 public int CursorLeft {
562 SetCursorPosition (value, CursorTop);
566 public int CursorTop {
579 SetCursorPosition (CursorLeft, value);
583 public bool CursorVisible {
589 return cursorVisible;
596 cursorVisible = value;
597 WriteConsole ((value ? csrVisible : csrInvisible));
601 // we have CursorNormal vs. CursorVisible...
603 public int CursorSize {
618 public bool KeyAvailable {
624 return (writepos > readpos || ConsoleDriver.InternalKeyAvailable (0) > 0);
628 // We don't know these next 2 values, so return something reasonable
629 public int LargestWindowHeight {
630 get { return WindowHeight; }
633 public int LargestWindowWidth {
634 get { return WindowWidth; }
637 public bool NumberLock {
647 public string Title {
661 WriteConsole (String.Format (titleFormat, value));
665 public bool TreatControlCAsInput {
670 return controlCAsInput;
677 if (controlCAsInput == value)
680 ConsoleDriver.SetBreak (value);
681 controlCAsInput = value;
686 // Requries that caller calls Init () if not !inited.
688 unsafe void CheckWindowDimensions ()
690 if (native_terminal_size == null || terminal_size == *native_terminal_size)
693 if (*native_terminal_size == -1){
694 int c = reader.Get (TermInfoNumbers.Columns);
698 c = reader.Get (TermInfoNumbers.Lines);
702 terminal_size = *native_terminal_size;
703 windowWidth = terminal_size >> 16;
704 windowHeight = terminal_size & 0xffff;
706 bufferHeight = windowHeight;
707 bufferWidth = windowWidth;
711 public int WindowHeight {
717 CheckWindowDimensions ();
725 throw new NotSupportedException ();
729 public int WindowLeft {
735 //CheckWindowDimensions ();
743 throw new NotSupportedException ();
747 public int WindowTop {
753 //CheckWindowDimensions ();
761 throw new NotSupportedException ();
765 public int WindowWidth {
771 CheckWindowDimensions ();
779 throw new NotSupportedException ();
789 WriteConsole (clear);
794 public void Beep (int frequency, int duration)
803 public void MoveBufferArea (int sourceLeft, int sourceTop, int sourceWidth, int sourceHeight,
804 int targetLeft, int targetTop, Char sourceChar,
805 ConsoleColor sourceForeColor, ConsoleColor sourceBackColor)
811 throw new NotImplementedException ();
814 void AddToBuffer (int b)
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);
824 buffer [writepos++] = (char) b;
829 if (readpos >= writepos) {
830 readpos = writepos = 0;
834 ConsoleKeyInfo CreateKeyInfoFromInt (int n, bool alt)
837 ConsoleKey key = (ConsoleKey)n;
843 key = ConsoleKey.Enter;
846 key = ConsoleKey.Spacebar;
849 key = ConsoleKey.Subtract;
852 key = ConsoleKey.Add;
855 key = ConsoleKey.Divide;
858 key = ConsoleKey.Multiply;
860 case 8: case 9: case 12: case 13: case 19:
861 /* Values in ConsoleKey */
864 key = ConsoleKey.Escape;
868 if (n >= 1 && n <= 26) {
869 // For Ctrl-a to Ctrl-z.
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') {
876 } else if (n >= '0' && n <= '9') {
882 return new ConsoleKeyInfo (c, key, shift, alt, ctrl);
885 object GetKeyFromBuffer (bool cooked)
887 if (readpos >= writepos)
890 int next = buffer [readpos];
891 if (!cooked || !rootmap.StartsWith (next)) {
894 return CreateKeyInfoFromInt (next, false);
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){
904 if (buffer [readpos+1] == 127)
905 return new ConsoleKeyInfo ((char)8, ConsoleKey.Backspace, false, true, false);
906 return CreateKeyInfoFromInt (buffer [readpos+1], true);
912 if (keymap [str] != null) {
913 key = (ConsoleKeyInfo) keymap [str];
917 return CreateKeyInfoFromInt (next, false);
925 ConsoleKeyInfo ReadKeyInternal (out bool fresh)
934 if ((o = GetKeyFromBuffer (true)) == null) {
936 if (ConsoleDriver.InternalKeyAvailable (150) > 0) {
938 AddToBuffer (stdin.Read ());
939 } while (ConsoleDriver.InternalKeyAvailable (0) > 0);
940 } else if (stdin.DataAvailable ()) {
942 AddToBuffer (stdin.Read ());
943 } while (stdin.DataAvailable ());
945 if ((o = GetKeyFromBuffer (false)) != null)
948 AddToBuffer (stdin.Read ());
951 o = GetKeyFromBuffer (true);
954 // freshly read character
957 // this char was pre-buffered (e.g. not fresh)
961 return (ConsoleKeyInfo) o;
964 #region Input echoing optimization
967 // check if we've got pending input we can read immediately
968 return readpos < writepos || stdin.DataAvailable ();
971 char [] echobuf = null;
974 // Queues a character to be echo'd back to the console
975 void QueueEcho (char c)
978 echobuf = new char [1024];
980 echobuf[echon++] = c;
982 if (echon == echobuf.Length || !InputPending ()) {
983 // blit our echo buffer to the console
984 stdout.InternalWriteChars (echobuf, echon);
989 // Queues a key to be echo'd back to the console
990 void Echo (ConsoleKeyInfo key)
992 if (!IsSpecialKey (key)) {
993 QueueEcho (key.KeyChar);
997 // flush pending echo's
1000 WriteSpecialKey (key);
1003 // Flush the pending echo queue
1009 // flush our echo buffer to the console
1010 stdout.InternalWriteChars (echobuf, echon);
1015 public int Read ([In, Out] char [] dest, int index, int count)
1017 bool fresh, echo = false;
1020 int BoL = 0; // Beginning-of-Line marker (can't backspace beyond this)
1024 sbuf = new StringBuilder ();
1026 // consume buffered keys first (do not echo, these have already been echo'd)
1028 if ((o = GetKeyFromBuffer (true)) == null)
1031 key = (ConsoleKeyInfo) o;
1034 if (key.Key != ConsoleKey.Backspace) {
1035 if (key.Key == ConsoleKey.Enter)
1039 } else if (sbuf.Length > BoL) {
1044 // continue reading until Enter is hit
1045 rl_startx = cursorLeft;
1046 rl_starty = cursorTop;
1049 key = ReadKeyInternal (out fresh);
1050 echo = echo || fresh;
1053 if (key.Key != ConsoleKey.Backspace) {
1054 if (key.Key == ConsoleKey.Enter)
1058 } else if (sbuf.Length > BoL) {
1064 // echo fresh keys back to the console
1067 } while (key.Key != ConsoleKey.Enter);
1074 // copy up to count chars into dest
1076 while (count > 0 && nread < sbuf.Length) {
1077 dest[index + nread] = sbuf[nread];
1082 // put the rest back into our key buffer
1083 for (int i = nread; i < sbuf.Length; i++)
1084 AddToBuffer (sbuf[i]);
1089 public ConsoleKeyInfo ReadKey (bool intercept)
1093 ConsoleKeyInfo key = ReadKeyInternal (out fresh);
1095 if (!intercept && fresh) {
1096 // echo the fresh key back to the console
1104 public string ReadLine ()
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 ();
1114 StringBuilder builder = new StringBuilder ();
1115 bool fresh, echo = false;
1119 rl_startx = cursorLeft;
1120 rl_starty = cursorTop;
1121 char eof = (char) control_characters [ControlCharacters.EOF];
1124 key = ReadKeyInternal (out fresh);
1125 echo = echo || fresh;
1127 // EOF -> Ctrl-D (EOT) pressed.
1128 if (c == eof && c != 0 && builder.Length == 0)
1131 if (key.Key != ConsoleKey.Enter) {
1132 if (key.Key != ConsoleKey.Backspace) {
1134 } else if (builder.Length > 0) {
1137 // skips over echoing the key to the console
1142 // echo fresh keys back to the console
1145 } while (key.Key != ConsoleKey.Enter);
1152 return builder.ToString ();
1155 public void ResetColor ()
1161 string str = (origPair != null) ? origPair : origColors;
1165 public void SetBufferSize (int width, int height)
1171 throw new NotImplementedException (String.Empty);
1174 public void SetCursorPosition (int left, int top)
1180 CheckWindowDimensions ();
1181 if (left < 0 || left >= bufferWidth)
1182 throw new ArgumentOutOfRangeException ("left", "Value must be positive and below the buffer width.");
1184 if (top < 0 || top >= bufferHeight)
1185 throw new ArgumentOutOfRangeException ("top", "Value must be positive and below the buffer height.");
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.");
1192 WriteConsole (ParameterizedStrings.Evaluate (cursorAddress, top, left));
1197 public void SetWindowPosition (int left, int top)
1203 // No need to throw exceptions here.
1204 //throw new NotSupportedException ();
1207 public void SetWindowSize (int width, int height)
1213 // No need to throw exceptions here.
1214 //throw new NotSupportedException ();
1218 void CreateKeyMap ()
1220 keymap = new Hashtable ();
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);
1292 rootmap = new ByteMatcher ();
1295 // The keys that we know about and use
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,
1357 // These were missing
1358 TermInfoStrings.KeyDc,
1359 TermInfoStrings.KeyIc
1362 foreach (TermInfoStrings tis in UsedKeys)
1363 AddStringMapping (tis);
1365 rootmap.AddMapping (TermInfoStrings.KeyBackspace, new byte [] { control_characters [ControlCharacters.Erase] });
1370 void AddStringMapping (TermInfoStrings s)
1372 byte [] bytes = reader.GetStringBytes (s);
1376 rootmap.AddMapping (s, bytes);
1380 /// <summary>Provides support for evaluating parameterized terminfo database format strings.</summary>
1381 internal static class ParameterizedStrings
1383 /// <summary>A cached stack to use to avoid allocating a new stack object for every evaluation.</summary>
1385 private static LowLevelStack _cachedStack;
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)
1394 throw new ArgumentNullException("format");
1396 throw new ArgumentNullException("args");
1398 // Initialize the stack to use for processing.
1399 LowLevelStack stack = _cachedStack;
1401 _cachedStack = stack = new LowLevelStack();
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;
1411 return EvaluateInternal(format, ref pos, args, stack, ref dynamicVars, ref staticVars);
1413 // EvaluateInternal may throw IndexOutOfRangeException and InvalidOperationException
1414 // if the format string is malformed or if it's inconsistent with the parameters provided.
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>
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.
1429 private static string EvaluateInternal(
1430 string format, ref int pos, FormatParam[] args, LowLevelStack stack,
1431 ref FormatParam[] dynamicVars, ref FormatParam[] staticVars)
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);
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;
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]);
1454 // We have a special parameter sequence to process. Now we need
1455 // to look at what comes after the '%'.
1457 switch (format[pos]) {
1458 // Output appending operations
1459 case '%': // Output the escaped '%'
1462 case 'c': // Pop the stack and output it as a char
1463 output.Append((char)stack.Pop().Int32);
1465 case 's': // Pop the stack and output it as a string
1466 output.Append(stack.Pop().String);
1468 case 'd': // Pop the stack and output it as an integer
1469 output.Append(stack.Pop().Int32);
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
1492 char ec = format[printfEnd];
1493 if (ec == 'd' || ec == 'o' || ec == 'x' || ec == 'X' || ec == 's')
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
1506 // Stack pushing operations
1507 case 'p': // Push the specified parameter (1-based) onto the stack
1509 stack.Push(args[format[pos] - '1']);
1511 case 'l': // Pop a string and push its length
1512 stack.Push(stack.Pop().String.Length);
1514 case '{': // Push integer literal, enclosed between braces
1517 while (format[pos] != '}')
1519 intLit = (intLit * 10) + (format[pos] - '0');
1524 case '\'': // Push literal character, enclosed between single quotes
1525 stack.Push((int)format[pos + 1]);
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
1533 FormatParam[] targetVars = GetDynamicOrStaticVariables(format[pos], ref dynamicVars, ref staticVars, out setIndex);
1534 targetVars[setIndex] = stack.Pop();
1536 case 'g': // Push a static or dynamic variable; which is based on whether the a-z variable is capitalized
1539 FormatParam[] sourceVars = GetDynamicOrStaticVariables(format[pos], ref dynamicVars, ref staticVars, out getIndex);
1540 stack.Push(sourceVars[getIndex]);
1543 // Binary operations
1549 case '^': // arithmetic
1551 case '|': // bitwise
1554 case '<': // comparison
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 char c = format[pos];
1561 c == '+' ? (first + second) :
1562 c == '-' ? (first - second) :
1563 c == '*' ? (first * second) :
1564 c == '/' ? (first / second) :
1565 c == 'm' ? (first % second) :
1566 c == '^' ? (first ^ second) :
1567 c == '&' ? (first & second) :
1568 c == '|' ? (first | second) :
1569 c == '=' ? AsInt(first == second) :
1570 c == '>' ? AsInt(first > second) :
1571 c == '<' ? AsInt(first < second) :
1572 c == 'A' ? AsInt(AsBool(first) && AsBool(second)) :
1573 c == 'O' ? AsInt(AsBool(first) || AsBool(second)) :
1574 0); // not possible; we just validated above
1580 int value = stack.Pop().Int32;
1582 format[pos] == '!' ? AsInt(!AsBool(value)) :
1586 // Augment first two parameters by 1
1588 args[0] = 1 + args[0].Int32;
1589 args[1] = 1 + args[1].Int32;
1592 // Conditional of the form %? if-part %t then-part %e else-part %;
1593 // The "%e else-part" is optional.
1595 sawIfConditional = true;
1598 // We hit the end of the if-part and are about to start the then-part.
1599 // The if-part left its result on the stack; pop and evaluate.
1600 bool conditionalResult = AsBool(stack.Pop().Int32);
1602 // Regardless of whether it's true, run the then-part to get past it.
1603 // If the conditional was true, output the then results.
1605 string thenResult = EvaluateInternal(format, ref pos, args, stack, ref dynamicVars, ref staticVars);
1606 if (conditionalResult)
1608 output.Append(thenResult);
1611 // We're past the then; the top of the stack should now be a Boolean
1612 // indicating whether this conditional has more to be processed (an else clause).
1613 if (!AsBool(stack.Pop().Int32))
1615 // Process the else clause, and if the conditional was false, output the else results.
1617 string elseResult = EvaluateInternal(format, ref pos, args, stack, ref dynamicVars, ref staticVars);
1618 if (!conditionalResult)
1620 output.Append(elseResult);
1622 // Now we should be done (any subsequent elseif logic will have bene handled in the recursive call).
1623 if (!AsBool(stack.Pop().Int32))
1625 throw new InvalidOperationException("Terminfo database contains invalid values");
1629 // If we're in a nested processing, return to our parent.
1630 if (!sawIfConditional)
1633 return output.ToString();
1635 // Otherwise, we're done processing the conditional in its entirety.
1636 sawIfConditional = false;
1640 // Let our caller know why we're exiting, whether due to the end of the conditional or an else branch.
1641 stack.Push(AsInt(format[pos] == ';'));
1642 return output.ToString();
1644 // Anything else is an error
1646 throw new InvalidOperationException("Terminfo database contains invalid values");
1650 return output.ToString();
1653 /// <summary>Converts an Int32 to a Boolean, with 0 meaning false and all non-zero values meaning true.</summary>
1654 /// <param name="i">The integer value to convert.</param>
1655 /// <returns>true if the integer was non-zero; otherwise, false.</returns>
1656 static bool AsBool(Int32 i) { return i != 0; }
1658 /// <summary>Converts a Boolean to an Int32, with true meaning 1 and false meaning 0.</summary>
1659 /// <param name="b">The Boolean value to convert.</param>
1660 /// <returns>1 if the Boolean is true; otherwise, 0.</returns>
1661 static int AsInt(bool b) { return b ? 1 : 0; }
1663 static string StringFromAsciiBytes(byte[] buffer, int offset, int length)
1665 // Special-case for empty strings
1667 return string.Empty;
1669 // new string(sbyte*, ...) doesn't exist in the targeted reference assembly,
1670 // so we first copy to an array of chars, and then create a string from that.
1671 char[] chars = new char[length];
1672 for (int i = 0, j = offset; i < length; i++, j++)
1673 chars[i] = (char)buffer[j];
1674 return new string(chars);
1678 static extern unsafe int snprintf(byte* str, IntPtr size, string format, string arg1);
1681 static extern unsafe int snprintf(byte* str, IntPtr size, string format, int arg1);
1683 /// <summary>Formats an argument into a printf-style format string.</summary>
1684 /// <param name="format">The printf-style format string.</param>
1685 /// <param name="arg">The argument to format. This must be an Int32 or a String.</param>
1686 /// <returns>The formatted string.</returns>
1687 static unsafe string FormatPrintF(string format, object arg)
1689 // Determine how much space is needed to store the formatted string.
1690 string stringArg = arg as string;
1691 int neededLength = stringArg != null ?
1692 snprintf(null, IntPtr.Zero, format, stringArg) :
1693 snprintf(null, IntPtr.Zero, format, (int)arg);
1694 if (neededLength == 0)
1695 return string.Empty;
1696 if (neededLength < 0)
1697 throw new InvalidOperationException("The printf operation failed");
1699 // Allocate the needed space, format into it, and return the data as a string.
1700 byte[] bytes = new byte[neededLength + 1]; // extra byte for the null terminator
1701 fixed (byte* ptr = bytes){
1702 int length = stringArg != null ?
1703 snprintf(ptr, (IntPtr)bytes.Length, format, stringArg) :
1704 snprintf(ptr, (IntPtr)bytes.Length, format, (int)arg);
1705 if (length != neededLength)
1707 throw new InvalidOperationException("Invalid printf operation");
1710 return StringFromAsciiBytes(bytes, 0, neededLength);
1713 /// <summary>Gets the lazily-initialized dynamic or static variables collection, based on the supplied variable name.</summary>
1714 /// <param name="c">The name of the variable.</param>
1715 /// <param name="dynamicVars">The lazily-initialized dynamic variables collection.</param>
1716 /// <param name="staticVars">The lazily-initialized static variables collection.</param>
1717 /// <param name="index">The index to use to index into the variables.</param>
1718 /// <returns>The variables collection.</returns>
1719 private static FormatParam[] GetDynamicOrStaticVariables(
1720 char c, ref FormatParam[] dynamicVars, ref FormatParam[] staticVars, out int index)
1722 if (c >= 'A' && c <= 'Z'){
1724 return staticVars ?? (staticVars = new FormatParam[26]); // one slot for each letter of alphabet
1725 } else if (c >= 'a' && c <= 'z') {
1727 return dynamicVars ?? (dynamicVars = new FormatParam[26]); // one slot for each letter of alphabet
1729 else throw new InvalidOperationException("Terminfo database contains invalid values");
1733 /// Represents a parameter to a terminfo formatting string.
1734 /// It is a discriminated union of either an integer or a string,
1735 /// with characters represented as integers.
1737 public struct FormatParam
1739 /// <summary>The integer stored in the parameter.</summary>
1740 private readonly int _int32;
1741 /// <summary>The string stored in the parameter.</summary>
1742 private readonly string _string; // null means an Int32 is stored
1744 /// <summary>Initializes the parameter with an integer value.</summary>
1745 /// <param name="value">The value to be stored in the parameter.</param>
1746 public FormatParam(Int32 value) : this(value, null) { }
1748 /// <summary>Initializes the parameter with a string value.</summary>
1749 /// <param name="value">The value to be stored in the parameter.</param>
1750 public FormatParam(String value) : this(0, value ?? string.Empty) { }
1752 /// <summary>Initializes the parameter.</summary>
1753 /// <param name="intValue">The integer value.</param>
1754 /// <param name="stringValue">The string value.</param>
1755 private FormatParam(Int32 intValue, String stringValue)
1758 _string = stringValue;
1761 /// <summary>Implicit converts an integer into a parameter.</summary>
1762 public static implicit operator FormatParam(int value)
1764 return new FormatParam(value);
1767 /// <summary>Implicit converts a string into a parameter.</summary>
1768 public static implicit operator FormatParam(string value)
1770 return new FormatParam(value);
1773 /// <summary>Gets the integer value of the parameter. If a string was stored, 0 is returned.</summary>
1774 public int Int32 { get { return _int32; } }
1776 /// <summary>Gets the string value of the parameter. If an Int32 or a null String were stored, an empty string is returned.</summary>
1777 public string String { get { return _string ?? string.Empty; } }
1779 /// <summary>Gets the string or the integer value as an object.</summary>
1780 public object Object { get { return _string ?? (object)_int32; } }
1783 /// <summary>Provides a basic stack data structure.</summary>
1784 /// <typeparam name="T">Specifies the type of data in the stack.</typeparam>
1785 private sealed class LowLevelStack
1787 private const int DefaultSize = 4;
1788 private FormatParam[] _arr;
1791 public LowLevelStack() { _arr = new FormatParam[DefaultSize]; }
1793 public FormatParam Pop()
1796 throw new InvalidOperationException("Terminfo: Invalid Stack");
1798 var item = _arr[--_count];
1799 _arr[_count] = default(FormatParam);
1803 public void Push(FormatParam item)
1805 if (_arr.Length == _count){
1806 var newArr = new FormatParam[_arr.Length * 2];
1807 Array.Copy(_arr, 0, newArr, 0, _arr.Length);
1810 _arr[_count++] = item;
1815 Array.Clear(_arr, 0, _count);
1822 Hashtable map = new Hashtable ();
1823 Hashtable starts = new Hashtable ();
1825 public void AddMapping (TermInfoStrings key, byte [] val)
1827 if (val.Length == 0)
1831 starts [(int) val [0]] = true;
1838 public bool StartsWith (int c)
1840 return (starts [c] != null);
1843 public TermInfoStrings Match (char [] buffer, int offset, int length, out int used)
1845 foreach (byte [] bytes in map.Keys) {
1846 for (int i = 0; i < bytes.Length && i < length; i++) {
1847 if ((char) bytes [i] != buffer [offset + i])
1850 if (bytes.Length - 1 == i) {
1851 used = bytes.Length;
1852 return (TermInfoStrings) map [bytes];
1858 return (TermInfoStrings) (-1);