revert(ui): remove the per-tab regex filter
This commit is contained in:
@@ -1,13 +1,11 @@
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Dalamud;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Configuration;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Interface.FontIdentifier;
|
||||
using HellionChat._Helpers;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.GameFunctions.Types;
|
||||
using HellionChat.Resources;
|
||||
@@ -473,10 +471,6 @@ public class Tab
|
||||
public bool EnableNotificationSound;
|
||||
public uint NotificationSoundId = 1;
|
||||
|
||||
// UI-8: optional regex applied on top of the channel filter. Null or
|
||||
// empty means no filter, today's behaviour unchanged.
|
||||
public string? MessageRegex;
|
||||
|
||||
[NonSerialized]
|
||||
public uint Unread;
|
||||
|
||||
@@ -530,32 +524,11 @@ public class Tab
|
||||
[NonSerialized]
|
||||
internal float _cardHoverAlpha;
|
||||
|
||||
// UI-8: compiled-regex cache. Recompiled only when MessageRegex changes;
|
||||
// _compiledRegexKey is the validation key (mirrors the _cachedTint* keys).
|
||||
[NonSerialized]
|
||||
private Regex? _compiledRegex;
|
||||
|
||||
[NonSerialized]
|
||||
private string? _compiledRegexKey;
|
||||
|
||||
public bool Matches(Message message)
|
||||
{
|
||||
if (!message.Matches(SelectedChannels, ExtraChatAll, ExtraChatChannels))
|
||||
return false;
|
||||
|
||||
// UI-8: optional regex content filter, AND-constraint after the channel
|
||||
// check. An empty pattern or an invalid one (cached regex is null) lets
|
||||
// every message through, so existing tabs behave unchanged.
|
||||
if (!string.IsNullOrEmpty(MessageRegex))
|
||||
{
|
||||
var text = string.Join(
|
||||
"",
|
||||
message.Content.OfType<TextChunk>().Select(c => c.Content)
|
||||
);
|
||||
if (!RegexRouteMatcher.IsMatch(GetCompiledRegex(), text))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Temp tabs are bound to a single conversation partner — other tells
|
||||
// matching the channel filter must not land here.
|
||||
if (IsTempTab && TellTarget?.IsSet() == true)
|
||||
@@ -564,20 +537,6 @@ public class Tab
|
||||
return true;
|
||||
}
|
||||
|
||||
// UI-8: lazily compiles MessageRegex and caches the result. Recompiles only
|
||||
// when the pattern string changed (validation-key pattern, same idea as the
|
||||
// _cachedTint* caches). An invalid pattern caches a null regex.
|
||||
private Regex? GetCompiledRegex()
|
||||
{
|
||||
if (_compiledRegexKey != MessageRegex)
|
||||
{
|
||||
(_compiledRegex, _) = RegexRouteMatcher.Compile(MessageRegex);
|
||||
_compiledRegexKey = MessageRegex;
|
||||
}
|
||||
|
||||
return _compiledRegex;
|
||||
}
|
||||
|
||||
public void AddMessage(Message message, bool unread = true)
|
||||
{
|
||||
Messages.AddPrune(message, MessageManager.MessageDisplayLimit);
|
||||
@@ -632,7 +591,6 @@ public class Tab
|
||||
TellTarget = TellTarget.Clone(),
|
||||
EnableNotificationSound = EnableNotificationSound,
|
||||
NotificationSoundId = NotificationSoundId,
|
||||
MessageRegex = MessageRegex,
|
||||
IsGreeted = IsGreeted,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -201,10 +201,10 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
|
||||
// Schema gate: v1.4.x+ requires config v16+. Users on older schemas
|
||||
// must install v1.4.2 first to run the migration chain. v19 adds the
|
||||
// per-tab MessageRegex field (UI-8) and the top-level CustomSoundVolume,
|
||||
// WindowOpacityInactive, WorldSuffixMode and NameFormMode fields — all
|
||||
// additive with defaults, so v16-v18 configs load cleanly and get their
|
||||
// Version stamp bumped after the gate.
|
||||
// top-level CustomSoundVolume, WindowOpacityInactive, WorldSuffixMode
|
||||
// and NameFormMode fields — all additive with defaults, so v16-v18
|
||||
// configs load cleanly and get their Version stamp bumped after the
|
||||
// gate.
|
||||
if (Config.Version < 16)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
|
||||
@@ -470,11 +470,6 @@ internal class HellionStrings
|
||||
internal static string ChatLog_Insert_MapFlag => Get(nameof(ChatLog_Insert_MapFlag));
|
||||
internal static string ChatLog_Insert_ItemLink => Get(nameof(ChatLog_Insert_ItemLink));
|
||||
|
||||
// v1.5.6: per-tab regex filter
|
||||
internal static string Settings_Tabs_MessageRegex_Name => Get(nameof(Settings_Tabs_MessageRegex_Name));
|
||||
internal static string Settings_Tabs_MessageRegex_Description => Get(nameof(Settings_Tabs_MessageRegex_Description));
|
||||
internal static string Settings_Tabs_MessageRegex_Invalid => Get(nameof(Settings_Tabs_MessageRegex_Invalid));
|
||||
|
||||
// v1.5.6: plugin-disclosure warning
|
||||
internal static string Settings_Chat_NotifyPluginDisclosure_Name => Get(nameof(Settings_Chat_NotifyPluginDisclosure_Name));
|
||||
internal static string Settings_Chat_NotifyPluginDisclosure_Description => Get(nameof(Settings_Chat_NotifyPluginDisclosure_Description));
|
||||
|
||||
@@ -1091,17 +1091,6 @@
|
||||
<value>Insert linked item <item></value>
|
||||
</data>
|
||||
|
||||
<!-- v1.5.6: per-tab regex filter -->
|
||||
<data name="Settings_Tabs_MessageRegex_Name" xml:space="preserve">
|
||||
<value>Message filter (regex)</value>
|
||||
</data>
|
||||
<data name="Settings_Tabs_MessageRegex_Description" xml:space="preserve">
|
||||
<value>Only keep messages whose text matches this regular expression. Applied on top of the channel filter. Leave empty to disable. Matching is case-insensitive.</value>
|
||||
</data>
|
||||
<data name="Settings_Tabs_MessageRegex_Invalid" xml:space="preserve">
|
||||
<value>Invalid pattern: {0}</value>
|
||||
</data>
|
||||
|
||||
<!-- v1.5.6: plugin-disclosure warning -->
|
||||
<data name="Settings_Chat_NotifyPluginDisclosure_Name" xml:space="preserve">
|
||||
<value>Warn before sending plugin-only symbols</value>
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using HellionChat._Helpers;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
@@ -240,25 +238,6 @@ internal sealed class Tabs : ISettingsTab
|
||||
}
|
||||
}
|
||||
}
|
||||
// UI-8: optional regex filter for this tab.
|
||||
var regexBuffer = tab.MessageRegex ?? string.Empty;
|
||||
if (ImGui.InputText($"{HellionStrings.Settings_Tabs_MessageRegex_Name}##regex-{i}", ref regexBuffer, 256))
|
||||
tab.MessageRegex = string.IsNullOrEmpty(regexBuffer) ? null : regexBuffer;
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Settings_Tabs_MessageRegex_Description);
|
||||
|
||||
// Validity feedback: the settings window is not a hot path, so a
|
||||
// per-frame Compile is fine. An invalid pattern shows a red line and
|
||||
// the tab behaves as if no regex were set.
|
||||
if (!string.IsNullOrEmpty(tab.MessageRegex))
|
||||
{
|
||||
var (_, regexError) = RegexRouteMatcher.Compile(tab.MessageRegex);
|
||||
if (regexError is not null)
|
||||
ImGui.TextColored(
|
||||
ImGuiColors.DalamudRed,
|
||||
string.Format(HellionStrings.Settings_Tabs_MessageRegex_Invalid, regexError)
|
||||
);
|
||||
}
|
||||
|
||||
ImGui.Checkbox(Language.Options_Tabs_PopOut, ref tab.PopOut);
|
||||
if (tab.PopOut)
|
||||
{
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace HellionChat._Helpers;
|
||||
|
||||
// UI-8 pure decision helper for the optional per-tab regex filter. Compiling a
|
||||
// user pattern can throw (invalid syntax) and matching a pathological pattern
|
||||
// can hang (catastrophic backtracking), so both are contained here. Kept free
|
||||
// of Dalamud types so the Build Suite can test compile/match/timeout in
|
||||
// isolation.
|
||||
// TEST-MIRROR: ../../../Hellion Build test/Ui/RegexRouteMatcherTests.cs
|
||||
public static class RegexRouteMatcher
|
||||
{
|
||||
// A match must never block the message-routing hot path. 100ms is far
|
||||
// beyond any sane pattern yet short enough that a runaway pattern fails
|
||||
// fast instead of freezing the chat.
|
||||
public static readonly TimeSpan MatchTimeout = TimeSpan.FromMilliseconds(100);
|
||||
|
||||
// Compiles a user pattern. Null/empty pattern -> (null, null): "no filter".
|
||||
// Invalid pattern -> (null, errorMessage): the tab then behaves as if no
|
||||
// regex were set (see IsMatch).
|
||||
public static (Regex? Regex, string? Error) Compile(string? pattern)
|
||||
{
|
||||
if (string.IsNullOrEmpty(pattern))
|
||||
return (null, null);
|
||||
|
||||
try
|
||||
{
|
||||
var regex = new Regex(pattern, RegexOptions.IgnoreCase, MatchTimeout);
|
||||
return (regex, null);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
return (null, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
// Runs the filter. A null regex (no filter, or an invalid pattern) returns
|
||||
// true so nothing is filtered out. A timeout on a pathological pattern is
|
||||
// treated as "no match" rather than a crash.
|
||||
public static bool IsMatch(Regex? regex, string text)
|
||||
{
|
||||
if (regex is null)
|
||||
return true;
|
||||
|
||||
try
|
||||
{
|
||||
return regex.IsMatch(text);
|
||||
}
|
||||
catch (RegexMatchTimeoutException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user