fix(input): let the arrow keys walk the sent-message history again

Reported against v1.11.0: type a command, send it, need it again, and
up-arrow does nothing. v1.5.6 recalled it.

Everything needed was already in the tree. InputHistoryService holds the
last thirty entries with move-to-newest dedup, CompactInputHistoryNavigator
owns the cursor maths, and both have their own test mirrors. Neither had
a single caller, and the input field never set CallbackHistory, so ImGui
had no reason to raise the event in the first place.

So: the flag goes on, the callback grows a history branch, and TrySend
pushes the trimmed line before it clears the buffer. Editing ends the
walk, otherwise down-arrow after changing a recalled line would jump to
the next entry and discard the edit.

The cursor is per input bar while the history is global. Which line each
window is looking at is not something the others should inherit.
This commit is contained in:
2026-08-18 22:13:41 +02:00
parent 0279a1a9d6
commit 06ef0bfb1c
+53 -1
View File
@@ -311,6 +311,11 @@ internal sealed class InputBar
} }
} }
// -1 means "not browsing". Per input bar, not shared: the history itself is
// global across the main window and every pop-out, but where each of them is
// in it is not.
private int _historyCursor = -1;
private void DrawInputField(Tab? activeTab) private void DrawInputField(Tab? activeTab)
{ {
if (Activate) if (Activate)
@@ -328,7 +333,8 @@ internal sealed class InputBar
ImGuiInputTextFlags.EnterReturnsTrue ImGuiInputTextFlags.EnterReturnsTrue
| ImGuiInputTextFlags.CallbackEdit | ImGuiInputTextFlags.CallbackEdit
| ImGuiInputTextFlags.CallbackCompletion | ImGuiInputTextFlags.CallbackCompletion
| ImGuiInputTextFlags.CallbackAlways, | ImGuiInputTextFlags.CallbackAlways
| ImGuiInputTextFlags.CallbackHistory,
SlashCommandCallback SlashCommandCallback
) )
) )
@@ -360,6 +366,39 @@ internal sealed class InputBar
return 0; return 0;
} }
// Up and down walk the sent-message history, the way 1.5.6 did. ImGui
// only raises this event when CallbackHistory is set on the field, which
// is why the arrows did nothing at all before: the service and the
// cursor maths were both here and tested, with no caller and no flag.
if (data.EventFlag == ImGuiInputTextFlags.CallbackHistory)
{
var direction =
data.EventKey == ImGuiKey.UpArrow
? CompactInputHistoryNavigator.Direction.Up
: CompactInputHistoryNavigator.Direction.Down;
var (cursor, replacement) = CompactInputHistoryNavigator.Navigate(
direction,
_historyCursor,
_pendingMessage,
() => InputHistoryService.Count,
InputHistoryService.Push,
InputHistoryService.GetByCursor
);
_historyCursor = cursor;
if (replacement is null)
return 0;
// The buffer belongs to ImGui inside a callback; writing the managed
// field here would be overwritten on the way out.
data.DeleteChars(0, data.BufTextLen);
if (replacement.Length > 0)
data.InsertChars(0, replacement);
return 0;
}
if (data.EventFlag == ImGuiInputTextFlags.CallbackCompletion) if (data.EventFlag == ImGuiInputTextFlags.CallbackCompletion)
{ {
// CursorPos is a BYTE offset into the UTF-8 buffer. We decode the // CursorPos is a BYTE offset into the UTF-8 buffer. We decode the
@@ -381,6 +420,13 @@ internal sealed class InputBar
// CallbackEdit (or any remaining event): v1.5.6 character-level slash // CallbackEdit (or any remaining event): v1.5.6 character-level slash
// detection keeps CommandHelpWindow in sync with what the user is // detection keeps CommandHelpWindow in sync with what the user is
// typing without a per-frame poll. // typing without a per-frame poll.
//
// Typing also ends the history walk. Without this, down-arrow after
// editing a recalled line would jump to the next entry and throw the
// edit away.
if (data.EventFlag == ImGuiInputTextFlags.CallbackEdit)
_historyCursor = -1;
_commandHelpWindow.IsOpen = false; _commandHelpWindow.IsOpen = false;
var text = Encoding.UTF8.GetString(data.BufTextSpan); var text = Encoding.UTF8.GetString(data.BufTextSpan);
@@ -455,6 +501,12 @@ internal sealed class InputBar
return; return;
} }
ChatBox.SendMessageUnsafe(bytes); ChatBox.SendMessageUnsafe(bytes);
// Pushed before the buffer is cleared, and the trimmed form is what
// goes in: the history is for recalling what you typed, not the
// whitespace around it.
InputHistoryService.Push(text);
_historyCursor = -1;
_pendingMessage = string.Empty; _pendingMessage = string.Empty;
// 1.5.6 parity (1d3b429:ChatLogWindow.cs:1558): clear the temp channel // 1.5.6 parity (1d3b429:ChatLogWindow.cs:1558): clear the temp channel