feat(messages): render sender names through the name-aware path
This commit is contained in:
@@ -385,6 +385,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
|||||||
new SelfTests.PerformanceBaselineStep(this),
|
new SelfTests.PerformanceBaselineStep(this),
|
||||||
new SelfTests.MainWindowFocusOpacityStep(this),
|
new SelfTests.MainWindowFocusOpacityStep(this),
|
||||||
new SelfTests.MainWindowFlagsStep(this),
|
new SelfTests.MainWindowFlagsStep(this),
|
||||||
|
new SelfTests.SenderNameReformatStep(this),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Re-surface the wizard for existing users when a major UX
|
// Re-surface the wizard for existing users when a major UX
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
|
using Dalamud.Plugin.SelfTest;
|
||||||
|
|
||||||
|
namespace HellionChat.SelfTests;
|
||||||
|
|
||||||
|
// B2-1/B2-2: proves the WorldSuffixMode/NameFormMode reformat reaches the REAL
|
||||||
|
// render entry. Drives ChunkRenderer.DrawChunks (a SelfTests/README-sanctioned
|
||||||
|
// real entry that wires SenderNameDisplay.ForDisplay at ChunkRenderer.cs:54)
|
||||||
|
// with a synthetic ChunkSource.Sender chunk carrying a PlayerPayload, at a
|
||||||
|
// non-neutral NameFormMode, and reads the LastRenderedSenderText observability
|
||||||
|
// the real draw produced. NameFormMode.Initials + WorldSuffixMode.Never is
|
||||||
|
// world-independent ("Test Tester" -> "T. T."), so the assertion is
|
||||||
|
// deterministic without a live world lookup. The MessageList routing (its row
|
||||||
|
// methods pass message.Sender to DrawChunks) is gated by the reviewer-grep
|
||||||
|
// (Step 2.6) + in-game smoke, since the visible sender change needs real chat +
|
||||||
|
// the world sheet. Does NOT call SenderNameFormatter/ForDisplay in isolation
|
||||||
|
// (the false-green trap — both are green today on a path the message list never
|
||||||
|
// takes for the sender).
|
||||||
|
internal sealed class SenderNameReformatStep : ISelfTestStep
|
||||||
|
{
|
||||||
|
private readonly Plugin plugin;
|
||||||
|
|
||||||
|
public SenderNameReformatStep(Plugin plugin)
|
||||||
|
{
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name => "Hellion Chat - sender name reformat";
|
||||||
|
|
||||||
|
public SelfTestStepResult RunStep()
|
||||||
|
{
|
||||||
|
var renderer = this.plugin.ChunkRenderer;
|
||||||
|
if (renderer is null)
|
||||||
|
{
|
||||||
|
ImGui.Text("Plugin.ChunkRenderer is null");
|
||||||
|
return SelfTestStepResult.Fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
var savedForm = Plugin.Config.NameFormMode;
|
||||||
|
var savedSuffix = Plugin.Config.WorldSuffixMode;
|
||||||
|
var savedScreenshot = Plugin.Config.ScreenshotMode;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Initials (non-neutral) so ForDisplay reformats; Never + screenshot
|
||||||
|
// off so the result is world-independent and the reformat is not
|
||||||
|
// skipped.
|
||||||
|
Plugin.Config.NameFormMode = NameFormMode.Initials;
|
||||||
|
Plugin.Config.WorldSuffixMode = WorldSuffixMode.Never;
|
||||||
|
Plugin.Config.ScreenshotMode = false;
|
||||||
|
|
||||||
|
// ForDisplay formats payload.PlayerName, not the chunk text.
|
||||||
|
var payload = new PlayerPayload("Test Tester", 1u);
|
||||||
|
var senderChunks = new List<Chunk>
|
||||||
|
{
|
||||||
|
new TextChunk(ChunkSource.Sender, payload, "Test Tester"),
|
||||||
|
};
|
||||||
|
|
||||||
|
renderer.DrawChunks(senderChunks);
|
||||||
|
|
||||||
|
if (renderer.LastRenderedSenderText != "T. T.")
|
||||||
|
{
|
||||||
|
ImGui.Text(
|
||||||
|
$"LastRenderedSenderText = '{renderer.LastRenderedSenderText}', expected 'T. T.' (Initials reformat through the real render path)"
|
||||||
|
);
|
||||||
|
return SelfTestStepResult.Fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SelfTestStepResult.Pass;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Plugin.Config.NameFormMode = savedForm;
|
||||||
|
Plugin.Config.WorldSuffixMode = savedSuffix;
|
||||||
|
Plugin.Config.ScreenshotMode = savedScreenshot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CleanUp() { }
|
||||||
|
}
|
||||||
@@ -38,6 +38,13 @@ internal sealed class ChunkRenderer
|
|||||||
_ = _logger;
|
_ = _logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// B2-1/B2-2 render-observability: the formatted sender text the real draw
|
||||||
|
// path actually produced (post-ForDisplay). A SelfTest reads this after
|
||||||
|
// driving DrawChunks to prove the WorldSuffixMode/NameFormMode reformat
|
||||||
|
// reached the real render entry — never the helper in isolation. null until
|
||||||
|
// a sender span is reformatted for display.
|
||||||
|
internal string? LastRenderedSenderText { get; private set; }
|
||||||
|
|
||||||
public void DrawChunks(
|
public void DrawChunks(
|
||||||
IReadOnlyList<Chunk> chunks,
|
IReadOnlyList<Chunk> chunks,
|
||||||
bool wrap = true,
|
bool wrap = true,
|
||||||
@@ -51,7 +58,25 @@ internal sealed class ChunkRenderer
|
|||||||
// the list unchanged when nothing applies, so non-sender lists and the
|
// the list unchanged when nothing applies, so non-sender lists and the
|
||||||
// neutral default cost only a quick scan.
|
// neutral default cost only a quick scan.
|
||||||
if (!Plugin.Config.ScreenshotMode)
|
if (!Plugin.Config.ScreenshotMode)
|
||||||
chunks = SenderNameDisplay.ForDisplay(chunks);
|
{
|
||||||
|
var displayed = SenderNameDisplay.ForDisplay(chunks);
|
||||||
|
// ForDisplay only allocates a NEW list when it actually reformatted
|
||||||
|
// a sender span (same reference on the neutral default / non-sender
|
||||||
|
// lists). So this scan runs only when a sender name was reformatted
|
||||||
|
// for display — zero overhead on the neutral-default hot path.
|
||||||
|
if (!ReferenceEquals(displayed, chunks))
|
||||||
|
{
|
||||||
|
chunks = displayed;
|
||||||
|
foreach (var c in chunks)
|
||||||
|
{
|
||||||
|
if (c.Source == ChunkSource.Sender && c is TextChunk reformatted)
|
||||||
|
{
|
||||||
|
LastRenderedSenderText = reformatted.Content;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||||
|
|
||||||
|
|||||||
@@ -88,12 +88,25 @@ internal sealed class MessageList
|
|||||||
|
|
||||||
private void DrawCompactRow(Message message)
|
private void DrawCompactRow(Message message)
|
||||||
{
|
{
|
||||||
|
// B2-1/B2-2: render the sender through DrawChunks (the name-aware path
|
||||||
|
// that applies WorldSuffixMode/NameFormMode via ForDisplay), not as a
|
||||||
|
// flat SenderSource.TextValue string. message.Sender already carries the
|
||||||
|
// channel brackets/colon as ChunkSource.None wrappers (MessageManager
|
||||||
|
// .cs:300-314), so the separator is rendered by the chunks. 1.5.6 parity
|
||||||
|
// (ChatLogWindow.cs:1965: DrawChunks(message.Sender) + SameLine).
|
||||||
var timestamp = FormatTimestamp(message.Date);
|
var timestamp = FormatTimestamp(message.Date);
|
||||||
var sender = message.SenderSource.TextValue;
|
if (message.Sender.Count > 0)
|
||||||
ImGui.TextUnformatted(
|
{
|
||||||
string.IsNullOrEmpty(sender) ? timestamp : $"{timestamp} {sender}: "
|
ImGui.TextUnformatted($"{timestamp} ");
|
||||||
);
|
|
||||||
ImGui.SameLine(0f, 0f);
|
ImGui.SameLine(0f, 0f);
|
||||||
|
_chunkRenderer.DrawChunks(message.Sender, wrap: true, handler: _handler, lineWidth: 0f);
|
||||||
|
ImGui.SameLine(0f, 0f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted(timestamp);
|
||||||
|
ImGui.SameLine(0f, 0f);
|
||||||
|
}
|
||||||
_chunkRenderer.DrawChunks(message.Content, wrap: true, handler: _handler, lineWidth: 0f);
|
_chunkRenderer.DrawChunks(message.Content, wrap: true, handler: _handler, lineWidth: 0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,9 +141,22 @@ internal sealed class MessageList
|
|||||||
|
|
||||||
private void DrawCardRow(Message message)
|
private void DrawCardRow(Message message)
|
||||||
{
|
{
|
||||||
|
// 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
|
||||||
|
// text here.
|
||||||
var timestamp = FormatTimestamp(message.Date);
|
var timestamp = FormatTimestamp(message.Date);
|
||||||
var sender = message.SenderSource.TextValue;
|
if (message.Sender.Count > 0)
|
||||||
ImGui.TextUnformatted(string.IsNullOrEmpty(sender) ? timestamp : $"{timestamp} {sender}");
|
{
|
||||||
|
ImGui.TextUnformatted($"{timestamp} ");
|
||||||
|
ImGui.SameLine(0f, 0f);
|
||||||
|
_chunkRenderer.DrawChunks(message.Sender, wrap: true, handler: _handler, lineWidth: 0f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted(timestamp);
|
||||||
|
}
|
||||||
_chunkRenderer.DrawChunks(message.Content, wrap: true, handler: _handler, lineWidth: 0f);
|
_chunkRenderer.DrawChunks(message.Content, wrap: true, handler: _handler, lineWidth: 0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user