Jo Shields a575963da9 Imported Upstream version 3.6.0
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
2014-08-13 10:39:27 +01:00

1465 lines
35 KiB
C#

//
// System.ConsoleDriver
//
// Authors:
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
//
// (C) 2005,2006 Novell, Inc (http://www.novell.com)
//
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if !NET_2_1
//
// Defining this writes the output to console.log
//#define DEBUG
using System.Collections;
using System.IO;
using System.Text;
using System.Runtime.InteropServices;
namespace System {
class TermInfoDriver : IConsoleDriver {
// This points to a variable that is updated by unmanage code on window size changes.
unsafe static int *native_terminal_size;
// The current size that we believe we have
static int terminal_size;
//static uint flag = 0xdeadbeef;
readonly static string [] locations = { "/etc/terminfo", "/usr/share/terminfo", "/usr/lib/terminfo" };
TermInfoReader reader;
int cursorLeft;
int cursorTop;
string title = String.Empty;
string titleFormat = String.Empty;
bool cursorVisible = true;
string csrVisible;
string csrInvisible;
string clear;
string bell;
string term;
StreamReader stdin;
CStreamWriter stdout;
int windowWidth;
int windowHeight;
//int windowTop;
//int windowLeft;
int bufferHeight;
int bufferWidth;
char [] buffer;
int readpos;
int writepos;
string keypadXmit, keypadLocal;
bool controlCAsInput;
bool inited;
object initLock = new object ();
bool initKeys;
string origPair;
string origColors;
string cursorAddress;
ConsoleColor fgcolor = ConsoleColor.White;
ConsoleColor bgcolor = ConsoleColor.Black;
bool color16 = false; // terminal supports 16 colors
string setlfgcolor;
string setlbgcolor;
string setfgcolor;
string setbgcolor;
bool noGetPosition;
Hashtable keymap;
ByteMatcher rootmap;
bool home_1_1; // if true, we have to add 1 to x and y when using cursorAddress
int rl_startx = -1, rl_starty = -1;
byte [] control_characters; // Indexed by ControlCharacters.XXXXXX
#if DEBUG
StreamWriter logger;
#endif
static string SearchTerminfo (string term)
{
if (term == null || term == String.Empty)
return null;
// Ignore TERMINFO and TERMINFO_DIRS by now
//string terminfo = Environment.GetEnvironmentVariable ("TERMINFO");
//string terminfoDirs = Environment.GetEnvironmentVariable ("TERMINFO_DIRS");
foreach (string dir in locations) {
if (!Directory.Exists (dir))
continue;
string path = Path.Combine (dir, term.Substring (0, 1));
if (!Directory.Exists (dir))
continue;
path = Path.Combine (path, term);
if (!File.Exists (path))
continue;
return path;
}
return null;
}
void WriteConsole (string str)
{
if (str == null)
return;
stdout.InternalWriteString (str);
}
public TermInfoDriver ()
: this (Environment.GetEnvironmentVariable ("TERM"))
{
}
public TermInfoDriver (string term)
{
#if DEBUG
File.Delete ("console.log");
logger = new StreamWriter (File.OpenWrite ("console.log"));
#endif
this.term = term;
if (term == "xterm") {
reader = new TermInfoReader (term, KnownTerminals.xterm);
color16 = true;
} else if (term == "linux") {
reader = new TermInfoReader (term, KnownTerminals.linux);
color16 = true;
} else {
string filename = SearchTerminfo (term);
if (filename != null)
reader = new TermInfoReader (term, filename);
}
if (reader == null)
reader = new TermInfoReader (term, KnownTerminals.ansi);
if (!(Console.stdout is CStreamWriter)) {
// Application set its own stdout, we need a reference to the real stdout
stdout = new CStreamWriter (Console.OpenStandardOutput (0), Console.OutputEncoding);
((StreamWriter) stdout).AutoFlush = true;
} else {
stdout = (CStreamWriter) Console.stdout;
}
}
public bool Initialized {
get { return inited; }
}
public void Init ()
{
if (inited)
return;
lock (initLock){
if (inited)
return;
inited = true;
/* This should not happen any more, since it is checked for in Console */
if (!ConsoleDriver.IsConsole)
throw new IOException ("Not a tty.");
ConsoleDriver.SetEcho (false);
string endString = null;
keypadXmit = reader.Get (TermInfoStrings.KeypadXmit);
keypadLocal = reader.Get (TermInfoStrings.KeypadLocal);
if (keypadXmit != null) {
WriteConsole (keypadXmit); // Needed to get the arrows working
if (keypadLocal != null)
endString += keypadLocal;
}
origPair = reader.Get (TermInfoStrings.OrigPair);
origColors = reader.Get (TermInfoStrings.OrigColors);
setfgcolor = MangleParameters (reader.Get (TermInfoStrings.SetAForeground));
setbgcolor = MangleParameters (reader.Get (TermInfoStrings.SetABackground));
// lighter fg colours are 90 -> 97 rather than 30 -> 37
setlfgcolor = color16 ? setfgcolor.Replace ("[3", "[9") : setfgcolor;
// lighter bg colours are 100 -> 107 rather than 40 -> 47
setlbgcolor = color16 ? setbgcolor.Replace ("[4", "[10") : setbgcolor;
string resetColors = (origColors == null) ? origPair : origColors;
if (resetColors != null)
endString += resetColors;
unsafe {
if (!ConsoleDriver.TtySetup (keypadXmit, endString, out control_characters, out native_terminal_size)){
control_characters = new byte [17];
native_terminal_size = null;
//throw new IOException ("Error initializing terminal.");
}
}
stdin = new StreamReader (Console.OpenStandardInput (0), Console.InputEncoding);
clear = reader.Get (TermInfoStrings.ClearScreen);
bell = reader.Get (TermInfoStrings.Bell);
if (clear == null) {
clear = reader.Get (TermInfoStrings.CursorHome);
clear += reader.Get (TermInfoStrings.ClrEos);
}
csrVisible = reader.Get (TermInfoStrings.CursorNormal);
if (csrVisible == null)
csrVisible = reader.Get (TermInfoStrings.CursorVisible);
csrInvisible = reader.Get (TermInfoStrings.CursorInvisible);
if (term == "cygwin" || term == "linux" || (term != null && term.StartsWith ("xterm")) ||
term == "rxvt" || term == "dtterm") {
titleFormat = "\x1b]0;{0}\x7"; // icon + window title
} else if (term == "iris-ansi") {
titleFormat = "\x1bP1.y{0}\x1b\\"; // not tested
} else if (term == "sun-cmd") {
titleFormat = "\x1b]l{0}\x1b\\"; // not tested
}
cursorAddress = reader.Get (TermInfoStrings.CursorAddress);
if (cursorAddress != null) {
string result = cursorAddress.Replace ("%i", String.Empty);
home_1_1 = (cursorAddress != result);
cursorAddress = MangleParameters (result);
}
GetCursorPosition ();
#if DEBUG
logger.WriteLine ("noGetPosition: {0} left: {1} top: {2}", noGetPosition, cursorLeft, cursorTop);
logger.Flush ();
#endif
if (noGetPosition) {
WriteConsole (clear);
cursorLeft = 0;
cursorTop = 0;
}
}
}
static string MangleParameters (string str)
{
if (str == null)
return null;
str = str.Replace ("{", "{{");
str = str.Replace ("}", "}}");
str = str.Replace ("%p1%d", "{0}");
return str.Replace ("%p2%d", "{1}");
}
static int TranslateColor (ConsoleColor desired, out bool light)
{
switch (desired) {
// Dark colours
case ConsoleColor.Black:
light = false;
return 0;
case ConsoleColor.DarkRed:
light = false;
return 1;
case ConsoleColor.DarkGreen:
light = false;
return 2;
case ConsoleColor.DarkYellow:
light = false;
return 3;
case ConsoleColor.DarkBlue:
light = false;
return 4;
case ConsoleColor.DarkMagenta:
light = false;
return 5;
case ConsoleColor.DarkCyan:
light = false;
return 6;
case ConsoleColor.Gray:
light = false;
return 7;
// Light colours
case ConsoleColor.DarkGray:
light = true;
return 0;
case ConsoleColor.Red:
light = true;
return 1;
case ConsoleColor.Green:
light = true;
return 2;
case ConsoleColor.Yellow:
light = true;
return 3;
case ConsoleColor.Blue:
light = true;
return 4;
case ConsoleColor.Magenta:
light = true;
return 5;
case ConsoleColor.Cyan:
light = true;
return 6;
case ConsoleColor.White:
light = true;
return 7;
}
light = false;
return 0;
}
void IncrementX ()
{
cursorLeft++;
if (cursorLeft >= WindowWidth) {
cursorTop++;
cursorLeft = 0;
if (cursorTop >= WindowHeight) {
// Writing beyond the initial screen
if (rl_starty != -1) rl_starty--;
cursorTop--;
}
}
}
// Should never get called unless inited
public void WriteSpecialKey (ConsoleKeyInfo key)
{
switch (key.Key) {
case ConsoleKey.Backspace:
if (cursorLeft > 0) {
if (cursorLeft <= rl_startx && cursorTop == rl_starty)
break;
cursorLeft--;
SetCursorPosition (cursorLeft, cursorTop);
WriteConsole (" ");
SetCursorPosition (cursorLeft, cursorTop);
}
#if DEBUG
logger.WriteLine ("BS left: {0} top: {1}", cursorLeft, cursorTop);
logger.Flush ();
#endif
break;
case ConsoleKey.Tab:
int n = 8 - (cursorLeft % 8);
for (int i = 0; i < n; i++){
IncrementX ();
}
WriteConsole ("\t");
break;
case ConsoleKey.Clear:
WriteConsole (clear);
cursorLeft = 0;
cursorTop = 0;
break;
case ConsoleKey.Enter:
break;
default:
break;
}
#if DEBUG
logger.WriteLine ("left: {0} top: {1}", cursorLeft, cursorTop);
logger.Flush ();
#endif
}
// Should never get called unless inited
public void WriteSpecialKey (char c)
{
WriteSpecialKey (CreateKeyInfoFromInt (c, false));
}
public bool IsSpecialKey (ConsoleKeyInfo key)
{
if (!inited)
return false;
switch (key.Key) {
case ConsoleKey.Backspace:
return true;
case ConsoleKey.Tab:
return true;
case ConsoleKey.Clear:
return true;
case ConsoleKey.Enter:
cursorLeft = 0;
cursorTop++;
if (cursorTop >= WindowHeight) {
cursorTop--;
//TODO: scroll up
}
return false;
default:
// CStreamWriter will handle writing this key
IncrementX ();
return false;
}
}
public bool IsSpecialKey (char c)
{
return IsSpecialKey (CreateKeyInfoFromInt (c, false));
}
public ConsoleColor BackgroundColor {
get {
if (!inited) {
Init ();
}
return bgcolor;
}
set {
if (!inited) {
Init ();
}
bgcolor = value;
bool light;
int colour = TranslateColor (value, out light);
if (light)
WriteConsole (String.Format (setlbgcolor, colour));
else
WriteConsole (String.Format (setbgcolor, colour));
}
}
public ConsoleColor ForegroundColor {
get {
if (!inited) {
Init ();
}
return fgcolor;
}
set {
if (!inited) {
Init ();
}
fgcolor = value;
bool light;
int colour = TranslateColor (value, out light);
if (light)
WriteConsole (String.Format (setlfgcolor, colour));
else
WriteConsole (String.Format (setfgcolor, colour));
}
}
void GetCursorPosition ()
{
int row = 0, col = 0;
int b;
// First, get any data in the input buffer. Merely reduces the likelyhood of getting an error
int inqueue = ConsoleDriver.InternalKeyAvailable (0);
while (inqueue-- > 0){
b = stdin.Read ();
AddToBuffer (b);
}
// Then try to probe for the cursor coordinates
WriteConsole ("\x1b[6n");
if (ConsoleDriver.InternalKeyAvailable (1000) <= 0) {
noGetPosition = true;
return;
}
b = stdin.Read ();
while (b != '\x1b') {
AddToBuffer (b);
if (ConsoleDriver.InternalKeyAvailable (100) <= 0)
return;
b = stdin.Read ();
}
b = stdin.Read ();
if (b != '[') {
AddToBuffer ('\x1b');
AddToBuffer (b);
return;
}
b = stdin.Read ();
if (b != ';') {
row = b - '0';
b = stdin.Read ();
while ((b >= '0') && (b <= '9')) {
row = row * 10 + b - '0';
b = stdin.Read ();
}
// Row/col is 0 based
row --;
}
b = stdin.Read ();
if (b != 'R') {
col = b - '0';
b = stdin.Read ();
while ((b >= '0') && (b <= '9')) {
col = col * 10 + b - '0';
b = stdin.Read ();
}
// Row/col is 0 based
col --;
}
#if DEBUG
logger.WriteLine ("GetCursorPosition: {0}, {1}", col, row);
logger.Flush ();
#endif
cursorLeft = col;
cursorTop = row;
}
public int BufferHeight {
get {
if (!inited) {
Init ();
}
CheckWindowDimensions ();
return bufferHeight;
}
set {
if (!inited) {
Init ();
}
throw new NotSupportedException ();
}
}
public int BufferWidth {
get {
if (!inited) {
Init ();
}
CheckWindowDimensions ();
return bufferWidth;
}
set {
if (!inited) {
Init ();
}
throw new NotSupportedException ();
}
}
public bool CapsLock {
get {
if (!inited) {
Init ();
}
return false;
}
}
public int CursorLeft {
get {
if (!inited) {
Init ();
}
return cursorLeft;
}
set {
if (!inited) {
Init ();
}
SetCursorPosition (value, CursorTop);
}
}
public int CursorTop {
get {
if (!inited) {
Init ();
}
return cursorTop;
}
set {
if (!inited) {
Init ();
}
SetCursorPosition (CursorLeft, value);
}
}
public bool CursorVisible {
get {
if (!inited) {
Init ();
}
return cursorVisible;
}
set {
if (!inited) {
Init ();
}
cursorVisible = value;
WriteConsole ((value ? csrVisible : csrInvisible));
}
}
// we have CursorNormal vs. CursorVisible...
[MonoTODO]
public int CursorSize {
get {
if (!inited) {
Init ();
}
return 1;
}
set {
if (!inited) {
Init ();
}
}
}
public bool KeyAvailable {
get {
if (!inited) {
Init ();
}
return (writepos > readpos || ConsoleDriver.InternalKeyAvailable (0) > 0);
}
}
// We don't know these next 2 values, so return something reasonable
public int LargestWindowHeight {
get { return WindowHeight; }
}
public int LargestWindowWidth {
get { return WindowWidth; }
}
public bool NumberLock {
get {
if (!inited) {
Init ();
}
return false;
}
}
public string Title {
get {
if (!inited) {
Init ();
}
return title;
}
set {
if (!inited) {
Init ();
}
title = value;
WriteConsole (String.Format (titleFormat, value));
}
}
public bool TreatControlCAsInput {
get {
if (!inited) {
Init ();
}
return controlCAsInput;
}
set {
if (!inited) {
Init ();
}
if (controlCAsInput == value)
return;
ConsoleDriver.SetBreak (value);
controlCAsInput = value;
}
}
//
// Requries that caller calls Init () if not !inited.
//
unsafe void CheckWindowDimensions ()
{
if (native_terminal_size == null || terminal_size == *native_terminal_size)
return;
if (*native_terminal_size == -1){
int c = reader.Get (TermInfoNumbers.Columns);
if (c != 0)
windowWidth = c;
c = reader.Get (TermInfoNumbers.Lines);
if (c != 0)
windowHeight = c;
} else {
terminal_size = *native_terminal_size;
windowWidth = terminal_size >> 16;
windowHeight = terminal_size & 0xffff;
}
bufferHeight = windowHeight;
bufferWidth = windowWidth;
}
public int WindowHeight {
get {
if (!inited) {
Init ();
}
CheckWindowDimensions ();
return windowHeight;
}
set {
if (!inited) {
Init ();
}
throw new NotSupportedException ();
}
}
public int WindowLeft {
get {
if (!inited) {
Init ();
}
//CheckWindowDimensions ();
return 0;
}
set {
if (!inited) {
Init ();
}
throw new NotSupportedException ();
}
}
public int WindowTop {
get {
if (!inited) {
Init ();
}
//CheckWindowDimensions ();
return 0;
}
set {
if (!inited) {
Init ();
}
throw new NotSupportedException ();
}
}
public int WindowWidth {
get {
if (!inited) {
Init ();
}
CheckWindowDimensions ();
return windowWidth;
}
set {
if (!inited) {
Init ();
}
throw new NotSupportedException ();
}
}
public void Clear ()
{
if (!inited) {
Init ();
}
WriteConsole (clear);
cursorLeft = 0;
cursorTop = 0;
}
public void Beep (int frequency, int duration)
{
if (!inited) {
Init ();
}
WriteConsole (bell);
}
public void MoveBufferArea (int sourceLeft, int sourceTop, int sourceWidth, int sourceHeight,
int targetLeft, int targetTop, Char sourceChar,
ConsoleColor sourceForeColor, ConsoleColor sourceBackColor)
{
if (!inited) {
Init ();
}
throw new NotImplementedException ();
}
void AddToBuffer (int b)
{
if (buffer == null) {
buffer = new char [1024];
} else if (writepos >= buffer.Length) {
char [] newbuf = new char [buffer.Length * 2];
Buffer.BlockCopy (buffer, 0, newbuf, 0, buffer.Length);
buffer = newbuf;
}
buffer [writepos++] = (char) b;
}
void AdjustBuffer ()
{
if (readpos >= writepos) {
readpos = writepos = 0;
}
}
ConsoleKeyInfo CreateKeyInfoFromInt (int n, bool alt)
{
char c = (char) n;
ConsoleKey key = (ConsoleKey)n;
bool shift = false;
bool ctrl = false;
switch (n){
case 10:
key = ConsoleKey.Enter;
break;
case 0x20:
key = ConsoleKey.Spacebar;
break;
case 45:
key = ConsoleKey.Subtract;
break;
case 43:
key = ConsoleKey.Add;
break;
case 47:
key = ConsoleKey.Divide;
break;
case 42:
key = ConsoleKey.Multiply;
break;
case 8: case 9: case 12: case 13: case 19:
/* Values in ConsoleKey */
break;
case 27:
key = ConsoleKey.Escape;
break;
default:
if (n >= 1 && n <= 26) {
// For Ctrl-a to Ctrl-z.
ctrl = true;
key = ConsoleKey.A + n - 1;
} else if (n >= 'a' && n <= 'z') {
key = ConsoleKey.A - 'a' + n;
} else if (n >= 'A' && n <= 'Z') {
shift = true;
} else if (n >= '0' && n <= '9') {
} else
key = 0;
break;
}
return new ConsoleKeyInfo (c, key, shift, alt, ctrl);
}
object GetKeyFromBuffer (bool cooked)
{
if (readpos >= writepos)
return null;
int next = buffer [readpos];
if (!cooked || !rootmap.StartsWith (next)) {
readpos++;
AdjustBuffer ();
return CreateKeyInfoFromInt (next, false);
}
int used;
TermInfoStrings str = rootmap.Match (buffer, readpos, writepos - readpos, out used);
if ((int) str == -1){
// Escape sequences: alt keys are sent as ESC-key
if (buffer [readpos] == 27 && (writepos - readpos) >= 2){
readpos += 2;
AdjustBuffer ();
if (buffer [readpos+1] == 127)
return new ConsoleKeyInfo ((char)8, ConsoleKey.Backspace, false, true, false);
return CreateKeyInfoFromInt (buffer [readpos+1], true);
} else
return null;
}
ConsoleKeyInfo key;
if (keymap [str] != null) {
key = (ConsoleKeyInfo) keymap [str];
} else {
readpos++;
AdjustBuffer ();
return CreateKeyInfoFromInt (next, false);
}
readpos += used;
AdjustBuffer ();
return key;
}
ConsoleKeyInfo ReadKeyInternal (out bool fresh)
{
if (!inited)
Init ();
InitKeys ();
object o;
if ((o = GetKeyFromBuffer (true)) == null) {
do {
if (ConsoleDriver.InternalKeyAvailable (150) > 0) {
do {
AddToBuffer (stdin.Read ());
} while (ConsoleDriver.InternalKeyAvailable (0) > 0);
} else if (stdin.DataAvailable ()) {
do {
AddToBuffer (stdin.Read ());
} while (stdin.DataAvailable ());
} else {
if ((o = GetKeyFromBuffer (false)) != null)
break;
AddToBuffer (stdin.Read ());
}
o = GetKeyFromBuffer (true);
} while (o == null);
// freshly read character
fresh = true;
} else {
// this char was pre-buffered (e.g. not fresh)
fresh = false;
}
return (ConsoleKeyInfo) o;
}
#region Input echoing optimization
bool InputPending ()
{
// check if we've got pending input we can read immediately
return readpos < writepos || stdin.DataAvailable ();
}
char [] echobuf = null;
int echon = 0;
// Queues a character to be echo'd back to the console
void QueueEcho (char c)
{
if (echobuf == null)
echobuf = new char [1024];
echobuf[echon++] = c;
if (echon == echobuf.Length || !InputPending ()) {
// blit our echo buffer to the console
stdout.InternalWriteChars (echobuf, echon);
echon = 0;
}
}
// Queues a key to be echo'd back to the console
void Echo (ConsoleKeyInfo key)
{
if (!IsSpecialKey (key)) {
QueueEcho (key.KeyChar);
return;
}
// flush pending echo's
EchoFlush ();
WriteSpecialKey (key);
}
// Flush the pending echo queue
void EchoFlush ()
{
if (echon == 0)
return;
// flush our echo buffer to the console
stdout.InternalWriteChars (echobuf, echon);
echon = 0;
}
#endregion
public int Read ([In, Out] char [] dest, int index, int count)
{
bool fresh, echo = false;
StringBuilder sbuf;
ConsoleKeyInfo key;
int BoL = 0; // Beginning-of-Line marker (can't backspace beyond this)
object o;
char c;
sbuf = new StringBuilder ();
// consume buffered keys first (do not echo, these have already been echo'd)
while (true) {
if ((o = GetKeyFromBuffer (true)) == null)
break;
key = (ConsoleKeyInfo) o;
c = key.KeyChar;
if (key.Key != ConsoleKey.Backspace) {
if (key.Key == ConsoleKey.Enter)
BoL = sbuf.Length;
sbuf.Append (c);
} else if (sbuf.Length > BoL) {
sbuf.Length--;
}
}
// continue reading until Enter is hit
rl_startx = cursorLeft;
rl_starty = cursorTop;
do {
key = ReadKeyInternal (out fresh);
echo = echo || fresh;
c = key.KeyChar;
if (key.Key != ConsoleKey.Backspace) {
if (key.Key == ConsoleKey.Enter)
BoL = sbuf.Length;
sbuf.Append (c);
} else if (sbuf.Length > BoL) {
sbuf.Length--;
} else {
continue;
}
// echo fresh keys back to the console
if (echo)
Echo (key);
} while (key.Key != ConsoleKey.Enter);
EchoFlush ();
rl_startx = -1;
rl_starty = -1;
// copy up to count chars into dest
int nread = 0;
while (count > 0 && nread < sbuf.Length) {
dest[index + nread] = sbuf[nread];
nread++;
count--;
}
// put the rest back into our key buffer
for (int i = nread; i < sbuf.Length; i++)
AddToBuffer (sbuf[i]);
return nread;
}
public ConsoleKeyInfo ReadKey (bool intercept)
{
bool fresh;
ConsoleKeyInfo key = ReadKeyInternal (out fresh);
if (!intercept && fresh) {
// echo the fresh key back to the console
Echo (key);
EchoFlush ();
}
return key;
}
public string ReadLine ()
{
if (!inited)
Init ();
// Hack to make Iron Python work (since it goes behind our backs
// when writing to the console thus preventing us from keeping
// cursor state normally).
GetCursorPosition ();
StringBuilder builder = new StringBuilder ();
bool fresh, echo = false;
ConsoleKeyInfo key;
char c;
rl_startx = cursorLeft;
rl_starty = cursorTop;
char eof = (char) control_characters [ControlCharacters.EOF];
do {
key = ReadKeyInternal (out fresh);
echo = echo || fresh;
c = key.KeyChar;
// EOF -> Ctrl-D (EOT) pressed.
if (c == eof && c != 0 && builder.Length == 0)
return null;
if (key.Key != ConsoleKey.Enter) {
if (key.Key != ConsoleKey.Backspace) {
builder.Append (c);
} else if (builder.Length > 0) {
builder.Length--;
} else {
// skips over echoing the key to the console
continue;
}
}
// echo fresh keys back to the console
if (echo)
Echo (key);
} while (key.Key != ConsoleKey.Enter);
EchoFlush ();
rl_startx = -1;
rl_starty = -1;
return builder.ToString ();
}
public void ResetColor ()
{
if (!inited) {
Init ();
}
string str = (origPair != null) ? origPair : origColors;
WriteConsole (str);
}
public void SetBufferSize (int width, int height)
{
if (!inited) {
Init ();
}
throw new NotImplementedException (String.Empty);
}
public void SetCursorPosition (int left, int top)
{
if (!inited) {
Init ();
}
CheckWindowDimensions ();
if (left < 0 || left >= bufferWidth)
throw new ArgumentOutOfRangeException ("left", "Value must be positive and below the buffer width.");
if (top < 0 || top >= bufferHeight)
throw new ArgumentOutOfRangeException ("top", "Value must be positive and below the buffer height.");
// Either CursorAddress or nothing.
// We might want to play with up/down/left/right/home when ca is not available.
if (cursorAddress == null)
throw new NotSupportedException ("This terminal does not suport setting the cursor position.");
int one = (home_1_1 ? 1 : 0);
WriteConsole (String.Format (cursorAddress, top + one, left + one));
cursorLeft = left;
cursorTop = top;
}
public void SetWindowPosition (int left, int top)
{
if (!inited) {
Init ();
}
// No need to throw exceptions here.
//throw new NotSupportedException ();
}
public void SetWindowSize (int width, int height)
{
if (!inited) {
Init ();
}
// No need to throw exceptions here.
//throw new NotSupportedException ();
}
void CreateKeyMap ()
{
keymap = new Hashtable ();
keymap [TermInfoStrings.KeyBackspace] = new ConsoleKeyInfo ('\0', ConsoleKey.Backspace, false, false, false);
keymap [TermInfoStrings.KeyClear] = new ConsoleKeyInfo ('\0', ConsoleKey.Clear, false, false, false);
// Delete character...
keymap [TermInfoStrings.KeyDown] = new ConsoleKeyInfo ('\0', ConsoleKey.DownArrow, false, false, false);
keymap [TermInfoStrings.KeyF1] = new ConsoleKeyInfo ('\0', ConsoleKey.F1, false, false, false);
keymap [TermInfoStrings.KeyF10] = new ConsoleKeyInfo ('\0', ConsoleKey.F10, false, false, false);
keymap [TermInfoStrings.KeyF2] = new ConsoleKeyInfo ('\0', ConsoleKey.F2, false, false, false);
keymap [TermInfoStrings.KeyF3] = new ConsoleKeyInfo ('\0', ConsoleKey.F3, false, false, false);
keymap [TermInfoStrings.KeyF4] = new ConsoleKeyInfo ('\0', ConsoleKey.F4, false, false, false);
keymap [TermInfoStrings.KeyF5] = new ConsoleKeyInfo ('\0', ConsoleKey.F5, false, false, false);
keymap [TermInfoStrings.KeyF6] = new ConsoleKeyInfo ('\0', ConsoleKey.F6, false, false, false);
keymap [TermInfoStrings.KeyF7] = new ConsoleKeyInfo ('\0', ConsoleKey.F7, false, false, false);
keymap [TermInfoStrings.KeyF8] = new ConsoleKeyInfo ('\0', ConsoleKey.F8, false, false, false);
keymap [TermInfoStrings.KeyF9] = new ConsoleKeyInfo ('\0', ConsoleKey.F9, false, false, false);
keymap [TermInfoStrings.KeyHome] = new ConsoleKeyInfo ('\0', ConsoleKey.Home, false, false, false);
keymap [TermInfoStrings.KeyLeft] = new ConsoleKeyInfo ('\0', ConsoleKey.LeftArrow, false, false, false);
keymap [TermInfoStrings.KeyLl] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad1, false, false, false);
keymap [TermInfoStrings.KeyNpage] = new ConsoleKeyInfo ('\0', ConsoleKey.PageDown, false, false, false);
keymap [TermInfoStrings.KeyPpage] = new ConsoleKeyInfo ('\0', ConsoleKey.PageUp, false, false, false);
keymap [TermInfoStrings.KeyRight] = new ConsoleKeyInfo ('\0', ConsoleKey.RightArrow, false, false, false);
keymap [TermInfoStrings.KeySf] = new ConsoleKeyInfo ('\0', ConsoleKey.PageDown, false, false, false);
keymap [TermInfoStrings.KeySr] = new ConsoleKeyInfo ('\0', ConsoleKey.PageUp, false, false, false);
keymap [TermInfoStrings.KeyUp] = new ConsoleKeyInfo ('\0', ConsoleKey.UpArrow, false, false, false);
keymap [TermInfoStrings.KeyA1] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad7, false, false, false);
keymap [TermInfoStrings.KeyA3] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad9, false, false, false);
keymap [TermInfoStrings.KeyB2] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad5, false, false, false);
keymap [TermInfoStrings.KeyC1] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad1, false, false, false);
keymap [TermInfoStrings.KeyC3] = new ConsoleKeyInfo ('\0', ConsoleKey.NumPad3, false, false, false);
keymap [TermInfoStrings.KeyBtab] = new ConsoleKeyInfo ('\0', ConsoleKey.Tab, true, false, false);
keymap [TermInfoStrings.KeyBeg] = new ConsoleKeyInfo ('\0', ConsoleKey.Home, false, false, false);
keymap [TermInfoStrings.KeyCopy] = new ConsoleKeyInfo ('C', ConsoleKey.C, false, true, false);
keymap [TermInfoStrings.KeyEnd] = new ConsoleKeyInfo ('\0', ConsoleKey.End, false, false, false);
keymap [TermInfoStrings.KeyEnter] = new ConsoleKeyInfo ('\n', ConsoleKey.Enter, false, false, false);
keymap [TermInfoStrings.KeyHelp] = new ConsoleKeyInfo ('\0', ConsoleKey.Help, false, false, false);
keymap [TermInfoStrings.KeyPrint] = new ConsoleKeyInfo ('\0', ConsoleKey.Print, false, false, false);
keymap [TermInfoStrings.KeyUndo] = new ConsoleKeyInfo ('Z', ConsoleKey.Z , false, true, false);
keymap [TermInfoStrings.KeySbeg] = new ConsoleKeyInfo ('\0', ConsoleKey.Home, true, false, false);
keymap [TermInfoStrings.KeyScopy] = new ConsoleKeyInfo ('C', ConsoleKey.C , true, true, false);
keymap [TermInfoStrings.KeySdc] = new ConsoleKeyInfo ('\x9', ConsoleKey.Delete, true, false, false);
keymap [TermInfoStrings.KeyShelp] = new ConsoleKeyInfo ('\0', ConsoleKey.Help, true, false, false);
keymap [TermInfoStrings.KeyShome] = new ConsoleKeyInfo ('\0', ConsoleKey.Home, true, false, false);
keymap [TermInfoStrings.KeySleft] = new ConsoleKeyInfo ('\0', ConsoleKey.LeftArrow, true, false, false);
keymap [TermInfoStrings.KeySprint] = new ConsoleKeyInfo ('\0', ConsoleKey.Print, true, false, false);
keymap [TermInfoStrings.KeySright] = new ConsoleKeyInfo ('\0', ConsoleKey.RightArrow, true, false, false);
keymap [TermInfoStrings.KeySundo] = new ConsoleKeyInfo ('Z', ConsoleKey.Z, true, false, false);
keymap [TermInfoStrings.KeyF11] = new ConsoleKeyInfo ('\0', ConsoleKey.F11, false, false, false);
keymap [TermInfoStrings.KeyF12] = new ConsoleKeyInfo ('\0', ConsoleKey.F12 , false, false, false);
keymap [TermInfoStrings.KeyF13] = new ConsoleKeyInfo ('\0', ConsoleKey.F13, false, false, false);
keymap [TermInfoStrings.KeyF14] = new ConsoleKeyInfo ('\0', ConsoleKey.F14, false, false, false);
keymap [TermInfoStrings.KeyF15] = new ConsoleKeyInfo ('\0', ConsoleKey.F15, false, false, false);
keymap [TermInfoStrings.KeyF16] = new ConsoleKeyInfo ('\0', ConsoleKey.F16, false, false, false);
keymap [TermInfoStrings.KeyF17] = new ConsoleKeyInfo ('\0', ConsoleKey.F17, false, false, false);
keymap [TermInfoStrings.KeyF18] = new ConsoleKeyInfo ('\0', ConsoleKey.F18, false, false, false);
keymap [TermInfoStrings.KeyF19] = new ConsoleKeyInfo ('\0', ConsoleKey.F19, false, false, false);
keymap [TermInfoStrings.KeyF20] = new ConsoleKeyInfo ('\0', ConsoleKey.F20, false, false, false);
keymap [TermInfoStrings.KeyF21] = new ConsoleKeyInfo ('\0', ConsoleKey.F21, false, false, false);
keymap [TermInfoStrings.KeyF22] = new ConsoleKeyInfo ('\0', ConsoleKey.F22, false, false, false);
keymap [TermInfoStrings.KeyF23] = new ConsoleKeyInfo ('\0', ConsoleKey.F23, false, false, false);
keymap [TermInfoStrings.KeyF24] = new ConsoleKeyInfo ('\0', ConsoleKey.F24, false, false, false);
// These were previously missing:
keymap [TermInfoStrings.KeyDc] = new ConsoleKeyInfo ('\0', ConsoleKey.Delete, false, false, false);
keymap [TermInfoStrings.KeyIc] = new ConsoleKeyInfo ('\0', ConsoleKey.Insert, false, false, false);
}
void InitKeys ()
{
if (initKeys)
return;
CreateKeyMap ();
rootmap = new ByteMatcher ();
//
// The keys that we know about and use
//
var UsedKeys = new [] {
TermInfoStrings.KeyBackspace,
TermInfoStrings.KeyClear,
TermInfoStrings.KeyDown,
TermInfoStrings.KeyF1,
TermInfoStrings.KeyF10,
TermInfoStrings.KeyF2,
TermInfoStrings.KeyF3,
TermInfoStrings.KeyF4,
TermInfoStrings.KeyF5,
TermInfoStrings.KeyF6,
TermInfoStrings.KeyF7,
TermInfoStrings.KeyF8,
TermInfoStrings.KeyF9,
TermInfoStrings.KeyHome,
TermInfoStrings.KeyLeft,
TermInfoStrings.KeyLl,
TermInfoStrings.KeyNpage,
TermInfoStrings.KeyPpage,
TermInfoStrings.KeyRight,
TermInfoStrings.KeySf,
TermInfoStrings.KeySr,
TermInfoStrings.KeyUp,
TermInfoStrings.KeyA1,
TermInfoStrings.KeyA3,
TermInfoStrings.KeyB2,
TermInfoStrings.KeyC1,
TermInfoStrings.KeyC3,
TermInfoStrings.KeyBtab,
TermInfoStrings.KeyBeg,
TermInfoStrings.KeyCopy,
TermInfoStrings.KeyEnd,
TermInfoStrings.KeyEnter,
TermInfoStrings.KeyHelp,
TermInfoStrings.KeyPrint,
TermInfoStrings.KeyUndo,
TermInfoStrings.KeySbeg,
TermInfoStrings.KeyScopy,
TermInfoStrings.KeySdc,
TermInfoStrings.KeyShelp,
TermInfoStrings.KeyShome,
TermInfoStrings.KeySleft,
TermInfoStrings.KeySprint,
TermInfoStrings.KeySright,
TermInfoStrings.KeySundo,
TermInfoStrings.KeyF11,
TermInfoStrings.KeyF12,
TermInfoStrings.KeyF13,
TermInfoStrings.KeyF14,
TermInfoStrings.KeyF15,
TermInfoStrings.KeyF16,
TermInfoStrings.KeyF17,
TermInfoStrings.KeyF18,
TermInfoStrings.KeyF19,
TermInfoStrings.KeyF20,
TermInfoStrings.KeyF21,
TermInfoStrings.KeyF22,
TermInfoStrings.KeyF23,
TermInfoStrings.KeyF24,
// These were missing
TermInfoStrings.KeyDc,
TermInfoStrings.KeyIc
};
foreach (TermInfoStrings tis in UsedKeys)
AddStringMapping (tis);
rootmap.AddMapping (TermInfoStrings.KeyBackspace, new byte [] { control_characters [ControlCharacters.Erase] });
rootmap.Sort ();
initKeys = true;
}
void AddStringMapping (TermInfoStrings s)
{
byte [] bytes = reader.GetStringBytes (s);
if (bytes == null)
return;
rootmap.AddMapping (s, bytes);
}
}
class ByteMatcher {
Hashtable map = new Hashtable ();
Hashtable starts = new Hashtable ();
public void AddMapping (TermInfoStrings key, byte [] val)
{
if (val.Length == 0)
return;
map [val] = key;
starts [(int) val [0]] = true;
}
public void Sort ()
{
}
public bool StartsWith (int c)
{
return (starts [c] != null);
}
public TermInfoStrings Match (char [] buffer, int offset, int length, out int used)
{
foreach (byte [] bytes in map.Keys) {
for (int i = 0; i < bytes.Length && i < length; i++) {
if ((char) bytes [i] != buffer [offset + i])
break;
if (bytes.Length - 1 == i) {
used = bytes.Length;
return (TermInfoStrings) map [bytes];
}
}
}
used = 0;
return (TermInfoStrings) (-1);
}
}
}
#endif