using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using Explorer.UI.Main.Pages.Console; using ExplorerBeta; using ExplorerBeta.UI; using ExplorerBeta.UI.Main; using ExplorerBeta.Unstrip.Resources; using TMPro; using UnhollowerRuntimeLib; using UnityEngine; using UnityEngine.UI; namespace Explorer.UI.Main.Pages { public class ConsolePage : BaseMenuPage { public override string Name => "C# Console"; public static ConsolePage Instance { get; private set; } private CodeEditor m_codeEditor; private ScriptEvaluator m_evaluator; public static List AutoCompletes = new List(); public static List UsingDirectives; public static readonly string[] DefaultUsing = new string[] { "System", "UnityEngine", "System.Linq", "System.Collections", "System.Collections.Generic", "System.Reflection" }; public override void Init() { Instance = this; try { ResetConsole(); foreach (var use in DefaultUsing) { AddUsing(use); } ConstructUI(); } catch (Exception e) { ExplorerCore.LogWarning($"Error setting up console!\r\nMessage: {e.Message}"); // TODO } } public override void Update() { m_codeEditor?.Update(); } internal string AsmToUsing(string asm, bool richText = false) { if (richText) { return $"using {asm};"; } return $"using {asm};"; } public void AddUsing(string asm) { if (!UsingDirectives.Contains(asm)) { UsingDirectives.Add(asm); Evaluate(AsmToUsing(asm), true); } } public object Evaluate(string str, bool suppressWarning = false) { object ret = VoidType.Value; m_evaluator.Compile(str, out var compiled); try { if (compiled == null) { throw new Exception("Mono.Csharp Service was unable to compile the code provided."); } compiled.Invoke(ref ret); } catch (Exception e) { if (!suppressWarning) { ExplorerCore.LogWarning(e.GetType() + ", " + e.Message); } } return ret; } public void ResetConsole() { if (m_evaluator != null) { m_evaluator.Dispose(); } m_evaluator = new ScriptEvaluator(new StringWriter(new StringBuilder())) { InteractiveBaseClass = typeof(ScriptInteraction) }; UsingDirectives = new List(); } private static string m_prevInput = "NULL"; // TODO call from OnInputChanged private void CheckAutocomplete() { var input = m_codeEditor.InputField.text; var caretIndex = m_codeEditor.InputField.caretPosition; if (!string.IsNullOrEmpty(input)) { try { var splitChars = new[] { ',', ';', '<', '>', '(', ')', '[', ']', '=', '|', '&' }; // Credit ManlyMarco // Separate input into parts, grab only the part with cursor in it var start = caretIndex <= 0 ? 0 : input.LastIndexOfAny(splitChars, (int)(caretIndex - 1)) + 1; var end = caretIndex <= 0 ? input.Length : input.IndexOfAny(splitChars, (int)(caretIndex - 1)); if (end < 0 || end < start) end = input.Length; input = input.Substring(start, end - start); } catch (ArgumentException) { } if (!string.IsNullOrEmpty(input) && input != m_prevInput) { GetAutocompletes(input); } } else { ClearAutocompletes(); } m_prevInput = input; } private void ClearAutocompletes() { if (AutoCompletes.Any()) { AutoCompletes.Clear(); } } private void UseAutocomplete(string suggestion) { int cursorIndex = m_codeEditor.InputField.caretPosition; var input = m_codeEditor.InputField.text; input = input.Insert(cursorIndex, suggestion); m_codeEditor.InputField.text = input; ClearAutocompletes(); } private void GetAutocompletes(string input) { try { //ExplorerCore.Log("Fetching suggestions for input " + input); // Credit ManylMarco AutoCompletes.Clear(); var completions = m_evaluator.GetCompletions(input, out string prefix); if (completions != null) { if (prefix == null) prefix = input; AutoCompletes.AddRange(completions .Where(x => !string.IsNullOrEmpty(x)) .Select(x => new AutoComplete(x, prefix, AutoComplete.Contexts.Other)) ); } var trimmed = input.Trim(); if (trimmed.StartsWith("using")) trimmed = trimmed.Remove(0, 5).Trim(); var namespaces = AutoCompleteHelpers.Namespaces .Where(x => x.StartsWith(trimmed) && x.Length > trimmed.Length) .Select(x => new AutoComplete( x.Substring(trimmed.Length), x.Substring(0, trimmed.Length), AutoComplete.Contexts.Namespace)); AutoCompletes.AddRange(namespaces); } catch (Exception ex) { ExplorerCore.Log("C# Console error:\r\n" + ex); ClearAutocompletes(); } } // Call on OnInputChanged, or maybe limit frequency if its too laggy // update autocomplete buttons private void RefreshAutocompleteButtons() { throw new NotImplementedException("TODO"); } #region UI Construction public void ConstructUI() { Content = UIFactory.CreateUIObject("C# Console", MainMenu.Instance.PageViewport); var mainLayout = Content.AddComponent(); mainLayout.preferredHeight = 300; mainLayout.flexibleHeight = 4; var mainGroup = Content.AddComponent(); mainGroup.childControlHeight = true; mainGroup.childControlWidth = true; mainGroup.childForceExpandHeight = true; mainGroup.childForceExpandWidth = true; var topBarObj = UIFactory.CreateHorizontalGroup(Content); var topBarLayout = topBarObj.AddComponent(); topBarLayout.minHeight = 50; topBarLayout.flexibleHeight = 0; var topBarGroup = topBarObj.GetComponent(); topBarGroup.padding.left = 30; topBarGroup.padding.right = 30; topBarGroup.padding.top = 8; topBarGroup.padding.bottom = 8; topBarGroup.spacing = 10; topBarGroup.childForceExpandHeight = true; topBarGroup.childForceExpandWidth = true; topBarGroup.childControlWidth = true; topBarGroup.childControlHeight = true; var topBarLabel = UIFactory.CreateLabel(topBarObj, TextAnchor.MiddleLeft); var topBarLabelLayout = topBarLabel.AddComponent(); topBarLabelLayout.preferredWidth = 800; topBarLabelLayout.flexibleWidth = 10; var topBarText = topBarLabel.GetComponent(); topBarText.text = "C# Console"; topBarText.fontSize = 20; var compileBtnObj = UIFactory.CreateButton(topBarObj); var compileBtnLayout = compileBtnObj.AddComponent(); compileBtnLayout.preferredWidth = 80; compileBtnLayout.flexibleWidth = 0; var compileButton = compileBtnObj.GetComponent