mirror of
https://github.com/GrahamKracker/UnityExplorer.git
synced 2025-07-14 23:56:36 +08:00
Finished scene explorer, lots of cleanups. Inspector and Search left now.
This commit is contained in:
320
src/UI/Main/Console/AutoCompleter.cs
Normal file
320
src/UI/Main/Console/AutoCompleter.cs
Normal file
@ -0,0 +1,320 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
#if CPP
|
||||
#endif
|
||||
|
||||
namespace ExplorerBeta.UI.Main.Console
|
||||
{
|
||||
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 RectTransform m_thisRect;
|
||||
|
||||
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 (!ConsolePage.EnableSuggestions)
|
||||
{
|
||||
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()
|
||||
{
|
||||
var editor = ConsolePage.Instance.m_codeEditor;
|
||||
|
||||
if (editor.InputField.text.Length < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int caretPos = editor.InputField.caretPosition;
|
||||
while (caretPos >= editor.inputText.textInfo.characterInfo.Length)
|
||||
{
|
||||
caretPos--;
|
||||
}
|
||||
|
||||
if (caretPos == m_lastCaretPos)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_lastCaretPos = caretPos;
|
||||
|
||||
if (caretPos >= 0 && caretPos < editor.inputText.textInfo.characterInfo.Length)
|
||||
{
|
||||
var pos = editor.inputText.textInfo.characterInfo[caretPos].bottomLeft;
|
||||
|
||||
pos = editor.InputField.transform.TransformPoint(pos);
|
||||
|
||||
m_mainObj.transform.position = new Vector3(pos.x, pos.y - 3, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly char[] splitChars = new[] { '{', '}', ',', ';', '<', '>', '(', ')', '[', ']', '=', '|', '&' };
|
||||
|
||||
public static void CheckAutocomplete()
|
||||
{
|
||||
var m_codeEditor = ConsolePage.Instance.m_codeEditor;
|
||||
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 (ConsolePage.AutoCompletes.Any())
|
||||
{
|
||||
ConsolePage.AutoCompletes.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetAutocompletes(string input)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Credit ManylMarco
|
||||
ConsolePage.AutoCompletes.Clear();
|
||||
string[] completions = ConsolePage.Instance.m_evaluator.GetCompletions(input, out string prefix);
|
||||
if (completions != null)
|
||||
{
|
||||
if (prefix == null)
|
||||
{
|
||||
prefix = input;
|
||||
}
|
||||
|
||||
ConsolePage.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));
|
||||
|
||||
ConsolePage.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));
|
||||
|
||||
ConsolePage.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, out GameObject content, 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.childControlHeight = false;
|
||||
mainGroup.childControlWidth = true;
|
||||
mainGroup.childForceExpandHeight = false;
|
||||
mainGroup.childForceExpandWidth = true;
|
||||
|
||||
for (int i = 0; i < MAX_LABELS; i++)
|
||||
{
|
||||
var buttonObj = UIFactory.CreateButton(content);
|
||||
Button btn = buttonObj.GetComponent<Button>();
|
||||
ColorBlock btnColors = btn.colors;
|
||||
btnColors.normalColor = new Color(0f, 0f, 0f, 0f);
|
||||
btnColors.highlightedColor = new Color(0.2f, 0.2f, 0.2f, 1.0f);
|
||||
btn.colors = btnColors;
|
||||
|
||||
var nav = btn.navigation;
|
||||
nav.mode = Navigation.Mode.Vertical;
|
||||
btn.navigation = nav;
|
||||
|
||||
var btnLayout = buttonObj.AddComponent<LayoutElement>();
|
||||
btnLayout.minHeight = 20;
|
||||
|
||||
var text = btn.GetComponentInChildren<Text>();
|
||||
text.alignment = TextAnchor.MiddleLeft;
|
||||
text.color = Color.white;
|
||||
|
||||
var hiddenChild = UIFactory.CreateUIObject("HiddenText", buttonObj);
|
||||
hiddenChild.SetActive(false);
|
||||
var hiddenText = hiddenChild.AddComponent<Text>();
|
||||
m_hiddenSuggestionTexts.Add(hiddenText);
|
||||
|
||||
#if CPP
|
||||
btn.onClick.AddListener(new Action(UseAutocompleteButton));
|
||||
#else
|
||||
btn.onClick.AddListener(UseAutocompleteButton);
|
||||
#endif
|
||||
|
||||
void UseAutocompleteButton()
|
||||
{
|
||||
ConsolePage.Instance.UseAutocomplete(hiddenText.text);
|
||||
EventSystem.current.SetSelectedGameObject(ConsolePage.Instance.m_codeEditor.InputField.gameObject,
|
||||
null);
|
||||
}
|
||||
|
||||
m_suggestionButtons.Add(buttonObj);
|
||||
m_suggestionTexts.Add(text);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
181
src/UI/Main/Console/CSharpLexer.cs
Normal file
181
src/UI/Main/Console/CSharpLexer.cs
Normal file
@ -0,0 +1,181 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using ExplorerBeta.UI.Main.Console.Lexer;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ExplorerBeta.UI.Main.Console
|
||||
{
|
||||
public static class CSharpLexer
|
||||
{
|
||||
public static char indentIncreaseCharacter = '{';
|
||||
public static char indentDecreaseCharacter = '}';
|
||||
|
||||
public static string delimiterSymbols = "[ ] ( ) { } ; : , .";
|
||||
|
||||
private static readonly StringBuilder indentBuilder = new StringBuilder();
|
||||
|
||||
public static CommentMatch commentMatcher = new CommentMatch();
|
||||
public static SymbolMatch symbolMatcher = new SymbolMatch();
|
||||
public static NumberMatch numberMatcher = new NumberMatch();
|
||||
public static StringMatch stringMatcher = new StringMatch();
|
||||
|
||||
public static KeywordMatch validKeywordMatcher = new KeywordMatch
|
||||
{
|
||||
highlightColor = new Color(0.33f, 0.61f, 0.83f, 1.0f),
|
||||
keywords = @"add as ascending await bool break by byte
|
||||
case catch char checked const continue decimal default descending do dynamic
|
||||
else equals false finally float for foreach from global goto group
|
||||
if in int into is join let lock long new null object on orderby out
|
||||
ref remove return sbyte select short sizeof stackalloc string
|
||||
switch throw true try typeof uint ulong ushort
|
||||
value var where while yield"
|
||||
};
|
||||
|
||||
public static KeywordMatch invalidKeywordMatcher = new KeywordMatch()
|
||||
{
|
||||
highlightColor = new Color(0.95f, 0.10f, 0.10f, 1.0f),
|
||||
keywords = @"abstract async base class delegate enum explicit extern fixed get
|
||||
implicit interface internal namespace operator override params private protected public
|
||||
using partial readonly sealed set static struct this unchecked unsafe virtual volatile void"
|
||||
};
|
||||
|
||||
private static char[] delimiterSymbolCache = null;
|
||||
internal static char[] DelimiterSymbols
|
||||
{
|
||||
get
|
||||
{
|
||||
if (delimiterSymbolCache == null)
|
||||
{
|
||||
string[] symbols = delimiterSymbols.Split(' ');
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < symbols.Length; i++)
|
||||
{
|
||||
if (symbols[i].Length == 1)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
delimiterSymbolCache = new char[count];
|
||||
|
||||
for (int i = 0, index = 0; i < symbols.Length; i++)
|
||||
{
|
||||
if (symbols[i].Length == 1)
|
||||
{
|
||||
delimiterSymbolCache[index] = symbols[i][0];
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return delimiterSymbolCache;
|
||||
}
|
||||
}
|
||||
|
||||
private static MatchLexer[] matchers = null;
|
||||
internal static MatchLexer[] Matchers
|
||||
{
|
||||
get
|
||||
{
|
||||
if (matchers == null)
|
||||
{
|
||||
List<MatchLexer> matcherList = new List<MatchLexer>
|
||||
{
|
||||
commentMatcher,
|
||||
symbolMatcher,
|
||||
numberMatcher,
|
||||
stringMatcher,
|
||||
validKeywordMatcher,
|
||||
invalidKeywordMatcher,
|
||||
};
|
||||
|
||||
matchers = matcherList.ToArray();
|
||||
}
|
||||
return matchers;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetIndentForInput(string input, int indent, out int caretPosition)
|
||||
{
|
||||
indentBuilder.Clear();
|
||||
|
||||
indent += 1;
|
||||
|
||||
bool stringState = false;
|
||||
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
if (input[i] == '"')
|
||||
{
|
||||
stringState = !stringState;
|
||||
}
|
||||
|
||||
if (input[i] == '\n')
|
||||
{
|
||||
indentBuilder.Append('\n');
|
||||
for (int j = 0; j < indent; j++)
|
||||
{
|
||||
indentBuilder.Append("\t");
|
||||
}
|
||||
}
|
||||
else if (input[i] == '\t')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (!stringState && input[i] == indentIncreaseCharacter)
|
||||
{
|
||||
indentBuilder.Append(indentIncreaseCharacter);
|
||||
indent++;
|
||||
}
|
||||
else if (!stringState && input[i] == indentDecreaseCharacter)
|
||||
{
|
||||
indentBuilder.Append(indentDecreaseCharacter);
|
||||
indent--;
|
||||
}
|
||||
else
|
||||
{
|
||||
indentBuilder.Append(input[i]);
|
||||
}
|
||||
}
|
||||
|
||||
string formattedSection = indentBuilder.ToString();
|
||||
|
||||
caretPosition = formattedSection.Length - 1;
|
||||
|
||||
for (int i = formattedSection.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (formattedSection[i] == '\n')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
caretPosition = i;
|
||||
break;
|
||||
}
|
||||
|
||||
return formattedSection;
|
||||
}
|
||||
|
||||
public static int GetIndentLevel(string inputString, int startIndex, int endIndex)
|
||||
{
|
||||
int indent = 0;
|
||||
|
||||
for (int i = startIndex; i < endIndex; i++)
|
||||
{
|
||||
if (inputString[i] == '\t')
|
||||
{
|
||||
indent++;
|
||||
}
|
||||
|
||||
// Check for end line or other characters
|
||||
if (inputString[i] == '\n' || inputString[i] != ' ')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return indent;
|
||||
}
|
||||
}
|
||||
}
|
439
src/UI/Main/Console/CodeEditor.cs
Normal file
439
src/UI/Main/Console/CodeEditor.cs
Normal file
@ -0,0 +1,439 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using ExplorerBeta.Input;
|
||||
using ExplorerBeta.UI.Main.Console.Lexer;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace ExplorerBeta.UI.Main.Console
|
||||
{
|
||||
public class CodeEditor
|
||||
{
|
||||
private readonly InputLexer inputLexer = new InputLexer();
|
||||
|
||||
public TMP_InputField InputField { get; }
|
||||
|
||||
public readonly TextMeshProUGUI inputText;
|
||||
private readonly TextMeshProUGUI inputHighlightText;
|
||||
private readonly TextMeshProUGUI lineText;
|
||||
private readonly Image background;
|
||||
private readonly Image lineHighlight;
|
||||
private readonly Image lineNumberBackground;
|
||||
private readonly Image scrollbar;
|
||||
|
||||
private bool lineHighlightLocked;
|
||||
private readonly RectTransform inputTextTransform;
|
||||
private readonly RectTransform lineHighlightTransform;
|
||||
|
||||
public int LineCount { get; private set; }
|
||||
public int CurrentLine { get; private set; }
|
||||
public int CurrentColumn { get; private set; }
|
||||
public int CurrentIndent { get; private set; }
|
||||
|
||||
private static readonly StringBuilder highlightedBuilder = new StringBuilder(4096);
|
||||
private static readonly StringBuilder lineBuilder = new StringBuilder();
|
||||
|
||||
private static readonly KeyCode[] lineChangeKeys =
|
||||
{
|
||||
KeyCode.Return, KeyCode.Backspace, KeyCode.UpArrow,
|
||||
KeyCode.DownArrow, KeyCode.LeftArrow, KeyCode.RightArrow
|
||||
};
|
||||
|
||||
public string HighlightedText => inputHighlightText.text;
|
||||
|
||||
public string Text
|
||||
{
|
||||
get { return InputField.text; }
|
||||
set
|
||||
{
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
InputField.text = value;
|
||||
inputHighlightText.text = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
InputField.text = string.Empty;
|
||||
inputHighlightText.text = string.Empty;
|
||||
}
|
||||
|
||||
inputText.ForceMeshUpdate(false);
|
||||
}
|
||||
}
|
||||
|
||||
public CodeEditor(TMP_InputField inputField, TextMeshProUGUI inputText, TextMeshProUGUI inputHighlightText, TextMeshProUGUI lineText,
|
||||
Image background, Image lineHighlight, Image lineNumberBackground, Image scrollbar)
|
||||
{
|
||||
InputField = inputField;
|
||||
this.inputText = inputText;
|
||||
this.inputHighlightText = inputHighlightText;
|
||||
this.lineText = lineText;
|
||||
this.background = background;
|
||||
this.lineHighlight = lineHighlight;
|
||||
this.lineNumberBackground = lineNumberBackground;
|
||||
this.scrollbar = scrollbar;
|
||||
|
||||
if (!AllReferencesAssigned())
|
||||
{
|
||||
throw new Exception("References are missing!");
|
||||
}
|
||||
|
||||
inputTextTransform = inputText.GetComponent<RectTransform>();
|
||||
lineHighlightTransform = lineHighlight.GetComponent<RectTransform>();
|
||||
|
||||
ApplyTheme();
|
||||
inputLexer.UseMatchers(CSharpLexer.DelimiterSymbols, CSharpLexer.Matchers);
|
||||
|
||||
// subscribe to text input changing
|
||||
#if CPP
|
||||
InputField.onValueChanged.AddListener(new Action<string>((string s) => { OnInputChanged(); }));
|
||||
#else
|
||||
this.InputField.onValueChanged.AddListener((string s) => { OnInputChanged(); });
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Check for new line
|
||||
if (ConsolePage.EnableAutoIndent && InputManager.GetKeyDown(KeyCode.Return))
|
||||
{
|
||||
AutoIndentCaret();
|
||||
}
|
||||
|
||||
if (EventSystem.current?.currentSelectedGameObject?.name == "InputField (TMP)")
|
||||
{
|
||||
bool focusKeyPressed = false;
|
||||
|
||||
// Check for any focus key pressed
|
||||
foreach (KeyCode key in lineChangeKeys)
|
||||
{
|
||||
if (InputManager.GetKeyDown(key))
|
||||
{
|
||||
focusKeyPressed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Update line highlight
|
||||
if (focusKeyPressed || InputManager.GetMouseButton(0))
|
||||
{
|
||||
UpdateHighlight();
|
||||
ConsolePage.Instance.OnInputChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnInputChanged(bool forceUpdate = false)
|
||||
{
|
||||
string newText = InputField.text;
|
||||
|
||||
UpdateIndent();
|
||||
|
||||
if (!forceUpdate && string.IsNullOrEmpty(newText))
|
||||
{
|
||||
inputHighlightText.text = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
inputHighlightText.text = SyntaxHighlightContent(newText);
|
||||
}
|
||||
|
||||
UpdateLineNumbers();
|
||||
UpdateHighlight();
|
||||
|
||||
ConsolePage.Instance.OnInputChanged();
|
||||
}
|
||||
|
||||
public void SetLineHighlight(int lineNumber, bool lockLineHighlight)
|
||||
{
|
||||
if (lineNumber < 1 || lineNumber > LineCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lineHighlightTransform.anchoredPosition = new Vector2(5,
|
||||
(inputText.textInfo.lineInfo[inputText.textInfo.characterInfo[0].lineNumber].lineHeight *
|
||||
-(lineNumber - 1)) - 4f +
|
||||
inputTextTransform.anchoredPosition.y);
|
||||
|
||||
lineHighlightLocked = lockLineHighlight;
|
||||
}
|
||||
|
||||
private void UpdateLineNumbers()
|
||||
{
|
||||
int currentLineCount = inputText.textInfo.lineCount;
|
||||
|
||||
int currentLineNumber = 1;
|
||||
|
||||
if (currentLineCount != LineCount)
|
||||
{
|
||||
try
|
||||
{
|
||||
lineBuilder.Length = 0;
|
||||
|
||||
for (int i = 1; i < currentLineCount + 2; i++)
|
||||
{
|
||||
if (i - 1 > 0 && i - 1 < currentLineCount - 1)
|
||||
{
|
||||
int characterStart = inputText.textInfo.lineInfo[i - 1].firstCharacterIndex;
|
||||
int characterCount = inputText.textInfo.lineInfo[i - 1].characterCount;
|
||||
|
||||
if (characterStart >= 0 && characterStart < inputText.text.Length &&
|
||||
characterCount != 0 && !inputText.text.Substring(characterStart, characterCount).Contains("\n"))
|
||||
{
|
||||
lineBuilder.Append("\n");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
lineBuilder.Append(currentLineNumber);
|
||||
lineBuilder.Append('\n');
|
||||
|
||||
currentLineNumber++;
|
||||
|
||||
if (i - 1 == 0 && i - 1 < currentLineCount - 1)
|
||||
{
|
||||
int characterStart = inputText.textInfo.lineInfo[i - 1].firstCharacterIndex;
|
||||
int characterCount = inputText.textInfo.lineInfo[i - 1].characterCount;
|
||||
|
||||
if (characterStart >= 0 && characterStart < inputText.text.Length &&
|
||||
characterCount != 0 && !inputText.text.Substring(characterStart, characterCount).Contains("\n"))
|
||||
{
|
||||
lineBuilder.Append("\n");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lineText.text = lineBuilder.ToString();
|
||||
LineCount = currentLineCount;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateIndent()
|
||||
{
|
||||
int caret = InputField.caretPosition;
|
||||
|
||||
if (caret < 0 || caret >= inputText.textInfo.characterInfo.Length)
|
||||
{
|
||||
while (caret >= 0 && caret >= inputText.textInfo.characterInfo.Length)
|
||||
{
|
||||
caret--;
|
||||
}
|
||||
|
||||
if (caret < 0 || caret >= inputText.textInfo.characterInfo.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CurrentLine = inputText.textInfo.characterInfo[caret].lineNumber;
|
||||
|
||||
int charCount = 0;
|
||||
for (int i = 0; i < CurrentLine; i++)
|
||||
{
|
||||
charCount += inputText.textInfo.lineInfo[i].characterCount;
|
||||
}
|
||||
|
||||
CurrentColumn = caret - charCount;
|
||||
CurrentIndent = 0;
|
||||
|
||||
for (int i = 0; i < caret && i < InputField.text.Length; i++)
|
||||
{
|
||||
char character = InputField.text[i];
|
||||
|
||||
if (character == CSharpLexer.indentIncreaseCharacter)
|
||||
{
|
||||
CurrentIndent++;
|
||||
}
|
||||
|
||||
if (character == CSharpLexer.indentDecreaseCharacter)
|
||||
{
|
||||
CurrentIndent--;
|
||||
}
|
||||
}
|
||||
|
||||
if (CurrentIndent < 0)
|
||||
{
|
||||
CurrentIndent = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateHighlight()
|
||||
{
|
||||
if (lineHighlightLocked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
int caret = InputField.caretPosition - 1;
|
||||
|
||||
float lineHeight = inputText.textInfo.lineInfo[inputText.textInfo.characterInfo[0].lineNumber].lineHeight;
|
||||
int lineNumber = inputText.textInfo.characterInfo[caret].lineNumber;
|
||||
float offset = lineNumber + inputTextTransform.anchoredPosition.y;
|
||||
|
||||
lineHighlightTransform.anchoredPosition = new Vector2(5, -(offset * lineHeight));
|
||||
}
|
||||
catch //(Exception e)
|
||||
{
|
||||
//ExplorerCore.LogWarning("Exception on Update Line Highlight: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
private const string CLOSE_COLOR_TAG = "</color>";
|
||||
|
||||
private string SyntaxHighlightContent(string inputText)
|
||||
{
|
||||
int offset = 0;
|
||||
|
||||
highlightedBuilder.Length = 0;
|
||||
|
||||
foreach (LexerMatchInfo match in inputLexer.LexInputString(inputText))
|
||||
{
|
||||
for (int i = offset; i < match.startIndex; i++)
|
||||
{
|
||||
highlightedBuilder.Append(inputText[i]);
|
||||
}
|
||||
|
||||
highlightedBuilder.Append(match.htmlColor);
|
||||
|
||||
for (int i = match.startIndex; i < match.endIndex; i++)
|
||||
{
|
||||
highlightedBuilder.Append(inputText[i]);
|
||||
}
|
||||
|
||||
highlightedBuilder.Append(CLOSE_COLOR_TAG);
|
||||
|
||||
offset = match.endIndex;
|
||||
}
|
||||
|
||||
for (int i = offset; i < inputText.Length; i++)
|
||||
{
|
||||
highlightedBuilder.Append(inputText[i]);
|
||||
}
|
||||
|
||||
inputText = highlightedBuilder.ToString();
|
||||
|
||||
return inputText;
|
||||
}
|
||||
|
||||
private void AutoIndentCaret()
|
||||
{
|
||||
if (CurrentIndent > 0)
|
||||
{
|
||||
string indent = GetAutoIndentTab(CurrentIndent);
|
||||
|
||||
if (indent.Length > 0)
|
||||
{
|
||||
int caretPos = InputField.caretPosition;
|
||||
|
||||
string indentMinusOne = indent.Substring(0, indent.Length - 1);
|
||||
|
||||
// get last index of {
|
||||
// chuck it on the next line if its not already
|
||||
string text = InputField.text;
|
||||
string sub = InputField.text.Substring(0, InputField.caretPosition);
|
||||
int lastIndex = sub.LastIndexOf("{");
|
||||
int offset = lastIndex - 1;
|
||||
if (offset >= 0 && text[offset] != '\n' && text[offset] != '\t')
|
||||
{
|
||||
string open = "\n" + indentMinusOne;
|
||||
|
||||
InputField.text = text.Insert(offset + 1, open);
|
||||
|
||||
caretPos += open.Length;
|
||||
}
|
||||
|
||||
// check if should add auto-close }
|
||||
int numOpen = InputField.text.Where(x => x == CSharpLexer.indentIncreaseCharacter).Count();
|
||||
int numClose = InputField.text.Where(x => x == CSharpLexer.indentDecreaseCharacter).Count();
|
||||
|
||||
if (numOpen > numClose)
|
||||
{
|
||||
// add auto-indent closing
|
||||
indentMinusOne = $"\n{indentMinusOne}}}";
|
||||
InputField.text = InputField.text.Insert(caretPos, indentMinusOne);
|
||||
}
|
||||
|
||||
// insert the actual auto indent now
|
||||
InputField.text = InputField.text.Insert(caretPos, indent);
|
||||
|
||||
InputField.stringPosition = caretPos + indent.Length;
|
||||
}
|
||||
}
|
||||
|
||||
// Update line column and indent positions
|
||||
UpdateIndent();
|
||||
|
||||
inputText.text = InputField.text;
|
||||
inputText.SetText(InputField.text, true);
|
||||
inputText.Rebuild(CanvasUpdate.Prelayout);
|
||||
InputField.ForceLabelUpdate();
|
||||
InputField.Rebuild(CanvasUpdate.Prelayout);
|
||||
|
||||
OnInputChanged(true);
|
||||
}
|
||||
|
||||
private string GetAutoIndentTab(int amount)
|
||||
{
|
||||
string tab = string.Empty;
|
||||
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
tab += "\t";
|
||||
}
|
||||
|
||||
return tab;
|
||||
}
|
||||
|
||||
private static Color caretColor = new Color32(255, 255, 255, 255);
|
||||
private static Color textColor = new Color32(255, 255, 255, 255);
|
||||
private static Color backgroundColor = new Color32(37, 37, 37, 255);
|
||||
private static Color lineHighlightColor = new Color32(50, 50, 50, 255);
|
||||
private static Color lineNumberBackgroundColor = new Color32(25, 25, 25, 255);
|
||||
private static Color lineNumberTextColor = new Color32(180, 180, 180, 255);
|
||||
private static Color scrollbarColor = new Color32(45, 50, 50, 255);
|
||||
|
||||
private void ApplyTheme()
|
||||
{
|
||||
var highlightTextRect = inputHighlightText.GetComponent<RectTransform>();
|
||||
highlightTextRect.anchorMin = Vector2.zero;
|
||||
highlightTextRect.anchorMax = Vector2.one;
|
||||
highlightTextRect.offsetMin = Vector2.zero;
|
||||
highlightTextRect.offsetMax = Vector2.zero;
|
||||
|
||||
InputField.caretColor = caretColor;
|
||||
inputText.color = textColor;
|
||||
inputHighlightText.color = textColor;
|
||||
background.color = backgroundColor;
|
||||
lineHighlight.color = lineHighlightColor;
|
||||
lineNumberBackground.color = lineNumberBackgroundColor;
|
||||
lineText.color = lineNumberTextColor;
|
||||
scrollbar.color = scrollbarColor;
|
||||
}
|
||||
|
||||
private bool AllReferencesAssigned()
|
||||
{
|
||||
if (!InputField ||
|
||||
!inputText ||
|
||||
!inputHighlightText ||
|
||||
!lineText ||
|
||||
!background ||
|
||||
!lineHighlight ||
|
||||
!lineNumberBackground ||
|
||||
!scrollbar)
|
||||
{
|
||||
// One or more references are not assigned
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
49
src/UI/Main/Console/Lexer/CommentMatch.cs
Normal file
49
src/UI/Main/Console/Lexer/CommentMatch.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ExplorerBeta.UI.Main.Console.Lexer
|
||||
{
|
||||
public sealed class CommentMatch : MatchLexer
|
||||
{
|
||||
public string lineCommentStart = @"//";
|
||||
public string blockCommentStart = @"/*";
|
||||
public string blockCommentEnd = @"*/";
|
||||
|
||||
public override Color HighlightColor => new Color(0.34f, 0.65f, 0.29f, 1.0f);
|
||||
public override IEnumerable<char> StartChars => new char[] { lineCommentStart[0], blockCommentStart[0] };
|
||||
public override IEnumerable<char> EndChars => new char[] { blockCommentEnd[0] };
|
||||
public override bool IsImplicitMatch(ILexer lexer) => IsMatch(lexer, lineCommentStart) || IsMatch(lexer, blockCommentStart);
|
||||
|
||||
private bool IsMatch(ILexer lexer, string commentType)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(commentType))
|
||||
{
|
||||
lexer.Rollback();
|
||||
|
||||
bool match = true;
|
||||
for (int i = 0; i < commentType.Length; i++)
|
||||
{
|
||||
if (commentType[i] != lexer.ReadNext())
|
||||
{
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match)
|
||||
{
|
||||
// Read until end
|
||||
while (!IsEndLineOrEndFile(lexer, lexer.ReadNext()))
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsEndLineOrEndFile(ILexer lexer, char character) => lexer.EndOfStream || character == '\n' || character == '\r';
|
||||
}
|
||||
}
|
19
src/UI/Main/Console/Lexer/ILexer.cs
Normal file
19
src/UI/Main/Console/Lexer/ILexer.cs
Normal file
@ -0,0 +1,19 @@
|
||||
namespace ExplorerBeta.UI.Main.Console.Lexer
|
||||
{
|
||||
public enum SpecialCharacterPosition
|
||||
{
|
||||
Start,
|
||||
End,
|
||||
};
|
||||
|
||||
public interface ILexer
|
||||
{
|
||||
bool EndOfStream { get; }
|
||||
char Previous { get; }
|
||||
|
||||
char ReadNext();
|
||||
void Rollback(int amount = -1);
|
||||
void Commit();
|
||||
bool IsSpecialSymbol(char character, SpecialCharacterPosition position = SpecialCharacterPosition.Start);
|
||||
}
|
||||
}
|
195
src/UI/Main/Console/Lexer/InputLexer.cs
Normal file
195
src/UI/Main/Console/Lexer/InputLexer.cs
Normal file
@ -0,0 +1,195 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ExplorerBeta.UI.Main.Console.Lexer
|
||||
{
|
||||
internal struct LexerMatchInfo
|
||||
{
|
||||
public int startIndex;
|
||||
public int endIndex;
|
||||
public string htmlColor;
|
||||
}
|
||||
|
||||
internal class InputLexer : ILexer
|
||||
{
|
||||
private string inputString = null;
|
||||
private MatchLexer[] matchers = null;
|
||||
private readonly HashSet<char> specialStartSymbols = new HashSet<char>();
|
||||
private readonly HashSet<char> specialEndSymbols = new HashSet<char>();
|
||||
private int currentIndex = 0;
|
||||
private int currentLookaheadIndex = 0;
|
||||
|
||||
private char current = ' ';
|
||||
public char Previous { get; private set; } = ' ';
|
||||
|
||||
public bool EndOfStream
|
||||
{
|
||||
get { return currentLookaheadIndex >= inputString.Length; }
|
||||
}
|
||||
|
||||
public void UseMatchers(char[] delimiters, MatchLexer[] matchers)
|
||||
{
|
||||
this.matchers = matchers;
|
||||
|
||||
specialStartSymbols.Clear();
|
||||
specialEndSymbols.Clear();
|
||||
|
||||
if (delimiters != null)
|
||||
{
|
||||
foreach (char character in delimiters)
|
||||
{
|
||||
if (!specialStartSymbols.Contains(character))
|
||||
{
|
||||
specialStartSymbols.Add(character);
|
||||
}
|
||||
|
||||
if (!specialEndSymbols.Contains(character))
|
||||
{
|
||||
specialEndSymbols.Add(character);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matchers != null)
|
||||
{
|
||||
foreach (MatchLexer lexer in matchers)
|
||||
{
|
||||
foreach (char special in lexer.StartChars)
|
||||
{
|
||||
if (!specialStartSymbols.Contains(special))
|
||||
{
|
||||
specialStartSymbols.Add(special);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (char special in lexer.EndChars)
|
||||
{
|
||||
if (!specialEndSymbols.Contains(special))
|
||||
{
|
||||
specialEndSymbols.Add(special);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<LexerMatchInfo> LexInputString(string input)
|
||||
{
|
||||
if (input == null || matchers == null || matchers.Length == 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
inputString = input;
|
||||
current = ' ';
|
||||
Previous = ' ';
|
||||
currentIndex = 0;
|
||||
currentLookaheadIndex = 0;
|
||||
|
||||
while (!EndOfStream)
|
||||
{
|
||||
bool didMatchLexer = false;
|
||||
|
||||
ReadWhiteSpace();
|
||||
|
||||
foreach (MatchLexer matcher in matchers)
|
||||
{
|
||||
int startIndex = currentIndex;
|
||||
|
||||
bool isMatched = matcher.IsMatch(this);
|
||||
|
||||
if (isMatched)
|
||||
{
|
||||
int endIndex = currentIndex;
|
||||
|
||||
didMatchLexer = true;
|
||||
|
||||
yield return new LexerMatchInfo
|
||||
{
|
||||
startIndex = startIndex,
|
||||
endIndex = endIndex,
|
||||
htmlColor = matcher.HexColor,
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!didMatchLexer)
|
||||
{
|
||||
ReadNext();
|
||||
Commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public char ReadNext()
|
||||
{
|
||||
if (EndOfStream)
|
||||
{
|
||||
return '\0';
|
||||
}
|
||||
|
||||
Previous = current;
|
||||
|
||||
current = inputString[currentLookaheadIndex];
|
||||
currentLookaheadIndex++;
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
public void Rollback(int amount = -1)
|
||||
{
|
||||
if (amount == -1)
|
||||
{
|
||||
currentLookaheadIndex = currentIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentLookaheadIndex > currentIndex)
|
||||
{
|
||||
currentLookaheadIndex -= amount;
|
||||
}
|
||||
}
|
||||
|
||||
int previousIndex = currentLookaheadIndex - 1;
|
||||
|
||||
if (previousIndex >= inputString.Length)
|
||||
{
|
||||
Previous = inputString[inputString.Length - 1];
|
||||
}
|
||||
else if (previousIndex >= 0)
|
||||
{
|
||||
Previous = inputString[previousIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
Previous = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
public void Commit()
|
||||
{
|
||||
currentIndex = currentLookaheadIndex;
|
||||
}
|
||||
|
||||
public bool IsSpecialSymbol(char character, SpecialCharacterPosition position = SpecialCharacterPosition.Start)
|
||||
{
|
||||
if (position == SpecialCharacterPosition.Start)
|
||||
{
|
||||
return specialStartSymbols.Contains(character);
|
||||
}
|
||||
|
||||
return specialEndSymbols.Contains(character);
|
||||
}
|
||||
|
||||
private void ReadWhiteSpace()
|
||||
{
|
||||
while (char.IsWhiteSpace(ReadNext()) == true)
|
||||
{
|
||||
Commit();
|
||||
}
|
||||
|
||||
Rollback();
|
||||
}
|
||||
}
|
||||
}
|
116
src/UI/Main/Console/Lexer/KeywordMatch.cs
Normal file
116
src/UI/Main/Console/Lexer/KeywordMatch.cs
Normal file
@ -0,0 +1,116 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ExplorerBeta.UI.Main.Console.Lexer
|
||||
{
|
||||
public sealed class KeywordMatch : MatchLexer
|
||||
{
|
||||
public string keywords;
|
||||
|
||||
public override Color HighlightColor => highlightColor;
|
||||
public Color highlightColor;
|
||||
|
||||
private readonly HashSet<string> shortlist = new HashSet<string>();
|
||||
private readonly Stack<string> removeList = new Stack<string>();
|
||||
public string[] keywordCache = null;
|
||||
|
||||
public override bool IsImplicitMatch(ILexer lexer)
|
||||
{
|
||||
BuildKeywordCache();
|
||||
|
||||
if (!char.IsWhiteSpace(lexer.Previous) &&
|
||||
!lexer.IsSpecialSymbol(lexer.Previous, SpecialCharacterPosition.End))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
shortlist.Clear();
|
||||
|
||||
int currentIndex = 0;
|
||||
char currentChar = lexer.ReadNext();
|
||||
|
||||
for (int i = 0; i < keywordCache.Length; i++)
|
||||
{
|
||||
if (keywordCache[i][0] == currentChar)
|
||||
{
|
||||
shortlist.Add(keywordCache[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (shortlist.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
if (lexer.EndOfStream)
|
||||
{
|
||||
RemoveLongStrings(currentIndex + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
currentChar = lexer.ReadNext();
|
||||
currentIndex++;
|
||||
|
||||
if (char.IsWhiteSpace(currentChar) ||
|
||||
lexer.IsSpecialSymbol(currentChar, SpecialCharacterPosition.Start))
|
||||
{
|
||||
RemoveLongStrings(currentIndex);
|
||||
lexer.Rollback(1);
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (string keyword in shortlist)
|
||||
{
|
||||
if (currentIndex >= keyword.Length || keyword[currentIndex] != currentChar)
|
||||
{
|
||||
removeList.Push(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
while (removeList.Count > 0)
|
||||
{
|
||||
shortlist.Remove(removeList.Pop());
|
||||
}
|
||||
}
|
||||
while (shortlist.Count > 0);
|
||||
|
||||
return shortlist.Count > 0;
|
||||
}
|
||||
|
||||
private void RemoveLongStrings(int length)
|
||||
{
|
||||
foreach (string keyword in shortlist)
|
||||
{
|
||||
if (keyword.Length > length)
|
||||
{
|
||||
removeList.Push(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
while (removeList.Count > 0)
|
||||
{
|
||||
shortlist.Remove(removeList.Pop());
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildKeywordCache()
|
||||
{
|
||||
if (keywordCache == null)
|
||||
{
|
||||
string[] kwSplit = keywords.Split(' ');
|
||||
|
||||
List<string> list = new List<string>();
|
||||
foreach (string kw in kwSplit)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(kw) && kw.Length > 0)
|
||||
{
|
||||
list.Add(kw);
|
||||
}
|
||||
}
|
||||
keywordCache = list.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
31
src/UI/Main/Console/Lexer/MatchLexer.cs
Normal file
31
src/UI/Main/Console/Lexer/MatchLexer.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
using ExplorerBeta.Unstrip.ColorUtility;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ExplorerBeta.UI.Main.Console.Lexer
|
||||
{
|
||||
public abstract class MatchLexer
|
||||
{
|
||||
public abstract Color HighlightColor { get; }
|
||||
|
||||
public string HexColor => htmlColor ?? (htmlColor = "<#" + HighlightColor.ToHex() + ">");
|
||||
private string htmlColor = null;
|
||||
|
||||
public virtual IEnumerable<char> StartChars { get { yield break; } }
|
||||
public virtual IEnumerable<char> EndChars { get { yield break; } }
|
||||
|
||||
public abstract bool IsImplicitMatch(ILexer lexer);
|
||||
|
||||
public bool IsMatch(ILexer lexer)
|
||||
{
|
||||
if (IsImplicitMatch(lexer))
|
||||
{
|
||||
lexer.Commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
lexer.Rollback();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
39
src/UI/Main/Console/Lexer/NumberMatch.cs
Normal file
39
src/UI/Main/Console/Lexer/NumberMatch.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace ExplorerBeta.UI.Main.Console.Lexer
|
||||
{
|
||||
public sealed class NumberMatch : MatchLexer
|
||||
{
|
||||
public override Color HighlightColor => new Color(0.58f, 0.33f, 0.33f, 1.0f);
|
||||
|
||||
public override bool IsImplicitMatch(ILexer lexer)
|
||||
{
|
||||
if (!char.IsWhiteSpace(lexer.Previous) &&
|
||||
!lexer.IsSpecialSymbol(lexer.Previous, SpecialCharacterPosition.End))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool matchedNumber = false;
|
||||
|
||||
while (!lexer.EndOfStream)
|
||||
{
|
||||
if (IsNumberOrDecimalPoint(lexer.ReadNext()))
|
||||
{
|
||||
matchedNumber = true;
|
||||
lexer.Commit();
|
||||
}
|
||||
else
|
||||
{
|
||||
lexer.Rollback();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return matchedNumber;
|
||||
}
|
||||
|
||||
private bool IsNumberOrDecimalPoint(char character) => char.IsNumber(character) || character == '.';
|
||||
}
|
||||
|
||||
}
|
37
src/UI/Main/Console/Lexer/StringMatch.cs
Normal file
37
src/UI/Main/Console/Lexer/StringMatch.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ExplorerBeta.UI.Main.Console.Lexer
|
||||
{
|
||||
public sealed class StringMatch : MatchLexer
|
||||
{
|
||||
public override Color HighlightColor => new Color(0.79f, 0.52f, 0.32f, 1.0f);
|
||||
|
||||
public override IEnumerable<char> StartChars { get { yield return '"'; } }
|
||||
public override IEnumerable<char> EndChars { get { yield return '"'; } }
|
||||
|
||||
public override bool IsImplicitMatch(ILexer lexer)
|
||||
{
|
||||
if (lexer.ReadNext() == '"')
|
||||
{
|
||||
while (!IsClosingQuoteOrEndFile(lexer, lexer.ReadNext()))
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsClosingQuoteOrEndFile(ILexer lexer, char character)
|
||||
{
|
||||
if (lexer.EndOfStream == true ||
|
||||
character == '"')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
151
src/UI/Main/Console/Lexer/SymbolMatch.cs
Normal file
151
src/UI/Main/Console/Lexer/SymbolMatch.cs
Normal file
@ -0,0 +1,151 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ExplorerBeta.UI.Main.Console.Lexer
|
||||
{
|
||||
public sealed class SymbolMatch : MatchLexer
|
||||
{
|
||||
public override Color HighlightColor => new Color(0.58f, 0.47f, 0.37f, 1.0f);
|
||||
|
||||
public string Symbols => @"[ ] ( ) . ? : + - * / % & | ^ ~ = < > ++ -- && || << >> == != <= >=
|
||||
+= -= *= /= %= &= |= ^= <<= >>= -> ?? =>";
|
||||
|
||||
private static readonly List<string> shortlist = new List<string>();
|
||||
private static readonly Stack<string> removeList = new Stack<string>();
|
||||
private string[] symbolCache = null;
|
||||
|
||||
public override IEnumerable<char> StartChars
|
||||
{
|
||||
get
|
||||
{
|
||||
BuildSymbolCache();
|
||||
foreach (string symbol in symbolCache.Where(x => x.Length > 0))
|
||||
{
|
||||
yield return symbol[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<char> EndChars
|
||||
{
|
||||
get
|
||||
{
|
||||
BuildSymbolCache();
|
||||
foreach (string symbol in symbolCache.Where(x => x.Length > 0))
|
||||
{
|
||||
yield return symbol[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsImplicitMatch(ILexer lexer)
|
||||
{
|
||||
if (lexer == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
BuildSymbolCache();
|
||||
|
||||
if (!char.IsWhiteSpace(lexer.Previous) &&
|
||||
!char.IsLetter(lexer.Previous) &&
|
||||
!char.IsDigit(lexer.Previous) &&
|
||||
!lexer.IsSpecialSymbol(lexer.Previous, SpecialCharacterPosition.End))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
shortlist.Clear();
|
||||
|
||||
int currentIndex = 0;
|
||||
char currentChar = lexer.ReadNext();
|
||||
|
||||
for (int i = symbolCache.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (symbolCache[i][0] == currentChar)
|
||||
{
|
||||
shortlist.Add(symbolCache[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (shortlist.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
if (lexer.EndOfStream)
|
||||
{
|
||||
RemoveLongStrings(currentIndex + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
currentChar = lexer.ReadNext();
|
||||
currentIndex++;
|
||||
|
||||
if (char.IsWhiteSpace(currentChar) ||
|
||||
char.IsLetter(currentChar) ||
|
||||
char.IsDigit(currentChar) ||
|
||||
lexer.IsSpecialSymbol(currentChar, SpecialCharacterPosition.Start))
|
||||
{
|
||||
RemoveLongStrings(currentIndex);
|
||||
lexer.Rollback(1);
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (string symbol in shortlist)
|
||||
{
|
||||
if (currentIndex >= symbol.Length || symbol[currentIndex] != currentChar)
|
||||
{
|
||||
removeList.Push(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
while (removeList.Count > 0)
|
||||
{
|
||||
shortlist.Remove(removeList.Pop());
|
||||
}
|
||||
}
|
||||
while (shortlist.Count > 0);
|
||||
|
||||
return shortlist.Count > 0;
|
||||
}
|
||||
|
||||
private void RemoveLongStrings(int length)
|
||||
{
|
||||
foreach (string keyword in shortlist)
|
||||
{
|
||||
if (keyword.Length > length)
|
||||
{
|
||||
removeList.Push(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
while (removeList.Count > 0)
|
||||
{
|
||||
shortlist.Remove(removeList.Pop());
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildSymbolCache()
|
||||
{
|
||||
if (symbolCache != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string[] symSplit = Symbols.Split(' ');
|
||||
List<string> list = new List<string>();
|
||||
foreach (string sym in symSplit)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(sym) && sym.Length > 0)
|
||||
{
|
||||
list.Add(sym);
|
||||
}
|
||||
}
|
||||
symbolCache = list.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
76
src/UI/Main/Console/ScriptEvaluator.cs
Normal file
76
src/UI/Main/Console/ScriptEvaluator.cs
Normal file
@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Mono.CSharp;
|
||||
|
||||
// Thanks to ManlyMarco for this
|
||||
|
||||
namespace ExplorerBeta.UI.Main.Console
|
||||
{
|
||||
public class ScriptEvaluator : Evaluator, IDisposable
|
||||
{
|
||||
private static readonly HashSet<string> StdLib = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase)
|
||||
{
|
||||
"mscorlib", "System.Core", "System", "System.Xml"
|
||||
};
|
||||
|
||||
private readonly TextWriter _logger;
|
||||
|
||||
public ScriptEvaluator(TextWriter logger) : base(BuildContext(logger))
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
ImportAppdomainAssemblies(ReferenceAssembly);
|
||||
AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
AppDomain.CurrentDomain.AssemblyLoad -= OnAssemblyLoad;
|
||||
_logger.Dispose();
|
||||
}
|
||||
|
||||
private void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args)
|
||||
{
|
||||
string name = args.LoadedAssembly.GetName().Name;
|
||||
if (StdLib.Contains(name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ReferenceAssembly(args.LoadedAssembly);
|
||||
}
|
||||
|
||||
private static CompilerContext BuildContext(TextWriter tw)
|
||||
{
|
||||
var reporter = new StreamReportPrinter(tw);
|
||||
|
||||
var settings = new CompilerSettings
|
||||
{
|
||||
Version = LanguageVersion.Experimental,
|
||||
GenerateDebugInfo = false,
|
||||
StdLib = true,
|
||||
Target = Target.Library,
|
||||
WarningLevel = 0,
|
||||
EnhancedWarnings = false
|
||||
};
|
||||
|
||||
return new CompilerContext(settings, reporter);
|
||||
}
|
||||
|
||||
private static void ImportAppdomainAssemblies(Action<Assembly> import)
|
||||
{
|
||||
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
string name = assembly.GetName().Name;
|
||||
if (StdLib.Contains(name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
import(assembly);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
92
src/UI/Main/Console/ScriptInteraction.cs
Normal file
92
src/UI/Main/Console/ScriptInteraction.cs
Normal file
@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using Mono.CSharp;
|
||||
|
||||
namespace ExplorerBeta.UI.Main.Console
|
||||
{
|
||||
public class ScriptInteraction : InteractiveBase
|
||||
{
|
||||
public static void Log(object message)
|
||||
{
|
||||
ExplorerCore.Log(message);
|
||||
}
|
||||
|
||||
public static void AddUsing(string directive)
|
||||
{
|
||||
ConsolePage.Instance.AddUsing(directive);
|
||||
}
|
||||
|
||||
public static void GetUsing()
|
||||
{
|
||||
ExplorerCore.Log(ConsolePage.Instance.m_evaluator.GetUsing());
|
||||
}
|
||||
|
||||
public static void Reset()
|
||||
{
|
||||
ConsolePage.Instance.ResetConsole();
|
||||
}
|
||||
|
||||
public static object CurrentTarget()
|
||||
{
|
||||
throw new NotImplementedException("TODO");
|
||||
}
|
||||
|
||||
public static object[] AllTargets()
|
||||
{
|
||||
throw new NotImplementedException("TODO");
|
||||
}
|
||||
|
||||
public static void Inspect(object obj)
|
||||
{
|
||||
throw new NotImplementedException("TODO");
|
||||
}
|
||||
|
||||
public static void Inspect(Type type)
|
||||
{
|
||||
throw new NotImplementedException("TODO");
|
||||
}
|
||||
|
||||
// public static void Help()
|
||||
// {
|
||||
// ExplorerCore.Log(@"
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// C# Console Help
|
||||
|
||||
//The following helper methods are available:
|
||||
|
||||
//void Log(object message)
|
||||
// prints a message to the console window and debug log
|
||||
// usage: Log(""hello world"");
|
||||
|
||||
//void AddUsing(string directive)
|
||||
// adds a using directive to the console.
|
||||
// usage: AddUsing(""UnityEngine.UI"");
|
||||
|
||||
//void GetUsing()
|
||||
// logs the current using directives to the debug console
|
||||
// usage: GetUsing();
|
||||
|
||||
//void Reset()
|
||||
// resets the C# console, clearing all variables and using directives.
|
||||
// usage: Reset();
|
||||
//");
|
||||
|
||||
//TODO:
|
||||
|
||||
//ExplorerCore.Log("object CurrentTarget()");
|
||||
//ExplorerCore.Log(" returns the target object of the current tab (in tab view mode only)");
|
||||
//ExplorerCore.Log(" usage: var target = CurrentTarget();");
|
||||
//ExplorerCore.Log("");
|
||||
//ExplorerCore.Log("object[] AllTargets()");
|
||||
//ExplorerCore.Log(" returns an object[] array containing all currently inspected objects");
|
||||
//ExplorerCore.Log(" usage: var targets = AllTargets();");
|
||||
//ExplorerCore.Log("");
|
||||
//ExplorerCore.Log("void Inspect(object obj)");
|
||||
//ExplorerCore.Log(" inspects the provided object in a new window.");
|
||||
//ExplorerCore.Log(" usage: Inspect(Camera.main);");
|
||||
//ExplorerCore.Log("");
|
||||
//ExplorerCore.Log("void Inspect(Type type)");
|
||||
//ExplorerCore.Log(" attempts to inspect the provided type with static-only reflection.");
|
||||
//ExplorerCore.Log(" usage: Inspect(typeof(Camera));");
|
||||
//}
|
||||
}
|
||||
}
|
81
src/UI/Main/Console/Suggestion.cs
Normal file
81
src/UI/Main/Console/Suggestion.cs
Normal file
@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ExplorerBeta.UI.Main.Console
|
||||
{
|
||||
public struct Suggestion
|
||||
{
|
||||
public string Full => Prefix + Addition;
|
||||
|
||||
public readonly string Prefix;
|
||||
public readonly string Addition;
|
||||
public readonly Contexts Context;
|
||||
|
||||
public Color TextColor
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (Context)
|
||||
{
|
||||
case Contexts.Namespace: return Color.grey;
|
||||
case Contexts.Keyword: return systemBlue;
|
||||
default: return Color.white;
|
||||
}
|
||||
}
|
||||
}
|
||||
private static readonly Color systemBlue = new Color(80f / 255f, 150f / 255f, 215f / 255f);
|
||||
|
||||
public Suggestion(string addition, string prefix, Contexts type)
|
||||
{
|
||||
Addition = addition;
|
||||
Prefix = prefix;
|
||||
Context = type;
|
||||
}
|
||||
|
||||
public enum Contexts
|
||||
{
|
||||
Namespace,
|
||||
Keyword,
|
||||
Other
|
||||
}
|
||||
|
||||
public static HashSet<string> Namespaces => m_namspaces ?? GetNamespaces();
|
||||
private static HashSet<string> m_namspaces;
|
||||
|
||||
private static HashSet<string> GetNamespaces()
|
||||
{
|
||||
HashSet<string> set = new HashSet<string>(
|
||||
AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(GetTypes)
|
||||
.Where(x => x.IsPublic && !string.IsNullOrEmpty(x.Namespace))
|
||||
.Select(x => x.Namespace));
|
||||
|
||||
return m_namspaces = set;
|
||||
|
||||
IEnumerable<Type> GetTypes(Assembly asm) => asm.TryGetTypes();
|
||||
}
|
||||
|
||||
public static HashSet<string> Keywords => m_keywords ?? GetKeywords();
|
||||
private static HashSet<string> m_keywords;
|
||||
|
||||
private static HashSet<string> GetKeywords()
|
||||
{
|
||||
if (CSharpLexer.validKeywordMatcher.keywordCache == null)
|
||||
{
|
||||
return new HashSet<string>();
|
||||
}
|
||||
|
||||
HashSet<string> set = new HashSet<string>();
|
||||
|
||||
foreach (string keyword in CSharpLexer.validKeywordMatcher.keywordCache)
|
||||
{
|
||||
set.Add(keyword);
|
||||
}
|
||||
|
||||
return m_keywords = set;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user