diff --git a/src/Core/Reflection/ReflectionUtility.cs b/src/Core/Reflection/ReflectionUtility.cs
index e64f1cd..dfb7f8d 100644
--- a/src/Core/Reflection/ReflectionUtility.cs
+++ b/src/Core/Reflection/ReflectionUtility.cs
@@ -37,7 +37,23 @@ namespace UnityExplorer
/// Key: Type.FullName
public static readonly SortedDictionary AllTypes = new SortedDictionary(StringComparer.OrdinalIgnoreCase);
- private static readonly SortedSet allTypeNames = new SortedSet(StringComparer.OrdinalIgnoreCase);
+ //private static readonly SortedSet allTypeNames = new SortedSet(StringComparer.OrdinalIgnoreCase);
+
+ private static string[] allTypesArray;
+ private static string[] GetTypeNameArray()
+ {
+ if (allTypesArray == null || allTypesArray.Length != AllTypes.Count)
+ {
+ allTypesArray = new string[AllTypes.Count];
+ int i = 0;
+ foreach (var name in AllTypes.Keys)
+ {
+ allTypesArray[i] = name;
+ i++;
+ }
+ }
+ return allTypesArray;
+ }
private static void SetupTypeCache()
{
@@ -64,7 +80,7 @@ namespace UnityExplorer
else
{
AllTypes.Add(type.FullName, type);
- allTypeNames.Add(type.FullName);
+ //allTypeNames.Add(type.FullName);
}
OnTypeLoaded?.Invoke(type);
@@ -211,14 +227,24 @@ namespace UnityExplorer
///
/// The base type, which can optionally be abstract / interface.
/// All implementations of the type in the current AppDomain.
- public static HashSet GetImplementationsOf(Type baseType, bool allowAbstract, bool allowGeneric)
+ public static HashSet GetImplementationsOf(Type baseType, bool allowAbstract, bool allowGeneric, bool allowRecursive = true)
{
- var key = GetImplementationKey(baseType); //baseType.FullName;
+ var key = GetImplementationKey(baseType);
+ int count = AllTypes.Count;
+ HashSet ret;
if (!baseType.IsGenericParameter)
- return GetImplementations(key, baseType, allowAbstract, allowGeneric);
+ ret = GetImplementations(key, baseType, allowAbstract, allowGeneric);
else
- return GetGenericParameterImplementations(key, baseType, allowAbstract, allowGeneric);
+ ret = GetGenericParameterImplementations(key, baseType, allowAbstract, allowGeneric);
+
+ // types were resolved during the parse, do it again if we're not already rebuilding.
+ if (allowRecursive && AllTypes.Count != count)
+ {
+ ret = GetImplementationsOf(baseType, allowAbstract, allowGeneric, false);
+ }
+
+ return ret;
}
private static HashSet GetImplementations(string key, Type baseType, bool allowAbstract, bool allowGeneric)
@@ -226,8 +252,10 @@ namespace UnityExplorer
if (!typeInheritance.ContainsKey(key))
{
var set = new HashSet();
- foreach (var name in allTypeNames)
+ var names = GetTypeNameArray();
+ for (int i = 0; i < names.Length; i++)
{
+ var name = names[i];
try
{
var type = AllTypes[name];
@@ -263,8 +291,10 @@ namespace UnityExplorer
{
var set = new HashSet();
- foreach (var name in allTypeNames)
+ var names = GetTypeNameArray();
+ for (int i = 0; i < names.Length; i++)
{
+ var name = names[i];
try
{
var type = AllTypes[name];
diff --git a/src/Core/Runtime/Mono/MonoProvider.cs b/src/Core/Runtime/Mono/MonoProvider.cs
index 9c415a0..d6ace86 100644
--- a/src/Core/Runtime/Mono/MonoProvider.cs
+++ b/src/Core/Runtime/Mono/MonoProvider.cs
@@ -10,9 +10,6 @@ using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
-using UnityExplorer.Core;
-using UnityExplorer.Core.CSharp;
-using UnityExplorer.Core.Input;
namespace UnityExplorer.Core.Runtime.Mono
{
diff --git a/src/Core/Runtime/Mono/SortedSet.cs b/src/Core/Runtime/Mono/SortedSet.cs
deleted file mode 100644
index 0b0f396..0000000
--- a/src/Core/Runtime/Mono/SortedSet.cs
+++ /dev/null
@@ -1,3107 +0,0 @@
-#if MONO
-using System;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.Serialization;
-using System.Runtime.CompilerServices;
-
-// Reimplementation of SortedSet and required supplementary classes for .NET 3.5
-// https://referencesource.microsoft.com/#System/compmod/system/collections/generic/sortedset.cs
-
-namespace System.Collections.Generic
-{
- #region ISet
-
- ///
- /// Generic collection that guarantees the uniqueness of its elements, as defined
- /// by some comparer. It also supports basic set operations such as Union, Intersection,
- /// Complement and Exclusive Complement.
- ///
- public interface ISet : ICollection
- {
- //Add ITEM to the set, return true if added, false if duplicate
- new bool Add(T item);
-
- //Transform this set into its union with the IEnumerable other
- void UnionWith(IEnumerable other);
-
- //Transform this set into its intersection with the IEnumberable other
- void IntersectWith(IEnumerable other);
-
- //Transform this set so it contains no elements that are also in other
- void ExceptWith(IEnumerable other);
-
- //Transform this set so it contains elements initially in this or in other, but not both
- void SymmetricExceptWith(IEnumerable other);
-
- //Check if this set is a subset of other
- bool IsSubsetOf(IEnumerable other);
-
- //Check if this set is a superset of other
- bool IsSupersetOf(IEnumerable other);
-
- //Check if this set is a subset of other, but not the same as it
- bool IsProperSupersetOf(IEnumerable other);
-
- //Check if this set is a superset of other, but not the same as it
- bool IsProperSubsetOf(IEnumerable other);
-
- //Check if this set has any elements in common with other
- bool Overlaps(IEnumerable other);
-
- //Check if this set contains the same and only the same elements as other
- bool SetEquals(IEnumerable other);
- }
-
- #endregion
-
-
- #region Bit Helper
-
- ///
- /// ABOUT:
- /// Helps with operations that rely on bit marking to indicate whether an item in the
- /// collection should be added, removed, visited already, etc.
- ///
- /// BitHelper doesn't allocate the array; you must pass in an array or ints allocated on the
- /// stack or heap. ToIntArrayLength() tells you the int array size you must allocate.
- ///
- /// USAGE:
- /// Suppose you need to represent a bit array of length (i.e. logical bit array length)
- /// BIT_ARRAY_LENGTH. Then this is the suggested way to instantiate BitHelper:
- /// ***************************************************************************
- /// int intArrayLength = BitHelper.ToIntArrayLength(BIT_ARRAY_LENGTH);
- /// BitHelper bitHelper;
- /// if (intArrayLength less than stack alloc threshold)
- /// int* m_arrayPtr = stackalloc int[intArrayLength];
- /// bitHelper = new BitHelper(m_arrayPtr, intArrayLength);
- /// else
- /// int[] m_arrayPtr = new int[intArrayLength];
- /// bitHelper = new BitHelper(m_arrayPtr, intArrayLength);
- /// ***************************************************************************
- ///
- /// IMPORTANT:
- /// The second ctor args, length, should be specified as the length of the int array, not
- /// the logical bit array. Because length is used for bounds checking into the int array,
- /// it's especially important to get this correct for the stackalloc version. See the code
- /// samples above; this is the value gotten from ToIntArrayLength().
- ///
- /// The length ctor argument is the only exception; for other methods -- MarkBit and
- /// IsMarked -- pass in values as indices into the logical bit array, and it will be mapped
- /// to the position within the array of ints.
- ///
- ///
-
- unsafe internal class BitHelper
- { // should not be serialized
-
- private const byte MarkedBitFlag = 1;
- private const byte IntSize = 32;
-
- // m_length of underlying int array (not logical bit array)
- private int m_length;
-
- // ptr to stack alloc'd array of ints
- [System.Security.SecurityCritical]
- private int* m_arrayPtr;
-
- // array of ints
- private int[] m_array;
-
- // whether to operate on stack alloc'd or heap alloc'd array
- private bool useStackAlloc;
-
- ///
- /// Instantiates a BitHelper with a heap alloc'd array of ints
- ///
- /// int array to hold bits
- /// length of int array
- //
- //
- //
- //
- [System.Security.SecurityCritical]
- internal BitHelper(int* bitArrayPtr, int length)
- {
- this.m_arrayPtr = bitArrayPtr;
- this.m_length = length;
- useStackAlloc = true;
- }
-
- ///
- /// Instantiates a BitHelper with a heap alloc'd array of ints
- ///
- /// int array to hold bits
- /// length of int array
- internal BitHelper(int[] bitArray, int length)
- {
- this.m_array = bitArray;
- this.m_length = length;
- }
-
- ///
- /// Mark bit at specified position
- ///
- ///
- //
- //
- //
- [System.Security.SecurityCritical]
- internal unsafe void MarkBit(int bitPosition)
- {
- if (useStackAlloc)
- {
- int bitArrayIndex = bitPosition / IntSize;
- if (bitArrayIndex < m_length && bitArrayIndex >= 0)
- {
- m_arrayPtr[bitArrayIndex] |= (MarkedBitFlag << (bitPosition % IntSize));
- }
- }
- else
- {
- int bitArrayIndex = bitPosition / IntSize;
- if (bitArrayIndex < m_length && bitArrayIndex >= 0)
- {
- m_array[bitArrayIndex] |= (MarkedBitFlag << (bitPosition % IntSize));
- }
- }
- }
-
- ///
- /// Is bit at specified position marked?
- ///
- ///
- ///
- //
- //
- //
- [System.Security.SecurityCritical]
- internal unsafe bool IsMarked(int bitPosition)
- {
- if (useStackAlloc)
- {
- int bitArrayIndex = bitPosition / IntSize;
- if (bitArrayIndex < m_length && bitArrayIndex >= 0)
- {
- return ((m_arrayPtr[bitArrayIndex] & (MarkedBitFlag << (bitPosition % IntSize))) != 0);
- }
- return false;
- }
- else
- {
- int bitArrayIndex = bitPosition / IntSize;
- if (bitArrayIndex < m_length && bitArrayIndex >= 0)
- {
- return ((m_array[bitArrayIndex] & (MarkedBitFlag << (bitPosition % IntSize))) != 0);
- }
- return false;
- }
- }
-
- ///
- /// How many ints must be allocated to represent n bits. Returns (n+31)/32, but
- /// avoids overflow
- ///
- ///
- ///
- internal static int ToIntArrayLength(int n)
- {
- return n > 0 ? ((n - 1) / IntSize + 1) : 0;
- }
-
- }
-
- #endregion
-
-
- #region SortedSet
-
- /*============================================================
- **
- ** Class: SortedSet
- **
- ** Purpose: A generic sorted set.
- **
- ** Date: August 15, 2008
- **
- ===========================================================*/
-
- //
- // A binary search tree is a red-black tree if it satisfies the following red-black properties:
- // 1. Every node is either red or black
- // 2. Every leaf (nil node) is black
- // 3. If a node is red, then both its children are black
- // 4. Every simple path from a node to a descendant leaf contains the same number of black nodes
- //
- // The basic idea of red-black tree is to represent 2-3-4 trees as standard BSTs but to add one extra bit of information
- // per node to encode 3-nodes and 4-nodes.
- // 4-nodes will be represented as: B
- // R R
- // 3 -node will be represented as: B or B
- // R B B R
- //
- // For a detailed description of the algorithm, take a look at "Algorithms" by Robert Sedgewick.
- //
-
- internal delegate bool TreeWalkPredicate(SortedSet.Node node);
-
- internal enum TreeRotation
- {
- LeftRotation = 1,
- RightRotation = 2,
- RightLeftRotation = 3,
- LeftRightRotation = 4,
- }
-
- //[SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "by design name choice")]
- [DebuggerDisplay("Count = {Count}")]
-#if !FEATURE_NETCORE
- [Serializable]
- public class SortedSet : ISet, ICollection, ICollection, ISerializable, IDeserializationCallback
- {
-#else
- public class SortedSet : ISet, ICollection, ICollection, IReadOnlyCollection {
-#endif //!FEATURE_NETCORE
- #region local variables/constants
- Node root;
- IComparer comparer;
- int count;
- int version;
- private Object _syncRoot;
-
- private const String ComparerName = "Comparer";
- private const String CountName = "Count";
- private const String ItemsName = "Items";
- private const String VersionName = "Version";
- //needed for enumerator
- private const String TreeName = "Tree";
- private const String NodeValueName = "Item";
- private const String EnumStartName = "EnumStarted";
- private const String ReverseName = "Reverse";
- private const String EnumVersionName = "EnumVersion";
-
-#if !FEATURE_NETCORE
- //needed for TreeSubset
- private const String minName = "Min";
- private const String maxName = "Max";
- private const String lBoundActiveName = "lBoundActive";
- private const String uBoundActiveName = "uBoundActive";
-
- private SerializationInfo siInfo; //A temporary variable which we need during deserialization.
-#endif
- internal const int StackAllocThreshold = 100;
-
- #endregion
-
- #region Constructors
- public SortedSet()
- {
- this.comparer = Comparer.Default;
-
- }
-
- public SortedSet(IComparer comparer)
- {
- if (comparer == null)
- {
- this.comparer = Comparer.Default;
- }
- else
- {
- this.comparer = comparer;
- }
- }
-
-
- public SortedSet(IEnumerable collection) : this(collection, Comparer.Default) { }
-
- public SortedSet(IEnumerable collection, IComparer comparer)
- : this(comparer)
- {
-
- if (collection == null)
- {
- throw new ArgumentNullException("collection");
- }
-
- // these are explicit type checks in the mould of HashSet. It would have worked better
- // with something like an ISorted (we could make this work for SortedList.Keys etc)
- SortedSet baseSortedSet = collection as SortedSet;
- SortedSet baseTreeSubSet = collection as TreeSubSet;
- if (baseSortedSet != null && baseTreeSubSet == null && AreComparersEqual(this, baseSortedSet))
- {
- //breadth first traversal to recreate nodes
- if (baseSortedSet.Count == 0)
- {
- count = 0;
- version = 0;
- root = null;
- return;
- }
-
-
- //pre order way to replicate nodes
- Stack theirStack = new Stack.Node>(2 * log2(baseSortedSet.Count) + 2);
- Stack myStack = new Stack.Node>(2 * log2(baseSortedSet.Count) + 2);
- Node theirCurrent = baseSortedSet.root;
- Node myCurrent = (theirCurrent != null ? new SortedSet.Node(theirCurrent.Item, theirCurrent.IsRed) : null);
- root = myCurrent;
- while (theirCurrent != null)
- {
- theirStack.Push(theirCurrent);
- myStack.Push(myCurrent);
- myCurrent.Left = (theirCurrent.Left != null ? new SortedSet.Node(theirCurrent.Left.Item, theirCurrent.Left.IsRed) : null);
- theirCurrent = theirCurrent.Left;
- myCurrent = myCurrent.Left;
- }
- while (theirStack.Count != 0)
- {
- theirCurrent = theirStack.Pop();
- myCurrent = myStack.Pop();
- Node theirRight = theirCurrent.Right;
- Node myRight = null;
- if (theirRight != null)
- {
- myRight = new SortedSet.Node(theirRight.Item, theirRight.IsRed);
- }
- myCurrent.Right = myRight;
-
- while (theirRight != null)
- {
- theirStack.Push(theirRight);
- myStack.Push(myRight);
- myRight.Left = (theirRight.Left != null ? new SortedSet.Node(theirRight.Left.Item, theirRight.Left.IsRed) : null);
- theirRight = theirRight.Left;
- myRight = myRight.Left;
- }
- }
- count = baseSortedSet.count;
- version = 0;
- }
- else
- { //As it stands, you're doing an NlogN sort of the collection
-
- List els = new List(collection);
- els.Sort(this.comparer);
- for (int i = 1; i < els.Count; i++)
- {
- if (comparer.Compare(els[i], els[i - 1]) == 0)
- {
- els.RemoveAt(i);
- i--;
- }
- }
- root = ConstructRootFromSortedArray(els.ToArray(), 0, els.Count - 1, null);
- count = els.Count;
- version = 0;
- }
- }
-
-
-#if !FEATURE_NETCORE
- protected SortedSet(SerializationInfo info, StreamingContext context)
- {
- siInfo = info;
- }
-#endif
- #endregion
-
- #region Bulk Operation Helpers
- private void AddAllElements(IEnumerable collection)
- {
-
- foreach (T item in collection)
- {
- if (!this.Contains(item))
- Add(item);
- }
- }
-
- private void RemoveAllElements(IEnumerable collection)
- {
- T min = this.Min;
- T max = this.Max;
- foreach (T item in collection)
- {
- if (!(comparer.Compare(item, min) < 0 || comparer.Compare(item, max) > 0) && this.Contains(item))
- this.Remove(item);
- }
- }
-
- private bool ContainsAllElements(IEnumerable collection)
- {
- foreach (T item in collection)
- {
- if (!this.Contains(item))
- {
- return false;
- }
- }
- return true;
- }
-
- //
- // Do a in order walk on tree and calls the delegate for each node.
- // If the action delegate returns false, stop the walk.
- //
- // Return true if the entire tree has been walked.
- // Otherwise returns false.
- //
- internal bool InOrderTreeWalk(TreeWalkPredicate action)
- {
- return InOrderTreeWalk(action, false);
- }
-
- // Allows for the change in traversal direction. Reverse visits nodes in descending order
- internal virtual bool InOrderTreeWalk(TreeWalkPredicate action, bool reverse)
- {
- if (root == null)
- {
- return true;
- }
-
- // The maximum height of a red-black tree is 2*lg(n+1).
- // See page 264 of "Introduction to algorithms" by Thomas H. Cormen
- // note: this should be logbase2, but since the stack grows itself, we
- // don't want the extra cost
- Stack stack = new Stack(2 * (int)(SortedSet.log2(Count + 1)));
- Node current = root;
- while (current != null)
- {
- stack.Push(current);
- current = (reverse ? current.Right : current.Left);
- }
- while (stack.Count != 0)
- {
- current = stack.Pop();
- if (!action(current))
- {
- return false;
- }
-
- Node node = (reverse ? current.Left : current.Right);
- while (node != null)
- {
- stack.Push(node);
- node = (reverse ? node.Right : node.Left);
- }
- }
- return true;
- }
-
- //
- // Do a left to right breadth first walk on tree and
- // calls the delegate for each node.
- // If the action delegate returns false, stop the walk.
- //
- // Return true if the entire tree has been walked.
- // Otherwise returns false.
- //
- internal virtual bool BreadthFirstTreeWalk(TreeWalkPredicate action)
- {
- if (root == null)
- {
- return true;
- }
-
- List processQueue = new List();
- processQueue.Add(root);
- Node current;
-
- while (processQueue.Count != 0)
- {
- current = processQueue[0];
- processQueue.RemoveAt(0);
- if (!action(current))
- {
- return false;
- }
- if (current.Left != null)
- {
- processQueue.Add(current.Left);
- }
- if (current.Right != null)
- {
- processQueue.Add(current.Right);
- }
- }
- return true;
- }
- #endregion
-
- #region Properties
- public int Count
- {
- get
- {
- VersionCheck();
- return count;
- }
- }
-
- public IComparer Comparer
- {
- get
- {
- return comparer;
- }
- }
-
- bool ICollection.IsReadOnly
- {
- get
- {
- return false;
- }
- }
-
- bool ICollection.IsSynchronized
- {
- get
- {
- return false;
- }
- }
-
- object ICollection.SyncRoot
- {
- get
- {
- if (_syncRoot == null)
- {
- System.Threading.Interlocked.CompareExchange(ref _syncRoot, new Object(), null);
- }
- return _syncRoot;
- }
- }
- #endregion
-
- #region Subclass helpers
-
- //virtual function for subclass that needs to update count
- internal virtual void VersionCheck() { }
-
-
- //virtual function for subclass that needs to do range checks
- internal virtual bool IsWithinRange(T item)
- {
- return true;
-
- }
- #endregion
-
- #region ICollection Members
- ///
- /// Add the value ITEM to the tree, returns true if added, false if duplicate
- ///
- /// item to be added
- public bool Add(T item)
- {
- return AddIfNotPresent(item);
- }
-
- void ICollection.Add(T item)
- {
- AddIfNotPresent(item);
- }
-
-
- ///
- /// Adds ITEM to the tree if not already present. Returns TRUE if value was successfully added
- /// or FALSE if it is a duplicate
- ///
- internal virtual bool AddIfNotPresent(T item)
- {
- if (root == null)
- { // empty tree
- root = new Node(item, false);
- count = 1;
- version++;
- return true;
- }
-
- //
- // Search for a node at bottom to insert the new node.
- // If we can guanratee the node we found is not a 4-node, it would be easy to do insertion.
- // We split 4-nodes along the search path.
- //
- Node current = root;
- Node parent = null;
- Node grandParent = null;
- Node greatGrandParent = null;
-
- //even if we don't actually add to the set, we may be altering its structure (by doing rotations
- //and such). so update version to disable any enumerators/subsets working on it
- version++;
-
-
- int order = 0;
- while (current != null)
- {
- order = comparer.Compare(item, current.Item);
- if (order == 0)
- {
- // We could have changed root node to red during the search process.
- // We need to set it to black before we return.
- root.IsRed = false;
- return false;
- }
-
- // split a 4-node into two 2-nodes
- if (Is4Node(current))
- {
- Split4Node(current);
- // We could have introduced two consecutive red nodes after split. Fix that by rotation.
- if (IsRed(parent))
- {
- InsertionBalance(current, ref parent, grandParent, greatGrandParent);
- }
- }
- greatGrandParent = grandParent;
- grandParent = parent;
- parent = current;
- current = (order < 0) ? current.Left : current.Right;
- }
-
- Debug.Assert(parent != null, "Parent node cannot be null here!");
- // ready to insert the new node
- Node node = new Node(item);
- if (order > 0)
- {
- parent.Right = node;
- }
- else
- {
- parent.Left = node;
- }
-
- // the new node will be red, so we will need to adjust the colors if parent node is also red
- if (parent.IsRed)
- {
- InsertionBalance(node, ref parent, grandParent, greatGrandParent);
- }
-
- // Root node is always black
- root.IsRed = false;
- ++count;
- return true;
- }
-
- ///
- /// Remove the T ITEM from this SortedSet. Returns true if successfully removed.
- ///
- ///
- ///
- public bool Remove(T item)
- {
- return this.DoRemove(item); // hack so it can be made non-virtual
- }
-
- internal virtual bool DoRemove(T item)
- {
-
- if (root == null)
- {
- return false;
- }
-
-
- // Search for a node and then find its succesor.
- // Then copy the item from the succesor to the matching node and delete the successor.
- // If a node doesn't have a successor, we can replace it with its left child (if not empty.)
- // or delete the matching node.
- //
- // In top-down implementation, it is important to make sure the node to be deleted is not a 2-node.
- // Following code will make sure the node on the path is not a 2 Node.
-
- //even if we don't actually remove from the set, we may be altering its structure (by doing rotations
- //and such). so update version to disable any enumerators/subsets working on it
- version++;
-
- Node current = root;
- Node parent = null;
- Node grandParent = null;
- Node match = null;
- Node parentOfMatch = null;
- bool foundMatch = false;
- while (current != null)
- {
- if (Is2Node(current))
- { // fix up 2-Node
- if (parent == null)
- { // current is root. Mark it as red
- current.IsRed = true;
- }
- else
- {
- Node sibling = GetSibling(current, parent);
- if (sibling.IsRed)
- {
- // If parent is a 3-node, flip the orientation of the red link.
- // We can acheive this by a single rotation
- // This case is converted to one of other cased below.
- Debug.Assert(!parent.IsRed, "parent must be a black node!");
- if (parent.Right == sibling)
- {
- RotateLeft(parent);
- }
- else
- {
- RotateRight(parent);
- }
-
- parent.IsRed = true;
- sibling.IsRed = false; // parent's color
- // sibling becomes child of grandParent or root after rotation. Update link from grandParent or root
- ReplaceChildOfNodeOrRoot(grandParent, parent, sibling);
- // sibling will become grandParent of current node
- grandParent = sibling;
- if (parent == match)
- {
- parentOfMatch = sibling;
- }
-
- // update sibling, this is necessary for following processing
- sibling = (parent.Left == current) ? parent.Right : parent.Left;
- }
- Debug.Assert(sibling != null || sibling.IsRed == false, "sibling must not be null and it must be black!");
-
- if (Is2Node(sibling))
- {
- Merge2Nodes(parent, current, sibling);
- }
- else
- {
- // current is a 2-node and sibling is either a 3-node or a 4-node.
- // We can change the color of current to red by some rotation.
- TreeRotation rotation = RotationNeeded(parent, current, sibling);
- Node newGrandParent = null;
- switch (rotation)
- {
- case TreeRotation.RightRotation:
- Debug.Assert(parent.Left == sibling, "sibling must be left child of parent!");
- Debug.Assert(sibling.Left.IsRed, "Left child of sibling must be red!");
- sibling.Left.IsRed = false;
- newGrandParent = RotateRight(parent);
- break;
- case TreeRotation.LeftRotation:
- Debug.Assert(parent.Right == sibling, "sibling must be left child of parent!");
- Debug.Assert(sibling.Right.IsRed, "Right child of sibling must be red!");
- sibling.Right.IsRed = false;
- newGrandParent = RotateLeft(parent);
- break;
-
- case TreeRotation.RightLeftRotation:
- Debug.Assert(parent.Right == sibling, "sibling must be left child of parent!");
- Debug.Assert(sibling.Left.IsRed, "Left child of sibling must be red!");
- newGrandParent = RotateRightLeft(parent);
- break;
-
- case TreeRotation.LeftRightRotation:
- Debug.Assert(parent.Left == sibling, "sibling must be left child of parent!");
- Debug.Assert(sibling.Right.IsRed, "Right child of sibling must be red!");
- newGrandParent = RotateLeftRight(parent);
- break;
- }
-
- newGrandParent.IsRed = parent.IsRed;
- parent.IsRed = false;
- current.IsRed = true;
- ReplaceChildOfNodeOrRoot(grandParent, parent, newGrandParent);
- if (parent == match)
- {
- parentOfMatch = newGrandParent;
- }
- grandParent = newGrandParent;
- }
- }
- }
-
- // we don't need to compare any more once we found the match
- int order = foundMatch ? -1 : comparer.Compare(item, current.Item);
- if (order == 0)
- {
- // save the matching node
- foundMatch = true;
- match = current;
- parentOfMatch = parent;
- }
-
- grandParent = parent;
- parent = current;
-
- if (order < 0)
- {
- current = current.Left;
- }
- else
- {
- current = current.Right; // continue the search in right sub tree after we find a match
- }
- }
-
- // move successor to the matching node position and replace links
- if (match != null)
- {
- ReplaceNode(match, parentOfMatch, parent, grandParent);
- --count;
- }
-
- if (root != null)
- {
- root.IsRed = false;
- }
- return foundMatch;
- }
-
- public virtual void Clear()
- {
- root = null;
- count = 0;
- ++version;
- }
-
-
- public virtual bool Contains(T item)
- {
-
- return FindNode(item) != null;
- }
-
-
-
-
- public void CopyTo(T[] array) { CopyTo(array, 0, Count); }
-
- public void CopyTo(T[] array, int index) { CopyTo(array, index, Count); }
-
- public void CopyTo(T[] array, int index, int count)
- {
- if (array == null)
- throw new ArgumentNullException("array");
-
- if (index < 0)
- throw new ArgumentOutOfRangeException("index");
-
- if (count < 0)
- throw new ArgumentOutOfRangeException("count");
-
- // will array, starting at arrayIndex, be able to hold elements? Note: not
- // checking arrayIndex >= array.Length (consistency with list of allowing
- // count of 0; subsequent check takes care of the rest)
- if (index > array.Length || count > array.Length - index)
- throw new ArgumentException("count and index");
-
- //upper bound
- count += index;
-
- InOrderTreeWalk(delegate (Node node)
- {
- if (index >= count)
- {
- return false;
- }
- else
- {
- array[index++] = node.Item;
- return true;
- }
- });
- }
-
- void ICollection.CopyTo(Array array, int index)
- {
- // TODO THROWS
-
- //if (array == null)
- //{
- // ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
- //}
- //
- //if (array.Rank != 1)
- //{
- // ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
- //}
- //
- //if (array.GetLowerBound(0) != 0)
- //{
- // ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound);
- //}
- //
- //if (index < 0)
- //{
- // ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.arrayIndex, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
- //}
- //
- //if (array.Length - index < Count)
- //{
- // ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
- //}
-
- T[] tarray = array as T[];
- if (tarray != null)
- {
- CopyTo(tarray, index);
- }
- else
- {
- object[] objects = array as object[];
- //if (objects == null)
- //{
- // ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType);
- //}
-
- try
- {
- InOrderTreeWalk(delegate (Node node) { objects[index++] = node.Item; return true; });
- }
- catch (ArrayTypeMismatchException)
- {
- //ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType);
- // TODO THROW
- }
- }
- }
-
- #endregion
-
- #region IEnumerable members
- public Enumerator GetEnumerator()
- {
- return new Enumerator(this);
- }
-
-
-
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return new Enumerator(this);
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return new Enumerator(this);
- }
- #endregion
-
- #region Tree Specific Operations
-
- private static Node GetSibling(Node node, Node parent)
- {
- if (parent.Left == node)
- {
- return parent.Right;
- }
- return parent.Left;
- }
-
- // After calling InsertionBalance, we need to make sure current and parent up-to-date.
- // It doesn't matter if we keep grandParent and greatGrantParent up-to-date
- // because we won't need to split again in the next node.
- // By the time we need to split again, everything will be correctly set.
- //
- private void InsertionBalance(Node current, ref Node parent, Node grandParent, Node greatGrandParent)
- {
- Debug.Assert(grandParent != null, "Grand parent cannot be null here!");
- bool parentIsOnRight = (grandParent.Right == parent);
- bool currentIsOnRight = (parent.Right == current);
-
- Node newChildOfGreatGrandParent;
- if (parentIsOnRight == currentIsOnRight)
- { // same orientation, single rotation
- newChildOfGreatGrandParent = currentIsOnRight ? RotateLeft(grandParent) : RotateRight(grandParent);
- }
- else
- { // different orientaton, double rotation
- newChildOfGreatGrandParent = currentIsOnRight ? RotateLeftRight(grandParent) : RotateRightLeft(grandParent);
- // current node now becomes the child of greatgrandparent
- parent = greatGrandParent;
- }
- // grand parent will become a child of either parent of current.
- grandParent.IsRed = true;
- newChildOfGreatGrandParent.IsRed = false;
-
- ReplaceChildOfNodeOrRoot(greatGrandParent, grandParent, newChildOfGreatGrandParent);
- }
-
- private static bool Is2Node(Node node)
- {
- Debug.Assert(node != null, "node cannot be null!");
- return IsBlack(node) && IsNullOrBlack(node.Left) && IsNullOrBlack(node.Right);
- }
-
- private static bool Is4Node(Node node)
- {
- return IsRed(node.Left) && IsRed(node.Right);
- }
-
- private static bool IsBlack(Node node)
- {
- return (node != null && !node.IsRed);
- }
-
- private static bool IsNullOrBlack(Node node)
- {
- return (node == null || !node.IsRed);
- }
-
- private static bool IsRed(Node node)
- {
- return (node != null && node.IsRed);
- }
-
- private static void Merge2Nodes(Node parent, Node child1, Node child2)
- {
- Debug.Assert(IsRed(parent), "parent must be be red");
- // combing two 2-nodes into a 4-node
- parent.IsRed = false;
- child1.IsRed = true;
- child2.IsRed = true;
- }
-
- // Replace the child of a parent node.
- // If the parent node is null, replace the root.
- private void ReplaceChildOfNodeOrRoot(Node parent, Node child, Node newChild)
- {
- if (parent != null)
- {
- if (parent.Left == child)
- {
- parent.Left = newChild;
- }
- else
- {
- parent.Right = newChild;
- }
- }
- else
- {
- root = newChild;
- }
- }
-
- // Replace the matching node with its succesor.
- private void ReplaceNode(Node match, Node parentOfMatch, Node succesor, Node parentOfSuccesor)
- {
- if (succesor == match)
- { // this node has no successor, should only happen if right child of matching node is null.
- Debug.Assert(match.Right == null, "Right child must be null!");
- succesor = match.Left;
- }
- else
- {
- Debug.Assert(parentOfSuccesor != null, "parent of successor cannot be null!");
- Debug.Assert(succesor.Left == null, "Left child of succesor must be null!");
- Debug.Assert((succesor.Right == null && succesor.IsRed) || (succesor.Right.IsRed && !succesor.IsRed), "Succesor must be in valid state");
- if (succesor.Right != null)
- {
- succesor.Right.IsRed = false;
- }
-
- if (parentOfSuccesor != match)
- { // detach succesor from its parent and set its right child
- parentOfSuccesor.Left = succesor.Right;
- succesor.Right = match.Right;
- }
-
- succesor.Left = match.Left;
- }
-
- if (succesor != null)
- {
- succesor.IsRed = match.IsRed;
- }
-
- ReplaceChildOfNodeOrRoot(parentOfMatch, match, succesor);
-
- }
-
- internal virtual Node FindNode(T item)
- {
- Node current = root;
- while (current != null)
- {
- int order = comparer.Compare(item, current.Item);
- if (order == 0)
- {
- return current;
- }
- else
- {
- current = (order < 0) ? current.Left : current.Right;
- }
- }
-
- return null;
- }
-
- //used for bithelpers. Note that this implementation is completely different
- //from the Subset's. The two should not be mixed. This indexes as if the tree were an array.
- //http://en.wikipedia.org/wiki/Binary_Tree#Methods_for_storing_binary_trees
- internal virtual int InternalIndexOf(T item)
- {
- Node current = root;
- int count = 0;
- while (current != null)
- {
- int order = comparer.Compare(item, current.Item);
- if (order == 0)
- {
- return count;
- }
- else
- {
- current = (order < 0) ? current.Left : current.Right;
- count = (order < 0) ? (2 * count + 1) : (2 * count + 2);
- }
- }
- return -1;
- }
-
-
-
- internal Node FindRange(T from, T to)
- {
- return FindRange(from, to, true, true);
- }
- internal Node FindRange(T from, T to, bool lowerBoundActive, bool upperBoundActive)
- {
- Node current = root;
- while (current != null)
- {
- if (lowerBoundActive && comparer.Compare(from, current.Item) > 0)
- {
- current = current.Right;
- }
- else
- {
- if (upperBoundActive && comparer.Compare(to, current.Item) < 0)
- {
- current = current.Left;
- }
- else
- {
- return current;
- }
- }
- }
-
- return null;
- }
-
- internal void UpdateVersion()
- {
- ++version;
- }
-
-
- private static Node RotateLeft(Node node)
- {
- Node x = node.Right;
- node.Right = x.Left;
- x.Left = node;
- return x;
- }
-
- private static Node RotateLeftRight(Node node)
- {
- Node child = node.Left;
- Node grandChild = child.Right;
-
- node.Left = grandChild.Right;
- grandChild.Right = node;
- child.Right = grandChild.Left;
- grandChild.Left = child;
- return grandChild;
- }
-
- private static Node RotateRight(Node node)
- {
- Node x = node.Left;
- node.Left = x.Right;
- x.Right = node;
- return x;
- }
-
- private static Node RotateRightLeft(Node node)
- {
- Node child = node.Right;
- Node grandChild = child.Left;
-
- node.Right = grandChild.Left;
- grandChild.Left = node;
- child.Left = grandChild.Right;
- grandChild.Right = child;
- return grandChild;
- }
- ///
- /// Testing counter that can track rotations
- ///
-
-
- private static TreeRotation RotationNeeded(Node parent, Node current, Node sibling)
- {
- Debug.Assert(IsRed(sibling.Left) || IsRed(sibling.Right), "sibling must have at least one red child");
- if (IsRed(sibling.Left))
- {
- if (parent.Left == current)
- {
- return TreeRotation.RightLeftRotation;
- }
- return TreeRotation.RightRotation;
- }
- else
- {
- if (parent.Left == current)
- {
- return TreeRotation.LeftRotation;
- }
- return TreeRotation.LeftRightRotation;
- }
- }
-
- ///
- /// Used for deep equality of SortedSet testing
- ///
- ///
- public static IEqualityComparer> CreateSetComparer()
- {
- return new SortedSetEqualityComparer();
- }
-
- ///
- /// Create a new set comparer for this set, where this set's members' equality is defined by the
- /// memberEqualityComparer. Note that this equality comparer's definition of equality must be the
- /// same as this set's Comparer's definition of equality
- ///
- public static IEqualityComparer> CreateSetComparer(IEqualityComparer memberEqualityComparer)
- {
- return new SortedSetEqualityComparer(memberEqualityComparer);
- }
-
-
- ///
- /// Decides whether these sets are the same, given the comparer. If the EC's are the same, we can
- /// just use SetEquals, but if they aren't then we have to manually check with the given comparer
- ///
- internal static bool SortedSetEquals(SortedSet set1, SortedSet set2, IComparer comparer)
- {
- // handle null cases first
- if (set1 == null)
- {
- return (set2 == null);
- }
- else if (set2 == null)
- {
- // set1 != null
- return false;
- }
-
- if (AreComparersEqual(set1, set2))
- {
- if (set1.Count != set2.Count)
- return false;
-
- return set1.SetEquals(set2);
- }
- else
- {
- bool found = false;
- foreach (T item1 in set1)
- {
- found = false;
- foreach (T item2 in set2)
- {
- if (comparer.Compare(item1, item2) == 0)
- {
- found = true;
- break;
- }
- }
- if (!found)
- return false;
- }
- return true;
- }
-
- }
-
-
- //This is a little frustrating because we can't support more sorted structures
- private static bool AreComparersEqual(SortedSet set1, SortedSet set2)
- {
- return set1.Comparer.Equals(set2.Comparer);
- }
-
-
- private static void Split4Node(Node node)
- {
- node.IsRed = true;
- node.Left.IsRed = false;
- node.Right.IsRed = false;
- }
-
- ///
- /// Copies this to an array. Used for DebugView
- ///
- ///
- internal T[] ToArray()
- {
- T[] newArray = new T[Count];
- CopyTo(newArray);
- return newArray;
- }
-
-
- #endregion
-
- #region ISet Members
-
- ///
- /// Transform this set into its union with the IEnumerable OTHER
- ///Attempts to insert each element and rejects it if it exists.
- /// NOTE: The caller object is important as UnionWith uses the Comparator
- ///associated with THIS to check equality
- /// Throws ArgumentNullException if OTHER is null
- ///
- ///
- public void UnionWith(IEnumerable other)
- {
- if (other == null)
- {
- throw new ArgumentNullException("other");
- }
-
- SortedSet s = other as SortedSet;
- TreeSubSet t = this as TreeSubSet;
-
- if (t != null)
- VersionCheck();
-
- if (s != null && t == null && this.count == 0)
- {
- SortedSet dummy = new SortedSet(s, this.comparer);
- this.root = dummy.root;
- this.count = dummy.count;
- this.version++;
- return;
- }
-
-
- if (s != null && t == null && AreComparersEqual(this, s) && (s.Count > this.Count / 2))
- { //this actually hurts if N is much greater than M the /2 is arbitrary
- //first do a merge sort to an array.
- T[] merged = new T[s.Count + this.Count];
- int c = 0;
- Enumerator mine = this.GetEnumerator();
- Enumerator theirs = s.GetEnumerator();
- bool mineEnded = !mine.MoveNext(), theirsEnded = !theirs.MoveNext();
- while (!mineEnded && !theirsEnded)
- {
- int comp = Comparer.Compare(mine.Current, theirs.Current);
- if (comp < 0)
- {
- merged[c++] = mine.Current;
- mineEnded = !mine.MoveNext();
- }
- else if (comp == 0)
- {
- merged[c++] = theirs.Current;
- mineEnded = !mine.MoveNext();
- theirsEnded = !theirs.MoveNext();
- }
- else
- {
- merged[c++] = theirs.Current;
- theirsEnded = !theirs.MoveNext();
- }
- }
-
- if (!mineEnded || !theirsEnded)
- {
- Enumerator remaining = (mineEnded ? theirs : mine);
- do
- {
- merged[c++] = remaining.Current;
- } while (remaining.MoveNext());
- }
-
- //now merged has all c elements
-
- //safe to gc the root, we have all the elements
- root = null;
-
-
- root = SortedSet.ConstructRootFromSortedArray(merged, 0, c - 1, null);
- count = c;
- version++;
- }
- else
- {
- AddAllElements(other);
- }
- }
-
-
- private static Node ConstructRootFromSortedArray(T[] arr, int startIndex, int endIndex, Node redNode)
- {
-
-
-
- //what does this do?
- //you're given a sorted array... say 1 2 3 4 5 6
- //2 cases:
- // If there are odd # of elements, pick the middle element (in this case 4), and compute
- // its left and right branches
- // If there are even # of elements, pick the left middle element, save the right middle element
- // and call the function on the rest
- // 1 2 3 4 5 6 -> pick 3, save 4 and call the fn on 1,2 and 5,6
- // now add 4 as a red node to the lowest element on the right branch
- // 3 3
- // 1 5 -> 1 5
- // 2 6 2 4 6
- // As we're adding to the leftmost of the right branch, nesting will not hurt the red-black properties
- // Leaf nodes are red if they have no sibling (if there are 2 nodes or if a node trickles
- // down to the bottom
-
-
- //the iterative way to do this ends up wasting more space than it saves in stack frames (at
- //least in what i tried)
- //so we're doing this recursively
- //base cases are described below
- int size = endIndex - startIndex + 1;
- if (size == 0)
- {
- return null;
- }
- Node root = null;
- if (size == 1)
- {
- root = new Node(arr[startIndex], false);
- if (redNode != null)
- {
- root.Left = redNode;
- }
- }
- else if (size == 2)
- {
- root = new Node(arr[startIndex], false);
- root.Right = new Node(arr[endIndex], false);
- root.Right.IsRed = true;
- if (redNode != null)
- {
- root.Left = redNode;
- }
- }
- else if (size == 3)
- {
- root = new Node(arr[startIndex + 1], false);
- root.Left = new Node(arr[startIndex], false);
- root.Right = new Node(arr[endIndex], false);
- if (redNode != null)
- {
- root.Left.Left = redNode;
-
- }
- }
- else
- {
- int midpt = ((startIndex + endIndex) / 2);
- root = new Node(arr[midpt], false);
- root.Left = ConstructRootFromSortedArray(arr, startIndex, midpt - 1, redNode);
- if (size % 2 == 0)
- {
- root.Right = ConstructRootFromSortedArray(arr, midpt + 2, endIndex, new Node(arr[midpt + 1], true));
- }
- else
- {
- root.Right = ConstructRootFromSortedArray(arr, midpt + 1, endIndex, null);
- }
- }
- return root;
-
- }
-
-
- ///
- /// Transform this set into its intersection with the IEnumerable OTHER
- /// NOTE: The caller object is important as IntersectionWith uses the
- /// comparator associated with THIS to check equality
- /// Throws ArgumentNullException if OTHER is null
- ///
- ///
- public virtual void IntersectWith(IEnumerable other)
- {
- if (other == null)
- {
- throw new ArgumentNullException("other");
- }
-
- if (Count == 0)
- return;
-
- //HashSet optimizations can't be done until equality comparers and comparers are related
-
- //Technically, this would work as well with an ISorted
- SortedSet s = other as SortedSet;
- TreeSubSet t = this as TreeSubSet;
- if (t != null)
- VersionCheck();
- //only let this happen if i am also a SortedSet, not a SubSet
- if (s != null && t == null && AreComparersEqual(this, s))
- {
-
-
- //first do a merge sort to an array.
- T[] merged = new T[this.Count];
- int c = 0;
- Enumerator mine = this.GetEnumerator();
- Enumerator theirs = s.GetEnumerator();
- bool mineEnded = !mine.MoveNext(), theirsEnded = !theirs.MoveNext();
- T max = Max;
- T min = Min;
-
- while (!mineEnded && !theirsEnded && Comparer.Compare(theirs.Current, max) <= 0)
- {
- int comp = Comparer.Compare(mine.Current, theirs.Current);
- if (comp < 0)
- {
- mineEnded = !mine.MoveNext();
- }
- else if (comp == 0)
- {
- merged[c++] = theirs.Current;
- mineEnded = !mine.MoveNext();
- theirsEnded = !theirs.MoveNext();
- }
- else
- {
- theirsEnded = !theirs.MoveNext();
- }
- }
-
- //now merged has all c elements
-
- //safe to gc the root, we have all the elements
- root = null;
-
- root = SortedSet.ConstructRootFromSortedArray(merged, 0, c - 1, null);
- count = c;
- version++;
- }
- else
- {
- IntersectWithEnumerable(other);
- }
- }
-
- internal virtual void IntersectWithEnumerable(IEnumerable other)
- {
- //
- List toSave = new List(this.Count);
- foreach (T item in other)
- {
- if (this.Contains(item))
- {
- toSave.Add(item);
- this.Remove(item);
- }
- }
- this.Clear();
- AddAllElements(toSave);
-
- }
-
-
-
- ///
- /// Transform this set into its complement with the IEnumerable OTHER
- /// NOTE: The caller object is important as ExceptWith uses the
- /// comparator associated with THIS to check equality
- /// Throws ArgumentNullException if OTHER is null
- ///
- ///
- public void ExceptWith(IEnumerable other)
- {
- if (other == null)
- {
- throw new ArgumentNullException("other");
- }
-
- if (count == 0)
- return;
-
- if (other == this)
- {
- this.Clear();
- return;
- }
-
- SortedSet asSorted = other as SortedSet;
-
- if (asSorted != null && AreComparersEqual(this, asSorted))
- {
- //outside range, no point doing anything
- if (!(comparer.Compare(asSorted.Max, this.Min) < 0 || comparer.Compare(asSorted.Min, this.Max) > 0))
- {
- T min = this.Min;
- T max = this.Max;
- foreach (T item in other)
- {
- if (comparer.Compare(item, min) < 0)
- continue;
- if (comparer.Compare(item, max) > 0)
- break;
- Remove(item);
- }
- }
-
- }
- else
- {
- RemoveAllElements(other);
- }
- }
-
- ///
- /// Transform this set so it contains elements in THIS or OTHER but not both
- /// NOTE: The caller object is important as SymmetricExceptWith uses the
- /// comparator associated with THIS to check equality
- /// Throws ArgumentNullException if OTHER is null
- ///
- ///
- public void SymmetricExceptWith(IEnumerable other)
- {
- if (other == null)
- {
- throw new ArgumentNullException("other");
- }
-
- if (this.Count == 0)
- {
- this.UnionWith(other);
- return;
- }
-
- if (other == this)
- {
- this.Clear();
- return;
- }
-
-
- SortedSet asSorted = other as SortedSet;
-
-#if USING_HASH_SET
- HashSet asHash = other as HashSet;
-#endif
- if (asSorted != null && AreComparersEqual(this, asSorted))
- {
- SymmetricExceptWithSameEC(asSorted);
- }
-#if USING_HASH_SET
- else if (asHash != null && this.comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) {
- SymmetricExceptWithSameEC(asHash);
- }
-#endif
- else
- {
- //need perf improvement on this
- T[] elements = (new List(other)).ToArray();
- Array.Sort(elements, this.Comparer);
- SymmetricExceptWithSameEC(elements);
- }
- }
-
- //OTHER must be a set
- internal void SymmetricExceptWithSameEC(ISet other)
- {
- foreach (T item in other)
- {
- //yes, it is classier to say
- //if (!this.Remove(item))this.Add(item);
- //but this ends up saving on rotations
- if (this.Contains(item))
- {
- this.Remove(item);
- }
- else
- {
- this.Add(item);
- }
- }
- }
-
- //OTHER must be a sorted array
- internal void SymmetricExceptWithSameEC(T[] other)
- {
- if (other.Length == 0)
- {
- return;
- }
- T last = other[0];
- for (int i = 0; i < other.Length; i++)
- {
- while (i < other.Length && i != 0 && comparer.Compare(other[i], last) == 0)
- i++;
- if (i >= other.Length)
- break;
- if (this.Contains(other[i]))
- {
- this.Remove(other[i]);
- }
- else
- {
- this.Add(other[i]);
- }
- last = other[i];
- }
- }
-
-
- ///
- /// Checks whether this Tree is a subset of the IEnumerable other
- ///
- ///
- ///
- [System.Security.SecuritySafeCritical]
- public bool IsSubsetOf(IEnumerable other)
- {
- if (other == null)
- {
- throw new ArgumentNullException("other");
- }
-
- if (Count == 0)
- return true;
-
-
- SortedSet asSorted = other as SortedSet;
- if (asSorted != null && AreComparersEqual(this, asSorted))
- {
- if (this.Count > asSorted.Count)
- return false;
- return IsSubsetOfSortedSetWithSameEC(asSorted);
- }
- else
- {
- //worst case: mark every element in my set and see if i've counted all
- //O(MlogN)
-
- ElementCount result = CheckUniqueAndUnfoundElements(other, false);
- return (result.uniqueCount == Count && result.unfoundCount >= 0);
- }
- }
-
- private bool IsSubsetOfSortedSetWithSameEC(SortedSet asSorted)
- {
- SortedSet prunedOther = asSorted.GetViewBetween(this.Min, this.Max);
- foreach (T item in this)
- {
- if (!prunedOther.Contains(item))
- return false;
- }
- return true;
-
- }
-
-
- ///
- /// Checks whether this Tree is a proper subset of the IEnumerable other
- ///
- ///
- ///
- [System.Security.SecuritySafeCritical]
- public bool IsProperSubsetOf(IEnumerable other)
- {
- if (other == null)
- {
- throw new ArgumentNullException("other");
- }
-
- if ((other as ICollection) != null)
- {
- if (Count == 0)
- return (other as ICollection).Count > 0;
- }
-
-
-#if USING_HASH_SET
- //do it one way for HashSets
- HashSet asHash = other as HashSet;
- if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) {
- return asHash.IsProperSupersetOf(this);
- }
-#endif
- //another for sorted sets with the same comparer
- SortedSet asSorted = other as SortedSet;
- if (asSorted != null && AreComparersEqual(this, asSorted))
- {
- if (this.Count >= asSorted.Count)
- return false;
- return IsSubsetOfSortedSetWithSameEC(asSorted);
- }
-
-
- //worst case: mark every element in my set and see if i've counted all
- //O(MlogN).
- ElementCount result = CheckUniqueAndUnfoundElements(other, false);
- return (result.uniqueCount == Count && result.unfoundCount > 0);
- }
-
-
- ///
- /// Checks whether this Tree is a super set of the IEnumerable other
- ///
- ///
- ///
- public bool IsSupersetOf(IEnumerable other)
- {
- if (other == null)
- {
- throw new ArgumentNullException("other");
- }
-
- if ((other as ICollection) != null && (other as ICollection).Count == 0)
- return true;
-
- //do it one way for HashSets
-#if USING_HASH_SET
- HashSet asHash = other as HashSet;
- if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) {
- return asHash.IsSubsetOf(this);
- }
-#endif
- //another for sorted sets with the same comparer
- SortedSet asSorted = other as SortedSet;
- if (asSorted != null && AreComparersEqual(this, asSorted))
- {
- if (this.Count < asSorted.Count)
- return false;
- SortedSet pruned = GetViewBetween(asSorted.Min, asSorted.Max);
- foreach (T item in asSorted)
- {
- if (!pruned.Contains(item))
- return false;
- }
- return true;
- }
- //and a third for everything else
- return ContainsAllElements(other);
- }
-
- ///
- /// Checks whether this Tree is a proper super set of the IEnumerable other
- ///
- ///
- ///
- [System.Security.SecuritySafeCritical]
- public bool IsProperSupersetOf(IEnumerable other)
- {
- if (other == null)
- {
- throw new ArgumentNullException("other");
- }
-
- if (Count == 0)
- return false;
-
- if ((other as ICollection) != null && (other as ICollection).Count == 0)
- return true;
-
-#if USING_HASH_SET
- //do it one way for HashSets
-
- HashSet asHash = other as HashSet;
- if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) {
- return asHash.IsProperSubsetOf(this);
- }
-#endif
- //another way for sorted sets
- SortedSet asSorted = other as SortedSet;
- if (asSorted != null && AreComparersEqual(asSorted, this))
- {
- if (asSorted.Count >= this.Count)
- return false;
- SortedSet pruned = GetViewBetween(asSorted.Min, asSorted.Max);
- foreach (T item in asSorted)
- {
- if (!pruned.Contains(item))
- return false;
- }
- return true;
- }
-
-
- //worst case: mark every element in my set and see if i've counted all
- //O(MlogN)
- //slight optimization, put it into a HashSet and then check can do it in O(N+M)
- //but slower in better cases + wastes space
- ElementCount result = CheckUniqueAndUnfoundElements(other, true);
- return (result.uniqueCount < Count && result.unfoundCount == 0);
- }
-
-
-
- ///
- /// Checks whether this Tree has all elements in common with IEnumerable other
- ///
- ///
- ///
- [System.Security.SecuritySafeCritical]
- public bool SetEquals(IEnumerable other)
- {
- if (other == null)
- {
- throw new ArgumentNullException("other");
- }
-
-#if USING_HASH_SET
- HashSet asHash = other as HashSet;
- if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) {
- return asHash.SetEquals(this);
- }
-#endif
- SortedSet asSorted = other as SortedSet;
- if (asSorted != null && AreComparersEqual(this, asSorted))
- {
- IEnumerator mine = this.GetEnumerator();
- IEnumerator theirs = asSorted.GetEnumerator();
- bool mineEnded = !mine.MoveNext();
- bool theirsEnded = !theirs.MoveNext();
- while (!mineEnded && !theirsEnded)
- {
- if (Comparer.Compare(mine.Current, theirs.Current) != 0)
- {
- return false;
- }
- mineEnded = !mine.MoveNext();
- theirsEnded = !theirs.MoveNext();
- }
- return mineEnded && theirsEnded;
- }
-
- //worst case: mark every element in my set and see if i've counted all
- //O(N) by size of other
- ElementCount result = CheckUniqueAndUnfoundElements(other, true);
- return (result.uniqueCount == Count && result.unfoundCount == 0);
- }
-
-
-
- ///
- /// Checks whether this Tree has any elements in common with IEnumerable other
- ///
- ///
- ///
- public bool Overlaps(IEnumerable other)
- {
- if (other == null)
- {
- throw new ArgumentNullException("other");
- }
-
- if (this.Count == 0)
- return false;
-
- if ((other as ICollection != null) && (other as ICollection).Count == 0)
- return false;
-
- SortedSet asSorted = other as SortedSet;
- if (asSorted != null && AreComparersEqual(this, asSorted) && (comparer.Compare(Min, asSorted.Max) > 0 || comparer.Compare(Max, asSorted.Min) < 0))
- {
- return false;
- }
-#if USING_HASH_SET
- HashSet asHash = other as HashSet;
- if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) {
- return asHash.Overlaps(this);
- }
-#endif
- foreach (T item in other)
- {
- if (this.Contains(item))
- {
- return true;
- }
- }
- return false;
- }
-
- ///
- /// This works similar to HashSet's CheckUniqueAndUnfound (description below), except that the bit
- /// array maps differently than in the HashSet. We can only use this for the bulk boolean checks.
- ///
- /// Determines counts that can be used to determine equality, subset, and superset. This
- /// is only used when other is an IEnumerable and not a HashSet. If other is a HashSet
- /// these properties can be checked faster without use of marking because we can assume
- /// other has no duplicates.
- ///
- /// The following count checks are performed by callers:
- /// 1. Equals: checks if unfoundCount = 0 and uniqueFoundCount = Count; i.e. everything
- /// in other is in this and everything in this is in other
- /// 2. Subset: checks if unfoundCount >= 0 and uniqueFoundCount = Count; i.e. other may
- /// have elements not in this and everything in this is in other
- /// 3. Proper subset: checks if unfoundCount > 0 and uniqueFoundCount = Count; i.e
- /// other must have at least one element not in this and everything in this is in other
- /// 4. Proper superset: checks if unfound count = 0 and uniqueFoundCount strictly less
- /// than Count; i.e. everything in other was in this and this had at least one element
- /// not contained in other.
- ///
- /// An earlier implementation used delegates to perform these checks rather than returning
- /// an ElementCount struct; however this was changed due to the perf overhead of delegates.
- ///
- ///
- /// Allows us to finish faster for equals and proper superset
- /// because unfoundCount must be 0.
- ///
- //
- //
- //
- //
- //
- //
- [System.Security.SecurityCritical]
- private unsafe ElementCount CheckUniqueAndUnfoundElements(IEnumerable other, bool returnIfUnfound)
- {
- ElementCount result;
-
- // need special case in case this has no elements.
- if (Count == 0)
- {
- int numElementsInOther = 0;
- foreach (T item in other)
- {
- numElementsInOther++;
- // break right away, all we want to know is whether other has 0 or 1 elements
- break;
- }
- result.uniqueCount = 0;
- result.unfoundCount = numElementsInOther;
- return result;
- }
-
-
- int originalLastIndex = Count;
- int intArrayLength = BitHelper.ToIntArrayLength(originalLastIndex);
-
- BitHelper bitHelper;
- if (intArrayLength <= StackAllocThreshold)
- {
- int* bitArrayPtr = stackalloc int[intArrayLength];
- bitHelper = new BitHelper(bitArrayPtr, intArrayLength);
- }
- else
- {
- int[] bitArray = new int[intArrayLength];
- bitHelper = new BitHelper(bitArray, intArrayLength);
- }
-
- // count of items in other not found in this
- int unfoundCount = 0;
- // count of unique items in other found in this
- int uniqueFoundCount = 0;
-
- foreach (T item in other)
- {
- int index = InternalIndexOf(item);
- if (index >= 0)
- {
- if (!bitHelper.IsMarked(index))
- {
- // item hasn't been seen yet
- bitHelper.MarkBit(index);
- uniqueFoundCount++;
- }
- }
- else
- {
- unfoundCount++;
- if (returnIfUnfound)
- {
- break;
- }
- }
- }
-
- result.uniqueCount = uniqueFoundCount;
- result.unfoundCount = unfoundCount;
- return result;
- }
- public int RemoveWhere(Predicate match)
- {
- if (match == null)
- {
- throw new ArgumentNullException("match");
- }
- List matches = new List(this.Count);
-
- BreadthFirstTreeWalk(delegate (Node n)
- {
- if (match(n.Item))
- {
- matches.Add(n.Item);
- }
- return true;
- });
- // reverse breadth first to (try to) incur low cost
- int actuallyRemoved = 0;
- for (int i = matches.Count - 1; i >= 0; i--)
- {
- if (this.Remove(matches[i]))
- {
- actuallyRemoved++;
- }
- }
-
- return actuallyRemoved;
-
- }
-
-
- #endregion
-
- #region ISorted Members
-
-
- public T Min
- {
- get
- {
- T ret = default(T);
- InOrderTreeWalk(delegate (SortedSet.Node n) { ret = n.Item; return false; });
- return ret;
- }
- }
-
- public T Max
- {
- get
- {
- T ret = default(T);
- InOrderTreeWalk(delegate (SortedSet.Node n) { ret = n.Item; return false; }, true);
- return ret;
- }
- }
-
- public IEnumerable Reverse()
- {
- Enumerator e = new Enumerator(this, true);
- while (e.MoveNext())
- {
- yield return e.Current;
- }
- }
-
-
- ///
- /// Returns a subset of this tree ranging from values lBound to uBound
- /// Any changes made to the subset reflect in the actual tree
- ///
- /// Lowest Value allowed in the subset
- /// Highest Value allowed in the subset
- public virtual SortedSet GetViewBetween(T lowerValue, T upperValue)
- {
- if (Comparer.Compare(lowerValue, upperValue) > 0)
- {
- throw new ArgumentException("lowerBound is greater than upperBound");
- }
- return new TreeSubSet(this, lowerValue, upperValue, true, true);
- }
-
-#if DEBUG
-
- ///
- /// debug status to be checked whenever any operation is called
- ///
- ///
- internal virtual bool versionUpToDate() {
- return true;
- }
-#endif
-
-
- ///
- /// This class represents a subset view into the tree. Any changes to this view
- /// are reflected in the actual tree. Uses the Comparator of the underlying tree.
- ///
- ///
-#if !FEATURE_NETCORE
- [Serializable]
- internal sealed class TreeSubSet : SortedSet, ISerializable, IDeserializationCallback
- {
-#else
- internal sealed class TreeSubSet : SortedSet {
-#endif
- SortedSet underlying;
- T min, max;
- //these exist for unbounded collections
- //for instance, you could allow this subset to be defined for i>10. The set will throw if
- //anything <=10 is added, but there is no upperbound. These features Head(), Tail(), were punted
- //in the spec, and are not available, but the framework is there to make them available at some point.
- bool lBoundActive, uBoundActive;
- //used to see if the count is out of date
-
-
-#if DEBUG
- internal override bool versionUpToDate() {
- return (this.version == underlying.version);
- }
-#endif
-
- public TreeSubSet(SortedSet Underlying, T Min, T Max, bool lowerBoundActive, bool upperBoundActive)
- : base(Underlying.Comparer)
- {
- underlying = Underlying;
- min = Min;
- max = Max;
- lBoundActive = lowerBoundActive;
- uBoundActive = upperBoundActive;
- root = underlying.FindRange(min, max, lBoundActive, uBoundActive); // root is first element within range
- count = 0;
- version = -1;
- VersionCheckImpl();
- }
-
-#if !FEATURE_NETCORE
- ///
- /// For serialization and deserialization
- ///
- private TreeSubSet()
- {
- comparer = null;
- }
-
-
- [SuppressMessage("Microsoft.Usage", "CA2236:CallBaseClassMethodsOnISerializableTypes", Justification = "special case TreeSubSet serialization")]
- private TreeSubSet(SerializationInfo info, StreamingContext context)
- {
- siInfo = info;
- OnDeserializationImpl(info);
- }
-#endif // !FEATURE_NETCORE
-
- ///
- /// Additions to this tree need to be added to the underlying tree as well
- ///
-
- internal override bool AddIfNotPresent(T item)
- {
-
- if (!IsWithinRange(item))
- {
- throw new ArgumentOutOfRangeException("collection");
- }
-
- bool ret = underlying.AddIfNotPresent(item);
- VersionCheck();
-#if DEBUG
- Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max));
-#endif
-
- return ret;
- }
-
-
- public override bool Contains(T item)
- {
- VersionCheck();
-#if DEBUG
- Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max));
-#endif
- return base.Contains(item);
- }
-
- internal override bool DoRemove(T item)
- { // todo: uppercase this and others
-
- if (!IsWithinRange(item))
- {
- return false;
- }
-
- bool ret = underlying.Remove(item);
- VersionCheck();
-#if DEBUG
- Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max));
-#endif
- return ret;
- }
-
- public override void Clear()
- {
-
-
- if (count == 0)
- {
- return;
- }
-
- List toRemove = new List();
- BreadthFirstTreeWalk(delegate (Node n) { toRemove.Add(n.Item); return true; });
- while (toRemove.Count != 0)
- {
- underlying.Remove(toRemove[toRemove.Count - 1]);
- toRemove.RemoveAt(toRemove.Count - 1);
- }
- root = null;
- count = 0;
- version = underlying.version;
- }
-
-
- internal override bool IsWithinRange(T item)
- {
-
- int comp = (lBoundActive ? Comparer.Compare(min, item) : -1);
- if (comp > 0)
- {
- return false;
- }
- comp = (uBoundActive ? Comparer.Compare(max, item) : 1);
- if (comp < 0)
- {
- return false;
- }
- return true;
- }
-
- internal override bool InOrderTreeWalk(TreeWalkPredicate action, Boolean reverse)
- {
- VersionCheck();
-
- if (root == null)
- {
- return true;
- }
-
- // The maximum height of a red-black tree is 2*lg(n+1).
- // See page 264 of "Introduction to algorithms" by Thomas H. Cormen
- Stack stack = new Stack(2 * (int)SortedSet.log2(count + 1)); //this is not exactly right if count is out of date, but the stack can grow
- Node current = root;
- while (current != null)
- {
- if (IsWithinRange(current.Item))
- {
- stack.Push(current);
- current = (reverse ? current.Right : current.Left);
- }
- else if (lBoundActive && Comparer.Compare(min, current.Item) > 0)
- {
- current = current.Right;
- }
- else
- {
- current = current.Left;
- }
- }
-
- while (stack.Count != 0)
- {
- current = stack.Pop();
- if (!action(current))
- {
- return false;
- }
-
- Node node = (reverse ? current.Left : current.Right);
- while (node != null)
- {
- if (IsWithinRange(node.Item))
- {
- stack.Push(node);
- node = (reverse ? node.Right : node.Left);
- }
- else if (lBoundActive && Comparer.Compare(min, node.Item) > 0)
- {
- node = node.Right;
- }
- else
- {
- node = node.Left;
- }
- }
- }
- return true;
- }
-
- internal override bool BreadthFirstTreeWalk(TreeWalkPredicate action)
- {
- VersionCheck();
-
- if (root == null)
- {
- return true;
- }
-
- List processQueue = new List();
- processQueue.Add(root);
- Node current;
-
- while (processQueue.Count != 0)
- {
- current = processQueue[0];
- processQueue.RemoveAt(0);
- if (IsWithinRange(current.Item) && !action(current))
- {
- return false;
- }
- if (current.Left != null && (!lBoundActive || Comparer.Compare(min, current.Item) < 0))
- {
- processQueue.Add(current.Left);
- }
- if (current.Right != null && (!uBoundActive || Comparer.Compare(max, current.Item) > 0))
- {
- processQueue.Add(current.Right);
- }
-
- }
- return true;
- }
-
- internal override SortedSet.Node FindNode(T item)
- {
-
- if (!IsWithinRange(item))
- {
- return null;
- }
- VersionCheck();
-#if DEBUG
- Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max));
-#endif
- return base.FindNode(item);
- }
-
- //this does indexing in an inefficient way compared to the actual sortedset, but it saves a
- //lot of space
- internal override int InternalIndexOf(T item)
- {
- int count = -1;
- foreach (T i in this)
- {
- count++;
- if (Comparer.Compare(item, i) == 0)
- return count;
- }
-#if DEBUG
- Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max));
-#endif
- return -1;
- }
- ///
- /// checks whether this subset is out of date. updates if necessary.
- ///
- internal override void VersionCheck()
- {
- VersionCheckImpl();
- }
-
- private void VersionCheckImpl()
- {
- Debug.Assert(underlying != null, "Underlying set no longer exists");
- if (this.version != underlying.version)
- {
- this.root = underlying.FindRange(min, max, lBoundActive, uBoundActive);
- this.version = underlying.version;
- count = 0;
- InOrderTreeWalk(delegate (Node n) { count++; return true; });
- }
- }
-
-
-
- //This passes functionality down to the underlying tree, clipping edges if necessary
- //There's nothing gained by having a nested subset. May as well draw it from the base
- //Cannot increase the bounds of the subset, can only decrease it
- public override SortedSet GetViewBetween(T lowerValue, T upperValue)
- {
-
- if (lBoundActive && Comparer.Compare(min, lowerValue) > 0)
- {
- //lBound = min;
- throw new ArgumentOutOfRangeException("lowerValue");
- }
- if (uBoundActive && Comparer.Compare(max, upperValue) < 0)
- {
- //uBound = max;
- throw new ArgumentOutOfRangeException("upperValue");
- }
- TreeSubSet ret = (TreeSubSet)underlying.GetViewBetween(lowerValue, upperValue);
- return ret;
- }
-
- internal override void IntersectWithEnumerable(IEnumerable other)
- {
-
- List toSave = new List(this.Count);
- foreach (T item in other)
- {
- if (this.Contains(item))
- {
- toSave.Add(item);
- this.Remove(item);
- }
- }
- this.Clear();
- this.AddAllElements(toSave);
-#if DEBUG
- Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max));
-#endif
- }
-
-#if !FEATURE_NETCORE
- void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
- {
- GetObjectData(info, context);
- }
-
- protected override void GetObjectData(SerializationInfo info, StreamingContext context)
- {
- if (info == null)
- {
- throw new ArgumentNullException("info");
- }
- info.AddValue(maxName, max, typeof(T));
- info.AddValue(minName, min, typeof(T));
- info.AddValue(lBoundActiveName, lBoundActive);
- info.AddValue(uBoundActiveName, uBoundActive);
- base.GetObjectData(info, context);
- }
-
- void IDeserializationCallback.OnDeserialization(Object sender)
- {
- //don't do anything here as its already been done by the constructor
- //OnDeserialization(sender);
-
- }
-
- protected override void OnDeserialization(Object sender)
- {
- OnDeserializationImpl(sender);
- }
-
- private void OnDeserializationImpl(Object sender)
- {
- if (siInfo == null)
- {
- throw new SerializationException("Invalid deserialization");
- }
-
- comparer = (IComparer)siInfo.GetValue(ComparerName, typeof(IComparer));
- int savedCount = siInfo.GetInt32(CountName);
- max = (T)siInfo.GetValue(maxName, typeof(T));
- min = (T)siInfo.GetValue(minName, typeof(T));
- lBoundActive = siInfo.GetBoolean(lBoundActiveName);
- uBoundActive = siInfo.GetBoolean(uBoundActiveName);
- underlying = new SortedSet();
-
- if (savedCount != 0)
- {
- T[] items = (T[])siInfo.GetValue(ItemsName, typeof(T[]));
-
- if (items == null)
- {
- throw new SerializationException("Missing values");
- }
-
- for (int i = 0; i < items.Length; i++)
- {
- underlying.Add(items[i]);
- }
- }
- underlying.version = siInfo.GetInt32(VersionName);
- count = underlying.count;
- version = underlying.version - 1;
- VersionCheck(); //this should update the count to be right and update root to be right
-
- if (count != savedCount)
- {
- throw new SerializationException("Mismatched count");
- }
- siInfo = null;
-
- }
-#endif // !FEATURE_NETCORE
- }
-
-
- #endregion
-
- #region Serialization methods
-
-#if !FEATURE_NETCORE
- // LinkDemand here is unnecessary as this is a methodimpl and linkdemand from the interface should suffice
- void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
- {
- GetObjectData(info, context);
- }
-
- protected virtual void GetObjectData(SerializationInfo info, StreamingContext context)
- {
- if (info == null)
- {
- throw new ArgumentException("info");
- }
-
- info.AddValue(CountName, count); //This is the length of the bucket array.
- info.AddValue(ComparerName, comparer, typeof(IComparer));
- info.AddValue(VersionName, version);
-
- if (root != null)
- {
- T[] items = new T[Count];
- CopyTo(items, 0);
- info.AddValue(ItemsName, items, typeof(T[]));
- }
- }
-
- void IDeserializationCallback.OnDeserialization(Object sender)
- {
- OnDeserialization(sender);
- }
-
- protected virtual void OnDeserialization(Object sender)
- {
- if (comparer != null)
- {
- return; //Somebody had a dependency on this class and fixed us up before the ObjectManager got to it.
- }
-
- if (siInfo == null)
- {
- throw new SerializationException("invalid on deserialize");
- }
-
- comparer = (IComparer)siInfo.GetValue(ComparerName, typeof(IComparer));
- int savedCount = siInfo.GetInt32(CountName);
-
- if (savedCount != 0)
- {
- T[] items = (T[])siInfo.GetValue(ItemsName, typeof(T[]));
-
- if (items == null)
- {
- throw new SerializationException("Missing values");
-
- }
-
- for (int i = 0; i < items.Length; i++)
- {
- Add(items[i]);
- }
- }
-
- version = siInfo.GetInt32(VersionName);
- if (count != savedCount)
- {
- throw new SerializationException("Mismatched count");
- }
- siInfo = null;
- }
-#endif //!FEATURE_NETCORE
- #endregion
-
- #region Helper Classes
- internal class Node
- {
- public bool IsRed;
- public T Item;
- public Node Left;
- public Node Right;
-
- public Node(T item)
- {
- // The default color will be red, we never need to create a black node directly.
- this.Item = item;
- IsRed = true;
- }
-
- public Node(T item, bool isRed)
- {
- // The default color will be red, we never need to create a black node directly.
- this.Item = item;
- this.IsRed = isRed;
- }
- }
-
- [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "not an expected scenario")]
-#if !FEATURE_NETCORE
- [Serializable]
- public struct Enumerator : IEnumerator, IEnumerator, ISerializable, IDeserializationCallback
- {
-#else
- public struct Enumerator : IEnumerator, IEnumerator {
-#endif
- private SortedSet tree;
- private int version;
-
-
- private Stack.Node> stack;
- private SortedSet.Node current;
- static SortedSet.Node dummyNode = new SortedSet.Node(default(T));
-
- private bool reverse;
-
-#if !FEATURE_NETCORE
- private SerializationInfo siInfo;
-#endif
- internal Enumerator(SortedSet set)
- {
- tree = set;
- //this is a hack to make sure that the underlying subset has not been changed since
- //
- tree.VersionCheck();
-
- version = tree.version;
-
- // 2lg(n + 1) is the maximum height
- stack = new Stack.Node>(2 * (int)SortedSet.log2(set.Count + 1));
- current = null;
- reverse = false;
-#if !FEATURE_NETCORE
- siInfo = null;
-#endif
- Intialize();
- }
-
- internal Enumerator(SortedSet set, bool reverse)
- {
- tree = set;
- //this is a hack to make sure that the underlying subset has not been changed since
- //
- tree.VersionCheck();
- version = tree.version;
-
- // 2lg(n + 1) is the maximum height
- stack = new Stack.Node>(2 * (int)SortedSet.log2(set.Count + 1));
- current = null;
- this.reverse = reverse;
-#if !FEATURE_NETCORE
- siInfo = null;
-#endif
- Intialize();
-
- }
-
-#if !FEATURE_NETCORE
- private Enumerator(SerializationInfo info, StreamingContext context)
- {
- tree = null;
- version = -1;
- current = null;
- reverse = false;
- stack = null;
- this.siInfo = info;
- }
-
- void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
- {
- GetObjectData(info, context);
- }
-
- private void GetObjectData(SerializationInfo info, StreamingContext context)
- {
- if (info == null)
- {
- throw new ArgumentNullException("info");
-
- }
- info.AddValue(TreeName, tree, typeof(SortedSet));
- info.AddValue(EnumVersionName, version);
- info.AddValue(ReverseName, reverse);
- info.AddValue(EnumStartName, !NotStartedOrEnded);
- info.AddValue(NodeValueName, (current == null ? dummyNode.Item : current.Item), typeof(T));
- }
-
- void IDeserializationCallback.OnDeserialization(Object sender)
- {
- OnDeserialization(sender);
- }
-
- private void OnDeserialization(Object sender)
- {
- if (siInfo == null)
- {
- throw new SerializationException("invalid on deserialize");
- }
-
- tree = (SortedSet)siInfo.GetValue(TreeName, typeof(SortedSet));
- version = siInfo.GetInt32(EnumVersionName);
- reverse = siInfo.GetBoolean(ReverseName);
- bool EnumStarted = siInfo.GetBoolean(EnumStartName);
- stack = new Stack.Node>(2 * (int)SortedSet.log2(tree.Count + 1));
- current = null;
- if (EnumStarted)
- {
- T item = (T)siInfo.GetValue(NodeValueName, typeof(T));
- Intialize();
- //go until it reaches the value we want
- while (this.MoveNext())
- {
- if (tree.Comparer.Compare(this.Current, item) == 0)
- break;
- }
- }
-
-
- }
-#endif //!FEATURE_NETCORE
-
-
- private void Intialize()
- {
-
- current = null;
- SortedSet.Node node = tree.root;
- Node next = null, other = null;
- while (node != null)
- {
- next = (reverse ? node.Right : node.Left);
- other = (reverse ? node.Left : node.Right);
- if (tree.IsWithinRange(node.Item))
- {
- stack.Push(node);
- node = next;
- }
- else if (next == null || !tree.IsWithinRange(next.Item))
- {
- node = other;
- }
- else
- {
- node = next;
- }
- }
- }
-
- public bool MoveNext()
- {
-
- //this is a hack to make sure that the underlying subset has not been changed since
- //
- tree.VersionCheck();
-
- if (version != tree.version)
- {
- throw new Exception("Collection has been modified during enumeration");
- }
-
- if (stack.Count == 0)
- {
- current = null;
- return false;
- }
-
- current = stack.Pop();
- SortedSet.Node node = (reverse ? current.Left : current.Right);
- Node next = null, other = null;
- while (node != null)
- {
- next = (reverse ? node.Right : node.Left);
- other = (reverse ? node.Left : node.Right);
- if (tree.IsWithinRange(node.Item))
- {
- stack.Push(node);
- node = next;
- }
- else if (other == null || !tree.IsWithinRange(other.Item))
- {
- node = next;
- }
- else
- {
- node = other;
- }
- }
- return true;
- }
-
- public void Dispose()
- {
- }
-
- public T Current
- {
- get
- {
- if (current != null)
- {
- return current.Item;
- }
- return default(T);
- }
- }
-
- object IEnumerator.Current
- {
- get
- {
- if (current == null)
- {
- //ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
- throw new InvalidOperationException("Current is null");
- }
-
- return current.Item;
- }
- }
-
- internal bool NotStartedOrEnded
- {
- get
- {
- return current == null;
- }
- }
-
- internal void Reset()
- {
- if (version != tree.version)
- {
- throw new Exception("Collection has been modified during enumeration");
- }
-
- stack.Clear();
- Intialize();
- }
-
- void IEnumerator.Reset()
- {
- Reset();
- }
-
-
-
-
- }
-
-
-
- internal struct ElementCount
- {
- internal int uniqueCount;
- internal int unfoundCount;
- }
- #endregion
-
- #region misc
-
- ///
- /// Searches the set for a given value and returns the equal value it finds, if any.
- ///
- /// The value to search for.
- /// The value from the set that the search found, or the default value of when the search yielded no match.
- /// A value indicating whether the search was successful.
- ///
- /// This can be useful when you want to reuse a previously stored reference instead of
- /// a newly constructed one (so that more sharing of references can occur) or to look up
- /// a value that has more complete data than the value you currently have, although their
- /// comparer functions indicate they are equal.
- ///
- public bool TryGetValue(T equalValue, out T actualValue)
- {
- Node node = FindNode(equalValue);
- if (node != null)
- {
- actualValue = node.Item;
- return true;
- }
- actualValue = default(T);
- return false;
- }
-
- // used for set checking operations (using enumerables) that rely on counting
- private static int log2(int value)
- {
- //Contract.Requires(value>0)
- int c = 0;
- while (value > 0)
- {
- c++;
- value >>= 1;
- }
- return c;
- }
- #endregion
-
-
- }
-
- ///
- /// A class that generates an IEqualityComparer for this SortedSet. Requires that the definition of
- /// equality defined by the IComparer for this SortedSet be consistent with the default IEqualityComparer
- /// for the type T. If not, such an IEqualityComparer should be provided through the constructor.
- ///
- internal class SortedSetEqualityComparer : IEqualityComparer>
- {
- private IComparer comparer;
- private IEqualityComparer e_comparer;
-
- public SortedSetEqualityComparer() : this(null, null) { }
-
- public SortedSetEqualityComparer(IComparer comparer) : this(comparer, null) { }
-
- public SortedSetEqualityComparer(IEqualityComparer memberEqualityComparer) : this(null, memberEqualityComparer) { }
-
- ///
- /// Create a new SetEqualityComparer, given a comparer for member order and another for member equality (these
- /// must be consistent in their definition of equality)
- ///
- public SortedSetEqualityComparer(IComparer comparer, IEqualityComparer memberEqualityComparer)
- {
- if (comparer == null)
- this.comparer = Comparer.Default;
- else
- this.comparer = comparer;
- if (memberEqualityComparer == null)
- e_comparer = EqualityComparer.Default;
- else
- e_comparer = memberEqualityComparer;
- }
-
-
- // using comparer to keep equals properties in tact; don't want to choose one of the comparers
- public bool Equals(SortedSet x, SortedSet y)
- {
- return SortedSet.SortedSetEquals(x, y, comparer);
- }
- //IMPORTANT: this part uses the fact that GetHashCode() is consistent with the notion of equality in
- //the set
- public int GetHashCode(SortedSet obj)
- {
- int hashCode = 0;
- if (obj != null)
- {
- foreach (T t in obj)
- {
- hashCode = hashCode ^ (e_comparer.GetHashCode(t) & 0x7FFFFFFF);
- }
- } // else returns hashcode of 0 for null HashSets
- return hashCode;
- }
-
- // Equals method for the comparer itself.
- public override bool Equals(Object obj)
- {
- SortedSetEqualityComparer comparer = obj as SortedSetEqualityComparer;
- if (comparer == null)
- {
- return false;
- }
- return (this.comparer == comparer.comparer);
- }
-
- public override int GetHashCode()
- {
- return comparer.GetHashCode() ^ e_comparer.GetHashCode();
- }
-
-
- }
-
- #endregion
-}
-
-#endif
\ No newline at end of file
diff --git a/src/Core/Utility/SignatureHighlighter.cs b/src/Core/Utility/SignatureHighlighter.cs
index 03d22d3..8ae4c75 100644
--- a/src/Core/Utility/SignatureHighlighter.cs
+++ b/src/Core/Utility/SignatureHighlighter.cs
@@ -44,6 +44,7 @@ namespace UnityExplorer
public static readonly Color StringOrange = new Color(0.83f, 0.61f, 0.52f);
public static readonly Color EnumGreen = new Color(0.57f, 0.76f, 0.43f);
public static readonly Color KeywordBlue = new Color(0.3f, 0.61f, 0.83f);
+ public static readonly string keywordBlueHex = KeywordBlue.ToHex();
public static readonly Color NumberGreen = new Color(0.71f, 0.8f, 0.65f);
internal static string GetClassColor(Type type)
diff --git a/src/UI/CSConsole/CSAutoCompleter.cs b/src/UI/CSConsole/CSAutoCompleter.cs
index 11368f0..dbf0ac7 100644
--- a/src/UI/CSConsole/CSAutoCompleter.cs
+++ b/src/UI/CSConsole/CSAutoCompleter.cs
@@ -3,70 +3,95 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
-using UnityExplorer.UI.CSharpConsole.Lexers;
+using UnityExplorer.UI.CSConsole.Lexers;
using UnityExplorer.UI.Widgets.AutoComplete;
-namespace UnityExplorer.UI.CSharpConsole
+namespace UnityExplorer.UI.CSConsole
{
public class CSAutoCompleter : ISuggestionProvider
{
- public InputFieldRef InputField => CSConsole.Input;
+ public InputFieldRef InputField => ConsoleController.Input;
public bool AnchorToCaretPosition => true;
public void OnSuggestionClicked(Suggestion suggestion)
{
- CSConsole.InsertSuggestionAtCaret(suggestion.UnderlyingValue);
+ ConsoleController.InsertSuggestionAtCaret(suggestion.UnderlyingValue);
+ AutoCompleteModal.Instance.ReleaseOwnership(this);
}
+ // Delimiters for completions, notably does not include '.'
private readonly HashSet delimiters = new HashSet
{
'{', '}', ',', ';', '<', '>', '(', ')', '[', ']', '=', '|', '&', '?'
};
- //private readonly HashSet expressions = new HashSet
- //{
- // "new",
- // "is", "as",
- // "return", "yield", "throw", "in",
- // "do", "for", "foreach",
- // "else",
- //};
+ private readonly List suggestions = new List();
public void CheckAutocompletes()
{
-
if (string.IsNullOrEmpty(InputField.Text))
{
AutoCompleteModal.Instance.ReleaseOwnership(this);
return;
}
- int caret = Math.Max(0, Math.Min(InputField.Text.Length - 1, InputField.Component.caretPosition - 1));
- int i = caret;
+ suggestions.Clear();
- while (i > 0)
+ int caret = Math.Max(0, Math.Min(InputField.Text.Length - 1, InputField.Component.caretPosition - 1));
+ int start = caret;
+
+ // If the character at the caret index is whitespace or delimiter,
+ // or if the next character (if it exists) is not whitespace,
+ // then we don't want to provide suggestions.
+ if (char.IsWhiteSpace(InputField.Text[caret])
+ || delimiters.Contains(InputField.Text[caret])
+ || (InputField.Text.Length > caret + 1 && !char.IsWhiteSpace(InputField.Text[caret + 1])))
{
- i--;
- char c = InputField.Text[i];
+ AutoCompleteModal.Instance.ReleaseOwnership(this);
+ return;
+ }
+
+ // get the current composition string (from caret back to last delimiter or whitespace)
+ while (start > 0)
+ {
+ start--;
+ char c = InputField.Text[start];
if (char.IsWhiteSpace(c) || delimiters.Contains(c))
{
- i++;
+ start++;
break;
}
}
+ string input = InputField.Text.Substring(start, caret - start + 1);
- i = Math.Max(0, i);
+ // Get MCS completions
- string input = InputField.Text.Substring(i, (caret - i + 1));
+ string[] evaluatorCompletions = ConsoleController.Evaluator.GetCompletions(input, out string prefix);
- string[] evaluatorCompletions = CSConsole.Evaluator.GetCompletions(input, out string prefix);
-
- if (evaluatorCompletions != null && evaluatorCompletions.Any())
+ if (!string.IsNullOrEmpty(prefix) && evaluatorCompletions != null && evaluatorCompletions.Any())
{
- var suggestions = from completion in evaluatorCompletions
- select new Suggestion($"{prefix}{completion}", completion);
+ suggestions.AddRange(from completion in evaluatorCompletions
+ select new Suggestion($"{prefix}{completion}", completion));
+ }
+ // Get manual keyword completions
+
+ foreach (var kw in KeywordLexer.keywords)
+ {
+ if (kw.StartsWith(input))
+ {
+ string completion = kw.Substring(input.Length, kw.Length - input.Length);
+
+ suggestions.Add(new Suggestion(
+ $"{input}" +
+ $"{completion}",
+ completion));
+ }
+ }
+
+ if (suggestions.Any())
+ {
AutoCompleteModal.Instance.TakeOwnership(this);
AutoCompleteModal.Instance.SetSuggestions(suggestions);
}
diff --git a/src/UI/CSConsole/CSConsole.cs b/src/UI/CSConsole/CSConsole.cs
deleted file mode 100644
index 9066afa..0000000
--- a/src/UI/CSConsole/CSConsole.cs
+++ /dev/null
@@ -1,506 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text;
-using UnityEngine;
-using UnityEngine.EventSystems;
-using UnityEngine.UI;
-using UnityExplorer.Core.CSharp;
-using UnityExplorer.Core.Input;
-using UnityExplorer.UI.Panels;
-using UnityExplorer.UI.Widgets.AutoComplete;
-
-namespace UnityExplorer.UI.CSharpConsole
-{
- public static class CSConsole
- {
- #region Strings / defaults
-
- internal const string STARTUP_TEXT = @"Welcome to the UnityExplorer C# Console.
-
-The following helper methods are available:
-
-* Log(""message"") logs a message to the debug console
-
-* StartCoroutine(IEnumerator routine) start the IEnumerator as a UnityEngine.Coroutine
-
-* CurrentTarget() returns the target of the active Inspector tab as System.Object
-
-* AllTargets() returns a System.Object[] array containing the targets of all active tabs
-
-* Inspect(someObject) to inspect an instance, eg. Inspect(Camera.main);
-
-* Inspect(typeof(SomeClass)) to inspect a Class with static reflection
-
-* AddUsing(""SomeNamespace"") adds a using directive to the C# console
-
-* GetUsing() logs the current using directives to the debug console
-
-* Reset() resets all using directives and variables
-";
-
- internal static readonly string[] DefaultUsing = new string[]
- {
- "System",
- "System.Linq",
- "System.Collections",
- "System.Collections.Generic",
- "System.Reflection",
- "UnityEngine",
-#if CPP
- "UnhollowerBaseLib",
- "UnhollowerRuntimeLib",
-#endif
- };
-
- #endregion
-
- public static ScriptEvaluator Evaluator;
- public static LexerBuilder Lexer;
- public static CSAutoCompleter Completer;
-
- private static HashSet usingDirectives;
- private static StringBuilder evaluatorOutput;
-
- public static CSConsolePanel Panel => UIManager.CSharpConsole;
- public static InputFieldRef Input => Panel.Input;
-
- public static int LastCaretPosition { get; private set; }
- internal static float defaultInputFieldAlpha;
-
- // Todo save as config?
- public static bool EnableCtrlRShortcut { get; private set; } = true;
- public static bool EnableAutoIndent { get; private set; } = true;
- public static bool EnableSuggestions { get; private set; } = true;
-
- public static void Init()
- {
- try
- {
- ResetConsole(false);
- Evaluator.Compile("0 == 0");
- }
- catch
- {
- ExplorerCore.LogWarning("C# Console probably not supported, todo");
- return;
- }
-
- Lexer = new LexerBuilder();
- Completer = new CSAutoCompleter();
-
- Panel.OnInputChanged += OnConsoleInputChanged;
- Panel.InputScroll.OnScroll += OnInputScrolled;
- Panel.OnCompileClicked += Evaluate;
- Panel.OnResetClicked += ResetConsole;
- Panel.OnAutoIndentToggled += OnToggleAutoIndent;
- Panel.OnCtrlRToggled += OnToggleCtrlRShortcut;
- Panel.OnSuggestionsToggled += OnToggleSuggestions;
-
- }
-
- // Updating and event listeners
-
- private static void OnInputScrolled() => HighlightVisibleInput(Input.Text);
-
- // Invoked at most once per frame
- private static void OnConsoleInputChanged(string value)
- {
- LastCaretPosition = Input.Component.caretPosition;
-
- if (EnableSuggestions)
- Completer.CheckAutocompletes();
-
- HighlightVisibleInput(value);
- }
-
- public static void Update()
- {
- int lastCaretPos = LastCaretPosition;
- UpdateCaret();
- bool caretMoved = lastCaretPos != LastCaretPosition;
-
- if (EnableSuggestions && caretMoved)
- {
- Completer.CheckAutocompletes();
- }
-
- //if (EnableAutoIndent && caretMoved)
- // DoAutoIndent();
-
- if (EnableCtrlRShortcut
- && (InputManager.GetKey(KeyCode.LeftControl) || InputManager.GetKey(KeyCode.RightControl))
- && InputManager.GetKeyDown(KeyCode.R))
- {
- Evaluate(Panel.Input.Text);
- }
- }
-
- private static void UpdateCaret()
- {
- LastCaretPosition = Input.Component.caretPosition;
-
- // todo check if out of bounds, move content if so
- }
-
-
- #region Evaluating
-
- public static void ResetConsole() => ResetConsole(true);
-
- public static void ResetConsole(bool logSuccess = true)
- {
- if (Evaluator != null)
- Evaluator.Dispose();
-
- evaluatorOutput = new StringBuilder();
- Evaluator = new ScriptEvaluator(new StringWriter(evaluatorOutput))
- {
- InteractiveBaseClass = typeof(ScriptInteraction)
- };
-
- usingDirectives = new HashSet();
- foreach (var use in DefaultUsing)
- AddUsing(use);
-
- if (logSuccess)
- ExplorerCore.Log($"C# Console reset. Using directives:\r\n{Evaluator.GetUsing()}");
- }
-
- public static void AddUsing(string assemblyName)
- {
- if (!usingDirectives.Contains(assemblyName))
- {
- Evaluate($"using {assemblyName};", true);
- usingDirectives.Add(assemblyName);
- }
- }
-
- public static void Evaluate()
- {
- Evaluate(Input.Text);
- }
-
- public static void Evaluate(string input, bool supressLog = false)
- {
- try
- {
- Evaluator.Run(input);
-
- string output = ScriptEvaluator._textWriter.ToString();
- var outputSplit = output.Split('\n');
- if (outputSplit.Length >= 2)
- output = outputSplit[outputSplit.Length - 2];
- evaluatorOutput.Clear();
-
- if (ScriptEvaluator._reportPrinter.ErrorsCount > 0)
- throw new FormatException($"Unable to compile the code. Evaluator's last output was:\r\n{output}");
-
- if (!supressLog)
- ExplorerCore.Log("Code executed successfully.");
- }
- catch (FormatException fex)
- {
- if (!supressLog)
- ExplorerCore.LogWarning(fex.Message);
- }
- catch (Exception ex)
- {
- if (!supressLog)
- ExplorerCore.LogWarning(ex);
- }
- }
-
- #endregion
-
-
- #region Lexer Highlighting
-
- private static void HighlightVisibleInput(string value)
- {
- int startIdx = 0;
- int endIdx = value.Length - 1;
- int topLine = 0;
-
- // Calculate visible text if necessary
- if (Input.Rect.rect.height > Panel.InputScroll.ViewportRect.rect.height)
- {
- topLine = -1;
- int bottomLine = -1;
-
- // the top and bottom position of the viewport in relation to the text height
- // they need the half-height adjustment to normalize against the 'line.topY' value.
- var viewportMin = Input.Rect.rect.height - Input.Rect.anchoredPosition.y - (Input.Rect.rect.height * 0.5f);
- var viewportMax = viewportMin - Panel.InputScroll.ViewportRect.rect.height - (Input.Rect.rect.height * 0.5f);
-
- for (int i = 0; i < Input.TextGenerator.lineCount; i++)
- {
- var line = Input.TextGenerator.lines[i];
- // if not set the top line yet, and top of line is below the viewport top
- if (topLine == -1 && line.topY <= viewportMin)
- topLine = i;
- // if bottom of line is below the viewport bottom
- if ((line.topY - line.height) >= viewportMax)
- bottomLine = i;
- }
- // make sure lines are valid
- topLine = Math.Max(0, topLine - 1);
- bottomLine = Math.Min(Input.TextGenerator.lineCount - 1, bottomLine + 1);
-
- startIdx = Input.TextGenerator.lines[topLine].startCharIdx;
- endIdx = bottomLine == Input.TextGenerator.lineCount
- ? value.Length - 1
- : (Input.TextGenerator.lines[bottomLine + 1].startCharIdx - 1);
- }
-
-
- // Highlight the visible text with the LexerBuilder
- Panel.HighlightText.text = Lexer.BuildHighlightedString(value, startIdx, endIdx, topLine);
- }
-
- #endregion
-
-
- #region Autocompletes
-
- public static void InsertSuggestionAtCaret(string suggestion)
- {
- string input = Input.Text;
- input = input.Insert(LastCaretPosition, suggestion);
- Input.Text = input;
-
- RuntimeProvider.Instance.StartCoroutine(SetAutocompleteCaret(LastCaretPosition += suggestion.Length));
- }
-
- private static IEnumerator SetAutocompleteCaret(int caretPosition)
- {
- var color = Input.Component.selectionColor;
- color.a = 0f;
- Input.Component.selectionColor = color;
- yield return null;
-
- EventSystem.current.SetSelectedGameObject(Panel.Input.UIRoot, null);
- yield return null;
-
- Input.Component.caretPosition = caretPosition;
- Input.Component.selectionFocusPosition = caretPosition;
- color.a = defaultInputFieldAlpha;
- Input.Component.selectionColor = color;
- }
-
-
- #endregion
-
-
- // TODO indenting
- #region AUTO INDENT TODO
-
- //private static void DoAutoIndent()
- //{
- // int caret = Panel.LastCaretPosition;
- // Panel.InputField.Text = Lexer.AutoIndentOnEnter(InputField.text, ref caret);
- // InputField.caretPosition = caret;
- //
- // Panel.InputText.Rebuild(CanvasUpdate.Prelayout);
- // InputField.ForceLabelUpdate();
- // InputField.Rebuild(CanvasUpdate.Prelayout);
- //
- // OnConsoleInputChanged(InputField.text);
- //}
-
-
- // 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
-
-
- #region UI Listeners and options
-
- private static void OnToggleAutoIndent(bool value)
- {
- // TODO
- }
-
- private static void OnToggleCtrlRShortcut(bool value)
- {
- // TODO
- }
-
- private static void OnToggleSuggestions(bool value)
- {
- // TODO
- }
-
- #endregion
-
- }
-}
diff --git a/src/UI/CSConsole/ConsoleController.cs b/src/UI/CSConsole/ConsoleController.cs
new file mode 100644
index 0000000..57c5973
--- /dev/null
+++ b/src/UI/CSConsole/ConsoleController.cs
@@ -0,0 +1,392 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using UnityEngine;
+using UnityEngine.EventSystems;
+using UnityEngine.UI;
+using UnityExplorer.UI.CSConsole;
+using UnityExplorer.Core.Input;
+using UnityExplorer.UI.Panels;
+using UnityExplorer.UI.Widgets.AutoComplete;
+
+namespace UnityExplorer.UI.CSConsole
+{
+ public static class ConsoleController
+ {
+ #region Strings / defaults
+
+ internal const string STARTUP_TEXT = @"Welcome to the UnityExplorer C# Console.
+
+The following helper methods are available:
+
+* Log(""message"") logs a message to the debug console
+
+* StartCoroutine(IEnumerator routine) start the IEnumerator as a UnityEngine.Coroutine
+
+* CurrentTarget() returns the target of the active Inspector tab as System.Object
+
+* AllTargets() returns a System.Object[] array containing the targets of all active tabs
+
+* Inspect(someObject) to inspect an instance, eg. Inspect(Camera.main);
+
+* Inspect(typeof(SomeClass)) to inspect a Class with static reflection
+
+* AddUsing(""SomeNamespace"") adds a using directive to the C# console
+
+* GetUsing() logs the current using directives to the debug console
+
+* Reset() resets all using directives and variables
+";
+
+ internal static readonly string[] DefaultUsing = new string[]
+ {
+ "System",
+ "System.Linq",
+ "System.Collections",
+ "System.Collections.Generic",
+ "System.Reflection",
+ "UnityEngine",
+#if CPP
+ "UnhollowerBaseLib",
+ "UnhollowerRuntimeLib",
+#endif
+ };
+
+ #endregion
+
+ public static ScriptEvaluator Evaluator;
+ public static LexerBuilder Lexer;
+ public static CSAutoCompleter Completer;
+
+ private static HashSet usingDirectives;
+ private static StringBuilder evaluatorOutput;
+
+ public static CSConsolePanel Panel => UIManager.CSharpConsole;
+ public static InputFieldRef Input => Panel.Input;
+
+ public static int LastCaretPosition { get; private set; }
+ public static int PreviousCaretPosition { get; private set; }
+ internal static float defaultInputFieldAlpha;
+
+ // Todo save as config?
+ public static bool EnableCtrlRShortcut { get; private set; } = true;
+ public static bool EnableAutoIndent { get; private set; } = true;
+ public static bool EnableSuggestions { get; private set; } = true;
+
+ public static void Init()
+ {
+ try
+ {
+ ResetConsole(false);
+ Evaluator.Compile("0 == 0");
+ }
+ catch
+ {
+ ExplorerCore.LogWarning("C# Console probably not supported, todo");
+ return;
+ }
+
+ Lexer = new LexerBuilder();
+ Completer = new CSAutoCompleter();
+
+ Panel.OnInputChanged += OnInputChanged;
+ Panel.InputScroll.OnScroll += OnInputScrolled;
+ Panel.OnCompileClicked += Evaluate;
+ Panel.OnResetClicked += ResetConsole;
+ Panel.OnAutoIndentToggled += OnToggleAutoIndent;
+ Panel.OnCtrlRToggled += OnToggleCtrlRShortcut;
+ Panel.OnSuggestionsToggled += OnToggleSuggestions;
+
+ }
+
+ #region UI Listeners and options
+
+ // TODO save
+
+ private static void OnToggleAutoIndent(bool value)
+ {
+ EnableAutoIndent = value;
+ }
+
+ private static void OnToggleCtrlRShortcut(bool value)
+ {
+ EnableCtrlRShortcut = value;
+ }
+
+ private static void OnToggleSuggestions(bool value)
+ {
+ EnableSuggestions = value;
+ }
+
+ #endregion
+
+ // Updating and event listeners
+
+ private static bool settingAutoCompletion;
+
+ private static void OnInputScrolled() => HighlightVisibleInput();
+
+ // Invoked at most once per frame
+ private static void OnInputChanged(string value)
+ {
+ if (!settingAutoCompletion && EnableSuggestions)
+ Completer.CheckAutocompletes();
+
+ if (!settingAutoCompletion && EnableAutoIndent)
+ DoAutoIndent();
+
+ HighlightVisibleInput();
+ }
+
+ public static void Update()
+ {
+ int lastCaretPos = LastCaretPosition;
+ UpdateCaret();
+ bool caretMoved = lastCaretPos != LastCaretPosition;
+
+ if (!settingAutoCompletion && EnableSuggestions && caretMoved)
+ {
+ Completer.CheckAutocompletes();
+ }
+
+ if (EnableCtrlRShortcut
+ && (InputManager.GetKey(KeyCode.LeftControl) || InputManager.GetKey(KeyCode.RightControl))
+ && InputManager.GetKeyDown(KeyCode.R))
+ {
+ Evaluate(Panel.Input.Text);
+ }
+ }
+
+ private const int CSCONSOLE_LINEHEIGHT = 18;
+
+ private static void UpdateCaret()
+ {
+ if (Input.Component.isFocused)
+ {
+ PreviousCaretPosition = LastCaretPosition;
+ LastCaretPosition = Input.Component.caretPosition;
+ }
+
+ if (Input.Text.Length == 0)
+ return;
+
+ var charInfo = Input.TextGenerator.characters[LastCaretPosition];
+ var charTop = charInfo.cursorPos.y;
+ var charBot = charTop - CSCONSOLE_LINEHEIGHT;
+
+ var viewportMin = Input.Rect.rect.height - Input.Rect.anchoredPosition.y - (Input.Rect.rect.height * 0.5f);
+ var viewportMax = viewportMin - Panel.InputScroll.ViewportRect.rect.height;
+
+ float diff = 0f;
+ if (charTop > viewportMin)
+ diff = charTop - viewportMin;
+ else if (charBot < viewportMax)
+ diff = charBot - viewportMax;
+
+ if (Math.Abs(diff) > 1)
+ {
+ var rect = Input.Rect;
+ rect.anchoredPosition = new Vector2(rect.anchoredPosition.x, rect.anchoredPosition.y - diff);
+ }
+ }
+
+
+ #region Evaluating
+
+ public static void ResetConsole() => ResetConsole(true);
+
+ public static void ResetConsole(bool logSuccess = true)
+ {
+ if (Evaluator != null)
+ Evaluator.Dispose();
+
+ evaluatorOutput = new StringBuilder();
+ Evaluator = new ScriptEvaluator(new StringWriter(evaluatorOutput))
+ {
+ InteractiveBaseClass = typeof(ScriptInteraction)
+ };
+
+ usingDirectives = new HashSet();
+ foreach (var use in DefaultUsing)
+ AddUsing(use);
+
+ if (logSuccess)
+ ExplorerCore.Log($"C# Console reset. Using directives:\r\n{Evaluator.GetUsing()}");
+ }
+
+ public static void AddUsing(string assemblyName)
+ {
+ if (!usingDirectives.Contains(assemblyName))
+ {
+ Evaluate($"using {assemblyName};", true);
+ usingDirectives.Add(assemblyName);
+ }
+ }
+
+ public static void Evaluate()
+ {
+ Evaluate(Input.Text);
+ }
+
+ public static void Evaluate(string input, bool supressLog = false)
+ {
+ try
+ {
+ Evaluator.Run(input);
+
+ string output = ScriptEvaluator._textWriter.ToString();
+ var outputSplit = output.Split('\n');
+ if (outputSplit.Length >= 2)
+ output = outputSplit[outputSplit.Length - 2];
+ evaluatorOutput.Clear();
+
+ if (ScriptEvaluator._reportPrinter.ErrorsCount > 0)
+ throw new FormatException($"Unable to compile the code. Evaluator's last output was:\r\n{output}");
+
+ if (!supressLog)
+ ExplorerCore.Log("Code executed successfully.");
+ }
+ catch (FormatException fex)
+ {
+ if (!supressLog)
+ ExplorerCore.LogWarning(fex.Message);
+ }
+ catch (Exception ex)
+ {
+ if (!supressLog)
+ ExplorerCore.LogWarning(ex);
+ }
+ }
+
+ #endregion
+
+
+ #region Lexer Highlighting
+
+ private static void HighlightVisibleInput()
+ {
+ int startIdx = 0;
+ int endIdx = Input.Text.Length - 1;
+ int topLine = 0;
+
+ try
+ {
+ // Calculate visible text if necessary
+ if (Input.Rect.rect.height > Panel.InputScroll.ViewportRect.rect.height)
+ {
+ topLine = -1;
+ int bottomLine = -1;
+
+ // the top and bottom position of the viewport in relation to the text height
+ // they need the half-height adjustment to normalize against the 'line.topY' value.
+ var viewportMin = Input.Rect.rect.height - Input.Rect.anchoredPosition.y - (Input.Rect.rect.height * 0.5f);
+ var viewportMax = viewportMin - Panel.InputScroll.ViewportRect.rect.height;
+
+ for (int i = 0; i < Input.TextGenerator.lineCount; i++)
+ {
+ var line = Input.TextGenerator.lines[i];
+ // if not set the top line yet, and top of line is below the viewport top
+ if (topLine == -1 && line.topY <= viewportMin)
+ topLine = i;
+ // if bottom of line is below the viewport bottom
+ if ((line.topY - line.height) >= viewportMax)
+ bottomLine = i;
+ }
+
+ topLine = Math.Max(0, topLine - 1);
+ bottomLine = Math.Min(Input.TextGenerator.lineCount - 1, bottomLine + 1);
+
+ startIdx = Input.TextGenerator.lines[topLine].startCharIdx;
+ endIdx = (bottomLine >= Input.TextGenerator.lineCount - 1)
+ ? Input.Text.Length - 1
+ : (Input.TextGenerator.lines[bottomLine + 1].startCharIdx - 1);
+ }
+ }
+ catch (Exception ex)
+ {
+ ExplorerCore.Log("Exception on HighlightVisibleText: " + ex.GetType().Name + ", " + ex.Message);
+
+ }
+
+
+ // Highlight the visible text with the LexerBuilder
+ Panel.HighlightText.text = Lexer.BuildHighlightedString(Input.Text, startIdx, endIdx, topLine);
+ }
+
+ #endregion
+
+
+ #region Autocompletes
+
+ public static void InsertSuggestionAtCaret(string suggestion)
+ {
+ settingAutoCompletion = true;
+ Input.Text = Input.Text.Insert(LastCaretPosition, suggestion);
+
+ RuntimeProvider.Instance.StartCoroutine(SetAutocompleteCaret(LastCaretPosition + suggestion.Length));
+ LastCaretPosition = Input.Component.caretPosition;
+ }
+
+ private static IEnumerator SetAutocompleteCaret(int caretPosition)
+ {
+ var color = Input.Component.selectionColor;
+ color.a = 0f;
+ Input.Component.selectionColor = color;
+ yield return null;
+
+ EventSystem.current.SetSelectedGameObject(Panel.Input.UIRoot, null);
+ yield return null;
+
+ Input.Component.caretPosition = caretPosition;
+ Input.Component.selectionFocusPosition = caretPosition;
+ LastCaretPosition = Input.Component.caretPosition;
+
+ color.a = defaultInputFieldAlpha;
+ Input.Component.selectionColor = color;
+
+ settingAutoCompletion = false;
+ }
+
+
+ #endregion
+
+
+ #region Auto indenting
+
+ private static int prevContentLen = 0;
+
+ private static void DoAutoIndent()
+ {
+ if (Input.Text.Length > prevContentLen)
+ {
+ int inc = Input.Text.Length - prevContentLen;
+
+ if (inc == 1)
+ {
+ int caret = Input.Component.caretPosition;
+ Input.Text = Lexer.IndentCharacter(Input.Text, ref caret);
+ Input.Component.caretPosition = caret;
+ LastCaretPosition = caret;
+ }
+ else
+ {
+ // todo indenting for copy+pasted content
+
+ //ExplorerCore.Log("Content increased by " + inc);
+ //var comp = Input.Text.Substring(PreviousCaretPosition, inc);
+ //ExplorerCore.Log("composition string: " + comp);
+ }
+ }
+
+ prevContentLen = Input.Text.Length;
+ }
+
+ #endregion
+
+
+
+ }
+}
diff --git a/src/UI/CSConsole/LexerBuilder.cs b/src/UI/CSConsole/LexerBuilder.cs
index 376cbae..8e76f89 100644
--- a/src/UI/CSConsole/LexerBuilder.cs
+++ b/src/UI/CSConsole/LexerBuilder.cs
@@ -1,12 +1,13 @@
-using System;
+using Mono.CSharp;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
-using UnityExplorer.UI.CSharpConsole.Lexers;
+using UnityExplorer.UI.CSConsole.Lexers;
-namespace UnityExplorer.UI.CSharpConsole
+namespace UnityExplorer.UI.CSConsole
{
public struct MatchInfo
{
@@ -20,19 +21,22 @@ namespace UnityExplorer.UI.CSharpConsole
#region Core and initialization
public const char WHITESPACE = ' ';
- public const char INDENT_OPEN = '{';
- public const char INDENT_CLOSE = '}';
+ public readonly HashSet IndentOpenChars = new HashSet { '{', '(' };
+ public readonly HashSet IndentCloseChars = new HashSet { '}', ')' };
private readonly Lexer[] lexers;
private readonly HashSet delimiters = new HashSet();
+ private readonly StringLexer stringLexer = new StringLexer();
+ private readonly CommentLexer commentLexer = new CommentLexer();
+
public LexerBuilder()
{
lexers = new Lexer[]
{
- new CommentLexer(),
+ commentLexer,
+ stringLexer,
new SymbolLexer(),
- new StringLexer(),
new NumberLexer(),
new KeywordLexer(),
};
@@ -49,14 +53,22 @@ namespace UnityExplorer.UI.CSharpConsole
#endregion
- public int LastCommittedIndex { get; private set; }
- public int LookaheadIndex { get; private set; }
+ /// The last committed index for a match or no-match. Starts at -1 for a new parse.
+ public int CommittedIndex { get; private set; }
+ /// The index of the character we are currently parsing, at minimum it will be CommittedIndex + 1.
+ public int CurrentIndex { get; private set; }
- public char Current => !EndOfInput ? currentInput[LookaheadIndex] : WHITESPACE;
- public char Previous => LookaheadIndex >= 1 ? currentInput[LookaheadIndex - 1] : WHITESPACE;
+ /// The current character we are parsing, determined by CurrentIndex.
+ public char Current => !EndOfInput ? currentInput[CurrentIndex] : WHITESPACE;
+ /// The previous character (CurrentIndex - 1), or whitespace if no previous character.
+ public char Previous => CurrentIndex >= 1 ? currentInput[CurrentIndex - 1] : WHITESPACE;
- public bool EndOfInput => LookaheadIndex > currentEndIdx;
- public bool EndOrNewLine => EndOfInput || Current == '\n' || Current == '\r';
+ /// Returns true if CurrentIndex is >= the current input length.
+ public bool EndOfInput => CurrentIndex > currentEndIdx;
+ /// Returns true if EndOfInput or current character is a new line.
+ public bool EndOrNewLine => EndOfInput || IsNewLine(Current);
+
+ public static bool IsNewLine(char c) => c == '\n' || c == '\r';
private string currentInput;
private int currentStartIdx;
@@ -72,6 +84,9 @@ namespace UnityExplorer.UI.CSharpConsole
/// A string which contains the amount of leading lines specified, as well as the rich-text highlighted section.
public string BuildHighlightedString(string input, int startIdx, int endIdx, int leadingLines)
{
+
+
+
if (string.IsNullOrEmpty(input) || endIdx <= startIdx)
return input;
@@ -109,16 +124,16 @@ namespace UnityExplorer.UI.CSharpConsole
// Match builder, iterates through each Lexer and returns all matches found.
- private IEnumerable GetMatches()
+ public IEnumerable GetMatches()
{
- LastCommittedIndex = currentStartIdx - 1;
+ CommittedIndex = currentStartIdx - 1;
Rollback();
while (!EndOfInput)
{
SkipWhitespace();
bool anyMatch = false;
- int startIndex = LastCommittedIndex + 1;
+ int startIndex = CommittedIndex + 1;
foreach (var lexer in lexers)
{
@@ -129,7 +144,7 @@ namespace UnityExplorer.UI.CSharpConsole
yield return new MatchInfo
{
startIndex = startIndex,
- endIndex = LastCommittedIndex,
+ endIndex = CommittedIndex,
htmlColorTag = lexer.ColorTag,
};
break;
@@ -140,7 +155,7 @@ namespace UnityExplorer.UI.CSharpConsole
if (!anyMatch)
{
- LookaheadIndex = LastCommittedIndex + 1;
+ CurrentIndex = CommittedIndex + 1;
Commit();
}
}
@@ -150,23 +165,23 @@ namespace UnityExplorer.UI.CSharpConsole
public char PeekNext(int amount = 1)
{
- LookaheadIndex += amount;
+ CurrentIndex += amount;
return Current;
}
public void Commit()
{
- LastCommittedIndex = Math.Min(currentEndIdx, LookaheadIndex);
+ CommittedIndex = Math.Min(currentEndIdx, CurrentIndex);
}
public void Rollback()
{
- LookaheadIndex = LastCommittedIndex + 1;
+ CurrentIndex = CommittedIndex + 1;
}
public void RollbackBy(int amount)
{
- LookaheadIndex = Math.Max(LastCommittedIndex + 1, LookaheadIndex - amount);
+ CurrentIndex = Math.Max(CommittedIndex + 1, CurrentIndex - amount);
}
public bool IsDelimiter(char character, bool orWhitespace = false, bool orLetterOrDigit = false)
@@ -185,8 +200,139 @@ namespace UnityExplorer.UI.CSharpConsole
PeekNext();
}
- // revert the last PeekNext which would have returned false
- Rollback();
+ if (!char.IsWhiteSpace(Current))
+ Rollback();
}
+
+ #region Auto Indenting
+
+ // Using the Lexer for indenting as it already has what we need to tokenize strings and comments.
+ // At the moment this only handles when a single newline or close-delimiter is composed.
+ // Does not handle copy+paste or any other characters yet.
+
+ public string IndentCharacter(string input, ref int caretIndex)
+ {
+ int lastCharIndex = caretIndex - 1;
+ char c = input[lastCharIndex];
+
+ // we only want to indent for new lines and close indents
+ if (!IsNewLine(c) && !IndentCloseChars.Contains(c))
+ return input;
+
+ // perform a light parse up to the caret to determine indent level
+ currentInput = input;
+ currentStartIdx = 0;
+ currentEndIdx = lastCharIndex;
+ CommittedIndex = -1;
+ Rollback();
+
+ int indent = 0;
+
+ while (!EndOfInput)
+ {
+ if (CurrentIndex >= lastCharIndex)
+ {
+ // reached the caret index
+ if (indent <= 0)
+ break;
+
+ if (IsNewLine(c))
+ input = IndentNewLine(input, indent, ref caretIndex);
+ else // closing indent
+ input = IndentCloseDelimiter(input, indent, lastCharIndex, ref caretIndex);
+
+ break;
+ }
+
+ // Try match strings and comments (Lexer will commit to the end of the match)
+ if (stringLexer.TryMatchCurrent(this) || commentLexer.TryMatchCurrent(this))
+ {
+ PeekNext();
+ continue;
+ }
+
+ // Still parsing, check indent
+
+ if (IndentOpenChars.Contains(Current))
+ indent++;
+ else if (IndentCloseChars.Contains(Current))
+ indent--;
+
+ Commit();
+ PeekNext();
+ }
+
+ return input;
+ }
+
+ private string IndentNewLine(string input, int indent, ref int caretIndex)
+ {
+ // continue until the end of line or next non-whitespace character.
+ // if there's a close-indent on this line, reduce the indent level.
+ while (CurrentIndex < input.Length - 1)
+ {
+ CurrentIndex++;
+ char next = input[CurrentIndex];
+ if (IsNewLine(next))
+ break;
+ if (char.IsWhiteSpace(next))
+ continue;
+ else if (IndentCloseChars.Contains(next))
+ indent--;
+
+ break;
+ }
+
+ if (indent > 0)
+ {
+ input = input.Insert(caretIndex, new string('\t', indent));
+ caretIndex += indent;
+ }
+
+ return input;
+ }
+
+ private string IndentCloseDelimiter(string input, int indent, int lastCharIndex, ref int caretIndex)
+ {
+ if (CurrentIndex > lastCharIndex)
+ {
+ return input;
+ }
+
+ // lower the indent level by one as we would not have accounted for this closing symbol
+ indent--;
+
+ while (CurrentIndex > 0)
+ {
+ CurrentIndex--;
+ char prev = input[CurrentIndex];
+ if (IsNewLine(prev))
+ break;
+ if (!char.IsWhiteSpace(prev))
+ {
+ // the line containing the closing bracket has non-whitespace characters before it. do not indent.
+ indent = 0;
+ break;
+ }
+ else if (prev == '\t')
+ indent--;
+ }
+
+ if (indent > 0)
+ {
+ input = input.Insert(caretIndex, new string('\t', indent));
+ caretIndex += indent;
+ }
+ else if (indent < 0)
+ {
+ // line is overly indented
+ input = input.Remove(lastCharIndex - 1, -indent);
+ caretIndex += indent;
+ }
+
+ return input;
+ }
+
+ #endregion
}
}
diff --git a/src/UI/CSConsole/Lexers/CommentLexer.cs b/src/UI/CSConsole/Lexers/CommentLexer.cs
index bd903e7..e4c541e 100644
--- a/src/UI/CSConsole/Lexers/CommentLexer.cs
+++ b/src/UI/CSConsole/Lexers/CommentLexer.cs
@@ -2,7 +2,7 @@
using System.Linq;
using UnityEngine;
-namespace UnityExplorer.UI.CSharpConsole.Lexers
+namespace UnityExplorer.UI.CSConsole.Lexers
{
public class CommentLexer : Lexer
{
diff --git a/src/UI/CSConsole/Lexers/KeywordLexer.cs b/src/UI/CSConsole/Lexers/KeywordLexer.cs
index 1de9fd8..376c793 100644
--- a/src/UI/CSConsole/Lexers/KeywordLexer.cs
+++ b/src/UI/CSConsole/Lexers/KeywordLexer.cs
@@ -2,14 +2,14 @@
using System.Text;
using UnityEngine;
-namespace UnityExplorer.UI.CSharpConsole.Lexers
+namespace UnityExplorer.UI.CSConsole.Lexers
{
public class KeywordLexer : Lexer
{
// system blue
protected override Color HighlightColor => new Color(0.33f, 0.61f, 0.83f, 1.0f);
- private readonly HashSet keywords = new HashSet
+ public static readonly HashSet keywords = new HashSet
{
// reserved keywords
"abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue",
diff --git a/src/UI/CSConsole/Lexers/Lexer.cs b/src/UI/CSConsole/Lexers/Lexer.cs
index c4f5c06..a7adf58 100644
--- a/src/UI/CSConsole/Lexers/Lexer.cs
+++ b/src/UI/CSConsole/Lexers/Lexer.cs
@@ -2,7 +2,7 @@
using UnityEngine;
using System.Linq;
-namespace UnityExplorer.UI.CSharpConsole.Lexers
+namespace UnityExplorer.UI.CSConsole.Lexers
{
public abstract class Lexer
{
diff --git a/src/UI/CSConsole/Lexers/NumberLexer.cs b/src/UI/CSConsole/Lexers/NumberLexer.cs
index 542ff30..98c0c17 100644
--- a/src/UI/CSConsole/Lexers/NumberLexer.cs
+++ b/src/UI/CSConsole/Lexers/NumberLexer.cs
@@ -1,6 +1,6 @@
using UnityEngine;
-namespace UnityExplorer.UI.CSharpConsole.Lexers
+namespace UnityExplorer.UI.CSConsole.Lexers
{
public class NumberLexer : Lexer
{
diff --git a/src/UI/CSConsole/Lexers/StringLexer.cs b/src/UI/CSConsole/Lexers/StringLexer.cs
index 403f1af..e7c5e02 100644
--- a/src/UI/CSConsole/Lexers/StringLexer.cs
+++ b/src/UI/CSConsole/Lexers/StringLexer.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using UnityEngine;
-namespace UnityExplorer.UI.CSharpConsole.Lexers
+namespace UnityExplorer.UI.CSConsole.Lexers
{
public class StringLexer : Lexer
{
diff --git a/src/UI/CSConsole/Lexers/SymbolLexer.cs b/src/UI/CSConsole/Lexers/SymbolLexer.cs
index 95c83f3..2562d0a 100644
--- a/src/UI/CSConsole/Lexers/SymbolLexer.cs
+++ b/src/UI/CSConsole/Lexers/SymbolLexer.cs
@@ -3,7 +3,7 @@ using System.Linq;
using System.Text;
using UnityEngine;
-namespace UnityExplorer.UI.CSharpConsole.Lexers
+namespace UnityExplorer.UI.CSConsole.Lexers
{
public class SymbolLexer : Lexer
{
diff --git a/src/UI/CSConsole/ScriptEvaluator.cs b/src/UI/CSConsole/ScriptEvaluator.cs
index dc4f376..f5f6b51 100644
--- a/src/UI/CSConsole/ScriptEvaluator.cs
+++ b/src/UI/CSConsole/ScriptEvaluator.cs
@@ -2,11 +2,12 @@
using System.Collections.Generic;
using System.IO;
using System.Reflection;
+using System.Text;
using Mono.CSharp;
// Thanks to ManlyMarco for this
-namespace UnityExplorer.Core.CSharp
+namespace UnityExplorer.UI.CSConsole
{
public class ScriptEvaluator : Evaluator, IDisposable
{
diff --git a/src/UI/CSConsole/ScriptInteraction.cs b/src/UI/CSConsole/ScriptInteraction.cs
index 6765a43..5abb977 100644
--- a/src/UI/CSConsole/ScriptInteraction.cs
+++ b/src/UI/CSConsole/ScriptInteraction.cs
@@ -6,7 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using UnityExplorer.Core.Runtime;
-namespace UnityExplorer.UI.CSharpConsole
+namespace UnityExplorer.UI.CSConsole
{
public class ScriptInteraction : InteractiveBase
{
@@ -22,17 +22,17 @@ namespace UnityExplorer.UI.CSharpConsole
public static void AddUsing(string directive)
{
- CSConsole.AddUsing(directive);
+ ConsoleController.AddUsing(directive);
}
public static void GetUsing()
{
- ExplorerCore.Log(CSConsole.Evaluator.GetUsing());
+ ExplorerCore.Log(ConsoleController.Evaluator.GetUsing());
}
public static void Reset()
{
- CSConsole.ResetConsole();
+ ConsoleController.ResetConsole();
}
public static object CurrentTarget()
diff --git a/src/UI/CacheObject/CacheMember.cs b/src/UI/CacheObject/CacheMember.cs
index da4e8a1..600a7ba 100644
--- a/src/UI/CacheObject/CacheMember.cs
+++ b/src/UI/CacheObject/CacheMember.cs
@@ -338,9 +338,9 @@ namespace UnityExplorer.UI.CacheObject
// Blacklists
private static readonly HashSet bl_typeAndMember = new HashSet
{
- // these cause a crash in IL2CPP
+ // these can cause a crash in IL2CPP
#if CPP
- //"Type.DeclaringMethod",
+ "Type.DeclaringMethod",
"Rigidbody2D.Cast",
"Collider2D.Cast",
"Collider2D.Raycast",
@@ -363,6 +363,7 @@ namespace UnityExplorer.UI.CacheObject
"Component.renderer",
"Component.rigidbody",
"Component.rigidbody2D",
+ "Light.flare",
};
private static readonly HashSet