diff --git a/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs b/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs index 51ab7404a..e6d7abd9c 100644 --- a/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs +++ b/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs @@ -79,6 +79,9 @@ internal class ConsoleWindow : Window, IDisposable private int historyPos; private int copyStart = -1; + private string? completionZipText = null; + private int completionTabIdx = 0; + private IActiveNotification? prevCopyNotification; /// Initializes a new instance of the class. @@ -315,7 +318,7 @@ internal class ConsoleWindow : Window, IDisposable ref this.commandText, 255, ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CallbackCompletion | - ImGuiInputTextFlags.CallbackHistory, + ImGuiInputTextFlags.CallbackHistory | ImGuiInputTextFlags.CallbackEdit, this.CommandInputCallback)) { this.ProcessCommand(); @@ -832,6 +835,11 @@ internal class ConsoleWindow : Window, IDisposable switch (data->EventFlag) { + case ImGuiInputTextFlags.CallbackEdit: + this.completionZipText = null; + this.completionTabIdx = 0; + break; + case ImGuiInputTextFlags.CallbackCompletion: var textBytes = new byte[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 if (words.Length > 1) 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. // https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L6443-L6484 var candidates = Service.Get().Entries - .Where(x => x.Key.StartsWith(words[0])) + .Where(x => x.Key.StartsWith(wordToComplete)) .Select(x => x.Key); candidates = candidates.Union( Service.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 (enumerable.Length != 0) + if (candidates.Any()) { - ptr.DeleteChars(0, ptr.BufTextLen); - ptr.InsertChars(0, enumerable[0]); + string? toComplete = null; + 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;