From 54c6a920baa0ef02a9ea09e08cc1effcef3b0a3a Mon Sep 17 00:00:00 2001 From: Melanie Thielker Date: Sat, 7 Feb 2009 12:25:39 +0000 Subject: Replace the console for all OpenSim apps with a new console featuring command line editing, context sensitive help (press ? at any time), command line history, a new plugin command system and new appender features thet let you type while the console is scrolling. Seamlessly integrates the ICommander interfaces. --- OpenSim/Framework/Console/ConsoleBase.cs | 661 +++++++++++++++++----- OpenSim/Framework/Console/ConsoleCallbacksBase.cs | 35 -- OpenSim/Framework/Console/OpenSimAppender.cs | 16 + 3 files changed, 531 insertions(+), 181 deletions(-) delete mode 100644 OpenSim/Framework/Console/ConsoleCallbacksBase.cs (limited to 'OpenSim/Framework/Console') diff --git a/OpenSim/Framework/Console/ConsoleBase.cs b/OpenSim/Framework/Console/ConsoleBase.cs index 30af23a..f990748 100644 --- a/OpenSim/Framework/Console/ConsoleBase.cs +++ b/OpenSim/Framework/Console/ConsoleBase.cs @@ -26,22 +26,250 @@ */ using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Net; +using System.Text; using System.Reflection; -using System.Text.RegularExpressions; +using System.Diagnostics; +using System.Collections.Generic; using log4net; namespace OpenSim.Framework.Console { + public delegate void CommandDelegate(string module, string[] cmd); + + public class Commands + { + private class CommandInfo + { + public string module; + public string help_text; + public string long_help; + public CommandDelegate fn; + } + + private Dictionary tree = + new Dictionary(); + + public List GetHelp() + { + List help = new List(); + + help.AddRange(CollectHelp(tree)); + + help.Sort(); + + return help; + } + + private List CollectHelp(Dictionary dict) + { + List result = new List(); + + foreach (KeyValuePair kvp in dict) + { + if (kvp.Value is Dictionary) + { + result.AddRange(CollectHelp((Dictionary)kvp.Value)); + } + else + { + if (((CommandInfo)kvp.Value).long_help != String.Empty) + result.Add(((CommandInfo)kvp.Value).help_text+" - "+ + ((CommandInfo)kvp.Value).long_help); + } + } + return result; + } + + public void AddCommand(string module, string command, string help, string longhelp, CommandDelegate fn) + { + string[] parts = Parser.Parse(command); + + Dictionary current = tree; + foreach (string s in parts) + { + if (current.ContainsKey(s)) + { + if (current[s] is Dictionary) + { + current = (Dictionary)current[s]; + } + else + return; + } + else + { + current[s] = new Dictionary(); + current = (Dictionary)current[s]; + } + } + + if (current.ContainsKey(String.Empty)) + return; + CommandInfo info = new CommandInfo(); + info.module = module; + info.help_text = help; + info.long_help = longhelp; + info.fn = fn; + current[String.Empty] = info; + } + + public string[] FindNextOption(string[] cmd, bool term) + { + Dictionary current = tree; + + int remaining = cmd.Length; + + foreach (string s in cmd) + { + remaining--; + + List found = new List(); + + foreach (string opt in current.Keys) + { + if (opt.StartsWith(s)) + { + found.Add(opt); + } + } + + if (found.Count == 1 && (remaining != 0 || term)) + { + current = (Dictionary)current[found[0]]; + } + else if (found.Count > 0) + { + return found.ToArray(); + } + else + { + break; +// return new string[] {""}; + } + } + + if (current.Count > 1) + { + List choices = new List(); + + bool addcr = false; + foreach (string s in current.Keys) + { + if (s == String.Empty) + { + CommandInfo ci = (CommandInfo)current[String.Empty]; + if (ci.fn != null) + addcr = true; + } + else + choices.Add(s); + } + if (addcr) + choices.Add(""); + return choices.ToArray(); + } + + if (current.ContainsKey(String.Empty)) + return new string[] { "Command help: "+((CommandInfo)current[String.Empty]).help_text}; + + return new string[] { new List(current.Keys)[0] }; + } + + public string[] Resolve(string[] cmd) + { + string[] result = cmd; + int index = -1; + + Dictionary current = tree; + + foreach (string s in cmd) + { + index++; + + List found = new List(); + + foreach (string opt in current.Keys) + { + if (opt.StartsWith(s)) + { + found.Add(opt); + } + } + + if (found.Count == 1) + { + result[index] = found[0]; + current = (Dictionary)current[found[0]]; + } + else if (found.Count > 0) + { + return new string[0]; + } + else + { + break; + } + } + + if (current.ContainsKey(String.Empty)) + { + CommandInfo ci = (CommandInfo)current[String.Empty]; + if (ci.fn == null) + return new string[0]; + ci.fn(ci.module, result); + return result; + } + return new string[0]; + } + } + + public class Parser + { + public static string[] Parse(string text) + { + List result = new List(); + + int index; + + string[] unquoted = text.Split(new char[] {'"'}); + + for (index = 0 ; index < unquoted.Length ; index++) + { + if (index % 2 == 0) + { + string[] words = unquoted[index].Split(new char[] {' '}); + foreach (string w in words) + { + if (w != String.Empty) + result.Add(w); + } + } + else + { + result.Add(unquoted[index]); + } + } + + return result.ToArray(); + } + } + public class ConsoleBase { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private readonly object m_syncRoot = new object(); - public conscmd_callback m_cmdParser; + private int y = -1; + private int cp = 0; + private int h = 1; + private string prompt = "# "; + private StringBuilder cmdline = new StringBuilder(); + public Commands Commands = new Commands(); + private bool echo = true; + private List history = new List(); + + public object ConsoleScene = null; /// /// The default prompt text. @@ -53,15 +281,19 @@ namespace OpenSim.Framework.Console } protected string m_defaultPrompt; - /// - /// Constructor. - /// - /// - /// - public ConsoleBase(string defaultPrompt, conscmd_callback cmdparser) + public ConsoleBase(string defaultPrompt) { DefaultPrompt = defaultPrompt; - m_cmdParser = cmdparser; + + Commands.AddCommand("console", "help", "help", "Get command list", Help); + } + + private void AddToHistory(string text) + { + while (history.Count >= 100) + history.RemoveAt(0); + + history.Add(text); } /// @@ -95,8 +327,7 @@ namespace OpenSim.Framework.Console /// WriteLine-style message arguments public void Warn(string sender, string format, params object[] args) { - WritePrefixLine(DeriveColor(sender), sender); - WriteNewLine(ConsoleColor.Yellow, format, args); + WriteNewLine(DeriveColor(sender), sender, ConsoleColor.Yellow, format, args); } /// @@ -117,10 +348,8 @@ namespace OpenSim.Framework.Console /// WriteLine-style message arguments public void Notice(string sender, string format, params object[] args) { - WritePrefixLine(DeriveColor(sender), sender); - WriteNewLine(ConsoleColor.White, format, args); + WriteNewLine(DeriveColor(sender), sender, ConsoleColor.White, format, args); } - /// /// Sends an error to the current console output /// @@ -139,8 +368,7 @@ namespace OpenSim.Framework.Console /// WriteLine-style message arguments public void Error(string sender, string format, params object[] args) { - WritePrefixLine(DeriveColor(sender), sender); - Error(format, args); + WriteNewLine(DeriveColor(sender), sender, ConsoleColor.Red, format, args); } /// @@ -161,8 +389,7 @@ namespace OpenSim.Framework.Console /// WriteLine-style message arguments public void Status(string sender, string format, params object[] args) { - WritePrefixLine(DeriveColor(sender), sender); - WriteNewLine(ConsoleColor.Blue, format, args); + WriteNewLine(DeriveColor(sender), sender, ConsoleColor.Blue, format, args); } [Conditional("DEBUG")] @@ -174,12 +401,60 @@ namespace OpenSim.Framework.Console [Conditional("DEBUG")] public void Debug(string sender, string format, params object[] args) { - WritePrefixLine(DeriveColor(sender), sender); - WriteNewLine(ConsoleColor.Gray, format, args); + WriteNewLine(DeriveColor(sender), sender, ConsoleColor.Gray, format, args); + } + + private void WriteNewLine(ConsoleColor senderColor, string sender, ConsoleColor color, string format, params object[] args) + { + lock (cmdline) + { + if (y != -1) + { + System.Console.CursorTop = y; + System.Console.CursorLeft = 0; + + int count = cmdline.Length; + + System.Console.Write(" "); + while (count-- > 0) + System.Console.Write(" "); + + System.Console.CursorTop = y; + System.Console.CursorLeft = 0; + } + WritePrefixLine(senderColor, sender); + WriteConsoleLine(color, format, args); + if (y != -1) + y = System.Console.CursorTop; + } } private void WriteNewLine(ConsoleColor color, string format, params object[] args) { + lock (cmdline) + { + if (y != -1) + { + System.Console.CursorTop = y; + System.Console.CursorLeft = 0; + + int count = cmdline.Length; + + System.Console.Write(" "); + while (count-- > 0) + System.Console.Write(" "); + + System.Console.CursorTop = y; + System.Console.CursorLeft = 0; + } + WriteConsoleLine(color, format, args); + if (y != -1) + y = System.Console.CursorTop; + } + } + + private void WriteConsoleLine(ConsoleColor color, string format, params object[] args) + { try { lock (m_syncRoot) @@ -240,108 +515,150 @@ namespace OpenSim.Framework.Console } } - public string ReadLine() + private void Help(string module, string[] cmd) { - try - { - string line = System.Console.ReadLine(); + List help = Commands.GetHelp(); + + foreach (string s in help) + Output(s); + } - while (line == null) + private void Show() + { + lock (cmdline) + { + if (y == -1 || System.Console.BufferWidth == 0) + return; + + int xc = prompt.Length + cp; + int new_x = xc % System.Console.BufferWidth; + int new_y = y + xc / System.Console.BufferWidth; + int end_y = y + (cmdline.Length + prompt.Length) / System.Console.BufferWidth; + if (end_y / System.Console.BufferWidth >= h) + h++; + if (end_y >= System.Console.BufferHeight) // wrap { - line = System.Console.ReadLine(); + y--; + new_y--; + System.Console.CursorLeft = 0; + System.Console.CursorTop = System.Console.BufferHeight-1; + System.Console.WriteLine(" "); } - return line; - } - catch (Exception e) - { - m_log.Error("[Console]: System.Console.ReadLine exception " + e.ToString()); - return String.Empty; - } - } + System.Console.CursorTop = y; + System.Console.CursorLeft = 0; - public int Read() - { - return System.Console.Read(); + if (echo) + System.Console.Write("{0}{1}", prompt, cmdline); + else + System.Console.Write("{0}", prompt); + + System.Console.CursorLeft = new_x; + System.Console.CursorTop = new_y; + } } - public IPAddress CmdPromptIPAddress(string prompt, string defaultvalue) + public void LockOutput() { - IPAddress address; - string addressStr; - - while (true) + System.Threading.Monitor.Enter(cmdline); + try { - addressStr = CmdPrompt(prompt, defaultvalue); - if (IPAddress.TryParse(addressStr, out address)) + if (y != -1) { - break; - } - else - { - m_log.Error("Illegal address. Please re-enter."); + System.Console.CursorTop = y; + System.Console.CursorLeft = 0; + + int count = cmdline.Length + prompt.Length; + + while (count-- > 0) + System.Console.Write(" "); + + System.Console.CursorTop = y; + System.Console.CursorLeft = 0; + } } - - return address; + catch (Exception) + { + } } - public uint CmdPromptIPPort(string prompt, string defaultvalue) + public void UnlockOutput() { - uint port; - string portStr; + if (y != -1) + { + y = System.Console.CursorTop; + Show(); + } + System.Threading.Monitor.Exit(cmdline); + } - while (true) + public void Output(string text) + { + lock (cmdline) { - portStr = CmdPrompt(prompt, defaultvalue); - if (uint.TryParse(portStr, out port)) + if (y == -1) { - if (port >= IPEndPoint.MinPort && port <= IPEndPoint.MaxPort) - { - break; - } + System.Console.WriteLine(text); + + return; } - m_log.Error("Illegal address. Please re-enter."); - } + System.Console.CursorTop = y; + System.Console.CursorLeft = 0; - return port; - } + int count = cmdline.Length + prompt.Length; - // Displays a prompt and waits for the user to enter a string, then returns that string - // (Done with no echo and suitable for passwords - currently disabled) - public string PasswdPrompt(string prompt) - { - // FIXME: Needs to be better abstracted - System.Console.WriteLine(String.Format("{0}: ", prompt)); - //ConsoleColor oldfg = System.Console.ForegroundColor; - //System.Console.ForegroundColor = System.Console.BackgroundColor; - string temp = System.Console.ReadLine(); - //System.Console.ForegroundColor = oldfg; - return temp; + while (count-- > 0) + System.Console.Write(" "); + + System.Console.CursorTop = y; + System.Console.CursorLeft = 0; + + System.Console.WriteLine(text); + + y = System.Console.CursorTop; + + Show(); + } } - // Displays a command prompt and waits for the user to enter a string, then returns that string - public string CmdPrompt(string prompt) + private void ContextHelp() { - System.Console.WriteLine(String.Format("{0}: ", prompt)); - return ReadLine(); + string[] words = Parser.Parse(cmdline.ToString()); + + string[] opts = Commands.FindNextOption(words, cmdline.ToString().EndsWith(" ")); + + if (opts[0].StartsWith("Command help:")) + Output(opts[0]); + else + Output(String.Format("Options: {0}", String.Join(" ", opts))); } - // Displays a command prompt and returns a default value if the user simply presses enter - public string CmdPrompt(string prompt, string defaultresponse) + public void Prompt() { - string temp = CmdPrompt(String.Format("{0} [{1}]", prompt, defaultresponse)); - if (temp == String.Empty) - { - return defaultresponse; - } - else + string line = ReadLine(m_defaultPrompt, true, true); + + if (line != String.Empty) { - return temp; + m_log.Info("Invalid command"); } } + public string CmdPrompt(string p) + { + return ReadLine(String.Format("{0}: ", p), false, true); + } + + public string CmdPrompt(string p, string def) + { + string ret = ReadLine(String.Format("{0} [{1}]: ", p, def), false, true); + if (ret == String.Empty) + ret = def; + + return ret; + } + // Displays a command prompt and returns a default value, user may only enter 1 of 2 options public string CmdPrompt(string prompt, string defaultresponse, string OptionA, string OptionB) { @@ -362,85 +679,137 @@ namespace OpenSim.Framework.Console return temp; } - // Runs a command with a number of parameters - public Object RunCmd(string Cmd, string[] cmdparams) - { - m_cmdParser.RunCmd(Cmd, cmdparams); - return null; - } - - // Shows data about something - public void ShowCommands(string ShowWhat) + // Displays a prompt and waits for the user to enter a string, then returns that string + // (Done with no echo and suitable for passwords) + public string PasswdPrompt(string p) { - m_cmdParser.Show(new string[] { ShowWhat }); + return ReadLine(p, false, false); } - public void Prompt() + public void RunCommand(string cmd) { - string tempstr = CmdPrompt(m_defaultPrompt); - RunCommand(tempstr); + string[] parts = Parser.Parse(cmd); + Commands.Resolve(parts); } - public void RunCommand(string cmdline) + public string ReadLine(string p, bool isCommand, bool e) { - Regex Extractor = new Regex(@"(['""][^""]+['""])\s*|([^\s]+)\s*", RegexOptions.Compiled); - char[] delims = {' ', '"'}; - MatchCollection matches = Extractor.Matches(cmdline); - // Get matches + h = 1; + cp = 0; + prompt = p; + echo = e; + int historyLine = history.Count; - if (matches.Count == 0) - return; + System.Console.CursorLeft = 0; // Needed for mono + System.Console.Write(" "); // Needed for mono - string cmd = matches[0].Value.Trim(delims); - string[] cmdparams = new string[matches.Count - 1]; + y = System.Console.CursorTop; + cmdline = new StringBuilder(); - for (int i = 1; i < matches.Count; i++) + while(true) { - cmdparams[i-1] = matches[i].Value.Trim(delims); - } + Show(); - try - { - RunCmd(cmd, cmdparams); - } - catch (Exception e) - { - m_log.ErrorFormat("[Console]: Command [{0}] failed with exception {1}", cmdline, e.ToString()); - m_log.Error(e.StackTrace); - } - } + ConsoleKeyInfo key = System.Console.ReadKey(true); + char c = key.KeyChar; - public string LineInfo - { - get - { - string result = String.Empty; + if (!Char.IsControl(c)) + { + if (cp >= 318) + continue; - string stacktrace = Environment.StackTrace; - List lines = new List(stacktrace.Split(new string[] {"at "}, StringSplitOptions.None)); + if (c == '?' && isCommand) + { + ContextHelp(); + continue; + } - if (lines.Count > 4) + cmdline.Insert(cp, c); + cp++; + } + else { - lines.RemoveRange(0, 4); + switch (key.Key) + { + case ConsoleKey.Backspace: + if (cp == 0) + break; + cmdline.Remove(cp-1, 1); + cp--; - string tmpLine = lines[0]; + System.Console.CursorLeft = 0; + System.Console.CursorTop = y; - int inIndex = tmpLine.IndexOf(" in "); + System.Console.Write("{0}{1} ", prompt, cmdline); - if (inIndex > -1) - { - result = tmpLine.Substring(0, inIndex); + break; + case ConsoleKey.End: + cp = cmdline.Length; + break; + case ConsoleKey.Home: + cp = 0; + break; + case ConsoleKey.UpArrow: + if (historyLine < 1) + break; + historyLine--; + LockOutput(); + cmdline = new StringBuilder(history[historyLine]); + cp = cmdline.Length; + UnlockOutput(); + break; + case ConsoleKey.DownArrow: + if (historyLine >= history.Count) + break; + historyLine++; + LockOutput(); + if (historyLine == history.Count) + cmdline = new StringBuilder(); + else + cmdline = new StringBuilder(history[historyLine]); + cp = cmdline.Length; + UnlockOutput(); + break; + case ConsoleKey.LeftArrow: + if (cp > 0) + cp--; + break; + case ConsoleKey.RightArrow: + if (cp < cmdline.Length) + cp++; + break; + case ConsoleKey.Enter: + System.Console.CursorLeft = 0; + System.Console.CursorTop = y; + + System.Console.WriteLine("{0}{1}", prompt, cmdline); - int lineIndex = tmpLine.IndexOf(":line "); + y = -1; - if (lineIndex > -1) + if (isCommand) { - lineIndex += 6; - result += ", line " + tmpLine.Substring(lineIndex, (tmpLine.Length - lineIndex) - 5); + string[] cmd = Commands.Resolve(Parser.Parse(cmdline.ToString())); + + if (cmd.Length != 0) + { + int i; + + for (i=0 ; i < cmd.Length ; i++) + { + if (cmd[i].Contains(" ")) + cmd[i] = "\"" + cmd[i] + "\""; + } + AddToHistory(String.Join(" ", cmd)); + return String.Empty; + } } + + AddToHistory(cmdline.ToString()); + return cmdline.ToString(); + default: + break; } } - return result; } } } diff --git a/OpenSim/Framework/Console/ConsoleCallbacksBase.cs b/OpenSim/Framework/Console/ConsoleCallbacksBase.cs deleted file mode 100644 index d37c47a..0000000 --- a/OpenSim/Framework/Console/ConsoleCallbacksBase.cs +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 OpenSim 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. - */ - -namespace OpenSim.Framework.Console -{ - public interface conscmd_callback - { - void RunCmd(string cmd, string[] cmdparams); - void Show(string[] showParams); - } -} diff --git a/OpenSim/Framework/Console/OpenSimAppender.cs b/OpenSim/Framework/Console/OpenSimAppender.cs index b07617f..6193bac 100644 --- a/OpenSim/Framework/Console/OpenSimAppender.cs +++ b/OpenSim/Framework/Console/OpenSimAppender.cs @@ -37,6 +37,14 @@ namespace OpenSim.Framework.Console /// public class OpenSimAppender : AnsiColorTerminalAppender { + private ConsoleBase m_console = null; + + public ConsoleBase Console + { + get { return m_console; } + set { m_console = value; } + } + private static readonly ConsoleColor[] Colors = { // the dark colors don't seem to be visible on some black background terminals like putty :( //ConsoleColor.DarkBlue, @@ -55,6 +63,9 @@ namespace OpenSim.Framework.Console override protected void Append(LoggingEvent le) { + if (m_console != null) + m_console.LockOutput(); + try { string loggingMessage = RenderLoggingEvent(le); @@ -96,6 +107,11 @@ namespace OpenSim.Framework.Console { System.Console.WriteLine("Couldn't write out log message: {0}", e.ToString()); } + finally + { + if (m_console != null) + m_console.UnlockOutput(); + } } private void WriteColorText(ConsoleColor color, string sender) -- cgit v1.1