mirror of
https://github.com/sinai-dev/UnityExplorer.git
synced 2025-06-17 22:48:04 +08:00
348 lines
11 KiB
C#
348 lines
11 KiB
C#
![]() |
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Linq;
|
|||
|
using System.Text;
|
|||
|
using UnityEngine;
|
|||
|
using UnityEngine.UI;
|
|||
|
using UnityExplorer.UI.CSharpConsole.Lexers;
|
|||
|
|
|||
|
namespace UnityExplorer.UI.CSharpConsole
|
|||
|
{
|
|||
|
public struct MatchInfo
|
|||
|
{
|
|||
|
public int startIndex;
|
|||
|
public int endIndex;
|
|||
|
public string htmlColorTag;
|
|||
|
}
|
|||
|
|
|||
|
public class LexerBuilder
|
|||
|
{
|
|||
|
// Initialization and core
|
|||
|
|
|||
|
public const char WHITESPACE = ' ';
|
|||
|
public const char INDENT_OPEN = '{';
|
|||
|
public const char INDENT_CLOSE = '}';
|
|||
|
|
|||
|
private readonly Lexer[] lexers;
|
|||
|
private readonly HashSet<char> delimiters = new HashSet<char>();
|
|||
|
|
|||
|
public LexerBuilder()
|
|||
|
{
|
|||
|
lexers = new Lexer[]
|
|||
|
{
|
|||
|
new CommentLexer(),
|
|||
|
new SymbolLexer(),
|
|||
|
new StringLexer(),
|
|||
|
new NumberLexer(),
|
|||
|
new KeywordLexer(),
|
|||
|
};
|
|||
|
|
|||
|
foreach (var matcher in lexers)
|
|||
|
{
|
|||
|
foreach (char c in matcher.Delimiters)
|
|||
|
{
|
|||
|
if (!delimiters.Contains(c))
|
|||
|
delimiters.Add(c);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public bool IsDelimiter(char character, bool orWhitespace = false, bool orLetterOrDigit = false)
|
|||
|
{
|
|||
|
return delimiters.Contains(character)
|
|||
|
|| (orWhitespace && char.IsWhiteSpace(character))
|
|||
|
|| (orLetterOrDigit && char.IsLetterOrDigit(character));
|
|||
|
}
|
|||
|
|
|||
|
// Lexer enumeration
|
|||
|
|
|||
|
public string InputString { get; private set; }
|
|||
|
public int Length => InputString.Length;
|
|||
|
|
|||
|
public int LastCommittedIndex { get; private set; }
|
|||
|
public int LookaheadIndex { get; private set; }
|
|||
|
|
|||
|
public char Current => !EndOfInput ? InputString[LookaheadIndex] : WHITESPACE;
|
|||
|
public char Previous => LookaheadIndex >= 1 ? InputString[LookaheadIndex - 1] : WHITESPACE;
|
|||
|
|
|||
|
public bool EndOfInput => LookaheadIndex >= Length;
|
|||
|
public bool EndOrNewLine => EndOfInput || Current == '\n' || Current == '\r';
|
|||
|
|
|||
|
public string SyntaxHighlight(string input)
|
|||
|
{
|
|||
|
var sb = new StringBuilder();
|
|||
|
int lastUnhighlighted = 0;
|
|||
|
|
|||
|
// TODO auto indent as part of this parse
|
|||
|
|
|||
|
foreach (var match in GetMatches(input))
|
|||
|
{
|
|||
|
// append non-highlighted text between last match and this
|
|||
|
for (int i = lastUnhighlighted; i < match.startIndex; i++)
|
|||
|
sb.Append(input[i]);
|
|||
|
|
|||
|
// append the highlighted match
|
|||
|
sb.Append(match.htmlColorTag);
|
|||
|
|
|||
|
for (int i = match.startIndex; i <= match.endIndex && i < input.Length; i++)
|
|||
|
sb.Append(input[i]);
|
|||
|
|
|||
|
sb.Append(SignatureHighlighter.CLOSE_COLOR);
|
|||
|
|
|||
|
// update the last unhighlighted start index
|
|||
|
lastUnhighlighted = match.endIndex + 1;
|
|||
|
}
|
|||
|
|
|||
|
return sb.ToString();
|
|||
|
}
|
|||
|
|
|||
|
public IEnumerable<MatchInfo> GetMatches(string input)
|
|||
|
{
|
|||
|
if (string.IsNullOrEmpty(input))
|
|||
|
yield break;
|
|||
|
|
|||
|
InputString = input;
|
|||
|
LastCommittedIndex = -1;
|
|||
|
Rollback();
|
|||
|
|
|||
|
while (!EndOfInput)
|
|||
|
{
|
|||
|
SkipWhitespace();
|
|||
|
bool anyMatch = false;
|
|||
|
int startIndex = LastCommittedIndex + 1;
|
|||
|
|
|||
|
foreach (var lexer in lexers)
|
|||
|
{
|
|||
|
if (lexer.TryMatchCurrent(this))
|
|||
|
{
|
|||
|
anyMatch = true;
|
|||
|
|
|||
|
yield return new MatchInfo
|
|||
|
{
|
|||
|
startIndex = startIndex,
|
|||
|
endIndex = LastCommittedIndex,
|
|||
|
htmlColorTag = lexer.ColorTag,
|
|||
|
};
|
|||
|
break;
|
|||
|
}
|
|||
|
else
|
|||
|
Rollback();
|
|||
|
}
|
|||
|
|
|||
|
if (!anyMatch)
|
|||
|
{
|
|||
|
LookaheadIndex = LastCommittedIndex + 1;
|
|||
|
Commit();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public char PeekNext(int amount = 1)
|
|||
|
{
|
|||
|
LookaheadIndex += amount;
|
|||
|
return Current;
|
|||
|
}
|
|||
|
|
|||
|
public void Commit()
|
|||
|
{
|
|||
|
LastCommittedIndex = Math.Min(Length - 1, LookaheadIndex);
|
|||
|
}
|
|||
|
|
|||
|
public void Rollback()
|
|||
|
{
|
|||
|
LookaheadIndex = LastCommittedIndex + 1;
|
|||
|
}
|
|||
|
|
|||
|
public void RollbackBy(int amount)
|
|||
|
{
|
|||
|
LookaheadIndex = Math.Max(LastCommittedIndex + 1, LookaheadIndex - amount);
|
|||
|
}
|
|||
|
|
|||
|
private void SkipWhitespace()
|
|||
|
{
|
|||
|
// peek and commit as long as there is whitespace
|
|||
|
while (!EndOfInput && char.IsWhiteSpace(Current))
|
|||
|
{
|
|||
|
Commit();
|
|||
|
PeekNext();
|
|||
|
}
|
|||
|
|
|||
|
// revert the last PeekNext which would have returned false
|
|||
|
Rollback();
|
|||
|
}
|
|||
|
|
|||
|
#region AUTO INDENT TODO
|
|||
|
|
|||
|
// Auto-indenting
|
|||
|
|
|||
|
//public int GetIndentLevel(string input, int toIndex)
|
|||
|
//{
|
|||
|
// bool stringState = false;
|
|||
|
// int indent = 0;
|
|||
|
//
|
|||
|
// for (int i = 0; i < toIndex && i < input.Length; i++)
|
|||
|
// {
|
|||
|
// char character = input[i];
|
|||
|
//
|
|||
|
// if (character == '"')
|
|||
|
// stringState = !stringState;
|
|||
|
// else if (!stringState && character == INDENT_OPEN)
|
|||
|
// indent++;
|
|||
|
// else if (!stringState && character == INDENT_CLOSE)
|
|||
|
// indent--;
|
|||
|
// }
|
|||
|
//
|
|||
|
// if (indent < 0)
|
|||
|
// indent = 0;
|
|||
|
//
|
|||
|
// return indent;
|
|||
|
//}
|
|||
|
|
|||
|
//// TODO not quite correct, but almost there.
|
|||
|
//
|
|||
|
//public string AutoIndentOnEnter(string input, ref int caretPos)
|
|||
|
//{
|
|||
|
// var sb = new StringBuilder(input);
|
|||
|
//
|
|||
|
// bool inString = false;
|
|||
|
// bool inChar = false;
|
|||
|
// int currentIndent = 0;
|
|||
|
// int curLineIndent = 0;
|
|||
|
// bool prevWasNewLine = true;
|
|||
|
//
|
|||
|
// // process before caret position
|
|||
|
// for (int i = 0; i < caretPos; i++)
|
|||
|
// {
|
|||
|
// char c = sb[i];
|
|||
|
//
|
|||
|
// ExplorerCore.Log(i + ": " + c);
|
|||
|
//
|
|||
|
// // update string/char state
|
|||
|
// if (!inChar && c == '\"')
|
|||
|
// inString = !inString;
|
|||
|
// else if (!inString && c == '\'')
|
|||
|
// inChar = !inChar;
|
|||
|
//
|
|||
|
// // continue if inside string or char
|
|||
|
// if (inString || inChar)
|
|||
|
// continue;
|
|||
|
//
|
|||
|
// // check for new line
|
|||
|
// if (c == '\n')
|
|||
|
// {
|
|||
|
// ExplorerCore.Log("new line, resetting line counts");
|
|||
|
// curLineIndent = 0;
|
|||
|
// prevWasNewLine = true;
|
|||
|
// }
|
|||
|
// // check for indent
|
|||
|
// else if (c == '\t' && prevWasNewLine)
|
|||
|
// {
|
|||
|
// ExplorerCore.Log("its a tab");
|
|||
|
// if (curLineIndent > currentIndent)
|
|||
|
// {
|
|||
|
// ExplorerCore.Log("too many tabs, removing");
|
|||
|
// // already reached the indent we should have
|
|||
|
// sb.Remove(i, 1);
|
|||
|
// i--;
|
|||
|
// caretPos--;
|
|||
|
// curLineIndent--;
|
|||
|
// }
|
|||
|
// else
|
|||
|
// curLineIndent++;
|
|||
|
// }
|
|||
|
// // remove spaces on new lines
|
|||
|
// else if (c == ' ' && prevWasNewLine)
|
|||
|
// {
|
|||
|
// ExplorerCore.Log("removing newline-space");
|
|||
|
// sb.Remove(i, 1);
|
|||
|
// i--;
|
|||
|
// caretPos--;
|
|||
|
// }
|
|||
|
// else
|
|||
|
// {
|
|||
|
// if (c == INDENT_CLOSE)
|
|||
|
// currentIndent--;
|
|||
|
//
|
|||
|
// if (prevWasNewLine && curLineIndent < currentIndent)
|
|||
|
// {
|
|||
|
// ExplorerCore.Log("line is not indented enough");
|
|||
|
// // line is not indented enough
|
|||
|
// int diff = currentIndent - curLineIndent;
|
|||
|
// sb.Insert(i, new string('\t', diff));
|
|||
|
// caretPos += diff;
|
|||
|
// i += diff;
|
|||
|
// }
|
|||
|
//
|
|||
|
// // check for brackets
|
|||
|
// if ((c == INDENT_CLOSE || c == INDENT_OPEN) && !prevWasNewLine)
|
|||
|
// {
|
|||
|
// ExplorerCore.Log("bracket needs new line");
|
|||
|
//
|
|||
|
// // need to put it on a new line
|
|||
|
// sb.Insert(i, $"\n{new string('\t', currentIndent)}");
|
|||
|
// caretPos += 1 + currentIndent;
|
|||
|
// i += 1 + currentIndent;
|
|||
|
// }
|
|||
|
//
|
|||
|
// if (c == INDENT_OPEN)
|
|||
|
// currentIndent++;
|
|||
|
//
|
|||
|
// prevWasNewLine = false;
|
|||
|
// }
|
|||
|
// }
|
|||
|
//
|
|||
|
// // todo put caret on new line after previous bracket if needed
|
|||
|
// // indent caret to current indent
|
|||
|
//
|
|||
|
// // process after caret position, make sure there are equal opened/closed brackets
|
|||
|
// ExplorerCore.Log("-- after caret --");
|
|||
|
// for (int i = caretPos; i < sb.Length; i++)
|
|||
|
// {
|
|||
|
// char c = sb[i];
|
|||
|
// ExplorerCore.Log(i + ": " + c);
|
|||
|
//
|
|||
|
// // update string/char state
|
|||
|
// if (!inChar && c == '\"')
|
|||
|
// inString = !inString;
|
|||
|
// else if (!inString && c == '\'')
|
|||
|
// inChar = !inChar;
|
|||
|
//
|
|||
|
// if (inString || inChar)
|
|||
|
// continue;
|
|||
|
//
|
|||
|
// if (c == INDENT_OPEN)
|
|||
|
// currentIndent++;
|
|||
|
// else if (c == INDENT_CLOSE)
|
|||
|
// currentIndent--;
|
|||
|
// }
|
|||
|
//
|
|||
|
// if (currentIndent > 0)
|
|||
|
// {
|
|||
|
// ExplorerCore.Log("there are not enough closing brackets, curIndent is " + currentIndent);
|
|||
|
// // There are not enough close brackets
|
|||
|
//
|
|||
|
// // TODO this should append in reverse indent order (small indents inserted first, then biggest).
|
|||
|
// while (currentIndent > 0)
|
|||
|
// {
|
|||
|
// ExplorerCore.Log("Inserting closing bracket with " + currentIndent + " indent");
|
|||
|
// // append the indented '}' on a new line
|
|||
|
// sb.Insert(caretPos, $"\n{new string('\t', currentIndent - 1)}}}");
|
|||
|
//
|
|||
|
// currentIndent--;
|
|||
|
// }
|
|||
|
//
|
|||
|
// }
|
|||
|
// //else if (currentIndent < 0)
|
|||
|
// //{
|
|||
|
// // // There are too many close brackets
|
|||
|
// //
|
|||
|
// // // todo?
|
|||
|
// //}
|
|||
|
//
|
|||
|
// return sb.ToString();
|
|||
|
//}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
}
|