chore: comments say what the code does, not which task produced it

A comment that reads "MUST stay in lockstep with TryGetActiveCrossfade (K8)"
helps nobody outside the plan that used to have a K8 in it, and the plans are
not in this repo. Same for "Spec FR-4", "plan §B.2", "Sub-Task 4.4" and the
F/R/M/A/S round codes scattered through the style engine and the self-tests.

Personal names go too. "tester feedback from Jin (v1.4.7)" and "Flo decision
2026-06-15" carry the reason fine without naming anyone -- the version and the
reason are the parts a reader can act on, and a public repo should not need a
cast list to be read.

The rule applied throughout: keep the why, drop the reference. Version numbers
stay, since those resolve through the changelog. 77 files.

ChunkUtil also carried 281 lines of commented-out code -- an older ToChunks
variant and two helpers with no callers, inherited and never removed. Deleted;
git remembers them.
This commit is contained in:
2026-08-19 21:50:31 +02:00
parent 52237fda7e
commit 5b738e6885
48 changed files with 113 additions and 397 deletions
+2 -2
View File
@@ -58,8 +58,8 @@ internal sealed class AutoTellTabsService : IDisposable
// Derived from the tab list on read. Pin/Unpin/Promote/Logout simply
// mutate IsPinned or remove tabs — the count adapts automatically.
// Replaces the F2.1 Interlocked counter because the new pin-state
// transitions are cold-path and don't need lock-free reads.
// Replaces an Interlocked counter: the pin-state transitions are cold-path
// and don't need lock-free reads.
internal int ActiveTempTabCount =>
Plugin.Config.Tabs.Count(TabLifecycleHelpers.IsInUnpinnedPool);
+5 -5
View File
@@ -77,7 +77,7 @@ public class Configuration : IPluginConfiguration
.PrivacyDefaults
.DefaultPersistUnknownChannels;
// F3.2: dedup unknown-ChatType warnings so a chatty filter doesn't spam
// Dedup unknown-ChatType warnings so a chatty filter doesn't spam
// the log every frame. NonSerialized so the warning fires once per
// runtime, not once-ever-per-install.
[NonSerialized]
@@ -107,7 +107,7 @@ public class Configuration : IPluginConfiguration
var known = Enum.IsDefined(typeof(ChatType), type);
// F3.2: log first occurrence of a ChatType the running build doesn't
// Log the first occurrence of a ChatType the running build doesn't
// recognise — i.e. one a future FFXIV patch may have added.
if (!known && !listed && _warnedUnknownChannels.Add(type))
{
@@ -402,9 +402,9 @@ public class Tab
public bool IsTempTab;
// Pinned TempTabs survive plugin reload and logout — tester feedback from
// Jin (v1.4.7). Pinned tabs live in their own pool (MaxPinnedTempTabs)
// separate from the AutoTellTabsLimit bucket.
// Pinned TempTabs survive plugin reload and logout -- tester feedback in
// v1.4.7. Pinned tabs live in their own pool (MaxPinnedTempTabs) separate
// from the AutoTellTabsLimit bucket.
public bool IsPinned;
public bool AllSenderMessages;
public TellTarget TellTarget = TellTarget.Empty();
+5 -5
View File
@@ -234,7 +234,7 @@ internal sealed unsafe class Chat : IDisposable
// Seed the just-typed character into our input field and focus it, the
// same InputBar.AppendPending + Activate prefill path inventory item-links
// use. Prefill-only — no tab switch (Flo decision 2026-06-15).
// use. Prefill only, deliberately: no tab switch.
if (input != null)
{
Plugin.InputBar.AppendPending(input);
@@ -355,9 +355,9 @@ internal sealed unsafe class Chat : IDisposable
if (playerName != null)
{
// Right-click -> Send Tell: prefill our input the same way our own
// "Send Tell" payload menu does (PayloadHandler), then focus. Prefill-
// only — no tab switch, no ChatActivatedArgs revival (Flo decision
// 2026-06-15). The game supplies worldName here, so no sheet lookup.
// "Send Tell" payload menu does (PayloadHandler), then focus. Prefill
// only, deliberately: no tab switch, no ChatActivatedArgs revival.
// The game supplies worldName here, so no sheet lookup.
PrefillTellInput(
playerName->ToString(),
worldName != null ? worldName->ToString() : null
@@ -393,7 +393,7 @@ internal sealed unsafe class Chat : IDisposable
{
// In-foray right-click -> Send Tell: same prefill path as the non-foray
// tell. The foray-specific TellSpecial channel routing stays deferred
// (v1.8.1, SetEurekaTellChannel) — prefill-only here (Flo decision 2026-06-15).
// (v1.8.1, SetEurekaTellChannel) -- prefill only here as well.
PrefillTellInput(
playerName->ToString(),
worldName != null ? worldName->ToString() : null
+1 -1
View File
@@ -530,7 +530,7 @@ internal unsafe class KeybindManager : IDisposable
{
// Direct channel-switch binds (CMD_SAY/PARTY/numbered linkshells/…): switch
// the game channel AND mirror it onto the resolved tab so the input pill
// shows the real send target (pill-sync, Flo decision 2026-06-15).
// shows the real send target (pill-sync).
Plugin.Instance.Functions.Chat.SetChannel(channel);
// Only mirror onto the tab when the game actually accepted the switch — an
// empty linkshell slot leaves the game channel untouched, so the pill must
@@ -135,7 +135,7 @@ internal sealed class PayloadHandlerInitHostedService(
{
public async Task StartAsync(CancellationToken cancellationToken)
{
// §6.2 cycle-resolution: both singletons exist by the time HostedServices
// Cycle resolution: both singletons exist by the time HostedServices
// run, so this is the first safe point to wire the setter.
messageList.AttachPayloadHandler(payloadHandler);
@@ -172,7 +172,7 @@ internal sealed class PayloadHandlerInitHostedService(
// InputBar -> CommandHelpWindow -> MainWindow -> InputBar (MS.DI does not catch
// it through FactoryCallSite registrations and the resolve recurses silently).
// Both singletons exist by host.StartAsync time, so this is the first safe point
// to wire the setter — same §6.2 pattern as MessageList.AttachPayloadHandler.
// to wire the setter — same setter-injection pattern as MessageList.AttachPayloadHandler.
internal sealed class CommandHelpWindowInitHostedService(
CommandHelpWindow commandHelpWindow,
MainWindow mainWindow
@@ -190,7 +190,7 @@ internal sealed class CommandHelpWindowInitHostedService(
// Attaches the singleton PayloadHandler to every pre-allocated pop-out
// window's MessageList post-container-build. Pool/window cannot take the
// PayloadHandler via ctor (that would close the silent FactoryCallSite cycle —
// same §6.2 reason as MessageList.AttachPayloadHandler / CommandHelpWindow.
// same setter-injection reason as MessageList.AttachPayloadHandler / CommandHelpWindow.
// AttachMainWindow). Both singletons exist by host.StartAsync time.
internal sealed class ChannelPopoutInitHostedService(
ChannelPopoutPool pool,
+3 -3
View File
@@ -20,7 +20,7 @@ internal sealed class TypingIpc : IDisposable
private ICallGateProvider<ChatInputState> StateQueryGate { get; }
private ICallGateProvider<ChatInputState, object?> StateChangedGate { get; }
// v1.4.9 R4: ChatTwo IPC compatibility mirror. Some third-party plugins
// v1.4.9: ChatTwo IPC compatibility mirror. Some third-party plugins
// have a no-fork policy and subscribe only to ChatTwo.*-prefixed IPC
// gates. HellionChat replaces ChatTwo (conflict detection prevents
// parallel loading), so mirroring the ChatTwo provider slots lets those
@@ -50,7 +50,7 @@ internal sealed class TypingIpc : IDisposable
"HellionChat.ChatInputStateChanged"
);
// v1.4.9 R4: ChatTwo-prefixed compatibility slots (see class-level comment).
// v1.4.9: ChatTwo-prefixed compatibility slots (see class-level comment).
ChatTwoStateQueryGate = Plugin.Interface.GetIpcProvider<ChatInputState>(
"ChatTwo.GetChatInputState"
);
@@ -102,7 +102,7 @@ internal sealed class TypingIpc : IDisposable
HasState = true;
LastState = state;
StateChangedGate.SendMessage(state);
// v1.4.9 R4: mirror on ChatTwo-prefixed slot for no-fork-policy plugins.
// v1.4.9: mirror on ChatTwo-prefixed slot for no-fork-policy plugins.
ChatTwoStateChangedGate.SendMessage(state);
}
+3 -3
View File
@@ -22,7 +22,7 @@ internal sealed class IpcManager : IDisposable
object?
> InvokeGate { get; }
// v1.4.9 R4: ChatTwo IPC compatibility mirror. Third-party plugins with
// v1.4.9: ChatTwo IPC compatibility mirror. Third-party plugins with
// a no-fork policy (e.g. Artisan, AllaganTools) only subscribe to the
// ChatTwo.*-prefixed context-menu integration gates. Mirroring all four
// provider slots under the ChatTwo namespace lets those plugins keep
@@ -65,7 +65,7 @@ internal sealed class IpcManager : IDisposable
object?
>("HellionChat.Invoke");
// v1.4.9 R4: ChatTwo-prefixed mirrors of the four context-menu slots
// v1.4.9: ChatTwo-prefixed mirrors of the four context-menu slots
// above. Share the same Register/Unregister backing methods so a
// plugin that subscribes via either namespace lands in the same
// Registered list. SendMessage on Invoke fans out to both gates.
@@ -103,7 +103,7 @@ internal sealed class IpcManager : IDisposable
)
{
InvokeGate.SendMessage(id, sender, contentId, payload, senderString, content);
// v1.4.9 R4: fan out the same event to plugins listening on ChatTwo.Invoke.
// v1.4.9: fan out the same event to plugins listening on ChatTwo.Invoke.
ChatTwoInvokeGate.SendMessage(id, sender, contentId, payload, senderString, content);
}
+6 -7
View File
@@ -248,9 +248,8 @@ internal class MessageManager : IAsyncDisposable
_logger.LogError(ex, "Error in FilterAllTabs");
}
// v1.4.9 R3 profiling: Information so the xllog tail surfaces this
// without a Debug filter. Belt-and-suspenders for future plugin-load
// regressions; remains in place after Sub-Task 3.4 Befund.
// Information, not Debug, so the xllog tail surfaces this without a
// filter. Kept as a guard against future plugin-load regressions.
_logger.LogInformation($"FilterAllTabs took {stopwatch.ElapsedMilliseconds}ms");
});
}
@@ -436,10 +435,10 @@ internal class MessageManager : IAsyncDisposable
// TEST-MIRROR: ../_Helpers/TabSoundDecision.cs
// Unseen ("count only what you haven't seen") suppresses unread on an inactive
// tab when the active tab ALSO shows this message — you already saw it in the
// tab you're looking at (1.5.6 / upstream ChatTwo behavior). Pre-F2 the "active
// tab" was wrongly pinned to Tabs[0], so this fired against the wrong tab; F2
// recoupled CurrentTab to the REAL active tab, so currentTabMatches is now
// measured against the tab you actually see. All -> always counts; None ->
// tab you're looking at (1.5.6 / upstream ChatTwo behavior). The "active tab"
// used to be pinned to Tabs[0], so this fired against the wrong one until
// CurrentTab was recoupled to the real active tab, and currentTabMatches is
// now measured against the tab you see. All -> always counts; None ->
// counts here and is gated out at the display layer. Pure + SelfTest-able.
internal static bool ShouldCountUnread(Tab tab, Tab currentTab, bool currentTabMatches) =>
!(
+3 -4
View File
@@ -245,7 +245,7 @@ internal class MessageStore : IDisposable
private SqliteConnection Connect()
{
// v1.4.9 R3 profiling: trace cost of SQLite open + pragma-apply. Paired
// v1.4.9 profiling: trace cost of SQLite open + pragma-apply. Paired
// with the Migrate-Stopwatch below — Connect alone is the cheap half
// (Open + a handful of PRAGMAs); the expensive half typically lives in
// Migrate, especially on a large DB after a schema bump.
@@ -260,7 +260,7 @@ internal class MessageStore : IDisposable
private void Migrate()
{
// v1.4.9 R3 profiling: trace cost of the schema-migration chain. On a
// v1.4.9 profiling: trace cost of the schema-migration chain. On a
// large DB after a fresh schema bump this is the dominant SQLite cost
// at plugin-load, not Connect.
var migrateSw = System.Diagnostics.Stopwatch.StartNew();
@@ -939,8 +939,7 @@ internal class MessageStore : IDisposable
// storage form on both sides so the IN(...) compare matches. SQLite has a
// hard parameter limit of 999 in default builds, so we chunk the input --
// a 1000-hit FTS query never explodes the SELECT. Result ordering is not
// guaranteed; callers re-sort (e.g. DbViewer sorts by Date descending in
// Sub-Task 4.4).
// guaranteed; callers re-sort (DbViewer sorts by Date descending).
public IReadOnlyList<Message> LoadByGuids(IReadOnlyList<string> guidStrings)
{
if (guidStrings.Count == 0)
+3 -3
View File
@@ -263,7 +263,7 @@ internal sealed class PayloadHandler
// Eureka, Bozja and Occult need special handling as tells work different
if (!Sheets.IsInForay())
{
// §6.9: single SetPendingMessage call; v1.5.6 used incremental Chat += writes.
// Single SetPendingMessage call; v1.5.6 used incremental Chat += writes.
// XC-8: shares the /tell builder with the native detours. IsPublic (not
// IsNullOrEmpty) is resolved HERE — a private/null world must NOT leak @World.
_inputBar.SetPendingMessage(
@@ -393,7 +393,7 @@ internal sealed class PayloadHandler
var inputChannel = chunk.Message?.Code.Type.ToInputChannel();
if (inputChannel != null && ImGui.Selectable(Language.Context_ReplyInSelectedChatMode))
{
// §6.3: route channel-switch through MainWindow's active tab
// Route the channel switch through MainWindow's active tab
_mainWindow.ActiveTab?.CurrentChannel?.SetChannel(inputChannel.Value);
_inputBar.Activate = true;
}
@@ -731,7 +731,7 @@ internal sealed class PayloadHandler
using (ImRaii.Tooltip())
using (ImRaii.TextWrapPos(0.0f))
using (
// §4.2: use active theme text colour instead of the former LogWindow.DefaultText static.
// Use the active theme text colour instead of the former LogWindow.DefaultText static.
ImRaii.PushColor(
ImGuiCol.Text,
ColourUtil.RgbaToVector4(_themes.Active.Colors.TextPrimary)
+7 -7
View File
@@ -15,7 +15,7 @@ namespace HellionChat;
// Builds the generic-host DI container that drives v1.5.0+. The factory is
// invoked synchronously from Plugin.ctor (after the schema gate clears) so the
// container exists before PluginLifecycle.LoadAsync runs. See plan §1 for the
// container exists before PluginLifecycle.LoadAsync runs. For the
// deliberate divergence from Lightless' deferred Func-delegate pattern.
internal static class PluginHostFactory
{
@@ -48,7 +48,7 @@ internal static class PluginHostFactory
PluginHostDependencies dependencies
)
{
// Block A — Dalamud services (21 [PluginService] singletons).
// Dalamud services (21 [PluginService] singletons).
services.AddSingleton(dependencies);
services.AddSingleton(dependencies.PluginInterface);
services.AddSingleton(dependencies.PluginLog);
@@ -77,7 +77,7 @@ internal static class PluginHostFactory
services.AddSingleton(plugin.WindowSystem);
services.AddSingleton<PluginLifecycle>();
// Block B — HellionChat singletons. Factory lambdas because most
// HellionChat singletons. Factory lambdas because most
// classes are internal-sealed and the default activator only sees
// public ctors.
services.AddSingleton<IPlatformUtil>(_ => new DalamudPlatformUtil());
@@ -307,7 +307,7 @@ internal static class PluginHostFactory
// Pop-out windows: each gets its OWN MessageList + InputBar so the
// channel pill and message scroll are per-window. The PayloadHandler is
// attached post-build (ChannelPopoutInitHostedService), NEVER via ctor
// (plan §B.2 — would close a silent FactoryCallSite cycle).
// (would close a silent FactoryCallSite cycle).
services.AddSingleton<Func<int, Ui.Windows.ChannelPopoutWindow>>(sp =>
slot => new Ui.Windows.ChannelPopoutWindow(
slot,
@@ -336,7 +336,7 @@ internal static class PluginHostFactory
sp.GetRequiredService<ILogger<Ui.Windows.ChannelPopoutPool>>()
));
// Block C — Windows. WindowSystem.AddWindow is called from
// Windows. WindowSystem.AddWindow is called from
// PluginLifecycle.LoadAsync on the framework thread.
services.AddSingleton(sp => new Ui.Windows.SettingsWindow(
sp.GetRequiredService<Plugin>(),
@@ -380,8 +380,8 @@ internal static class PluginHostFactory
));
#endif
// The style lab: variants side by side, in-game, against the live theme.
// Permanent by Flo's call, and deliberately not behind DEBUG -- style
// decisions happen in the build he actually runs.
// Permanent, and deliberately not behind DEBUG: style decisions get
// made in the build that actually ships.
services.AddSingleton(sp => new Ui.Windows.InputBarLabWindow(
sp.GetRequiredService<Plugin>()
));
+1 -1
View File
@@ -4,7 +4,7 @@ namespace HellionChat.Privacy;
internal static class PrivacyDefaults
{
// F3.1: failsafe for ChatTypes added by future FFXIV patches. New installs
// Failsafe for ChatTypes added by future FFXIV patches. New installs
// persist unknown channels so a major patch's added ChatType isn't silently
// dropped before the user can opt in or out. Existing configs keep their
// explicit choice — see Configuration.cs PrivacyPersistUnknownChannels.
+1 -1
View File
@@ -56,7 +56,7 @@ internal sealed class CardClipPlanStep : ISelfTestStep
int remaining;
try
{
// v1.10.0/A1: the fingerprint gate waits for the value to settle, so
// v1.10.0: the fingerprint gate waits for the value to settle, so
// the step walks a synthetic clock past the window instead of sleeping.
var clock = Environment.TickCount64;
messages.RunHeightCacheInvalidationForSelfTest(tab, 400f, clock);
@@ -3,7 +3,7 @@ using Dalamud.Plugin.SelfTest;
namespace HellionChat.SelfTests;
// F2: CurrentTab is coupled to MainWindow.ActiveTab (no longer the fixed index-0
// CurrentTab is coupled to MainWindow.ActiveTab (no longer the fixed index-0
// Tabs lookup). Asserts ReferenceEquals between the two, with false-green
// defenses: (1) empty-config exercises the getter's fallback; (2) null ActiveTab
// opens the window so the Draw-seed sets it and retries via Waiting (bounded so a
@@ -5,12 +5,12 @@ using HellionChat.GameFunctions.Types;
namespace HellionChat.SelfTests;
// F2 (guided): interactive, fires NO synthetic probes. Shows the full measured
// Guided: interactive, fires NO synthetic probes. Shows the full measured
// state every frame so a result is observable, not a guess, and walks the user
// through the real switch-away-and-back flow. It verifies the PRIVACY-relevant
// effect, keyed on the tab type:
// - a NORMAL tab carrying a game-side tell must lose its RUNTIME target
// (CurrentChannel.TellTarget) on switch-away-and-back (the F1 strip), so a
// (CurrentChannel.TellTarget) on switch-away-and-back, so a
// typed line can't /tell the old partner;
// - a BOUND auto-tell tab keeps its partner by design (leg1) — its binding is
// Tab.TellTarget and is deliberately untouched by the strip.
+1 -1
View File
@@ -7,7 +7,7 @@ using HellionChat.Util;
namespace HellionChat.SelfTests;
// v1.12.0/A2: the exporter now reads text from the chunk lists instead of the
// v1.12.0: the exporter now reads text from the chunk lists instead of the
// raw SeStrings. That change is invisible to the build suite -- ExportToFile
// takes IEnumerable<Message>, Message needs SeString, and xUnit cannot load
// Dalamud.dll, so even an empty list fails before the body runs.
@@ -127,7 +127,7 @@ internal sealed class FontManagerCtorSmokeStep : ISelfTestStep
return SelfTestStepResult.Fail;
}
// Report what was actually verified (Flo's request: don't just show Pass).
// Report what was actually verified rather than a bare Pass.
// The glyph-range entry counts make the B1 dedup visible — the cjk-fallback
// range is now a small trimmed remainder next to the large primary range.
var counts = fm.GlyphRangeLengths;
@@ -43,7 +43,7 @@ internal sealed class GlobalStyleScopeAllocStep : ISelfTestStep
GlobalStyleScope.Push(theme, registry, opacity).Dispose();
var delta = GC.GetAllocatedBytesForCurrentThread() - before;
// Report the measured figure on BOTH outcomes (Flo's request: don't just
// Report the measured figure on BOTH outcomes (a bare Pass hides
// show Pass) — the byte delta is the whole point of the GC-reserve probe.
var ok = delta <= AllocBudgetBytes;
var status = ok ? "PASS" : "FAIL";
@@ -68,7 +68,7 @@ internal sealed class HonorificHeaderRenderStep : ISelfTestStep
);
// Draw at a deliberately wide 420px so the title never hits the truncation
// clamp — LastTitleRendered then reflects the GATE outcome, not the width.
// clamp -- LastTitleRendered then reflects the gate outcome, not the width.
try
{
// (a) available + valid title + toggle on -> title renders
@@ -4,7 +4,7 @@ using HellionChat.Ui.StyleEngine;
namespace HellionChat.SelfTests;
// Master-spec §7.5 scope note: the hover registry must not grow frame by frame.
// the hover registry must not grow frame by frame.
// Successor to HoverSheenAllocStep, which pinned the same contract against the
// old sheen start-timestamp dictionary.
//
+2 -2
View File
@@ -7,8 +7,8 @@ namespace HellionChat.SelfTests;
// B1-2 window flags. Drives the REAL MainWindow.PreDraw and asserts it wired
// Window.Flags to ResolveFlags(CanMove, CanResize), then pins the pure
// fresh-base contract: false/false adds NoMove|NoResize, true/true clears them
// (the masterplan's "flags must rebuild from a fresh base, else NoMove sticks
// after toggling back" risk). NoScrollbar|NoScrollWithMouse always present.
// -- flags must rebuild from a fresh base, or NoMove sticks after toggling
// back. NoScrollbar|NoScrollWithMouse always present.
// Non-test caller of ResolveFlags: MainWindow.PreDraw.
internal sealed class MainWindowFlagsStep : ISelfTestStep
{
@@ -7,7 +7,7 @@ namespace HellionChat.SelfTests;
// step so the per-frame hot path never references file IO. Writes one
// perf-baseline.json into the plugin ConfigDirectory, atomically (tmp + move)
// like ThemeRegistry's theme writer, so a mid-write crash leaves either the
// old file or the new file, never a half JSON. Field names track §7.5:
// old file or the new file, never a half JSON. Field names track the performance-baseline layout:
// steady-state Draw cost (avg/max ms), the quad-proxy draw-call count
// (avg/max), and frame delta (avg/max). First-frame-HITCH is read off
// drawMs max/avg by the human author, platform-annotated in the notes.
@@ -6,14 +6,14 @@ namespace HellionChat.SelfTests;
// Optional metric capture. Accumulates 1000 steady-state frames of ImGui IO
// counters plus the plugin's full-Draw wall-time (Plugin.LastDrawMs, B5-1),
// then writes a single perf-baseline.json into the plugin ConfigDirectory so
// the cycle-notes author can copy the §7.5 figures without a separate
// the cycle-notes author can copy the baseline figures without a separate
// profiling harness. The step only records — it never fails on a threshold
// (the budgets are evaluated by a human against the JSON, §7.5 "optional,
// (the budgets are evaluated by a human against the JSON ("optional,
// manual"). It returns Waiting until the sample window fills, mirroring the
// per-frame poll idiom of ThemeSwitchSelfTestStep.
internal sealed class PerformanceBaselineStep : ISelfTestStep
{
// §7.5 steady-state window. 1000 frames ≈ 16s at 60fps, long enough to
// Steady-state window. 1000 frames ≈ 16s at 60fps, long enough to
// average out GC blips without making the manual step tedious.
private const int TargetFrames = 1000;
@@ -4,7 +4,7 @@ using HellionChat.Code;
namespace HellionChat.SelfTests;
// F3: the unread dot the v1.8.x sidebar rebuild dropped. Drives the REAL
// The unread dot the v1.8.x sidebar rebuild dropped. Drives the REAL
// Sidebar.Draw (render precedent: SidebarGreetedGlyphStep) with a probe tab that
// is inactive and carries Unread>0, then reads the render-observability counter
// so a regressed/absent dot fails. Asserts: dot drawn for an inactive Unseen tab;
@@ -7,7 +7,7 @@ using HellionChat.Util;
namespace HellionChat.SelfTests;
// F1: the activation strip. Drives the REAL OnTabActivated — the entry the
// The activation strip. Drives the REAL OnTabActivated -- the entry the
// Sidebar/TopTabBar click handlers, the pop-out path and the Draw-seed all call
// — with local probe tabs (Plugin.Config.Tabs is never touched). Asserts the
// five contracts: strip-on-switch, no-strip-on-reclick (TR-4), leg1 preserve,
+1 -1
View File
@@ -7,7 +7,7 @@ using HellionChat.Ui.StyleEngine;
namespace HellionChat.SelfTests;
// v1.13.0/A7: the type scale has no call site in the message list until block C,
// v1.13.0: the type scale has no call site in the message list until block C,
// so without this step block A would end with nothing to look at and two helpers
// (TypeScale, BaselineMath) with no caller at all.
//
+7 -6
View File
@@ -3,12 +3,12 @@ using Dalamud.Plugin.SelfTest;
namespace HellionChat.SelfTests;
// F3: the unread decision (MessageManager.ShouldCountUnread). Unseen suppresses
// The unread decision (MessageManager.ShouldCountUnread). Unseen suppresses
// unread on an inactive tab only when the active tab ALSO shows the message (you
// saw it there) — 1.5.6/upstream semantics, now measured against the REAL active
// tab thanks to F2. Asserts the truth table: suppressed when active tab also
// matches; counts when it does not (the Carla/Jin case); All always counts; None
// counts at the increment layer (the display gate hides it).
// saw it there) -- 1.5.6/upstream semantics, measured against the real active
// tab. Asserts the truth table: suppressed when the active tab also matches;
// counts when it does not; All always counts; None counts at the increment
// layer (the display gate hides it).
internal sealed class UnreadDecisionStep : ISelfTestStep
{
public string Name => "Hellion Chat - Unread decision (per active tab)";
@@ -27,7 +27,8 @@ internal sealed class UnreadDecisionStep : ISelfTestStep
}
// (b) inactive Unseen tab + the active tab does NOT show the message
// (currentTabMatches=false) => counts (badge). The Carla/Jin case.
// (currentTabMatches=false) => counts (badge). Two people talking in
// a channel the active tab does not carry.
if (!MessageManager.ShouldCountUnread(inactive, active, currentTabMatches: false))
{
ImGui.Text("(b) inactive Unseen tab must count when the active tab does not show it");
@@ -64,7 +64,7 @@ internal sealed class WizardStateSmokeStep : ISelfTestStep
// jumps straight to Step 4 (no Step-3 entry → no seed for
// FilterIncludePreviousSessions), commits, and asserts the history
// toggle remained on its pre-test value. Pins the null-semantics
// from Spec Z.176 so a regression in CommitPending that started
// so a regression in CommitPending that started
// writing seeded recommendations unconditionally would surface
// here.
// CommitPending → ApplyRoleplay overwrites six privacy /
+1 -1
View File
@@ -5,7 +5,7 @@ using Microsoft.Extensions.Logging;
namespace HellionChat.Services;
// Routes an incoming tell to the configured TellAutoOpenMode (Off/Sidebar/
// TopTab/Popout). Decoupled from AutoTellTabsService (Flo decision 2026-06-15):
// TopTab/Popout). Deliberately decoupled from AutoTellTabsService:
// that service owns tab CREATION + lifecycle; this only REVEALS/pops the tab it
// finds. Popout guards on pool.IsOpen so it never double-pops a tab the
// AutoTellTabsOpenAsPopout path already opened. Subscribes to the resolved
+11 -11
View File
@@ -54,7 +54,7 @@ public sealed class ThemeRegistry
internal void SetActiveChangedCallback(Action callback) => _onActiveChanged = callback;
// Shared slug guard for any code path that turns a slug into a filename.
// Both SaveEditingBuffer (F1) and ImportFromPath (M6) call this so the
// Both SaveEditingBuffer and ImportFromPath call this so the
// path-traversal/invalid-char rules live in exactly one place.
//
// Whitespace rejection is intentional: Path.GetInvalidFileNameChars on
@@ -112,7 +112,7 @@ public sealed class ThemeRegistry
public Theme Active => _active;
// Read-only exposure of the configured custom themes directory.
// M6 ThemeImportExportRow opens this path via Process.Start.
// ThemeImportExportRow opens this path via Process.Start.
public string? CustomThemesDir => _customThemesDir;
// Read-only enumeration of all built-in theme slugs. T2 ThemePickerCategoryStep
@@ -120,8 +120,8 @@ public sealed class ThemeRegistry
public IEnumerable<string> BuiltinSlugs => _builtIns.Keys;
// True try-pattern lookup: returns false when neither built-in nor custom
// cache holds the slug, no fallback to default. M3 ThemePicker uses this
// for card-rendering, M6 ThemeImportExportRow for fork-slug collisions.
// cache holds the slug, no fallback to default. ThemePicker uses this
// for card-rendering, ThemeImportExportRow for fork-slug collisions.
// Cold-cache fallback: see `LoadCustomBySlug` lookup-by-slug reverse
// iteration — it only walks the pre-populated _customCache. If a freshly
// imported file has not been enumerated yet (or no warm-up ran), the first
@@ -292,7 +292,7 @@ public sealed class ThemeRegistry
// Switch() prefers built-ins over custom themes with the same slug
// (see `Switch` built-in-first lookup), so saving a custom file under
// a built-in slug persists the file but leaves the built-in active —
// looks green, behaves broken. M4 ColorPicker DrawIdleState forks
// looks green, behaves broken. ColorPicker DrawIdleState forks
// built-in themes into a custom slug before BeginEditing, M6
// ImportFromPath renames built-in-colliding imports to <slug>_imported.
// New call-sites must either fork first or rename to a non-built-in slug.
@@ -308,7 +308,7 @@ public sealed class ThemeRegistry
// separators, parent-directory tokens, or platform-invalid filename chars.
// Without this guard an imported theme with Slug "../../../etc/passwd"
// would let Path.Combine escape _customThemesDir entirely. Shared helper
// so M6 ImportFromPath uses the exact same rule set.
// so ImportFromPath uses the exact same rule set.
var safeSlug = _editingThemeBuffer.Slug;
if (!IsSafeThemeSlug(safeSlug))
{
@@ -326,8 +326,8 @@ public sealed class ThemeRegistry
// persist a custom file under a built-in slug — the file lands on disk,
// Switch keeps the built-in active, and the post-save active-slug check
// below returns false. The caller then sees "save failed" while a garbage
// file accumulates in the themes dir on every retry. M4 ColorPicker forks
// built-in themes into a custom slug before BeginEditing, M6 ImportFromPath
// file accumulates in the themes dir on every retry. ColorPicker forks
// built-in themes into a custom slug before BeginEditing, ImportFromPath
// renames built-in-colliding imports to <slug>_imported, so production
// paths already steer clear; this guard catches everything else.
if (_builtIns.ContainsKey(safeSlug))
@@ -522,8 +522,8 @@ public sealed class ThemeRegistry
)
{
var t = (float)(now - _crossfadeStartTickMs) / CrossfadeDurationMs;
// A2: SmoothStep easing so the fade eases in/out instead of a
// linear ramp. MUST stay in lockstep with TryGetActiveCrossfade (K8).
// SmoothStep easing so the fade eases in and out instead of running
// linear. MUST stay in lockstep with TryGetActiveCrossfade.
var te = t * t * (3f - 2f * t);
snapshot = ThemeAbgrCacheLerp.Lerp(_previousAbgrSnapshot.Value, _active.AbgrCache, te);
}
@@ -551,7 +551,7 @@ public sealed class ThemeRegistry
return false;
var t = (float)elapsed / CrossfadeDurationMs;
// A2: SmoothStep easing -- keep identical to ArmCrossfade (K8).
// SmoothStep easing -- keep identical to ArmCrossfade.
var te = t * t * (3f - 2f * t);
lerped = ThemeAbgrCacheLerp.Lerp(_previousAbgrSnapshot.Value, _active.AbgrCache, te);
return true;
+2 -2
View File
@@ -18,7 +18,7 @@ internal sealed class CommandHelpWindow : Window
// Setter-injected post-ctor to break the InputBar -> CommandHelpWindow ->
// MainWindow -> InputBar singleton cycle (MS.DI does not detect cycles
// through FactoryCallSite registrations). Wired in
// CommandHelpWindowInitHostedService.StartAsync, same §6.2 pattern as
// CommandHelpWindowInitHostedService.StartAsync, same setter-injection pattern as
// MessageList.AttachPayloadHandler.
private Windows.MainWindow? _mainWindow;
@@ -41,7 +41,7 @@ internal sealed class CommandHelpWindow : Window
RespectCloseHotkey = false;
DisableWindowSounds = true;
// Logger injected for future diagnostic hooks (no call-sites yet in R2).
// Logger injected for future diagnostic hooks; no call sites yet.
_ = _logger;
}
+1 -1
View File
@@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace HellionChat.Ui.Components;
// B2 (PERF-B2): variable-height clip plan. ImGuiListClipper needs a constant
// row height, and since v1.10.0/A2 neither density has one (compact rows wrap
// row height, and since v1.10.0 neither density has one (compact rows wrap
// too), so both compute a plan from the cached per-row heights: a lead dummy
// for the rows above
// the viewport, the [first..last] index range that overlaps the viewport, and
+2 -2
View File
@@ -118,7 +118,7 @@ internal sealed class InputBar
// Note: when MainWindow is closed, DrawInputField never runs, so
// _isFocused keeps the last value written by the previous draw pass.
// The consumer that actually pushes this state across the IPC boundary
// (TypingIpc.BuildState, see F3 Step 2) gates on Plugin.MainWindow.IsOpen
// (TypingIpc.BuildState) gates on Plugin.MainWindow.IsOpen
// itself, so the stale backing-field never leaks to subscribers. Mirroring
// the gate here would require an extra Plugin-backref in InputBar that the
// rest of the component doesn't need.
@@ -1183,7 +1183,7 @@ internal sealed class InputBar
}
// DTO for an in-flight auto-translate completion. Lives as a companion type
// in this file because it is only consumed by InputBar (see v1.7.1 Fix #4 plan §2.4).
// in this file because it is only consumed by InputBar.
internal sealed class AutoCompleteInfo
{
// ToComplete MUST be a mutable field (not an auto-property), because the
+4 -4
View File
@@ -38,7 +38,7 @@ internal sealed class MessageList
private readonly Action<Message, string?> _drawCardRow;
// Reused across frames: at MessageManager.MessageDisplayLimit a fresh array
// per frame is 40 KB of garbage, and A2 put the default density on this
// per frame is 40 KB of garbage, and A later cycle put the default density on this
// path. The old comment named MaxLinesToRender and its 2500 default, a
// config field that had stopped bounding anything.
private float[] _heightScratch = [];
@@ -49,7 +49,7 @@ internal sealed class MessageList
private bool _stampVisible;
private float _metaDrop;
// §6.2: setter-injection breaks the PayloadHandler → MainWindow → MessageList → PayloadHandler 3-cycle.
// Setter-injection breaks the PayloadHandler → MainWindow → MessageList → PayloadHandler 3-cycle.
// Wired by PayloadHandlerInitHostedService.StartAsync after both singletons exist.
internal void AttachPayloadHandler(PayloadHandler handler)
{
@@ -88,7 +88,7 @@ internal sealed class MessageList
// SelfTest hook (B2): drives the live invalidation, returns the tab's remaining
// cached-height count so the step can assert the drop. nowMs is a parameter so
// the step can step past the settle window without sleeping (v1.10.0/A1).
// the step can step past the settle window without sleeping (v1.10.0).
internal int RunHeightCacheInvalidationForSelfTest(Tab tab, float contentWidth, long nowMs)
{
InvalidateHeightCacheIfLayoutChanged(tab, contentWidth, nowMs);
@@ -569,7 +569,7 @@ internal sealed class MessageList
// B2-1/B2-2: sender via DrawChunks (name-aware path), on its own line
// with content below — 1.5.6 card parity (ChatLogWindow.cs:1913, no
// SameLine after the sender). The 1.5.6 channel-colour push on the
// sender is deferred styling polish (masterplan §6 -> v1.9.0); plain
// sender is deferred styling polish (deferred to v1.9.0); plain
// text here.
// A system message has no sender, so a header row would be a stamp on a
// line of its own -- an empty gesture. Those stay single-line in both
@@ -20,7 +20,7 @@ internal sealed class LivePreviewPanel : IDisposable
private static uint Abgr(StyleEngine.Token token, ThemeColors colors) =>
ColourUtil.RgbaToAbgr(Tokens.Resolve(token, colors));
// Static counter for S5 reload-stress verification: after 10 reloads the
// Static counter for reload-stress verification: after 10 reloads the
// counter must read 0 (plugin disabled) or 1 (plugin enabled). Anything
// higher signals a Dispose skip and a subscriber leak against ThemeRegistry.
internal static int InstanceCount;
@@ -254,7 +254,7 @@ internal sealed class DataPrivacyTab
)
{
// Read-only statement, not a switch. Do not promote it to one
// without an explicit Sub-Spec change: a toggle implies there is
// without an explicit design change: a toggle implies there is
// something to turn off.
ImGuiUtil.HelpText(HellionStrings.Settings_Telemetry_None);
}
@@ -173,8 +173,7 @@ internal sealed class ThemeImportExportRow
// Slug sanitisation BEFORE BeginEditing — SaveEditingBuffer would
// reject too, but rejecting here means an unsafe slug never enters
// the editing buffer. Shared helper ThemeRegistry.IsSafeThemeSlug
// keeps the rule set in sync with F1's save-side guard (see
// ThemeRegistry.IsSafeThemeSlug shared helper).
// keeps the rule set in sync with the save-side guard.
var importSlug = theme.Slug;
if (!ThemeRegistry.IsSafeThemeSlug(importSlug))
{
@@ -186,9 +185,9 @@ internal sealed class ThemeImportExportRow
return;
}
// Pragmatic deviation from §1.6 wording ("File.Copy into themes/"):
// BeginEditing+SaveEditingBuffer produces the same end-state and
// reuses the validated F1 save pipeline. Trade-off: destination
// Not a plain File.Copy into themes/: BeginEditing+SaveEditingBuffer
// produces the same end state and reuses the validated save
// pipeline. Trade-off: destination
// filename becomes the theme's slug, not the original filename.
//
// Slug-collision handling:
+1 -1
View File
@@ -54,7 +54,7 @@ internal sealed class InputPreview : Window
DisableWindowSounds = true;
IsOpen = true;
// TODO Polish-Sweep: remove discard once logging call-sites exist
// TODO: remove discard once logging call sites exist
_ = _logger;
}
@@ -12,7 +12,7 @@ namespace HellionChat.Ui.StyleEngine;
// ABGR before delegating to ImDrawList.
internal static class DrawListExtensions
{
// A1 accent-tint (Variante A): how far the white sweep is pulled toward
// Accent tint: how far the white sweep is pulled toward
// the element's accent hue. Kept low so the sheen reads as a tinted
// highlight, not a saturated accent flash (effect level "subtle").
private const float SheenTintStrength = 0.35f;
+2 -2
View File
@@ -9,7 +9,7 @@ namespace HellionChat.Ui.StyleEngine;
// compare an unscaled value (Sidebar.GetWidth, the width slider bounds).
//
// Deliberately NOT part of ThemeLayout: that record is serialised into theme
// JSON, and layout customisation is out of scope per the master spec.
// JSON, and layout customisation is deliberately out of scope.
internal static class Metrics
{
// --- Sidebar ---
@@ -65,7 +65,7 @@ internal static class Metrics
return _cachedScale;
// Safe variant: GlobalScale throws while the interface manager is
// still coming up, and Block F pulls Metrics into more call sites.
// still coming up, and Metrics reaches more call sites than it did.
_cachedScale = ImGuiHelpers.GlobalScaleSafe;
_cachedFrame = frame;
return _cachedScale;
+1 -1
View File
@@ -23,7 +23,7 @@ internal enum TypeRole
// exactly Body and never be pushed -- a value with no call site, which is the one
// thing this whole style track exists to stop.
//
// The factors are defaults, not constants. The master spec puts typography under
// The factors are defaults, not constants. Typography is meant to sit under
// theme control rather than user control, and ThemeTypography already exists as
// the extension point for exactly that. A const would wall it off. What is
// deliberately absent either way is a user-facing slider per role.
@@ -18,8 +18,8 @@ namespace HellionChat.Ui.StyleEngine.Widgets;
//
// So: no plate, but a tint. A tenth-opacity accent wash falling from the top
// edge -- colour as atmosphere rather than as a box, which is what the plate got
// wrong. Flo picked it from the lab over the rule-only variant; at this strength
// it survives the violet themes that killed the filled bar.
// wrong. Picked from the lab over the rule-only variant; at this strength it
// survives the violet themes that killed the filled bar.
//
// Which face draws what is not a style choice here, it is a constraint. The meta
// face has a glyph range of ASCII plus a middle dot, so only the world name and
+2 -2
View File
@@ -7,7 +7,7 @@ namespace HellionChat.Ui.Windows;
// windows via the injected factory, all registered once in the WindowSystem
// (PluginLifecycle.RegisterWindows, framework thread). Open/Close is IsOpen +
// Bind/Unbind only — NEVER runtime AddWindow/RemoveWindow (v1.4.9 Stage-2
// freeze lesson). Pure DI-sink: no PayloadHandler in the ctor (plan §B.2).
// freeze lesson). Pure DI-sink: no PayloadHandler in the ctor.
internal sealed class ChannelPopoutPool
{
private readonly List<ChannelPopoutWindow> _instances;
@@ -30,7 +30,7 @@ internal sealed class ChannelPopoutPool
// Route each window's in-body close through the pool so closing releases
// the slot. Wired here (post-construction) rather than via ctor to avoid
// a Window->Pool edge that would re-enter pool resolution (plan §B.2).
// a Window->Pool edge that would re-enter pool resolution.
foreach (var window in _instances)
window.CloseRequested = TryClose;
}
@@ -13,7 +13,7 @@ namespace HellionChat.Ui.Windows;
// One pre-allocated pop-out window bound to a single Tab. Pure DI-sink: the
// PayloadHandler arrives via AttachPayloadHandler (post-build setter), NEVER
// via ctor — see plan §B.2. The ###id carries the slot index so all N
// via ctor. The ###id carries the slot index so all N
// instances are unique for WindowSystem.AddWindow and ImGui state is stable
// per slot (not per bound tab).
internal sealed class ChannelPopoutWindow : Window, IFocusableChatWindow
@@ -66,10 +66,10 @@ internal sealed class ChannelPopoutWindow : Window, IFocusableChatWindow
// Wired post-build by ChannelPopoutPool so closing routes through the pool
// (which owns the slot map). The window can't reach the pool by ctor without
// a DI cycle, so the pool sets this after construction. See plan §B.2.
// a DI cycle, so the pool sets this after construction.
public Action<Guid>? CloseRequested { get; set; }
// Post-build setter — see plan §B.2. Wired by ChannelPopoutInitHostedService.
// Post-build setter. Wired by ChannelPopoutInitHostedService.
public void AttachPayloadHandler(PayloadHandler handler) =>
_messages.AttachPayloadHandler(handler);
@@ -78,7 +78,7 @@ internal sealed class ChannelPopoutWindow : Window, IFocusableChatWindow
Bound = tab;
var isTell = tab is { IsTempTab: true, TellTarget: { } target } && target.IsSet();
// Master §4.3 default sizes: Tell is the more compact conversation window.
// Default sizes: Tell is the more compact conversation window.
Size = isTell ? new Vector2(380f, 320f) : new Vector2(420f, 320f);
SizeCondition = ImGuiCond.FirstUseEver;
+2 -2
View File
@@ -13,8 +13,8 @@ namespace HellionChat.Ui.Windows;
// Variants of UI elements, drawn side by side so decisions are made by looking
// rather than by imagining. Reachable with /hellion lab.
//
// Permanent, by Flo's call: a dev playground for seeing ideas in-game against
// the live theme. The radios default to whatever shipped, so the window also
// Permanent: a playground for seeing ideas in-game against the live theme.
// The radios default to whatever shipped, so the window also
// documents which variant won and what it beat.
//
// It exists because the alternative was drawing mockups, and mockups are what
+2 -2
View File
@@ -62,9 +62,9 @@ internal static class AutoTranslate
{
var sw = Stopwatch.StartNew();
AllEntries();
// v1.4.9 R3 profiling: Information so the xllog tail surfaces this
// v1.4.9 profiling: Information so the xllog tail surfaces this
// without a Debug filter. Belt-and-suspenders for future plugin-load
// regressions; remains in place after Sub-Task 3.4 Befund.
// regressions.
Plugin.LogProxy.Information(
$"Warming up auto-translate took {sw.ElapsedMilliseconds}ms"
);
-281
View File
@@ -9,244 +9,6 @@ namespace HellionChat.Util;
internal static class ChunkUtil
{
// internal static IEnumerable<Chunk> ToChunks(ReadOnlySeString msg, ChunkSource source, ChatType? defaultColour)
// {
// var chunks = new List<Chunk>();
//
// var italic = false;
// var foreground = new Stack<uint>();
// var glow = new Stack<uint>();
// Payload? link = null;
//
// void Append(string text)
// {
// chunks.Add(new TextChunk(source, link, text)
// {
// FallbackColour = defaultColour,
// Foreground = foreground.Count > 0 ? foreground.Peek() : null,
// Glow = glow.Count > 0 ? glow.Peek() : null,
// Italic = italic,
// });
// }
//
// foreach (var payload in msg)
// {
// if (payload.Type == ReadOnlySePayloadType.Text)
// {
// // We don't want to parse any null string
// var str = payload.ToString();
// var nulIndex = str.IndexOf('\0');
// if (nulIndex > 0)
// str = str[..nulIndex];
// if (string.IsNullOrEmpty(str))
// continue;
//
// Append(str);
// continue;
// }
//
// switch (payload.MacroCode)
// {
// case MacroCode.Italic:
// var newStatus = payload.TryGetExpression(out var expression) && expression.TryGetUInt(out var value) && value == 1;
// italic = newStatus;
// break;
// case MacroCode.Color:
// if (payload.TryGetExpression(out var eColor))
// {
// if (eColor.TryGetPlaceholderExpression(out var ph) && ph == (int)ExpressionType.StackColor)
// {
// if (foreground.Count > 0)
// foreground.Pop();
// }
// else if (TryResolveUInt(eColor, out var eColorVal))
// {
// var color = ColourUtil.ArgbToRgba(eColorVal);
//
// if (color > 0)
// foreground.Push(color);
// else if (foreground.Count > 0) // Push the previous color as we don't want invisible text
// foreground.Push(foreground.Peek());
// }
// }
// break;
// case MacroCode.EdgeColor:
// if (payload.TryGetExpression(out eColor))
// {
// if (eColor.TryGetPlaceholderExpression(out var ph) && ph == (int)ExpressionType.StackColor)
// {
// if (glow.Count > 0)
// glow.Pop();
// }
// else if (TryResolveUInt(eColor, out var eColorVal))
// {
// glow.Push(ColourUtil.ArgbToRgba(eColorVal));
// }
// }
// break;
// case MacroCode.ColorType:
// if (!payload.TryGetExpression(out var eColorType) || !eColorType.TryGetUInt(out var eColorTypeVal))
// {
// if (foreground.Count > 0)
// foreground.Pop();
// break;
// }
//
// if (eColorTypeVal == 0)
// {
// if (foreground.Count > 0)
// foreground.Pop();
// }
// else if (Sheets.UIColorSheet.TryGetRow(eColorTypeVal, out var row))
// {
// foreground.Push(row.Dark);
// }
// break;
// case MacroCode.EdgeColorType:
// if (!payload.TryGetExpression(out var eEdgeColor) || !eEdgeColor.TryGetUInt(out var eEdgeColorVal))
// {
// if (glow.Count > 0)
// glow.Pop();
// break;
// }
//
// if (eEdgeColorVal == 0)
// {
// if (glow.Count > 0)
// glow.Pop();
// }
// else if (Sheets.UIColorSheet.TryGetRow(eEdgeColorVal, out var row))
// {
// glow.Push(row.Dark);
// }
// break;
// case MacroCode.Fixed:
// if (!payload.TryGetExpression(out var expr1, out var expr2))
// break;
//
// if (expr1.TryGetUInt(out var group) && expr2.TryGetUInt(out var key))
// {
// chunks.Add(new IconChunk(source, null, BitmapFontIcon.AutoTranslateBegin));
// using var rssb = new RentedSeStringBuilder();
// var translatePayload = rssb.Builder
// .BeginMacro(MacroCode.Fixed)
// .AppendUIntExpression(group - 1)
// .AppendUIntExpression(key)
// .EndMacro()
// .ToReadOnlySeString();
//
// Append(Plugin.Evaluator.Evaluate(translatePayload).ToString());
// chunks.Add(new IconChunk(source, null, BitmapFontIcon.AutoTranslateEnd));
// }
// break;
// case MacroCode.Icon:
// if (payload.TryGetExpression(out var eIcon) && TryResolveInt(eIcon, out var iconVal))
// chunks.Add(new IconChunk(source, link, (BitmapFontIcon)iconVal));
// break;
// case MacroCode.Link:
// if (!payload.TryGetExpression(
// out var linkTypeExpr1,
// out var uintExpr2,
// out var intExpr3,
// out var intExpr4,
// out var strExpr5))
// break;
//
// if (!linkTypeExpr1.TryGetUInt(out var linkType))
// break;
//
// switch ((LinkMacroPayloadType)linkType)
// {
// case LinkMacroPayloadType.Terminator:
// link = null;
// break;
// case LinkMacroPayloadType.MapPosition:
// if (!uintExpr2.TryGetUInt(out var ids))
// break;
//
// if (!intExpr3.TryGetInt(out var rawX))
// break;
//
// if (!intExpr4.TryGetInt(out var rawY))
// break;
//
// var mapId = ids & 0xFF;
// var territoryId = (ids >> 16) & 0xFF;
// break;
// case (LinkMacroPayloadType)Payload.EmbeddedInfoType.DalamudLink - 1:
// if (!uintExpr2.TryGetUInt(out var commandId))
// break;
//
// if (!intExpr3.TryGetInt(out var extra1))
// break;
//
// if (!intExpr4.TryGetInt(out var extra2))
// break;
//
// if (!strExpr5.TryGetString(out var extraStr))
// break;
// break;
// case LinkMacroPayloadType.Quest:
// if (!uintExpr2.TryGetUInt(out var questId))
// break;
// break;
// case LinkMacroPayloadType.Status:
// if (!uintExpr2.TryGetUInt(out var statusId))
// break;
// break;
// case LinkMacroPayloadType.Item:
// if (!uintExpr2.TryGetUInt(out var itemId))
// break;
// break;
// case LinkMacroPayloadType.Character:
// if (!uintExpr2.TryGetUInt(out var flags))
// break;
//
// if (!intExpr3.TryGetUInt(out var worldId))
// break;
// break;
// case LinkMacroPayloadType.PartyFinder:
// if (!uintExpr2.TryGetUInt(out var listingId))
// break;
//
// // intExpr3 is unused
//
// if (!intExpr4.TryGetUInt(out worldId))
// break;
// break;
// case LinkMacroPayloadType.PartyFinderNotification:
// // no expr used
// break;
// case LinkMacroPayloadType.Achievement:
// if (!uintExpr2.TryGetUInt(out var achievementId))
// break;
// break;
// }
// break;
// case MacroCode.NonBreakingSpace:
// Append(" ");
// break;
// case PayloadType.Unknown:
// var rawPayload = (RawPayload)payload;
// else if (rawPayload.Data.Length > 1 && rawPayload.Data[1] == 0x14)
// {
// if (glow.Count > 0)
// {
// glow.Pop();
// }
// else if (rawPayload.Data.Length > 6 && rawPayload.Data[2] == 0x05 && rawPayload.Data[3] == 0xF6)
// {
// var (r, g, b) = (rawPayload.Data[4], rawPayload.Data[5], rawPayload.Data[6]);
// glow.Push(ColourUtil.ComponentsToRgba(r, g, b));
// }
// }
// break;
// }
// }
//
// return chunks;
// }
internal static IEnumerable<Chunk> ToChunks(
SeString msg,
ChunkSource source,
@@ -499,47 +261,4 @@ internal static class ChunkUtil
return BitConverter.ToUInt32(numArray, 0);
}
// private static bool TryResolveUInt(in ReadOnlySeExpressionSpan expression, out uint value)
// {
// if (expression.TryGetUInt(out value))
// return true;
//
// if (expression.TryGetParameterExpression(out var exprType, out var operand1))
// {
// if (!TryResolveUInt(operand1, out var paramIndex))
// return false;
//
// if (paramIndex == 0)
// return false;
//
// paramIndex--;
// if ((ExpressionType)exprType == ExpressionType.GlobalNumber)
// {
// value = (uint) GlobalParametersCache.GetValue((int)paramIndex);
// return true;
// }
// // return (ExpressionType)exprType switch
// // {
// // // ExpressionType.LocalNumber => context.TryGetLNum((int)paramIndex, out value), // lnum
// // ExpressionType.GlobalNumber => (uint) GlobalParametersCache.GetValue((int)paramIndex), // gnum
// // _ => false, // gstr, lstr
// // };
// }
//
// return false;
// }
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// private static bool TryResolveInt(in ReadOnlySeExpressionSpan expression, out int value)
// {
// if (TryResolveUInt(expression, out var u32))
// {
// value = (int)u32;
// return true;
// }
//
// value = 0;
// return false;
// }
}
+1 -2
View File
@@ -11,8 +11,7 @@ namespace HellionChat.Util;
// a later settings change reformats history too.
// Known trade-off: at non-default settings, this allocates one List<Chunk> per
// visible message per frame. Lists are small and the path is skipped at the
// neutral defaults, so GC pressure is low in practice. Accepted; noted in
// Cycle Notes.
// neutral defaults, so GC pressure is low in practice. Accepted as-is.
internal static class SenderNameDisplay
{
// Returns a copy of the list with the whole ChunkSource.Sender span