mirror of
https://github.com/GrahamKracker/UnityExplorer.git
synced 2025-07-03 12:02:28 +08:00
303 lines
9.6 KiB
C#
303 lines
9.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.UI;
|
|
using UnityExplorer.Core.CSharp;
|
|
using UnityExplorer.Core.Input;
|
|
using UnityExplorer.Core.Runtime;
|
|
using UnityExplorer.Core.Unity;
|
|
using UnityExplorer.UI;
|
|
using UnityExplorer.UI.Main;
|
|
|
|
namespace UnityExplorer.UI.Main.CSConsole
|
|
{
|
|
public class AutoCompleter
|
|
{
|
|
public static AutoCompleter Instance;
|
|
|
|
public const int MAX_LABELS = 500;
|
|
private const int UPDATES_PER_BATCH = 100;
|
|
|
|
public static GameObject m_mainObj;
|
|
|
|
private static readonly List<GameObject> m_suggestionButtons = new List<GameObject>();
|
|
private static readonly List<Text> m_suggestionTexts = new List<Text>();
|
|
private static readonly List<Text> m_hiddenSuggestionTexts = new List<Text>();
|
|
|
|
private static bool m_suggestionsDirty;
|
|
private static Suggestion[] m_suggestions = new Suggestion[0];
|
|
private static int m_lastBatchIndex;
|
|
|
|
private static string m_prevInput = "NULL";
|
|
private static int m_lastCaretPos;
|
|
|
|
public static void Init()
|
|
{
|
|
ConstructUI();
|
|
|
|
m_mainObj.SetActive(false);
|
|
}
|
|
|
|
public static void Update()
|
|
{
|
|
if (!m_mainObj)
|
|
return;
|
|
|
|
if (!CSharpConsole.EnableAutocompletes)
|
|
{
|
|
if (m_mainObj.activeSelf)
|
|
m_mainObj.SetActive(false);
|
|
|
|
return;
|
|
}
|
|
|
|
RefreshButtons();
|
|
|
|
UpdatePosition();
|
|
}
|
|
|
|
public static void SetSuggestions(Suggestion[] suggestions)
|
|
{
|
|
m_suggestions = suggestions;
|
|
|
|
m_suggestionsDirty = true;
|
|
m_lastBatchIndex = 0;
|
|
}
|
|
|
|
private static void RefreshButtons()
|
|
{
|
|
if (!m_suggestionsDirty)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (m_suggestions.Length < 1)
|
|
{
|
|
if (m_mainObj.activeSelf)
|
|
{
|
|
m_mainObj?.SetActive(false);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!m_mainObj.activeSelf)
|
|
{
|
|
m_mainObj.SetActive(true);
|
|
}
|
|
|
|
if (m_suggestions.Length < 1 || m_lastBatchIndex >= MAX_LABELS)
|
|
{
|
|
m_suggestionsDirty = false;
|
|
return;
|
|
}
|
|
|
|
int end = m_lastBatchIndex + UPDATES_PER_BATCH;
|
|
for (int i = m_lastBatchIndex; i < end && i < MAX_LABELS; i++)
|
|
{
|
|
if (i >= m_suggestions.Length)
|
|
{
|
|
if (m_suggestionButtons[i].activeSelf)
|
|
{
|
|
m_suggestionButtons[i].SetActive(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!m_suggestionButtons[i].activeSelf)
|
|
{
|
|
m_suggestionButtons[i].SetActive(true);
|
|
}
|
|
|
|
var suggestion = m_suggestions[i];
|
|
var label = m_suggestionTexts[i];
|
|
var hiddenLabel = m_hiddenSuggestionTexts[i];
|
|
|
|
label.text = suggestion.Full;
|
|
hiddenLabel.text = suggestion.Addition;
|
|
|
|
label.color = suggestion.TextColor;
|
|
}
|
|
|
|
m_lastBatchIndex = i;
|
|
}
|
|
|
|
m_lastBatchIndex++;
|
|
}
|
|
|
|
private static void UpdatePosition()
|
|
{
|
|
try
|
|
{
|
|
var editor = CSharpConsole.Instance;
|
|
|
|
if (!editor.InputField.isFocused)
|
|
return;
|
|
|
|
var textGen = editor.InputText.cachedTextGenerator;
|
|
int caretPos = editor.m_lastCaretPos;
|
|
|
|
if (caretPos == m_lastCaretPos)
|
|
return;
|
|
|
|
m_lastCaretPos = caretPos;
|
|
|
|
if (caretPos >= 1)
|
|
caretPos--;
|
|
|
|
var pos = textGen.characters[caretPos].cursorPos;
|
|
|
|
pos = editor.InputField.transform.TransformPoint(pos);
|
|
|
|
m_mainObj.transform.position = new Vector3(pos.x + 10, pos.y - 20, 0);
|
|
}
|
|
catch //(Exception e)
|
|
{
|
|
//ExplorerCore.Log(e.ToString());
|
|
}
|
|
}
|
|
|
|
private static readonly char[] splitChars = new[] { '{', '}', ',', ';', '<', '>', '(', ')', '[', ']', '=', '|', '&', '?' };
|
|
|
|
public static void CheckAutocomplete()
|
|
{
|
|
var m_codeEditor = CSharpConsole.Instance;
|
|
string input = m_codeEditor.InputField.text;
|
|
int caretIndex = m_codeEditor.InputField.caretPosition;
|
|
|
|
if (!string.IsNullOrEmpty(input))
|
|
{
|
|
try
|
|
{
|
|
int start = caretIndex <= 0 ? 0 : input.LastIndexOfAny(splitChars, caretIndex - 1) + 1;
|
|
input = input.Substring(start, caretIndex - start).Trim();
|
|
}
|
|
catch (ArgumentException) { }
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(input) && input != m_prevInput)
|
|
{
|
|
GetAutocompletes(input);
|
|
}
|
|
else
|
|
{
|
|
ClearAutocompletes();
|
|
}
|
|
|
|
m_prevInput = input;
|
|
}
|
|
|
|
public static void ClearAutocompletes()
|
|
{
|
|
if (CSharpConsole.AutoCompletes.Any())
|
|
{
|
|
CSharpConsole.AutoCompletes.Clear();
|
|
}
|
|
}
|
|
|
|
public static void GetAutocompletes(string input)
|
|
{
|
|
try
|
|
{
|
|
// Credit ManylMarco
|
|
CSharpConsole.AutoCompletes.Clear();
|
|
string[] completions = CSharpConsole.Instance.Evaluator.GetCompletions(input, out string prefix);
|
|
if (completions != null)
|
|
{
|
|
if (prefix == null)
|
|
{
|
|
prefix = input;
|
|
}
|
|
|
|
CSharpConsole.AutoCompletes.AddRange(completions
|
|
.Where(x => !string.IsNullOrEmpty(x))
|
|
.Select(x => new Suggestion(x, prefix, Suggestion.Contexts.Other))
|
|
);
|
|
}
|
|
|
|
string trimmed = input.Trim();
|
|
if (trimmed.StartsWith("using"))
|
|
{
|
|
trimmed = trimmed.Remove(0, 5).Trim();
|
|
}
|
|
|
|
IEnumerable<Suggestion> namespaces = Suggestion.Namespaces
|
|
.Where(x => x.StartsWith(trimmed) && x.Length > trimmed.Length)
|
|
.Select(x => new Suggestion(
|
|
x.Substring(trimmed.Length),
|
|
x.Substring(0, trimmed.Length),
|
|
Suggestion.Contexts.Namespace));
|
|
|
|
CSharpConsole.AutoCompletes.AddRange(namespaces);
|
|
|
|
IEnumerable<Suggestion> keywords = Suggestion.Keywords
|
|
.Where(x => x.StartsWith(trimmed) && x.Length > trimmed.Length)
|
|
.Select(x => new Suggestion(
|
|
x.Substring(trimmed.Length),
|
|
x.Substring(0, trimmed.Length),
|
|
Suggestion.Contexts.Keyword));
|
|
|
|
CSharpConsole.AutoCompletes.AddRange(keywords);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ExplorerCore.Log("Autocomplete error:\r\n" + ex.ToString());
|
|
ClearAutocompletes();
|
|
}
|
|
}
|
|
|
|
#region UI Construction
|
|
|
|
private static void ConstructUI()
|
|
{
|
|
var parent = UIManager.CanvasRoot;
|
|
|
|
var obj = UIFactory.CreateScrollView(parent, "AutoCompleterScrollView", out GameObject content, out _, new Color(0.1f, 0.1f, 0.1f, 0.95f));
|
|
|
|
m_mainObj = obj;
|
|
|
|
var mainRect = obj.GetComponent<RectTransform>();
|
|
//m_thisRect = mainRect;
|
|
mainRect.pivot = new Vector2(0f, 1f);
|
|
mainRect.anchorMin = new Vector2(0.45f, 0.45f);
|
|
mainRect.anchorMax = new Vector2(0.65f, 0.6f);
|
|
mainRect.offsetMin = Vector2.zero;
|
|
mainRect.offsetMax = Vector2.zero;
|
|
|
|
var mainGroup = content.GetComponent<VerticalLayoutGroup>();
|
|
mainGroup.SetChildControlHeight(false);
|
|
mainGroup.SetChildControlWidth(true);
|
|
mainGroup.childForceExpandHeight = false;
|
|
mainGroup.childForceExpandWidth = true;
|
|
|
|
for (int i = 0; i < MAX_LABELS; i++)
|
|
{
|
|
var btn = UIFactory.CreateButton(content, "AutoCompleteButton", "", null);
|
|
RuntimeProvider.Instance.SetColorBlock(btn, new Color(0, 0, 0, 0), highlighted: new Color(0.2f, 0.2f, 0.2f, 1.0f));
|
|
|
|
var nav = btn.navigation;
|
|
nav.mode = Navigation.Mode.Vertical;
|
|
btn.navigation = nav;
|
|
|
|
UIFactory.SetLayoutElement(btn.gameObject, minHeight: 20);
|
|
|
|
var text = btn.GetComponentInChildren<Text>();
|
|
text.alignment = TextAnchor.MiddleLeft;
|
|
text.color = Color.white;
|
|
|
|
var hiddenChild = UIFactory.CreateUIObject("HiddenText", btn.gameObject);
|
|
hiddenChild.SetActive(false);
|
|
var hiddenText = hiddenChild.AddComponent<Text>();
|
|
m_hiddenSuggestionTexts.Add(hiddenText);
|
|
btn.onClick.AddListener(() => { CSharpConsole.Instance.UseAutocomplete(hiddenText.text); });
|
|
|
|
m_suggestionButtons.Add(btn.gameObject);
|
|
m_suggestionTexts.Add(text);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|