feat(ipc): wire TypingIpc state from InputBar API
This commit is contained in:
@@ -34,11 +34,13 @@ internal sealed class TypingIpc : IDisposable
|
|||||||
private ChatInputState LastState;
|
private ChatInputState LastState;
|
||||||
private bool HasState;
|
private bool HasState;
|
||||||
|
|
||||||
|
private readonly Ui.Components.InputBar _inputBar;
|
||||||
private readonly ILogger<TypingIpc> _logger;
|
private readonly ILogger<TypingIpc> _logger;
|
||||||
|
|
||||||
internal TypingIpc(Plugin plugin, ILogger<TypingIpc> logger)
|
internal TypingIpc(Plugin plugin, Ui.Components.InputBar inputBar, ILogger<TypingIpc> logger)
|
||||||
{
|
{
|
||||||
Plugin = plugin;
|
Plugin = plugin;
|
||||||
|
_inputBar = inputBar;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
StateQueryGate = Plugin.Interface.GetIpcProvider<ChatInputState>(
|
StateQueryGate = Plugin.Interface.GetIpcProvider<ChatInputState>(
|
||||||
@@ -62,26 +64,51 @@ internal sealed class TypingIpc : IDisposable
|
|||||||
|
|
||||||
private ChatInputState BuildState()
|
private ChatInputState BuildState()
|
||||||
{
|
{
|
||||||
// Input visibility and focus come back when the new chat layer
|
|
||||||
// exposes the matching state. The channel type still resolves
|
|
||||||
// from the active tab so IPC consumers can read it today.
|
|
||||||
var usedChannel = Plugin.CurrentTab.CurrentChannel;
|
var usedChannel = Plugin.CurrentTab.CurrentChannel;
|
||||||
var inputChannel = usedChannel.UseTempChannel
|
var inputChannel = usedChannel.UseTempChannel
|
||||||
? usedChannel.TempChannel
|
? usedChannel.TempChannel
|
||||||
: usedChannel.Channel;
|
: usedChannel.Channel;
|
||||||
var channelType = inputChannel.ToChatType();
|
var channelType = inputChannel.ToChatType();
|
||||||
|
|
||||||
|
// `Plugin` here is the instance property on TypingIpc (TypingIpc.cs:18
|
||||||
|
// `private Plugin Plugin { get; }`), NOT the type `HellionChat.Plugin`.
|
||||||
|
// C# resolves `Plugin.MainWindow` as `this.Plugin.MainWindow` because
|
||||||
|
// the instance property shadows the type name inside this class. Do
|
||||||
|
// NOT switch to a type-qualified read (CS0120 — `MainWindow` is an
|
||||||
|
// instance member, not a static one). MainWindow is resolved in Phase-1
|
||||||
|
// (Plugin.cs:295) and never re-assigned, so `this.Plugin.MainWindow`
|
||||||
|
// is non-null by the time TypingIpc.Update() runs (HostedServices only
|
||||||
|
// start after Phase-1). The `null!`-suppression on the MainWindow
|
||||||
|
// property would let us drop the `?.`, but a null-safe read costs
|
||||||
|
// nothing at runtime and shields against a theoretical pre-Phase-1
|
||||||
|
// caller (a future IPC-pull that fires before HostedServices start).
|
||||||
|
// Defense in depth, no behaviour change for the production path.
|
||||||
|
var mainWindowOpen = this.Plugin.MainWindow?.IsOpen ?? false;
|
||||||
|
|
||||||
|
// Stale-state guard: InputBar._isFocused is only written in DrawInputField,
|
||||||
|
// which only runs while MainWindow is open. After the user closes the
|
||||||
|
// window, _isFocused freezes on the last value. _pendingMessage has the
|
||||||
|
// same stale problem — it is only cleared in TrySend (successful send)
|
||||||
|
// or ClearBuffer (manual reset), so closing MainWindow with non-empty
|
||||||
|
// buffer leaves PendingLength frozen above zero. Without gating all
|
||||||
|
// four state fields on mainWindowOpen, Cross-Plugin-Subscribers would
|
||||||
|
// keep seeing InputFocused: true / HasText: true / IsTyping: true /
|
||||||
|
// TextLength: N even though no input field exists.
|
||||||
|
var inputFocused = mainWindowOpen && _inputBar.IsFocused;
|
||||||
|
var hasText = mainWindowOpen && _inputBar.PendingLength > 0;
|
||||||
|
var textLength = mainWindowOpen ? _inputBar.PendingLength : 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
InputVisible: false,
|
InputVisible: mainWindowOpen,
|
||||||
InputFocused: false,
|
InputFocused: inputFocused,
|
||||||
HasText: false,
|
HasText: hasText,
|
||||||
IsTyping: false,
|
IsTyping: hasText,
|
||||||
TextLength: 0,
|
TextLength: textLength,
|
||||||
ChannelType: channelType
|
ChannelType: channelType
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ChatInputState GetState() => BuildState();
|
internal ChatInputState GetState() => BuildState();
|
||||||
|
|
||||||
internal void Update()
|
internal void Update()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ internal static class PluginHostFactory
|
|||||||
));
|
));
|
||||||
services.AddSingleton(sp => new TypingIpc(
|
services.AddSingleton(sp => new TypingIpc(
|
||||||
sp.GetRequiredService<Plugin>(),
|
sp.GetRequiredService<Plugin>(),
|
||||||
|
sp.GetRequiredService<Ui.Components.InputBar>(),
|
||||||
sp.GetRequiredService<ILogger<TypingIpc>>()
|
sp.GetRequiredService<ILogger<TypingIpc>>()
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user