530 lines
18 KiB
C#
530 lines
18 KiB
C#
/*
|
|
* Copyright (c) Contributors, http://opensimulator.org/
|
|
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of the OpenSimulator Project nor the
|
|
* names of its contributors may be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using log4net;
|
|
|
|
namespace OpenSim.Framework.Console
|
|
{
|
|
/// <summary>
|
|
/// A console that uses cursor control and color
|
|
/// </summary>
|
|
public class LocalConsole : CommandConsole
|
|
{
|
|
// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
|
|
|
// private readonly object m_syncRoot = new object();
|
|
private const string LOGLEVEL_NONE = "(none)";
|
|
|
|
private int m_cursorYPosition = -1;
|
|
private int m_cursorXPosition = 0;
|
|
private StringBuilder m_commandLine = new StringBuilder();
|
|
private bool m_echo = true;
|
|
private List<string> m_history = new List<string>();
|
|
|
|
private static readonly ConsoleColor[] Colors = {
|
|
// the dark colors don't seem to be visible on some black background terminals like putty :(
|
|
//ConsoleColor.DarkBlue,
|
|
//ConsoleColor.DarkGreen,
|
|
//ConsoleColor.DarkCyan,
|
|
//ConsoleColor.DarkMagenta,
|
|
//ConsoleColor.DarkYellow,
|
|
ConsoleColor.Gray,
|
|
//ConsoleColor.DarkGray,
|
|
ConsoleColor.Blue,
|
|
ConsoleColor.Green,
|
|
ConsoleColor.Cyan,
|
|
ConsoleColor.Magenta,
|
|
ConsoleColor.Yellow
|
|
};
|
|
|
|
private static ConsoleColor DeriveColor(string input)
|
|
{
|
|
// it is important to do Abs, hash values can be negative
|
|
return Colors[(Math.Abs(input.ToUpper().GetHashCode()) % Colors.Length)];
|
|
}
|
|
|
|
public LocalConsole(string defaultPrompt) : base(defaultPrompt)
|
|
{
|
|
}
|
|
|
|
private void AddToHistory(string text)
|
|
{
|
|
while (m_history.Count >= 100)
|
|
m_history.RemoveAt(0);
|
|
|
|
m_history.Add(text);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the cursor row.
|
|
/// </summary>
|
|
///
|
|
/// <param name="top">
|
|
/// Row to set. If this is below 0, then the row is set to 0. If it is equal to the buffer height or greater
|
|
/// then it is set to one less than the height.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The new cursor row.
|
|
/// </returns>
|
|
private int SetCursorTop(int top)
|
|
{
|
|
// From at least mono 2.4.2.3, window resizing can give mono an invalid row and column values. If we try
|
|
// to set a cursor row position with a currently invalid column, mono will throw an exception.
|
|
// Therefore, we need to make sure that the column position is valid first.
|
|
int left = System.Console.CursorLeft;
|
|
|
|
if (left < 0)
|
|
{
|
|
System.Console.CursorLeft = 0;
|
|
}
|
|
else
|
|
{
|
|
int bufferWidth = System.Console.BufferWidth;
|
|
|
|
// On Mono 2.4.2.3 (and possibly above), the buffer value is sometimes erroneously zero (Mantis 4657)
|
|
if (bufferWidth > 0 && left >= bufferWidth)
|
|
System.Console.CursorLeft = bufferWidth - 1;
|
|
}
|
|
|
|
if (top < 0)
|
|
{
|
|
top = 0;
|
|
}
|
|
else
|
|
{
|
|
int bufferHeight = System.Console.BufferHeight;
|
|
|
|
// On Mono 2.4.2.3 (and possibly above), the buffer value is sometimes erroneously zero (Mantis 4657)
|
|
if (bufferHeight > 0 && top >= bufferHeight)
|
|
top = bufferHeight - 1;
|
|
}
|
|
|
|
System.Console.CursorTop = top;
|
|
|
|
return top;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the cursor column.
|
|
/// </summary>
|
|
///
|
|
/// <param name="left">
|
|
/// Column to set. If this is below 0, then the column is set to 0. If it is equal to the buffer width or greater
|
|
/// then it is set to one less than the width.
|
|
/// </param>
|
|
/// <returns>
|
|
/// The new cursor column.
|
|
/// </returns>
|
|
private int SetCursorLeft(int left)
|
|
{
|
|
// From at least mono 2.4.2.3, window resizing can give mono an invalid row and column values. If we try
|
|
// to set a cursor column position with a currently invalid row, mono will throw an exception.
|
|
// Therefore, we need to make sure that the row position is valid first.
|
|
int top = System.Console.CursorTop;
|
|
|
|
if (top < 0)
|
|
{
|
|
System.Console.CursorTop = 0;
|
|
}
|
|
else
|
|
{
|
|
int bufferHeight = System.Console.BufferHeight;
|
|
// On Mono 2.4.2.3 (and possibly above), the buffer value is sometimes erroneously zero (Mantis 4657)
|
|
if (bufferHeight > 0 && top >= bufferHeight)
|
|
System.Console.CursorTop = bufferHeight - 1;
|
|
}
|
|
|
|
if (left < 0)
|
|
{
|
|
left = 0;
|
|
}
|
|
else
|
|
{
|
|
int bufferWidth = System.Console.BufferWidth;
|
|
|
|
// On Mono 2.4.2.3 (and possibly above), the buffer value is sometimes erroneously zero (Mantis 4657)
|
|
if (bufferWidth > 0 && left >= bufferWidth)
|
|
left = bufferWidth - 1;
|
|
}
|
|
|
|
System.Console.CursorLeft = left;
|
|
|
|
return left;
|
|
}
|
|
|
|
private void Show()
|
|
{
|
|
lock (m_commandLine)
|
|
{
|
|
if (m_cursorYPosition == -1 || System.Console.BufferWidth == 0)
|
|
return;
|
|
|
|
int xc = prompt.Length + m_cursorXPosition;
|
|
int new_x = xc % System.Console.BufferWidth;
|
|
int new_y = m_cursorYPosition + xc / System.Console.BufferWidth;
|
|
int end_y = m_cursorYPosition + (m_commandLine.Length + prompt.Length) / System.Console.BufferWidth;
|
|
|
|
if (end_y >= System.Console.BufferHeight) // wrap
|
|
{
|
|
m_cursorYPosition--;
|
|
new_y--;
|
|
SetCursorLeft(0);
|
|
SetCursorTop(System.Console.BufferHeight - 1);
|
|
System.Console.WriteLine(" ");
|
|
}
|
|
|
|
m_cursorYPosition = SetCursorTop(m_cursorYPosition);
|
|
SetCursorLeft(0);
|
|
|
|
if (m_echo)
|
|
System.Console.Write("{0}{1}", prompt, m_commandLine);
|
|
else
|
|
System.Console.Write("{0}", prompt);
|
|
|
|
SetCursorTop(new_y);
|
|
SetCursorLeft(new_x);
|
|
}
|
|
}
|
|
|
|
public override void LockOutput()
|
|
{
|
|
Monitor.Enter(m_commandLine);
|
|
try
|
|
{
|
|
if (m_cursorYPosition != -1)
|
|
{
|
|
m_cursorYPosition = SetCursorTop(m_cursorYPosition);
|
|
System.Console.CursorLeft = 0;
|
|
|
|
int count = m_commandLine.Length + prompt.Length;
|
|
|
|
while (count-- > 0)
|
|
System.Console.Write(" ");
|
|
|
|
m_cursorYPosition = SetCursorTop(m_cursorYPosition);
|
|
SetCursorLeft(0);
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
}
|
|
|
|
public override void UnlockOutput()
|
|
{
|
|
if (m_cursorYPosition != -1)
|
|
{
|
|
m_cursorYPosition = System.Console.CursorTop;
|
|
Show();
|
|
}
|
|
Monitor.Exit(m_commandLine);
|
|
}
|
|
|
|
private void WriteColorText(ConsoleColor color, string sender)
|
|
{
|
|
try
|
|
{
|
|
lock (this)
|
|
{
|
|
try
|
|
{
|
|
System.Console.ForegroundColor = color;
|
|
System.Console.Write(sender);
|
|
System.Console.ResetColor();
|
|
}
|
|
catch (ArgumentNullException)
|
|
{
|
|
// Some older systems dont support coloured text.
|
|
System.Console.WriteLine(sender);
|
|
}
|
|
}
|
|
}
|
|
catch (ObjectDisposedException)
|
|
{
|
|
}
|
|
}
|
|
|
|
private void WriteLocalText(string text, string level)
|
|
{
|
|
string outText = text;
|
|
|
|
if (level != LOGLEVEL_NONE)
|
|
{
|
|
string regex = @"^(?<Front>.*?)\[(?<Category>[^\]]+)\]:?(?<End>.*)";
|
|
|
|
Regex RE = new Regex(regex, RegexOptions.Multiline);
|
|
MatchCollection matches = RE.Matches(text);
|
|
|
|
if (matches.Count == 1)
|
|
{
|
|
outText = matches[0].Groups["End"].Value;
|
|
System.Console.Write(matches[0].Groups["Front"].Value);
|
|
|
|
System.Console.Write("[");
|
|
WriteColorText(DeriveColor(matches[0].Groups["Category"].Value),
|
|
matches[0].Groups["Category"].Value);
|
|
System.Console.Write("]:");
|
|
}
|
|
else
|
|
{
|
|
outText = outText.Trim();
|
|
}
|
|
}
|
|
|
|
if (level == "error")
|
|
WriteColorText(ConsoleColor.Red, outText);
|
|
else if (level == "warn")
|
|
WriteColorText(ConsoleColor.Yellow, outText);
|
|
else
|
|
System.Console.Write(outText);
|
|
|
|
System.Console.WriteLine();
|
|
}
|
|
|
|
public override void Output(string text)
|
|
{
|
|
Output(text, LOGLEVEL_NONE);
|
|
}
|
|
|
|
public override void Output(string text, string level)
|
|
{
|
|
FireOnOutput(text);
|
|
|
|
lock (m_commandLine)
|
|
{
|
|
if (m_cursorYPosition == -1)
|
|
{
|
|
WriteLocalText(text, level);
|
|
|
|
return;
|
|
}
|
|
|
|
m_cursorYPosition = SetCursorTop(m_cursorYPosition);
|
|
SetCursorLeft(0);
|
|
|
|
int count = m_commandLine.Length + prompt.Length;
|
|
|
|
while (count-- > 0)
|
|
System.Console.Write(" ");
|
|
|
|
m_cursorYPosition = SetCursorTop(m_cursorYPosition);
|
|
SetCursorLeft(0);
|
|
|
|
WriteLocalText(text, level);
|
|
|
|
m_cursorYPosition = System.Console.CursorTop;
|
|
|
|
Show();
|
|
}
|
|
}
|
|
|
|
private bool ContextHelp()
|
|
{
|
|
string[] words = Parser.Parse(m_commandLine.ToString());
|
|
|
|
bool trailingSpace = m_commandLine.ToString().EndsWith(" ");
|
|
|
|
// Allow ? through while typing a URI
|
|
//
|
|
if (words.Length > 0 && words[words.Length-1].StartsWith("http") && !trailingSpace)
|
|
return false;
|
|
|
|
string[] opts = Commands.FindNextOption(words, trailingSpace);
|
|
|
|
if (opts[0].StartsWith("Command help:"))
|
|
Output(opts[0]);
|
|
else
|
|
Output(String.Format("Options: {0}", String.Join(" ", opts)));
|
|
|
|
return true;
|
|
}
|
|
|
|
public override string ReadLine(string p, bool isCommand, bool e)
|
|
{
|
|
m_cursorXPosition = 0;
|
|
prompt = p;
|
|
m_echo = e;
|
|
int historyLine = m_history.Count;
|
|
|
|
SetCursorLeft(0); // Needed for mono
|
|
System.Console.Write(" "); // Needed for mono
|
|
|
|
lock (m_commandLine)
|
|
{
|
|
m_cursorYPosition = System.Console.CursorTop;
|
|
m_commandLine.Remove(0, m_commandLine.Length);
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
Show();
|
|
|
|
ConsoleKeyInfo key = System.Console.ReadKey(true);
|
|
char enteredChar = key.KeyChar;
|
|
|
|
if (!Char.IsControl(enteredChar))
|
|
{
|
|
if (m_cursorXPosition >= 318)
|
|
continue;
|
|
|
|
if (enteredChar == '?' && isCommand)
|
|
{
|
|
if (ContextHelp())
|
|
continue;
|
|
}
|
|
|
|
m_commandLine.Insert(m_cursorXPosition, enteredChar);
|
|
m_cursorXPosition++;
|
|
}
|
|
else
|
|
{
|
|
switch (key.Key)
|
|
{
|
|
case ConsoleKey.Backspace:
|
|
if (m_cursorXPosition == 0)
|
|
break;
|
|
m_commandLine.Remove(m_cursorXPosition-1, 1);
|
|
m_cursorXPosition--;
|
|
|
|
SetCursorLeft(0);
|
|
m_cursorYPosition = SetCursorTop(m_cursorYPosition);
|
|
|
|
if (m_echo)
|
|
System.Console.Write("{0}{1} ", prompt, m_commandLine);
|
|
else
|
|
System.Console.Write("{0}", prompt);
|
|
|
|
break;
|
|
case ConsoleKey.Delete:
|
|
if (m_cursorXPosition == m_commandLine.Length)
|
|
break;
|
|
|
|
m_commandLine.Remove(m_cursorXPosition, 1);
|
|
|
|
SetCursorLeft(0);
|
|
m_cursorYPosition = SetCursorTop(m_cursorYPosition);
|
|
|
|
if (m_echo)
|
|
System.Console.Write("{0}{1} ", prompt, m_commandLine);
|
|
else
|
|
System.Console.Write("{0}", prompt);
|
|
|
|
break;
|
|
case ConsoleKey.End:
|
|
m_cursorXPosition = m_commandLine.Length;
|
|
break;
|
|
case ConsoleKey.Home:
|
|
m_cursorXPosition = 0;
|
|
break;
|
|
case ConsoleKey.UpArrow:
|
|
if (historyLine < 1)
|
|
break;
|
|
historyLine--;
|
|
LockOutput();
|
|
m_commandLine.Remove(0, m_commandLine.Length);
|
|
m_commandLine.Append(m_history[historyLine]);
|
|
m_cursorXPosition = m_commandLine.Length;
|
|
UnlockOutput();
|
|
break;
|
|
case ConsoleKey.DownArrow:
|
|
if (historyLine >= m_history.Count)
|
|
break;
|
|
historyLine++;
|
|
LockOutput();
|
|
if (historyLine == m_history.Count)
|
|
{
|
|
m_commandLine.Remove(0, m_commandLine.Length);
|
|
}
|
|
else
|
|
{
|
|
m_commandLine.Remove(0, m_commandLine.Length);
|
|
m_commandLine.Append(m_history[historyLine]);
|
|
}
|
|
m_cursorXPosition = m_commandLine.Length;
|
|
UnlockOutput();
|
|
break;
|
|
case ConsoleKey.LeftArrow:
|
|
if (m_cursorXPosition > 0)
|
|
m_cursorXPosition--;
|
|
break;
|
|
case ConsoleKey.RightArrow:
|
|
if (m_cursorXPosition < m_commandLine.Length)
|
|
m_cursorXPosition++;
|
|
break;
|
|
case ConsoleKey.Enter:
|
|
SetCursorLeft(0);
|
|
m_cursorYPosition = SetCursorTop(m_cursorYPosition);
|
|
|
|
System.Console.WriteLine();
|
|
//Show();
|
|
|
|
lock (m_commandLine)
|
|
{
|
|
m_cursorYPosition = -1;
|
|
}
|
|
|
|
string commandLine = m_commandLine.ToString();
|
|
|
|
if (isCommand)
|
|
{
|
|
string[] cmd = Commands.Resolve(Parser.Parse(commandLine));
|
|
|
|
if (cmd.Length != 0)
|
|
{
|
|
int index;
|
|
|
|
for (index=0 ; index < cmd.Length ; index++)
|
|
{
|
|
if (cmd[index].Contains(" "))
|
|
cmd[index] = "\"" + cmd[index] + "\"";
|
|
}
|
|
AddToHistory(String.Join(" ", cmd));
|
|
return String.Empty;
|
|
}
|
|
}
|
|
|
|
// If we're not echoing to screen (e.g. a password) then we probably don't want it in history
|
|
if (m_echo && commandLine != "")
|
|
AddToHistory(commandLine);
|
|
|
|
return commandLine;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|