From 993819f8cbe74991612d6eae4cbec1128da1468a Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 9 Jun 2024 14:47:23 +0200 Subject: [PATCH 1/6] console: add bearable autocompletion --- .../Internal/Windows/ConsoleWindow.cs | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs b/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs index 51ab7404a7..e6d7abd9c1 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 @@ public override void Draw() ref this.commandText, 255, ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CallbackCompletion | - ImGuiInputTextFlags.CallbackHistory, + ImGuiInputTextFlags.CallbackHistory | ImGuiInputTextFlags.CallbackEdit, this.CommandInputCallback)) { this.ProcessCommand(); @@ -832,6 +835,11 @@ private unsafe int CommandInputCallback(ImGuiInputTextCallbackData* data) 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 @@ private unsafe int CommandInputCallback(ImGuiInputTextCallbackData* data) // 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; From 2d5c4ed7dc9bf99abdc245a55d6fdeb2512ab9dc Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 9 Jun 2024 21:32:23 +0200 Subject: [PATCH 2/6] console: print variable contents, flip bools, print command on enter --- Dalamud/Console/ConsoleManager.cs | 26 ++++++++++++++----- .../Internal/Windows/ConsoleWindow.cs | 1 + 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Dalamud/Console/ConsoleManager.cs b/Dalamud/Console/ConsoleManager.cs index 6600069c26..4112cde2a8 100644 --- a/Dalamud/Console/ConsoleManager.cs +++ b/Dalamud/Console/ConsoleManager.cs @@ -270,7 +270,9 @@ public bool ProcessCommand(string command) for (var i = parsedArguments.Count; i < entry.ValidArguments.Count; i++) { var argument = entry.ValidArguments[i]; - if (argument.DefaultValue == null) + + // If the default value is DBNull, we need to error out as that means it was not specified + if (argument.DefaultValue == DBNull.Value) { Log.Error("Not enough arguments for command {CommandName}", entryName); PrintUsage(entry); @@ -382,11 +384,8 @@ public ConsoleEntry(string name, string description) /// The default value to use if none is specified. /// An instance. /// Thrown if the given type cannot be handled by the console system. - protected static ArgumentInfo TypeToArgument(Type type, object? defaultValue = null) + protected static ArgumentInfo TypeToArgument(Type type, object? defaultValue) { - // If the default value is DBNull, we want to treat it as null - defaultValue = defaultValue == DBNull.Value ? null : defaultValue; - if (type == typeof(string)) return new ArgumentInfo(ConsoleArgumentType.String, defaultValue); @@ -490,7 +489,7 @@ private class ConsoleVariable : ConsoleVariable, IConsoleVariable public ConsoleVariable(string name, string description) : base(name, description) { - this.ValidArguments = new List { TypeToArgument(typeof(T)) }; + this.ValidArguments = new List { TypeToArgument(typeof(T), null) }; } /// @@ -500,7 +499,20 @@ public ConsoleVariable(string name, string description) public override bool Invoke(IEnumerable arguments) { var first = arguments.FirstOrDefault(); - if (first == null || first.GetType() != typeof(T)) + + if (first == null) + { + // Invert the value if it's a boolean + if (this.Value is bool boolValue) + { + this.Value = (T)(object)!boolValue; + } + + Log.WriteLog(LogEventLevel.Information, "{VariableName} = {VariableValue}", null, this.Name, this.Value); + return true; + } + + if (first.GetType() != typeof(T)) throw new ArgumentException($"Console variable must be set with an argument of type {typeof(T).Name}."); this.Value = (T)first; diff --git a/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs b/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs index e6d7abd9c1..85c7a7380b 100644 --- a/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs +++ b/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs @@ -321,6 +321,7 @@ public override void Draw() ImGuiInputTextFlags.CallbackHistory | ImGuiInputTextFlags.CallbackEdit, this.CommandInputCallback)) { + this.newLogEntries.Enqueue((this.commandText, new LogEvent(DateTimeOffset.Now, LogEventLevel.Information, null, new MessageTemplate(string.Empty, []), []))); this.ProcessCommand(); getFocus = true; } From 1133fcfb9dd199fe652857289840a925661b73ab Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 9 Jun 2024 21:42:30 +0200 Subject: [PATCH 3/6] console: persist log command buffer in config --- .../Internal/Windows/ConsoleWindow.cs | 69 +++++++++---------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs b/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs index 85c7a7380b..ebb89fbee7 100644 --- a/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs +++ b/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs @@ -38,6 +38,7 @@ internal class ConsoleWindow : Window, IDisposable { private const int LogLinesMinimum = 100; private const int LogLinesMaximum = 1000000; + private const int HistorySize = 50; // Only this field may be touched from any thread. private readonly ConcurrentQueue<(string Line, LogEvent LogEvent)> newLogEntries; @@ -45,9 +46,10 @@ internal class ConsoleWindow : Window, IDisposable // Fields below should be touched only from the main thread. private readonly RollingList logText; private readonly RollingList filteredLogEntries; - - private readonly List history = new(); + private readonly List pluginFilters = new(); + + private readonly DalamudConfiguration configuration; private int newRolledLines; private bool pendingRefilter; @@ -89,6 +91,8 @@ internal class ConsoleWindow : Window, IDisposable public ConsoleWindow(DalamudConfiguration configuration) : base("Dalamud Console", ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse) { + this.configuration = configuration; + this.autoScroll = configuration.LogAutoScroll; this.autoOpen = configuration.LogOpenAtStartup; SerilogEventSink.Instance.LogLine += this.OnLogLine; @@ -115,7 +119,7 @@ public ConsoleWindow(DalamudConfiguration configuration) this.logText = new(limit); this.filteredLogEntries = new(limit); - configuration.DalamudConfigurationSaved += this.OnDalamudConfigurationSaved; + this.configuration.DalamudConfigurationSaved += this.OnDalamudConfigurationSaved; unsafe { @@ -134,7 +138,7 @@ public override void OnOpen() public void Dispose() { SerilogEventSink.Instance.LogLine -= this.OnLogLine; - Service.Get().DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved; + this.configuration.DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved; if (Service.GetNullable() is { } framework) framework.Update -= this.FrameworkOnUpdate; @@ -465,8 +469,6 @@ private void CopyFilteredLogEntries(bool selectedOnly) private void DrawOptionsToolbar() { - var configuration = Service.Get(); - ImGui.PushItemWidth(150.0f * ImGuiHelpers.GlobalScale); if (ImGui.BeginCombo("##log_level", $"{EntryPoint.LogLevelSwitch.MinimumLevel}+")) { @@ -475,8 +477,8 @@ private void DrawOptionsToolbar() if (ImGui.Selectable(value.ToString(), value == EntryPoint.LogLevelSwitch.MinimumLevel)) { EntryPoint.LogLevelSwitch.MinimumLevel = value; - configuration.LogLevel = value; - configuration.QueueSave(); + this.configuration.LogLevel = value; + this.configuration.QueueSave(); this.QueueRefilter(); } } @@ -489,13 +491,13 @@ private void DrawOptionsToolbar() var settingsPopup = ImGui.BeginPopup("##console_settings"); if (settingsPopup) { - this.DrawSettingsPopup(configuration); + this.DrawSettingsPopup(); ImGui.EndPopup(); } else if (this.settingsPopupWasOpen) { // Prevent side effects in case Apply wasn't clicked - this.logLinesLimit = configuration.LogLinesLimit; + this.logLinesLimit = this.configuration.LogLinesLimit; } this.settingsPopupWasOpen = settingsPopup; @@ -643,18 +645,18 @@ private void DrawOptionsToolbar() } } - private void DrawSettingsPopup(DalamudConfiguration configuration) + private void DrawSettingsPopup() { if (ImGui.Checkbox("Open at startup", ref this.autoOpen)) { - configuration.LogOpenAtStartup = this.autoOpen; - configuration.QueueSave(); + this.configuration.LogOpenAtStartup = this.autoOpen; + this.configuration.QueueSave(); } if (ImGui.Checkbox("Auto-scroll", ref this.autoScroll)) { - configuration.LogAutoScroll = this.autoScroll; - configuration.QueueSave(); + this.configuration.LogAutoScroll = this.autoScroll; + this.configuration.QueueSave(); } ImGui.TextUnformatted("Logs buffer"); @@ -663,8 +665,8 @@ private void DrawSettingsPopup(DalamudConfiguration configuration) { this.logLinesLimit = Math.Max(LogLinesMinimum, this.logLinesLimit); - configuration.LogLinesLimit = this.logLinesLimit; - configuration.QueueSave(); + this.configuration.LogLinesLimit = this.logLinesLimit; + this.configuration.QueueSave(); ImGui.CloseCurrentPopup(); } @@ -800,23 +802,18 @@ private void ProcessCommand() { try { - this.historyPos = -1; - for (var i = this.history.Count - 1; i >= 0; i--) - { - if (this.history[i] == this.commandText) - { - this.history.RemoveAt(i); - break; - } - } - - this.history.Add(this.commandText); - - if (this.commandText is "clear" or "cls") - { - this.QueueClear(); + if (string.IsNullOrEmpty(this.commandText)) return; - } + + this.historyPos = -1; + + if (this.commandText != this.configuration.LogCommandHistory.LastOrDefault()) + this.configuration.LogCommandHistory.Add(this.commandText); + + if (this.configuration.LogCommandHistory.Count > HistorySize) + this.configuration.LogCommandHistory.RemoveAt(0); + + this.configuration.QueueSave(); this.lastCmdSuccess = Service.Get().ProcessCommand(this.commandText); this.commandText = string.Empty; @@ -902,7 +899,7 @@ private unsafe int CommandInputCallback(ImGuiInputTextCallbackData* data) if (ptr.EventKey == ImGuiKey.UpArrow) { if (this.historyPos == -1) - this.historyPos = this.history.Count - 1; + this.historyPos = this.configuration.LogCommandHistory.Count - 1; else if (this.historyPos > 0) this.historyPos--; } @@ -910,7 +907,7 @@ private unsafe int CommandInputCallback(ImGuiInputTextCallbackData* data) { if (this.historyPos != -1) { - if (++this.historyPos >= this.history.Count) + if (++this.historyPos >= this.configuration.LogCommandHistory.Count) { this.historyPos = -1; } @@ -919,7 +916,7 @@ private unsafe int CommandInputCallback(ImGuiInputTextCallbackData* data) if (prevPos != this.historyPos) { - var historyStr = this.historyPos >= 0 ? this.history[this.historyPos] : string.Empty; + var historyStr = this.historyPos >= 0 ? this.configuration.LogCommandHistory[this.historyPos] : string.Empty; ptr.DeleteChars(0, ptr.BufTextLen); ptr.InsertChars(0, historyStr); From d0cba3eca8150f3c92d65861b7ca01f49e2ac9f5 Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Mon, 10 Jun 2024 21:01:26 +0200 Subject: [PATCH 4/6] build: 9.1.0.11 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 5cf16101fe..b47accc9b7 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -8,7 +8,7 @@ - 9.1.0.10 + 9.1.0.11 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) From 9fdc7048343e85b67f8efa65358de97390383e7d Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 10 Jun 2024 21:23:25 +0200 Subject: [PATCH 5/6] add missing history property to config --- Dalamud/Configuration/Internal/DalamudConfiguration.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs index 67c2208003..0267042edd 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -228,6 +228,11 @@ internal sealed class DalamudConfiguration : IInternalDisposableService /// public int LogLinesLimit { get; set; } = 10000; + /// + /// Gets or sets a list of commands that have been run in the console window. + /// + public List LogCommandHistory { get; set; } = new(); + /// /// Gets or sets a value indicating whether or not the dev bar should open at startup. /// From 6adf0d7bd9fd4ed8b4a5acb237a544034a8eb0e9 Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 10 Jun 2024 21:24:25 +0200 Subject: [PATCH 6/6] build: 9.1.0.12 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index b47accc9b7..b25aeeccea 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -8,7 +8,7 @@ - 9.1.0.11 + 9.1.0.12 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion)