using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Brigadier.NET;
using FuzzySharp;
using MinecraftClient.CommandHandler;
using MinecraftClient.Scripting;
using static MinecraftClient.Settings;
namespace MinecraftClient
{
///
/// Allows simultaneous console input and output without breaking user input
/// (Without having this annoying behaviour : User inp[Some Console output]ut)
/// Provide some fancy features such as formatted output, text pasting and tab-completion.
/// By ORelio - (c) 2012-2018 - Available under the CDDL-1.0 license
///
public static class ConsoleIO
{
private static IAutoComplete? autocomplete_engine;
///
/// Reset the IO mechanism and clear all buffers
///
public static void Reset()
{
ClearLineAndBuffer();
}
///
/// Set an auto-completion engine for TAB autocompletion.
///
/// Engine implementing the IAutoComplete interface
public static void SetAutoCompleteEngine(IAutoComplete engine)
{
autocomplete_engine = engine;
}
///
/// Determines whether to use interactive IO or basic IO.
/// Set to true to disable interactive command prompt and use the default Console.Read|Write() methods.
/// Color codes are printed as is when BasicIO is enabled.
///
public static bool BasicIO = false;
///
/// Determines whether not to print color codes in BasicIO mode.
///
public static bool BasicIO_NoColor = false;
///
/// Determine whether WriteLineFormatted() should prepend lines with timestamps by default.
///
public static bool EnableTimestamps = false;
///
/// Specify a generic log line prefix for WriteLogLine()
///
public static string LogPrefix = "§8[Log] ";
///
/// Read a password from the standard input
///
public static string? ReadPassword()
{
if (BasicIO)
return Console.ReadLine();
else
{
ConsoleInteractive.ConsoleReader.SetInputVisible(false);
var input = ConsoleInteractive.ConsoleReader.RequestImmediateInput();
ConsoleInteractive.ConsoleReader.SetInputVisible(true);
return input;
}
}
///
/// Read a line from the standard input
///
public static string ReadLine()
{
if (BasicIO)
return Console.ReadLine() ?? String.Empty;
else
return ConsoleInteractive.ConsoleReader.RequestImmediateInput();
}
///
/// Debug routine: print all keys pressed in the console
///
public static void DebugReadInput()
{
ConsoleKeyInfo k;
while (true)
{
k = Console.ReadKey(true);
Console.WriteLine("Key: {0}\tChar: {1}\tModifiers: {2}", k.Key, k.KeyChar, k.Modifiers);
}
}
///
/// Write a string to the standard output with a trailing newline
///
public static void WriteLine(string line)
{
if (BasicIO)
Console.WriteLine(line);
else
ConsoleInteractive.ConsoleWriter.WriteLine(line);
}
///
/// Write a Minecraft-Like formatted string to the standard output, using §c color codes
/// See minecraft.gamepedia.com/Classic_server_protocol#Color_Codes for more info
///
/// String to write
/// If false, space are printed instead of newlines
///
/// If false, no timestamp is prepended.
/// If true, "hh-mm-ss" timestamp will be prepended.
/// If unspecified, value is retrieved from EnableTimestamps.
///
public static void WriteLineFormatted(string str, bool acceptnewlines = false, bool? displayTimestamp = null)
{
StringBuilder output = new();
if (!String.IsNullOrEmpty(str))
{
displayTimestamp ??= EnableTimestamps;
if (displayTimestamp.Value)
{
int hour = DateTime.Now.Hour, minute = DateTime.Now.Minute, second = DateTime.Now.Second;
output.Append(String.Format("{0}:{1}:{2} ", hour.ToString("00"), minute.ToString("00"), second.ToString("00")));
}
if (!acceptnewlines)
{
str = str.Replace('\n', ' ');
}
if (BasicIO)
{
if (BasicIO_NoColor)
{
output.Append(ChatBot.GetVerbatim(str));
}
else
{
output.Append(str);
}
Console.WriteLine(output.ToString());
return;
}
output.Append(str);
ConsoleInteractive.ConsoleWriter.WriteLineFormatted(output.ToString());
}
}
///
/// Write a prefixed log line. Prefix is set in LogPrefix.
///
/// Text of the log line
/// Allow line breaks
public static void WriteLogLine(string text, bool acceptnewlines = true)
{
if (!acceptnewlines)
text = text.Replace('\n', ' ');
WriteLineFormatted(LogPrefix + text, acceptnewlines);
}
#region Subfunctions
///
/// Clear all text inside the input prompt
///
private static void ClearLineAndBuffer()
{
if (BasicIO) return;
ConsoleInteractive.ConsoleReader.ClearBuffer();
}
#endregion
internal static bool AutoCompleteDone = false;
internal static string[] AutoCompleteResult = Array.Empty();
private static HashSet Commands = new();
private static string[] CommandsFromAutoComplete = Array.Empty();
private static string[] CommandsFromDeclareCommands = Array.Empty();
private static Task _latestTask = Task.CompletedTask;
private static CancellationTokenSource? _cancellationTokenSource;
private static void MccAutocompleteHandler(ConsoleInteractive.ConsoleReader.Buffer buffer)
{
string fullCommand = buffer.Text;
if (string.IsNullOrEmpty(fullCommand))
{
ConsoleInteractive.ConsoleSuggestion.ClearSuggestions();
return;
}
var InternalCmdChar = Config.Main.Advanced.InternalCmdChar;
if (InternalCmdChar == MainConfigHealper.MainConfig.AdvancedConfig.InternalCmdCharType.none || fullCommand[0] == InternalCmdChar.ToChar())
{
int offset = InternalCmdChar == MainConfigHealper.MainConfig.AdvancedConfig.InternalCmdCharType.none ? 0 : 1;
if (buffer.CursorPosition - offset < 0)
{
ConsoleInteractive.ConsoleSuggestion.ClearSuggestions();
return;
}
_cancellationTokenSource?.Cancel();
using var cts = new CancellationTokenSource();
_cancellationTokenSource = cts;
var previousTask = _latestTask;
var newTask = new Task(async () =>
{
string command = fullCommand[offset..];
if (command.Length == 0)
{
var childs = CmdResult.client!.dispatcher.GetRoot().Children;
int index = 0;
var sugList = new ConsoleInteractive.ConsoleSuggestion.Suggestion[childs.Count + Commands.Count + 1];
sugList[index++] = new("/");
foreach (var child in childs)
sugList[index++] = new(child.Name);
foreach (var cmd in Commands)
sugList[index++] = new(cmd);
ConsoleInteractive.ConsoleSuggestion.UpdateSuggestions(sugList, new(offset, offset));
}
else if (command.Length > 0 && command[0] == '/' && !command.Contains(' '))
{
var sorted = Process.ExtractSorted(command[1..], Commands);
var sugList = new ConsoleInteractive.ConsoleSuggestion.Suggestion[sorted.Count()];
int index = 0;
foreach (var sug in sorted)
sugList[index++] = new(sug.Value);
ConsoleInteractive.ConsoleSuggestion.UpdateSuggestions(sugList, new(offset, offset + command.Length));
}
else
{
var parse = CmdResult.client!.dispatcher.Parse(command, CmdResult.Empty);
var suggestion = await CmdResult.client!.dispatcher.GetCompletionSuggestions(parse, buffer.CursorPosition - offset);
int sugLen = suggestion.List.Count;
if (sugLen == 0)
{
ConsoleInteractive.ConsoleSuggestion.ClearSuggestions();
return;
}
var sugList = new ConsoleInteractive.ConsoleSuggestion.Suggestion[sugLen];
if (cts.IsCancellationRequested)
return;
Tuple range = new(suggestion.Range.Start + offset, suggestion.Range.End + offset);
var sorted = Process.ExtractSorted(fullCommand[range.Item1..range.Item2], suggestion.List.Select(_ => _.Text).ToList());
if (cts.IsCancellationRequested)
return;
int index = 0;
foreach (var sug in sorted)
sugList[index++] = new(sug.Value);
ConsoleInteractive.ConsoleSuggestion.UpdateSuggestions(sugList, range);
}
}, cts.Token);
_latestTask = newTask;
try { newTask.Start(); } catch { }
if (_cancellationTokenSource == cts) _cancellationTokenSource = null;
}
else
{
ConsoleInteractive.ConsoleSuggestion.ClearSuggestions();
return;
}
}
public static void AutocompleteHandler(object? sender, ConsoleInteractive.ConsoleReader.Buffer buffer)
{
MccAutocompleteHandler(buffer);
}
public static void CancelAutocomplete()
{
_cancellationTokenSource?.Cancel();
_latestTask = Task.CompletedTask;
ConsoleInteractive.ConsoleSuggestion.ClearSuggestions();
AutoCompleteDone = false;
AutoCompleteResult = Array.Empty();
CommandsFromAutoComplete = Array.Empty();
CommandsFromDeclareCommands = Array.Empty();
}
private static void MergeCommands()
{
Commands.Clear();
foreach (string cmd in CommandsFromAutoComplete)
Commands.Add('/' + cmd);
foreach (string cmd in CommandsFromDeclareCommands)
Commands.Add('/' + cmd);
}
public static void OnAutoCompleteDone(int transactionId, string[] result)
{
AutoCompleteResult = result;
if (transactionId == 0)
{
CommandsFromAutoComplete = result;
MergeCommands();
}
AutoCompleteDone = true;
}
public static void OnDeclareMinecraftCommand(string[] rootCommands)
{
CommandsFromDeclareCommands = rootCommands;
MergeCommands();
}
public static void InitCommandList(CommandDispatcher dispatcher)
{
autocomplete_engine!.AutoComplete("/");
}
}
///
/// Interface for TAB autocompletion
/// Allows to use any object which has an AutoComplete() method using the IAutocomplete interface
///
public interface IAutoComplete
{
///
/// Provide a list of auto-complete strings based on the provided input behing the cursor
///
/// Text behind the cursor, e.g. "my input comm"
/// List of auto-complete words, e.g. ["command", "comment"]
int AutoComplete(string BehindCursor);
}
}