From 5328ea891dd1fa41d98535c0626aa862b639e811 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 20 Oct 2024 23:23:54 +0200 Subject: [PATCH] Add command line input to Output Log in Editor --- Source/Editor/Windows/OutputLogWindow.cs | 84 +++++++++++++++++++++++- Source/Engine/Debug/DebugCommands.cpp | 33 +++++++++- Source/Engine/Debug/DebugCommands.h | 10 ++- 3 files changed, 123 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index f8c4ea9b5..36c6a5c6c 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -125,6 +125,78 @@ namespace FlaxEditor.Windows } } + /// + /// Command line input textbox control which can execute debug commands. + /// + private class CommandLineBox : TextBox + { + public CommandLineBox(float x, float y, float width) + : base(false, x, y, width) + { + WatermarkText = ">"; + } + + /// + public override bool OnKeyDown(KeyboardKeys key) + { + switch (key) + { + case KeyboardKeys.Return: + { + // Run command + DebugCommands.Execute(Text); + SetText(string.Empty); + return true; + } + case KeyboardKeys.Tab: + { + // Auto-complete + DebugCommands.Search(Text, out var matches, true); + if (matches.Length == 0) + { + // Nothing found + } + else if (matches.Length == 1) + { + // Exact match + SetText(matches[0]); + SetSelection(Text.Length); + } + else + { + // Find the most common part + Array.Sort(matches); + int minLength = Text.Length; + int maxLength = matches[0].Length; + int sharedLength = minLength + 1; + bool allMatch = true; + for (; allMatch && sharedLength < maxLength; sharedLength++) + { + var shared = matches[0].Substring(0, sharedLength); + for (int i = 1; i < matches.Length; i++) + { + if (!matches[i].StartsWith(shared, StringComparison.OrdinalIgnoreCase)) + { + sharedLength -= 2; + allMatch = false; + break; + } + } + } + if (sharedLength > minLength) + { + // Use the largest shared part of all matches + SetText(matches[0].Substring(0, sharedLength)); + SetSelection(sharedLength); + } + } + return true; + } + } + return base.OnKeyDown(key); + } + } + private InterfaceOptions.TimestampsFormats _timestampsFormats; private bool _showLogType; @@ -147,6 +219,7 @@ namespace FlaxEditor.Windows private HScrollBar _hScroll; private VScrollBar _vScroll; private OutputTextBox _output; + private CommandLineBox _commandLineBox; private ContextMenu _contextMenu; /// @@ -173,13 +246,13 @@ namespace FlaxEditor.Windows Parent = this, }; _searchBox.TextChanged += Refresh; - _hScroll = new HScrollBar(this, Height - _scrollSize, Width - _scrollSize, _scrollSize) + _hScroll = new HScrollBar(this, Height - _scrollSize - TextBox.DefaultHeight - 2, Width - _scrollSize, _scrollSize) { ThumbThickness = 10, Maximum = 0, }; _hScroll.ValueChanged += OnHScrollValueChanged; - _vScroll = new VScrollBar(this, Width - _scrollSize, Height - _viewDropdown.Height - 2, _scrollSize) + _vScroll = new VScrollBar(this, Width - _scrollSize, Height - _viewDropdown.Height - 4 - TextBox.DefaultHeight, _scrollSize) { ThumbThickness = 10, Maximum = 0, @@ -197,6 +270,10 @@ namespace FlaxEditor.Windows }; _output.TargetViewOffsetChanged += OnOutputTargetViewOffsetChanged; _output.TextChanged += OnOutputTextChanged; + _commandLineBox = new CommandLineBox(2, Height - 2 - TextBox.DefaultHeight, Width - 4) + { + Parent = this, + }; // Setup context menu _contextMenu = new ContextMenu(); @@ -422,6 +499,8 @@ namespace FlaxEditor.Windows { _searchBox.Width = Width - _viewDropdown.Right - 4; _output.Size = new Float2(_vScroll.X - 2, _hScroll.Y - 4 - _viewDropdown.Bottom); + _commandLineBox.Width = Width - 4; + _commandLineBox.Y = Height - 2 - _commandLineBox.Height; } } @@ -664,6 +743,7 @@ namespace FlaxEditor.Windows _hScroll = null; _vScroll = null; _output = null; + _commandLineBox = null; _contextMenu = null; base.OnDestroy(); diff --git a/Source/Engine/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp index e9f5b9b93..19c569b8a 100644 --- a/Source/Engine/Debug/DebugCommands.cpp +++ b/Source/Engine/Debug/DebugCommands.cpp @@ -266,6 +266,37 @@ void DebugCommands::Execute(StringView command) LOG(Error, "Unknown command '{}'", name); } +void DebugCommands::Search(StringView searchText, Array& matches, bool startsWith) +{ + if (searchText.IsEmpty()) + return; + + ScopeLock lock(Locker); + if (!Inited) + InitCommands(); + + if (startsWith) + { + for (auto& command : Commands) + { + if (command.Name.StartsWith(searchText, StringSearchCase::IgnoreCase)) + { + matches.Add(command.Name); + } + } + } + else + { + for (auto& command : Commands) + { + if (command.Name.Contains(searchText.Get(), StringSearchCase::IgnoreCase)) + { + matches.Add(command.Name); + } + } + } +} + bool DebugCommands::Iterate(const StringView& searchText, int32& index) { ScopeLock lock(Locker); @@ -286,7 +317,7 @@ bool DebugCommands::Iterate(const StringView& searchText, int32& index) return false; } -String DebugCommands::GetCommandName(int32 index) +StringView DebugCommands::GetCommandName(int32 index) { ScopeLock lock(Locker); CHECK_RETURN(Commands.IsValidIndex(index), String::Empty); diff --git a/Source/Engine/Debug/DebugCommands.h b/Source/Engine/Debug/DebugCommands.h index e30ec048e..f89cba371 100644 --- a/Source/Engine/Debug/DebugCommands.h +++ b/Source/Engine/Debug/DebugCommands.h @@ -18,7 +18,15 @@ public: /// The command line (optionally with arguments). API_FUNCTION() static void Execute(StringView command); + /// + /// Searches the list of commands to return candidates that match the given query text. + /// + /// The query text. + /// The output list of commands that match a given query (unsorted). + /// True if filter commands that start with a specific search text, otherwise will return commands that contain a specific query. + API_FUNCTION() static void Search(StringView searchText, API_PARAM(Out) Array& matches, bool startsWith = false); + public: static bool Iterate(const StringView& searchText, int32& index); - static String GetCommandName(int32 index); + static StringView GetCommandName(int32 index); };