Finished scene explorer, lots of cleanups. Inspector and Search left now.

This commit is contained in:
sinaioutlander
2020-10-28 06:39:26 +11:00
parent 7328610252
commit ff684d4d4b
64 changed files with 2376 additions and 1624 deletions

View 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
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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';
}
}

View 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);
}
}

View 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();
}
}
}

View 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();
}
}
}
}

View 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;
}
}
}

View 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 == '.';
}
}

View 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;
}
}
}

View 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();
}
}
}

View 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);
}
}
}
}

View 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));");
//}
}
}

View 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;
}
}
}