perf: pre-compile regexes in redaction helpers and UI pages
Replace 11 on-demand Regex.Replace/Match calls (in CommandCenterTextHelper, WizardPage, SchemaConfigEditor, and QuickSendDialog) with static readonly pre-compiled Regex fields using RegexOptions.Compiled. Each field is compiled once at startup and reused on every invocation, eliminating the overhead of per-call regex compilation or .NET cache lookups. Affected call sites: - CommandCenterTextHelper.RedactSupportPath: 2 patterns - CommandCenterTextHelper.RedactSupportValue: 6 patterns - WizardPage.Render: 2 patterns (URL detection + device code) - SchemaConfigEditor.GetLabel: 1 pattern (camelCase → label) - QuickSendDialog.TryExtractMissingScope: 1 pattern No behaviour change; all 393 Tray tests and 1219 Shared tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
584a19fadd
commit
ab227a7c1c
@ -17,6 +17,10 @@ public sealed partial class SchemaConfigEditor : UserControl
|
||||
private JsonElement _config;
|
||||
private readonly Dictionary<string, object?> _changes = new();
|
||||
|
||||
private static readonly Regex CamelCaseSplitPattern = new(
|
||||
"([a-z])([A-Z])",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
private static readonly SolidColorBrush SecondaryBrush =
|
||||
new(ColorHelper.FromArgb(255, 140, 150, 170));
|
||||
|
||||
@ -378,7 +382,7 @@ public sealed partial class SchemaConfigEditor : UserControl
|
||||
|
||||
private static string GetLabel(string path, string name)
|
||||
{
|
||||
var result = Regex.Replace(name, "([a-z])([A-Z])", "$1 $2");
|
||||
var result = CamelCaseSplitPattern.Replace(name, "$1 $2");
|
||||
result = result.Replace("_", " ").Replace(".", " \u203A ");
|
||||
// Title-case the first character
|
||||
if (result.Length > 0)
|
||||
|
||||
@ -42,6 +42,9 @@ public sealed class QuickSendDialog : WindowEx
|
||||
uint uFlags);
|
||||
|
||||
private static readonly IntPtr HWND_TOPMOST = new(-1);
|
||||
private static readonly Regex MissingScopePattern = new(
|
||||
@"missing\s+scope\s*:\s*([A-Za-z0-9._-]+)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
private const int SW_SHOWNORMAL = 1;
|
||||
private const uint SWP_NOMOVE = 0x0002;
|
||||
private const uint SWP_NOSIZE = 0x0001;
|
||||
@ -280,7 +283,7 @@ public sealed class QuickSendDialog : WindowEx
|
||||
return false;
|
||||
}
|
||||
|
||||
var match = Regex.Match(message, @"missing\s+scope\s*:\s*([A-Za-z0-9._-]+)", RegexOptions.IgnoreCase);
|
||||
var match = MissingScopePattern.Match(message);
|
||||
if (!match.Success)
|
||||
{
|
||||
return false;
|
||||
|
||||
@ -11,6 +11,48 @@ namespace OpenClawTray.Helpers;
|
||||
|
||||
internal static class CommandCenterTextHelper
|
||||
{
|
||||
// Pre-compiled patterns used in RedactSupportPath / RedactSupportValue.
|
||||
// Compiled once at startup; reused on every diagnostic / support-text build.
|
||||
private static readonly Regex PathWindowsUserPattern = new(
|
||||
@"\b[A-Za-z]:\\Users\\[^\\]+",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
|
||||
private static readonly Regex PathUnixUserPattern = new(
|
||||
@"/Users/[^/]+",
|
||||
RegexOptions.Compiled,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
|
||||
private static readonly Regex ValueUrlHostPattern = new(
|
||||
@"\b[a-z][a-z0-9+.-]*://(?:[^@\s/]+@)?([^:/\s]+)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
|
||||
private static readonly Regex ValueIpPattern = new(
|
||||
@"\b(?:\d{1,3}\.){3}\d{1,3}\b",
|
||||
RegexOptions.Compiled,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
|
||||
private static readonly Regex ValueEmailPattern = new(
|
||||
@"\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
|
||||
private static readonly Regex ValueUserAtHostPattern = new(
|
||||
@"\b(?<user>[A-Za-z0-9._-]+)@(?<host>[A-Za-z0-9._-]+)(?=[:\s]|$)",
|
||||
RegexOptions.Compiled,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
|
||||
private static readonly Regex ValueHostAfterToPattern = new(
|
||||
@"(?<=\bto\s)[A-Za-z0-9._-]+(?=:\d{1,5}\b)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
|
||||
private static readonly Regex ValueLeadingHostPattern = new(
|
||||
@"^\s*[A-Za-z0-9._-]+(?=:\d{1,5}\b)",
|
||||
RegexOptions.Compiled,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
|
||||
internal static string BuildSupportContext(GatewayCommandCenterState state)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
@ -346,19 +388,9 @@ internal static class CommandCenterTextHelper
|
||||
}
|
||||
}
|
||||
|
||||
redacted = Regex.Replace(
|
||||
redacted,
|
||||
@"\b[A-Za-z]:\\Users\\[^\\]+",
|
||||
"%USERPROFILE%",
|
||||
RegexOptions.IgnoreCase,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
redacted = PathWindowsUserPattern.Replace(redacted, "%USERPROFILE%");
|
||||
|
||||
redacted = Regex.Replace(
|
||||
redacted,
|
||||
@"/Users/[^/]+",
|
||||
"$HOME",
|
||||
RegexOptions.None,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
redacted = PathUnixUserPattern.Replace(redacted, "$HOME");
|
||||
|
||||
return redacted;
|
||||
}
|
||||
@ -368,47 +400,19 @@ internal static class CommandCenterTextHelper
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return "unknown";
|
||||
|
||||
var redacted = Regex.Replace(
|
||||
var redacted = ValueUrlHostPattern.Replace(
|
||||
value,
|
||||
@"\b[a-z][a-z0-9+.-]*://(?:[^@\s/]+@)?([^:/\s]+)",
|
||||
match => match.Value.Replace(match.Groups[1].Value, "<host>"),
|
||||
RegexOptions.IgnoreCase,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
match => match.Value.Replace(match.Groups[1].Value, "<host>"));
|
||||
|
||||
redacted = Regex.Replace(
|
||||
redacted,
|
||||
@"\b(?:\d{1,3}\.){3}\d{1,3}\b",
|
||||
"<ip>",
|
||||
RegexOptions.None,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
redacted = ValueIpPattern.Replace(redacted, "<ip>");
|
||||
|
||||
redacted = Regex.Replace(
|
||||
redacted,
|
||||
@"\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b",
|
||||
"<email>",
|
||||
RegexOptions.IgnoreCase,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
redacted = ValueEmailPattern.Replace(redacted, "<email>");
|
||||
|
||||
redacted = Regex.Replace(
|
||||
redacted,
|
||||
@"\b(?<user>[A-Za-z0-9._-]+)@(?<host>[A-Za-z0-9._-]+)(?=[:\s]|$)",
|
||||
"<user>@<host>",
|
||||
RegexOptions.None,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
redacted = ValueUserAtHostPattern.Replace(redacted, "<user>@<host>");
|
||||
|
||||
redacted = Regex.Replace(
|
||||
redacted,
|
||||
@"(?<=\bto\s)[A-Za-z0-9._-]+(?=:\d{1,5}\b)",
|
||||
"<host>",
|
||||
RegexOptions.IgnoreCase,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
redacted = ValueHostAfterToPattern.Replace(redacted, "<host>");
|
||||
|
||||
redacted = Regex.Replace(
|
||||
redacted,
|
||||
@"^\s*[A-Za-z0-9._-]+(?=:\d{1,5}\b)",
|
||||
"<host>",
|
||||
RegexOptions.None,
|
||||
TimeSpan.FromMilliseconds(100));
|
||||
redacted = ValueLeadingHostPattern.Replace(redacted, "<host>");
|
||||
|
||||
return redacted;
|
||||
}
|
||||
|
||||
@ -18,6 +18,13 @@ namespace OpenClawTray.Onboarding.Pages;
|
||||
/// </summary>
|
||||
public sealed class WizardPage : Component<OnboardingState>
|
||||
{
|
||||
private static readonly Regex UrlInMessagePattern = new(
|
||||
@"(https?://[^\s\)\"",]+)",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex DeviceCodePattern = new(
|
||||
@"(?:^|\s)(?:[Cc]ode|user_code|USER_CODE)\s*[:=]\s*([A-Z0-9]{2,8}(?:-[A-Z0-9]{2,8})+|[A-Z0-9]{4,12})\b",
|
||||
RegexOptions.Compiled);
|
||||
public override Element Render()
|
||||
{
|
||||
// Read persisted wizard state from shared OnboardingState
|
||||
@ -523,7 +530,7 @@ public sealed class WizardPage : Component<OnboardingState>
|
||||
if (!string.IsNullOrEmpty(displayMessage))
|
||||
{
|
||||
// URL detection — find https:// URLs in the message
|
||||
var urlMatch = Regex.Match(displayMessage, @"(https?://[^\s\)\"",]+)");
|
||||
var urlMatch = UrlInMessagePattern.Match(displayMessage);
|
||||
if (urlMatch.Success)
|
||||
{
|
||||
var detectedUrl = urlMatch.Value;
|
||||
@ -545,9 +552,7 @@ public sealed class WizardPage : Component<OnboardingState>
|
||||
// Capture must contain a digit or hyphen (or be all uppercase) to avoid
|
||||
// matching common English words like "below" that follow "code".
|
||||
// Case-sensitive on the value to require the GitHub-style uppercase code.
|
||||
var codeMatch = Regex.Match(
|
||||
displayMessage,
|
||||
@"(?:^|\s)(?:[Cc]ode|user_code|USER_CODE)\s*[:=]\s*([A-Z0-9]{2,8}(?:-[A-Z0-9]{2,8})+|[A-Z0-9]{4,12})\b");
|
||||
var codeMatch = DeviceCodePattern.Match(displayMessage);
|
||||
if (codeMatch.Success)
|
||||
{
|
||||
var code = codeMatch.Groups[1].Value;
|
||||
@ -581,7 +586,7 @@ public sealed class WizardPage : Component<OnboardingState>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(displayMessage))
|
||||
{
|
||||
var urlMatch = Regex.Match(displayMessage, @"(https?://[^\s\)\"",]+)");
|
||||
var urlMatch = UrlInMessagePattern.Match(displayMessage);
|
||||
if (urlMatch.Success)
|
||||
{
|
||||
try
|
||||
|
||||
Loading…
Reference in New Issue
Block a user