OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
LocalConsole.cs
Go to the documentation of this file.
1 /*
2  * Copyright (c) Contributors, http://opensimulator.org/
3  * See CONTRIBUTORS.TXT for a full list of copyright holders.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  * * Redistributions of source code must retain the above copyright
8  * notice, this list of conditions and the following disclaimer.
9  * * Redistributions in binary form must reproduce the above copyright
10  * notice, this list of conditions and the following disclaimer in the
11  * documentation and/or other materials provided with the distribution.
12  * * Neither the name of the OpenSimulator Project nor the
13  * names of its contributors may be used to endorse or promote products
14  * derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 using System;
29 using System.Collections.Generic;
30 using System.Diagnostics;
31 using System.Reflection;
32 using System.Text;
33 using System.Text.RegularExpressions;
34 using System.Threading;
35 using System.IO;
36 using Nini.Config;
37 using log4net;
38 
39 namespace OpenSim.Framework.Console
40 {
45  {
46  private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
47  private string m_historyPath;
48  private bool m_historyEnable;
49 
50  // private readonly object m_syncRoot = new object();
51  private const string LOGLEVEL_NONE = "(none)";
52 
53  // Used to extract categories for colourization.
54  private Regex m_categoryRegex
55  = new Regex(
56  @"^(?<Front>.*?)\[(?<Category>[^\]]+)\]:?(?<End>.*)", RegexOptions.Singleline | RegexOptions.Compiled);
57 
58  private int m_cursorYPosition = -1;
59  private int m_cursorXPosition = 0;
60  private StringBuilder m_commandLine = new StringBuilder();
61  private bool m_echo = true;
62  private List<string> m_history = new List<string>();
63 
64  private static readonly ConsoleColor[] Colors = {
65  // the dark colors don't seem to be visible on some black background terminals like putty :(
66  //ConsoleColor.DarkBlue,
67  //ConsoleColor.DarkGreen,
68  //ConsoleColor.DarkCyan,
69  //ConsoleColor.DarkMagenta,
70  //ConsoleColor.DarkYellow,
71  ConsoleColor.Gray,
72  //ConsoleColor.DarkGray,
73  ConsoleColor.Blue,
74  ConsoleColor.Green,
75  ConsoleColor.Cyan,
76  ConsoleColor.Magenta,
77  ConsoleColor.Yellow
78  };
79 
80  private static ConsoleColor DeriveColor(string input)
81  {
82  // it is important to do Abs, hash values can be negative
83  return Colors[(Math.Abs(input.ToUpper().GetHashCode()) % Colors.Length)];
84  }
85 
86  public LocalConsole(string defaultPrompt, IConfig startupConfig = null) : base(defaultPrompt)
87  {
88 
89  if (startupConfig == null) return;
90 
91  m_historyEnable = startupConfig.GetBoolean("ConsoleHistoryFileEnabled", false);
92  if (!m_historyEnable)
93  {
94  m_log.Info("[LOCAL CONSOLE]: Persistent command line history from file is Disabled");
95  return;
96  }
97 
98  string m_historyFile = startupConfig.GetString("ConsoleHistoryFile", "OpenSimConsoleHistory.txt");
99  int m_historySize = startupConfig.GetInt("ConsoleHistoryFileLines", 100);
100  m_historyPath = Path.GetFullPath(Path.Combine(Util.configDir(), m_historyFile));
101  m_log.InfoFormat("[LOCAL CONSOLE]: Persistent command line history is Enabled, up to {0} lines from file {1}", m_historySize, m_historyPath);
102 
103  if (File.Exists(m_historyPath))
104  {
105  using (StreamReader history_file = new StreamReader(m_historyPath))
106  {
107  string line;
108  while ((line = history_file.ReadLine()) != null)
109  {
110  m_history.Add(line);
111  }
112  }
113 
114  if (m_history.Count > m_historySize)
115  {
116  while (m_history.Count > m_historySize)
117  m_history.RemoveAt(0);
118 
119  using (StreamWriter history_file = new StreamWriter(m_historyPath))
120  {
121  foreach (string line in m_history)
122  {
123  history_file.WriteLine(line);
124  }
125  }
126  }
127  m_log.InfoFormat("[LOCAL CONSOLE]: Read {0} lines of command line history from file {1}", m_history.Count, m_historyPath);
128  }
129  else
130  {
131  m_log.InfoFormat("[LOCAL CONSOLE]: Creating new empty command line history file {0}", m_historyPath);
132  File.Create(m_historyPath).Dispose();
133  }
134  }
135 
136  private void AddToHistory(string text)
137  {
138  while (m_history.Count >= 100)
139  m_history.RemoveAt(0);
140 
141  m_history.Add(text);
142  if (m_historyEnable)
143  {
144  File.AppendAllText(m_historyPath, text + Environment.NewLine);
145  }
146  }
147 
159  private int SetCursorTop(int top)
160  {
161  // From at least mono 2.4.2.3, window resizing can give mono an invalid row and column values. If we try
162  // to set a cursor row position with a currently invalid column, mono will throw an exception.
163  // Therefore, we need to make sure that the column position is valid first.
164  int left = System.Console.CursorLeft;
165 
166  if (left < 0)
167  {
168  System.Console.CursorLeft = 0;
169  }
170  else
171  {
172  int bufferWidth = System.Console.BufferWidth;
173 
174  // On Mono 2.4.2.3 (and possibly above), the buffer value is sometimes erroneously zero (Mantis 4657)
175  if (bufferWidth > 0 && left >= bufferWidth)
176  System.Console.CursorLeft = bufferWidth - 1;
177  }
178 
179  if (top < 0)
180  {
181  top = 0;
182  }
183  else
184  {
185  int bufferHeight = System.Console.BufferHeight;
186 
187  // On Mono 2.4.2.3 (and possibly above), the buffer value is sometimes erroneously zero (Mantis 4657)
188  if (bufferHeight > 0 && top >= bufferHeight)
189  top = bufferHeight - 1;
190  }
191 
192  System.Console.CursorTop = top;
193 
194  return top;
195  }
196 
208  private int SetCursorLeft(int left)
209  {
210  // From at least mono 2.4.2.3, window resizing can give mono an invalid row and column values. If we try
211  // to set a cursor column position with a currently invalid row, mono will throw an exception.
212  // Therefore, we need to make sure that the row position is valid first.
213  int top = System.Console.CursorTop;
214 
215  if (top < 0)
216  {
217  System.Console.CursorTop = 0;
218  }
219  else
220  {
221  int bufferHeight = System.Console.BufferHeight;
222  // On Mono 2.4.2.3 (and possibly above), the buffer value is sometimes erroneously zero (Mantis 4657)
223  if (bufferHeight > 0 && top >= bufferHeight)
224  System.Console.CursorTop = bufferHeight - 1;
225  }
226 
227  if (left < 0)
228  {
229  left = 0;
230  }
231  else
232  {
233  int bufferWidth = System.Console.BufferWidth;
234 
235  // On Mono 2.4.2.3 (and possibly above), the buffer value is sometimes erroneously zero (Mantis 4657)
236  if (bufferWidth > 0 && left >= bufferWidth)
237  left = bufferWidth - 1;
238  }
239 
240  System.Console.CursorLeft = left;
241 
242  return left;
243  }
244 
245  private void Show()
246  {
247  lock (m_commandLine)
248  {
249  if (m_cursorYPosition == -1 || System.Console.BufferWidth == 0)
250  return;
251 
252  int xc = prompt.Length + m_cursorXPosition;
253  int new_x = xc % System.Console.BufferWidth;
254  int new_y = m_cursorYPosition + xc / System.Console.BufferWidth;
255  int end_y = m_cursorYPosition + (m_commandLine.Length + prompt.Length) / System.Console.BufferWidth;
256 
257  if (end_y >= System.Console.BufferHeight) // wrap
258  {
259  m_cursorYPosition--;
260  new_y--;
261  SetCursorLeft(0);
262  SetCursorTop(System.Console.BufferHeight - 1);
263  System.Console.WriteLine(" ");
264  }
265 
266  m_cursorYPosition = SetCursorTop(m_cursorYPosition);
267  SetCursorLeft(0);
268 
269  if (m_echo)
270  System.Console.Write("{0}{1}", prompt, m_commandLine);
271  else
272  System.Console.Write("{0}", prompt);
273 
274  SetCursorTop(new_y);
275  SetCursorLeft(new_x);
276  }
277  }
278 
279  public override void LockOutput()
280  {
281  Monitor.Enter(m_commandLine);
282  try
283  {
284  if (m_cursorYPosition != -1)
285  {
286  m_cursorYPosition = SetCursorTop(m_cursorYPosition);
287  System.Console.CursorLeft = 0;
288 
289  int count = m_commandLine.Length + prompt.Length;
290 
291  while (count-- > 0)
292  System.Console.Write(" ");
293 
294  m_cursorYPosition = SetCursorTop(m_cursorYPosition);
295  SetCursorLeft(0);
296  }
297  }
298  catch (Exception)
299  {
300  }
301  }
302 
303  public override void UnlockOutput()
304  {
305  if (m_cursorYPosition != -1)
306  {
307  m_cursorYPosition = System.Console.CursorTop;
308  Show();
309  }
310  Monitor.Exit(m_commandLine);
311  }
312 
313  private void WriteColorText(ConsoleColor color, string sender)
314  {
315  try
316  {
317  lock (this)
318  {
319  try
320  {
321  System.Console.ForegroundColor = color;
322  System.Console.Write(sender);
323  System.Console.ResetColor();
324  }
325  catch (ArgumentNullException)
326  {
327  // Some older systems dont support coloured text.
328  System.Console.WriteLine(sender);
329  }
330  }
331  }
332  catch (ObjectDisposedException)
333  {
334  }
335  }
336 
337  private void WriteLocalText(string text, string level)
338  {
339  string outText = text;
340 
341  if (level != LOGLEVEL_NONE)
342  {
343  MatchCollection matches = m_categoryRegex.Matches(text);
344 
345  if (matches.Count == 1)
346  {
347  outText = matches[0].Groups["End"].Value;
348  System.Console.Write(matches[0].Groups["Front"].Value);
349 
350  System.Console.Write("[");
351  WriteColorText(DeriveColor(matches[0].Groups["Category"].Value),
352  matches[0].Groups["Category"].Value);
353  System.Console.Write("]:");
354  }
355  else
356  {
357  outText = outText.Trim();
358  }
359  }
360 
361  if (level == "error")
362  WriteColorText(ConsoleColor.Red, outText);
363  else if (level == "warn")
364  WriteColorText(ConsoleColor.Yellow, outText);
365  else
366  System.Console.Write(outText);
367 
368  System.Console.WriteLine();
369  }
370 
371  public override void Output(string text)
372  {
373  Output(text, LOGLEVEL_NONE);
374  }
375 
376  public override void Output(string text, string level)
377  {
378  FireOnOutput(text);
379 
380  lock (m_commandLine)
381  {
382  if (m_cursorYPosition == -1)
383  {
384  WriteLocalText(text, level);
385 
386  return;
387  }
388 
389  m_cursorYPosition = SetCursorTop(m_cursorYPosition);
390  SetCursorLeft(0);
391 
392  int count = m_commandLine.Length + prompt.Length;
393 
394  while (count-- > 0)
395  System.Console.Write(" ");
396 
397  m_cursorYPosition = SetCursorTop(m_cursorYPosition);
398  SetCursorLeft(0);
399 
400  WriteLocalText(text, level);
401 
402  m_cursorYPosition = System.Console.CursorTop;
403 
404  Show();
405  }
406  }
407 
408  private bool ContextHelp()
409  {
410  string[] words = Parser.Parse(m_commandLine.ToString());
411 
412  bool trailingSpace = m_commandLine.ToString().EndsWith(" ");
413 
414  // Allow ? through while typing a URI
415  //
416  if (words.Length > 0 && words[words.Length-1].StartsWith("http") && !trailingSpace)
417  return false;
418 
419  string[] opts = Commands.FindNextOption(words, trailingSpace);
420 
421  if (opts[0].StartsWith("Command help:"))
422  Output(opts[0]);
423  else
424  Output(String.Format("Options: {0}", String.Join(" ", opts)));
425 
426  return true;
427  }
428 
429  public override string ReadLine(string p, bool isCommand, bool e)
430  {
431  m_cursorXPosition = 0;
432  prompt = p;
433  m_echo = e;
434  int historyLine = m_history.Count;
435 
436  SetCursorLeft(0); // Needed for mono
437  System.Console.Write(" "); // Needed for mono
438 
439  lock (m_commandLine)
440  {
441  m_cursorYPosition = System.Console.CursorTop;
442  m_commandLine.Remove(0, m_commandLine.Length);
443  }
444 
445  while (true)
446  {
447  Show();
448 
449  ConsoleKeyInfo key = System.Console.ReadKey(true);
450  char enteredChar = key.KeyChar;
451 
452  if (!Char.IsControl(enteredChar))
453  {
454  if (m_cursorXPosition >= 318)
455  continue;
456 
457  if (enteredChar == '?' && isCommand)
458  {
459  if (ContextHelp())
460  continue;
461  }
462 
463  m_commandLine.Insert(m_cursorXPosition, enteredChar);
464  m_cursorXPosition++;
465  }
466  else
467  {
468  switch (key.Key)
469  {
470  case ConsoleKey.Backspace:
471  if (m_cursorXPosition == 0)
472  break;
473  m_commandLine.Remove(m_cursorXPosition-1, 1);
474  m_cursorXPosition--;
475 
476  SetCursorLeft(0);
477  m_cursorYPosition = SetCursorTop(m_cursorYPosition);
478 
479  if (m_echo)
480  System.Console.Write("{0}{1} ", prompt, m_commandLine);
481  else
482  System.Console.Write("{0}", prompt);
483 
484  break;
485  case ConsoleKey.Delete:
486  if (m_cursorXPosition == m_commandLine.Length)
487  break;
488 
489  m_commandLine.Remove(m_cursorXPosition, 1);
490 
491  SetCursorLeft(0);
492  m_cursorYPosition = SetCursorTop(m_cursorYPosition);
493 
494  if (m_echo)
495  System.Console.Write("{0}{1} ", prompt, m_commandLine);
496  else
497  System.Console.Write("{0}", prompt);
498 
499  break;
500  case ConsoleKey.End:
501  m_cursorXPosition = m_commandLine.Length;
502  break;
503  case ConsoleKey.Home:
504  m_cursorXPosition = 0;
505  break;
506  case ConsoleKey.UpArrow:
507  if (historyLine < 1)
508  break;
509  historyLine--;
510  LockOutput();
511  m_commandLine.Remove(0, m_commandLine.Length);
512  m_commandLine.Append(m_history[historyLine]);
513  m_cursorXPosition = m_commandLine.Length;
514  UnlockOutput();
515  break;
516  case ConsoleKey.DownArrow:
517  if (historyLine >= m_history.Count)
518  break;
519  historyLine++;
520  LockOutput();
521  if (historyLine == m_history.Count)
522  {
523  m_commandLine.Remove(0, m_commandLine.Length);
524  }
525  else
526  {
527  m_commandLine.Remove(0, m_commandLine.Length);
528  m_commandLine.Append(m_history[historyLine]);
529  }
530  m_cursorXPosition = m_commandLine.Length;
531  UnlockOutput();
532  break;
533  case ConsoleKey.LeftArrow:
534  if (m_cursorXPosition > 0)
535  m_cursorXPosition--;
536  break;
537  case ConsoleKey.RightArrow:
538  if (m_cursorXPosition < m_commandLine.Length)
539  m_cursorXPosition++;
540  break;
541  case ConsoleKey.Enter:
542  SetCursorLeft(0);
543  m_cursorYPosition = SetCursorTop(m_cursorYPosition);
544 
545  System.Console.WriteLine();
546  //Show();
547 
548  lock (m_commandLine)
549  {
550  m_cursorYPosition = -1;
551  }
552 
553  string commandLine = m_commandLine.ToString();
554 
555  if (isCommand)
556  {
557  string[] cmd = Commands.Resolve(Parser.Parse(commandLine));
558 
559  if (cmd.Length != 0)
560  {
561  int index;
562 
563  for (index=0 ; index < cmd.Length ; index++)
564  {
565  if (cmd[index].Contains(" "))
566  cmd[index] = "\"" + cmd[index] + "\"";
567  }
568  AddToHistory(String.Join(" ", cmd));
569  return String.Empty;
570  }
571  }
572 
573  // If we're not echoing to screen (e.g. a password) then we probably don't want it in history
574  if (m_echo && commandLine != "")
575  AddToHistory(commandLine);
576 
577  return commandLine;
578  default:
579  break;
580  }
581  }
582  }
583  }
584  }
585 }
override void Output(string text, string level)
override void Output(string text)
OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString key
Definition: ICM_Api.cs:31
A console that uses cursor control and color
Definition: LocalConsole.cs:44
LocalConsole(string defaultPrompt, IConfig startupConfig=null)
Definition: LocalConsole.cs:86
override string ReadLine(string p, bool isCommand, bool e)
A console that processes commands internally