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);
};