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
36 #if MONO_FEATURE_CONSOLE
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
);
279 if (cursorLeft
>= WindowWidth
) {
282 if (cursorTop
>= WindowHeight
) {
283 // Writing beyond the initial screen
284 if (rl_starty
!= -1) rl_starty
--;
290 // Should never get called unless inited
291 public void WriteSpecialKey (ConsoleKeyInfo key
)
294 case ConsoleKey
.Backspace
:
295 if (cursorLeft
> 0) {
296 if (cursorLeft
<= rl_startx
&& cursorTop
== rl_starty
)
299 SetCursorPosition (cursorLeft
, cursorTop
);
301 SetCursorPosition (cursorLeft
, cursorTop
);
304 logger
.WriteLine ("BS left: {0} top: {1}", cursorLeft
, cursorTop
);
309 int n
= 8 - (cursorLeft
% 8);
310 for (int i
= 0; i
< n
; i
++){
315 case ConsoleKey
.Clear
:
316 WriteConsole (clear
);
320 case ConsoleKey
.Enter
:
326 logger
.WriteLine ("left: {0} top: {1}", cursorLeft
, cursorTop
);
331 // Should never get called unless inited
332 public void WriteSpecialKey (char c
)
334 WriteSpecialKey (CreateKeyInfoFromInt (c
, false));
337 public bool IsSpecialKey (ConsoleKeyInfo key
)
343 case ConsoleKey
.Backspace
:
347 case ConsoleKey
.Clear
:
349 case ConsoleKey
.Enter
:
352 if (cursorTop
>= WindowHeight
) {
358 // CStreamWriter will handle writing this key
364 public bool IsSpecialKey (char c
)
366 return IsSpecialKey (CreateKeyInfoFromInt (c
, false));
370 /// The values of the ConsoleColor enums unfortunately don't map to the
371 /// corresponding ANSI values. We need to do the mapping manually.
372 /// See http://en.wikipedia.org/wiki/ANSI_escape_code#Colors
374 private static readonly int[] _consoleColorToAnsiCode
= new int[]
376 // Dark/Normal colors
397 void ChangeColor (string format
, ConsoleColor color
)
399 if (String
.IsNullOrEmpty (format
))
400 // the terminal doesn't support colors
403 int ccValue
= (int)color
;
404 if ((ccValue
& ~
0xF) != 0)
405 throw new ArgumentException("Invalid Console Color");
407 int ansiCode
= _consoleColorToAnsiCode
[ccValue
] % maxColors
;
409 WriteConsole (ParameterizedStrings
.Evaluate (format
, ansiCode
));
412 public ConsoleColor BackgroundColor
{
424 ChangeColor (setbgcolor
, value);
429 public ConsoleColor ForegroundColor
{
441 ChangeColor (setfgcolor
, value);
446 void GetCursorPosition ()
448 int row
= 0, col
= 0;
451 // First, get any data in the input buffer. Merely reduces the likelyhood of getting an error
452 int inqueue
= ConsoleDriver
.InternalKeyAvailable (0);
453 while (inqueue
-- > 0){
458 // Then try to probe for the cursor coordinates
459 WriteConsole ("\x1b[6n");
460 if (ConsoleDriver
.InternalKeyAvailable (1000) <= 0) {
461 noGetPosition
= true;
466 while (b
!= '\x1b') {
468 if (ConsoleDriver
.InternalKeyAvailable (100) <= 0)
475 AddToBuffer ('\x1b');
484 while ((b
>= '0') && (b
<= '9')) {
485 row
= row
* 10 + b
- '0';
488 // Row/col is 0 based
496 while ((b
>= '0') && (b
<= '9')) {
497 col
= col
* 10 + b
- '0';
500 // Row/col is 0 based
505 logger
.WriteLine ("GetCursorPosition: {0}, {1}", col
, row
);
513 public int BufferHeight
{
519 CheckWindowDimensions ();
527 throw new NotSupportedException ();
531 public int BufferWidth
{
537 CheckWindowDimensions ();
545 throw new NotSupportedException ();
549 public bool CapsLock
{
558 public int CursorLeft
{
571 SetCursorPosition (value, CursorTop
);
575 public int CursorTop
{
588 SetCursorPosition (CursorLeft
, value);
592 public bool CursorVisible
{
598 return cursorVisible
;
605 cursorVisible
= value;
606 WriteConsole ((value ? csrVisible
: csrInvisible
));
610 // we have CursorNormal vs. CursorVisible...
612 public int CursorSize
{
627 public bool KeyAvailable
{
633 return (writepos
> readpos
|| ConsoleDriver
.InternalKeyAvailable (0) > 0);
637 // We don't know these next 2 values, so return something reasonable
638 public int LargestWindowHeight
{
639 get { return WindowHeight; }
642 public int LargestWindowWidth
{
643 get { return WindowWidth; }
646 public bool NumberLock
{
656 public string Title
{
670 WriteConsole (String
.Format (titleFormat
, value));
674 public bool TreatControlCAsInput
{
679 return controlCAsInput
;
686 if (controlCAsInput
== value)
689 ConsoleDriver
.SetBreak (value);
690 controlCAsInput
= value;
695 // Requries that caller calls Init () if not !inited.
697 unsafe void CheckWindowDimensions ()
699 if (native_terminal_size
== null || terminal_size
== *native_terminal_size
)
702 if (*native_terminal_size
== -1){
703 int c
= reader
.Get (TermInfoNumbers
.Columns
);
707 c
= reader
.Get (TermInfoNumbers
.Lines
);
711 terminal_size
= *native_terminal_size
;
712 windowWidth
= terminal_size
>> 16;
713 windowHeight
= terminal_size
& 0xffff;
715 bufferHeight
= windowHeight
;
716 bufferWidth
= windowWidth
;
720 public int WindowHeight
{
726 CheckWindowDimensions ();
734 throw new NotSupportedException ();
738 public int WindowLeft
{
744 //CheckWindowDimensions ();
752 throw new NotSupportedException ();
756 public int WindowTop
{
762 //CheckWindowDimensions ();
770 throw new NotSupportedException ();
774 public int WindowWidth
{
780 CheckWindowDimensions ();
788 throw new NotSupportedException ();
798 WriteConsole (clear
);
803 public void Beep (int frequency
, int duration
)
812 public void MoveBufferArea (int sourceLeft
, int sourceTop
, int sourceWidth
, int sourceHeight
,
813 int targetLeft
, int targetTop
, Char sourceChar
,
814 ConsoleColor sourceForeColor
, ConsoleColor sourceBackColor
)
820 throw new NotImplementedException ();
823 void AddToBuffer (int b
)
825 if (buffer
== null) {
826 buffer
= new char [1024];
827 } else if (writepos
>= buffer
.Length
) {
828 char [] newbuf
= new char [buffer
.Length
* 2];
829 Buffer
.BlockCopy (buffer
, 0, newbuf
, 0, buffer
.Length
);
833 buffer
[writepos
++] = (char) b
;
838 if (readpos
>= writepos
) {
839 readpos
= writepos
= 0;
843 ConsoleKeyInfo
CreateKeyInfoFromInt (int n
, bool alt
)
846 ConsoleKey key
= (ConsoleKey
)n
;
852 key
= ConsoleKey
.Enter
;
855 key
= ConsoleKey
.Spacebar
;
858 key
= ConsoleKey
.Subtract
;
861 key
= ConsoleKey
.Add
;
864 key
= ConsoleKey
.Divide
;
867 key
= ConsoleKey
.Multiply
;
869 case 8: case 9: case 12: case 13: case 19:
870 /* Values in ConsoleKey */
873 key
= ConsoleKey
.Escape
;
877 if (n
>= 1 && n
<= 26) {
878 // For Ctrl-a to Ctrl-z.
880 key
= ConsoleKey
.A
+ n
- 1;
881 } else if (n
>= 'a' && n
<= 'z') {
882 key
= ConsoleKey
.A
- 'a' + n
;
883 } else if (n
>= 'A' && n
<= 'Z') {
885 } else if (n
>= '0' && n
<= '9') {
891 return new ConsoleKeyInfo (c
, key
, shift
, alt
, ctrl
);
894 object GetKeyFromBuffer (bool cooked
)
896 if (readpos
>= writepos
)
899 int next
= buffer
[readpos
];
900 if (!cooked
|| !rootmap
.StartsWith (next
)) {
903 return CreateKeyInfoFromInt (next
, false);
907 TermInfoStrings str
= rootmap
.Match (buffer
, readpos
, writepos
- readpos
, out used
);
908 if ((int) str
== -1){
909 // Escape sequences: alt keys are sent as ESC-key
910 if (buffer
[readpos
] == 27 && (writepos
- readpos
) >= 2){
913 if (buffer
[readpos
+1] == 127)
914 return new ConsoleKeyInfo ((char)8, ConsoleKey
.Backspace
, false, true, false);
915 return CreateKeyInfoFromInt (buffer
[readpos
+1], true);
921 if (keymap
[str
] != null) {
922 key
= (ConsoleKeyInfo
) keymap
[str
];
926 return CreateKeyInfoFromInt (next
, false);
934 ConsoleKeyInfo
ReadKeyInternal (out bool fresh
)
943 if ((o
= GetKeyFromBuffer (true)) == null) {
945 if (ConsoleDriver
.InternalKeyAvailable (150) > 0) {
947 AddToBuffer (stdin
.Read ());
948 } while (ConsoleDriver
.InternalKeyAvailable (0) > 0);
949 } else if (stdin
.DataAvailable ()) {
951 AddToBuffer (stdin
.Read ());
952 } while (stdin
.DataAvailable ());
954 if ((o
= GetKeyFromBuffer (false)) != null)
957 AddToBuffer (stdin
.Read ());
960 o
= GetKeyFromBuffer (true);
963 // freshly read character
966 // this char was pre-buffered (e.g. not fresh)
970 return (ConsoleKeyInfo
) o
;
973 #region Input echoing optimization
976 // check if we've got pending input we can read immediately
977 return readpos
< writepos
|| stdin
.DataAvailable ();
980 char [] echobuf
= null;
983 // Queues a character to be echo'd back to the console
984 void QueueEcho (char c
)
987 echobuf
= new char [1024];
989 echobuf
[echon
++] = c
;
991 if (echon
== echobuf
.Length
|| !InputPending ()) {
992 // blit our echo buffer to the console
993 stdout
.InternalWriteChars (echobuf
, echon
);
998 // Queues a key to be echo'd back to the console
999 void Echo (ConsoleKeyInfo key
)
1001 if (!IsSpecialKey (key
)) {
1002 QueueEcho (key
.KeyChar
);
1006 // flush pending echo's
1009 WriteSpecialKey (key
);
1012 // Flush the pending echo queue
1018 // flush our echo buffer to the console
1019 stdout
.InternalWriteChars (echobuf
, echon
);
1024 public int Read ([In
, Out
] char [] dest
, int index
, int count
)
1026 bool fresh
, echo
= false;
1029 int BoL
= 0; // Beginning-of-Line marker (can't backspace beyond this)
1033 sbuf
= new StringBuilder ();
1035 // consume buffered keys first (do not echo, these have already been echo'd)
1037 if ((o
= GetKeyFromBuffer (true)) == null)
1040 key
= (ConsoleKeyInfo
) o
;
1043 if (key
.Key
!= ConsoleKey
.Backspace
) {
1044 if (key
.Key
== ConsoleKey
.Enter
)
1048 } else if (sbuf
.Length
> BoL
) {
1053 // continue reading until Enter is hit
1054 rl_startx
= cursorLeft
;
1055 rl_starty
= cursorTop
;
1058 key
= ReadKeyInternal (out fresh
);
1059 echo
= echo
|| fresh
;
1062 if (key
.Key
!= ConsoleKey
.Backspace
) {
1063 if (key
.Key
== ConsoleKey
.Enter
)
1067 } else if (sbuf
.Length
> BoL
) {
1073 // echo fresh keys back to the console
1076 } while (key
.Key
!= ConsoleKey
.Enter
);
1083 // copy up to count chars into dest
1085 while (count
> 0 && nread
< sbuf
.Length
) {
1086 dest
[index
+ nread
] = sbuf
[nread
];
1091 // put the rest back into our key buffer
1092 for (int i
= nread
; i
< sbuf
.Length
; i
++)
1093 AddToBuffer (sbuf
[i
]);
1098 public ConsoleKeyInfo
ReadKey (bool intercept
)
1102 ConsoleKeyInfo key
= ReadKeyInternal (out fresh
);
1104 if (!intercept
&& fresh
) {
1105 // echo the fresh key back to the console
1113 public string ReadLine ()
1115 return ReadUntilConditionInternal (true);
1118 public string ReadToEnd ()
1120 return ReadUntilConditionInternal (false);
1123 private string ReadUntilConditionInternal (bool haltOnNewLine
)
1128 // Hack to make Iron Python work (since it goes behind our backs
1129 // when writing to the console thus preventing us from keeping
1130 // cursor state normally).
1131 GetCursorPosition ();
1133 StringBuilder builder
= new StringBuilder ();
1134 bool fresh
, echo
= false;
1138 rl_startx
= cursorLeft
;
1139 rl_starty
= cursorTop
;
1140 char eof
= (char) control_characters
[ControlCharacters
.EOF
];
1142 bool treatAsEnterKey
;
1145 key
= ReadKeyInternal (out fresh
);
1146 echo
= echo
|| fresh
;
1148 // EOF -> Ctrl-D (EOT) pressed.
1149 if (c
== eof
&& c
!= 0 && builder
.Length
== 0)
1152 treatAsEnterKey
= haltOnNewLine
&& (key
.Key
== ConsoleKey
.Enter
);
1154 if (!treatAsEnterKey
) {
1155 if (key
.Key
!= ConsoleKey
.Backspace
) {
1157 } else if (builder
.Length
> 0) {
1160 // skips over echoing the key to the console
1165 // echo fresh keys back to the console
1168 } while (!treatAsEnterKey
);
1175 return builder
.ToString ();
1178 public void ResetColor ()
1184 string str
= (origPair
!= null) ? origPair
: origColors
;
1188 public void SetBufferSize (int width
, int height
)
1194 throw new NotImplementedException (String
.Empty
);
1197 public void SetCursorPosition (int left
, int top
)
1203 CheckWindowDimensions ();
1204 if (left
< 0 || left
>= bufferWidth
)
1205 throw new ArgumentOutOfRangeException ("left", "Value must be positive and below the buffer width.");
1207 if (top
< 0 || top
>= bufferHeight
)
1208 throw new ArgumentOutOfRangeException ("top", "Value must be positive and below the buffer height.");
1210 // Either CursorAddress or nothing.
1211 // We might want to play with up/down/left/right/home when ca is not available.
1212 if (cursorAddress
== null)
1213 throw new NotSupportedException ("This terminal does not suport setting the cursor position.");
1215 WriteConsole (ParameterizedStrings
.Evaluate (cursorAddress
, top
, left
));
1220 public void SetWindowPosition (int left
, int top
)
1226 // No need to throw exceptions here.
1227 //throw new NotSupportedException ();
1230 public void SetWindowSize (int width
, int height
)
1236 // No need to throw exceptions here.
1237 //throw new NotSupportedException ();
1241 void CreateKeyMap ()
1243 keymap
= new Hashtable ();
1245 keymap
[TermInfoStrings
.KeyBackspace
] = new ConsoleKeyInfo ('\0', ConsoleKey
.Backspace
, false, false, false);
1246 keymap
[TermInfoStrings
.KeyClear
] = new ConsoleKeyInfo ('\0', ConsoleKey
.Clear
, false, false, false);
1247 // Delete character...
1248 keymap
[TermInfoStrings
.KeyDown
] = new ConsoleKeyInfo ('\0', ConsoleKey
.DownArrow
, false, false, false);
1249 keymap
[TermInfoStrings
.KeyF1
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F1
, false, false, false);
1250 keymap
[TermInfoStrings
.KeyF10
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F10
, false, false, false);
1251 keymap
[TermInfoStrings
.KeyF2
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F2
, false, false, false);
1252 keymap
[TermInfoStrings
.KeyF3
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F3
, false, false, false);
1253 keymap
[TermInfoStrings
.KeyF4
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F4
, false, false, false);
1254 keymap
[TermInfoStrings
.KeyF5
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F5
, false, false, false);
1255 keymap
[TermInfoStrings
.KeyF6
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F6
, false, false, false);
1256 keymap
[TermInfoStrings
.KeyF7
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F7
, false, false, false);
1257 keymap
[TermInfoStrings
.KeyF8
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F8
, false, false, false);
1258 keymap
[TermInfoStrings
.KeyF9
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F9
, false, false, false);
1259 keymap
[TermInfoStrings
.KeyHome
] = new ConsoleKeyInfo ('\0', ConsoleKey
.Home
, false, false, false);
1260 keymap
[TermInfoStrings
.KeyLeft
] = new ConsoleKeyInfo ('\0', ConsoleKey
.LeftArrow
, false, false, false);
1261 keymap
[TermInfoStrings
.KeyLl
] = new ConsoleKeyInfo ('\0', ConsoleKey
.NumPad1
, false, false, false);
1262 keymap
[TermInfoStrings
.KeyNpage
] = new ConsoleKeyInfo ('\0', ConsoleKey
.PageDown
, false, false, false);
1263 keymap
[TermInfoStrings
.KeyPpage
] = new ConsoleKeyInfo ('\0', ConsoleKey
.PageUp
, false, false, false);
1264 keymap
[TermInfoStrings
.KeyRight
] = new ConsoleKeyInfo ('\0', ConsoleKey
.RightArrow
, false, false, false);
1265 keymap
[TermInfoStrings
.KeySf
] = new ConsoleKeyInfo ('\0', ConsoleKey
.PageDown
, false, false, false);
1266 keymap
[TermInfoStrings
.KeySr
] = new ConsoleKeyInfo ('\0', ConsoleKey
.PageUp
, false, false, false);
1267 keymap
[TermInfoStrings
.KeyUp
] = new ConsoleKeyInfo ('\0', ConsoleKey
.UpArrow
, false, false, false);
1268 keymap
[TermInfoStrings
.KeyA1
] = new ConsoleKeyInfo ('\0', ConsoleKey
.NumPad7
, false, false, false);
1269 keymap
[TermInfoStrings
.KeyA3
] = new ConsoleKeyInfo ('\0', ConsoleKey
.NumPad9
, false, false, false);
1270 keymap
[TermInfoStrings
.KeyB2
] = new ConsoleKeyInfo ('\0', ConsoleKey
.NumPad5
, false, false, false);
1271 keymap
[TermInfoStrings
.KeyC1
] = new ConsoleKeyInfo ('\0', ConsoleKey
.NumPad1
, false, false, false);
1272 keymap
[TermInfoStrings
.KeyC3
] = new ConsoleKeyInfo ('\0', ConsoleKey
.NumPad3
, false, false, false);
1273 keymap
[TermInfoStrings
.KeyBtab
] = new ConsoleKeyInfo ('\0', ConsoleKey
.Tab
, true, false, false);
1274 keymap
[TermInfoStrings
.KeyBeg
] = new ConsoleKeyInfo ('\0', ConsoleKey
.Home
, false, false, false);
1275 keymap
[TermInfoStrings
.KeyCopy
] = new ConsoleKeyInfo ('C', ConsoleKey
.C
, false, true, false);
1276 keymap
[TermInfoStrings
.KeyEnd
] = new ConsoleKeyInfo ('\0', ConsoleKey
.End
, false, false, false);
1277 keymap
[TermInfoStrings
.KeyEnter
] = new ConsoleKeyInfo ('\n', ConsoleKey
.Enter
, false, false, false);
1278 keymap
[TermInfoStrings
.KeyHelp
] = new ConsoleKeyInfo ('\0', ConsoleKey
.Help
, false, false, false);
1279 keymap
[TermInfoStrings
.KeyPrint
] = new ConsoleKeyInfo ('\0', ConsoleKey
.Print
, false, false, false);
1280 keymap
[TermInfoStrings
.KeyUndo
] = new ConsoleKeyInfo ('Z', ConsoleKey
.Z
, false, true, false);
1281 keymap
[TermInfoStrings
.KeySbeg
] = new ConsoleKeyInfo ('\0', ConsoleKey
.Home
, true, false, false);
1282 keymap
[TermInfoStrings
.KeyScopy
] = new ConsoleKeyInfo ('C', ConsoleKey
.C
, true, true, false);
1283 keymap
[TermInfoStrings
.KeySdc
] = new ConsoleKeyInfo ('\x9', ConsoleKey
.Delete
, true, false, false);
1284 keymap
[TermInfoStrings
.KeyShelp
] = new ConsoleKeyInfo ('\0', ConsoleKey
.Help
, true, false, false);
1285 keymap
[TermInfoStrings
.KeyShome
] = new ConsoleKeyInfo ('\0', ConsoleKey
.Home
, true, false, false);
1286 keymap
[TermInfoStrings
.KeySleft
] = new ConsoleKeyInfo ('\0', ConsoleKey
.LeftArrow
, true, false, false);
1287 keymap
[TermInfoStrings
.KeySprint
] = new ConsoleKeyInfo ('\0', ConsoleKey
.Print
, true, false, false);
1288 keymap
[TermInfoStrings
.KeySright
] = new ConsoleKeyInfo ('\0', ConsoleKey
.RightArrow
, true, false, false);
1289 keymap
[TermInfoStrings
.KeySundo
] = new ConsoleKeyInfo ('Z', ConsoleKey
.Z
, true, false, false);
1290 keymap
[TermInfoStrings
.KeyF11
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F11
, false, false, false);
1291 keymap
[TermInfoStrings
.KeyF12
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F12
, false, false, false);
1292 keymap
[TermInfoStrings
.KeyF13
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F13
, false, false, false);
1293 keymap
[TermInfoStrings
.KeyF14
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F14
, false, false, false);
1294 keymap
[TermInfoStrings
.KeyF15
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F15
, false, false, false);
1295 keymap
[TermInfoStrings
.KeyF16
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F16
, false, false, false);
1296 keymap
[TermInfoStrings
.KeyF17
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F17
, false, false, false);
1297 keymap
[TermInfoStrings
.KeyF18
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F18
, false, false, false);
1298 keymap
[TermInfoStrings
.KeyF19
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F19
, false, false, false);
1299 keymap
[TermInfoStrings
.KeyF20
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F20
, false, false, false);
1300 keymap
[TermInfoStrings
.KeyF21
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F21
, false, false, false);
1301 keymap
[TermInfoStrings
.KeyF22
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F22
, false, false, false);
1302 keymap
[TermInfoStrings
.KeyF23
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F23
, false, false, false);
1303 keymap
[TermInfoStrings
.KeyF24
] = new ConsoleKeyInfo ('\0', ConsoleKey
.F24
, false, false, false);
1304 // These were previously missing:
1305 keymap
[TermInfoStrings
.KeyDc
] = new ConsoleKeyInfo ('\0', ConsoleKey
.Delete
, false, false, false);
1306 keymap
[TermInfoStrings
.KeyIc
] = new ConsoleKeyInfo ('\0', ConsoleKey
.Insert
, false, false, false);
1315 rootmap
= new ByteMatcher ();
1318 // The keys that we know about and use
1320 var UsedKeys
= new [] {
1321 TermInfoStrings
.KeyBackspace
,
1322 TermInfoStrings
.KeyClear
,
1323 TermInfoStrings
.KeyDown
,
1324 TermInfoStrings
.KeyF1
,
1325 TermInfoStrings
.KeyF10
,
1326 TermInfoStrings
.KeyF2
,
1327 TermInfoStrings
.KeyF3
,
1328 TermInfoStrings
.KeyF4
,
1329 TermInfoStrings
.KeyF5
,
1330 TermInfoStrings
.KeyF6
,
1331 TermInfoStrings
.KeyF7
,
1332 TermInfoStrings
.KeyF8
,
1333 TermInfoStrings
.KeyF9
,
1334 TermInfoStrings
.KeyHome
,
1335 TermInfoStrings
.KeyLeft
,
1336 TermInfoStrings
.KeyLl
,
1337 TermInfoStrings
.KeyNpage
,
1338 TermInfoStrings
.KeyPpage
,
1339 TermInfoStrings
.KeyRight
,
1340 TermInfoStrings
.KeySf
,
1341 TermInfoStrings
.KeySr
,
1342 TermInfoStrings
.KeyUp
,
1343 TermInfoStrings
.KeyA1
,
1344 TermInfoStrings
.KeyA3
,
1345 TermInfoStrings
.KeyB2
,
1346 TermInfoStrings
.KeyC1
,
1347 TermInfoStrings
.KeyC3
,
1348 TermInfoStrings
.KeyBtab
,
1349 TermInfoStrings
.KeyBeg
,
1350 TermInfoStrings
.KeyCopy
,
1351 TermInfoStrings
.KeyEnd
,
1352 TermInfoStrings
.KeyEnter
,
1353 TermInfoStrings
.KeyHelp
,
1354 TermInfoStrings
.KeyPrint
,
1355 TermInfoStrings
.KeyUndo
,
1356 TermInfoStrings
.KeySbeg
,
1357 TermInfoStrings
.KeyScopy
,
1358 TermInfoStrings
.KeySdc
,
1359 TermInfoStrings
.KeyShelp
,
1360 TermInfoStrings
.KeyShome
,
1361 TermInfoStrings
.KeySleft
,
1362 TermInfoStrings
.KeySprint
,
1363 TermInfoStrings
.KeySright
,
1364 TermInfoStrings
.KeySundo
,
1365 TermInfoStrings
.KeyF11
,
1366 TermInfoStrings
.KeyF12
,
1367 TermInfoStrings
.KeyF13
,
1368 TermInfoStrings
.KeyF14
,
1369 TermInfoStrings
.KeyF15
,
1370 TermInfoStrings
.KeyF16
,
1371 TermInfoStrings
.KeyF17
,
1372 TermInfoStrings
.KeyF18
,
1373 TermInfoStrings
.KeyF19
,
1374 TermInfoStrings
.KeyF20
,
1375 TermInfoStrings
.KeyF21
,
1376 TermInfoStrings
.KeyF22
,
1377 TermInfoStrings
.KeyF23
,
1378 TermInfoStrings
.KeyF24
,
1380 // These were missing
1381 TermInfoStrings
.KeyDc
,
1382 TermInfoStrings
.KeyIc
1385 foreach (TermInfoStrings tis
in UsedKeys
)
1386 AddStringMapping (tis
);
1388 rootmap
.AddMapping (TermInfoStrings
.KeyBackspace
, new byte [] { control_characters [ControlCharacters.Erase] }
);
1393 void AddStringMapping (TermInfoStrings s
)
1395 byte [] bytes
= reader
.GetStringBytes (s
);
1399 rootmap
.AddMapping (s
, bytes
);
1403 /// <summary>Provides support for evaluating parameterized terminfo database format strings.</summary>
1404 internal static class ParameterizedStrings
1406 /// <summary>A cached stack to use to avoid allocating a new stack object for every evaluation.</summary>
1408 private static LowLevelStack _cachedStack
;
1410 /// <summary>Evaluates a terminfo formatting string, using the supplied arguments.</summary>
1411 /// <param name="format">The format string.</param>
1412 /// <param name="args">The arguments to the format string.</param>
1413 /// <returns>The formatted string.</returns>
1414 public static string Evaluate(string format
, params FormatParam
[] args
)
1417 throw new ArgumentNullException("format");
1419 throw new ArgumentNullException("args");
1421 // Initialize the stack to use for processing.
1422 LowLevelStack stack
= _cachedStack
;
1424 _cachedStack
= stack
= new LowLevelStack();
1428 // "dynamic" and "static" variables are much less often used (the "dynamic" and "static"
1429 // terminology appears to just refer to two different collections rather than to any semantic
1430 // meaning). As such, we'll only initialize them if we really need them.
1431 FormatParam
[] dynamicVars
= null, staticVars
= null;
1434 return EvaluateInternal(format
, ref pos
, args
, stack
, ref dynamicVars
, ref staticVars
);
1436 // EvaluateInternal may throw IndexOutOfRangeException and InvalidOperationException
1437 // if the format string is malformed or if it's inconsistent with the parameters provided.
1440 /// <summary>Evaluates a terminfo formatting string, using the supplied arguments and processing data structures.</summary>
1441 /// <param name="format">The format string.</param>
1442 /// <param name="pos">The position in <paramref name="format"/> to start processing.</param>
1443 /// <param name="args">The arguments to the format string.</param>
1444 /// <param name="stack">The stack to use as the format string is evaluated.</param>
1445 /// <param name="dynamicVars">A lazily-initialized collection of variables.</param>
1446 /// <param name="staticVars">A lazily-initialized collection of variables.</param>
1448 /// The formatted string; this may be empty if the evaluation didn't yield any output.
1449 /// The evaluation stack will have a 1 at the top if all processing was completed at invoked level
1450 /// of recursion, and a 0 at the top if we're still inside of a conditional that requires more processing.
1452 private static string EvaluateInternal(
1453 string format
, ref int pos
, FormatParam
[] args
, LowLevelStack stack
,
1454 ref FormatParam
[] dynamicVars
, ref FormatParam
[] staticVars
)
1456 // Create a StringBuilder to store the output of this processing. We use the format's length as an
1457 // approximation of an upper-bound for how large the output will be, though with parameter processing,
1458 // this is just an estimate, sometimes way over, sometimes under.
1459 StringBuilder output
= new StringBuilder(format
.Length
);
1461 // Format strings support conditionals, including the equivalent of "if ... then ..." and
1462 // "if ... then ... else ...", as well as "if ... then ... else ... then ..."
1463 // and so on, where an else clause can not only be evaluated for string output but also
1464 // as a conditional used to determine whether to evaluate a subsequent then clause.
1465 // We use recursion to process these subsequent parts, and we track whether we're processing
1466 // at the same level of the initial if clause (or whether we're nested).
1467 bool sawIfConditional
= false;
1469 // Process each character in the format string, starting from the position passed in.
1470 for (; pos
< format
.Length
; pos
++){
1471 // '%' is the escape character for a special sequence to be evaluated.
1472 // Anything else just gets pushed to output.
1473 if (format
[pos
] != '%') {
1474 output
.Append(format
[pos
]);
1477 // We have a special parameter sequence to process. Now we need
1478 // to look at what comes after the '%'.
1480 switch (format
[pos
]) {
1481 // Output appending operations
1482 case '%': // Output the escaped '%'
1485 case 'c': // Pop the stack and output it as a char
1486 output
.Append((char)stack
.Pop().Int32
);
1488 case 's': // Pop the stack and output it as a string
1489 output
.Append(stack
.Pop().String
);
1491 case 'd': // Pop the stack and output it as an integer
1492 output
.Append(stack
.Pop().Int32
);
1508 // printf strings of the format "%[[:]flags][width[.precision]][doxXs]" are allowed
1509 // (with a ':' used in front of flags to help differentiate from binary operations, as flags can
1510 // include '-' and '+'). While above we've special-cased common usage (e.g. %d, %s),
1511 // for more complicated expressions we delegate to printf.
1512 int printfEnd
= pos
;
1513 for (; printfEnd
< format
.Length
; printfEnd
++) // find the end of the printf format string
1515 char ec
= format
[printfEnd
];
1516 if (ec
== 'd' || ec
== 'o' || ec
== 'x' || ec
== 'X' || ec
== 's')
1521 if (printfEnd
>= format
.Length
)
1522 throw new InvalidOperationException("Terminfo database contains invalid values");
1523 string printfFormat
= format
.Substring(pos
- 1, printfEnd
- pos
+ 2); // extract the format string
1524 if (printfFormat
.Length
> 1 && printfFormat
[1] == ':')
1525 printfFormat
= printfFormat
.Remove(1, 1);
1526 output
.Append(FormatPrintF(printfFormat
, stack
.Pop().Object
)); // do the printf formatting and append its output
1529 // Stack pushing operations
1530 case 'p': // Push the specified parameter (1-based) onto the stack
1532 stack
.Push(args
[format
[pos
] - '1']);
1534 case 'l': // Pop a string and push its length
1535 stack
.Push(stack
.Pop().String
.Length
);
1537 case '{': // Push integer literal, enclosed between braces
1540 while (format
[pos
] != '}')
1542 intLit
= (intLit
* 10) + (format
[pos
] - '0');
1547 case '\'': // Push literal character, enclosed between single quotes
1548 stack
.Push((int)format
[pos
+ 1]);
1552 // Storing and retrieving "static" and "dynamic" variables
1553 case 'P': // Pop a value and store it into either static or dynamic variables based on whether the a-z variable is capitalized
1556 FormatParam
[] targetVars
= GetDynamicOrStaticVariables(format
[pos
], ref dynamicVars
, ref staticVars
, out setIndex
);
1557 targetVars
[setIndex
] = stack
.Pop();
1559 case 'g': // Push a static or dynamic variable; which is based on whether the a-z variable is capitalized
1562 FormatParam
[] sourceVars
= GetDynamicOrStaticVariables(format
[pos
], ref dynamicVars
, ref staticVars
, out getIndex
);
1563 stack
.Push(sourceVars
[getIndex
]);
1566 // Binary operations
1572 case '^': // arithmetic
1574 case '|': // bitwise
1577 case '<': // comparison
1579 case 'O': // logical
1580 int second
= stack
.Pop().Int32
; // it's a stack... the second value was pushed last
1581 int first
= stack
.Pop().Int32
;
1583 switch (format
[pos
]) {
1585 res
= first
+ second
;
1588 res
= first
- second
;
1591 res
= first
* second
;
1594 res
= first
/ second
;
1597 res
= first
% second
;
1600 res
= first ^ second
;
1603 res
= first
& second
;
1606 res
= first
| second
;
1609 res
= AsInt(first
== second
);
1612 res
= AsInt(first
> second
);
1615 res
= AsInt(first
< second
);
1618 res
= AsInt(AsBool(first
) && AsBool(second
));
1621 res
= AsInt(AsBool(first
) || AsBool(second
));
1633 int value = stack
.Pop().Int32
;
1635 format
[pos
] == '!' ? AsInt(!AsBool(value)) :
1639 // Augment first two parameters by 1
1641 args
[0] = 1 + args
[0].Int32
;
1642 args
[1] = 1 + args
[1].Int32
;
1645 // Conditional of the form %? if-part %t then-part %e else-part %;
1646 // The "%e else-part" is optional.
1648 sawIfConditional
= true;
1651 // We hit the end of the if-part and are about to start the then-part.
1652 // The if-part left its result on the stack; pop and evaluate.
1653 bool conditionalResult
= AsBool(stack
.Pop().Int32
);
1655 // Regardless of whether it's true, run the then-part to get past it.
1656 // If the conditional was true, output the then results.
1658 string thenResult
= EvaluateInternal(format
, ref pos
, args
, stack
, ref dynamicVars
, ref staticVars
);
1659 if (conditionalResult
)
1661 output
.Append(thenResult
);
1664 // We're past the then; the top of the stack should now be a Boolean
1665 // indicating whether this conditional has more to be processed (an else clause).
1666 if (!AsBool(stack
.Pop().Int32
))
1668 // Process the else clause, and if the conditional was false, output the else results.
1670 string elseResult
= EvaluateInternal(format
, ref pos
, args
, stack
, ref dynamicVars
, ref staticVars
);
1671 if (!conditionalResult
)
1673 output
.Append(elseResult
);
1675 // Now we should be done (any subsequent elseif logic will have bene handled in the recursive call).
1676 if (!AsBool(stack
.Pop().Int32
))
1678 throw new InvalidOperationException("Terminfo database contains invalid values");
1682 // If we're in a nested processing, return to our parent.
1683 if (!sawIfConditional
)
1686 return output
.ToString();
1688 // Otherwise, we're done processing the conditional in its entirety.
1689 sawIfConditional
= false;
1693 // Let our caller know why we're exiting, whether due to the end of the conditional or an else branch.
1694 stack
.Push(AsInt(format
[pos
] == ';'));
1695 return output
.ToString();
1697 // Anything else is an error
1699 throw new InvalidOperationException("Terminfo database contains invalid values");
1703 return output
.ToString();
1706 /// <summary>Converts an Int32 to a Boolean, with 0 meaning false and all non-zero values meaning true.</summary>
1707 /// <param name="i">The integer value to convert.</param>
1708 /// <returns>true if the integer was non-zero; otherwise, false.</returns>
1709 static bool AsBool(Int32 i
) { return i != 0; }
1711 /// <summary>Converts a Boolean to an Int32, with true meaning 1 and false meaning 0.</summary>
1712 /// <param name="b">The Boolean value to convert.</param>
1713 /// <returns>1 if the Boolean is true; otherwise, 0.</returns>
1714 static int AsInt(bool b
) { return b ? 1 : 0; }
1716 static string StringFromAsciiBytes(byte[] buffer
, int offset
, int length
)
1718 // Special-case for empty strings
1720 return string.Empty
;
1722 // new string(sbyte*, ...) doesn't exist in the targeted reference assembly,
1723 // so we first copy to an array of chars, and then create a string from that.
1724 char[] chars
= new char[length
];
1725 for (int i
= 0, j
= offset
; i
< length
; i
++, j
++)
1726 chars
[i
] = (char)buffer
[j
];
1727 return new string(chars
);
1731 static extern unsafe int snprintf(byte* str
, IntPtr size
, string format
, string arg1
);
1734 static extern unsafe int snprintf(byte* str
, IntPtr size
, string format
, int arg1
);
1736 /// <summary>Formats an argument into a printf-style format string.</summary>
1737 /// <param name="format">The printf-style format string.</param>
1738 /// <param name="arg">The argument to format. This must be an Int32 or a String.</param>
1739 /// <returns>The formatted string.</returns>
1740 static unsafe string FormatPrintF(string format
, object arg
)
1742 // Determine how much space is needed to store the formatted string.
1743 string stringArg
= arg
as string;
1744 int neededLength
= stringArg
!= null ?
1745 snprintf(null, IntPtr
.Zero
, format
, stringArg
) :
1746 snprintf(null, IntPtr
.Zero
, format
, (int)arg
);
1747 if (neededLength
== 0)
1748 return string.Empty
;
1749 if (neededLength
< 0)
1750 throw new InvalidOperationException("The printf operation failed");
1752 // Allocate the needed space, format into it, and return the data as a string.
1753 byte[] bytes
= new byte[neededLength
+ 1]; // extra byte for the null terminator
1754 fixed (byte* ptr
= bytes
){
1755 int length
= stringArg
!= null ?
1756 snprintf(ptr
, (IntPtr
)bytes
.Length
, format
, stringArg
) :
1757 snprintf(ptr
, (IntPtr
)bytes
.Length
, format
, (int)arg
);
1758 if (length
!= neededLength
)
1760 throw new InvalidOperationException("Invalid printf operation");
1763 return StringFromAsciiBytes(bytes
, 0, neededLength
);
1766 /// <summary>Gets the lazily-initialized dynamic or static variables collection, based on the supplied variable name.</summary>
1767 /// <param name="c">The name of the variable.</param>
1768 /// <param name="dynamicVars">The lazily-initialized dynamic variables collection.</param>
1769 /// <param name="staticVars">The lazily-initialized static variables collection.</param>
1770 /// <param name="index">The index to use to index into the variables.</param>
1771 /// <returns>The variables collection.</returns>
1772 private static FormatParam
[] GetDynamicOrStaticVariables(
1773 char c
, ref FormatParam
[] dynamicVars
, ref FormatParam
[] staticVars
, out int index
)
1775 if (c
>= 'A' && c
<= 'Z'){
1777 return staticVars
?? (staticVars
= new FormatParam
[26]); // one slot for each letter of alphabet
1778 } else if (c
>= 'a' && c
<= 'z') {
1780 return dynamicVars
?? (dynamicVars
= new FormatParam
[26]); // one slot for each letter of alphabet
1782 else throw new InvalidOperationException("Terminfo database contains invalid values");
1786 /// Represents a parameter to a terminfo formatting string.
1787 /// It is a discriminated union of either an integer or a string,
1788 /// with characters represented as integers.
1790 public struct FormatParam
1792 /// <summary>The integer stored in the parameter.</summary>
1793 private readonly int _int32
;
1794 /// <summary>The string stored in the parameter.</summary>
1795 private readonly string _string
; // null means an Int32 is stored
1797 /// <summary>Initializes the parameter with an integer value.</summary>
1798 /// <param name="value">The value to be stored in the parameter.</param>
1799 public FormatParam(Int32
value) : this(value, null) { }
1801 /// <summary>Initializes the parameter with a string value.</summary>
1802 /// <param name="value">The value to be stored in the parameter.</param>
1803 public FormatParam(String
value) : this(0, value ?? string.Empty
) { }
1805 /// <summary>Initializes the parameter.</summary>
1806 /// <param name="intValue">The integer value.</param>
1807 /// <param name="stringValue">The string value.</param>
1808 private FormatParam(Int32 intValue
, String stringValue
)
1811 _string
= stringValue
;
1814 /// <summary>Implicit converts an integer into a parameter.</summary>
1815 public static implicit operator FormatParam(int value)
1817 return new FormatParam(value);
1820 /// <summary>Implicit converts a string into a parameter.</summary>
1821 public static implicit operator FormatParam(string value)
1823 return new FormatParam(value);
1826 /// <summary>Gets the integer value of the parameter. If a string was stored, 0 is returned.</summary>
1827 public int Int32 { get { return _int32; }
}
1829 /// <summary>Gets the string value of the parameter. If an Int32 or a null String were stored, an empty string is returned.</summary>
1830 public string String { get { return _string ?? string.Empty; }
}
1832 /// <summary>Gets the string or the integer value as an object.</summary>
1833 public object Object { get { return _string ?? (object)_int32; }
}
1836 /// <summary>Provides a basic stack data structure.</summary>
1837 /// <typeparam name="T">Specifies the type of data in the stack.</typeparam>
1838 private sealed class LowLevelStack
1840 private const int DefaultSize
= 4;
1841 private FormatParam
[] _arr
;
1844 public LowLevelStack() { _arr = new FormatParam[DefaultSize]; }
1846 public FormatParam
Pop()
1849 throw new InvalidOperationException("Terminfo: Invalid Stack");
1851 var item
= _arr
[--_count
];
1852 _arr
[_count
] = default(FormatParam
);
1856 public void Push(FormatParam item
)
1858 if (_arr
.Length
== _count
){
1859 var newArr
= new FormatParam
[_arr
.Length
* 2];
1860 Array
.Copy(_arr
, 0, newArr
, 0, _arr
.Length
);
1863 _arr
[_count
++] = item
;
1868 Array
.Clear(_arr
, 0, _count
);
1875 Hashtable map
= new Hashtable ();
1876 Hashtable starts
= new Hashtable ();
1878 public void AddMapping (TermInfoStrings key
, byte [] val
)
1880 if (val
.Length
== 0)
1884 starts
[(int) val
[0]] = true;
1891 public bool StartsWith (int c
)
1893 return (starts
[c
] != null);
1896 public TermInfoStrings
Match (char [] buffer
, int offset
, int length
, out int used
)
1898 foreach (byte [] bytes
in map
.Keys
) {
1899 for (int i
= 0; i
< bytes
.Length
&& i
< length
; i
++) {
1900 if ((char) bytes
[i
] != buffer
[offset
+ i
])
1903 if (bytes
.Length
- 1 == i
) {
1904 used
= bytes
.Length
;
1905 return (TermInfoStrings
) map
[bytes
];
1911 return (TermInfoStrings
) (-1);