mirror of
https://github.com/sinai-dev/UnityExplorer.git
synced 2025-06-16 06:08:16 +08:00
developed new C# console
Derived heavily from notepad++ and Unity In-Game Code Editor, work will be done to refactor and strip down the code, most of it is unnecessary for our needs anyway. Temporary credit to IGCE for most of it.
This commit is contained in:
parent
0d4b4dc826
commit
648ac941df
@ -256,6 +256,21 @@
|
||||
<Compile Include="UI\Main\MainMenu.cs" />
|
||||
<Compile Include="UI\Main\Pages\BaseMenuPage.cs" />
|
||||
<Compile Include="UI\Main\Pages\ConsolePage.cs" />
|
||||
<Compile Include="UI\Main\Pages\Console\Editor\AutoIndent.cs" />
|
||||
<Compile Include="UI\Main\Pages\Console\Editor\CodeEditor.cs" />
|
||||
<Compile Include="UI\Main\Pages\Console\Editor\CodeTheme.cs" />
|
||||
<Compile Include="UI\Main\Pages\Console\Editor\InputTheme.cs" />
|
||||
<Compile Include="UI\Main\Pages\Console\Editor\Lexer\CommentGroupMatch.cs" />
|
||||
<Compile Include="UI\Main\Pages\Console\Editor\Lexer\ILexer.cs" />
|
||||
<Compile Include="UI\Main\Pages\Console\Editor\Lexer\InputStringLexer.cs" />
|
||||
<Compile Include="UI\Main\Pages\Console\Editor\Lexer\KeywordGroupMatch.cs" />
|
||||
<Compile Include="UI\Main\Pages\Console\Editor\Lexer\LiteralGroupMatch.cs" />
|
||||
<Compile Include="UI\Main\Pages\Console\Editor\Lexer\MatchLexer.cs" />
|
||||
<Compile Include="UI\Main\Pages\Console\Editor\Lexer\NumberGroupMatch.cs" />
|
||||
<Compile Include="UI\Main\Pages\Console\Editor\Lexer\SymbolGroupMatch.cs" />
|
||||
<Compile Include="UI\Main\Pages\Console\ScriptEvaluator\AutoComplete.cs" />
|
||||
<Compile Include="UI\Main\Pages\Console\ScriptEvaluator\ScriptEvaluator.cs" />
|
||||
<Compile Include="UI\Main\Pages\Console\ScriptEvaluator\ScriptInteraction.cs" />
|
||||
<Compile Include="UI\Main\Pages\HomePage.cs" />
|
||||
<Compile Include="UI\Main\Pages\OptionsPage.cs" />
|
||||
<Compile Include="UI\Main\Pages\SearchPage.cs" />
|
||||
|
@ -97,10 +97,6 @@ namespace ExplorerBeta
|
||||
m_doneUIInit = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UIManager.Update();
|
||||
}
|
||||
|
||||
if (InputManager.GetKeyDown(ModConfig.Instance.Main_Menu_Toggle))
|
||||
{
|
||||
|
@ -94,14 +94,6 @@ namespace ExplorerBeta.UI.Main
|
||||
tmpInput.verticalScrollbar = scroller;
|
||||
|
||||
m_textInput = input.GetComponent<TMP_InputField>();
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
Log("hello " + i);
|
||||
}
|
||||
|
||||
Log("hello", Color.red);
|
||||
Log("hello", Color.yellow);
|
||||
}
|
||||
|
||||
public static void Log(string message)
|
||||
|
@ -49,6 +49,7 @@ namespace ExplorerBeta.UI.Main
|
||||
foreach (var page in Pages)
|
||||
{
|
||||
page.Init();
|
||||
page.Content?.SetActive(false);
|
||||
}
|
||||
|
||||
SetPage(Pages[0]);
|
||||
@ -56,7 +57,7 @@ namespace ExplorerBeta.UI.Main
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// todo
|
||||
m_activePage?.Update();
|
||||
}
|
||||
|
||||
// todo
|
||||
|
133
src/UI/Main/Pages/Console/Editor/AutoIndent.cs
Normal file
133
src/UI/Main/Pages/Console/Editor/AutoIndent.cs
Normal file
@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Explorer.UI.Main.Pages.Console
|
||||
{
|
||||
public class AutoIndent
|
||||
{
|
||||
// Enum
|
||||
public enum IndentMode
|
||||
{
|
||||
None,
|
||||
AutoTab,
|
||||
AutoTabContextual,
|
||||
}
|
||||
|
||||
// Private
|
||||
private static readonly StringBuilder indentBuilder = new StringBuilder();
|
||||
|
||||
private static string indentDecreaseString = null;
|
||||
|
||||
// Public
|
||||
public static IndentMode autoIndentMode = IndentMode.AutoTabContextual;
|
||||
|
||||
/// <summary>
|
||||
/// Should auto indent be used for this language.
|
||||
/// </summary>
|
||||
public static bool allowAutoIndent = true;
|
||||
/// <summary>
|
||||
/// The character that causes the indent level to increase.
|
||||
/// </summary>
|
||||
public static char indentIncreaseCharacter = '{';
|
||||
/// <summary>
|
||||
/// The character that causes the indent level to decrease.
|
||||
/// </summary>
|
||||
public static char indentDecreaseCharacter = '}';
|
||||
|
||||
// Properties
|
||||
/// <summary>
|
||||
/// Get the string representation of the indent character.
|
||||
/// </summary>
|
||||
public static string IndentDecreaseString
|
||||
{
|
||||
get
|
||||
{
|
||||
if (indentDecreaseString == null)
|
||||
{
|
||||
indentDecreaseString = new string(indentDecreaseCharacter, 1);
|
||||
}
|
||||
return indentDecreaseString;
|
||||
}
|
||||
}
|
||||
|
||||
// Methods
|
||||
public static string GetAutoIndentedFormattedString(string indentSection, int currentIndent, out int caretPosition)
|
||||
{
|
||||
// Add indent level
|
||||
int indent = currentIndent + 1;
|
||||
|
||||
// Append characters
|
||||
for (int i = 0; i < indentSection.Length; i++)
|
||||
{
|
||||
if (indentSection[i] == '\n')
|
||||
{
|
||||
indentBuilder.Append('\n');
|
||||
AppendIndentString(indent);
|
||||
}
|
||||
else if (indentSection[i] == '\t')
|
||||
{
|
||||
// We will add tabs manually
|
||||
continue;
|
||||
}
|
||||
else if (indentSection[i] == indentIncreaseCharacter)
|
||||
{
|
||||
indentBuilder.Append(indentIncreaseCharacter);
|
||||
indent++;
|
||||
}
|
||||
else if (indentSection[i] == indentDecreaseCharacter)
|
||||
{
|
||||
indentBuilder.Append(indentDecreaseCharacter);
|
||||
indent--;
|
||||
}
|
||||
else
|
||||
{
|
||||
indentBuilder.Append(indentSection[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Build the string
|
||||
string formattedSection = indentBuilder.ToString();
|
||||
indentBuilder.Length = 0;
|
||||
|
||||
// Default caret position
|
||||
caretPosition = formattedSection.Length - 1;
|
||||
|
||||
// Find the caret position
|
||||
for (int i = formattedSection.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (formattedSection[i] == '\n')
|
||||
continue;
|
||||
|
||||
caretPosition = i;
|
||||
break;
|
||||
}
|
||||
|
||||
return formattedSection;
|
||||
}
|
||||
|
||||
public static int GetAutoIndentLevel(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;
|
||||
}
|
||||
|
||||
private static void AppendIndentString(int amount)
|
||||
{
|
||||
for (int i = 0; i < amount; i++)
|
||||
indentBuilder.Append("\t");
|
||||
}
|
||||
}
|
||||
}
|
552
src/UI/Main/Pages/Console/Editor/CodeEditor.cs
Normal file
552
src/UI/Main/Pages/Console/Editor/CodeEditor.cs
Normal file
@ -0,0 +1,552 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
using ExplorerBeta.Input;
|
||||
using Explorer.UI.Main.Pages.Console.Lexer;
|
||||
using ExplorerBeta;
|
||||
|
||||
namespace Explorer.UI.Main.Pages.Console
|
||||
{
|
||||
public class CodeEditor
|
||||
{
|
||||
public CodeEditor(TMP_InputField inputField, TextMeshProUGUI inputText, TextMeshProUGUI inputHighlightText, TextMeshProUGUI lineText,
|
||||
Image background, Image lineHighlight, Image lineNumberBackground, Image scrollbar)
|
||||
{
|
||||
this.InputField = inputField;
|
||||
this.inputText = inputText;
|
||||
this.inputHighlightText = inputHighlightText;
|
||||
this.lineText = lineText;
|
||||
this.background = background;
|
||||
this.lineHighlight = lineHighlight;
|
||||
this.lineNumberBackground = lineNumberBackground;
|
||||
this.scrollbar = scrollbar;
|
||||
|
||||
var highlightTextRect = inputHighlightText.GetComponent<RectTransform>();
|
||||
highlightTextRect.anchorMin = Vector2.zero;
|
||||
highlightTextRect.anchorMax = Vector2.one;
|
||||
highlightTextRect.offsetMin = Vector2.zero;
|
||||
highlightTextRect.offsetMax = Vector2.zero;
|
||||
|
||||
if (!AllReferencesAssigned())
|
||||
{
|
||||
throw new Exception("CodeEditor: Components are missing!");
|
||||
}
|
||||
|
||||
this.inputTextTransform = inputText.GetComponent<RectTransform>();
|
||||
this.lineHighlightTransform = lineHighlight.GetComponent<RectTransform>();
|
||||
|
||||
ApplyTheme();
|
||||
ApplyLanguage();
|
||||
|
||||
// subscribe to text input changing
|
||||
InputField.onValueChanged.AddListener(new Action<string>((string s) => { Refresh(); }));
|
||||
}
|
||||
|
||||
private static readonly KeyCode[] lineChangeKeys =
|
||||
{
|
||||
KeyCode.Return, KeyCode.Backspace, KeyCode.UpArrow,
|
||||
KeyCode.DownArrow, KeyCode.LeftArrow, KeyCode.RightArrow
|
||||
};
|
||||
|
||||
private static readonly StringBuilder highlightedBuilder = new StringBuilder(4096);
|
||||
private static readonly StringBuilder lineBuilder = new StringBuilder();
|
||||
|
||||
private readonly InputStringLexer lexer = new InputStringLexer();
|
||||
private readonly RectTransform inputTextTransform;
|
||||
private readonly RectTransform lineHighlightTransform;
|
||||
private string lastText;
|
||||
private bool lineHighlightLocked;
|
||||
|
||||
public readonly TMP_InputField InputField;
|
||||
private 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 lineNumbers = true;
|
||||
private int lineNumbersSize = 20;
|
||||
|
||||
public int LineCount { get; private set; } = 0;
|
||||
public int CurrentLine { get; private set; } = 0;
|
||||
public int CurrentColumn { get; private set; } = 0;
|
||||
public int CurrentIndent { get; private set; } = 0;
|
||||
|
||||
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 string HighlightedText => inputHighlightText.text;
|
||||
|
||||
public bool LineNumbers
|
||||
{
|
||||
get { return lineNumbers; }
|
||||
set
|
||||
{
|
||||
lineNumbers = value;
|
||||
|
||||
//RectTransform inputFieldTransform = InputField.transform as RectTransform;
|
||||
//RectTransform lineNumberBackgroudTransform = lineNumberBackground.transform as RectTransform;
|
||||
|
||||
//// Check for line numbers
|
||||
//if (lineNumbers == true)
|
||||
//{
|
||||
// // Enable line numbers
|
||||
// lineNumberBackground.gameObject.SetActive(true);
|
||||
// lineText.gameObject.SetActive(true);
|
||||
|
||||
// // Set left value
|
||||
// inputFieldTransform.offsetMin = new Vector2(lineNumbersSize, inputFieldTransform.offsetMin.y);
|
||||
// lineNumberBackgroudTransform.sizeDelta = new Vector2(lineNumbersSize + 15, lineNumberBackgroudTransform.sizeDelta.y);
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// // Disable line numbers
|
||||
// lineNumberBackground.gameObject.SetActive(false);
|
||||
// lineText.gameObject.SetActive(false);
|
||||
|
||||
// // Set left value
|
||||
// inputFieldTransform.offsetMin = new Vector2(0, inputFieldTransform.offsetMin.y);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
// todo maybe not needed
|
||||
public int LineNumbersSize
|
||||
{
|
||||
get { return lineNumbersSize; }
|
||||
set
|
||||
{
|
||||
lineNumbersSize = value;
|
||||
|
||||
// Update the line numbers
|
||||
LineNumbers = lineNumbers;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Auto indent
|
||||
if (AutoIndent.autoIndentMode != AutoIndent.IndentMode.None)
|
||||
{
|
||||
// Check for new line
|
||||
if (InputManager.GetKeyDown(KeyCode.Return))
|
||||
{
|
||||
AutoIndentCaret();
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
UpdateCurrentLineHighlight();
|
||||
}
|
||||
}
|
||||
|
||||
public void Refresh(bool forceUpdate = false)
|
||||
{
|
||||
// Trigger a content change event
|
||||
DisplayedContentChanged(InputField.text, forceUpdate);
|
||||
}
|
||||
|
||||
public void SetLineHighlight(int lineNumber, bool lockLineHighlight)
|
||||
{
|
||||
// Check if code editor is not active
|
||||
if (lineNumber < 1 || lineNumber > LineCount)
|
||||
return;
|
||||
|
||||
//int lineOffset = 0;
|
||||
//int lineIndex = lineNumber - 1;
|
||||
|
||||
// Highlight the current line
|
||||
lineHighlightTransform.anchoredPosition = new Vector2(5,
|
||||
(inputText.textInfo.lineInfo[inputText.textInfo.characterInfo[0].lineNumber].lineHeight *
|
||||
-(lineNumber - 1)) - 4f +
|
||||
inputTextTransform.anchoredPosition.y);
|
||||
|
||||
// Lock the line highlight so it cannot be moved
|
||||
if (lockLineHighlight == true)
|
||||
LockLineHighlight();
|
||||
else
|
||||
UnlockLineHighlight();
|
||||
}
|
||||
|
||||
public void LockLineHighlight()
|
||||
{
|
||||
lineHighlightLocked = true;
|
||||
}
|
||||
|
||||
public void UnlockLineHighlight()
|
||||
{
|
||||
lineHighlightLocked = false;
|
||||
}
|
||||
|
||||
private void DisplayedContentChanged(string newText, bool forceUpdate)
|
||||
{
|
||||
// Update caret position
|
||||
UpdateCurrentLineColumnIndent();
|
||||
|
||||
// Check for change
|
||||
if ((!forceUpdate && lastText == newText) || string.IsNullOrEmpty(newText))
|
||||
{
|
||||
if (string.IsNullOrEmpty(newText))
|
||||
{
|
||||
inputHighlightText.text = string.Empty;
|
||||
}
|
||||
|
||||
// Its possible the text was cleared so we need to sync numbers and highlighter
|
||||
UpdateCurrentLineNumbers();
|
||||
UpdateCurrentLineHighlight();
|
||||
return;
|
||||
}
|
||||
|
||||
inputHighlightText.text = SyntaxHighlightContent(newText);
|
||||
|
||||
// Sync line numbers and update the line highlight
|
||||
UpdateCurrentLineNumbers();
|
||||
UpdateCurrentLineHighlight();
|
||||
|
||||
this.lastText = newText;
|
||||
}
|
||||
|
||||
private void UpdateCurrentLineNumbers()
|
||||
{
|
||||
// Get the line count
|
||||
int currentLineCount = inputText.textInfo.lineCount;
|
||||
|
||||
int currentLineNumber = 1;
|
||||
|
||||
// Check for a change in line
|
||||
if (currentLineCount != LineCount)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Update line numbers
|
||||
lineBuilder.Length = 0;
|
||||
|
||||
// Build line numbers string
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update displayed line numbers
|
||||
lineText.text = lineBuilder.ToString();
|
||||
LineCount = currentLineCount;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCurrentLineColumnIndent()
|
||||
{
|
||||
// Get the current line number
|
||||
int caret = InputField.caretPosition;
|
||||
|
||||
if (caret < 0 || caret >= inputText.textInfo.characterInfo.Count)
|
||||
{
|
||||
while (caret >= 0 && caret >= inputText.textInfo.characterInfo.Count)
|
||||
caret--;
|
||||
|
||||
if (caret < 0 || caret >= inputText.textInfo.characterInfo.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CurrentLine = inputText.textInfo.characterInfo[caret].lineNumber;
|
||||
|
||||
// Get the total character count
|
||||
int charCount = 0;
|
||||
for (int i = 0; i < CurrentLine; i++)
|
||||
charCount += inputText.textInfo.lineInfo[i].characterCount;
|
||||
|
||||
// Get the column position
|
||||
CurrentColumn = caret - charCount;
|
||||
|
||||
CurrentIndent = 0;
|
||||
|
||||
// Check for auto indent allowed
|
||||
if (AutoIndent.allowAutoIndent)
|
||||
{
|
||||
for (int i = 0; i < caret && i < InputField.text.Length; i++)
|
||||
{
|
||||
char character = InputField.text[i];
|
||||
|
||||
// Check for opening indents
|
||||
if (character == AutoIndent.indentIncreaseCharacter)
|
||||
CurrentIndent++;
|
||||
|
||||
// Check for closing indents
|
||||
if (character == AutoIndent.indentDecreaseCharacter)
|
||||
CurrentIndent--;
|
||||
}
|
||||
|
||||
// Dont allow negative indents
|
||||
if (CurrentIndent < 0)
|
||||
CurrentIndent = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCurrentLineHighlight()
|
||||
{
|
||||
if (lineHighlightLocked)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// unity 2018.2 and older may need lineOffset as 0? not sure
|
||||
//int lineOffset = 1;
|
||||
|
||||
int caret = InputField.caretPosition - 1;
|
||||
|
||||
var lineHeight = inputText.textInfo.lineInfo[inputText.textInfo.characterInfo[0].lineNumber].lineHeight;
|
||||
var lineNumber = inputText.textInfo.characterInfo[caret].lineNumber;
|
||||
var 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)
|
||||
{
|
||||
if (!InputTheme.allowSyntaxHighlighting)
|
||||
return inputText;
|
||||
|
||||
int offset = 0;
|
||||
|
||||
highlightedBuilder.Length = 0;
|
||||
|
||||
foreach (var match in lexer.LexInputString(inputText))
|
||||
{
|
||||
// Copy text before the match
|
||||
for (int i = offset; i < match.startIndex; i++)
|
||||
highlightedBuilder.Append(inputText[i]);
|
||||
|
||||
// Add the opening color tag
|
||||
highlightedBuilder.Append(match.htmlColor);
|
||||
|
||||
// Copy text inbetween the match boundaries
|
||||
for (int i = match.startIndex; i < match.endIndex; i++)
|
||||
highlightedBuilder.Append(inputText[i]);
|
||||
|
||||
// Add the closing color tag
|
||||
highlightedBuilder.Append(CLOSE_COLOR_TAG);
|
||||
|
||||
// Update offset
|
||||
offset = match.endIndex;
|
||||
}
|
||||
|
||||
// Copy remaining text
|
||||
for (int i = offset; i < inputText.Length; i++)
|
||||
highlightedBuilder.Append(inputText[i]);
|
||||
|
||||
// Convert to string
|
||||
inputText = highlightedBuilder.ToString();
|
||||
|
||||
return inputText;
|
||||
}
|
||||
|
||||
// todo param is probably pointless
|
||||
private void AutoIndentCaret()
|
||||
{
|
||||
if (CurrentIndent > 0)
|
||||
{
|
||||
var indent = GetAutoIndentTab(CurrentIndent);
|
||||
|
||||
if (indent.Length > 0)
|
||||
{
|
||||
var caretPos = InputField.caretPosition;
|
||||
|
||||
var indentMinusOne = indent.Substring(0, indent.Length - 1);
|
||||
|
||||
// get last index of {
|
||||
// check it on the next line if its not already
|
||||
var text = InputField.text;
|
||||
var sub = InputField.text.Substring(0, InputField.caretPosition);
|
||||
var lastIndex = sub.LastIndexOf("{");
|
||||
var offset = lastIndex - 1;
|
||||
if (offset >= 0 && text[offset] != '\n' && text[offset] != '\t')
|
||||
{
|
||||
var open = "\n" + indentMinusOne;
|
||||
|
||||
InputField.text = text.Insert(offset + 1, open);
|
||||
|
||||
caretPos += open.Length;
|
||||
}
|
||||
|
||||
// check if should add auto-close }
|
||||
int numOpen = 0;
|
||||
int numClose = 0;
|
||||
char prevChar = default;
|
||||
foreach (var _char in InputField.text)
|
||||
{
|
||||
if (_char == '{')
|
||||
{
|
||||
if (prevChar != default && (prevChar == '\\' || prevChar == '{'))
|
||||
{
|
||||
if (prevChar == '{')
|
||||
numOpen--;
|
||||
}
|
||||
else
|
||||
{
|
||||
numOpen++;
|
||||
}
|
||||
}
|
||||
else if (_char == '}')
|
||||
{
|
||||
if (prevChar != default && (prevChar == '\\' || prevChar == '}'))
|
||||
{
|
||||
if (prevChar == '}')
|
||||
numClose--;
|
||||
}
|
||||
else
|
||||
{
|
||||
numClose++;
|
||||
}
|
||||
}
|
||||
prevChar = _char;
|
||||
}
|
||||
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
|
||||
UpdateCurrentLineColumnIndent();
|
||||
|
||||
inputText.text = InputField.text;
|
||||
inputText.SetText(InputField.text, true);
|
||||
inputText.Rebuild(CanvasUpdate.Prelayout);
|
||||
InputField.ForceLabelUpdate();
|
||||
InputField.Rebuild(CanvasUpdate.Prelayout);
|
||||
Refresh(true);
|
||||
}
|
||||
|
||||
private string GetAutoIndentTab(int amount)
|
||||
{
|
||||
string tab = string.Empty;
|
||||
|
||||
for (int i = 0; i < amount; i++)
|
||||
tab += "\t";
|
||||
|
||||
return tab;
|
||||
}
|
||||
|
||||
private void ApplyTheme()
|
||||
{
|
||||
// Check for missing references
|
||||
if (!AllReferencesAssigned())
|
||||
throw new Exception("Cannot apply theme because one or more required component references are missing. ");
|
||||
|
||||
// Apply theme colors
|
||||
InputField.caretColor = InputTheme.caretColor;
|
||||
inputText.color = InputTheme.textColor;
|
||||
inputHighlightText.color = InputTheme.textColor;
|
||||
background.color = InputTheme.backgroundColor;
|
||||
lineHighlight.color = InputTheme.lineHighlightColor;
|
||||
lineNumberBackground.color = InputTheme.lineNumberBackgroundColor;
|
||||
lineText.color = InputTheme.lineNumberTextColor;
|
||||
scrollbar.color = InputTheme.scrollbarColor;
|
||||
}
|
||||
|
||||
private void ApplyLanguage()
|
||||
{
|
||||
lexer.UseMatchers(CodeTheme.DelimiterSymbols, CodeTheme.Matchers);
|
||||
}
|
||||
|
||||
private bool AllReferencesAssigned()
|
||||
{
|
||||
if (!InputField ||
|
||||
!inputText ||
|
||||
!inputHighlightText ||
|
||||
!lineText ||
|
||||
!background ||
|
||||
!lineHighlight ||
|
||||
!lineNumberBackground ||
|
||||
!scrollbar)
|
||||
{
|
||||
// One or more references are not assigned
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
164
src/UI/Main/Pages/Console/Editor/CodeTheme.cs
Normal file
164
src/UI/Main/Pages/Console/Editor/CodeTheme.cs
Normal file
@ -0,0 +1,164 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using Explorer.UI.Main.Pages.Console.Lexer;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Explorer.UI.Main.Pages.Console
|
||||
{
|
||||
public static class CodeTheme
|
||||
{
|
||||
internal static readonly StringBuilder sharedBuilder = new StringBuilder();
|
||||
|
||||
private static char[] delimiterSymbolCache = null;
|
||||
private static MatchLexer[] matchers = null;
|
||||
|
||||
public static string languageName = "C#";
|
||||
|
||||
public static string delimiterSymbols = "[ ] ( ) { } ; : , .";
|
||||
|
||||
public static KeywordGroupMatch[] keywordGroups = new KeywordGroupMatch[]
|
||||
{
|
||||
// VALID KEYWORDS
|
||||
|
||||
new KeywordGroupMatch()
|
||||
{
|
||||
highlightColor = new Color(0.33f, 0.61f, 0.83f, 1.0f),
|
||||
caseSensitive = true,
|
||||
keywords = @"add as ascending await base bool break by byte
|
||||
case catch char checked const continue decimal default descending do dynamic
|
||||
else enum equals false finally fixed float for foreach from global goto group
|
||||
if in int into is join let lock long new null object on orderby out params
|
||||
partial ref remove return sbyte sealed select short sizeof stackalloc string
|
||||
struct switch this throw true try typeof uint ulong unchecked unsafe ushort
|
||||
using value var void where while yield"
|
||||
},
|
||||
|
||||
// INVALID KEYWORDS (cannot use inside method scope)
|
||||
|
||||
new KeywordGroupMatch()
|
||||
{
|
||||
highlightColor = new Color(0.95f, 0.10f, 0.10f, 1.0f),
|
||||
caseSensitive = true,
|
||||
keywords = @"abstract async class delegate explicit extern get
|
||||
implicit interface internal namespace operator override private protected public
|
||||
readonly set static virtual volatile"
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A symbol group used to specify which symbols should be highlighted.
|
||||
/// </summary>
|
||||
public static SymbolGroupMatch symbolGroup = new SymbolGroupMatch
|
||||
{
|
||||
symbols = @"[ ] ( ) . ? : + - * / % & | ^ ~ = < > ++ -- && || << >> == != <= >=
|
||||
+= -= *= /= %= &= |= ^= <<= >>= -> ?? =>",
|
||||
highlightColor = new Color(0.58f, 0.47f, 0.37f, 1.0f),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A number group used to specify whether numbers should be highlighted.
|
||||
/// </summary>
|
||||
public static NumberGroupMatch numberGroup = new NumberGroupMatch
|
||||
{
|
||||
highlightNumbers = true,
|
||||
highlightColor = new Color(0.58f, 0.33f, 0.33f, 1.0f)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A comment group used to specify which strings open and close comments.
|
||||
/// </summary>
|
||||
public static CommentGroupMatch commentGroup = new CommentGroupMatch
|
||||
{
|
||||
blockCommentEnd = @"*/",
|
||||
blockCommentStart = @"/*",
|
||||
lineCommentStart = @"//",
|
||||
lineCommentHasPresedence = true,
|
||||
highlightColor = new Color(0.34f, 0.65f, 0.29f, 1.0f),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A literal group used to specify whether quote strings should be highlighted.
|
||||
/// </summary>
|
||||
public static LiteralGroupMatch literalGroup = new LiteralGroupMatch
|
||||
{
|
||||
highlightLiterals = true,
|
||||
highlightColor = new Color(0.79f, 0.52f, 0.32f, 1.0f)
|
||||
};
|
||||
|
||||
///// <summary>
|
||||
///// Options group for all auto indent related settings.
|
||||
///// </summary>
|
||||
//public static AutoIndent autoIndent;
|
||||
|
||||
// Properties
|
||||
internal static char[] DelimiterSymbols
|
||||
{
|
||||
get
|
||||
{
|
||||
if (delimiterSymbolCache == null)
|
||||
{
|
||||
// Split by space
|
||||
string[] symbols = delimiterSymbols.Split(' ');
|
||||
|
||||
int count = 0;
|
||||
|
||||
// Count the number of valid symbols
|
||||
for (int i = 0; i < symbols.Length; i++)
|
||||
if (symbols[i].Length == 1)
|
||||
count++;
|
||||
|
||||
// Allocate array
|
||||
delimiterSymbolCache = new char[count];
|
||||
|
||||
// Copy symbols
|
||||
for (int i = 0, index = 0; i < symbols.Length; i++)
|
||||
{
|
||||
// Require only 1 character
|
||||
if (symbols[i].Length == 1)
|
||||
{
|
||||
// Get the first character for the string
|
||||
delimiterSymbolCache[index] = symbols[i][0];
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return delimiterSymbolCache;
|
||||
}
|
||||
}
|
||||
|
||||
internal static MatchLexer[] Matchers
|
||||
{
|
||||
get
|
||||
{
|
||||
if (matchers == null)
|
||||
{
|
||||
List<MatchLexer> matcherList = new List<MatchLexer>
|
||||
{
|
||||
commentGroup,
|
||||
symbolGroup,
|
||||
numberGroup,
|
||||
literalGroup
|
||||
};
|
||||
matcherList.AddRange(keywordGroups);
|
||||
|
||||
matchers = matcherList.ToArray();
|
||||
}
|
||||
return matchers;
|
||||
}
|
||||
}
|
||||
|
||||
// Methods
|
||||
internal static void Invalidate()
|
||||
{
|
||||
foreach (KeywordGroupMatch group in keywordGroups)
|
||||
group.Invalidate();
|
||||
|
||||
symbolGroup.Invalidate();
|
||||
commentGroup.Invalidate();
|
||||
numberGroup.Invalidate();
|
||||
literalGroup.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
21
src/UI/Main/Pages/Console/Editor/InputTheme.cs
Normal file
21
src/UI/Main/Pages/Console/Editor/InputTheme.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer.UI.Main.Pages.Console
|
||||
{
|
||||
public static class InputTheme
|
||||
{
|
||||
public static bool allowSyntaxHighlighting = true;
|
||||
|
||||
public static Color caretColor = new Color32(255, 255, 255, 255);
|
||||
public static Color textColor = new Color32(255, 255, 255, 255);
|
||||
public static Color backgroundColor = new Color32(37, 37, 37, 255);
|
||||
public static Color lineHighlightColor = new Color32(50, 50, 50, 255);
|
||||
public static Color lineNumberBackgroundColor = new Color32(25, 25, 25, 255);
|
||||
public static Color lineNumberTextColor = new Color32(180, 180, 180, 255);
|
||||
public static Color scrollbarColor = new Color32(45, 50, 50, 255);
|
||||
}
|
||||
}
|
221
src/UI/Main/Pages/Console/Editor/Lexer/CommentGroupMatch.cs
Normal file
221
src/UI/Main/Pages/Console/Editor/Lexer/CommentGroupMatch.cs
Normal file
@ -0,0 +1,221 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Explorer.Unstrip.ColorUtility;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer.UI.Main.Pages.Console.Lexer
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to match line and block comments.
|
||||
/// </summary>
|
||||
public sealed class CommentGroupMatch : MatchLexer
|
||||
{
|
||||
[NonSerialized]
|
||||
private string htmlColor = null;
|
||||
|
||||
// Public
|
||||
/// <summary>
|
||||
/// The string that denotes the start of a line comment.
|
||||
/// Leave this value empty if line comments should not be highlighted.
|
||||
/// </summary>
|
||||
public string lineCommentStart;
|
||||
/// <summary>
|
||||
/// The string that denotes the start of a block comment.
|
||||
/// Leave this value empty if block comments should not be highlighted.
|
||||
/// </summary>
|
||||
public string blockCommentStart;
|
||||
/// <summary>
|
||||
/// The string that denotes the end of a block comment.
|
||||
/// </summary>
|
||||
public string blockCommentEnd;
|
||||
/// <summary>
|
||||
/// The color that comments will be highlighted with.
|
||||
/// </summary>
|
||||
public Color highlightColor = Color.black;
|
||||
|
||||
public bool lineCommentHasPresedence = true;
|
||||
|
||||
// Properties
|
||||
/// <summary>
|
||||
/// Retrusn a value indicating whether any comment highlighting is enabled.
|
||||
/// A valid line or block comment start string must be specified in order for comment highlighting to be enabled.
|
||||
/// </summary>
|
||||
public bool HasCommentHighlighting
|
||||
{
|
||||
get
|
||||
{
|
||||
return string.IsNullOrEmpty(lineCommentStart) == false ||
|
||||
string.IsNullOrEmpty(blockCommentStart) == false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the html tag color that comments will be highlighted with.
|
||||
/// </summary>
|
||||
public override string HTMLColor
|
||||
{
|
||||
get
|
||||
{
|
||||
// Build html color string
|
||||
if (htmlColor == null)
|
||||
htmlColor = "<#" + highlightColor.ToHex() + ">";
|
||||
|
||||
return htmlColor;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerable collection of characters from this group that can act as delimiter symbols when they appear after a keyword.
|
||||
/// </summary>
|
||||
public override IEnumerable<char> SpecialStartCharacters
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(lineCommentStart) == false)
|
||||
yield return lineCommentStart[0];
|
||||
|
||||
if (string.IsNullOrEmpty(blockCommentEnd) == false)
|
||||
yield return blockCommentEnd[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerable collection of characters from this group that can act as delimiter symbols when they appear before a keyword.
|
||||
/// </summary>
|
||||
public override IEnumerable<char> SpecialEndCharacters
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(blockCommentEnd) == false)
|
||||
yield return blockCommentEnd[blockCommentEnd.Length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Methods
|
||||
/// <summary>
|
||||
/// Causes the cached values to be reloaded.
|
||||
/// Useful for editor visualisation.
|
||||
/// </summary>
|
||||
public override void Invalidate()
|
||||
{
|
||||
this.htmlColor = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the lexer input contains a valid comment format as the next character sequence.
|
||||
/// </summary>
|
||||
/// <param name="lexer">The input lexer</param>
|
||||
/// <returns>True if a comment was found or false if not</returns>
|
||||
public override bool IsImplicitMatch(ILexer lexer)
|
||||
{
|
||||
if (lineCommentHasPresedence == true)
|
||||
{
|
||||
// Parse line comments then block comments
|
||||
if (IsLineCommentMatch(lexer) == true ||
|
||||
IsBlockCommentMatch(lexer) == true)
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Parse block comments then line coments
|
||||
if (IsBlockCommentMatch(lexer) == true ||
|
||||
IsLineCommentMatch(lexer) == true)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Not a comment
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsLineCommentMatch(ILexer lexer)
|
||||
{
|
||||
// Check for line comment
|
||||
if (string.IsNullOrEmpty(lineCommentStart) == false)
|
||||
{
|
||||
lexer.Rollback();
|
||||
|
||||
bool match = true;
|
||||
|
||||
for (int i = 0; i < lineCommentStart.Length; i++)
|
||||
{
|
||||
if (lineCommentStart[i] != lexer.ReadNext())
|
||||
{
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for valid match
|
||||
if (match == true)
|
||||
{
|
||||
// Read until end
|
||||
while (IsEndLineOrEndFile(lexer, lexer.ReadNext()) == false) ;
|
||||
|
||||
// Matched a single line comment
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsBlockCommentMatch(ILexer lexer)
|
||||
{
|
||||
// Check for block comment
|
||||
if (string.IsNullOrEmpty(blockCommentStart) == false)
|
||||
{
|
||||
lexer.Rollback();
|
||||
|
||||
bool match = true;
|
||||
|
||||
for (int i = 0; i < blockCommentStart.Length; i++)
|
||||
{
|
||||
if (blockCommentStart[i] != lexer.ReadNext())
|
||||
{
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for valid match
|
||||
if (match == true)
|
||||
{
|
||||
// Read until end or closing block
|
||||
while (IsEndLineOrString(lexer, blockCommentEnd) == false) ;
|
||||
|
||||
// Matched a multi-line block commend
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsEndLineOrEndFile(ILexer lexer, char character)
|
||||
{
|
||||
if (lexer.EndOfStream == true ||
|
||||
character == '\n' ||
|
||||
character == '\r')
|
||||
{
|
||||
// Line end or file end
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsEndLineOrString(ILexer lexer, string endString)
|
||||
{
|
||||
for (int i = 0; i < endString.Length; i++)
|
||||
{
|
||||
// Check for end of stream
|
||||
if (lexer.EndOfStream == true)
|
||||
return true;
|
||||
|
||||
// Check for matching end string
|
||||
if (endString[i] != lexer.ReadNext())
|
||||
return false;
|
||||
}
|
||||
|
||||
// We matched the end string
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
67
src/UI/Main/Pages/Console/Editor/Lexer/ILexer.cs
Normal file
67
src/UI/Main/Pages/Console/Editor/Lexer/ILexer.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Explorer.UI.Main.Pages.Console.Lexer
|
||||
{
|
||||
// Types
|
||||
/// <summary>
|
||||
/// Represents a keyword position where a special character may appear.
|
||||
/// </summary>
|
||||
public enum SpecialCharacterPosition
|
||||
{
|
||||
/// <summary>
|
||||
/// The special character may appear before a keyword.
|
||||
/// </summary>
|
||||
Start,
|
||||
/// <summary>
|
||||
/// The special character may appear after a keyword.
|
||||
/// </summary>
|
||||
End,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Represents a streamable lexer input which can be examined by matchers.
|
||||
/// </summary>
|
||||
public interface ILexer
|
||||
{
|
||||
// Properties
|
||||
/// <summary>
|
||||
/// Returns true if there are no more characters to read.
|
||||
/// </summary>
|
||||
bool EndOfStream { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the previously read character or '\0' if there is no previous character.
|
||||
/// </summary>
|
||||
char Previous { get; }
|
||||
|
||||
// Methods
|
||||
/// <summary>
|
||||
/// Attempt to read the next character.
|
||||
/// </summary>
|
||||
/// <returns>The next character in the stream or '\0' if the end of stream is reached</returns>
|
||||
char ReadNext();
|
||||
|
||||
/// <summary>
|
||||
/// Causes the lexer to return its state to a previously commited state.
|
||||
/// </summary>
|
||||
/// <param name="amount">Use -1 to return to the last commited state or a positive number to represent the number of characters to rollback</param>
|
||||
void Rollback(int amount = -1);
|
||||
|
||||
/// <summary>
|
||||
/// Causes all read characters to be commited meaning that rollback will return to this lexer state.
|
||||
/// </summary>
|
||||
void Commit();
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified character is considered a special symbol by the lexer meaning that it is able to act as a delimiter.
|
||||
/// </summary>
|
||||
/// <param name="character">The character to check</param>
|
||||
/// <param name="position">The character position to check. This determines whether the character may appear at the start or end of a keyword</param>
|
||||
/// <returns>True if the character is a valid delimiter or false if not</returns>
|
||||
bool IsSpecialSymbol(char character, SpecialCharacterPosition position = SpecialCharacterPosition.Start);
|
||||
}
|
||||
}
|
207
src/UI/Main/Pages/Console/Editor/Lexer/InputStringLexer.cs
Normal file
207
src/UI/Main/Pages/Console/Editor/Lexer/InputStringLexer.cs
Normal file
@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer.UI.Main.Pages.Console.Lexer
|
||||
{
|
||||
internal struct InputStringMatchInfo
|
||||
{
|
||||
// Public
|
||||
public int startIndex;
|
||||
public int endIndex;
|
||||
public string htmlColor;
|
||||
}
|
||||
|
||||
internal class InputStringLexer : ILexer
|
||||
{
|
||||
// Private
|
||||
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 char current = ' ';
|
||||
private char previous = ' ';
|
||||
private int currentIndex = 0;
|
||||
private int currentLookaheadIndex = 0;
|
||||
|
||||
// Properties
|
||||
public bool EndOfStream
|
||||
{
|
||||
get { return currentLookaheadIndex >= inputString.Length; }
|
||||
}
|
||||
|
||||
public char Previous
|
||||
{
|
||||
get { return previous; }
|
||||
}
|
||||
|
||||
// Methods
|
||||
public void UseMatchers(char[] delimiters, MatchLexer[] matchers)
|
||||
{
|
||||
// Store matchers
|
||||
this.matchers = matchers;
|
||||
|
||||
// Clear old symbols
|
||||
specialStartSymbols.Clear();
|
||||
specialEndSymbols.Clear();
|
||||
|
||||
// Check for any delimiter characters
|
||||
if (delimiters != null)
|
||||
{
|
||||
// Add delimiters
|
||||
foreach (char character in delimiters)
|
||||
{
|
||||
// Add to start
|
||||
if (specialStartSymbols.Contains(character) == false)
|
||||
specialStartSymbols.Add(character);
|
||||
|
||||
// Add to end
|
||||
if (specialEndSymbols.Contains(character) == false)
|
||||
specialEndSymbols.Add(character);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for any matchers
|
||||
if (matchers != null)
|
||||
{
|
||||
// Add all special symbols which can act as a delimiter
|
||||
foreach (MatchLexer lexer in matchers)
|
||||
{
|
||||
foreach (char special in lexer.SpecialStartCharacters)
|
||||
if (specialStartSymbols.Contains(special) == false)
|
||||
specialStartSymbols.Add(special);
|
||||
|
||||
foreach (char special in lexer.SpecialEndCharacters)
|
||||
if (specialEndSymbols.Contains(special) == false)
|
||||
specialEndSymbols.Add(special);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<InputStringMatchInfo> LexInputString(string input)
|
||||
{
|
||||
if (input == null || matchers == null || matchers.Length == 0)
|
||||
yield break;
|
||||
|
||||
// Store the input string
|
||||
this.inputString = input;
|
||||
this.current = ' ';
|
||||
this.previous = ' ';
|
||||
this.currentIndex = 0;
|
||||
this.currentLookaheadIndex = 0;
|
||||
|
||||
// Process the input string
|
||||
while (EndOfStream == false)
|
||||
{
|
||||
bool didMatchLexer = false;
|
||||
|
||||
// Read any white spaces
|
||||
ReadWhiteSpace();
|
||||
|
||||
// Process each matcher
|
||||
foreach (MatchLexer matcher in matchers)
|
||||
{
|
||||
// Get the current index
|
||||
int startIndex = currentIndex;
|
||||
|
||||
// Try to match
|
||||
bool isMatched = matcher.IsMatch(this);
|
||||
|
||||
if (isMatched == true)
|
||||
{
|
||||
// Get the end index of the match
|
||||
int endIndex = currentIndex;
|
||||
|
||||
// Set matched flag
|
||||
didMatchLexer = true;
|
||||
|
||||
// Register the match
|
||||
yield return new InputStringMatchInfo
|
||||
{
|
||||
startIndex = startIndex,
|
||||
endIndex = endIndex,
|
||||
htmlColor = matcher.HTMLColor,
|
||||
};
|
||||
|
||||
// Move to next character
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (didMatchLexer == false)
|
||||
{
|
||||
// Move to next
|
||||
ReadNext();
|
||||
Commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public char ReadNext()
|
||||
{
|
||||
// Check for end of stream
|
||||
if (EndOfStream == true)
|
||||
return '\0';
|
||||
|
||||
// Update previous character
|
||||
previous = current;
|
||||
|
||||
// Get the character
|
||||
current = inputString[currentLookaheadIndex];
|
||||
currentLookaheadIndex++;
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
public void Rollback(int amount = -1)
|
||||
{
|
||||
if (amount == -1)
|
||||
{
|
||||
// Revert to index
|
||||
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()
|
||||
{
|
||||
// Read until white space
|
||||
while (char.IsWhiteSpace(ReadNext()) == true)
|
||||
{
|
||||
// Consume the char
|
||||
Commit();
|
||||
}
|
||||
|
||||
// Return lexer state
|
||||
Rollback();
|
||||
}
|
||||
}
|
||||
}
|
208
src/UI/Main/Pages/Console/Editor/Lexer/KeywordGroupMatch.cs
Normal file
208
src/UI/Main/Pages/Console/Editor/Lexer/KeywordGroupMatch.cs
Normal file
@ -0,0 +1,208 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Explorer.Unstrip.ColorUtility;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer.UI.Main.Pages.Console.Lexer
|
||||
{
|
||||
/// <summary>
|
||||
/// A matcher that checks for a number of predefined keywords in the lexer stream.
|
||||
/// </summary>
|
||||
public sealed class KeywordGroupMatch : MatchLexer
|
||||
{
|
||||
// Private
|
||||
private static readonly HashSet<string> shortlist = new HashSet<string>();
|
||||
private static readonly Stack<string> removeList = new Stack<string>();
|
||||
private string[] keywordCache = null;
|
||||
private string htmlColor = null;
|
||||
|
||||
// Public
|
||||
/// <summary>
|
||||
/// Used for editor gui only. Has no purpose other than to give the inspector foldout a nice name
|
||||
/// </summary>
|
||||
public string group = "Keyword Group"; // This value is not used - it just gives the inspector foldout a nice name
|
||||
/// <summary>
|
||||
/// A string containing one or more keywords separated by a space character that will be used by this matcher.
|
||||
/// </summary>
|
||||
public string keywords;
|
||||
/// <summary>
|
||||
/// The color that any matched keywords will be highlighted.
|
||||
/// </summary>
|
||||
public Color highlightColor = Color.black;
|
||||
/// <summary>
|
||||
/// Should keyword matching be case sensitive.
|
||||
/// </summary>
|
||||
public bool caseSensitive = true;
|
||||
|
||||
// Properties
|
||||
/// <summary>
|
||||
/// Get a value indicating whether keyword highlighting is enabled based upon the number of valid keywords found.
|
||||
/// </summary>
|
||||
public bool HasKeywordHighlighting
|
||||
{
|
||||
get
|
||||
{
|
||||
// Check for valid keyword
|
||||
if (string.IsNullOrEmpty(keywords) == false)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the html formatted color tag that any matched keywords will be highlighted with.
|
||||
/// </summary>
|
||||
public override string HTMLColor
|
||||
{
|
||||
get
|
||||
{
|
||||
// Get html color
|
||||
if (htmlColor == null)
|
||||
htmlColor = "<#" + highlightColor.ToHex() + ">";
|
||||
|
||||
return htmlColor;
|
||||
}
|
||||
}
|
||||
|
||||
// Methods
|
||||
/// <summary>
|
||||
/// Causes any cached data to be reloaded.
|
||||
/// </summary>
|
||||
public override void Invalidate()
|
||||
{
|
||||
this.htmlColor = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the specified lexer has a valid keyword at its current position.
|
||||
/// </summary>
|
||||
/// <param name="lexer">The input lexer to check</param>
|
||||
/// <returns>True if the stream has a keyword or false if not</returns>
|
||||
public override bool IsImplicitMatch(ILexer lexer)
|
||||
{
|
||||
// Make sure cache is built
|
||||
BuildKeywordCache();
|
||||
|
||||
// Require whitespace before character
|
||||
if (char.IsWhiteSpace(lexer.Previous) == false &&
|
||||
lexer.IsSpecialSymbol(lexer.Previous, SpecialCharacterPosition.End) == false)
|
||||
return false;
|
||||
|
||||
// Clear old data
|
||||
shortlist.Clear();
|
||||
|
||||
// Read the first character
|
||||
int currentIndex = 0;
|
||||
char currentChar = lexer.ReadNext();
|
||||
|
||||
// Add to shortlist
|
||||
for (int i = 0; i < keywordCache.Length; i++)
|
||||
if (CompareChar(keywordCache[i][0], currentChar) == true)
|
||||
shortlist.Add(keywordCache[i]);
|
||||
|
||||
// Check for no matches we can skip the heavy work quickly
|
||||
if (shortlist.Count == 0)
|
||||
return false;
|
||||
|
||||
do
|
||||
{
|
||||
// Check for end of stream
|
||||
if (lexer.EndOfStream == true)
|
||||
{
|
||||
RemoveLongStrings(currentIndex + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
// Read next character
|
||||
currentChar = lexer.ReadNext();
|
||||
currentIndex++;
|
||||
|
||||
// Check for end of word
|
||||
if (char.IsWhiteSpace(currentChar) == true ||
|
||||
lexer.IsSpecialSymbol(currentChar, SpecialCharacterPosition.Start) == true)
|
||||
{
|
||||
// Finalize any matching candidates and undo the reading of the space or special character
|
||||
RemoveLongStrings(currentIndex);
|
||||
lexer.Rollback(1);
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for shortlist match
|
||||
foreach (string keyword in shortlist)
|
||||
{
|
||||
if (currentIndex >= keyword.Length ||
|
||||
CompareChar(keyword[currentIndex], currentChar) == false)
|
||||
{
|
||||
removeList.Push(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from short list
|
||||
while (removeList.Count > 0)
|
||||
shortlist.Remove(removeList.Pop());
|
||||
}
|
||||
while (shortlist.Count > 0);
|
||||
|
||||
// Check for any words in the shortlist
|
||||
return shortlist.Count > 0;
|
||||
}
|
||||
|
||||
private void RemoveLongStrings(int length)
|
||||
{
|
||||
foreach (string keyword in shortlist)
|
||||
{
|
||||
if (keyword.Length > length)
|
||||
{
|
||||
removeList.Push(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from short list
|
||||
while (removeList.Count > 0)
|
||||
shortlist.Remove(removeList.Pop());
|
||||
}
|
||||
|
||||
private void BuildKeywordCache()
|
||||
{
|
||||
// Check if we need to build the cache
|
||||
if (keywordCache == null)
|
||||
{
|
||||
// Get keyowrds and insert them into a cache array for quick reference
|
||||
var kwSplit = keywords.Split(' ');
|
||||
|
||||
var list = new List<string>();
|
||||
foreach (var kw in kwSplit)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(kw) && kw.Length > 0)
|
||||
{
|
||||
list.Add(kw);
|
||||
}
|
||||
}
|
||||
keywordCache = list.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private bool CompareChar(char a, char b)
|
||||
{
|
||||
// Check for direct match
|
||||
if (a == b)
|
||||
return true;
|
||||
|
||||
// Check for case sensitive
|
||||
if (caseSensitive == false)
|
||||
{
|
||||
if (char.ToUpper(a, CultureInfo.CurrentCulture) ==
|
||||
char.ToUpper(b, CultureInfo.CurrentCulture))
|
||||
{
|
||||
// Check for match ignoring case
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
120
src/UI/Main/Pages/Console/Editor/Lexer/LiteralGroupMatch.cs
Normal file
120
src/UI/Main/Pages/Console/Editor/Lexer/LiteralGroupMatch.cs
Normal file
@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Explorer.Unstrip.ColorUtility;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer.UI.Main.Pages.Console.Lexer
|
||||
{
|
||||
/// <summary>
|
||||
/// A matcher that checks for quote strings in the lexer stream.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class LiteralGroupMatch : MatchLexer
|
||||
{
|
||||
// Private
|
||||
private string htmlColor = null;
|
||||
|
||||
// Public
|
||||
/// <summary>
|
||||
/// Should literal be highlighted.
|
||||
/// When true, any text surrounded by double quotes will be highlighted.
|
||||
/// </summary>
|
||||
public bool highlightLiterals = true;
|
||||
/// <summary>
|
||||
/// The color that any matched literals will be highlighted.
|
||||
/// </summary>
|
||||
public Color highlightColor = Color.black;
|
||||
|
||||
// Properties
|
||||
/// <summary>
|
||||
/// Get a value indicating whether literal highlighting is enabled.
|
||||
/// </summary>
|
||||
public bool HasLiteralHighlighting
|
||||
{
|
||||
get { return highlightLiterals; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the html formatted color tag that any matched literals will be highlighted with.
|
||||
/// </summary>
|
||||
public override string HTMLColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (htmlColor == null)
|
||||
htmlColor = "<#" + highlightColor.ToHex() + ">";
|
||||
|
||||
return htmlColor;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns special symbols that can act as delimiters when appearing before a word.
|
||||
/// In this case '"' will be returned.
|
||||
/// </summary>
|
||||
public override IEnumerable<char> SpecialStartCharacters
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return '"';
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns special symbols that can act as delimiters when appearing after a word.
|
||||
/// In this case '"' will be returned.
|
||||
/// </summary>
|
||||
public override IEnumerable<char> SpecialEndCharacters
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return '"';
|
||||
}
|
||||
}
|
||||
|
||||
// Methods
|
||||
/// <summary>
|
||||
/// Causes any cached data to be reloaded.
|
||||
/// </summary>
|
||||
public override void Invalidate()
|
||||
{
|
||||
this.htmlColor = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the specified lexer has a valid literal at its current position.
|
||||
/// </summary>
|
||||
/// <param name="lexer">The input lexer to check</param>
|
||||
/// <returns>True if the stream has a literal or false if not</returns>
|
||||
public override bool IsImplicitMatch(ILexer lexer)
|
||||
{
|
||||
// Skip highlighting
|
||||
if (highlightLiterals == false)
|
||||
return false;
|
||||
|
||||
// Check for quote
|
||||
if (lexer.ReadNext() == '"')
|
||||
{
|
||||
// Read all characters inside the quote
|
||||
while (IsClosingQuoteOrEndFile(lexer, lexer.ReadNext()) == false) ;
|
||||
|
||||
// Found a valid literal
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsClosingQuoteOrEndFile(ILexer lexer, char character)
|
||||
{
|
||||
if (lexer.EndOfStream == true ||
|
||||
character == '"')
|
||||
{
|
||||
// We have found the end of the file or quote
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
61
src/UI/Main/Pages/Console/Editor/Lexer/MatchLexer.cs
Normal file
61
src/UI/Main/Pages/Console/Editor/Lexer/MatchLexer.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer.UI.Main.Pages.Console.Lexer
|
||||
{
|
||||
public abstract class MatchLexer
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the html formatted color tag that any matched text will be highlighted with.
|
||||
/// </summary>
|
||||
public abstract string HTMLColor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get an enumerable collection of special characters that can act as delimiter symbols when they appear before a word.
|
||||
/// </summary>
|
||||
public virtual IEnumerable<char> SpecialStartCharacters { get { yield break; } }
|
||||
|
||||
/// <summary>
|
||||
/// Get an enumerable collection of special characters that can act as delimiter symbols when they appear after a word.
|
||||
/// </summary>
|
||||
public virtual IEnumerable<char> SpecialEndCharacters { get { yield break; } }
|
||||
|
||||
// Methods
|
||||
/// <summary>
|
||||
/// Checks the specified lexers current position for a certain sequence of characters as defined by the inheriting matcher.
|
||||
/// </summary>
|
||||
/// <param name="lexer"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool IsImplicitMatch(ILexer lexer);
|
||||
|
||||
/// <summary>
|
||||
/// Causes the matcher to invalidate any cached data forcing it to be regenerated or reloaded.
|
||||
/// </summary>
|
||||
public virtual void Invalidate() { }
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to check for a match in the specified lexer.
|
||||
/// </summary>
|
||||
/// <param name="lexer">The lexer that will be checked</param>
|
||||
/// <returns>True if a match was found or false if not</returns>
|
||||
public bool IsMatch(ILexer lexer)
|
||||
{
|
||||
// Check for implicit match
|
||||
bool match = IsImplicitMatch(lexer);
|
||||
|
||||
if (match == true)
|
||||
{
|
||||
// Consume read tokens
|
||||
lexer.Commit();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Revert lexer state
|
||||
lexer.Rollback();
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
}
|
||||
}
|
105
src/UI/Main/Pages/Console/Editor/Lexer/NumberGroupMatch.cs
Normal file
105
src/UI/Main/Pages/Console/Editor/Lexer/NumberGroupMatch.cs
Normal file
@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using Explorer.Unstrip.ColorUtility;
|
||||
|
||||
namespace Explorer.UI.Main.Pages.Console.Lexer
|
||||
{
|
||||
/// <summary>
|
||||
/// A matcher that checks for any numbers that appear in the lexer stream.
|
||||
/// </summary>
|
||||
public sealed class NumberGroupMatch : MatchLexer
|
||||
{
|
||||
// Private
|
||||
private string htmlColor = null;
|
||||
|
||||
// Public
|
||||
/// <summary>
|
||||
/// Should number highlighting be used.
|
||||
/// When false, numbers will appear in the default text color as defined by the current editor theme.
|
||||
/// </summary>
|
||||
public bool highlightNumbers = true;
|
||||
/// <summary>
|
||||
/// The color that any matched numbers will be highlighted.
|
||||
/// </summary>
|
||||
public Color highlightColor = Color.black;
|
||||
|
||||
// Properties
|
||||
/// <summary>
|
||||
/// Get a value indicating whether keyword highlighting is enabled.
|
||||
/// </summary>
|
||||
public bool HasNumberHighlighting
|
||||
{
|
||||
get { return highlightNumbers; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the html formatted color tag that any matched numbers will be highlighted with.
|
||||
/// </summary>
|
||||
public override string HTMLColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (htmlColor == null)
|
||||
htmlColor = "<#" + highlightColor.ToHex() + ">";
|
||||
|
||||
return htmlColor;
|
||||
}
|
||||
}
|
||||
|
||||
// Methods
|
||||
/// <summary>
|
||||
/// Causes any cached data to be reloaded.
|
||||
/// </summary>
|
||||
public override void Invalidate()
|
||||
{
|
||||
this.htmlColor = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the specified lexer has a valid number sequence at its current position.
|
||||
/// </summary>
|
||||
/// <param name="lexer">The input lexer to check</param>
|
||||
/// <returns>True if the stream has a number sequence or false if not</returns>
|
||||
public override bool IsImplicitMatch(ILexer lexer)
|
||||
{
|
||||
// Skip highlighting
|
||||
if (highlightNumbers == false)
|
||||
return false;
|
||||
|
||||
// Require whitespace or symbols before numbers
|
||||
if (char.IsWhiteSpace(lexer.Previous) == false &&
|
||||
lexer.IsSpecialSymbol(lexer.Previous, SpecialCharacterPosition.End) == false)
|
||||
{
|
||||
// There is some other character before the potential number
|
||||
return false;
|
||||
}
|
||||
|
||||
bool matchedNumber = false;
|
||||
|
||||
// Consume the number characters
|
||||
while (lexer.EndOfStream == false)
|
||||
{
|
||||
// Check for valid numerical character
|
||||
if (IsNumberOrDecimalPoint(lexer.ReadNext()) == true)
|
||||
{
|
||||
// We have found a number or decimal
|
||||
matchedNumber = true;
|
||||
lexer.Commit();
|
||||
}
|
||||
else
|
||||
{
|
||||
lexer.Rollback();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return matchedNumber;
|
||||
}
|
||||
|
||||
private bool IsNumberOrDecimalPoint(char character) => char.IsNumber(character) || character == '.';
|
||||
}
|
||||
|
||||
}
|
223
src/UI/Main/Pages/Console/Editor/Lexer/SymbolGroupMatch.cs
Normal file
223
src/UI/Main/Pages/Console/Editor/Lexer/SymbolGroupMatch.cs
Normal file
@ -0,0 +1,223 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Explorer.Unstrip.ColorUtility;
|
||||
using ExplorerBeta;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer.UI.Main.Pages.Console.Lexer
|
||||
{
|
||||
/// <summary>
|
||||
/// A matcher that checks for a number of predefined symbols in the lexer stream.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class SymbolGroupMatch : MatchLexer
|
||||
{
|
||||
// Private
|
||||
private static readonly List<string> shortlist = new List<string>();
|
||||
private static readonly Stack<string> removeList = new Stack<string>();
|
||||
[NonSerialized]
|
||||
private string[] symbolCache = null;
|
||||
[NonSerialized]
|
||||
private string htmlColor = null;
|
||||
|
||||
// Public
|
||||
/// <summary>
|
||||
/// A string containing one or more symbols separated by a space character that will be used by the matcher.
|
||||
/// Symbols can be one or more characters long and should not contain numbers or letters.
|
||||
/// </summary>
|
||||
public string symbols;
|
||||
/// <summary>
|
||||
/// The color that any matched symbols will be highlighted.
|
||||
/// </summary>
|
||||
public Color highlightColor = Color.black;
|
||||
|
||||
// Properties
|
||||
/// <summary>
|
||||
/// Get a value indicating whether symbol highlighting is enabled based upon the number of valid symbols found.
|
||||
/// </summary>
|
||||
public bool HasSymbolHighlighting
|
||||
{
|
||||
get { return symbols.Length > 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the html formatted color tag that any matched symbols will be highlighted with.
|
||||
/// </summary>
|
||||
public override string HTMLColor
|
||||
{
|
||||
get
|
||||
{
|
||||
// Get html color
|
||||
if (htmlColor == null)
|
||||
htmlColor = "<#" + highlightColor.ToHex() + ">";
|
||||
|
||||
return htmlColor;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns special symbols that can act as delimiters when appearing before a word.
|
||||
/// </summary>
|
||||
public override IEnumerable<char> SpecialStartCharacters
|
||||
{
|
||||
get
|
||||
{
|
||||
// Make sure cahce is created
|
||||
BuildSymbolCache();
|
||||
|
||||
// Get the first character of each symbol
|
||||
foreach (string symbol in symbolCache.Where(x => x.Length > 0))
|
||||
yield return symbol[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns special symbols that can act as delimiters when appearing after a word.
|
||||
/// In this case '"' will be returned.
|
||||
/// </summary>
|
||||
public override IEnumerable<char> SpecialEndCharacters
|
||||
{
|
||||
get
|
||||
{
|
||||
// Make sure cahce is created
|
||||
BuildSymbolCache();
|
||||
|
||||
// Get the first character of each symbol
|
||||
foreach (string symbol in symbolCache.Where(x => x.Length > 0))
|
||||
yield return symbol[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Methods
|
||||
/// <summary>
|
||||
/// Causes any cached data to be reloaded.
|
||||
/// </summary>
|
||||
public override void Invalidate()
|
||||
{
|
||||
this.htmlColor = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the specified lexer has a valid symbol at its current posiiton.
|
||||
/// </summary>
|
||||
/// <param name="lexer">The input lexer to check</param>
|
||||
/// <returns>True if the stream has a symbol or false if not</returns>
|
||||
public override bool IsImplicitMatch(ILexer lexer)
|
||||
{
|
||||
// Make sure cache is created
|
||||
BuildSymbolCache();
|
||||
|
||||
if (lexer == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Require whitespace, letter or digit before symbol
|
||||
if (char.IsWhiteSpace(lexer.Previous) == false &&
|
||||
char.IsLetter(lexer.Previous) == false &&
|
||||
char.IsDigit(lexer.Previous) == false &&
|
||||
lexer.IsSpecialSymbol(lexer.Previous, SpecialCharacterPosition.End) == false)
|
||||
return false;
|
||||
|
||||
// Clear old data
|
||||
shortlist.Clear();
|
||||
|
||||
// Read the first character
|
||||
int currentIndex = 0;
|
||||
char currentChar = lexer.ReadNext();
|
||||
|
||||
// Add to shortlist
|
||||
for (int i = symbolCache.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (symbolCache[i][0] == currentChar)
|
||||
shortlist.Add(symbolCache[i]);
|
||||
}
|
||||
|
||||
// No potential matches
|
||||
if (shortlist.Count == 0)
|
||||
return false;
|
||||
|
||||
do
|
||||
{
|
||||
// Check for end of stream
|
||||
if (lexer.EndOfStream == true)
|
||||
{
|
||||
RemoveLongStrings(currentIndex + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
// Read next character
|
||||
currentChar = lexer.ReadNext();
|
||||
currentIndex++;
|
||||
|
||||
if (char.IsWhiteSpace(currentChar) == true ||
|
||||
char.IsLetter(currentChar) == true ||
|
||||
char.IsDigit(currentChar) == true ||
|
||||
lexer.IsSpecialSymbol(currentChar, SpecialCharacterPosition.Start) == true)
|
||||
{
|
||||
RemoveLongStrings(currentIndex);
|
||||
lexer.Rollback(1);
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for shortlist match
|
||||
foreach (string symbol in shortlist)
|
||||
{
|
||||
if (currentIndex >= symbol.Length ||
|
||||
symbol[currentIndex] != currentChar)
|
||||
{
|
||||
removeList.Push(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from short list
|
||||
while (removeList.Count > 0)
|
||||
shortlist.Remove(removeList.Pop());
|
||||
}
|
||||
while (shortlist.Count > 0);
|
||||
|
||||
// Check for any words in the shortlist
|
||||
return shortlist.Count > 0;
|
||||
}
|
||||
|
||||
private void RemoveLongStrings(int length)
|
||||
{
|
||||
foreach (string keyword in shortlist)
|
||||
{
|
||||
if (keyword.Length > length)
|
||||
{
|
||||
removeList.Push(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from short list
|
||||
while (removeList.Count > 0)
|
||||
shortlist.Remove(removeList.Pop());
|
||||
}
|
||||
|
||||
private void BuildSymbolCache()
|
||||
{
|
||||
if (string.IsNullOrEmpty(symbols))
|
||||
{
|
||||
ExplorerCore.LogWarning("Symbol cache is null!");
|
||||
symbolCache = new string[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get symbols and insert them into a cache array for quick reference
|
||||
var symSplit = symbols.Split(' ');
|
||||
var list = new List<string>();
|
||||
foreach (var sym in symSplit)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(sym) && sym.Length > 0)
|
||||
{
|
||||
list.Add(sym);
|
||||
}
|
||||
}
|
||||
symbolCache = list.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
57
src/UI/Main/Pages/Console/ScriptEvaluator/AutoComplete.cs
Normal file
57
src/UI/Main/Pages/Console/ScriptEvaluator/AutoComplete.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using ExplorerBeta;
|
||||
using UnityEngine;
|
||||
|
||||
// Thanks to ManlyMarco for this
|
||||
|
||||
namespace Explorer.UI.Main.Pages.Console
|
||||
{
|
||||
public struct AutoComplete
|
||||
{
|
||||
public string Full => Prefix + Addition;
|
||||
|
||||
public readonly string Prefix;
|
||||
public readonly string Addition;
|
||||
public readonly Contexts Context;
|
||||
|
||||
public Color TextColor => Context == Contexts.Namespace
|
||||
? Color.gray
|
||||
: Color.white;
|
||||
|
||||
public AutoComplete(string addition, string prefix, Contexts type)
|
||||
{
|
||||
Addition = addition;
|
||||
Prefix = prefix;
|
||||
Context = type;
|
||||
}
|
||||
|
||||
public enum Contexts
|
||||
{
|
||||
Namespace,
|
||||
Other
|
||||
}
|
||||
}
|
||||
|
||||
public static class AutoCompleteHelpers
|
||||
{
|
||||
public static HashSet<string> Namespaces => _namespaces ?? GetNamespaces();
|
||||
private static HashSet<string> _namespaces;
|
||||
|
||||
private static HashSet<string> GetNamespaces()
|
||||
{
|
||||
var set = new HashSet<string>(
|
||||
AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(GetTypes)
|
||||
.Where(x => x.IsPublic && !string.IsNullOrEmpty(x.Namespace))
|
||||
.Select(x => x.Namespace));
|
||||
|
||||
return _namespaces = set;
|
||||
|
||||
IEnumerable<Type> GetTypes(Assembly asm) => asm.TryGetTypes();
|
||||
}
|
||||
}
|
||||
}
|
70
src/UI/Main/Pages/Console/ScriptEvaluator/ScriptEvaluator.cs
Normal file
70
src/UI/Main/Pages/Console/ScriptEvaluator/ScriptEvaluator.cs
Normal file
@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Mono.CSharp;
|
||||
|
||||
// Thanks to ManlyMarco for this
|
||||
|
||||
namespace Explorer.UI.Main.Pages.Console
|
||||
{
|
||||
internal 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 (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
string name = assembly.GetName().Name;
|
||||
if (StdLib.Contains(name))
|
||||
continue;
|
||||
import(assembly);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Mono.CSharp;
|
||||
using UnityEngine;
|
||||
using ExplorerBeta;
|
||||
|
||||
namespace Explorer.UI.Main
|
||||
{
|
||||
public class ScriptInteraction : InteractiveBase
|
||||
{
|
||||
public static void Log(object message)
|
||||
{
|
||||
ExplorerCore.Log(message);
|
||||
}
|
||||
|
||||
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("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
|
||||
ExplorerCore.Log(" C# Console Help ");
|
||||
ExplorerCore.Log("");
|
||||
ExplorerCore.Log("The following helper methods are available:");
|
||||
ExplorerCore.Log("");
|
||||
ExplorerCore.Log("void Log(object message)");
|
||||
ExplorerCore.Log(" prints a message to the console window and debug log");
|
||||
ExplorerCore.Log(" usage: Log(\"hello world\");");
|
||||
ExplorerCore.Log("");
|
||||
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));");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Explorer.UI.Main.Pages.Console;
|
||||
using ExplorerBeta;
|
||||
using ExplorerBeta.UI;
|
||||
using ExplorerBeta.UI.Main;
|
||||
using ExplorerBeta.Unstrip.Resources;
|
||||
using TMPro;
|
||||
using UnhollowerRuntimeLib;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Explorer.UI.Main.Pages
|
||||
{
|
||||
@ -9,14 +19,350 @@ namespace Explorer.UI.Main.Pages
|
||||
{
|
||||
public override string Name => "C# Console";
|
||||
|
||||
public static ConsolePage Instance { get; private set; }
|
||||
|
||||
private CodeEditor codeEditor;
|
||||
|
||||
private ScriptEvaluator m_evaluator;
|
||||
|
||||
public static List<AutoComplete> AutoCompletes = new List<AutoComplete>();
|
||||
public static List<string> UsingDirectives;
|
||||
|
||||
public static readonly string[] DefaultUsing = new string[]
|
||||
{
|
||||
"System",
|
||||
"UnityEngine",
|
||||
"System.Linq",
|
||||
"System.Collections",
|
||||
"System.Collections.Generic",
|
||||
"System.Reflection"
|
||||
};
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
try
|
||||
{
|
||||
ResetConsole();
|
||||
|
||||
foreach (var use in DefaultUsing)
|
||||
{
|
||||
AddUsing(use);
|
||||
}
|
||||
|
||||
ConstructUI();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Error setting up console!\r\nMessage: {e.Message}");
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
codeEditor?.Update();
|
||||
}
|
||||
|
||||
internal string AsmToUsing(string asm, bool richText = false)
|
||||
{
|
||||
if (richText)
|
||||
{
|
||||
return $"<color=#569cd6>using</color> {asm};";
|
||||
}
|
||||
return $"using {asm};";
|
||||
}
|
||||
|
||||
public void AddUsing(string asm)
|
||||
{
|
||||
if (!UsingDirectives.Contains(asm))
|
||||
{
|
||||
UsingDirectives.Add(asm);
|
||||
Evaluate(AsmToUsing(asm), true);
|
||||
}
|
||||
}
|
||||
|
||||
public object Evaluate(string str, bool suppressWarning = false)
|
||||
{
|
||||
object ret = VoidType.Value;
|
||||
|
||||
m_evaluator.Compile(str, out var compiled);
|
||||
|
||||
try
|
||||
{
|
||||
if (compiled == null)
|
||||
{
|
||||
throw new Exception("Mono.Csharp Service was unable to compile the code provided.");
|
||||
}
|
||||
|
||||
compiled.Invoke(ref ret);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!suppressWarning)
|
||||
{
|
||||
ExplorerCore.LogWarning(e.GetType() + ", " + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void ResetConsole()
|
||||
{
|
||||
if (m_evaluator != null)
|
||||
{
|
||||
m_evaluator.Dispose();
|
||||
}
|
||||
|
||||
m_evaluator = new ScriptEvaluator(new StringWriter(new StringBuilder())) { InteractiveBaseClass = typeof(ScriptInteraction) };
|
||||
|
||||
UsingDirectives = new List<string>();
|
||||
}
|
||||
|
||||
#region UI Construction
|
||||
|
||||
public void ConstructUI()
|
||||
{
|
||||
Content = UIFactory.CreateUIObject("C# Console", MainMenu.Instance.PageViewport);
|
||||
|
||||
var mainLayout = Content.AddComponent<LayoutElement>();
|
||||
mainLayout.preferredHeight = 300;
|
||||
mainLayout.flexibleHeight = 4;
|
||||
|
||||
var mainGroup = Content.AddComponent<VerticalLayoutGroup>();
|
||||
mainGroup.childControlHeight = true;
|
||||
mainGroup.childControlWidth = true;
|
||||
mainGroup.childForceExpandHeight = true;
|
||||
mainGroup.childForceExpandWidth = true;
|
||||
|
||||
var topBarObj = UIFactory.CreateHorizontalGroup(Content);
|
||||
var topBarLayout = topBarObj.AddComponent<LayoutElement>();
|
||||
topBarLayout.preferredHeight = 50;
|
||||
topBarLayout.flexibleHeight = 0;
|
||||
|
||||
var topBarGroup = topBarObj.GetComponent<HorizontalLayoutGroup>();
|
||||
topBarGroup.padding.left = 30;
|
||||
topBarGroup.padding.right = 30;
|
||||
topBarGroup.padding.top = 8;
|
||||
topBarGroup.padding.bottom = 8;
|
||||
topBarGroup.spacing = 10;
|
||||
topBarGroup.childForceExpandHeight = true;
|
||||
topBarGroup.childForceExpandWidth = true;
|
||||
topBarGroup.childControlWidth = true;
|
||||
topBarGroup.childControlHeight = true;
|
||||
|
||||
var topBarLabel = UIFactory.CreateLabel(topBarObj, TextAnchor.MiddleLeft);
|
||||
var topBarLabelLayout = topBarLabel.AddComponent<LayoutElement>();
|
||||
topBarLabelLayout.preferredWidth = 800;
|
||||
topBarLabelLayout.flexibleWidth = 10;
|
||||
var topBarText = topBarLabel.GetComponent<Text>();
|
||||
topBarText.text = "C# Console";
|
||||
topBarText.fontSize = 20;
|
||||
|
||||
var compileBtnObj = UIFactory.CreateButton(topBarObj);
|
||||
var compileBtnLayout = compileBtnObj.AddComponent<LayoutElement>();
|
||||
compileBtnLayout.preferredWidth = 80;
|
||||
compileBtnLayout.flexibleWidth = 0;
|
||||
var compileButton = compileBtnObj.GetComponent<Button>();
|
||||
var compileBtnColors = compileButton.colors;
|
||||
compileBtnColors.normalColor = new Color(14f/255f, 106f/255f, 14f/255f);
|
||||
compileButton.colors = compileBtnColors;
|
||||
var btnText = compileBtnObj.GetComponentInChildren<Text>();
|
||||
btnText.text = ">";
|
||||
btnText.fontSize = 25;
|
||||
btnText.color = Color.white;
|
||||
|
||||
var consoleBase = UIFactory.CreateUIObject("CodeEditor", Content);
|
||||
|
||||
var consoleLayout = consoleBase.AddComponent<LayoutElement>();
|
||||
consoleLayout.preferredHeight = 500;
|
||||
consoleLayout.flexibleHeight = 5;
|
||||
|
||||
consoleBase.AddComponent<RectMask2D>();
|
||||
|
||||
var mainRect = consoleBase.GetComponent<RectTransform>();
|
||||
mainRect.pivot = Vector2.one * 0.5f;
|
||||
mainRect.anchorMin = Vector2.zero;
|
||||
mainRect.anchorMax = Vector2.one;
|
||||
mainRect.offsetMin = Vector2.zero;
|
||||
mainRect.offsetMax = Vector2.zero;
|
||||
|
||||
var mainBg = UIFactory.CreateUIObject("MainBackground", consoleBase);
|
||||
|
||||
var mainBgRect = mainBg.GetComponent<RectTransform>();
|
||||
mainBgRect.pivot = new Vector2(0, 1);
|
||||
mainBgRect.anchorMin = Vector2.zero;
|
||||
mainBgRect.anchorMax = Vector2.one;
|
||||
mainBgRect.offsetMin = Vector2.zero;
|
||||
mainBgRect.offsetMax = Vector2.zero;
|
||||
|
||||
var mainBgImage = mainBg.AddComponent<Image>();
|
||||
|
||||
var lineHighlight = UIFactory.CreateUIObject("LineHighlight", consoleBase);
|
||||
|
||||
var lineHighlightRect = lineHighlight.GetComponent<RectTransform>();
|
||||
lineHighlightRect.pivot = new Vector2(0.5f, 1);
|
||||
lineHighlightRect.anchorMin = new Vector2(0, 1);
|
||||
lineHighlightRect.anchorMax = new Vector2(1, 1);
|
||||
lineHighlightRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 21);
|
||||
|
||||
var lineHighlightImage = lineHighlight.GetComponent<Image>();
|
||||
if (!lineHighlightImage)
|
||||
lineHighlightImage = lineHighlight.AddComponent<Image>();
|
||||
|
||||
var linesBg = UIFactory.CreateUIObject("LinesBackground", consoleBase);
|
||||
var linesBgRect = linesBg.GetComponent<RectTransform>();
|
||||
linesBgRect.anchorMin = Vector2.zero;
|
||||
linesBgRect.anchorMax = new Vector2(0, 1);
|
||||
linesBgRect.offsetMin = new Vector2(-17.5f, 0);
|
||||
linesBgRect.offsetMax = new Vector2(17.5f, 0);
|
||||
linesBgRect.sizeDelta = new Vector2(65, 0);
|
||||
|
||||
var linesBgImage = linesBg.AddComponent<Image>();
|
||||
|
||||
var inputObj = UIFactory.CreateTMPInput(consoleBase);
|
||||
|
||||
var inputField = inputObj.GetComponent<TMP_InputField>();
|
||||
inputField.richText = false;
|
||||
|
||||
var inputRect = inputObj.GetComponent<RectTransform>();
|
||||
inputRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
inputRect.anchorMin = Vector2.zero;
|
||||
inputRect.anchorMax = new Vector2(0.92f, 1);
|
||||
inputRect.offsetMin = new Vector2(20, 0);
|
||||
inputRect.offsetMax = new Vector2(14, 0);
|
||||
inputRect.anchoredPosition = new Vector2(40, 0);
|
||||
|
||||
var textAreaObj = inputObj.transform.Find("TextArea");
|
||||
var textAreaRect = textAreaObj.GetComponent<RectTransform>();
|
||||
textAreaRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
textAreaRect.anchorMin = Vector2.zero;
|
||||
textAreaRect.anchorMax = Vector2.one;
|
||||
|
||||
var mainTextObj = textAreaObj.transform.Find("Text");
|
||||
var mainTextRect = mainTextObj.GetComponent<RectTransform>();
|
||||
mainTextRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
mainTextRect.anchorMin = Vector2.zero;
|
||||
mainTextRect.anchorMax = Vector2.one;
|
||||
mainTextRect.offsetMin = Vector2.zero;
|
||||
mainTextRect.offsetMax = Vector2.zero;
|
||||
|
||||
var mainTextInput = mainTextObj.GetComponent<TextMeshProUGUI>();
|
||||
mainTextInput.fontSize = 18;
|
||||
|
||||
var linesTextObj = UIFactory.CreateUIObject("LinesText", mainTextObj.gameObject);
|
||||
var linesTextRect = linesTextObj.GetComponent<RectTransform>();
|
||||
|
||||
var linesTextInput = linesTextObj.AddComponent<TextMeshProUGUI>();
|
||||
linesTextInput.fontSize = 18;
|
||||
|
||||
var highlightTextObj = UIFactory.CreateUIObject("HighlightText", mainTextObj.gameObject);
|
||||
var highlightTextRect = highlightTextObj.GetComponent<RectTransform>();
|
||||
highlightTextRect.anchorMin = Vector2.zero;
|
||||
highlightTextRect.anchorMax = Vector2.one;
|
||||
highlightTextRect.offsetMin = Vector2.zero;
|
||||
highlightTextRect.offsetMax = Vector2.zero;
|
||||
|
||||
var highlightTextInput = highlightTextObj.AddComponent<TextMeshProUGUI>();
|
||||
highlightTextInput.fontSize = 18;
|
||||
|
||||
var scroll = UIFactory.CreateScrollbar(consoleBase);
|
||||
|
||||
var scrollRect = scroll.GetComponent<RectTransform>();
|
||||
scrollRect.anchorMin = new Vector2(1, 0);
|
||||
scrollRect.anchorMax = new Vector2(1, 1);
|
||||
scrollRect.pivot = new Vector2(0.5f, 1);
|
||||
scrollRect.offsetMin = new Vector2(-25f, 0);
|
||||
|
||||
var scroller = scroll.GetComponent<Scrollbar>();
|
||||
scroller.direction = Scrollbar.Direction.TopToBottom;
|
||||
var scrollColors = scroller.colors;
|
||||
scrollColors.normalColor = new Color(0.6f, 0.6f, 0.6f, 1.0f);
|
||||
scroller.colors = scrollColors;
|
||||
|
||||
var scrollImage = scroll.GetComponent<Image>();
|
||||
|
||||
var tmpInput = inputObj.GetComponent<TMP_InputField>();
|
||||
tmpInput.scrollSensitivity = 15;
|
||||
tmpInput.verticalScrollbar = scroller;
|
||||
|
||||
// set lines text anchors here after UI is fleshed out
|
||||
linesTextRect.pivot = Vector2.zero;
|
||||
linesTextRect.anchorMin = new Vector2(0, 0);
|
||||
linesTextRect.anchorMax = new Vector2(1, 1);
|
||||
linesTextRect.offsetMin = Vector2.zero;
|
||||
linesTextRect.offsetMax = Vector2.zero;
|
||||
linesTextRect.anchoredPosition = new Vector2(-40, 0);
|
||||
|
||||
tmpInput.GetComponentInChildren<RectMask2D>().enabled = false;
|
||||
inputObj.GetComponent<Image>().enabled = false;
|
||||
|
||||
// setup button callbacks
|
||||
|
||||
compileButton.onClick.AddListener(new Action(() =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(tmpInput.text))
|
||||
{
|
||||
Evaluate(tmpInput.text.Trim());
|
||||
}
|
||||
}));
|
||||
|
||||
TMP_FontAsset fontToUse = null;
|
||||
#if CPP
|
||||
var fonts = ResourcesUnstrip.FindObjectsOfTypeAll(Il2CppType.Of<TMP_FontAsset>());
|
||||
foreach (var font in fonts)
|
||||
{
|
||||
var fontCast = font.Il2CppCast(typeof(TMP_FontAsset)) as TMP_FontAsset;
|
||||
|
||||
if (fontCast.name.Contains("LiberationSans"))
|
||||
{
|
||||
fontToUse = fontCast;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
var fonts = Resources.FindObjectsOfTypeAll<TMP_FontAsset>();
|
||||
foreach (var font in fonts)
|
||||
{
|
||||
if (font.name.Contains("LiberationSans"))
|
||||
{
|
||||
fontToUse = font;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (fontToUse != null)
|
||||
{
|
||||
fontToUse.faceInfo.tabWidth = 10;
|
||||
fontToUse.tabSize = 10;
|
||||
var faceInfo = fontToUse.faceInfo;
|
||||
faceInfo.tabWidth = 10;
|
||||
fontToUse.faceInfo = faceInfo;
|
||||
|
||||
tmpInput.fontAsset = fontToUse;
|
||||
mainTextInput.font = fontToUse;
|
||||
highlightTextInput.font = fontToUse;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
codeEditor = new CodeEditor(inputField, mainTextInput, highlightTextInput, linesTextInput,
|
||||
mainBgImage, lineHighlightImage, linesBgImage, scrollImage);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.Log(e);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private class VoidType
|
||||
{
|
||||
public static readonly VoidType Value = new VoidType();
|
||||
private VoidType() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace ExplorerBeta.UI
|
||||
@ -12,10 +13,10 @@ namespace ExplorerBeta.UI
|
||||
{
|
||||
private static Vector2 s_ThickElementSize = new Vector2(160f, 30f);
|
||||
private static Vector2 s_ThinElementSize = new Vector2(160f, 20f);
|
||||
//private static Vector2 s_ImageElementSize = new Vector2(100f, 100f);
|
||||
private static Color s_DefaultSelectableColor = new Color(1f, 1f, 1f, 1f);
|
||||
private static Color s_PanelColor = new Color(0.1f, 0.1f, 0.1f, 1.0f);
|
||||
private static Color s_TextColor = new Color(0.95f, 0.95f, 0.95f, 1f);
|
||||
//private static Color s_PanelColor = new Color(0.1f, 0.1f, 0.1f, 1.0f);
|
||||
//private static Vector2 s_ImageElementSize = new Vector2(100f, 100f);
|
||||
|
||||
public static Resources UIResources { get; set; }
|
||||
|
||||
@ -30,7 +31,7 @@ namespace ExplorerBeta.UI
|
||||
public Sprite mask;
|
||||
}
|
||||
|
||||
private static GameObject CreateUIObject(string name, GameObject parent, Vector2 size = default)
|
||||
public static GameObject CreateUIObject(string name, GameObject parent, Vector2 size = default)
|
||||
{
|
||||
GameObject obj = new GameObject(name);
|
||||
|
||||
@ -53,14 +54,22 @@ namespace ExplorerBeta.UI
|
||||
//lbl.resizeTextForBestFit = true;
|
||||
}
|
||||
|
||||
private static void SetDefaultColorTransitionValues(Selectable slider)
|
||||
private static void SetDefaultColorTransitionValues(Selectable selectable)
|
||||
{
|
||||
ColorBlock colors = slider.colors;
|
||||
colors.normalColor = new Color(0.3f, 0.3f, 0.3f);
|
||||
ColorBlock colors = selectable.colors;
|
||||
colors.normalColor = new Color(0.4f, 0.4f, 0.4f);
|
||||
colors.highlightedColor = new Color(0.45f, 0.45f, 0.45f);
|
||||
colors.pressedColor = new Color(0.1f, 0.1f, 0.1f);
|
||||
colors.disabledColor = new Color(0.7f, 0.7f, 0.7f);
|
||||
slider.colors = colors;
|
||||
|
||||
// fix to make all buttons become de-selected after being clicked.
|
||||
// this is because i'm not setting any ColorBlock.selectedColor, because it is commonly stripped.
|
||||
if (selectable is Button button)
|
||||
{
|
||||
button.onClick.AddListener(new Action(() => { button.OnDeselect(EventSystem.current?.baseEventDataCache); }));
|
||||
}
|
||||
|
||||
selectable.colors = colors;
|
||||
}
|
||||
|
||||
private static void SetParentAndAlign(GameObject child, GameObject parent)
|
||||
@ -379,7 +388,7 @@ namespace ExplorerBeta.UI
|
||||
var placeHolderObj = CreateUIObject("Placeholder", textArea);
|
||||
var placeholderText = placeHolderObj.AddComponent<TextMeshProUGUI>();
|
||||
placeholderText.fontSize = 16;
|
||||
placeholderText.text = "Nothing logged yet...";
|
||||
placeholderText.text = "...";
|
||||
placeholderText.color = new Color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
|
||||
var placeHolderRect = placeHolderObj.GetComponent<RectTransform>();
|
||||
|
@ -71,6 +71,8 @@ namespace ExplorerBeta.UI
|
||||
|
||||
public static void Update()
|
||||
{
|
||||
MainMenu.Instance?.Update();
|
||||
|
||||
if (EventSys && InputModule)
|
||||
{
|
||||
if (EventSystem.current != EventSys)
|
||||
|
Loading…
x
Reference in New Issue
Block a user