Updated .editorconfig to set indent_style=space and indent_size=4 for C# files. Reformat all .cs files to apply the new indentation settings. No code logic changes, just whitespace reformatting. also updated some comments in files in shorter and Precise way. No logic changes, just comment rewording for clarity and conciseness.
This commit is contained in:
@@ -10,14 +10,8 @@ using HellionChat.Util;
|
||||
|
||||
namespace HellionChat;
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs.
|
||||
//
|
||||
// Spawns a session-only tab per /tell partner so a club greeter can track
|
||||
// multiple parallel conversations without losing context. Subscribes to
|
||||
// MessageManager.MessageProcessed for live tells and to ClientState.Logout
|
||||
// for the cleanup pass; everything else hangs off these two entry points.
|
||||
//
|
||||
// See spec: Hellion Chat Auto-Tell-Tabs Spec (Obsidian vault).
|
||||
// Auto-Tell-Tabs: spawns session-only tabs per /tell partner.
|
||||
// Subscribes to MessageManager.MessageProcessed and ClientState.Logout.
|
||||
internal sealed class AutoTellTabsService : IDisposable
|
||||
{
|
||||
private readonly Plugin _plugin;
|
||||
@@ -87,10 +81,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
var partner = ExtractTellPartner(message);
|
||||
if (partner == null)
|
||||
{
|
||||
// Real message without a player payload — e.g. GM tells, which
|
||||
// we deliberately skip. The diagnostics make future regressions
|
||||
// (FFXIV changing tell payload shape, new edge cases) findable
|
||||
// without having to crank up debug logging at the source.
|
||||
// Diagnostics: helps detect regressions (FFXIV payload changes, new edge cases)
|
||||
Plugin.Log.Warning(
|
||||
$"[AutoTellTabs] Could not extract tell partner. type={message.Code.Type}, "
|
||||
+ $"senderChunks={message.Sender.Count}, contentChunks={message.Content.Count}, "
|
||||
@@ -105,9 +96,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
var existing = FindTempTab(partner.Value.Name, partner.Value.World);
|
||||
if (existing != null)
|
||||
{
|
||||
// Tab already exists; Tab.Matches has already routed this
|
||||
// message via the MessageManager pipeline (see Task 2 sender
|
||||
// filter).
|
||||
// Already routed via MessageManager pipeline
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -124,10 +113,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
{
|
||||
if (message.Code.Type == ChatType.TellIncoming)
|
||||
{
|
||||
// Incoming tell: the sender is the conversation partner. The
|
||||
// PlayerPayload normally rides on a chunk's Link slot, but for
|
||||
// some tell types FFXIV only puts it in the raw SeString —
|
||||
// fall back to that before giving up.
|
||||
// Sender is the partner; check chunks first, then raw SeString as fallback
|
||||
var fromSender =
|
||||
ChunkUtil.TryGetPlayerPayload(message.Sender)
|
||||
?? ChunkUtil.TryGetPlayerPayload(message.SenderSource);
|
||||
@@ -138,10 +124,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
return null;
|
||||
}
|
||||
|
||||
// Outgoing tell: the local player is the sender, the partner shows
|
||||
// up either as a payload in the content (for tells typed via the
|
||||
// Chat 2 input bar) or as the channel's tracked tell target (set by
|
||||
// the SetContextTellTarget game hook). Same SeString fallback.
|
||||
// Outgoing tell: check content first, then channels's TellTarget as fallback
|
||||
var fromContent =
|
||||
ChunkUtil.TryGetPlayerPayload(message.Content)
|
||||
?? ChunkUtil.TryGetPlayerPayload(message.ContentSource)
|
||||
@@ -175,10 +158,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
|
||||
private void DropOldestTempTab()
|
||||
{
|
||||
// Greeted tabs are dropped before un-greeted ones (the user said
|
||||
// "I'm done with that conversation"), and within each bucket we
|
||||
// pick the oldest LastActivity. This protects active conversations
|
||||
// and unfinished greetings while still freeing up a slot.
|
||||
// Prioritize greeted tabs for drop; within each bucket, drop by oldest LastActivity
|
||||
var victim = Plugin
|
||||
.Config.Tabs.Select((tab, idx) => (Tab: tab, Index: idx))
|
||||
.Where(t => t.Tab.IsTempTab)
|
||||
@@ -191,12 +171,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
// v0.6.1 — if the victim is currently popped out, tear down the
|
||||
// matching Popout window first. Otherwise the window stays in
|
||||
// PopOutWindows + WindowSystem and renders empty / re-spawns on the
|
||||
// next AddPopOutsToDraw tick. Latent since pop-outs were introduced;
|
||||
// becomes visible with AutoTellTabsOpenAsPopout where dropping a
|
||||
// popped tab is now a routine code path.
|
||||
// Clean up pop-out window if tab is popped out
|
||||
if (victim.Tab.PopOut)
|
||||
{
|
||||
var popout = _plugin.ChatLogWindow.ActivePopouts.FirstOrDefault(p =>
|
||||
@@ -210,8 +185,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
|
||||
Plugin.Config.Tabs.RemoveAt(victim.Index);
|
||||
|
||||
// Re-anchor the active tab so the user does not silently end up on
|
||||
// a different conversation when their tab gets dropped or shifted.
|
||||
// Re-anchor active tab to avoid silent switch when tab is dropped
|
||||
if (victim.Index <= _plugin.LastTab)
|
||||
{
|
||||
_plugin.WantedTab = 0;
|
||||
@@ -222,22 +196,12 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
{
|
||||
var tab = BuildTempTab(partner.Name, partner.World);
|
||||
|
||||
// Preload first so the tab opens with chronological history above
|
||||
// the current message — and so a slow DB query never causes a
|
||||
// visible "empty tab, then history pops in" effect on screen.
|
||||
// The current message is already persisted in the store by the
|
||||
// time MessageProcessed fires (see MessageManager.cs: UpsertMessage
|
||||
// runs before the event), so we have to exclude it explicitly to
|
||||
// avoid the separator landing below the live tell.
|
||||
// Preload history: chronological order with current message already persisted
|
||||
PreloadHistory(tab, partner.Name, partner.World, currentMessage.Id);
|
||||
|
||||
tab.AddMessage(currentMessage, unread: true);
|
||||
|
||||
// Hellion Chat v0.6.1 — opt-in: open new /tell tabs directly as a
|
||||
// pop-out window. Set BEFORE Tabs.Add so the next render-tick's
|
||||
// AddPopOutsToDraw() sees PopOut=true and spawns the Popout window
|
||||
// alongside the tab going into the list. No SaveConfig() because
|
||||
// auto-tell tabs are IsTempTab (session-only, never persisted).
|
||||
// Open as pop-out if configured (set before Tabs.Add for next render-tick)
|
||||
if (Plugin.Config.AutoTellTabsOpenAsPopout)
|
||||
{
|
||||
tab.PopOut = true;
|
||||
@@ -272,9 +236,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
{
|
||||
return $"{playerName}@{worldRow.Name}";
|
||||
}
|
||||
// World sheet lookup miss is rare (only for FFXIV worlds Dalamud has
|
||||
// not yet seen). Fall back to the raw RowId so the user still has a
|
||||
// unique, readable label.
|
||||
// Fallback if world lookup misses (rare; only for unseen worlds)
|
||||
return $"{playerName}@World{worldRowId}";
|
||||
}
|
||||
|
||||
@@ -288,9 +250,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
|
||||
try
|
||||
{
|
||||
// Pull one extra row because the live tell that triggered this
|
||||
// spawn is already in the store and would otherwise eat one of
|
||||
// the user's preload-budget slots.
|
||||
// Pull one extra row: current message is already in store and would eat a preload slot
|
||||
var history = _store.GetTellHistoryWithSender(
|
||||
_messageManager.CurrentContentId,
|
||||
senderName,
|
||||
@@ -305,23 +265,17 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
|
||||
if (historicMessages.Count == 0)
|
||||
{
|
||||
// No prior tells with this player — leave the tab to start
|
||||
// empty so the user does not see a "history loaded" marker
|
||||
// sitting alone above the very first message.
|
||||
// No prior tells; leave tab empty to avoid orphaned "history loaded" marker
|
||||
return;
|
||||
}
|
||||
|
||||
// The history list is already oldest-first, so a plain AddPrune
|
||||
// loop produces the chronological order the user expects to see
|
||||
// when the tab opens.
|
||||
// History is oldest-first; add in order for chronological display
|
||||
foreach (var message in historicMessages)
|
||||
{
|
||||
tab.Messages.AddPrune(message, MessageManager.MessageDisplayLimit);
|
||||
}
|
||||
|
||||
// Visible separator between the loaded history and the live
|
||||
// tell that triggered this spawn. Goes in last so it sorts
|
||||
// after the historical messages but before the current one.
|
||||
// Separator between history and live tell (sorts after history but before current)
|
||||
tab.Messages.AddPrune(
|
||||
MakeSystemMarker(HellionStrings.AutoTellTabs_HistorySeparator),
|
||||
MessageManager.MessageDisplayLimit
|
||||
@@ -329,9 +283,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Non-fatal: the tab still spawns, but the user gets a visible
|
||||
// notice instead of silently missing history. The error logs
|
||||
// once with full stack trace for diagnosis.
|
||||
// Non-fatal: tab still spawns with visible error notice instead of silent history loss
|
||||
Plugin.Log.Error(ex, "[AutoTellTabs] History preload failed");
|
||||
tab.Messages.AddPrune(
|
||||
MakeSystemMarker(HellionStrings.AutoTellTabs_HistoryLoadError),
|
||||
@@ -372,9 +324,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
|
||||
lock (_tempTabsLock)
|
||||
{
|
||||
// Frame-race guard (E5): the sidebar might still render a tab
|
||||
// that has already been removed by LRU drop or logout cleanup.
|
||||
// Silently skip the toggle so we don't mutate stale state.
|
||||
// Guard against frame-race: sidebar might render a tab already removed by LRU or logout
|
||||
if (!Plugin.Config.Tabs.Contains(tab))
|
||||
{
|
||||
return;
|
||||
@@ -388,18 +338,12 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
{
|
||||
lock (_tempTabsLock)
|
||||
{
|
||||
// Snapshot whether the active tab is about to be removed, BEFORE
|
||||
// we mutate the list — index lookups would lie to us afterwards.
|
||||
// Snapshot active tab index before mutating list
|
||||
var lastIndex = _plugin.LastTab;
|
||||
var lastIndexValid = lastIndex >= 0 && lastIndex < Plugin.Config.Tabs.Count;
|
||||
var currentWasTempTab = lastIndexValid && Plugin.Config.Tabs[lastIndex].IsTempTab;
|
||||
|
||||
// v0.6.1 — symmetric to DropOldestTempTab cleanup: tear down any
|
||||
// popped-out temp tab windows before removing the tabs themselves,
|
||||
// otherwise PopOutWindows + WindowSystem keep ghost entries until
|
||||
// the next plugin reload. Especially relevant once Auto-Pop-Out is
|
||||
// enabled — every logout would otherwise leak as many ghosts as
|
||||
// there were active /tell pop-outs.
|
||||
// Clean up pop-out windows before removing temp tabs
|
||||
var poppedTempTabIds = Plugin
|
||||
.Config.Tabs.Where(t => t.IsTempTab && t.PopOut)
|
||||
.Select(t => t.Identifier)
|
||||
@@ -419,9 +363,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
|
||||
Plugin.Config.Tabs.RemoveAll(t => t.IsTempTab);
|
||||
|
||||
// Force a switch to tab 0 if the active tab was a temp tab OR
|
||||
// if drops before the active index pushed LastTab out of range.
|
||||
// Otherwise the user keeps their current persistent tab.
|
||||
// Force switch to tab 0 if active tab was temp or index is now out of range
|
||||
var stillValid = lastIndex >= 0 && lastIndex < Plugin.Config.Tabs.Count;
|
||||
if (currentWasTempTab || !stillValid)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user