feat(input-bar): wire slash-command callback + AllCommands (J2)

J2 closes the trigger-gap discovered in J review (2026-05-27): J
migrated CommandHelpWindow as a window but the v1.5.6 trigger-path
was never ported. J2 restores it:

- InputBar.cs adds ImGuiInputTextFlags.CallbackEdit + character-level
  callback that reads data.BufTextSpan, detects /-prefix, extracts
  command word, and calls _commandHelpWindow.Value.UpdateContent(desc)
- AllCommands.cs (new file, 1:1 port from v1.5.6) populates a static
  Dictionary<string, TextCommand> from Sheets.TextCommandSheet at
  startup; Plugin.CommandManager.Commands is the fallback for
  non-hardcoded commands
- CommandHelpWindow injected into InputBar via Lazy<T> ctor param to
  break the InputBar <-> CommandHelpWindow circular dep; PluginHostFactory
  DI-reg extended with the Lazy wrapper accordingly

Closes the smoke-step-9 gap. Phase-3 windows are now all reachable
end-to-end (R1 InputPreview, R2 CommandHelpWindow, R3 DebuggerWindow).
This commit is contained in:
2026-05-27 23:19:21 +02:00
parent c652a1c450
commit cba6a16f8e
3 changed files with 72 additions and 3 deletions
+36
View File
@@ -0,0 +1,36 @@
using Lumina.Excel.Sheets;
namespace HellionChat;
// Ported 1:1 from v1.5.6 ChatLogWindow.SetUpAllCommands. Provides a fast
// lookup from slash-command string to the game's TextCommand row so the
// InputBar callback can feed descriptions to CommandHelpWindow without
// hitting the sheet on every keystroke.
internal static class AllCommands
{
private static readonly Dictionary<string, TextCommand> Commands = BuildCommands();
private static Dictionary<string, TextCommand> BuildCommands()
{
var dict = new Dictionary<string, TextCommand>(StringComparer.Ordinal);
foreach (var command in Sheets.TextCommandSheet)
{
if (!command.Command.IsEmpty)
dict.TryAdd(command.Command.ToString(), command);
if (!command.ShortCommand.IsEmpty)
dict.TryAdd(command.ShortCommand.ToString(), command);
if (!command.Alias.IsEmpty)
dict.TryAdd(command.Alias.ToString(), command);
if (!command.ShortAlias.IsEmpty)
dict.TryAdd(command.ShortAlias.ToString(), command);
}
return dict;
}
public static bool TryGetValue(string command, out TextCommand textCommand) =>
Commands.TryGetValue(command, out textCommand);
}
+2 -1
View File
@@ -142,7 +142,8 @@ internal static class PluginHostFactory
sp.GetRequiredService<ThemeRegistry>(), sp.GetRequiredService<ThemeRegistry>(),
sp.GetRequiredService<Ui.StyleEngine.TokenResolver>(), sp.GetRequiredService<Ui.StyleEngine.TokenResolver>(),
sp.GetRequiredService<ILogger<Ui.Components.InputBar>>(), sp.GetRequiredService<ILogger<Ui.Components.InputBar>>(),
() => sp.GetRequiredService<Plugin>().SettingsWindow.Toggle() () => sp.GetRequiredService<Plugin>().SettingsWindow.Toggle(),
new Lazy<Ui.CommandHelpWindow>(() => sp.GetRequiredService<Ui.CommandHelpWindow>())
)); ));
services.AddSingleton(sp => new Ui.Components.Settings.TabSidebar( services.AddSingleton(sp => new Ui.Components.Settings.TabSidebar(
sp.GetRequiredService<FontManager>() sp.GetRequiredService<FontManager>()
+34 -2
View File
@@ -1,10 +1,12 @@
using System.Numerics; using System.Numerics;
using System.Text;
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using HellionChat.Code; using HellionChat.Code;
using HellionChat.GameFunctions; using HellionChat.GameFunctions;
using HellionChat.Themes; using HellionChat.Themes;
using HellionChat.Ui;
using HellionChat.Ui.StyleEngine; using HellionChat.Ui.StyleEngine;
using HellionChat.Util; using HellionChat.Util;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -31,6 +33,7 @@ internal sealed class InputBar
private readonly TokenResolver _resolver; private readonly TokenResolver _resolver;
private readonly ILogger<InputBar> _logger; private readonly ILogger<InputBar> _logger;
private readonly Action _onOpenSettings; private readonly Action _onOpenSettings;
private readonly Lazy<CommandHelpWindow> _commandHelpWindow;
private string _pendingMessage = string.Empty; private string _pendingMessage = string.Empty;
private bool _isFocused; private bool _isFocused;
@@ -45,7 +48,8 @@ internal sealed class InputBar
ThemeRegistry themes, ThemeRegistry themes,
TokenResolver resolver, TokenResolver resolver,
ILogger<InputBar> logger, ILogger<InputBar> logger,
Action onOpenSettings Action onOpenSettings,
Lazy<CommandHelpWindow> commandHelpWindow
) )
{ {
_symbolPicker = symbolPicker; _symbolPicker = symbolPicker;
@@ -54,6 +58,7 @@ internal sealed class InputBar
_resolver = resolver; _resolver = resolver;
_logger = logger; _logger = logger;
_onOpenSettings = onOpenSettings; _onOpenSettings = onOpenSettings;
_commandHelpWindow = commandHelpWindow;
} }
public string PendingMessage => _pendingMessage; public string PendingMessage => _pendingMessage;
@@ -221,15 +226,42 @@ internal sealed class InputBar
"##hellion-input", "##hellion-input",
ref _pendingMessage, ref _pendingMessage,
BufferCapacity, BufferCapacity,
ImGuiInputTextFlags.EnterReturnsTrue ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CallbackEdit,
SlashCommandCallback
) )
) )
{ {
_commandHelpWindow.Value.IsOpen = false;
TrySend(activeTab); TrySend(activeTab);
} }
_isFocused = ImGui.IsItemFocused(); _isFocused = ImGui.IsItemFocused();
} }
// v1.5.6 character-level slash-detect: fires on every edit so CommandHelpWindow
// stays in sync with what the user is typing without a per-frame poll.
private int SlashCommandCallback(scoped ref ImGuiInputTextCallbackData data)
{
_commandHelpWindow.Value.IsOpen = false;
if (data.BufTextLen == 0)
return 0;
var text = Encoding.UTF8.GetString(data.BufTextSpan);
if (!text.StartsWith('/'))
return 0;
var spaceIdx = text.IndexOf(' ');
var command = spaceIdx > 0 ? text[..spaceIdx] : text;
if (AllCommands.TryGetValue(command, out var textCommand))
_commandHelpWindow.Value.UpdateContent(textCommand.Description);
else if (
Plugin.CommandManager.Commands.TryGetValue(command, out var info) && info.ShowInHelp
)
_commandHelpWindow.Value.UpdateContent(info.HelpMessage);
return 0;
}
private void TrySend(Tab? activeTab) private void TrySend(Tab? activeTab)
{ {
var text = _pendingMessage.Trim(); var text = _pendingMessage.Trim();