console: add bearable autocompletion

This commit is contained in:
goat 2024-06-09 14:47:23 +02:00
parent 1d939e2587
commit 993819f8cb

View file

@ -79,6 +79,9 @@ internal class ConsoleWindow : Window, IDisposable
private int historyPos; private int historyPos;
private int copyStart = -1; private int copyStart = -1;
private string? completionZipText = null;
private int completionTabIdx = 0;
private IActiveNotification? prevCopyNotification; private IActiveNotification? prevCopyNotification;
/// <summary>Initializes a new instance of the <see cref="ConsoleWindow"/> class.</summary> /// <summary>Initializes a new instance of the <see cref="ConsoleWindow"/> class.</summary>
@ -315,7 +318,7 @@ internal class ConsoleWindow : Window, IDisposable
ref this.commandText, ref this.commandText,
255, 255,
ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CallbackCompletion | ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CallbackCompletion |
ImGuiInputTextFlags.CallbackHistory, ImGuiInputTextFlags.CallbackHistory | ImGuiInputTextFlags.CallbackEdit,
this.CommandInputCallback)) this.CommandInputCallback))
{ {
this.ProcessCommand(); this.ProcessCommand();
@ -832,6 +835,11 @@ internal class ConsoleWindow : Window, IDisposable
switch (data->EventFlag) switch (data->EventFlag)
{ {
case ImGuiInputTextFlags.CallbackEdit:
this.completionZipText = null;
this.completionTabIdx = 0;
break;
case ImGuiInputTextFlags.CallbackCompletion: case ImGuiInputTextFlags.CallbackCompletion:
var textBytes = new byte[data->BufTextLen]; var textBytes = new byte[data->BufTextLen];
Marshal.Copy((IntPtr)data->Buf, textBytes, 0, data->BufTextLen); Marshal.Copy((IntPtr)data->Buf, textBytes, 0, data->BufTextLen);
@ -842,22 +850,47 @@ internal class ConsoleWindow : Window, IDisposable
// We can't do any completion for parameters at the moment since it just calls into CommandHandler // We can't do any completion for parameters at the moment since it just calls into CommandHandler
if (words.Length > 1) if (words.Length > 1)
return 0; return 0;
var wordToComplete = words[0];
if (wordToComplete.IsNullOrWhitespace())
return 0;
if (this.completionZipText is not null)
wordToComplete = this.completionZipText;
// TODO: Improve this, add partial completion, arguments, description, etc. // TODO: Improve this, add partial completion, arguments, description, etc.
// https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L6443-L6484 // https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L6443-L6484
var candidates = Service<ConsoleManager>.Get().Entries var candidates = Service<ConsoleManager>.Get().Entries
.Where(x => x.Key.StartsWith(words[0])) .Where(x => x.Key.StartsWith(wordToComplete))
.Select(x => x.Key); .Select(x => x.Key);
candidates = candidates.Union( candidates = candidates.Union(
Service<CommandManager>.Get().Commands Service<CommandManager>.Get().Commands
.Where(x => x.Key.StartsWith(words[0])).Select(x => x.Key)); .Where(x => x.Key.StartsWith(wordToComplete)).Select(x => x.Key))
.ToArray();
var enumerable = candidates as string[] ?? candidates.ToArray(); if (candidates.Any())
if (enumerable.Length != 0)
{ {
ptr.DeleteChars(0, ptr.BufTextLen); string? toComplete = null;
ptr.InsertChars(0, enumerable[0]); if (this.completionZipText == null)
{
// Find the "common" prefix of all matches
toComplete = candidates.Aggregate(
(prefix, candidate) => string.Concat(prefix.Zip(candidate, (a, b) => a == b ? a : '\0')));
this.completionZipText = toComplete;
}
else
{
toComplete = candidates.ElementAt(this.completionTabIdx);
this.completionTabIdx = (this.completionTabIdx + 1) % candidates.Count();
}
if (toComplete != null)
{
ptr.DeleteChars(0, ptr.BufTextLen);
ptr.InsertChars(0, toComplete);
}
} }
break; break;