├── .gitattributes ├── LICENSE ├── LICENSE.meta ├── README.md ├── README.md.meta ├── Runtime.meta ├── Runtime ├── AsyncHelpers.cs ├── AsyncHelpers.cs.meta ├── Attributes.meta ├── Attributes │ ├── AliasAttribute.cs │ ├── AliasAttribute.cs.meta │ ├── CategoryAttribute.cs │ ├── CategoryAttribute.cs.meta │ ├── CommandAttribute.cs │ └── CommandAttribute.cs.meta ├── Category.cs ├── Category.cs.meta ├── Command.cs ├── Command.cs.meta ├── CommandsBuiltin.cs ├── CommandsBuiltin.cs.meta ├── Console.cs ├── Console.cs.meta ├── ConsoleWindow.cs ├── ConsoleWindow.cs.meta ├── Converters.meta ├── Converters │ ├── BooleanConverter.cs │ ├── BooleanConverter.cs.meta │ ├── ByteConverter.cs │ ├── ByteConverter.cs.meta │ ├── CharConverter.cs │ ├── CharConverter.cs.meta │ ├── Converter.cs │ ├── Converter.cs.meta │ ├── DateTimeConverter.cs │ ├── DateTimeConverter.cs.meta │ ├── DoubleConverter.cs │ ├── DoubleConverter.cs.meta │ ├── FloatConverter.cs │ ├── FloatConverter.cs.meta │ ├── IntConverter.cs │ ├── IntConverter.cs.meta │ ├── LongConverter.cs │ ├── LongConverter.cs.meta │ ├── ObjectConverter.cs │ ├── ObjectConverter.cs.meta │ ├── SByteConverter.cs │ ├── SByteConverter.cs.meta │ ├── ShortConverter.cs │ ├── ShortConverter.cs.meta │ ├── StringConverter.cs │ ├── StringConverter.cs.meta │ ├── TypeConverter.cs │ ├── TypeConverter.cs.meta │ ├── UIntConverter.cs │ ├── UIntConverter.cs.meta │ ├── ULongConverter.cs │ ├── ULongConverter.cs.meta │ ├── UShortConverter.cs │ └── UShortConverter.cs.meta ├── Editor.meta ├── Editor │ ├── EnsureSettingsExists.cs │ ├── EnsureSettingsExists.cs.meta │ ├── SettingsInspector.cs │ ├── SettingsInspector.cs.meta │ ├── SettingsProvider.cs │ └── SettingsProvider.cs.meta ├── Exceptions.meta ├── Exceptions │ ├── ConverterNotFoundException.cs │ ├── ConverterNotFoundException.cs.meta │ ├── FailedToConvertException.cs │ └── FailedToConvertException.cs.meta ├── Extensions.cs ├── Extensions.cs.meta ├── Generator.cs ├── Generator.cs.meta ├── Library.cs ├── Library.cs.meta ├── Owner.cs ├── Owner.cs.meta ├── Parser.cs ├── Parser.cs.meta ├── Popcron.Console.asmdef ├── Popcron.Console.asmdef.meta ├── Resources.meta ├── Resources │ ├── CascadiaMono.ttf │ ├── CascadiaMono.ttf.meta │ ├── LICENSE.txt │ └── LICENSE.txt.meta ├── SearchResult.cs ├── SearchResult.cs.meta ├── Settings.cs ├── Settings.cs.meta ├── Table.cs └── Table.cs.meta ├── package.json └── package.json.meta /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Phillip DaSilva-Damaskin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 289cf411aade91749829b3bd0d175e24 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Woah console woah!!!](https://cdn.discordapp.com/attachments/377316629220032523/529091513104465920/unknown.png) 2 | 3 | # Console 4 | A command line parser for use in Unity. 5 | 6 | ## Requirements 7 | - .NET Framework 4.5 8 | 9 | ## Features 10 | - Text suggestions 11 | - Auto finds commands 12 | - Command categories for organization 13 | - Parameter support 14 | - Support for instance method commands 15 | - Support for methods, fields and properties 16 | - Code generation to minimize reflection at startup 17 | 18 | ## Installing 19 | To install for use in Unity, copy everything from this repository to `/Packages/Popcron.Console` folder. 20 | 21 | If using 2018.3.x, you can add a new entry to the manifest.json file in your Packages folder: 22 | ```json 23 | "com.popcron.console": "https://github.com/popcron/console.git" 24 | ``` 25 | After that, call `Console.Open = false;` in any Start method. 26 | 27 | NB: If you are referencing Console with Package manager you will have to create assembly definition for your code and reference it in settings (see FAQ). Otherwise Console will see only its assembly. 28 | 29 | ## Calling commands 30 | ```cs 31 | string command = "help"; 32 | object result = await Parser.Run(command); 33 | ``` 34 | 35 | or just `help` from the console window. 36 | 37 | ## Instance commands 38 | Command methods that are declared as static can be executed from any context. Instance methods however cannot be called in the same way. These commands require an object that owns the method. The parser class provides methods that allow you to register and unregister objects with a unique ID, which can allow instance commands to be called on an object. 39 | 40 | ```cs 41 | using UnityEngine; 42 | using Popcron.Console; 43 | 44 | public class PlayerAmmo : MonoBehaviour 45 | { 46 | [Command("ammo")] 47 | public int ammo = 100; 48 | 49 | //when the object gets created, register it 50 | private void OnEnable() 51 | { 52 | Parser.Register(this, "player"); 53 | } 54 | 55 | //when the object gets destroyed, unregister it 56 | private void OnDisable() 57 | { 58 | Parser.Unregister(this); 59 | } 60 | } 61 | ``` 62 | 63 | To call this command from the console, you call the method exactly as its writen, with the addition of the `@id` prefix. 64 | `@player ammo 100` 65 | 66 | ## Examples 67 | To create a simple command, add a `Command` attribute to your method. 68 | 69 | ```cs 70 | using Popcron.Console; 71 | 72 | public class Commands 73 | { 74 | [Command("add")] 75 | public static int Add(int a, int b) 76 | { 77 | return a + b; 78 | } 79 | } 80 | ``` 81 | `add 2 3` will return `5` 82 | 83 |
84 | Setting up categories 85 | 86 | Categories arent necessary, but they allow you to categorize commands into a list which can be retrieved using `Parser.Categories`. To add categories, add a `Category` attribute to the class itself. This is primarely useful when listing all of the commands using `help`. 87 | ```cs 88 | using Popcron.Console; 89 | 90 | [Category("Default commands")] 91 | public class Commands 92 | { 93 | [Command("add")] 94 | public static int Add(int a, int b) 95 | { 96 | return a + b; 97 | } 98 | } 99 | ``` 100 |
101 | 102 |
103 | Command aliases 104 | 105 | Commands can have multiple aliases. To give a command another calling name, add the `Alias` attribute 106 | ```cs 107 | using Popcron.Console; 108 | 109 | [Category("Default commands")] 110 | public class Commands 111 | { 112 | [Alias("+")] 113 | [Command("add")] 114 | public static int Add(int a, int b) 115 | { 116 | return a + b; 117 | } 118 | } 119 | ``` 120 | `+ 2 3` will return `5` 121 | 122 | `add 7 -2` will return `5` 123 |
124 | 125 | ## Supported types 126 | - string 127 | - char 128 | - bool 129 | - byte and sbyte 130 | - short and ushort 131 | - int and uint 132 | - long and ulong 133 | - float 134 | - double 135 | - object 136 | - Type 137 | - DateTime 138 | 139 | You can also add your own type converters by inheriting from the abstract `Converter` class. 140 | 141 | ## Built in commands 142 | - `info` Prints the system information (OS, Device, CPU and GPU, RAM) 143 | - `clear` Clears the console 144 | - `owners` Prints all registered owners, along with their instance methods, properties and fields 145 | - `converters` Lists all registered converters 146 | - `help` Prints all commands found as static methods, properties and fields 147 | - `echo` Dumb echo command 148 | - `scene` Prints out the entire scene hierarchy 149 | 150 | ## FAQ 151 | - **How do I add this to my unity project?** 152 | Add `"com.popcron.console": "https://github.com/popcron/console.git"` to your `manifest.json` file in the packages folder. 153 | - **It doesn't show up.** 154 | Press ~. 155 | - **I pressed it, it still doesn't show up!** 156 | Invoke the `Console.Initialize()` method, or any of the static methods/properties once in the game. 157 | - **How do I change the key to open the console?** 158 | Change the `Console.Key` property. 159 | - **I'm not using the built-in input system, does it work with the new one?** 160 | Yes, if the new InputSystem is available, it will use that instead of the old one. 161 | - **I want to use the original system console that's included.** 162 | If you want to reference the "original console", you can do so by referencing the namespace: `System.Console.WriteLine("wee")`. 163 | - **Can I use properties as commands?** 164 | Yes. 165 | - **Can I use fields as commands?** 166 | Yes. 167 | - **Static only?** 168 | No you can use instance members too, as long as you register and unregister the instance owner of the command. More info above. 169 | - **How do I categorize a command?** 170 | Add a `Category` attribute to the class type. 171 | - **How do I make my own converter?** 172 | Create a new class, and inherit from the `Converter` class. 173 | - **It's not detecting commands in my custom assembly!** 174 | Go to ProjectSettings/Console and add your custom assembly to the list of assemblies. 175 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 47127c37b0d93ed42b2cbcdf0f46b4dc 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f41bd5b1726fe764f86f7b31e72ec4d5 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/AsyncHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace Popcron.Console 7 | { 8 | //taken from https://stackoverflow.com/questions/5095183/how-would-i-run-an-async-taskt-method-synchronously 9 | public static class AsyncHelpers 10 | { 11 | /// 12 | /// Execute's an async Task method which has a void return value synchronously 13 | /// 14 | /// Task method to execute 15 | public static void RunSync(Func task) 16 | { 17 | var oldContext = SynchronizationContext.Current; 18 | var synch = new ExclusiveSynchronizationContext(); 19 | SynchronizationContext.SetSynchronizationContext(synch); 20 | synch.Post(async _ => 21 | { 22 | try 23 | { 24 | await task(); 25 | } 26 | catch (Exception e) 27 | { 28 | synch.InnerException = e; 29 | throw; 30 | } 31 | finally 32 | { 33 | synch.EndMessageLoop(); 34 | } 35 | }, null); 36 | synch.BeginMessageLoop(); 37 | 38 | SynchronizationContext.SetSynchronizationContext(oldContext); 39 | } 40 | 41 | /// 42 | /// Execute's an async Task method which has a T return type synchronously 43 | /// 44 | /// Return Type 45 | /// Task method to execute 46 | /// 47 | public static T RunSync(Func> task) 48 | { 49 | var oldContext = SynchronizationContext.Current; 50 | var synch = new ExclusiveSynchronizationContext(); 51 | SynchronizationContext.SetSynchronizationContext(synch); 52 | T ret = default(T); 53 | synch.Post(async _ => 54 | { 55 | try 56 | { 57 | ret = await task(); 58 | } 59 | catch (Exception e) 60 | { 61 | synch.InnerException = e; 62 | throw; 63 | } 64 | finally 65 | { 66 | synch.EndMessageLoop(); 67 | } 68 | }, null); 69 | synch.BeginMessageLoop(); 70 | SynchronizationContext.SetSynchronizationContext(oldContext); 71 | return ret; 72 | } 73 | 74 | private class ExclusiveSynchronizationContext : SynchronizationContext 75 | { 76 | private bool done; 77 | public Exception InnerException { get; set; } 78 | readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false); 79 | readonly Queue> items = 80 | new Queue>(); 81 | 82 | public override void Send(SendOrPostCallback d, object state) 83 | { 84 | throw new NotSupportedException("We cannot send to our same thread"); 85 | } 86 | 87 | public override void Post(SendOrPostCallback d, object state) 88 | { 89 | lock (items) 90 | { 91 | items.Enqueue(Tuple.Create(d, state)); 92 | } 93 | workItemsWaiting.Set(); 94 | } 95 | 96 | public void EndMessageLoop() 97 | { 98 | Post(_ => done = true, null); 99 | } 100 | 101 | public void BeginMessageLoop() 102 | { 103 | while (!done) 104 | { 105 | Tuple task = null; 106 | lock (items) 107 | { 108 | if (items.Count > 0) 109 | { 110 | task = items.Dequeue(); 111 | } 112 | } 113 | if (task != null) 114 | { 115 | task.Item1(task.Item2); 116 | if (InnerException != null) // the method threw an exeption 117 | { 118 | throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException); 119 | } 120 | } 121 | else 122 | { 123 | workItemsWaiting.WaitOne(); 124 | } 125 | } 126 | } 127 | 128 | public override SynchronizationContext CreateCopy() 129 | { 130 | return this; 131 | } 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /Runtime/AsyncHelpers.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b227a3349b185ca4a945a719529e0280 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Attributes.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5f78eb6b0da503545b740efae12f6497 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Attributes/AliasAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static Popcron.Console.Parser; 3 | 4 | namespace Popcron.Console 5 | { 6 | [Serializable] 7 | public class AliasAttribute : Attribute 8 | { 9 | public string name = ""; 10 | 11 | public AliasAttribute(string name) 12 | { 13 | this.name = Sanitize(name); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Runtime/Attributes/AliasAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6f317a23b7cc35245a1e1d2d83d33fa6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Attributes/CategoryAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static Popcron.Console.Parser; 3 | 4 | namespace Popcron.Console 5 | { 6 | public class CategoryAttribute : Attribute 7 | { 8 | public string Name { get; } 9 | 10 | public CategoryAttribute(string name) 11 | { 12 | Name = Sanitize(name); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Runtime/Attributes/CategoryAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 96d1cf00178e4584b80bc9a53d3b45d5 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Attributes/CommandAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static Popcron.Console.Parser; 3 | 4 | namespace Popcron.Console 5 | { 6 | [Serializable] 7 | public class CommandAttribute : Attribute 8 | { 9 | public string name = ""; 10 | public string description = ""; 11 | 12 | public CommandAttribute(string name, string description = "") 13 | { 14 | this.name = Sanitize(name); 15 | this.description = description; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Runtime/Attributes/CommandAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f4a3f55e228b2b349b2bd48be14c3150 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Category.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Popcron.Console 5 | { 6 | [Serializable] 7 | public class Category 8 | { 9 | private string name = ""; 10 | private List commands = new List(); 11 | 12 | public string Name => name; 13 | public List Commands => commands; 14 | 15 | private Category(string name) 16 | { 17 | this.name = name; 18 | } 19 | 20 | public override string ToString() 21 | { 22 | return name; 23 | } 24 | 25 | public static Category Create(string name) 26 | { 27 | return new Category(name); 28 | } 29 | 30 | public static Category Create(Type type) 31 | { 32 | CategoryAttribute attribute = type.GetCategoryAttribute(); 33 | if (attribute == null) 34 | { 35 | return null; 36 | } 37 | 38 | Category category = new Category(attribute.Name); 39 | List commands = Library.Commands; 40 | int commandsCount = commands.Count; 41 | for (int i = 0; i < commandsCount; i++) 42 | { 43 | Command command = commands[i]; 44 | if (command.OwnerClass == type) 45 | { 46 | category.commands.Add(command); 47 | } 48 | } 49 | 50 | return category; 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /Runtime/Category.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5dbd3829d457a2d4faf48bf52a284f2e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Command.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | 5 | namespace Popcron.Console 6 | { 7 | [Serializable] 8 | public class Command 9 | { 10 | public delegate void OnMethodInvokedEvent(); 11 | 12 | private string name; 13 | private List names = new List(); 14 | private string description; 15 | private MethodInfo method; 16 | private PropertyInfo property; 17 | private FieldInfo field; 18 | 19 | private MethodInfo get; 20 | private MethodInfo set; 21 | 22 | private Type ownerClass; 23 | private List parameters = new List(); 24 | 25 | public string Name => name; 26 | public List Names => names; 27 | 28 | /// 29 | /// The class in which the command is defined in. 30 | /// 31 | public Type OwnerClass => ownerClass; 32 | 33 | public string Description => description; 34 | public OnMethodInvokedEvent OnMethodInvoked { get; set; } 35 | 36 | public List Parameters 37 | { 38 | get 39 | { 40 | List names = new List(); 41 | for (int i = 0; i < parameters.Count; i++) 42 | { 43 | if (parameters[i] is FieldInfo field) 44 | { 45 | names.Add(field.Name); 46 | } 47 | else if (parameters[i] is ParameterInfo param) 48 | { 49 | names.Add(param.Name); 50 | } 51 | } 52 | 53 | return names; 54 | } 55 | } 56 | 57 | public bool IsStatic 58 | { 59 | get 60 | { 61 | if (method != null) 62 | { 63 | return method.IsStatic; 64 | } 65 | else if (field != null) 66 | { 67 | return field.IsStatic; 68 | } 69 | else if (property != null) 70 | { 71 | if (get != null) 72 | { 73 | return get.IsStatic; 74 | } 75 | else if (set != null) 76 | { 77 | return get.IsStatic; 78 | } 79 | } 80 | 81 | return false; 82 | } 83 | } 84 | 85 | public MemberInfo Member 86 | { 87 | get 88 | { 89 | if (method != null) 90 | { 91 | return method; 92 | } 93 | else if (property != null) 94 | { 95 | return property; 96 | } 97 | else if (field != null) 98 | { 99 | return field; 100 | } 101 | 102 | return null; 103 | } 104 | } 105 | 106 | private Command(string name, string description, MethodInfo method, Type ownerClassType = null) 107 | { 108 | this.name = name; 109 | this.description = description; 110 | this.method = method; 111 | this.ownerClass = ownerClassType; 112 | Initialize(); 113 | } 114 | 115 | private Command(string name, string description, PropertyInfo property, Type ownerClassType = null) 116 | { 117 | this.name = name; 118 | this.description = description; 119 | this.property = property; 120 | this.ownerClass = ownerClassType; 121 | Initialize(); 122 | } 123 | 124 | private Command(string name, string description, FieldInfo field, Type ownerClassType = null) 125 | { 126 | this.name = name; 127 | this.description = description; 128 | this.field = field; 129 | this.ownerClass = ownerClassType; 130 | Initialize(); 131 | } 132 | 133 | public override int GetHashCode() 134 | { 135 | unchecked 136 | { 137 | int hashCode = 1622228699; 138 | hashCode = hashCode * -1521134295 + name.GetHashCode(); 139 | 140 | if (!string.IsNullOrEmpty(description)) 141 | { 142 | hashCode = hashCode * -1521134295 + description.GetHashCode(); 143 | } 144 | 145 | hashCode = hashCode * -1521134295 + Member.GetHashCode(); 146 | return hashCode; 147 | } 148 | } 149 | 150 | private void Initialize() 151 | { 152 | //find alias attributes 153 | AliasAttribute[] aliases = Member.GetAliases(); 154 | foreach (AliasAttribute alias in aliases) 155 | { 156 | names.Add(alias.name); 157 | } 158 | 159 | names.Add(Name); 160 | 161 | if (method != null) 162 | { 163 | //set parameters 164 | parameters.Clear(); 165 | ParameterInfo[] methodParameters = method.GetParameters(); 166 | for (int i = 0; i < methodParameters.Length; i++) 167 | { 168 | parameters.Add(methodParameters[i]); 169 | } 170 | } 171 | else if (property != null) 172 | { 173 | ParameterInfo parameter = null; 174 | get = property.GetGetMethod(); 175 | set = property.GetSetMethod(); 176 | 177 | if (get != null) 178 | { 179 | parameter = get.ReturnParameter; 180 | } 181 | 182 | if (parameter != null) 183 | { 184 | parameters.Add(parameter); 185 | } 186 | } 187 | else if (field != null) 188 | { 189 | parameters.Add(field); 190 | } 191 | } 192 | 193 | /// 194 | /// Returns the parameter info from this command with this name. 195 | /// 196 | public ParameterInfo GetParameter(string parameterName) 197 | { 198 | for (int i = 0; i < parameters.Count; i++) 199 | { 200 | if (parameters[i] is ParameterInfo parameter) 201 | { 202 | if (parameter.Name == parameterName) 203 | { 204 | return parameter; 205 | } 206 | } 207 | } 208 | 209 | return null; 210 | } 211 | 212 | public object Invoke(object owner, params object[] parameters) 213 | { 214 | if (method != null) 215 | { 216 | OnMethodInvoked?.Invoke(); 217 | return method.Invoke(owner, parameters); 218 | } 219 | else if (property != null) 220 | { 221 | if (parameters == null || parameters.Length == 0) 222 | { 223 | //if no parameters were passed, then get 224 | if (get != null) 225 | { 226 | return get.Invoke(owner, parameters); 227 | } 228 | } 229 | else if (parameters != null && parameters.Length == 1) 230 | { 231 | //if 1 parameter was passed, then set 232 | if (set != null) 233 | { 234 | property.SetValue(owner, parameters[0]); 235 | } 236 | } 237 | } 238 | else if (field != null) 239 | { 240 | if (parameters == null || parameters.Length == 0) 241 | { 242 | return field.GetValue(owner); 243 | } 244 | else if (parameters != null && parameters.Length == 1) 245 | { 246 | field.SetValue(owner, parameters[0]); 247 | } 248 | } 249 | 250 | return null; 251 | } 252 | 253 | public static Command Create(string name, string description, MemberInfo member, Type memberOwningType = null) 254 | { 255 | if (member is FieldInfo field) 256 | { 257 | return Create(name, description, field, memberOwningType); 258 | } 259 | else if (member is MethodInfo method) 260 | { 261 | return Create(name, description, method, memberOwningType); 262 | } 263 | else if (member is PropertyInfo property) 264 | { 265 | return Create(name, description, property, memberOwningType); 266 | } 267 | else 268 | { 269 | return null; 270 | } 271 | } 272 | 273 | public static Command Create(string name, string description, MethodInfo method, Type methodOwningType = null) 274 | { 275 | Command command = new Command(name, description, method, methodOwningType); 276 | return command; 277 | } 278 | 279 | public static Command Create(MethodInfo method, Type methodOwningType = null) 280 | { 281 | CommandAttribute attribute = method.GetCommand(); 282 | if (attribute == null) 283 | { 284 | return null; 285 | } 286 | 287 | return Create(attribute.name, attribute.description, method, methodOwningType); 288 | } 289 | 290 | public static Command Create(string name, string description, PropertyInfo property, Type propertyOwningType = null) 291 | { 292 | Command command = new Command(name, description, property, propertyOwningType); 293 | return command; 294 | } 295 | 296 | public static Command Create(PropertyInfo property, Type propertyOwningType = null) 297 | { 298 | CommandAttribute attribute = property.GetCommand(); 299 | if (attribute == null) 300 | { 301 | return null; 302 | } 303 | 304 | return Create(attribute.name, attribute.description, property, propertyOwningType); 305 | } 306 | 307 | public static Command Create(string name, string description, FieldInfo field, Type fieldOwningType = null) 308 | { 309 | Command command = new Command(name, description, field, fieldOwningType); 310 | return command; 311 | } 312 | 313 | public static Command Create(FieldInfo field, Type fieldOwningType = null) 314 | { 315 | CommandAttribute attribute = field.GetCommand(); 316 | if (attribute == null) 317 | { 318 | return null; 319 | } 320 | 321 | return Create(attribute.name, attribute.description, field, fieldOwningType); 322 | } 323 | 324 | public bool Matches(List parametersGiven, out object[] convertedParameters) 325 | { 326 | convertedParameters = null; 327 | 328 | bool isProperty = property != null && parametersGiven.Count <= 2; 329 | bool isField = field != null && parametersGiven.Count <= 2; 330 | 331 | //get the total amount of params required 332 | int paramsRequired = 0; 333 | int optionalParams = 0; 334 | for (int i = 0; i < parameters.Count; i++) 335 | { 336 | if (parameters[i] is FieldInfo field) 337 | { 338 | paramsRequired++; 339 | } 340 | else if (parameters[i] is ParameterInfo param) 341 | { 342 | if (!param.IsOptional) 343 | { 344 | paramsRequired++; 345 | } 346 | else 347 | { 348 | optionalParams++; 349 | } 350 | } 351 | } 352 | 353 | //parameter amount mismatch 354 | if (method != null) 355 | { 356 | if (parametersGiven.Count < paramsRequired || parametersGiven.Count > paramsRequired + optionalParams) 357 | { 358 | return false; 359 | } 360 | } 361 | 362 | if (isProperty || isField) 363 | { 364 | if (parametersGiven.Count == 0) 365 | { 366 | //get the value 367 | return true; 368 | } 369 | } 370 | 371 | //try to infer the type from input parameters 372 | convertedParameters = new object[parameters.Count]; 373 | for (int i = 0; i < parameters.Count; i++) 374 | { 375 | string parameter = null; 376 | ParameterInfo param = parameters[i] as ParameterInfo; 377 | FieldInfo field = parameters[i] as FieldInfo; 378 | if (i >= parametersGiven.Count) 379 | { 380 | //out of bounds, optional parameter 381 | param = parameters[i] as ParameterInfo; 382 | parameter = param.DefaultValue.ToString(); 383 | } 384 | else 385 | { 386 | parameter = parametersGiven[i]; 387 | } 388 | 389 | object propValue = null; 390 | Type parameterType = param?.ParameterType ?? field.FieldType; 391 | if (!parameterType.IsEnum) 392 | { 393 | Converter converter = Converter.GetConverter(parameterType); 394 | if (converter == null) 395 | { 396 | throw new ConverterNotFoundException("No converter for type " + parameterType.Name + " was found"); 397 | } 398 | 399 | propValue = converter.Convert(parameter); 400 | 401 | //couldnt get a value 402 | if (propValue == null) 403 | { 404 | throw new FailedToConvertException("Failed to convert " + parameter + " to type " + parameterType.Name + " using " + converter.GetType().Name); 405 | } 406 | } 407 | else 408 | { 409 | //manually convert here if its an enum 410 | propValue = Enum.Parse(parameterType, parameter); 411 | 412 | //couldnt get a value 413 | if (propValue == null) 414 | { 415 | throw new FailedToConvertException("Failed to parse " + parameter + " to " + parameterType.Name); 416 | } 417 | } 418 | 419 | convertedParameters[i] = propValue; 420 | } 421 | 422 | return true; 423 | } 424 | } 425 | } -------------------------------------------------------------------------------- /Runtime/Command.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dca5130eaca4e9f4aab4dc01ddad2f5c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/CommandsBuiltin.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Text; 3 | using UnityEngine; 4 | using UnityEngine.Rendering; 5 | using UnityEngine.SceneManagement; 6 | using C = global::Console; 7 | 8 | namespace Popcron.Console.Commands 9 | { 10 | [Category("Built in commands")] 11 | public class CommandsBuiltin 12 | { 13 | private const string Indent = " "; 14 | private const string TwiceIndent = Indent + Indent; 15 | 16 | [Command("info", "Prints system information.")] 17 | public static string PrintSystemInfo() 18 | { 19 | string deviceName = SystemInfo.deviceName; 20 | string deviceModel = SystemInfo.deviceModel; 21 | DeviceType deviceType = SystemInfo.deviceType; 22 | string id = SystemInfo.deviceUniqueIdentifier; 23 | 24 | string os = SystemInfo.operatingSystem; 25 | OperatingSystemFamily osFamily = SystemInfo.operatingSystemFamily; 26 | string ram = (SystemInfo.systemMemorySize / 1000f) + " Gb"; 27 | 28 | string cpuName = SystemInfo.processorType; 29 | int cpuCount = SystemInfo.processorCount; 30 | int cpuFrequency = SystemInfo.processorFrequency; 31 | 32 | GraphicsDeviceType gpuType = SystemInfo.graphicsDeviceType; 33 | string gpuName = SystemInfo.graphicsDeviceName; 34 | string gpuVendor = SystemInfo.graphicsDeviceVendor; 35 | string gpuRam = (SystemInfo.graphicsMemorySize / 1000f) + " Gb"; 36 | 37 | int padding = 23; 38 | StringBuilder text = new StringBuilder(); 39 | text.AppendLine("Device"); 40 | text.AppendLine(Indent + "OS".PadRight(padding) + os + "(" + osFamily + ")"); 41 | text.AppendLine(Indent + "RAM".PadRight(padding) + ram); 42 | text.AppendLine(Indent + "Name".PadRight(padding) + deviceName); 43 | text.AppendLine(Indent + "Model".PadRight(padding) + deviceModel); 44 | text.AppendLine(Indent + "Type".PadRight(padding) + deviceType); 45 | text.AppendLine(Indent + "Unique ID".PadRight(padding) + id); 46 | 47 | text.AppendLine("CPU"); 48 | text.AppendLine(Indent + "Name".PadRight(padding) + cpuName); 49 | text.AppendLine(Indent + "Processors".PadRight(padding) + cpuCount); 50 | text.AppendLine(Indent + "Frequency".PadRight(padding) + cpuFrequency); 51 | 52 | text.AppendLine("GPU"); 53 | text.AppendLine(Indent + "Name".PadRight(padding) + gpuName); 54 | text.AppendLine(Indent + "Type".PadRight(padding) + gpuType); 55 | text.AppendLine(Indent + "Vendor".PadRight(padding) + gpuVendor); 56 | text.AppendLine(Indent + "Memory".PadRight(padding) + gpuRam); 57 | return text.ToString(); 58 | } 59 | 60 | [Command("clear", "Clears the console window.")] 61 | public static void ClearConsole() 62 | { 63 | C.Clear(); 64 | Debug.ClearDeveloperConsole(); 65 | } 66 | 67 | [Command("owners", "Lists all instance command owners.")] 68 | public static string Owners() 69 | { 70 | StringBuilder text = new StringBuilder(); 71 | foreach (Owner owner in Parser.Owners) 72 | { 73 | text.AppendLine(Indent + owner.id + " = " + owner.owner); 74 | foreach (Owner.OwnerMember method in owner.methods) 75 | { 76 | text.Append(TwiceIndent); 77 | text.AppendLine(method.name); 78 | } 79 | 80 | foreach (Owner.OwnerMember property in owner.properties) 81 | { 82 | if (property.canSetProperty) 83 | { 84 | text.Append(TwiceIndent); 85 | text.Append(property.name); 86 | text.AppendLine(" [value]"); 87 | } 88 | else 89 | { 90 | text.Append(TwiceIndent); 91 | text.AppendLine(property.name); 92 | } 93 | } 94 | 95 | foreach (Owner.OwnerMember field in owner.fields) 96 | { 97 | text.Append(TwiceIndent); 98 | text.Append(field.name); 99 | text.Append(" = "); 100 | text.AppendLine(field.Value?.ToString()); 101 | } 102 | } 103 | return text.ToString(); 104 | } 105 | 106 | [Command("converters", "Lists all type converters.")] 107 | public static string Converters() 108 | { 109 | StringBuilder text = new StringBuilder(); 110 | foreach (Converter converter in Converter.Converters) 111 | { 112 | text.AppendLine(Indent + converter.GetType() + " (" + converter.Type + ")"); 113 | } 114 | 115 | return text.ToString(); 116 | } 117 | 118 | [Command("help", "Outputs a list of all commands")] 119 | public static string Help() 120 | { 121 | StringBuilder builder = new StringBuilder(); 122 | builder.AppendLine("All commands registered: "); 123 | foreach (Category category in Library.Categories) 124 | { 125 | builder.AppendLine(Indent + category.Name); 126 | foreach (Command command in category.Commands) 127 | { 128 | string namesText = string.Join("/", command.Names); 129 | builder.Append(TwiceIndent); 130 | 131 | //not static, so an instance method with ID requirement 132 | if (!command.IsStatic) 133 | { 134 | builder.Append(Parser.IDPrefix); 135 | builder.Append(" "); 136 | } 137 | 138 | builder.Append(namesText); 139 | if (command.Member is MethodInfo method) 140 | { 141 | foreach (string parameter in command.Parameters) 142 | { 143 | builder.Append(" "); 144 | builder.Append(Parser.LeftAngleBracket); 145 | builder.Append(parameter); 146 | builder.Append(Parser.RightAngleBracket); 147 | } 148 | } 149 | else if (command.Member is PropertyInfo property) 150 | { 151 | MethodInfo set = property.GetSetMethod(); 152 | if (set != null) 153 | { 154 | builder.Append(" ["); 155 | builder.Append(property.PropertyType.Name); 156 | builder.Append("]"); 157 | } 158 | } 159 | else if (command.Member is FieldInfo field) 160 | { 161 | builder.Append(" ["); 162 | builder.Append(field.FieldType.Name); 163 | builder.Append("]"); 164 | } 165 | 166 | //add description if its present 167 | if (!string.IsNullOrEmpty(command.Description)) 168 | { 169 | builder.Append(" = "); 170 | builder.Append(command.Description); 171 | } 172 | 173 | builder.AppendLine(); 174 | } 175 | } 176 | 177 | return builder.ToString(); 178 | } 179 | 180 | [Command("echo")] 181 | public static string Echo(string text) 182 | { 183 | return text; 184 | } 185 | 186 | [Command("scene", "Prints out the entire scene hierarchy.")] 187 | public static void Scene() 188 | { 189 | string indent = Indent; 190 | GameObject[] root = SceneManager.GetActiveScene().GetRootGameObjects(); 191 | for (int i = 0; i < root.Length; i++) 192 | { 193 | PrintObjectAndKids(indent, root[i].transform); 194 | } 195 | } 196 | 197 | private static void PrintObjectAndKids(string indent, Transform transform) 198 | { 199 | if (transform.gameObject.activeInHierarchy && transform.gameObject.activeSelf) 200 | { 201 | C.WriteLine(indent + transform.name); 202 | } 203 | else 204 | { 205 | C.WriteLine(indent + "" + transform.name + ""); 206 | } 207 | 208 | int childCount = transform.childCount; 209 | for (int i = 0; i < childCount; i++) 210 | { 211 | Transform kid = transform.GetChild(i); 212 | PrintObjectAndKids(TwiceIndent, kid); 213 | } 214 | } 215 | } 216 | } -------------------------------------------------------------------------------- /Runtime/CommandsBuiltin.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8c5d1a805f2b69249b2c210dc1667cb3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Console.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CS0162 //unreachable code detected 2 | 3 | using Popcron.Console; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Collections.ObjectModel; 7 | using System.Threading.Tasks; 8 | using UnityEngine; 9 | using C = Popcron.Console.ConsoleWindow; 10 | 11 | public struct Console 12 | { 13 | /// 14 | /// Returns true if the console is meant to be functional. 15 | /// 16 | #if DISABLE_CONSOLE && !UNITY_EDITOR 17 | public const bool IsIncluded = false; 18 | #else 19 | public const bool IsIncluded = true; 20 | #endif 21 | 22 | /// 23 | /// Gets executed after a message is added to the console window. 24 | /// 25 | public static C.OnPrinted OnPrinted 26 | { 27 | get => C.onPrinted; 28 | set => C.onPrinted = value; 29 | } 30 | 31 | /// 32 | /// Gets executed after before a message is printed to the console. 33 | /// 34 | public static C.OnAboutToPrint OnAboutToPrint 35 | { 36 | get => C.onAboutToPrint; 37 | set => C.onAboutToPrint = value; 38 | } 39 | 40 | /// 41 | /// Read only list of commands that were submitted to the console. 42 | /// 43 | public static ReadOnlyCollection History => C.History; 44 | 45 | /// 46 | /// Read only list of the text that was submitted to the console (without the rich text tags). 47 | /// 48 | public static ReadOnlyCollection Text => C.Text; 49 | 50 | /// 51 | /// The text that is currently given to the console window? 52 | /// 53 | public static string TextInput 54 | { 55 | get => C.TextInput; 56 | set => C.TextInput = value; 57 | } 58 | 59 | /// 60 | /// Is the console window currently open? 61 | /// 62 | public static bool IsOpen 63 | { 64 | get => C.IsOpen; 65 | set => C.IsOpen = value; 66 | } 67 | 68 | [Obsolete("This is obsolete, use Console.IsOpen instead.")] 69 | public static bool Open 70 | { 71 | get => IsOpen; 72 | set => IsOpen = value; 73 | } 74 | 75 | /// 76 | /// The amount of lines that a single scroll should perform. 77 | /// 78 | public static int ScrollPosition 79 | { 80 | get => C.ScrollPosition; 81 | set => C.ScrollPosition = value; 82 | } 83 | 84 | /// 85 | /// Clears the console window. 86 | /// 87 | public static void Clear() => C.Clear(); 88 | 89 | /// 90 | /// Runs a single command synchronously. 91 | /// 92 | public static object Run(string command) => AsyncHelpers.RunSync(() => RunAsyncTask(command)); 93 | 94 | /// 95 | /// Runs a single command asynchronously. 96 | /// 97 | public static async void RunAsync(string command) => await RunAsyncTask(command); 98 | 99 | /// 100 | /// Runs a single command asynchronously. 101 | /// 102 | public static async Task RunAsyncTask(string command) 103 | { 104 | if (!IsIncluded) 105 | { 106 | return null; 107 | } 108 | 109 | //run 110 | char newLine = '\n'; 111 | object result = await Parser.Run(command); 112 | if (result != null) 113 | { 114 | if (result is Exception exception) 115 | { 116 | Exception inner = exception.InnerException; 117 | if (inner != null) 118 | { 119 | WriteLine(exception.Message + newLine + exception.Source + newLine + inner.Message + newLine + inner.StackTrace, LogType.Exception); 120 | } 121 | else 122 | { 123 | WriteLine(exception.Message + newLine + exception.StackTrace, LogType.Exception); 124 | } 125 | } 126 | else 127 | { 128 | WriteLine(result.ToString(), LogType.Log); 129 | } 130 | } 131 | 132 | return result; 133 | } 134 | 135 | /// 136 | /// Manually ask the console window to simulate the enter key. 137 | /// 138 | public static void PressedEnterKey() 139 | { 140 | C.PressedEnterKey(); 141 | } 142 | 143 | /// 144 | /// Runs a list of commands synchronously. 145 | /// 146 | public static void Run(List commands) 147 | { 148 | if (!IsIncluded) 149 | { 150 | return; 151 | } 152 | 153 | if (commands == null || commands.Count == 0) 154 | { 155 | return; 156 | } 157 | 158 | for (int i = 0; i < commands.Count; i++) 159 | { 160 | Run(commands[i]); 161 | } 162 | } 163 | 164 | /// 165 | /// Runs a list of commands asynchronously. 166 | /// 167 | public static async void RunAsync(List commands) 168 | { 169 | if (!IsIncluded) 170 | { 171 | return; 172 | } 173 | 174 | if (commands == null || commands.Count == 0) 175 | { 176 | return; 177 | } 178 | 179 | for (int i = 0; i < commands.Count; i++) 180 | { 181 | await RunAsyncTask(commands[i]); 182 | } 183 | } 184 | 185 | /// 186 | /// Runs a list of commands asynchronously. 187 | /// 188 | public static async Task RunAsyncTask(List commands) 189 | { 190 | if (!IsIncluded) 191 | { 192 | return; 193 | } 194 | 195 | if (commands == null || commands.Count == 0) 196 | { 197 | return; 198 | } 199 | 200 | for (int i = 0; i < commands.Count; i++) 201 | { 202 | await RunAsyncTask(commands[i]); 203 | } 204 | } 205 | 206 | /// 207 | /// Prints this object to the console as a log. 208 | /// 209 | public static void Print(object message) => C.WriteLine(message, LogType.Log); 210 | 211 | /// 212 | /// Prints this object to the console as a warning. 213 | /// 214 | public static void Warn(object message) => C.WriteLine(message, LogType.Warning); 215 | 216 | /// 217 | /// Prints this object to the console with a specific type. 218 | /// 219 | public static void WriteLine(object message, LogType type = LogType.Log) => C.WriteLine(message, type); 220 | 221 | /// 222 | /// Prints this object to the console as a log and a specific hex color. 223 | /// 224 | public static void WriteLine(object message, string hexColor) => C.WriteLine(message, hexColor); 225 | 226 | /// 227 | /// Prints an error to the console window. 228 | /// 229 | public static void Error(object message) => C.WriteLine(message, LogType.Error); 230 | 231 | /// 232 | /// Prints an exception to the console window. 233 | /// 234 | public static void Exception(object message) => C.WriteLine(message, LogType.Exception); 235 | } 236 | -------------------------------------------------------------------------------- /Runtime/Console.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 12c40999ec9cbe54cbd8df095024c26e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/ConsoleWindow.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CS0162 //unreachable code detected 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | using System.IO; 7 | using System.Reflection; 8 | using System.Text; 9 | using UnityEngine; 10 | using UnityEngine.SceneManagement; 11 | using C = Console; 12 | 13 | namespace Popcron.Console 14 | { 15 | [AddComponentMenu("")] 16 | public class ConsoleWindow : MonoBehaviour 17 | { 18 | /// 19 | /// All console windows in the scene. 20 | /// 21 | public static List All { get; private set; } = new List(); 22 | 23 | private static ConsoleWindow instance; 24 | private const string ConsoleControlName = "ControlField"; 25 | private static ReadOnlyCollection emptyHistory; 26 | private static ReadOnlyCollection emptyText; 27 | private static List<(object, LogType)> lazyWriteLineOperations = new List<(object, LogType)>(); 28 | private static Dictionary logTypeToString = new Dictionary(); 29 | private static StringBuilder textBuilder = new StringBuilder(); 30 | 31 | public delegate bool OnAboutToPrint(object obj, string text, LogType type); 32 | public delegate void OnPrinted(string text, LogType type); 33 | 34 | public static OnAboutToPrint onAboutToPrint; 35 | public static OnPrinted onPrinted; 36 | 37 | /// 38 | /// The maximum amount of lines that can be shown on screen. 39 | /// 40 | private static int MaxLines 41 | { 42 | get 43 | { 44 | float fontSize = Settings.Current.FontSize; 45 | int lines = Mathf.RoundToInt(Screen.height * 0.45f / fontSize); 46 | return Mathf.Clamp(lines, 4, 64); 47 | } 48 | } 49 | 50 | /// 51 | /// The console instance. 52 | /// 53 | private static ConsoleWindow Instance 54 | { 55 | get 56 | { 57 | if (!instance) 58 | { 59 | #if UNITY_EDITOR 60 | ConsoleWindow[] consoleWindows = Resources.FindObjectsOfTypeAll(); 61 | if (consoleWindows.Length > 0) 62 | { 63 | instance = consoleWindows[0]; 64 | } 65 | #else 66 | CreateConsoleWindow(); 67 | #endif 68 | } 69 | 70 | return instance; 71 | } 72 | } 73 | 74 | /// 75 | /// The text that is currently given to the console window? 76 | /// 77 | public static string TextInput 78 | { 79 | get 80 | { 81 | if (!C.IsIncluded) 82 | { 83 | return null; 84 | } 85 | 86 | ConsoleWindow instance = Instance; 87 | if (instance) 88 | { 89 | return instance.textInput; 90 | } 91 | else 92 | { 93 | return null; 94 | } 95 | } 96 | set 97 | { 98 | if (!C.IsIncluded) 99 | { 100 | return; 101 | } 102 | 103 | ConsoleWindow instance = Instance; 104 | if (instance) 105 | { 106 | instance.textInput = Parser.Sanitize(value); 107 | } 108 | } 109 | } 110 | 111 | /// 112 | /// Is the console window currently open? 113 | /// 114 | public static bool IsOpen 115 | { 116 | get 117 | { 118 | if (!C.IsIncluded) 119 | { 120 | return false; 121 | } 122 | 123 | ConsoleWindow instance = Instance; 124 | if (instance) 125 | { 126 | return instance.isOpen; 127 | } 128 | else 129 | { 130 | return false; 131 | } 132 | } 133 | set 134 | { 135 | if (!C.IsIncluded) 136 | { 137 | return; 138 | } 139 | 140 | ConsoleWindow instance = Instance; 141 | if (instance) 142 | { 143 | instance.textInput = null; 144 | instance.isOpen = value; 145 | if (value) 146 | { 147 | instance.typedSomething = false; 148 | instance.index = Instance.history.Count; 149 | instance.Search(null); 150 | } 151 | } 152 | } 153 | } 154 | 155 | [Obsolete("This is obsolete, use Console.IsOpen instead.")] 156 | public static bool Open 157 | { 158 | get => IsOpen; 159 | set => IsOpen = value; 160 | } 161 | 162 | /// 163 | /// The scroll position of the text in the window. 164 | /// 165 | public static int ScrollPosition 166 | { 167 | get 168 | { 169 | if (!C.IsIncluded) 170 | { 171 | return 0; 172 | } 173 | 174 | ConsoleWindow instance = Instance; 175 | if (instance) 176 | { 177 | return instance.scrollPosition; 178 | } 179 | else 180 | { 181 | return 0; 182 | } 183 | } 184 | set 185 | { 186 | if (!C.IsIncluded) 187 | { 188 | return; 189 | } 190 | 191 | if (value < 0) 192 | { 193 | value = 0; 194 | } 195 | 196 | int historySize = Settings.Current.historySize; 197 | if (value >= historySize) 198 | { 199 | value = historySize - 1; 200 | } 201 | 202 | ConsoleWindow instance = Instance; 203 | if (instance) 204 | { 205 | instance.scrollPosition = value; 206 | } 207 | } 208 | } 209 | 210 | /// 211 | /// Read only list of commands that were submitted to the console. 212 | /// 213 | public static ReadOnlyCollection History 214 | { 215 | get 216 | { 217 | if (!C.IsIncluded) 218 | { 219 | if (emptyHistory == null) 220 | { 221 | emptyHistory = new List().AsReadOnly(); 222 | } 223 | 224 | return emptyHistory; 225 | } 226 | 227 | //recreate if different 228 | ConsoleWindow instance = Instance; 229 | if (instance) 230 | { 231 | if (instance.historyReadOnly == null || instance.historyReadOnly.Count != instance.history.Count) 232 | { 233 | instance.historyReadOnly = instance.history.AsReadOnly(); 234 | } 235 | 236 | return instance.historyReadOnly; 237 | } 238 | else 239 | { 240 | return null; 241 | } 242 | } 243 | } 244 | 245 | /// 246 | /// Read only list of the text that was submitted to the console (without the rich text tags). 247 | /// 248 | public static ReadOnlyCollection Text 249 | { 250 | get 251 | { 252 | if (!C.IsIncluded) 253 | { 254 | if (emptyText == null) 255 | { 256 | emptyText = new List().AsReadOnly(); 257 | } 258 | 259 | return emptyText; 260 | } 261 | 262 | //recreate if different 263 | ConsoleWindow instance = Instance; 264 | if (instance) 265 | { 266 | if (instance.textReadOnly == null || instance.textReadOnly.Count != instance.text.Count) 267 | { 268 | instance.textReadOnly = instance.text.AsReadOnly(); 269 | } 270 | 271 | return instance.textReadOnly; 272 | } 273 | else 274 | { 275 | return null; 276 | } 277 | } 278 | } 279 | 280 | [SerializeField] 281 | private bool isOpen; 282 | 283 | [SerializeField] 284 | private List text = new List(); 285 | 286 | [SerializeField] 287 | private List history = new List(); 288 | 289 | private string textInput; 290 | private int scrollPosition; 291 | private int index; 292 | private int lastMaxLines; 293 | private Type keyboardType; 294 | private bool? hasKeyboardType; 295 | private int framePressedOn; 296 | private List searchResults = new List(); 297 | private string linesString; 298 | private bool typedSomething; 299 | private List rawText = new List(); 300 | private ReadOnlyCollection historyReadOnly; 301 | private ReadOnlyCollection textReadOnly; 302 | private bool manuallyPressedEnter; 303 | 304 | public void Initialize() 305 | { 306 | text.Clear(); 307 | history.Clear(); 308 | textInput = null; 309 | isOpen = false; 310 | index = 0; 311 | linesString = null; 312 | historyReadOnly = null; 313 | textReadOnly = null; 314 | 315 | if (Application.isPlaying) 316 | { 317 | ClearLogFile(); 318 | WriteLine("Initialized"); 319 | 320 | //run the init commands 321 | foreach (string command in Settings.Current.startupCommands) 322 | { 323 | C.Run(command); 324 | } 325 | 326 | //print the lazy operations 327 | foreach ((object obj, LogType logType) operation in lazyWriteLineOperations) 328 | { 329 | WriteLine(operation.obj, operation.logType); 330 | } 331 | 332 | lazyWriteLineOperations.Clear(); 333 | } 334 | } 335 | 336 | private void OnEnable() 337 | { 338 | All.Add(this); 339 | Library.FindCategories(); 340 | Library.FindCommands(); 341 | Converter.FindConverters(); 342 | 343 | //adds a listener for debug prints in editor 344 | Application.logMessageReceived += HandleLog; 345 | } 346 | 347 | private void OnDisable() 348 | { 349 | All.Remove(this); 350 | Application.logMessageReceived -= HandleLog; 351 | } 352 | 353 | private void OnApplicationQuit() 354 | { 355 | WriteLine("OnApplicationQuit"); 356 | } 357 | 358 | private void HandleLog(string message, string stack, LogType logType) 359 | { 360 | if (!C.IsIncluded) 361 | { 362 | return; 363 | } 364 | 365 | //pass this log type through the filter first 366 | if (!Settings.Current.IsAllowed(logType)) 367 | { 368 | return; 369 | } 370 | 371 | if (logType == LogType.Log) 372 | { 373 | //logs 374 | WriteLine(message, logType); 375 | } 376 | else if (logType == LogType.Warning) 377 | { 378 | //warnings 379 | WriteLine(message, logType); 380 | } 381 | else 382 | { 383 | //if any kind of error, print the stack as well 384 | WriteLine(message + "\n" + stack, logType); 385 | } 386 | } 387 | 388 | private static string GetStringFromObject(object message) 389 | { 390 | if (message == null) 391 | { 392 | return null; 393 | } 394 | 395 | if (message is string) 396 | { 397 | return message as string; 398 | } 399 | else if (message is List listOfBytes) 400 | { 401 | textBuilder.Clear(); 402 | int spacing = 25; 403 | for (int i = 0; i < listOfBytes.Count; i++) 404 | { 405 | textBuilder.Append(listOfBytes[i].ToString("000")); 406 | textBuilder.Append(" "); 407 | if (i % spacing == 0 && i >= spacing) 408 | { 409 | textBuilder.AppendLine(); 410 | } 411 | } 412 | 413 | return textBuilder.ToString(); 414 | } 415 | else if (message is byte[] arrayOfBytes) 416 | { 417 | textBuilder.Clear(); 418 | int spacing = 25; 419 | for (int i = 0; i < arrayOfBytes.Length; i++) 420 | { 421 | textBuilder.Append(arrayOfBytes[i].ToString("000")); 422 | textBuilder.Append(" "); 423 | if (i % spacing == 0 && i >= spacing) 424 | { 425 | textBuilder.AppendLine(); 426 | } 427 | } 428 | 429 | return textBuilder.ToString(); 430 | } 431 | 432 | return message.ToString(); 433 | } 434 | 435 | /// 436 | /// Clears the console. 437 | /// 438 | public static void Clear() 439 | { 440 | if (!C.IsIncluded) 441 | { 442 | return; 443 | } 444 | 445 | ScrollPosition = 0; 446 | TextInput = ""; 447 | 448 | //clear text 449 | ConsoleWindow instance = Instance; 450 | if (instance) 451 | { 452 | instance.rawText.Clear(); 453 | instance.text.Clear(); 454 | instance.UpdateText(); 455 | } 456 | } 457 | 458 | private static void EnsureLogTypeToStringCacheExists() 459 | { 460 | if (logTypeToString.Count == 0) 461 | { 462 | logTypeToString[LogType.Assert] = "Assert"; 463 | logTypeToString[LogType.Error] = "Error"; 464 | logTypeToString[LogType.Exception] = "Exception"; 465 | logTypeToString[LogType.Log] = "Log"; 466 | logTypeToString[LogType.Warning] = "Warning"; 467 | } 468 | } 469 | 470 | /// 471 | /// Manually ask the console window to simulate the enter key. 472 | /// 473 | public static void PressedEnterKey() 474 | { 475 | ConsoleWindow instance = Instance; 476 | if (instance) 477 | { 478 | if (instance.isOpen && !string.IsNullOrEmpty(instance.textInput)) 479 | { 480 | instance.manuallyPressedEnter = true; 481 | } 482 | } 483 | } 484 | 485 | /// 486 | /// Submit generic text to the console with an optional log type. 487 | /// 488 | public static void WriteLine(object obj, LogType type = LogType.Log) 489 | { 490 | if (!C.IsIncluded) 491 | { 492 | return; 493 | } 494 | 495 | ConsoleWindow instance = Instance; 496 | if (instance) 497 | { 498 | string stringText = GetStringFromObject(obj); 499 | if (stringText == null) 500 | { 501 | return; 502 | } 503 | 504 | //invoke the event 505 | if (onAboutToPrint?.Invoke(obj, stringText, type) == false) 506 | { 507 | return; 508 | } 509 | 510 | EnsureLogTypeToStringCacheExists(); 511 | string hexColor = Settings.Current.GetColor(type); 512 | instance.Add(stringText, hexColor); 513 | LogToFile(Parser.RemoveRichText(stringText), logTypeToString[type]); 514 | 515 | //invoke the printed event 516 | onPrinted?.Invoke(stringText, type); 517 | } 518 | else 519 | { 520 | lazyWriteLineOperations.Add((obj, type)); 521 | } 522 | } 523 | 524 | /// 525 | /// Submit log text to the console with a specific hex color. 526 | /// 527 | public static void WriteLine(object obj, string hexColor) 528 | { 529 | if (!C.IsIncluded) 530 | { 531 | return; 532 | } 533 | 534 | ConsoleWindow instance = Instance; 535 | LogType type = LogType.Log; 536 | if (instance) 537 | { 538 | string stringText = GetStringFromObject(obj); 539 | if (stringText == null) 540 | { 541 | return; 542 | } 543 | 544 | //invoke the event 545 | if (onAboutToPrint?.Invoke(obj, stringText, type) == false) 546 | { 547 | return; 548 | } 549 | 550 | EnsureLogTypeToStringCacheExists(); 551 | instance.Add(stringText, hexColor); 552 | LogToFile(Parser.RemoveRichText(stringText), logTypeToString[type]); 553 | 554 | //invoke the printed event 555 | onPrinted?.Invoke(stringText, type); 556 | } 557 | else 558 | { 559 | lazyWriteLineOperations.Add((obj, type)); 560 | } 561 | } 562 | 563 | private void ClearLogFile() 564 | { 565 | string path = Settings.Current.LogFilePath; 566 | if (File.Exists(path)) 567 | { 568 | File.WriteAllText(path, ""); 569 | } 570 | } 571 | 572 | /// 573 | /// Writes this text with this log type to a file on disk. 574 | /// 575 | private static void LogToFile(string text, string logType = null) 576 | { 577 | Settings settings = Settings.Current; 578 | if (settings.logToFile) 579 | { 580 | string path = settings.LogFilePath; 581 | if (path == "./") 582 | { 583 | //if its just the 2 chars, then its not valid! 584 | settings.logToFile = false; 585 | Debug.LogError($"Log file path is invalid, it must be a path to a file. Given path is {path}"); 586 | return; 587 | } 588 | 589 | //check if it has an extension, indicating a file 590 | if (!Path.HasExtension(path)) 591 | { 592 | settings.logToFile = false; 593 | Debug.LogError($"Log file path is invalid, it must be a path to a file. Given path is {path}"); 594 | return; 595 | } 596 | 597 | //ensure dir exists 598 | string directory = Path.GetDirectoryName(path); 599 | if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) 600 | { 601 | try 602 | { 603 | Directory.CreateDirectory(directory); 604 | } 605 | catch (Exception e) 606 | { 607 | Debug.LogError($"Tried to create dir at {directory}, but error with {e.GetType()}"); 608 | } 609 | } 610 | 611 | //append thy file 612 | text = settings.FormatText(text, logType); 613 | if (text != null) 614 | { 615 | using (StreamWriter stream = File.AppendText(path)) 616 | { 617 | stream.WriteLine(text); 618 | } 619 | } 620 | } 621 | } 622 | 623 | private void Add(string input, string color) 624 | { 625 | List lines = new List(); 626 | string str = input.ToString(); 627 | if (str.IndexOf('\n') != -1) 628 | { 629 | lines.AddRange(str.Split('\n')); 630 | } 631 | else 632 | { 633 | lines.Add(str); 634 | } 635 | 636 | int historySize = Settings.Current.historySize; 637 | for (int i = 0; i < lines.Count; i++) 638 | { 639 | string line = lines[i]; 640 | textBuilder.Clear(); 641 | textBuilder.Append(""); 644 | textBuilder.Append(line); 645 | textBuilder.Append(""); 646 | 647 | rawText.Add(textBuilder.ToString()); 648 | text.Add(Parser.RemoveRichText(line)); 649 | 650 | //too many entries! 651 | if (rawText.Count > historySize) 652 | { 653 | rawText.RemoveAt(0); 654 | text.RemoveAt(0); 655 | } 656 | } 657 | 658 | int newScroll = rawText.Count - MaxLines; 659 | if (newScroll > scrollPosition) 660 | { 661 | //set scroll to bottom 662 | scrollPosition = newScroll; 663 | } 664 | 665 | //update the lines string 666 | UpdateText(); 667 | } 668 | 669 | /// 670 | /// Creates a single text string to use when displaying the console. 671 | /// 672 | private void UpdateText() 673 | { 674 | string[] lines = new string[MaxLines]; 675 | int lineIndex = 0; 676 | int rawTextCount = rawText.Count; 677 | for (int i = 0; i < rawTextCount; i++) 678 | { 679 | int index = i + scrollPosition; 680 | if (index < 0) 681 | { 682 | continue; 683 | } 684 | else if (index >= rawTextCount) 685 | { 686 | continue; 687 | } 688 | 689 | string rawTextLine = rawText[index]; 690 | if (string.IsNullOrEmpty(rawTextLine)) 691 | { 692 | break; 693 | } 694 | 695 | //replace all \t with 4 spaces 696 | lines[lineIndex] = rawTextLine.Replace("\t", " "); 697 | lineIndex++; 698 | if (lineIndex == MaxLines) 699 | { 700 | break; 701 | } 702 | } 703 | 704 | linesString = string.Join("\n", lines); 705 | } 706 | 707 | private void Update() 708 | { 709 | //max lines amount changed 710 | if (lastMaxLines != MaxLines) 711 | { 712 | lastMaxLines = MaxLines; 713 | UpdateText(); 714 | } 715 | } 716 | 717 | private bool Matches(string text, Command command) 718 | { 719 | if (command.Name.StartsWith(text)) 720 | { 721 | return true; 722 | } 723 | else 724 | { 725 | //user typed in more than the command name, so check using the args 726 | List args = Parser.GetParameters(text); 727 | int userArgs = args.Count; 728 | if (userArgs > 0) 729 | { 730 | //check if the first arg is the command itself and that the args length is ok 731 | if (args[0] == command.Name && userArgs - 1 <= command.Parameters.Count) 732 | { 733 | return true; 734 | } 735 | else 736 | { 737 | return false; 738 | } 739 | } 740 | else 741 | { 742 | return false; 743 | } 744 | } 745 | } 746 | 747 | private void Search(string text) 748 | { 749 | //search through all commands 750 | searchResults.Clear(); 751 | if (string.IsNullOrEmpty(text)) 752 | { 753 | return; 754 | } 755 | 756 | //if it starts with a @, then concat 757 | bool instanceOnly = false; 758 | if (text.Length > 1 && text.StartsWith(Parser.IDPrefix)) 759 | { 760 | int index = text.IndexOf(' '); 761 | if (index != -1) 762 | { 763 | //remove everything before the space, which is what separates id from command 764 | text = text.Substring(index + 1); 765 | instanceOnly = true; 766 | if (string.IsNullOrEmpty(text)) 767 | { 768 | return; 769 | } 770 | } 771 | else 772 | { 773 | text = null; 774 | return; 775 | } 776 | } 777 | 778 | //fill in the suggestion text 779 | int categoryCount = Library.Categories.Count; 780 | for (int y = 0; y < categoryCount; y++) 781 | { 782 | Category category = Library.Categories[y]; 783 | if (category == null) 784 | { 785 | continue; 786 | } 787 | 788 | int commandCount = category.Commands.Count; 789 | for (int c = 0; c < commandCount; c++) 790 | { 791 | Command command = category.Commands[c]; 792 | if (Matches(text, command)) 793 | { 794 | textBuilder.Clear(); 795 | 796 | //not static, so show that it needs an @id 797 | if (!command.IsStatic) 798 | { 799 | textBuilder.Append(Parser.IDPrefix); 800 | textBuilder.Append("id "); 801 | } 802 | 803 | if (instanceOnly && command.IsStatic) 804 | { 805 | continue; 806 | } 807 | 808 | //append the name 809 | int nameCount = command.Names.Count; 810 | for (int n = 0; n < nameCount; n++) 811 | { 812 | string name = command.Names[n]; 813 | textBuilder.Append(name); 814 | if (n != nameCount - 1) 815 | { 816 | textBuilder.Append("/"); 817 | } 818 | } 819 | 820 | if (command.Member is MethodInfo) 821 | { 822 | foreach (string parameter in command.Parameters) 823 | { 824 | textBuilder.Append(" "); 825 | textBuilder.Append(Parser.LeftAngleBracket); 826 | textBuilder.Append(parameter); 827 | textBuilder.Append(Parser.RightAngleBracket); 828 | } 829 | } 830 | else if (command.Member is PropertyInfo property) 831 | { 832 | MethodInfo set = property.GetSetMethod(); 833 | if (set != null) 834 | { 835 | textBuilder.Append(" [value]"); 836 | } 837 | } 838 | else if (command.Member is FieldInfo) 839 | { 840 | textBuilder.Append(" [value]"); 841 | } 842 | 843 | if (!string.IsNullOrEmpty(command.Description)) 844 | { 845 | textBuilder.Append(" = "); 846 | textBuilder.Append(command.Description); 847 | } 848 | 849 | SearchResult result = new SearchResult(textBuilder.ToString(), command.Name); 850 | searchResults.Add(result); 851 | } 852 | } 853 | } 854 | } 855 | 856 | /// 857 | /// Moves the caret to the end of the text field. 858 | /// 859 | private void MoveCaretToEnd() 860 | { 861 | TextEditor te = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl); 862 | if (te != null) 863 | { 864 | te.MoveCursorToPosition(new Vector2(int.MaxValue, int.MaxValue)); 865 | } 866 | } 867 | 868 | private Type GetKeyboardType() 869 | { 870 | if (keyboardType == null && hasKeyboardType != false) 871 | { 872 | Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); 873 | for (int a = 0; a < assemblies.Length; a++) 874 | { 875 | Type[] types = assemblies[a].GetTypes(); 876 | for (int t = 0; t < types.Length; t++) 877 | { 878 | if (types[t].FullName.Equals("UnityEngine.InputSystem.Keyboard")) 879 | { 880 | //found the keyboard type from the input system 881 | keyboardType = types[t]; 882 | return keyboardType; 883 | } 884 | } 885 | } 886 | 887 | hasKeyboardType = false; 888 | } 889 | 890 | return null; 891 | } 892 | 893 | /// 894 | /// Returns true if the key to open the console window was opened. 895 | /// 896 | private bool CheckForOpen() 897 | { 898 | if (!Settings.Current.checkForOpenInput) 899 | { 900 | return false; 901 | } 902 | 903 | //try with input system if it exists 904 | Type keyboardType = GetKeyboardType(); 905 | if (keyboardType != null) 906 | { 907 | //get the static `current` property and the `enterKey` property 908 | PropertyInfo current = keyboardType.GetProperty("current"); 909 | PropertyInfo backquoteKey = keyboardType.GetProperty("backquoteKey"); 910 | 911 | //get the value from it 912 | object keyboardValue = current.GetValue(null); 913 | if (keyboardValue != null) 914 | { 915 | //get the `wasPressedThisFrame` property from the base type 916 | //enter Key is a KeyControl, which derives from ButtonControl, which has this property 917 | object enterKeyValue = backquoteKey.GetValue(keyboardValue); 918 | PropertyInfo wasPressedThisFrame = enterKeyValue.GetType().BaseType.GetProperty("wasPressedThisFrame"); 919 | 920 | //get the value from it 921 | bool yay = (bool)wasPressedThisFrame.GetValue(enterKeyValue); 922 | if (yay) 923 | { 924 | if (framePressedOn != Time.frameCount) 925 | { 926 | framePressedOn = Time.frameCount; 927 | return yay; 928 | } 929 | } 930 | } 931 | 932 | return false; 933 | } 934 | 935 | //try with the gui system 936 | if (Event.current.isKey && Event.current.type == EventType.KeyDown) 937 | { 938 | if (Settings.Current.IsConsoleOpenChar(Event.current.character)) 939 | { 940 | return true; 941 | } 942 | } 943 | 944 | return false; 945 | } 946 | 947 | /// 948 | /// Returns true if enter key was pressed. 949 | /// 950 | private bool CheckForEnter() 951 | { 952 | //try with input system if it exists 953 | Type keyboardType = GetKeyboardType(); 954 | if (keyboardType != null) 955 | { 956 | //get the static `current` property and the `enterKey` property 957 | PropertyInfo current = keyboardType.GetProperty("current"); 958 | PropertyInfo enterKey = keyboardType.GetProperty("enterKey"); 959 | 960 | //get the value from it 961 | object keyboardValue = current.GetValue(null); 962 | if (keyboardValue != null) 963 | { 964 | //get the `wasPressedThisFrame` property from the base type 965 | //enter Key is a KeyControl, which derives from ButtonControl, which has this property 966 | object enterKeyValue = enterKey.GetValue(keyboardValue); 967 | PropertyInfo wasPressedThisFrame = enterKeyValue.GetType().BaseType.GetProperty("wasPressedThisFrame"); 968 | 969 | //get the value from it 970 | bool yay = (bool)wasPressedThisFrame.GetValue(enterKeyValue); 971 | if (yay) 972 | { 973 | if (framePressedOn != Time.frameCount) 974 | { 975 | framePressedOn = Time.frameCount; 976 | manuallyPressedEnter = false; 977 | return true; 978 | } 979 | } 980 | } 981 | } 982 | 983 | //try with the gui system 984 | if (Event.current.isKey && Event.current.type == EventType.KeyDown) 985 | { 986 | bool enter = Event.current.character == '\n' || Event.current.character == '\r' || Event.current.keyCode == KeyCode.Return; 987 | if (enter) 988 | { 989 | manuallyPressedEnter = false; 990 | return true; 991 | } 992 | } 993 | 994 | if (manuallyPressedEnter) 995 | { 996 | manuallyPressedEnter = false; 997 | return true; 998 | } 999 | 1000 | return false; 1001 | } 1002 | 1003 | private GUIStyle GetStyle() 1004 | { 1005 | GUIStyle style = Settings.Current.consoleStyle; 1006 | if (!style.normal.background) 1007 | { 1008 | style.normal.background = Settings.Pixel; 1009 | } 1010 | 1011 | if (!style.hover.background) 1012 | { 1013 | style.hover.background = Settings.Pixel; 1014 | } 1015 | 1016 | if (!style.active.background) 1017 | { 1018 | style.active.background = Settings.Pixel; 1019 | } 1020 | 1021 | return style; 1022 | } 1023 | 1024 | private void OnGUI() 1025 | { 1026 | bool moveToEnd = false; 1027 | Event current = Event.current; 1028 | if (CheckForOpen()) 1029 | { 1030 | IsOpen = !IsOpen; 1031 | current.Use(); 1032 | return; 1033 | } 1034 | 1035 | //dont show the console if it shouldnt be open 1036 | //duh 1037 | if (!isOpen) 1038 | { 1039 | return; 1040 | } 1041 | 1042 | //view scrolling 1043 | if (current.type == EventType.ScrollWheel) 1044 | { 1045 | int scrollDirection = (int)Mathf.Sign(current.delta.y) * Settings.Current.scrollAmount; 1046 | ScrollPosition += scrollDirection; 1047 | UpdateText(); 1048 | current.Use(); 1049 | return; 1050 | } 1051 | 1052 | //history scrolling 1053 | if (current.type == EventType.KeyDown) 1054 | { 1055 | if (current.keyCode == KeyCode.UpArrow) 1056 | { 1057 | if (!typedSomething) 1058 | { 1059 | index--; 1060 | if (index < -1) 1061 | { 1062 | index = -1; 1063 | textInput = ""; 1064 | moveToEnd = true; 1065 | } 1066 | else 1067 | { 1068 | if (index >= 0 && index < history.Count) 1069 | { 1070 | textInput = history[index]; 1071 | moveToEnd = true; 1072 | } 1073 | } 1074 | } 1075 | else 1076 | { 1077 | index--; 1078 | if (index <= -1) 1079 | { 1080 | index = -1; 1081 | textInput = ""; 1082 | moveToEnd = true; 1083 | } 1084 | else 1085 | { 1086 | if (index >= 0 && index < searchResults.Count) 1087 | { 1088 | textInput = searchResults[index].Command; 1089 | moveToEnd = true; 1090 | } 1091 | } 1092 | } 1093 | } 1094 | else if (current.keyCode == KeyCode.DownArrow) 1095 | { 1096 | if (!typedSomething) 1097 | { 1098 | index++; 1099 | if (index > history.Count) 1100 | { 1101 | index = history.Count; 1102 | textInput = ""; 1103 | moveToEnd = true; 1104 | } 1105 | else 1106 | { 1107 | if (index >= 0 && index < history.Count) 1108 | { 1109 | textInput = history[index]; 1110 | moveToEnd = true; 1111 | } 1112 | } 1113 | } 1114 | else 1115 | { 1116 | index++; 1117 | if (index >= searchResults.Count) 1118 | { 1119 | index = searchResults.Count; 1120 | textInput = ""; 1121 | moveToEnd = true; 1122 | } 1123 | else 1124 | { 1125 | if (index >= 0 && index < searchResults.Count) 1126 | { 1127 | textInput = searchResults[index].Command; 1128 | moveToEnd = true; 1129 | } 1130 | } 1131 | } 1132 | } 1133 | } 1134 | 1135 | //go to home 1136 | if (current.type == EventType.KeyDown) 1137 | { 1138 | if (current.keyCode == KeyCode.Home) 1139 | { 1140 | ScrollPosition = 0; 1141 | UpdateText(); 1142 | current.Use(); 1143 | return; 1144 | } 1145 | else if (current.keyCode == KeyCode.End) 1146 | { 1147 | ScrollPosition = rawText.Count - MaxLines; 1148 | UpdateText(); 1149 | current.Use(); 1150 | return; 1151 | } 1152 | else if (current.keyCode == KeyCode.PageUp) 1153 | { 1154 | ScrollPosition -= MaxLines; 1155 | UpdateText(); 1156 | current.Use(); 1157 | return; 1158 | } 1159 | else if (current.keyCode == KeyCode.PageDown) 1160 | { 1161 | ScrollPosition += MaxLines; 1162 | UpdateText(); 1163 | current.Use(); 1164 | return; 1165 | } 1166 | } 1167 | 1168 | //draw elements 1169 | GUIStyle style = GetStyle(); 1170 | Color oldColor = GUI.color; 1171 | GUI.color = Color.white; 1172 | GUI.depth = -5; 1173 | int lineHeight = style.fontSize + 4; 1174 | 1175 | GUILayout.Box(linesString, style, GUILayout.Width(Screen.width)); 1176 | Rect lastControl = GUILayoutUtility.GetLastRect(); 1177 | 1178 | //draw the typing field 1179 | GUI.Box(new Rect(0, lastControl.y + lastControl.height, Screen.width, 2), "", style); 1180 | 1181 | GUI.SetNextControlName(ConsoleControlName); 1182 | string text = GUI.TextField(new Rect(0, lastControl.y + lastControl.height + 1, Screen.width, lineHeight), Parser.Sanitize(textInput), style); 1183 | GUI.FocusControl(ConsoleControlName); 1184 | 1185 | if (moveToEnd) 1186 | { 1187 | MoveCaretToEnd(); 1188 | } 1189 | 1190 | //text changed, search 1191 | if (textInput != text) 1192 | { 1193 | if (!typedSomething) 1194 | { 1195 | typedSomething = true; 1196 | index = -1; 1197 | } 1198 | 1199 | if (string.IsNullOrEmpty(text) && typedSomething) 1200 | { 1201 | typedSomething = false; 1202 | index = history.Count; 1203 | } 1204 | 1205 | textInput = text; 1206 | Search(text); 1207 | } 1208 | 1209 | //display the search suggestions 1210 | GUI.color = new Color(1f, 1f, 1f, 0.4f); 1211 | textBuilder.Clear(); 1212 | for (int i = 0; i < searchResults.Count; i++) 1213 | { 1214 | string searchResultText = Parser.Sanitize(searchResults[i].Text); 1215 | if (i == searchResults.Count - 1) 1216 | { 1217 | textBuilder.Append(searchResultText); 1218 | } 1219 | else 1220 | { 1221 | textBuilder.AppendLine(searchResultText); 1222 | } 1223 | } 1224 | 1225 | string suggestions = textBuilder.ToString(); 1226 | GUI.Box(new Rect(0, lastControl.y + lastControl.height + 1 + lineHeight, Screen.width, searchResults.Count * lineHeight), suggestions, style); 1227 | GUI.color = oldColor; 1228 | 1229 | //pressing enter to run command 1230 | if (!string.IsNullOrEmpty(textInput) && CheckForEnter()) 1231 | { 1232 | LogToFile(textInput, "Input"); 1233 | Add(textInput, Settings.Current.UserColor); 1234 | 1235 | //add to history 1236 | history.Add(textInput); 1237 | index = history.Count; 1238 | 1239 | Search(null); 1240 | C.Run(textInput); 1241 | Event.current.Use(); 1242 | 1243 | textInput = ""; 1244 | typedSomething = false; 1245 | } 1246 | } 1247 | 1248 | /// 1249 | /// Creates a new console window instance without initializing it. 1250 | /// 1251 | public static void CreateConsoleWindow() 1252 | { 1253 | if (!C.IsIncluded) 1254 | { 1255 | return; 1256 | } 1257 | 1258 | //is this scene blacklisted? 1259 | Settings settings = Settings.Current; 1260 | if (settings && settings.IsSceneBlacklisted()) 1261 | { 1262 | Scene currentScene = SceneManager.GetActiveScene(); 1263 | Debug.LogWarning($"Console window will not be created in blacklisted scene {currentScene.name}"); 1264 | return; 1265 | } 1266 | 1267 | instance = new GameObject(nameof(ConsoleWindow)).AddComponent(); 1268 | const HideFlags Flags = HideFlags.DontSaveInBuild | HideFlags.NotEditable; 1269 | instance.gameObject.hideFlags = Flags; 1270 | instance.Initialize(); 1271 | DontDestroyOnLoad(instance.gameObject); 1272 | } 1273 | } 1274 | } -------------------------------------------------------------------------------- /Runtime/ConsoleWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 468bf8789881567489322e39386c5288 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Converters.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a796544f6e5535f45a03af3acee6afdd 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Converters/BooleanConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Popcron.Console 4 | { 5 | public class BooleanConverter : Converter 6 | { 7 | public override Type Type 8 | { 9 | get 10 | { 11 | return typeof(bool); 12 | } 13 | } 14 | 15 | public BooleanConverter() { } 16 | 17 | public override object Convert(string value) 18 | { 19 | return value == "1" || value.ToLower() == "true"; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Runtime/Converters/BooleanConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b36d2710831a0a3438af3b6e972f7a77 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Converters/ByteConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Popcron.Console 4 | { 5 | public class ByteConverter : Converter 6 | { 7 | public override Type Type 8 | { 9 | get 10 | { 11 | return typeof(byte); 12 | } 13 | } 14 | 15 | public ByteConverter() { } 16 | 17 | public override object Convert(string value) 18 | { 19 | byte result; 20 | if (byte.TryParse(value, out result)) 21 | { 22 | return result; 23 | } 24 | else 25 | { 26 | return null; 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Runtime/Converters/ByteConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3f784d7b63b526943843d95639d3f607 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Converters/CharConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Popcron.Console 4 | { 5 | public class CharConverter : Converter 6 | { 7 | public override Type Type 8 | { 9 | get 10 | { 11 | return typeof(char); 12 | } 13 | } 14 | 15 | public CharConverter() { } 16 | 17 | public override object Convert(string value) 18 | { 19 | if (value == null) return null; 20 | 21 | string str = value.ToString(); 22 | if (str.Length == 0) return null; 23 | 24 | return str[0]; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /Runtime/Converters/CharConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e4019913f58177a4ab1a09ac5315f71d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Converters/Converter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | 5 | namespace Popcron.Console 6 | { 7 | public abstract class Converter 8 | { 9 | private static Dictionary typeToConverter = null; 10 | 11 | public static List Converters 12 | { 13 | get 14 | { 15 | List converters = new List(); 16 | 17 | foreach (var converter in typeToConverter) 18 | { 19 | converters.Add(converter.Value); 20 | } 21 | 22 | return converters; 23 | } 24 | } 25 | 26 | public static Converter GetConverter(Type type) 27 | { 28 | if (typeToConverter == null) 29 | { 30 | FindConverters(); 31 | } 32 | 33 | if (typeToConverter.TryGetValue(type, out Converter converter)) 34 | { 35 | return converter; 36 | } 37 | else 38 | { 39 | return null; 40 | } 41 | } 42 | 43 | public static void FindConverters() 44 | { 45 | //add the default ones first 46 | typeToConverter = new Dictionary 47 | { 48 | { typeof(bool), new BooleanConverter() }, 49 | { typeof(string), new StringConverter() }, 50 | { typeof(sbyte), new SByteConverter() }, 51 | { typeof(byte), new ByteConverter() }, 52 | { typeof(short), new ShortConverter() }, 53 | { typeof(ushort), new UShortConverter() }, 54 | { typeof(int), new IntConverter() }, 55 | { typeof(uint), new UIntConverter() }, 56 | { typeof(long), new LongConverter() }, 57 | { typeof(ulong), new ULongConverter() }, 58 | { typeof(float), new FloatConverter() }, 59 | { typeof(double), new DoubleConverter() }, 60 | { typeof(char), new CharConverter() }, 61 | { typeof(Type), new TypeConverter() }, 62 | { typeof(DateTime), new DateTimeConverter() }, 63 | { typeof(object), new ObjectConverter() } 64 | }; 65 | 66 | //then find any extras 67 | Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); 68 | foreach (var assembly in assemblies) 69 | { 70 | Type[] types = assembly.GetTypes(); 71 | foreach (Type type in types) 72 | { 73 | if (type.IsAbstract) continue; 74 | 75 | bool isSubclass = type.IsSubclassOf(typeof(Converter)); 76 | if (!isSubclass) continue; 77 | 78 | Converter converter = (Converter)Activator.CreateInstance(type); 79 | if (typeToConverter.ContainsKey(converter.Type)) continue; 80 | 81 | typeToConverter.Add(converter.Type, converter); 82 | } 83 | } 84 | } 85 | 86 | public abstract Type Type { get; } 87 | public abstract object Convert(string value); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Runtime/Converters/Converter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 92ff6395a5ed46c448713f6c4ea4d15c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Converters/DateTimeConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Popcron.Console 4 | { 5 | public class DateTimeConverter : Converter 6 | { 7 | public override Type Type 8 | { 9 | get 10 | { 11 | return typeof(DateTime); 12 | } 13 | } 14 | 15 | public DateTimeConverter() { } 16 | 17 | public override object Convert(string value) 18 | { 19 | DateTime result; 20 | if (DateTime.TryParse(value, out result)) 21 | { 22 | return result; 23 | } 24 | else 25 | { 26 | return null; 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Runtime/Converters/DateTimeConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e73f9f0a9c7bbd24da1afa10d790fd6f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Converters/DoubleConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace Popcron.Console 5 | { 6 | public class DoubleConverter : Converter 7 | { 8 | public override Type Type 9 | { 10 | get 11 | { 12 | return typeof(double); 13 | } 14 | } 15 | 16 | public DoubleConverter() { } 17 | 18 | public override object Convert(string value) 19 | { 20 | double result; 21 | if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out result)) 22 | { 23 | return result; 24 | } 25 | else 26 | { 27 | return null; 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Runtime/Converters/DoubleConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: da95208c3b9df2a49a5c7de17b3936a8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Converters/FloatConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace Popcron.Console 5 | { 6 | public class FloatConverter : Converter 7 | { 8 | public override Type Type 9 | { 10 | get 11 | { 12 | return typeof(float); 13 | } 14 | } 15 | 16 | public FloatConverter() { } 17 | 18 | public override object Convert(string value) 19 | { 20 | float result; 21 | if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out result)) 22 | { 23 | return result; 24 | } 25 | else 26 | { 27 | return null; 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Runtime/Converters/FloatConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8bdbbe11cd584ce49a036461643a5305 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Converters/IntConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Popcron.Console 4 | { 5 | public class IntConverter : Converter 6 | { 7 | public override Type Type 8 | { 9 | get 10 | { 11 | return typeof(int); 12 | } 13 | } 14 | 15 | public IntConverter() { } 16 | 17 | public override object Convert(string value) 18 | { 19 | int result; 20 | if (int.TryParse(value, out result)) 21 | { 22 | return result; 23 | } 24 | else 25 | { 26 | return null; 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Runtime/Converters/IntConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 07ebea1cb1fd59d4ca0f33cf0df61549 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Converters/LongConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Popcron.Console 4 | { 5 | public class LongConverter : Converter 6 | { 7 | public override Type Type 8 | { 9 | get 10 | { 11 | return typeof(long); 12 | } 13 | } 14 | 15 | public LongConverter() { } 16 | 17 | public override object Convert(string value) 18 | { 19 | long result; 20 | if (long.TryParse(value, out result)) 21 | { 22 | return result; 23 | } 24 | else 25 | { 26 | return null; 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Runtime/Converters/LongConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8a11999544d21cb488a39bd718e679f7 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Converters/ObjectConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Popcron.Console 4 | { 5 | public class ObjectConverter : Converter 6 | { 7 | public override Type Type 8 | { 9 | get 10 | { 11 | return typeof(object); 12 | } 13 | } 14 | 15 | public ObjectConverter() { } 16 | 17 | public override object Convert(string value) 18 | { 19 | return value as object; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Runtime/Converters/ObjectConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: be352e295ea51244f8044b566314ca41 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Converters/SByteConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Popcron.Console 4 | { 5 | public class SByteConverter : Converter 6 | { 7 | public override Type Type 8 | { 9 | get 10 | { 11 | return typeof(sbyte); 12 | } 13 | } 14 | 15 | public SByteConverter() { } 16 | 17 | public override object Convert(string value) 18 | { 19 | sbyte result; 20 | if (sbyte.TryParse(value, out result)) 21 | { 22 | return result; 23 | } 24 | else 25 | { 26 | return null; 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Runtime/Converters/SByteConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9369da93ca3939e46b3671d72a230185 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Converters/ShortConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Popcron.Console 4 | { 5 | public class ShortConverter : Converter 6 | { 7 | public override Type Type 8 | { 9 | get 10 | { 11 | return typeof(short); 12 | } 13 | } 14 | 15 | public ShortConverter() { } 16 | 17 | public override object Convert(string value) 18 | { 19 | short result; 20 | if (short.TryParse(value, out result)) 21 | { 22 | return result; 23 | } 24 | else 25 | { 26 | return null; 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Runtime/Converters/ShortConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 29f29f3a6d83c784989fce68a1fe8752 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Converters/StringConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Popcron.Console 4 | { 5 | public class StringConverter : Converter 6 | { 7 | public override Type Type 8 | { 9 | get 10 | { 11 | return typeof(string); 12 | } 13 | } 14 | 15 | public StringConverter() { } 16 | 17 | public override object Convert(string value) 18 | { 19 | return value; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Runtime/Converters/StringConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2da2ef2a3b5c8a540823c74b9156c955 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Converters/TypeConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Popcron.Console 4 | { 5 | public class TypeConverter : Converter 6 | { 7 | public override Type Type 8 | { 9 | get 10 | { 11 | return typeof(Type); 12 | } 13 | } 14 | 15 | public TypeConverter() { } 16 | 17 | public override object Convert(string value) 18 | { 19 | Type type = Type.GetType(value); 20 | return type; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Runtime/Converters/TypeConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e8f017c6c2ddbb34eae560f5b0fb8906 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Converters/UIntConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Popcron.Console 4 | { 5 | public class UIntConverter : Converter 6 | { 7 | public override Type Type 8 | { 9 | get 10 | { 11 | return typeof(uint); 12 | } 13 | } 14 | 15 | public UIntConverter() { } 16 | 17 | public override object Convert(string value) 18 | { 19 | uint result; 20 | if (uint.TryParse(value, out result)) 21 | { 22 | return result; 23 | } 24 | else 25 | { 26 | return null; 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Runtime/Converters/UIntConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 672d46808e560564b927806f209c5e32 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Converters/ULongConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Popcron.Console 4 | { 5 | public class ULongConverter : Converter 6 | { 7 | public override Type Type 8 | { 9 | get 10 | { 11 | return typeof(ulong); 12 | } 13 | } 14 | 15 | public ULongConverter() { } 16 | 17 | public override object Convert(string value) 18 | { 19 | ulong result; 20 | if (ulong.TryParse(value, out result)) 21 | { 22 | return result; 23 | } 24 | else 25 | { 26 | return null; 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Runtime/Converters/ULongConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 01dc033eb869ad345a5b96c0a11870bf 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Converters/UShortConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Popcron.Console 4 | { 5 | public class UShortConverter : Converter 6 | { 7 | public override Type Type 8 | { 9 | get 10 | { 11 | return typeof(ushort); 12 | } 13 | } 14 | 15 | public UShortConverter() { } 16 | 17 | public override object Convert(string value) 18 | { 19 | ushort result; 20 | if (ushort.TryParse(value, out result)) 21 | { 22 | return result; 23 | } 24 | else 25 | { 26 | return null; 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Runtime/Converters/UShortConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5ebf1e5f66b051f49b736b3e394da461 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 570335ddd5a911d46897083464798e1d 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Editor/EnsureSettingsExists.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using UnityEditor; 6 | using UnityEngine; 7 | 8 | namespace Popcron.Console 9 | { 10 | [InitializeOnLoad] 11 | public class EnsureSettingsExist 12 | { 13 | [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] 14 | private static void RuntimeInitialize() 15 | { 16 | ClearAllConsoleWindows(); 17 | ConsoleWindow.CreateConsoleWindow(); 18 | CreateSettingsIfMissing(); 19 | } 20 | 21 | static EnsureSettingsExist() 22 | { 23 | CreateSettingsIfMissing(); 24 | } 25 | 26 | /// 27 | /// Clears all console windows found in all scenes. 28 | /// 29 | public static void ClearAllConsoleWindows() 30 | { 31 | ConsoleWindow[] consoleWindows = Resources.FindObjectsOfTypeAll(); 32 | for (int i = consoleWindows.Length - 1; i >= 0; i--) 33 | { 34 | if (Application.isPlaying) 35 | { 36 | Object.Destroy(consoleWindows[i].gameObject); 37 | } 38 | else 39 | { 40 | Object.DestroyImmediate(consoleWindows[i].gameObject); 41 | } 42 | } 43 | } 44 | 45 | /// 46 | /// Puts a console object into the currently open scene. 47 | /// 48 | private static void CreateSettingsIfMissing() 49 | { 50 | if (!FindSettings()) 51 | { 52 | //make a file here 53 | string path = "Assets/Console Settings.asset"; 54 | 55 | Settings settings = ScriptableObject.CreateInstance(); 56 | settings.name = "Console Settings"; 57 | settings.consoleStyle = GetDefaultStyle(); 58 | AssetDatabase.CreateAsset(settings, path); 59 | AssetDatabase.Refresh(); 60 | PreloadSettings(settings); 61 | } 62 | } 63 | 64 | /// 65 | /// Returns the default console style. 66 | /// 67 | private static GUIStyle GetDefaultStyle() 68 | { 69 | Font font = Resources.Load("CascadiaMono"); 70 | GUIStyle consoleStyle = new GUIStyle 71 | { 72 | name = "Console", 73 | richText = true, 74 | alignment = TextAnchor.UpperLeft, 75 | font = font, 76 | fontSize = 14 77 | }; 78 | 79 | consoleStyle.normal.textColor = Color.white; 80 | consoleStyle.hover.textColor = Color.white; 81 | consoleStyle.active.textColor = Color.white; 82 | return consoleStyle; 83 | } 84 | 85 | public static Settings FindSettings() 86 | { 87 | Object[] preloadedAssets = PlayerSettings.GetPreloadedAssets(); 88 | foreach (Object preloadedAsset in preloadedAssets) 89 | { 90 | if (preloadedAsset && preloadedAsset is Settings) 91 | { 92 | return preloadedAsset as Settings; 93 | } 94 | } 95 | 96 | string[] guids = AssetDatabase.FindAssets($"t:{typeof(Settings).FullName}"); 97 | if (guids.Length > 0) 98 | { 99 | string path = AssetDatabase.GUIDToAssetPath(guids[0]); 100 | Settings settings = AssetDatabase.LoadAssetAtPath(path); 101 | PreloadSettings(settings); 102 | return settings; 103 | } 104 | 105 | return null; 106 | } 107 | 108 | private static void PreloadSettings(Settings settings) 109 | { 110 | List preloadedAssets = PlayerSettings.GetPreloadedAssets().ToList(); 111 | bool preloaded = false; 112 | for (int i = 0; i < preloadedAssets.Count; i++) 113 | { 114 | Object preloadedAsset = preloadedAssets[i]; 115 | if (!preloadedAsset || preloadedAsset is Settings) 116 | { 117 | preloadedAssets[i] = settings; 118 | preloaded = true; 119 | break; 120 | } 121 | } 122 | 123 | if (!preloaded) 124 | { 125 | preloadedAssets.Add(settings); 126 | } 127 | 128 | PlayerSettings.SetPreloadedAssets(preloadedAssets.ToArray()); 129 | } 130 | } 131 | } 132 | #endif -------------------------------------------------------------------------------- /Runtime/Editor/EnsureSettingsExists.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d17a5c2b6eb7e3c4a8219765551f8f07 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Editor/SettingsInspector.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using System; 3 | using UnityEditor; 4 | using UnityEditorInternal; 5 | using UnityEngine; 6 | 7 | namespace Popcron.Console 8 | { 9 | [CustomEditor(typeof(Settings))] 10 | public class SettingsInspector : Editor 11 | { 12 | private static bool ShowAllowanceFilter 13 | { 14 | get => EditorPrefs.GetBool("Popcron.Console.ShowAllowanceFilter"); 15 | set => EditorPrefs.SetBool("Popcron.Console.ShowAllowanceFilter", value); 16 | } 17 | 18 | public static void Show(SerializedObject serializedObject) 19 | { 20 | if (serializedObject == null || !(serializedObject.targetObject is Settings)) 21 | { 22 | return; 23 | } 24 | 25 | Settings settings = serializedObject.targetObject as Settings; 26 | SerializedProperty userColor = serializedObject.FindProperty("userColor"); 27 | SerializedProperty logColor = serializedObject.FindProperty("logColor"); 28 | SerializedProperty warnColor = serializedObject.FindProperty("warnColor"); 29 | SerializedProperty errorColor = serializedObject.FindProperty("errorColor"); 30 | SerializedProperty exceptionColor = serializedObject.FindProperty("exceptionColor"); 31 | SerializedProperty consoleStyle = serializedObject.FindProperty("consoleStyle"); 32 | SerializedProperty startupCommands = serializedObject.FindProperty("startupCommands"); 33 | SerializedProperty scrollAmount = serializedObject.FindProperty("scrollAmount"); 34 | SerializedProperty historySize = serializedObject.FindProperty("historySize"); 35 | SerializedProperty logToFile = serializedObject.FindProperty("logToFile"); 36 | SerializedProperty reportUnknownCommand = serializedObject.FindProperty("reportUnknownCommand"); 37 | SerializedProperty formatting = serializedObject.FindProperty("formatting"); 38 | SerializedProperty checkForOpenInput = serializedObject.FindProperty("checkForOpenInput"); 39 | 40 | //show the colours first 41 | EditorGUILayout.PropertyField(userColor, new GUIContent("User input")); 42 | EditorGUILayout.PropertyField(logColor, new GUIContent("Log and prints")); 43 | EditorGUILayout.PropertyField(warnColor, new GUIContent("Warnings")); 44 | EditorGUILayout.PropertyField(errorColor, new GUIContent("Errors")); 45 | EditorGUILayout.PropertyField(exceptionColor, new GUIContent("Exceptions")); 46 | 47 | //show the other garbage 48 | EditorGUILayout.PropertyField(consoleStyle, new GUIContent("Style")); 49 | EditorGUILayout.PropertyField(startupCommands, new GUIContent("Commands at startup"), true); 50 | 51 | ShowBlacklistedScenes(settings, serializedObject); 52 | ShowAssemblies(settings, serializedObject); 53 | 54 | //allowance filter 55 | ShowAllowanceFilter = EditorGUILayout.Foldout(ShowAllowanceFilter, "Filter", true); 56 | if (ShowAllowanceFilter) 57 | { 58 | SerializedProperty allowAsserts = serializedObject.FindProperty("allowAsserts"); 59 | SerializedProperty allowErrors = serializedObject.FindProperty("allowErrors"); 60 | SerializedProperty allowExceptions = serializedObject.FindProperty("allowExceptions"); 61 | SerializedProperty allowLogs = serializedObject.FindProperty("allowLogs"); 62 | SerializedProperty allowWarnings = serializedObject.FindProperty("allowWarnings"); 63 | 64 | EditorGUI.indentLevel++; 65 | EditorGUILayout.PropertyField(allowAsserts, new GUIContent("Asserts")); 66 | EditorGUILayout.PropertyField(allowErrors, new GUIContent("Errors")); 67 | EditorGUILayout.PropertyField(allowExceptions, new GUIContent("Exceptions")); 68 | EditorGUILayout.PropertyField(allowLogs, new GUIContent("Logs")); 69 | EditorGUILayout.PropertyField(allowWarnings, new GUIContent("Warnings")); 70 | EditorGUI.indentLevel--; 71 | } 72 | 73 | EditorGUILayout.PropertyField(reportUnknownCommand, new GUIContent("Report unknown command")); 74 | EditorGUILayout.PropertyField(formatting, new GUIContent("Formatting")); 75 | EditorGUILayout.PropertyField(scrollAmount, new GUIContent("Scroll amount")); 76 | EditorGUILayout.PropertyField(historySize, new GUIContent("History size")); 77 | 78 | //show the keys that gon be used 79 | EditorGUILayout.PropertyField(checkForOpenInput, new GUIContent("Built-in open check")); 80 | { 81 | EditorGUI.BeginDisabledGroup(!checkForOpenInput.boolValue); 82 | SerializedProperty consoleChararacters = serializedObject.FindProperty("consoleChararacters"); 83 | 84 | EditorGUI.indentLevel++; 85 | EditorGUILayout.PropertyField(consoleChararacters, new GUIContent("Console open keys")); 86 | EditorGUI.indentLevel--; 87 | EditorGUI.EndDisabledGroup(); 88 | } 89 | 90 | EditorGUILayout.PropertyField(logToFile, new GUIContent("Log to file")); 91 | { 92 | EditorGUI.BeginDisabledGroup(!logToFile.boolValue); 93 | SerializedProperty logFilePathPlayer = serializedObject.FindProperty("logFilePathPlayer"); 94 | SerializedProperty logFilePathEditor = serializedObject.FindProperty("logFilePathEditor"); 95 | 96 | EditorGUI.indentLevel++; 97 | EditorGUILayout.PropertyField(logFilePathPlayer, new GUIContent("Log path for player")); 98 | EditorGUILayout.PropertyField(logFilePathEditor, new GUIContent("Log path for editor")); 99 | EditorGUI.indentLevel--; 100 | EditorGUI.EndDisabledGroup(); 101 | } 102 | 103 | serializedObject.ApplyModifiedProperties(); 104 | } 105 | 106 | private static void ShowBlacklistedScenes(Settings settings, SerializedObject serializedObject) 107 | { 108 | SerializedProperty blacklistedScenes = serializedObject.FindProperty("blacklistedScenes"); 109 | EditorGUILayout.PropertyField(blacklistedScenes, new GUIContent("Blacklisted scenes"), true); 110 | 111 | SerializedProperty blacklistedSceneNames = serializedObject.FindProperty("blacklistedSceneNames"); 112 | blacklistedSceneNames.arraySize = settings.blacklistedScenes.Count; 113 | for (int i = 0; i < settings.blacklistedScenes.Count; i++) 114 | { 115 | SerializedProperty arrayElement = blacklistedSceneNames.GetArrayElementAtIndex(i); 116 | SceneAsset sceneAsset = settings.blacklistedScenes[i]; 117 | if (sceneAsset != null) 118 | { 119 | arrayElement.stringValue = sceneAsset.name; 120 | } 121 | else 122 | { 123 | arrayElement.stringValue = ""; 124 | } 125 | } 126 | } 127 | 128 | private static void ShowAssemblies(Settings settings, SerializedObject serializedObject) 129 | { 130 | SerializedProperty assemblyDefinitions = serializedObject.FindProperty("assemblyDefinitions"); 131 | EditorGUILayout.PropertyField(assemblyDefinitions, new GUIContent("Assemblies"), true); 132 | 133 | SerializedProperty assemblies = serializedObject.FindProperty("assemblies"); 134 | assemblies.arraySize = settings.assemblyDefinitions.Count; 135 | for (int i = 0; i < settings.assemblyDefinitions.Count; i++) 136 | { 137 | SerializedProperty arrayElement = assemblies.GetArrayElementAtIndex(i); 138 | AssemblyDefinitionAsset assemblyDefinitionAsset = settings.assemblyDefinitions[i]; 139 | try 140 | { 141 | AssemblyDefinition definition = JsonUtility.FromJson(assemblyDefinitionAsset.text); 142 | arrayElement.stringValue = definition.name; 143 | } 144 | catch 145 | { 146 | arrayElement.stringValue = ""; 147 | } 148 | } 149 | } 150 | 151 | public override void OnInspectorGUI() 152 | { 153 | Show(serializedObject); 154 | } 155 | 156 | [Serializable] 157 | internal class AssemblyDefinition 158 | { 159 | public string name; 160 | } 161 | } 162 | } 163 | #endif -------------------------------------------------------------------------------- /Runtime/Editor/SettingsInspector.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9ae26d5cbaed0b44588e78630aee7f89 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Editor/SettingsProvider.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEditor; 3 | 4 | #if UNITY_2019_1_OR_NEWER 5 | using UnityEngine.UIElements; 6 | #else 7 | using UnityEngine.Experimental.UIElements; 8 | #endif 9 | 10 | namespace Popcron.Console 11 | { 12 | public class ConsoleSettingsProvider : SettingsProvider 13 | { 14 | private SerializedObject settings; 15 | 16 | public ConsoleSettingsProvider(string path, SettingsScope scope = SettingsScope.Project) : base(path, scope) 17 | { 18 | 19 | } 20 | 21 | public override void OnActivate(string searchContext, VisualElement rootElement) 22 | { 23 | settings = new SerializedObject(Settings.Current); 24 | } 25 | 26 | public override void OnGUI(string searchContext) 27 | { 28 | SettingsInspector.Show(settings); 29 | } 30 | 31 | [SettingsProvider] 32 | public static SettingsProvider CreateMyCustomSettingsProvider() 33 | { 34 | ConsoleSettingsProvider provider = new ConsoleSettingsProvider("Project/Console", SettingsScope.Project) 35 | { 36 | keywords = new string[] { "console" } 37 | }; 38 | 39 | return provider; 40 | } 41 | } 42 | } 43 | #endif -------------------------------------------------------------------------------- /Runtime/Editor/SettingsProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6afc421d8a4b7c444a0aede17db10e2d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Exceptions.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 46dd9e439f4c8fe4ab2e6ec4fcc020be 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Exceptions/ConverterNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Popcron.Console 4 | { 5 | public class ConverterNotFoundException : Exception 6 | { 7 | public ConverterNotFoundException(string message) : base(message) 8 | { 9 | 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Runtime/Exceptions/ConverterNotFoundException.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0135b1e5c245eaf4c9c280373015f1a3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Exceptions/FailedToConvertException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Popcron.Console 4 | { 5 | public class FailedToConvertException : Exception 6 | { 7 | public FailedToConvertException(string message) : base(message) 8 | { 9 | 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Runtime/Exceptions/FailedToConvertException.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5e8450405d58ab44ca63945a34716bcb 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using UnityEngine; 5 | 6 | namespace Popcron.Console 7 | { 8 | internal static class Extensions 9 | { 10 | public static CommandAttribute GetCommand(this MemberInfo member) 11 | { 12 | try 13 | { 14 | CommandAttribute attribute = null; 15 | object[] attributes = member.GetCustomAttributes(typeof(CommandAttribute), false); 16 | for (int a = 0; a < attributes.Length; a++) 17 | { 18 | if (attributes[a].GetType() == typeof(CommandAttribute)) 19 | { 20 | attribute = attributes[a] as CommandAttribute; 21 | return attribute; 22 | } 23 | } 24 | } 25 | catch 26 | { 27 | //this could happen due to a TypeLoadException in builds 28 | } 29 | 30 | return null; 31 | } 32 | 33 | public static AliasAttribute[] GetAliases(this MemberInfo member) 34 | { 35 | try 36 | { 37 | List aliases = new List(); 38 | object[] attributes = member.GetCustomAttributes(typeof(AliasAttribute), false); 39 | for (int a = 0; a < attributes.Length; a++) 40 | { 41 | if (attributes[a].GetType() == typeof(AliasAttribute)) 42 | { 43 | AliasAttribute attribute = attributes[a] as AliasAttribute; 44 | aliases.Add(attribute); 45 | } 46 | } 47 | 48 | return aliases.ToArray(); 49 | } 50 | catch 51 | { 52 | //this could happen due to a TypeLoadException in builds 53 | } 54 | 55 | return new AliasAttribute[] { }; 56 | } 57 | 58 | public static CategoryAttribute GetCategoryAttribute(this Type type) 59 | { 60 | try 61 | { 62 | CategoryAttribute attribute = null; 63 | object[] attributes = type.GetCustomAttributes(typeof(CategoryAttribute), false); 64 | for (int a = 0; a < attributes.Length; a++) 65 | { 66 | if (attributes[a].GetType() == typeof(CategoryAttribute)) 67 | { 68 | attribute = attributes[a] as CategoryAttribute; 69 | return attribute; 70 | } 71 | } 72 | } 73 | catch 74 | { 75 | //this could happen due to a TypeLoadException in builds 76 | } 77 | 78 | return null; 79 | } 80 | 81 | public static char GetCharFromKeyCode(this KeyCode keyCode) 82 | { 83 | int index = (int)keyCode; 84 | return (char)index; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Runtime/Extensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8016952d7262f56498fc41a323c24f71 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Generator.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Reflection; 6 | using System.Text; 7 | using UnityEditor; 8 | using UnityEngine; 9 | 10 | namespace Popcron.Console 11 | { 12 | public static class Generator 13 | { 14 | private static readonly StringBuilder contents = new StringBuilder(); 15 | private const BindingFlags Flags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; 16 | 17 | private static (List, List, List) GetAll() 18 | { 19 | List categoriesFound = new List(); 20 | List membersFound = new List(); 21 | List indices = new List(); 22 | 23 | AppDomain domain = AppDomain.CurrentDomain; 24 | Assembly[] assemblies = domain.GetAssemblies(); 25 | int assemblyCount = assemblies.Length; 26 | for (int a = 0; a < assemblyCount; a++) 27 | { 28 | Assembly assembly = assemblies[a]; 29 | Type[] types = assembly.GetTypes(); 30 | int typeCount = types.Length; 31 | for (int t = 0; t < typeCount; t++) 32 | { 33 | Type type = types[t]; 34 | if (type.GetCustomAttribute() != null) 35 | { 36 | categoriesFound.Add(type); 37 | } 38 | 39 | MemberInfo[] members = type.GetMembers(Flags); 40 | int memberCount = members.Length; 41 | for (int m = 0; m < memberCount; m++) 42 | { 43 | MemberInfo member = members[m]; 44 | try 45 | { 46 | if (member.GetCustomAttribute() != null) 47 | { 48 | membersFound.Add(member); 49 | indices.Add(m); 50 | } 51 | } 52 | catch 53 | { 54 | 55 | } 56 | } 57 | } 58 | } 59 | 60 | return (categoriesFound, membersFound, indices); 61 | } 62 | 63 | /// 64 | /// Generates the script source for a command loader and writes it to a file at this path. 65 | /// 66 | public static void GenerateScriptSource(string path) 67 | { 68 | string typeName = Path.GetFileNameWithoutExtension(path); 69 | string fileContent = GenerateScriptSource(null, typeName); 70 | if (!File.Exists(path) || File.ReadAllText(path) != fileContent) 71 | { 72 | File.WriteAllText(path, fileContent); 73 | } 74 | } 75 | 76 | /// 77 | /// Generates the script source for a command loader. 78 | /// 79 | public static string GenerateScriptSource(string namespaceName, string typeName) 80 | { 81 | const string Indent = " "; 82 | List namespaces = new List(); 83 | namespaces.Add("UnityEngine"); 84 | namespaces.Add("UnityEngine.Scripting"); 85 | namespaces.Add("System"); 86 | namespaces.Add("System.Reflection"); 87 | namespaces.Add("System.Collections.Generic"); 88 | 89 | (List categories, List members, List indices) = GetAll(); 90 | 91 | contents.Clear(); 92 | foreach (string ns in namespaces) 93 | { 94 | contents.Append("using "); 95 | contents.Append(ns); 96 | contents.AppendLine(";"); 97 | } 98 | 99 | contents.AppendLine(); 100 | if (!string.IsNullOrEmpty(namespaceName)) 101 | { 102 | contents.Append("namespace "); 103 | contents.AppendLine(namespaceName); 104 | } 105 | 106 | contents.AppendLine("{"); 107 | 108 | contents.Append(Indent); 109 | contents.AppendLine("using Type = System.Type;"); 110 | contents.AppendLine(); 111 | 112 | contents.Append(Indent); 113 | contents.AppendLine("[Preserve]"); 114 | 115 | contents.Append(Indent); 116 | contents.Append("public static class "); 117 | contents.AppendLine(typeName); 118 | 119 | contents.Append(Indent); 120 | contents.AppendLine("{"); 121 | 122 | contents.Append(Indent); 123 | contents.Append(Indent); 124 | contents.Append("private const BindingFlags Flags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;"); 125 | contents.AppendLine(); 126 | 127 | contents.Append(Indent); 128 | contents.Append(Indent); 129 | contents.Append("private static Dictionary typeToMembers = new Dictionary();"); 130 | contents.AppendLine(); 131 | 132 | contents.AppendLine(); 133 | 134 | contents.Append(Indent); 135 | contents.Append(Indent); 136 | contents.Append("#if UNITY_EDITOR"); 137 | contents.AppendLine(); 138 | 139 | contents.Append(Indent); 140 | contents.Append(Indent); 141 | contents.Append("[UnityEditor.Callbacks.DidReloadScripts]"); 142 | contents.AppendLine(); 143 | 144 | contents.Append(Indent); 145 | contents.Append(Indent); 146 | contents.Append("private static void LoadBecauseRecompile()"); 147 | contents.AppendLine(); 148 | 149 | contents.Append(Indent); 150 | contents.Append(Indent); 151 | contents.Append("{"); 152 | contents.AppendLine(); 153 | 154 | contents.Append(Indent); 155 | contents.Append(Indent); 156 | contents.Append(Indent); 157 | contents.Append("Load();"); 158 | contents.AppendLine(); 159 | 160 | contents.Append(Indent); 161 | contents.Append(Indent); 162 | contents.Append("}"); 163 | 164 | contents.AppendLine(); 165 | contents.Append(Indent); 166 | contents.Append(Indent); 167 | contents.Append("#endif"); 168 | contents.AppendLine(); 169 | 170 | contents.Append(Indent); 171 | contents.Append(Indent); 172 | contents.Append("private static MemberInfo GetMemberOf(Type type, int index)"); 173 | contents.AppendLine(); 174 | 175 | contents.Append(Indent); 176 | contents.Append(Indent); 177 | contents.Append("{"); 178 | contents.AppendLine(); 179 | 180 | contents.Append(Indent); 181 | contents.Append(Indent); 182 | contents.Append(Indent); 183 | contents.Append("if (!typeToMembers.TryGetValue(type, out MemberInfo[] members))"); 184 | contents.AppendLine(); 185 | 186 | contents.Append(Indent); 187 | contents.Append(Indent); 188 | contents.Append(Indent); 189 | contents.Append("{"); 190 | contents.AppendLine(); 191 | 192 | contents.Append(Indent); 193 | contents.Append(Indent); 194 | contents.Append(Indent); 195 | contents.Append(Indent); 196 | contents.Append("members = type.GetMembers(Flags);"); 197 | contents.AppendLine(); 198 | 199 | contents.Append(Indent); 200 | contents.Append(Indent); 201 | contents.Append(Indent); 202 | contents.Append(Indent); 203 | contents.Append("typeToMembers[type] = members;"); 204 | contents.AppendLine(); 205 | 206 | contents.Append(Indent); 207 | contents.Append(Indent); 208 | contents.Append(Indent); 209 | contents.Append("}"); 210 | contents.AppendLine(); 211 | 212 | contents.AppendLine(); 213 | 214 | contents.Append(Indent); 215 | contents.Append(Indent); 216 | contents.Append(Indent); 217 | contents.Append("return members[index];"); 218 | contents.AppendLine(); 219 | 220 | contents.Append(Indent); 221 | contents.Append(Indent); 222 | contents.Append("}"); 223 | contents.AppendLine(); 224 | 225 | contents.AppendLine(); 226 | contents.Append(Indent); 227 | contents.Append(Indent); 228 | contents.Append("[RuntimeInitializeOnLoadMethodAttribute(RuntimeInitializeLoadType.AfterAssembliesLoaded), Preserve]"); 229 | contents.AppendLine(); 230 | 231 | contents.Append(Indent); 232 | contents.Append(Indent); 233 | contents.Append("private static void Load()"); 234 | contents.AppendLine(); 235 | 236 | contents.Append(Indent); 237 | contents.Append(Indent); 238 | contents.AppendLine("{"); 239 | 240 | contents.Append(Indent); 241 | contents.Append(Indent); 242 | contents.Append(Indent); 243 | contents.AppendLine("Command command;"); 244 | 245 | contents.Append(Indent); 246 | contents.Append(Indent); 247 | contents.Append(Indent); 248 | contents.AppendLine("Category category;"); 249 | 250 | contents.Append(Indent); 251 | contents.Append(Indent); 252 | contents.Append(Indent); 253 | contents.AppendLine("Type type;"); 254 | 255 | contents.Append(Indent); 256 | contents.Append(Indent); 257 | contents.Append(Indent); 258 | contents.AppendLine("MemberInfo member;"); 259 | 260 | contents.AppendLine(); 261 | 262 | foreach (Type category in categories) 263 | { 264 | contents.Append(Indent); 265 | contents.Append(Indent); 266 | contents.Append(Indent); 267 | contents.Append("type = Type.GetType(\""); 268 | contents.Append(category.AssemblyQualifiedName); 269 | contents.Append("\");"); 270 | contents.AppendLine(); 271 | 272 | contents.Append(Indent); 273 | contents.Append(Indent); 274 | contents.Append(Indent); 275 | contents.Append("category = Category.Create(type);"); 276 | contents.AppendLine(); 277 | 278 | contents.Append(Indent); 279 | contents.Append(Indent); 280 | contents.Append(Indent); 281 | contents.Append("Library.AddCategory(category);"); 282 | contents.AppendLine(); 283 | 284 | contents.AppendLine(); 285 | } 286 | 287 | int memberCount = members.Count; 288 | Type lastOwningType = null; 289 | for (int i = 0; i < memberCount; i++) 290 | { 291 | MemberInfo member = members[i]; 292 | int index = indices[i]; 293 | CommandAttribute command = member.GetCustomAttribute(); 294 | string name = command.name; 295 | string description = command.description; 296 | Type owningType = member.DeclaringType; 297 | CategoryAttribute category = owningType.GetCustomAttribute(); 298 | 299 | contents.Append(Indent); 300 | contents.Append(Indent); 301 | contents.Append(Indent); 302 | contents.Append("try"); 303 | contents.AppendLine(); 304 | 305 | contents.Append(Indent); 306 | contents.Append(Indent); 307 | contents.Append(Indent); 308 | contents.Append("{"); 309 | contents.AppendLine(); 310 | 311 | if (lastOwningType != owningType) 312 | { 313 | lastOwningType = owningType; 314 | contents.Append(Indent); 315 | contents.Append(Indent); 316 | contents.Append(Indent); 317 | contents.Append(Indent); 318 | contents.Append("type = Type.GetType(\""); 319 | contents.Append(owningType.AssemblyQualifiedName); 320 | contents.Append("\");"); 321 | contents.AppendLine(); 322 | } 323 | 324 | contents.Append(Indent); 325 | contents.Append(Indent); 326 | contents.Append(Indent); 327 | contents.Append(Indent); 328 | contents.Append("member = GetMemberOf(type, "); 329 | contents.Append(index); 330 | contents.Append(");"); 331 | contents.AppendLine(); 332 | 333 | contents.Append(Indent); 334 | contents.Append(Indent); 335 | contents.Append(Indent); 336 | contents.Append(Indent); 337 | contents.Append("command = Command.Create(\""); 338 | contents.Append(name); 339 | contents.Append("\", \""); 340 | contents.Append(description); 341 | contents.Append("\", member, type);"); 342 | contents.AppendLine(); 343 | 344 | contents.Append(Indent); 345 | contents.Append(Indent); 346 | contents.Append(Indent); 347 | contents.Append(Indent); 348 | if (category == null) 349 | { 350 | contents.Append("Library.AddCommand(command);"); 351 | contents.AppendLine(); 352 | } 353 | else 354 | { 355 | contents.Append("Library.AddCommand(command, \""); 356 | contents.Append(category.Name); 357 | contents.Append("\");"); 358 | contents.AppendLine(); 359 | } 360 | 361 | contents.Append(Indent); 362 | contents.Append(Indent); 363 | contents.Append(Indent); 364 | contents.Append("}"); 365 | contents.AppendLine(); 366 | 367 | contents.Append(Indent); 368 | contents.Append(Indent); 369 | contents.Append(Indent); 370 | contents.Append("catch { }"); 371 | contents.AppendLine(); 372 | 373 | contents.AppendLine(); 374 | } 375 | 376 | contents.Append(Indent); 377 | contents.Append(Indent); 378 | contents.AppendLine("}"); 379 | 380 | contents.Append(Indent); 381 | contents.AppendLine("}"); 382 | contents.AppendLine("}"); 383 | return contents.ToString(); 384 | } 385 | } 386 | } 387 | #endif 388 | -------------------------------------------------------------------------------- /Runtime/Generator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e7345f5dc3acdd745822b6f8b61f77e4 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Library.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace Popcron.Console 7 | { 8 | public sealed class Library 9 | { 10 | private static List<(Assembly assembly, Type[] types)> assemblies = null; 11 | private static List commands = new List(); 12 | private static List categories = new List(); 13 | 14 | /// 15 | /// List of all commands found. 16 | /// 17 | public static List Commands 18 | { 19 | get 20 | { 21 | return commands; 22 | } 23 | } 24 | 25 | public static Category Uncategorized 26 | { 27 | get 28 | { 29 | if (!TryGetCategory("Uncategorized", out Category category)) 30 | { 31 | category = Category.Create("Uncategorized"); 32 | if (categories != null) 33 | { 34 | categories.Add(category); 35 | } 36 | } 37 | 38 | return category; 39 | } 40 | } 41 | 42 | public static List Categories 43 | { 44 | get 45 | { 46 | return categories; 47 | } 48 | } 49 | 50 | private static List<(Assembly assembly, Type[] types)> Assemblies 51 | { 52 | get 53 | { 54 | if (assemblies == null || assemblies.Count == 0) 55 | { 56 | //possible in some cases 57 | if (!Settings.Current) 58 | { 59 | assemblies = new List<(Assembly assembly, Type[] types)>(); 60 | return assemblies; 61 | } 62 | 63 | FindAllAssemblies(); 64 | } 65 | 66 | return assemblies; 67 | } 68 | } 69 | 70 | private static void FindAllAssemblies() 71 | { 72 | List allAssemblies = Settings.Current.assemblies.ToList(); 73 | Assembly executingAssembly = Assembly.GetExecutingAssembly(); 74 | Assembly callingAssembly = Assembly.GetCallingAssembly(); 75 | Assembly entryAssembly = Assembly.GetEntryAssembly(); 76 | Assembly consoleAssembly = typeof(ConsoleWindow).Assembly; 77 | 78 | //ensure the last 4 assemblies exist in the list 79 | if (executingAssembly != null && !allAssemblies.Contains(executingAssembly.FullName)) 80 | { 81 | allAssemblies.Add(executingAssembly.FullName); 82 | } 83 | 84 | if (callingAssembly != null && !allAssemblies.Contains(callingAssembly.FullName)) 85 | { 86 | allAssemblies.Add(callingAssembly.FullName); 87 | } 88 | 89 | if (entryAssembly != null && !allAssemblies.Contains(entryAssembly.FullName)) 90 | { 91 | allAssemblies.Add(entryAssembly.FullName); 92 | } 93 | 94 | if (consoleAssembly != null && !allAssemblies.Contains(consoleAssembly.FullName)) 95 | { 96 | allAssemblies.Add(consoleAssembly.FullName); 97 | } 98 | 99 | assemblies = new List<(Assembly assembly, Type[] types)>(); 100 | for (int a = 0; a < allAssemblies.Count; a++) 101 | { 102 | AddAssembly(allAssemblies[a]); 103 | 104 | } 105 | 106 | if (!ContainsAssembly("Assembly-CSharp")) 107 | { 108 | AddAssembly("Assembly-CSharp", true); 109 | } 110 | 111 | #if UNITY_EDITOR 112 | if (!ContainsAssembly("Assembly-CSharp-Editor")) 113 | { 114 | AddAssembly("Assembly-CSharp-Editor", true); 115 | } 116 | #endif 117 | } 118 | 119 | private static bool ContainsAssembly(string name) 120 | { 121 | for (int i = 0; i < assemblies.Count; i++) 122 | { 123 | if (assemblies[i].assembly.GetName().Name == name) 124 | { 125 | return true; 126 | } 127 | } 128 | 129 | return false; 130 | } 131 | 132 | private static void AddAssembly(string name, bool searchAppDomain = false) 133 | { 134 | if (string.IsNullOrEmpty(name)) 135 | { 136 | return; 137 | } 138 | 139 | try 140 | { 141 | Assembly assemblyToLoad = null; 142 | if (searchAppDomain) 143 | { 144 | Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); 145 | int length = assemblies.Length; 146 | for (int i = 0; i < length; i++) 147 | { 148 | Assembly assembly = assemblies[i]; 149 | if (assembly.GetName().Name == name) 150 | { 151 | assemblyToLoad = assembly; 152 | break; 153 | } 154 | } 155 | } 156 | else 157 | { 158 | assemblyToLoad = Assembly.Load(name); 159 | } 160 | 161 | if (assemblyToLoad != null) 162 | { 163 | Type[] types = assemblyToLoad.GetTypes(); 164 | assemblies.Add((assemblyToLoad, types)); 165 | } 166 | } 167 | catch 168 | { 169 | 170 | } 171 | } 172 | 173 | public static void AddCategory(Category category) 174 | { 175 | if (category == null) 176 | { 177 | return; 178 | } 179 | 180 | if (categories == null) 181 | { 182 | categories = new List(); 183 | } 184 | 185 | //short circuit if already exists! 186 | int categoryCount = categories.Count; 187 | for (int i = 0; i < categoryCount; i++) 188 | { 189 | Category cat = categories[i]; 190 | if (cat.Name == category.Name) 191 | { 192 | return; 193 | } 194 | } 195 | 196 | categories.Add(category); 197 | } 198 | 199 | public static void FindCategories() 200 | { 201 | return; 202 | if (categories == null) 203 | { 204 | categories = new List(); 205 | HashSet classTypesWithoutCategories = new HashSet(); 206 | for (int a = 0; a < Assemblies.Count; a++) 207 | { 208 | Type[] types = Assemblies[a].types; 209 | int typesLength = types.Length; 210 | for (int t = 0; t < typesLength; t++) 211 | { 212 | Type classType = types[t]; 213 | Category category = Category.Create(classType); 214 | if (category != null) 215 | { 216 | if (TryGetCategory(category.Name, out Category existingCategory)) 217 | { 218 | existingCategory.Commands.AddRange(category.Commands); 219 | } 220 | else 221 | { 222 | categories.Add(category); 223 | } 224 | } 225 | else 226 | { 227 | classTypesWithoutCategories.Add(classType); 228 | } 229 | } 230 | } 231 | 232 | //put any commands that arent defined inside classes with a Category attribute 233 | //into the uncategorized category 234 | if (classTypesWithoutCategories.Count > 0) 235 | { 236 | Category uncat = Uncategorized; 237 | List commands = Commands; 238 | foreach (Command command in commands) 239 | { 240 | if (classTypesWithoutCategories.Contains(command.OwnerClass)) 241 | { 242 | uncat.Commands.Add(command); 243 | } 244 | } 245 | } 246 | 247 | //sort alphabetically 248 | categories = categories.OrderBy(x => x.Name).ToList(); 249 | } 250 | } 251 | 252 | public static bool TryGetCategory(string name, out Category category) 253 | { 254 | category = null; 255 | if (categories == null) 256 | { 257 | return false; 258 | } 259 | 260 | for (int i = 0; i < categories.Count; i++) 261 | { 262 | if (categories[i].Name == name) 263 | { 264 | category = categories[i]; 265 | return true; 266 | } 267 | } 268 | 269 | return false; 270 | } 271 | 272 | public static void RemoveCommand(Command command) 273 | { 274 | if (commands != null) 275 | { 276 | //remove from all 277 | int hashCode = command.GetHashCode(); 278 | int count = Commands.Count; 279 | for (int i = count - 1; i >= 0; i--) 280 | { 281 | Command existingCommand = Commands[i]; 282 | if (existingCommand.GetHashCode() == hashCode) 283 | { 284 | Commands.RemoveAt(i); 285 | } 286 | } 287 | 288 | //remove in all cats 289 | count = Categories.Count; 290 | for (int i = count - 1; i >= 0; i--) 291 | { 292 | Category category = Categories[i]; 293 | int commandCount = category.Commands.Count; 294 | for (int b = commandCount - 1; b >= 0; b--) 295 | { 296 | Command existingCommand = category.Commands[b]; 297 | if (existingCommand.GetHashCode() == hashCode) 298 | { 299 | category.Commands.RemoveAt(b); 300 | } 301 | } 302 | } 303 | } 304 | } 305 | 306 | /// 307 | /// Adds a command to the registry manually. 308 | /// 309 | public static void AddCommand(Command command, string category = null) 310 | { 311 | if (commands == null) 312 | { 313 | FindCommands(); 314 | } 315 | 316 | Category categoryToUse = Uncategorized; 317 | if (!string.IsNullOrEmpty(category)) 318 | { 319 | if (TryGetCategory(category, out Category existingCategory)) 320 | { 321 | categoryToUse = existingCategory; 322 | } 323 | } 324 | 325 | foreach (Command existingCommand in categoryToUse.Commands) 326 | { 327 | if (existingCommand.GetHashCode() == command.GetHashCode()) 328 | { 329 | //already exists! 330 | return; 331 | } 332 | } 333 | 334 | categoryToUse.Commands.Add(command); 335 | commands.Add(command); 336 | } 337 | 338 | public static void FindCommands() 339 | { 340 | return; 341 | const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; 342 | if (commands == null || commands.Count == 0) 343 | { 344 | commands = new List(); 345 | for (int a = 0; a < Assemblies.Count; a++) 346 | { 347 | Type[] types = Assemblies[a].types; 348 | for (int t = 0; t < types.Length; t++) 349 | { 350 | Type type = types[t]; 351 | MethodInfo[] methods = type.GetMethods(Flags); 352 | for (int m = 0; m < methods.Length; m++) 353 | { 354 | MethodInfo method = methods[m]; 355 | Command command = Command.Create(method, type); 356 | if (command != null) 357 | { 358 | commands.Add(command); 359 | } 360 | } 361 | 362 | PropertyInfo[] properties = type.GetProperties(Flags); 363 | for (int p = 0; p < properties.Length; p++) 364 | { 365 | PropertyInfo property = properties[p]; 366 | Command command = Command.Create(property, type); 367 | if (command != null) 368 | { 369 | commands.Add(command); 370 | } 371 | } 372 | 373 | FieldInfo[] fields = type.GetFields(Flags); 374 | for (int f = 0; f < fields.Length; f++) 375 | { 376 | FieldInfo field = fields[f]; 377 | Command command = Command.Create(field, type); 378 | if (command != null) 379 | { 380 | commands.Add(command); 381 | } 382 | } 383 | } 384 | } 385 | } 386 | } 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /Runtime/Library.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9941caa29bc910a4e8c722ee1403b2fa 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Owner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | 5 | namespace Popcron.Console 6 | { 7 | [Serializable] 8 | public class Owner 9 | { 10 | public class OwnerMember 11 | { 12 | public string name; 13 | public string description; 14 | public Type type; 15 | public bool canGetProperty; 16 | public bool canSetProperty; 17 | 18 | private object owner; 19 | private MemberInfo member; 20 | private object value; 21 | 22 | public object Value 23 | { 24 | get 25 | { 26 | if (value == null) 27 | { 28 | if (member is FieldInfo field) 29 | { 30 | value = field.GetValue(owner); 31 | } 32 | else 33 | { 34 | return null; 35 | } 36 | } 37 | 38 | return value; 39 | } 40 | } 41 | 42 | public OwnerMember(object owner, MemberInfo member) 43 | { 44 | this.owner = owner; 45 | this.member = member; 46 | } 47 | } 48 | 49 | public string id; 50 | public object owner; 51 | public List methods; 52 | public List properties; 53 | public List fields; 54 | 55 | internal void FindMethods() 56 | { 57 | //reset 58 | if (methods == null) 59 | { 60 | methods = new List(); 61 | } 62 | else 63 | { 64 | methods.Clear(); 65 | } 66 | 67 | //try to add all of its instance methods 68 | MethodInfo[] allMethods = owner.GetType().GetMethods(); 69 | for (int i = 0; i < allMethods.Length; i++) 70 | { 71 | //short circuit if static 72 | if (allMethods[i].IsStatic) continue; 73 | 74 | CommandAttribute attribute = allMethods[i].GetCommand(); 75 | if (attribute != null) 76 | { 77 | OwnerMember method = new OwnerMember(owner, allMethods[i]) 78 | { 79 | name = attribute.name, 80 | description = attribute.description, 81 | type = allMethods[i].ReturnType 82 | }; 83 | methods.Add(method); 84 | } 85 | } 86 | } 87 | 88 | internal void FindProperties() 89 | { 90 | //reset 91 | if (properties == null) 92 | { 93 | properties = new List(); 94 | } 95 | else 96 | { 97 | properties.Clear(); 98 | } 99 | 100 | //try to add all of its instance properties 101 | PropertyInfo[] allProperties = owner.GetType().GetProperties(); 102 | for (int i = 0; i < allProperties.Length; i++) 103 | { 104 | //short circuit if static 105 | MethodInfo get = allProperties[i].GetGetMethod(); 106 | MethodInfo set = null; 107 | if (get != null && get.IsStatic) continue; 108 | else if (get == null) 109 | { 110 | set = allProperties[i].GetSetMethod(); 111 | if (set != null && set.IsStatic) continue; 112 | } 113 | 114 | CommandAttribute attribute = allProperties[i].GetCommand(); 115 | if (attribute != null) 116 | { 117 | OwnerMember method = new OwnerMember(owner, allProperties[i]) 118 | { 119 | name = attribute.name, 120 | description = attribute.description, 121 | type = get?.ReturnType, 122 | canGetProperty = get != null, 123 | canSetProperty = set != null 124 | }; 125 | properties.Add(method); 126 | } 127 | } 128 | } 129 | 130 | internal void FindFields() 131 | { 132 | //reset methods 133 | if (fields == null) 134 | { 135 | fields = new List(); 136 | } 137 | else 138 | { 139 | fields.Clear(); 140 | } 141 | 142 | //try to add all of its instance methods 143 | FieldInfo[] allFields = owner.GetType().GetFields(); 144 | for (int i = 0; i < allFields.Length; i++) 145 | { 146 | //short circuit if static 147 | if (allFields[i].IsStatic) continue; 148 | 149 | CommandAttribute attribute = allFields[i].GetCommand(); 150 | if (attribute != null) 151 | { 152 | OwnerMember method = new OwnerMember(owner, allFields[i]) 153 | { 154 | name = attribute.name, 155 | description = attribute.description, 156 | type = allFields[i].FieldType 157 | }; 158 | fields.Add(method); 159 | } 160 | } 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Runtime/Owner.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9f3bd6e2182f39c46b73f2bd58e6ff1d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Parser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | using System.Threading.Tasks; 7 | 8 | namespace Popcron.Console 9 | { 10 | public class Parser 11 | { 12 | //< is escaped version of the < char 13 | /// 14 | /// A fake left angle bracket that looks like < but isnt one 15 | /// 16 | public const char LeftAngleBracket = '˂'; 17 | 18 | /// 19 | /// A fake right angle bracket that looks like > but isnt one 20 | /// 21 | public const char RightAngleBracket = '˃'; 22 | 23 | /// 24 | /// Prefix to use when specifying instance commands with an ID. 25 | /// 26 | public const string IDPrefix = "@"; 27 | 28 | /// 29 | /// A string that contains all valid characters. 30 | /// 31 | public const string ValidChars = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'/!@#$%^&*()_+-=[]{};':\",.˂˃/?"; 32 | 33 | private static readonly StringBuilder stringBuilder = new StringBuilder(); 34 | private static readonly Dictionary idToOwner = new Dictionary(); 35 | 36 | public static IEnumerable Owners 37 | { 38 | get 39 | { 40 | foreach (KeyValuePair owner in idToOwner) 41 | { 42 | yield return owner.Value; 43 | } 44 | } 45 | } 46 | 47 | /// 48 | /// Registers this object with a unique ID. 49 | /// 50 | public static void Register(object owner, int id) 51 | { 52 | Register(owner, id.ToString()); 53 | } 54 | 55 | /// 56 | /// Registers this object with a unique ID. 57 | /// 58 | public static void Register(object owner, string id) 59 | { 60 | if (!idToOwner.TryGetValue(id, out Owner ownerValue)) 61 | { 62 | ownerValue = new Owner(); 63 | idToOwner.Add(id, ownerValue); 64 | } 65 | 66 | ownerValue.owner = owner; 67 | ownerValue.id = id; 68 | 69 | ownerValue.FindMethods(); 70 | ownerValue.FindProperties(); 71 | ownerValue.FindFields(); 72 | } 73 | 74 | /// 75 | /// Unregisters this object so that it's no longer something that can be identified with @. 76 | /// 77 | public static void Unregister(object owner) 78 | { 79 | foreach (KeyValuePair stringToOwner in idToOwner) 80 | { 81 | if (stringToOwner.Value.owner == owner) 82 | { 83 | Unregister(stringToOwner.Value.id); 84 | break; 85 | } 86 | } 87 | } 88 | 89 | /// 90 | /// Unregisters this object so that it's no longer something that can be identified with @. 91 | /// 92 | public static void Unregister(int id) 93 | { 94 | Unregister(id.ToString()); 95 | } 96 | 97 | /// 98 | /// Unregisters this object so that it's no longer something that can be identified with @. 99 | /// 100 | public static void Unregister(string id) 101 | { 102 | //remove this object as an owner for any methods 103 | if (idToOwner.ContainsKey(id)) 104 | { 105 | idToOwner.Remove(id); 106 | } 107 | } 108 | 109 | /// 110 | /// Removes any tags inside angle brackets. 111 | /// 112 | public static string RemoveRichText(string input) 113 | { 114 | if (string.IsNullOrEmpty(input) || input.Length == 0) 115 | { 116 | return input; 117 | } 118 | 119 | if (input.IndexOf('<') == -1 && input.IndexOf('>') == -1) 120 | { 121 | return input; 122 | } 123 | 124 | int length = input.Length; 125 | char[] array = new char[length]; 126 | int arrayIndex = 0; 127 | bool inside = false; 128 | for (int i = 0; i < length; i++) 129 | { 130 | char c = input[i]; 131 | if (c == '<') 132 | { 133 | inside = true; 134 | continue; 135 | } 136 | else if (c == '>') 137 | { 138 | inside = false; 139 | continue; 140 | } 141 | 142 | if (!inside) 143 | { 144 | array[arrayIndex] = c; 145 | arrayIndex++; 146 | } 147 | } 148 | 149 | return new string(array, 0, arrayIndex); 150 | } 151 | 152 | /// 153 | /// Sanitizes the input string for proper GUI rendering. 154 | /// 155 | public static string Sanitize(string input) 156 | { 157 | if (string.IsNullOrEmpty(input) || input.Length == 0) 158 | { 159 | return string.Empty; 160 | } 161 | 162 | stringBuilder.Clear(); 163 | for (int i = 0; i < input.Length; i++) 164 | { 165 | char c = input[i]; 166 | if (c == '<') 167 | { 168 | stringBuilder.Append(LeftAngleBracket); 169 | } 170 | else if (c == '>') 171 | { 172 | stringBuilder.Append(RightAngleBracket); 173 | } 174 | else if (ValidChars.Contains(c)) 175 | { 176 | stringBuilder.Append(c); 177 | } 178 | } 179 | 180 | return stringBuilder.ToString(); 181 | } 182 | 183 | /// 184 | /// Runs a command. 185 | /// 186 | public static async Task Run(string input) 187 | { 188 | //sanitize the input so that it only contains alphanumeric characters 189 | input = Sanitize(input); 190 | 191 | //if input starts with id flag 192 | //remove the id flag and store it separately 193 | string id = null; 194 | if (input.StartsWith(IDPrefix)) 195 | { 196 | if (input.IndexOf(' ') < 0) // in case command contained only id name 197 | { 198 | id = input.Substring(1); 199 | input = string.Empty; 200 | } 201 | else 202 | { 203 | id = input.Substring(1, input.IndexOf(' ') - 1); 204 | input = input.Replace(IDPrefix + id + " ", string.Empty); 205 | } 206 | } 207 | 208 | int commandsCount = Library.Commands.Count; 209 | for (int c = 0; c < commandsCount; c++) 210 | { 211 | Command command = Library.Commands[c]; 212 | bool nameMatch = false; 213 | string commandInput = null; 214 | for (int n = 0; n < command.Names.Count; n++) 215 | { 216 | string name = command.Names[n]; 217 | if (input.StartsWith(name)) 218 | { 219 | nameMatch = true; 220 | commandInput = name; 221 | break; 222 | } 223 | } 224 | 225 | //name matches? oooh, ;) go on... 226 | if (nameMatch) 227 | { 228 | string text = input.Replace(commandInput, ""); 229 | List parameters = GetParameters(text); 230 | if (command.Matches(parameters, out object[] converted)) 231 | { 232 | object owner = FindOwner(command, id); 233 | if (owner == null && !command.IsStatic) 234 | { 235 | //this was an instance method that didnt have an id 236 | return new NullReferenceException($"Couldn't find owner with ID {id}"); 237 | } 238 | 239 | //try to exec 240 | try 241 | { 242 | object result = command.Invoke(owner, converted); 243 | if (result is Task) 244 | { 245 | Task task = result as Task; 246 | await task.ConfigureAwait(false); 247 | return task.GetType().GetProperty("Result").GetValue(task); 248 | } 249 | else 250 | { 251 | return result; 252 | } 253 | } 254 | catch (Exception exception) 255 | { 256 | return exception; 257 | } 258 | } 259 | } 260 | } 261 | 262 | if (Settings.Current.reportUnknownCommand) 263 | { 264 | return "Command not found"; 265 | } 266 | else 267 | { 268 | return null; 269 | } 270 | } 271 | 272 | /// 273 | /// Returns an object with this registered ID for this command. 274 | /// 275 | public static object FindOwner(Command command, string id) 276 | { 277 | //id passed was null, stoppp 278 | if (id == null) 279 | { 280 | return null; 281 | } 282 | 283 | //static commands cant have any owners 284 | if (command.IsStatic) 285 | { 286 | return null; 287 | } 288 | 289 | string memberName = command.Name; 290 | if (idToOwner.TryGetValue(id, out Owner owner)) 291 | { 292 | //check methods 293 | for (int i = 0; i < owner.methods.Count; i++) 294 | { 295 | if (owner.methods[i].name == memberName) 296 | { 297 | return owner.owner; 298 | } 299 | } 300 | 301 | //check properties 302 | for (int i = 0; i < owner.properties.Count; i++) 303 | { 304 | if (owner.properties[i].name == memberName) 305 | { 306 | return owner.owner; 307 | } 308 | } 309 | 310 | //check fields 311 | for (int i = 0; i < owner.fields.Count; i++) 312 | { 313 | if (owner.fields[i].name == memberName) 314 | { 315 | return owner.owner; 316 | } 317 | } 318 | } 319 | 320 | return null; 321 | } 322 | 323 | /// 324 | /// Returns a list of strings separated by a space from this text. 325 | /// 326 | public static List GetParameters(string input) 327 | { 328 | List parameters = Regex.Matches(input, @"[\""].+?[\""]|[^ ]+").Cast().Select(x => x.Value).ToList(); 329 | for (int i = 0; i < parameters.Count; i++) 330 | { 331 | if (parameters[i].StartsWith("\"") && parameters[i].EndsWith("\"")) 332 | { 333 | parameters[i] = parameters[i].TrimStart('\"').TrimEnd('\"'); 334 | } 335 | } 336 | 337 | return parameters; 338 | } 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /Runtime/Parser.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b5f2b4a7a933c734f82cd55001337078 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Popcron.Console.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Popcron.Console", 3 | "references": [], 4 | "optionalUnityReferences": [], 5 | "includePlatforms": [], 6 | "excludePlatforms": [], 7 | "allowUnsafeCode": false, 8 | "overrideReferences": false, 9 | "precompiledReferences": [], 10 | "autoReferenced": true, 11 | "defineConstraints": [], 12 | "versionDefines": [] 13 | } -------------------------------------------------------------------------------- /Runtime/Popcron.Console.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d187103b4bdd877429c736efbc905587 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/Resources.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 72f3706cfcac099499f2023aec0b639d 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Resources/CascadiaMono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/popcron/console/8c18286184d63b37ca064562b613dc818a88a49f/Runtime/Resources/CascadiaMono.ttf -------------------------------------------------------------------------------- /Runtime/Resources/CascadiaMono.ttf.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1b9b8e523215d30458716163d34c57be 3 | TrueTypeFontImporter: 4 | externalObjects: {} 5 | serializedVersion: 4 6 | fontSize: 16 7 | forceTextureCase: -2 8 | characterSpacing: 0 9 | characterPadding: 1 10 | includeFontData: 1 11 | fontNames: 12 | - Cascadia Mono 13 | fallbackFontReferences: 14 | - {fileID: 12800000, guid: 32f8f4eb8053b524c887acd51e90d930, type: 3} 15 | customCharacters: 16 | fontRenderingMode: 0 17 | ascentCalculationMode: 1 18 | useLegacyBoundsCalculation: 0 19 | shouldRoundAdvanceValue: 1 20 | userData: 21 | assetBundleName: 22 | assetBundleVariant: 23 | -------------------------------------------------------------------------------- /Runtime/Resources/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 - Present, Microsoft Corporation, 2 | with Reserved Font Name Cascadia Code. 3 | 4 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 5 | This license is copied below, and is also available with a FAQ at: 6 | http://scripts.sil.org/OFL 7 | 8 | 9 | ----------------------------------------------------------- 10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 11 | ----------------------------------------------------------- 12 | 13 | PREAMBLE 14 | The goals of the Open Font License (OFL) are to stimulate worldwide 15 | development of collaborative font projects, to support the font creation 16 | efforts of academic and linguistic communities, and to provide a free and 17 | open framework in which fonts may be shared and improved in partnership 18 | with others. 19 | 20 | The OFL allows the licensed fonts to be used, studied, modified and 21 | redistributed freely as long as they are not sold by themselves. The 22 | fonts, including any derivative works, can be bundled, embedded, 23 | redistributed and/or sold with any software provided that any reserved 24 | names are not used by derivative works. The fonts and derivatives, 25 | however, cannot be released under any other type of license. The 26 | requirement for fonts to remain under this license does not apply 27 | to any document created using the fonts or their derivatives. 28 | 29 | DEFINITIONS 30 | "Font Software" refers to the set of files released by the Copyright 31 | Holder(s) under this license and clearly marked as such. This may 32 | include source files, build scripts and documentation. 33 | 34 | "Reserved Font Name" refers to any names specified as such after the 35 | copyright statement(s). 36 | 37 | "Original Version" refers to the collection of Font Software components as 38 | distributed by the Copyright Holder(s). 39 | 40 | "Modified Version" refers to any derivative made by adding to, deleting, 41 | or substituting -- in part or in whole -- any of the components of the 42 | Original Version, by changing formats or by porting the Font Software to a 43 | new environment. 44 | 45 | "Author" refers to any designer, engineer, programmer, technical 46 | writer or other person who contributed to the Font Software. 47 | 48 | PERMISSION & CONDITIONS 49 | Permission is hereby granted, free of charge, to any person obtaining 50 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 51 | redistribute, and sell modified and unmodified copies of the Font 52 | Software, subject to the following conditions: 53 | 54 | 1) Neither the Font Software nor any of its individual components, 55 | in Original or Modified Versions, may be sold by itself. 56 | 57 | 2) Original or Modified Versions of the Font Software may be bundled, 58 | redistributed and/or sold with any software, provided that each copy 59 | contains the above copyright notice and this license. These can be 60 | included either as stand-alone text files, human-readable headers or 61 | in the appropriate machine-readable metadata fields within text or 62 | binary files as long as those fields can be easily viewed by the user. 63 | 64 | 3) No Modified Version of the Font Software may use the Reserved Font 65 | Name(s) unless explicit written permission is granted by the corresponding 66 | Copyright Holder. This restriction only applies to the primary font name as 67 | presented to the users. 68 | 69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 70 | Software shall not be used to promote, endorse or advertise any 71 | Modified Version, except to acknowledge the contribution(s) of the 72 | Copyright Holder(s) and the Author(s) or with their explicit written 73 | permission. 74 | 75 | 5) The Font Software, modified or unmodified, in part or in whole, 76 | must be distributed entirely under this license, and must not be 77 | distributed under any other license. The requirement for fonts to 78 | remain under this license does not apply to any document created 79 | using the Font Software. 80 | 81 | TERMINATION 82 | This license becomes null and void if any of the above conditions are 83 | not met. 84 | 85 | DISCLAIMER 86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 94 | OTHER DEALINGS IN THE FONT SOFTWARE. -------------------------------------------------------------------------------- /Runtime/Resources/LICENSE.txt.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 89896f71dddd30a4e81c4232e5986f52 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/SearchResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace Popcron.Console 5 | { 6 | [Serializable] 7 | public class SearchResult 8 | { 9 | [SerializeField] 10 | private string text; 11 | 12 | [SerializeField] 13 | private string command; 14 | 15 | public string Text => text; 16 | public string Command => command; 17 | 18 | public SearchResult(string text, string command) 19 | { 20 | this.text = text; 21 | this.command = command; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /Runtime/SearchResult.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c9dc972f9917ae54ba347f4b4ffc00e7 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Settings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using UnityEngine; 5 | using UnityEngine.SceneManagement; 6 | using System.Text; 7 | 8 | #if UNITY_EDITOR 9 | using UnityEditor; 10 | using UnityEditorInternal; 11 | #endif 12 | 13 | namespace Popcron.Console 14 | { 15 | public class Settings : ScriptableObject 16 | { 17 | private static StringBuilder stringBuilder = new StringBuilder(); 18 | private static Settings current; 19 | private static Texture2D pixel; 20 | 21 | /// 22 | /// Returns a single 1x1 pixel that has some alpha. 23 | /// 24 | public static Texture2D Pixel 25 | { 26 | get 27 | { 28 | if (!pixel) 29 | { 30 | pixel = new Texture2D(1, 1); 31 | pixel.SetPixel(0, 0, new Color(0f, 0f, 0f, 0.75f)); 32 | pixel.Apply(); 33 | } 34 | 35 | return pixel; 36 | } 37 | } 38 | 39 | /// 40 | /// The current settings data being used. 41 | /// 42 | public static Settings Current 43 | { 44 | get 45 | { 46 | #if UNITY_EDITOR 47 | if (!current) 48 | { 49 | current = EnsureSettingsExist.FindSettings(); 50 | } 51 | #endif 52 | 53 | return current; 54 | } 55 | } 56 | 57 | /// 58 | /// The hex value that represents the user typed text color. 59 | /// 60 | public string UserColor 61 | { 62 | get 63 | { 64 | if (string.IsNullOrEmpty(userColorHex)) 65 | { 66 | stringBuilder.Clear(); 67 | stringBuilder.Append("#"); 68 | stringBuilder.Append(ColorUtility.ToHtmlStringRGBA(userColor)); 69 | userColorHex = stringBuilder.ToString(); 70 | } 71 | 72 | return userColorHex; 73 | } 74 | } 75 | 76 | public float FontSize => fontSize; 77 | 78 | /// 79 | /// Returns the correct path to the log file. 80 | /// 81 | public string LogFilePath 82 | { 83 | get 84 | { 85 | if (string.IsNullOrEmpty(cachedLogFilePath)) 86 | { 87 | cachedLogFilePath = logFilePathPlayer; 88 | #if UNITY_EDITOR 89 | cachedLogFilePath = logFilePathEditor; 90 | #endif 91 | 92 | string root = Directory.GetParent(Application.dataPath).FullName; 93 | if (cachedLogFilePath == "./") 94 | { 95 | //if its just the 2 chars, then its not valid! 96 | } 97 | else if (cachedLogFilePath.StartsWith("./")) 98 | { 99 | //replace those 2 chars with the root 100 | cachedLogFilePath = cachedLogFilePath.Replace("./", $"{root}/"); 101 | } 102 | } 103 | 104 | return cachedLogFilePath; 105 | } 106 | } 107 | 108 | [SerializeField] 109 | private Color userColor = Color.green; 110 | 111 | [SerializeField] 112 | private Color errorColor = Color.red; 113 | 114 | [SerializeField] 115 | private Color exceptionColor = new Color(0.7f, 0f, 0f, 1f); 116 | 117 | [SerializeField] 118 | private Color warnColor = Color.Lerp(Color.red, Color.yellow, 0.8f); 119 | 120 | [SerializeField] 121 | private Color logColor = Color.white; 122 | 123 | #if UNITY_EDITOR 124 | public List assemblyDefinitions = new List(); 125 | public List blacklistedScenes = new List(); 126 | #endif 127 | 128 | public List assemblies = new List(); 129 | public List blacklistedSceneNames = new List(); 130 | public List startupCommands = new List() { "info" }; 131 | public GUIStyle consoleStyle = new GUIStyle(); 132 | public int scrollAmount = 3; 133 | 134 | [SerializeField] 135 | private string formatting = "[{time} {type}] {text}"; 136 | 137 | public int historySize = 1024; 138 | public bool logToFile = true; 139 | public bool checkForOpenInput = true; 140 | public bool reportUnknownCommand = true; 141 | 142 | [SerializeField] 143 | private string consoleChararacters = "`!~*^#\\Ё§"; 144 | 145 | /* 146 | ` = common/english qwerty 147 | ! = common/english dvorak 148 | ~ = khmer 149 | * = turkish 150 | ^ = germany 151 | # = french dvorak/bepo standard 152 | \ = albanian qwertz 153 | Ё = russian 154 | § = ukranian 155 | */ 156 | 157 | [SerializeField] 158 | private bool allowWarnings = false; 159 | 160 | [SerializeField] 161 | private bool allowLogs = true; 162 | 163 | [SerializeField] 164 | private bool allowExceptions = true; 165 | 166 | [SerializeField] 167 | private bool allowErrors = true; 168 | 169 | [SerializeField] 170 | private bool allowAsserts = true; 171 | 172 | [SerializeField] 173 | private string logFilePathPlayer = "./Log.txt"; 174 | 175 | [SerializeField] 176 | private string logFilePathEditor = "./Assets/Log.txt"; 177 | 178 | [NonSerialized] 179 | private string userColorHex; 180 | 181 | [NonSerialized] 182 | private string logColorHex; 183 | 184 | [NonSerialized] 185 | private string warnColorHex; 186 | 187 | [NonSerialized] 188 | private string errorColorHex; 189 | 190 | [NonSerialized] 191 | private string exceptionColorHex; 192 | 193 | [NonSerialized] 194 | private string cachedLogFilePath; 195 | 196 | [NonSerialized] 197 | private float fontSize; 198 | 199 | private void OnEnable() 200 | { 201 | fontSize = consoleStyle.fontSize; 202 | current = this; 203 | } 204 | 205 | /// 206 | /// Is the current scene blacklisted and disallowed. 207 | /// 208 | public bool IsSceneBlacklisted() 209 | { 210 | Scene scene = SceneManager.GetActiveScene(); 211 | string sceneName = scene.name; 212 | int sceneCount = blacklistedSceneNames.Count; 213 | for (int i = sceneCount - 1; i >= 0; i--) 214 | { 215 | if (blacklistedSceneNames[i].Equals(sceneName)) 216 | { 217 | return true; 218 | } 219 | } 220 | 221 | return false; 222 | } 223 | 224 | public string GetColor(LogType type) 225 | { 226 | if (type == LogType.Log) 227 | { 228 | if (string.IsNullOrEmpty(logColorHex)) 229 | { 230 | stringBuilder.Clear(); 231 | stringBuilder.Append("#"); 232 | stringBuilder.Append(ColorUtility.ToHtmlStringRGBA(logColor)); 233 | logColorHex = stringBuilder.ToString(); 234 | } 235 | 236 | return logColorHex; 237 | } 238 | else if (type == LogType.Error) 239 | { 240 | if (string.IsNullOrEmpty(errorColorHex)) 241 | { 242 | stringBuilder.Clear(); 243 | stringBuilder.Append("#"); 244 | stringBuilder.Append(ColorUtility.ToHtmlStringRGBA(errorColor)); 245 | errorColorHex = stringBuilder.ToString(); 246 | } 247 | 248 | return errorColorHex; 249 | } 250 | else if (type == LogType.Warning) 251 | { 252 | if (string.IsNullOrEmpty(warnColorHex)) 253 | { 254 | stringBuilder.Clear(); 255 | stringBuilder.Append("#"); 256 | stringBuilder.Append(ColorUtility.ToHtmlStringRGBA(warnColor)); 257 | warnColorHex = stringBuilder.ToString(); 258 | } 259 | 260 | return warnColorHex; 261 | } 262 | else if (type == LogType.Exception) 263 | { 264 | if (string.IsNullOrEmpty(exceptionColorHex)) 265 | { 266 | stringBuilder.Clear(); 267 | stringBuilder.Append("#"); 268 | stringBuilder.Append(ColorUtility.ToHtmlStringRGBA(exceptionColor)); 269 | exceptionColorHex = stringBuilder.ToString(); 270 | } 271 | 272 | return exceptionColorHex; 273 | } 274 | else 275 | { 276 | return "gray"; 277 | } 278 | } 279 | 280 | /// 281 | /// Returns true if this character is for opening the console. 282 | /// 283 | public bool IsConsoleOpenChar(char c) 284 | { 285 | if (!string.IsNullOrEmpty(consoleChararacters)) 286 | { 287 | int length = consoleChararacters.Length; 288 | for (int i = length - 1; i >= 0; i--) 289 | { 290 | if (consoleChararacters[i] == c) 291 | { 292 | return true; 293 | } 294 | } 295 | } 296 | 297 | return false; 298 | } 299 | 300 | /// 301 | /// Is this type of log allowed? 302 | /// 303 | public bool IsAllowed(LogType logType) 304 | { 305 | if (logType == LogType.Assert) 306 | { 307 | return allowAsserts; 308 | } 309 | else if (logType == LogType.Error) 310 | { 311 | return allowErrors; 312 | } 313 | else if (logType == LogType.Exception) 314 | { 315 | return allowExceptions; 316 | } 317 | else if (logType == LogType.Log) 318 | { 319 | return allowLogs; 320 | } 321 | else if (logType == LogType.Warning) 322 | { 323 | return allowWarnings; 324 | } 325 | else 326 | { 327 | return false; 328 | } 329 | } 330 | 331 | /// 332 | /// Formats the text that is used to log to disk with. 333 | /// 334 | public string FormatText(string text, string logType) 335 | { 336 | if (text != null) 337 | { 338 | stringBuilder.Clear(); 339 | stringBuilder.Append(formatting); 340 | stringBuilder.Replace("{time}", DateTime.Now.ToString()); 341 | stringBuilder.Replace("{text}", text); 342 | stringBuilder.Replace("{type}", logType); 343 | return stringBuilder.ToString(); 344 | } 345 | 346 | return null; 347 | } 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /Runtime/Settings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e1ea90ec52e939e4e869c35b33e0fcf3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Table.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using UnityEngine; 5 | 6 | namespace Popcron.Console 7 | { 8 | /// 9 | /// Utility class for creating a stringified table to print. 10 | /// 11 | public class Table 12 | { 13 | public const char HeaderSeparator = '='; 14 | public const char ColumnSeparator = '|'; 15 | public const string NullString = "null"; 16 | public const string TrueString = "✔"; 17 | public const string FalseString = "✕"; 18 | 19 | private static StringBuilder builder = new StringBuilder(); 20 | private const string Indent = " "; 21 | private const int Padding = 5; 22 | 23 | private int columns; 24 | private int rows; 25 | private List cells; 26 | private Dictionary richTextOffset; 27 | 28 | private Table() 29 | { 30 | 31 | } 32 | 33 | public Table(params object[] row) 34 | { 35 | if (row != null && row.Length != 0) 36 | { 37 | cells = new List(); 38 | richTextOffset = new Dictionary(); 39 | columns = row.Length; 40 | rows = 1; 41 | cells.Add(row); 42 | } 43 | else 44 | { 45 | throw new Exception($"Creating a table with no columns is not allowed"); 46 | } 47 | } 48 | 49 | /// 50 | /// Inserts a new row to the table at this index. 51 | /// Must match the original amount of columns provided. 52 | /// 53 | public void InsertRow(int index, params object[] row) 54 | { 55 | if (row.Length == columns) 56 | { 57 | cells.Insert(index + 1, row); 58 | rows++; 59 | } 60 | else 61 | { 62 | throw new Exception($"Column count does not match, must be {columns} column(s)"); 63 | } 64 | } 65 | 66 | /// 67 | /// Adds a new row to the bottom of the table. 68 | /// Must match the original amount of columns provided. 69 | /// 70 | public void AddRow(params object[] row) 71 | { 72 | if (row.Length == columns) 73 | { 74 | cells.Add(row); 75 | rows++; 76 | } 77 | else 78 | { 79 | throw new Exception($"Column count does not match, must be {columns} column(s)"); 80 | } 81 | } 82 | 83 | public override string ToString() 84 | { 85 | builder.Clear(); 86 | 87 | //gather all texts and max lengths 88 | string[,] texts = new string[columns, rows]; 89 | int[] maxLengths = new int[columns]; 90 | int totalWidth = Indent.Length; 91 | for (int c = 0; c < columns; c++) 92 | { 93 | int maxLength = 0; 94 | for (int r = 0; r < rows; r++) 95 | { 96 | object cell = cells[r][c]; 97 | string text; 98 | int length = 0; 99 | if (cell is bool) 100 | { 101 | text = ((bool)cell) ? TrueString : FalseString; 102 | richTextOffset[text] = 0; 103 | 104 | if (!string.IsNullOrEmpty(text)) 105 | { 106 | length = text.Length; 107 | } 108 | } 109 | else 110 | { 111 | if (cell is null) 112 | { 113 | text = NullString; 114 | richTextOffset[text] = 0; 115 | 116 | if (!string.IsNullOrEmpty(text)) 117 | { 118 | length = text.Length; 119 | } 120 | } 121 | else 122 | { 123 | text = cell.ToString(); 124 | if (!string.IsNullOrEmpty(text)) 125 | { 126 | int richTextCount = CountRichTextLength(text); 127 | length = text.Length - richTextCount; 128 | richTextOffset[text] = richTextCount; 129 | } 130 | else 131 | { 132 | richTextOffset[text] = 0; 133 | } 134 | } 135 | } 136 | 137 | texts[c, r] = text; 138 | maxLength = Mathf.Max(maxLength, length); 139 | } 140 | 141 | maxLengths[c] = maxLength; 142 | } 143 | 144 | //calculate total width of the table 145 | for (int c = 0; c < columns; c++) 146 | { 147 | totalWidth += maxLengths[c] + Padding; 148 | } 149 | 150 | //build text 151 | for (int r = 0; r < rows; r++) 152 | { 153 | builder.Append(Indent); 154 | for (int c = 0; c < columns; c++) 155 | { 156 | string text = texts[c, r]; 157 | int totalLength = maxLengths[c] + Padding; 158 | 159 | if (text != null) 160 | { 161 | AppendPadRight(text, totalLength + richTextOffset[text]); 162 | if (c < columns - 1) 163 | { 164 | builder.Append(ColumnSeparator); 165 | builder.Append(' '); 166 | } 167 | } 168 | else 169 | { 170 | AppendRepeatingString(' ', totalLength); 171 | if (c < columns - 1) 172 | { 173 | builder.Append(ColumnSeparator); 174 | builder.Append(' '); 175 | } 176 | } 177 | } 178 | 179 | if (r == 0) 180 | { 181 | //header row 182 | builder.AppendLine(); 183 | AppendRepeatingString(HeaderSeparator, totalWidth); 184 | builder.AppendLine(); 185 | } 186 | else 187 | { 188 | builder.AppendLine(); 189 | } 190 | } 191 | 192 | return builder.ToString(); 193 | } 194 | 195 | private int CountRichTextLength(string text) 196 | { 197 | int count = 0; 198 | int textLength = text.Length; 199 | bool insideTag = false; 200 | for (int i = 0; i < textLength; i++) 201 | { 202 | char c = text[i]; 203 | if (c == '<') 204 | { 205 | insideTag = true; 206 | } 207 | 208 | if (insideTag) 209 | { 210 | count++; 211 | } 212 | 213 | if (c == '>') 214 | { 215 | insideTag = false; 216 | } 217 | } 218 | 219 | return count; 220 | } 221 | 222 | private void AppendPadRight(string text, int totalWidth) 223 | { 224 | builder.Append(text); 225 | if (text.Length < totalWidth) 226 | { 227 | int remainder = totalWidth - text.Length; 228 | AppendRepeatingString(' ', remainder); 229 | } 230 | } 231 | 232 | private void AppendRepeatingString(char c, int length) 233 | { 234 | for (int i = length - 1; i >= 0; i--) 235 | { 236 | builder.Append(c); 237 | } 238 | } 239 | } 240 | } -------------------------------------------------------------------------------- /Runtime/Table.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8bb56ecedf9114a4fa0ff11cc8608864 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.popcron.console", 3 | "displayName": "Popcron Console", 4 | "version": "1.10.3", 5 | "unity": "2018.3", 6 | "description": "Command parsing API.", 7 | "keywords": [ 8 | "console", 9 | "cli" 10 | ], 11 | "author": { 12 | "name": "popcron", 13 | "url": "https://github.com/popcron" 14 | }, 15 | "type": "tool" 16 | } 17 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 70be996ecb68e5e40b7f15c86be6fbd8 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------