#### Brief overview of PR changes/additions
- `match_exact_match()` did `QString text = haystack;` then chopped a
trailing newline. The assignment is copy-on-write and cheap, but
`chop()` mutates, forcing the detach: a heap allocation plus a full
character copy of the line.
- The newline is always present - `TMainConsole::runTriggers()` appends
one to every line before dispatch (`TMainConsole.cpp:1570`) - so the
chop always fires and the copy always happens, once per exact-match
pattern, per reachable trigger, per line of game text.
- Use a `QStringView`. Chopping a view moves only its own end pointer,
so nothing is allocated, and the comparison against the needle is
unchanged. Both chop one UTF-16 code unit, so behaviour is identical for
every input.
#### Motivation for adding to Mudlet
Removes a per-line heap allocation and line copy from the trigger
matching path, which runs for every line of game text.
#### Other info (issues closed, discussion etc)
Measured with `PipelineBenchmark` on macOS/arm64, Release,
`-DUSE_SANITIZER=""`, two binaries from one toolchain run in 24
interleaved ABBA pairs so run-order effects cancel: trigger throughput
**+3.28%** (t=6.41), trigger overhead **-6.37%** (t=-4.94). The
text-only control moved +0.23% (t=-0.56, 12/24 paired wins), i.e. no
effect, which is the check that the trigger deltas are real.
Worth stating plainly: the stock benchmark corpus contains **no**
exact-match patterns, so `match_exact_match()` is never entered by it.
Twelve were added locally purely to measure this. The gain therefore
scales with how many exact-match patterns a profile actually has, and is
zero for a profile with none.
**Test case:** behaviour-neutral, so the checks are for regressions
around where the chop lands. Exact-match triggers fire correctly on a
plain ASCII line, on a line with an accented character, on one with an
em dash, and on one containing an emoji (a surrogate pair, i.e. two
UTF-16 code units - the case most likely to expose a code-unit-based
chop). A line with trailing whitespace before the newline correctly does
*not* match. Verified against before/after builds on macOS, plus 5
trigger functional test suites.
---------
Signed-off-by: Jay Howard <jay.patrick.howard@gmail.com>
#### Brief overview of PR changes/additions
- `match_perl()` called `pcre2_match_data_create_from_pattern()` +
`pcre2_match_data_free()` on every call: one heap allocation and free
per Perl-regex pattern, per reachable trigger, per line of game text.
- Cache one `match_data` per compiled pattern and reuse it across lines,
in a `QSharedPointer` with a deleter to match how `mRegexMap` already
owns its `pcre2_code`. Cleared alongside `mRegexMap` when patterns are
recompiled, since each block is sized from its pattern's capture count.
- Reuse is safe under re-entrancy: `processRegexMatch()` copies every
capture out of the ovector into `captureList` before calling
`execute()`, so a script that calls `feedTriggers()` and re-enters
`match_perl()` on the same pattern cannot clobber an ovector still being
read.
#### Motivation for adding to Mudlet
This is a regression, not a missed optimisation: before #8533 migrated
the regex engine to PCRE2 the match results went into a stack array
(`int ovector[MAX_CAPTURE_GROUPS]`), and the per-call heap allocation
arrived unnoticed with PCRE2's API style.
#### Other info (issues closed, discussion etc)
Measured with `PipelineBenchmark` on macOS/arm64, Release,
`-DUSE_SANITIZER=""`, using two binaries from one toolchain run in 24
interleaved ABBA pairs (alternating order, so run-order effects cancel):
trigger throughput **+3.11%** (t=3.00), trigger overhead **-5.51%**
(t=-2.13). The text-only control moved -0.92% (t=-0.26, 12/24 paired
wins) i.e. no effect, which is the check that the trigger deltas are
real. ASan must be off for this measurement since it instruments every
malloc.
**Test case:** behaviour-neutral, so the checks are for regressions.
Perl-regex triggers with 2 and 3 capture groups firing on one line both
return correct captures; editing a saved trigger's pattern to one with a
different capture count then re-firing still returns correct captures
(exercises cache invalidation); a trigger whose script calls
`feedTriggers()` into a second Perl trigger still fires correctly
(exercises re-entrancy). Verified against before/after builds on macOS,
plus 6 trigger functional test suites.
---------
Signed-off-by: Jay Howard <jay.patrick.howard@gmail.com>
#### Brief overview of PR changes/additions
- One budget of 100 covered every root trigger created while a line was
processed, and tripping it deactivated all of them, so a script arming
101 unrelated triggers lost all 101.
- Triggers created mid-line now carry the creation lineage they belong
to and how many generations deep they sit in it. A batch is one
generation however big it is; only a trigger that re-creates itself
keeps adding generations, so that is the only shape the budget counts.
The limit is 1000 generations, and only the runaway lineage is stopped
and named.
- Generations do not bound a lineage that widens as it deepens, so past
20000 creations on one line new triggers stop being offered that line.
Nothing is disowned there - they are all still armed for the lines that
follow.
#### Motivation for adding to Mudlet
#9697 fixed a real freeze, but its counter had no lineage, so it
destroyed legitimate triggers along with the runaway. Any routine arming
more than 100 triggers from a trigger loses them, permanent ones
included, which is a regression against 4.22.0.
#### Other info (issues closed, discussion etc)
Fixes a regression introduced by merged #9697; release-blocking for 5.0.
Test case: `lua fired=0; tempTrigger("GATE", function() for i=1,200 do
tempTrigger("PAY", function() fired=fired+1 end) end end);
feedTriggers("\nGATE\n"); feedTriggers("\nPAY\n"); print(fired)` -
prints 0 before, 200 after.
A/B against a shipped 4.22.0 binary (4.22.0 / 5.0 RC / this PR), counted
on the line after the one that armed them: 1001 unrelated temp triggers
1001 / 0 / 1001; two scripts of 600 each 600+600 / 0+0 / 600+600; 1001
permanent triggers 1001 / 0 / 1001. #9697's freeze is still stopped and
bounded (601 runaway lines in 8 s, RSS flat at ~820 MB) and its own
tests still pass. Eleven new tests parameterise the creation count, and
cover nested passes and folder/filter-chain children, which none of
#9697's did - that is why this shipped.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- Comment-only. `git diff origin/development...HEAD` changes no
statement, expression or declaration - every added and removed line is a
comment. 238 comment lines become 98.
- Applies the house standard to the comments added by "fix: a trigger
that re-creates itself freezes Mudlet" (#9697) and "Fix user key
bindings on Ctrl+1 to Ctrl+9 and Ctrl+Tab" (#9703): no historical
passages, and the rest cut to what a reader cannot derive from the code.
- Corrects four claims that were wrong, two of them inherited from those
PRs: a fires-per-line measurement taken with a smaller budget than the
one that shipped, an over-general note on `shortcutInstalledFor()`, a
`KeyUnit::disableKey()` note that had the mechanism backwards, and a
test comment crediting the `isEmpty()` guard for a result it does not
produce.
#### Motivation for adding to Mudlet
Both PRs merged while their comment-reduction pass was still in flight,
so the trim never landed with them.
#### Other info (issues closed, discussion etc)
The gotchas worth keeping survive in shorter form: why the same-line
creation budget is counted per pass rather than sharing the
`feedTriggers()` depth counter, why permanent triggers get
`deactivate()` and not `setIsActive(false)`, why `mCleanupSet` rather
than the deactivation is what stops `enableTrigger()` resurrecting a
spent trigger, that `QShortcutMap` retries with consumed modifiers
stripped, and the `Key_Backtab` versus `Shift+Tab` spelling.
The matching trim for "fix: stop treating long-time Mudlet users as
brand new players" (#9695) already landed separately as #9707, so it is
not repeated here.
No demo video: a comment-only change is not observable on screen.
**Test case:** `ctest` in the build directory - 79/80, with
`TelnetBenchmark` timing out only under parallel load (31s standalone
against a 60s limit) on a path this PR does not touch.
`TriggerSameLineMatchTest`, `UnitDeferredDeleteTest`,
`ProfileSwitchShortcutTest` and `ExperiencedPlayerGateTest` all pass.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- A trigger whose script creates another trigger matching the same line
kept extending the list `TriggerUnit::processDataStream()` walks, so the
line never finished: 100% CPU and RSS climbing 1.7 GB to 5.9 GB in 44
seconds, from one ordinary line of game text. Same-line matching for
triggers created mid-pass now has a budget (100 per line); when it runs
out the offending trigger is named in an error and what the loop created
during that line is stopped - temporary ones removed, permanent ones
switched off for the session only, so nothing is saved to the profile.
- `enableTrigger()` could resurrect a killed or expired temporary
trigger during the window before its deferred delete runs, so a one-shot
fired twice and a `killTrigger()`ed trigger fired 49 more times. It now
skips anything queued for cleanup, which is what makes the guarantee
`TTrigger::match()` states actually true.
- The behaviour restored by #9458 ("fix: triggers created by other
triggers react to the current line again") is kept: triggers created
while a line is being processed still match that line, chained creation
included. 10 new tests, and the 6 that pin that behaviour still pass.
#### Motivation for adding to Mudlet
Release blocker for 5.0 - the freeze is reachable from ordinary server
text with the standard "one-shot trigger that re-arms itself" idiom, and
4.22.0 was not affected.
#### Other info (issues closed, discussion etc)
5.0 QA findings C11 (hang) and C12. C11 was introduced by eb2627383
(#9458), which deliberately restored pre-#9267 same-line semantics
without bounding them; #9368's depth guard cannot see it, because
nothing recurses. The budget is deliberately its own constant rather
than the `feedTriggers()` recursion depth: the two measure different
resources, and sharing one made a pass entered deep in nested
`feedTriggers()` abort before running anything.
**Test case:** run `function arm() tempRegexTrigger("^HP: 100/100$",
[[arm()]], 1) end arm()` then `feedTriggers("HP: 100/100\n")` - on
development Mudlet freezes for good; here it reports the trigger and
carries on.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- An expired trigger is deactivated before it is queued for deletion, so
a nested `feedTriggers()` pass cannot fire it again while the deferred
delete is still pending.
- Deleting a temporary trigger, alias, key or timer unlinks only that
item from the by-name lookup table instead of every item filed under the
same name, and `killAlias()`/`killKey()`/`killTimer()` scan past a
same-named item they cannot kill rather than report failure over it.
- `AliasUnit` and `KeyUnit` gain the double-free guards `TriggerUnit`
and `TimerUnit` already had; `stopAllNamedTriggers()` and
`IDMgr:emergencyStop()` now stop named regex triggers too.
#### Motivation for adding to Mudlet
The four lookup tables are `QMultiMap`s, so names are not unique, but
the temporary-item branch used the single-argument `remove(key)` and
evicted live same-named items with it: a permanent trigger could stay
alive yet become invisible to `enableTrigger()`, `killTrigger()` and
`exists()` for the rest of the session. The kill-by-name asymmetry is
the same defect one level up - a permanent item restored from the
profile precedes this session's temporaries in the root node list, so it
stranded the temporary behind it.
#### Other info (issues closed, discussion etc)
Closes#9646, closes#9648, closes#9649, closes#9650
Test case: `permRegexTrigger("Health", "", {"^permanent$"},
[[echo("permanent fired\n")]])`, then `tempComplexRegexTrigger("Health",
"^temp$", [[]], 0,0,0,0,0,0,0,0,0,0)`, `killTrigger("Health")` and
`feedTriggers("permanent\n")` - `exists("Health", "trigger")` still
finds the permanent trigger.
New coverage: `test/functional_tests/UnitDeferredDeleteTest.cpp` (17
cases across all four units) plus additions to `Trigger_spec.lua`,
`Alias_spec.lua`, `KeyBinds_spec.lua` and `IDManager_spec.lua`, three of
which were `pending()` markers for these bugs.
Review turned up an adjacent defect deliberately **not** fixed here:
expiry is accounted for after `execute()` runs, so a trigger whose *own*
script re-feeds the matching line overshoots its `expireAfter`. Fixing
that means moving the expiry accounting ahead of `execute()` while
keeping the "return true to extend" contract, so it is left for a
follow-up and recorded as a `pending()` spec in `Trigger_spec.lua`.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- Script error banners in the editor now use the theme's text colour
instead of hardcoded blue, making them readable in dark mode
- Error text returned to scripts via getError() no longer contains HTML
font markup
#### Motivation for adding to Mudlet
Blue-on-dark error text in the editor banner was nearly impossible to
read in dark mode.
#### Other info (issues closed, discussion etc)
Assisted-by: Claude:claude-fable-5
**Test case:** In dark mode, create a trigger with script `+` and save -
the Lua syntax error in the banner is clearly readable (light text on
the dark banner).
#### Demo
https://github.com/user-attachments/assets/ec917ad4-afdf-4dac-a11b-4231e285c007
#### Brief overview of PR changes/additions
- `setScript()` on a temp trigger/timer/alias/key that was created with
a function callback (`tempTrigger`/`tempTimer`/`tempAlias`/`tempKey`
with a function argument) now releases that callback: it clears the
registered-function flag and removes the function from the Lua registry
before switching to the new script string.
- Without this, such an item would keep executing the stale callback so
the new script never runs (triggers/aliases/keys), and the old function
would never be freed from the Lua registry (all four types).
- Adds a functional test (`SetScriptCallbackTest`) covering all four
types, plus the empty-script and non-callback (no-op) cases.
#### Motivation for adding to Mudlet
Keeps a temp item's execution state and cleanup consistent when its
script is replaced. No current scripting API or editor path replaces a
temp item's script, so this closes the gap before anything can reach it.
#### Other info (issues closed, discussion etc)
A temp item's function callback is stored in the Lua registry keyed by
the item pointer; `setScript()` previously left both that entry and the
`mRegisteredAnonymousLuaFunction` flag untouched. Because each
destructor picks its cleanup branch from `mScript.isEmpty()`, once a
non-empty script was set the pointer-keyed entry was orphaned. `TAction`
(buttons) has no function-callback path and is unaffected. Behaviour of
every existing caller is unchanged, covered by the no-op guard case.
**Test case:**
```
run: flock /tmp/mudlet-functional-tests.lock ctest --output-on-failure -R SetScriptCallbackTest
```
All 6 sub-tests pass with the fix; 5 of 6 fail against the pre-fix
baseline (the non-callback no-op case passes by design, guarding that
existing behaviour is unchanged). Related suites
`EnableDisableByNameTest` and `TFeedTriggersRecursionTest` still pass.
Assisted-by: Claude:claude-opus-4-8
#### Brief overview of PR changes/additions
- Color triggers now match against a line's colors as they arrived from
the game, so a highlight/colorizer trigger earlier in the list no longer
breaks color triggers below it
- The line's original colors are kept for the duration of the trigger
pass (zero-copy: the already-built line is moved instead of discarded);
the display and scripts still see the highlighted colors
- Nested feedTriggers() passes each get their own snapshot; adds busted
tests covering both trigger orders and nesting
#### Motivation for adding to Mudlet
Trigger behavior should not depend on whether an unrelated highlight
trigger happens to sit above a color trigger.
#### Other info (issues closed, discussion etc)
Fixes#9357
**Test case:** Create a highlight trigger matching some text, and below
it a color trigger matching that text's colors (e.g. white on black).
`lua feedTriggers("\27[37;40mtext\27[0m\n")` - both fire: the line shows
the highlight and the color trigger matches. Previously the color
trigger stayed silent.
https://github.com/user-attachments/assets/a919f8ac-c843-4772-a6aa-eb77a73cadac
Signed-off-by: Vadim Peretokin <vadim.peretokin@mudlet.org>
#### Brief overview of PR changes/additions
- A color-pattern trigger nested under an "only pass matches" (filter)
parent could never fire: the filter ran children with no buffer line to
read colors from
- The real line number is now passed through, and the color scan is
limited to the parent's matched text so filter semantics hold; top-level
color triggers behave exactly as before
#### Motivation for adding to Mudlet
Filter chains are the standard way to scope triggers, and color patterns
silently did nothing inside them.
#### Other info (issues closed, discussion etc)
Fixes#8198
Known limitation: children of *multiline* filter parents still can't use
color patterns (a multiline match spans several lines, so no single line
applies).
**Test case:** create a perl-regex trigger `^(hello world)$` with "only
pass matches" ticked, nest a color trigger (foreground ANSI yellow)
under it with `echo("CHILD FIRED\n")`, then `lua
feedTriggers("\27[33mhello world\27[0m\n")` - the child fires. Covered
by the new ColorTriggerFilterChildTest (ctest).
https://github.com/user-attachments/assets/011d8e7f-f428-4727-b38c-51031202f35d
Signed-off-by: Vadim Peretokin <vadim.peretokin@mudlet.org>
<!-- Keep the title short & concise so anyone non-technical can
understand it,
the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Remove unnecessary else/else-if after return, break, continue, and throw
statements - and in places where fixing them is more trouble than its
worth, added NOLINT.
#### Motivation for adding to Mudlet
https://clang.llvm.org/extra/clang-tidy/checks/readability/else-after-return.html,
so it doesn't pop up in PR reviews.
#### Other info (issues closed, discussion etc)
---------
Co-authored-by: Stephen Lyons <slysven@virginmedia.com>
Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
#### Brief overview of PR changes/additions
Abort with a Lua error (naming the trigger) when trigger processing
recurses past a depth limit, instead of overflowing the stack. Covers
both `feedTriggers()` and `feedTelnet()` loops, each with a limit sized
to its stack usage.
#### Motivation for adding to Mudlet
A trigger whose action calls `feedTriggers()` with text that re-matches
it recurses the C++ stack until Mudlet hard-crashes (Sentry `fbda193d`,
`EXCEPTION_STACK_OVERFLOW`); this turns that into a clear, recoverable
script error. Review found `feedTelnet()` crashes the same way, so it
gets the same treatment.
#### Other info (issues closed, discussion etc)
Crash seen on Windows in Sentry. The same loop on Linux/macOS only
reaches Lua's own `C stack overflow` guard (~200 nested C calls) -
cryptic, but no crash; Windows' smaller ~1 MB stack dies first. The
fix's depth limit (50) trips before either, so every platform gets the
same clear, named error.
`feedTelnet()` needs a much lower limit (5): each nested telnet
processing level holds ~100KB of stack buffers, so a 1MB stack dies
after ~8 levels. Fixing this also surfaced a reentrancy bug where nested
`feedTelnet()` re-posted the ancestors' pending `mMudData`, duplicating
output lines and abort errors; `postData()` now detaches the data before
posting.
**Test case:**
1. New regex trigger: pattern `^loopme$`, script
`feedTriggers("loopme\n")`.
2. Run `feedTriggers("loopme\n")`.
3. Before: crash (Windows) / `<C stack overflow>` (Linux). After: it
stops with an error naming the trigger and stays running.
4. Same with `feedTelnet("loopme\n")` in the trigger and prompt (on a
disconnected profile): stops with a single error instead of crashing.
---------
Signed-off-by: Vadim Peretokin <vperetokin@hey.com>
### Refactor: replace raw pointer ownership with smart pointers across
core subsystems
#### Brief overview of PR changes/additions
Replaces raw pointer ownership patterns with `std::unique_ptr` and
`std::map`
across several core subsystems:
- **Host**: `mStopWatchMap` (`QMap<int, stopWatch*>` → `std::map<int,
unique_ptr<stopWatch>>`), `profileShortcuts` (`QMap<QString,
QKeySequence*>` → `std::map<QString, unique_ptr<QKeySequence>>`).
Removes `qDeleteAll` in destructor and `delete mMMCPServer`.
- **TMap**: `mpRoomDB` raw pointer → `unique_ptr`
- **VarUnit**: `base` raw pointer → `unique_ptr`
- **TTrigger**: condition map storage converted to `unique_ptr`,
destructor simplified
- **discord**: handler and presence maps converted from raw pointer
`QMap` to `unique_ptr` + `std::map`
- **Updater**: `mFeed` and `mUpdateDialog` converted to `unique_ptr`
#### Motivation for adding to Mudlet
These patterns were identified as sources of memory leaks and potential
use-after-free bugs. Using smart pointers makes ownership explicit,
eliminates manual cleanup code, and ensures correct destruction even on
early-exit paths.
#### Other info (issues closed, discussion etc)
sorry this one is still pretty big, but most of the changes are the same
for each thing so reviewing them together probably makes sense. sadly
there isn't much to see here other than no slow uptick of heap size :-[
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
#### Brief overview of PR changes/additions
One of a series of PRs, each addressing a type of issue reported by
Clazy.
This is the: "the 'inline' keyword is specified on the definition, but
not the declaration. This could lead to hard-to-suppress warnings with
some compilers (e.g. MinGW). The 'inline' keyword should be used for the
declaration only. [clazy-sanitize-inline-keyword]" one.
#### Motivation for adding to Mudlet
Remove warnings detected by the Clazy tool - either when explicitly run
on the Mudlet code-base or detected by the background scanner/analyser
that Qt Creator offers.
#### Other info (issues closed, discussion etc)
A summary I found for this is:
> This can lead to compiler warnings, and the recommended fix is to add
the inline keyword to the declaration in-class.
In some cases we had `static inline` and others `inline static` and the
inconsistency made it hard to spot all of them. This PR converts the
latter to the former.
Also add a missing `inline` to `(void) TTrigger::filter(std::string&,
int&)` in the declaration in the header file after it was removed from
the definition in the class file.
Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
#### Brief overview of PR changes/additions
Use `mInstalledModules` instead of `mModuleInfo` to distinguish modules
from packages in `packageName()`/`moduleName()` across all 6 item types,
since `mModuleInfo` is only populated for modules with a `config.lua`
that sets `mpackage`.
#### Motivation for adding to Mudlet
Modules were incorrectly showing the "This item is part of a package"
warning when clicking on their items in the editor.
#### Other info (issues closed, discussion etc)
Root cause traced back to PR #7411 - the PR review had suggested using
`mInstalledModules` but `mModuleInfo` was used instead, which is only
populated for modules that have a `config.lua` with `mpackage` set.
**Test case:** Install a module (especially a plain XML one without
config.lua), click on one of its items in the script editor - the
package warning banner should no longer appear.
<!-- Keep the title short & concise so anyone non-technical can
understand it,
the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
selectCaptureGroup used indexOf to find capture group positions, which
returned the
first occurrence of the captured text in the line instead of the actual
match position. Replace with
direct byte-offset-to-UTF-16 conversion from the PCRE2 ovector.
#### Motivation for adding to Mudlet
Fix: https://github.com/Mudlet/Mudlet/issues/8912, fix
https://github.com/Mudlet/Mudlet/issues/3016
#### Other info (issues closed, discussion etc)
---------
Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
#### Brief overview of PR changes/additions
Removes the `*`s in all but the first line of a multi-line translation
comment that explains to the translators details of the Engineering
English text in the source code. Whilst beginning each new line with an
`*` can happen automagically in the Qt Creator as a result of the
"Enable Doxygen Blocks" and "Add leading asterisks" options (in
"Preferences" -> "Text Editor" -> "Documentation Comments") these are
not always stripped out by the `lupdate` utility that generates the
`mudlet.ts` file.
Removes remaining `""` and replaces with `nullptr` any second arguments
to `QObject::tr(...)` where a third argument is needed for the quantity
for "numerus" (quantity dependent) translatable texts.
Removes some, now obsolete, Windows specific code that identified if a
32-Bit version of Mudlet was being run in a 64-Bit Operating System.
Since we no longer can produce such 32-Bit code it is now just cruft.
Revises some quantity dependent code in `./src/dlgPackManager.cpp` so
that only the zero cases are handled differently - in one place this
means the conversion of a `.length() > 0` to an inverted `.isEmpty()` -
and in three other places where a zero or one case was handled
differently to the more than one case.
#### Motivation for adding to Mudlet
Improve the code quality and/or the situation for our translators.
#### Other info (issues closed, discussion etc)
Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
#### Brief overview of PR changes/additions
Change `mIsProcessing` from bool to counter (`mProcessingDepth`) in
AliasUnit, TriggerUnit, and KeyUnit to properly handle nested Lua script
calls via `expandAlias()`/`send()`.
#### Motivation for adding to Mudlet
Fixes crash caused by cleanup running during nested processing when
inner call sets `mIsProcessing = false`.
#### Other info (issues closed, discussion etc)
Fixes#8817
**Test case:** Create an alias that calls `expandAlias()` which triggers
another alias that calls `killAlias()` on itself - should no longer
crash.
---------
Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
#### Brief overview of PR changes/additions
Ran clang-format on all 134 CPP files in src/ using the project's
.clang-format config
#### Motivation for adding to Mudlet
Ensures consistent code formatting across the codebase.
#### Other info (issues closed, discussion etc)
None
**Test case:** Build the project and verify it compiles successfully.
<!-- Keep the title short & concise so anyone non-technical can
understand it,
the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Regex triggers now respect 'keep' background color setting, instead of
forcing a black background.
#### Motivation for adding to Mudlet
Fix#6768
#### Other info (issues closed, discussion etc)
Co-authored-by: Claude <noreply@anthropic.com>
#### Brief overview of PR changes/additions
Remove unnecessary malloc/free in filter trigger matching by using
std::string's internal buffer directly.
#### Motivation for adding to Mudlet
Each filter trigger match was allocating a buffer with 2KB of extra
padding, copying the captured text, then freeing it. This adds overhead
for profiles with many filter triggers.
#### Other info (issues closed, discussion etc)
**Test case:**
1. Create a parent trigger with a filter child trigger
2. Verify filter triggers still match and fire correctly
3. For stress testing: rapid text with filter triggers should show
reduced memory churn
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
#### Brief overview of PR changes/additions
Enable PCRE2 JIT (Just-In-Time) compilation for regex patterns in
triggers and aliases.
#### Motivation for adding to Mudlet
JIT compilation can provide 2-10x faster regex matching. Benefits users
with many regex-based triggers, especially large profiles with thousands
of triggers.
#### Other info (issues closed, discussion etc)
JIT failure is non-fatal - PCRE2 falls back to the interpreter
automatically.
**Test case:**
1. Create a trigger with a Perl regex pattern (e.g., `^You have (\d+)
gold`)
2. Connect to a MUD and observe trigger matching works as before
3. For performance testing: create 10000+ regex triggers and compare CPU
usage when receiving rapid text
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Fixes named capture groups being placed in wrong regex index in
`multimatches` when multiline triggers use special line types (like line
spacers)
- Root cause: `nameCaptures` vector not staying synchronized with
`multiCaptureList`
## Changes
1. `match_line_spacer()`: Add empty `nameCaptures` entry when line
spacer matches
2. `updateMultistates()`: Add else clause to push empty `nameCaptures`
when `nameMatches` is null for non-first pattern conditions
## Test plan
- [x] Build succeeds
- [x] All 18 unit tests pass
- [x] Manual test with reproduction trigger from issue:
- Create multiline trigger with: regex pattern, line spacer, regex with
named capture
- Verify named captures appear in correct `multimatches[n]` index
Fixes#7989🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
#### Brief overview of PR changes/additions
Replace C-style string copy functions (`strcpy`/`strncpy`) with safer
`memcpy` + explicit null termination across 5 files. Adds null checks
for memory allocation failures.
#### Motivation for adding to Mudlet
Follows [curl's security
guidance](https://daniel.haxx.se/blog/2025/12/29/no-strcpy-either/) on
eliminating unsafe string functions. Also fixes a latent bug in
`TAlias.cpp` where a dangling pointer could occur from temporary object
lifetimes.
#### Other info (issues closed, discussion etc)
**Test case:** Verify triggers, aliases, and Discord Rich Presence still
work:
1. Create a regex alias (e.g. `^test (.+)$`) and confirm it captures
groups correctly
2. Create a trigger with a filter chain and confirm child triggers match
3. If Discord integration is enabled, verify game status displays in
Discord
No functional changes intended - this is a defensive code quality
improvement.
---------
Co-authored-by: Claude <noreply@anthropic.com>
<!-- Keep the title short & concise so anyone non-technical can
understand it,
the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Replace the deprecated PCRE library with its successor PCRE2 for all C++
regex operations in triggers and aliases. This modernizes the regex
engine while maintaining full API compatibility with existing Lua
scripts.
#### Motivation for adding to Mudlet
PCRE2 provides better Unicode support, improved performance, and is
actively maintained, while PCRE (version 1) is end-of-life.
#### Other info (issues closed, discussion etc)
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
Co-authored-by: Stephen Lyons <slysven@virginmedia.com>
<!-- Keep the title short & concise so anyone non-technical can
understand it,
the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Remove support for leak detection using MSVC - this functionality hasn't
been in use in a decade, and we're switching to to using
[LeakSanitizer](https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer)
instead.
#### Motivation for adding to Mudlet
Cleaner code.
#### Other info (issues closed, discussion etc)
---------
Co-authored-by: Claude <noreply@anthropic.com>
<!-- Keep the title short & concise so anyone non-technical can
understand it,
the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Move all variable initializers to header classes
#### Motivation for adding to Mudlet
Cleaner code, and a better example for LLMs to follow when generating
code
#### Other info (issues closed, discussion etc)
No functional changed are expected
---------
Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
Co-authored-by: Kebap <kebap_spam@gmx.net>
#### Brief overview of PR changes/additions
Removes dead code and puts other fragments behind appropriate `#if
defined` to remove unused code warnings.
Refactor some new-ish constant class initialisations to the header file,
to remove warnings about them not being initialised in the correct order
(and also add a `m` prefix).
#### Motivation for adding to Mudlet
To eliminate build warnings.
#### Other info (issues closed, discussion etc)
Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
#### Brief overview of PR changes/additions
This is meant to solve issue #7723. In order to allow triggers, aliases,
and scripts named "New trigger", "New alias", and "New script" to be
disabled, I added new variables and functions in the TTrigger, TAlias,
and TScript classes. These functions allow one to see whether the most
recent save was the "save" that created the trigger/alias/script; if it
is, then while saving, the variable is changed.
#### Motivation for adding to Mudlet
#### Other info (issues closed, discussion etc)
This is my second pull request. Please let me know if there is any way I
can improve it.
#### Brief overview of PR changes/additions
uses suggested code from https://github.com/Mudlet/Mudlet/issues/7937
replaces unbounded QString with a bounded QString::fromUtf8
#### Motivation for adding to Mudlet
see above issue, address sanitizer detected a problem
#### Other info (issues closed, discussion etc)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Fix compile errors in Lua patterns message box which currently shows
HTML tags in it
#### Motivation for adding to Mudlet
fix#7583
#### Other info (issues closed, discussion etc)
We want to escape the user text which might have less than signs and
whatnot, but not escape the reason we have produced which has html in
it.

<!-- Keep the title short & concise so anyone non-technical can
understand it,
the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
`matches[]` in temp*Trigger functions not always being populated as it
should
#### Motivation for adding to Mudlet
High importance bugfix
#### Other info (issues closed, discussion etc)
This reverts commit cac74ca594.
Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
<!-- Keep the title short & concise so anyone non-technical can
understand it,
the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Show a warning if a item in a package has been edited, letting players
know that they should copy it out of the package first.
#### Motivation for adding to Mudlet
Fix https://github.com/Mudlet/Mudlet/issues/7199, lots of players lose
their changes when editing the generic_mapper for example
#### Other info (issues closed, discussion etc)
---------
Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
Co-authored-by: Stephen Lyons <slysven@virginmedia.com>
<!-- Keep the title short & concise so anyone non-technical can
understand it,
the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
* no longer show an informational message when an item is activated or
deactivated - the checkbox status is enough, and it less visually noisy
this way
* no longer show an error right when you create a trigger - since you've
just created it, the trigger can't have any patterns
* reduce the visual noise with error message formatting - they were
bolded, coloured, and italicized all at once
#### Motivation for adding to Mudlet
A nice look'n'feel to the trigger editor
#### Other info (issues closed, discussion etc)
This is how it looked like before:
[before.webm](https://github.com/user-attachments/assets/e9ba1a50-30a0-4fff-b27b-c7275a32106a)
Now:
[after.webm](https://github.com/user-attachments/assets/7b80f5f9-2c5a-49d2-bfcb-dfa4c20080e4)
---------
Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
Co-authored-by: Zooka <136661366+ZookaOnGit@users.noreply.github.com>
Co-authored-by: Stephen Lyons <slysven@virginmedia.com>
<!-- Keep the title short & concise so anyone non-technical can
understand it,
the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Add: optional 'multiline' parameter to perm*Trigger functions
#### Motivation for adding to Mudlet
This enables the user to have control over the differences in behaviour.
At the moment, triggers created at the root level automatically get
'multiline' enabled and triggers below root level don't get it enabled,
and the player has no choice in this matter.
#### Other info (issues closed, discussion etc)
Fixes https://github.com/Mudlet/Mudlet/issues/7480
---------
Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
Co-authored-by: Stephen Lyons <slysven@virginmedia.com>
#### Brief overview of PR changes/additions
Simplify and improve the "trigger" editor part of the "Editor" by
combining/removing some group boxes and making a third one not have a
check-box. Also move them all from the bottom of the left to the right
side of that area and improve the behaviour as the splitter for the
right side is adjusted.
#### Motivation for adding to Mudlet
Improving the UI - helping to make the controls a bit more intuitive.
#### Other info (issues closed, discussion etc)
Having a QSpinBox to control the "condition line" delta for
AND/Multi-line triggers as well as placing it in a checkable group-box
to enable it is more complex than it needs to be. This PR removes the
checkbox and instead uses the ability of a `QComboBox` to have a
"special value" which it presents different - in this case to report the
trigger as an OR/Multi-item type trigger. Having done this I realised
that it and the other two items at the bottom of the left side of the
"triggers" editor could be simplified further and all moved to the right
side. Then I had to tweak the code that showed/hid the right and bottom
widgets (since the bottom ones had gone). Finally I put in code to
disable the "OR multi-item"/"AND multi-line" control when there is less
than two "items" in the trigger.
Also:
* rename `(QWidget*) dlgTriggerEditor::HpatternList` ==>
`dlgTriggerEditor::mpWidget_triggerItems`
* convert the - not used as a button - `pushButton_prompt` from a
`QPushButton` to a `QLabel` - and make it be enabled when GA/EoRs are
detected - as well as changing the text as before.
This was prompted by working on the fix for #7480.
#### Brief overview of PR changes/additions
This PR tries to put all `const`s before the type (class).
#### Motivation for adding to Mudlet
There is a mix of positioning of `const`s where it is used to indicate
that a variable is not to be modified by program code, we tend to put it
before the type but a prior PR (which looks to have been done with an
automated tool) has resulted in a mix of cases some with the const
adjacent to the variable. This lack of consistency can be confusing.
#### Other info (issues closed, discussion etc)
It looks like this came about in #6843.
This will upset the Danger detector because of the number of files
modified but it should be fairly straightforward to review.
Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
#### Brief overview of PR changes/additions
This has been done by carefully comparing the matching code from the
PCRE trigger item. Some variables have been renamed so they match up
better.
#### Motivation for adding to Mudlet
This has been a long-standing but unlisted wish-list item but someone
actually raised an issue for it! 😀
Fortunately much of the needed code was already in place, just the bit
that injected the named group results into the Lua sub-system was
missing (the call to `TLuaInterpreter::setCaptureNameGroups(nameGroups,
namePositions)`)!
#### Other info (issues closed, discussion etc)
It does actually make writing aliases a bit easier because it is easier
to construct an alias using `matches.target` say rather than
`matches[4]` if `?<target>` has been inserted at the start of the
relevant capture group. It also makes extending/modifying an alias
easier as there is no need to juggle indexes in the Lua script that
uses, say, `matches.target` compared to one that uses `matches[4]`.
This should close#7171.
Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
<!-- Keep the title short & concise so anyone non-technical can
understand it,
the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
To avoid a potential crash, improve the code's handling of allocation
failures by returning a null check instead of throwing an exception.
#### Motivation for adding to Mudlet
Cleaner code with fewer potential issues
#### Other info (issues closed, discussion etc)
This document explains what was going on really well: [Incorrect
allocation-error handling · Code scanning alert #138 ·
Mudlet_Mudlet.pdf](https://github.com/Mudlet/Mudlet/files/14232200/Incorrect.allocation-error.handling.Code.scanning.alert.138.Mudlet_Mudlet.pdf)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Behind the scenes there are things like `code = qsl("function Alias%1()
%2\nend").arg(id, code)` which handles the code of an alias and embeds
it into a function. Timers, triggers, and keybinds all have similar, but
with the `function X()` on a line alone. When they get compiled, the
first line of an alias code box is also the first line of the resulting
function, so an error on the first line of the edit box code is also the
first line of the post-embed code. But for the other type of items, the
first line of edit box is second line of post-embed code. When
compiling, the system is looking at the post-embed code, so it gives
errors with line numbers that make sense with aliases but are confusing
with the others.
This edit should make them all similar to aliases.
#### Motivation for adding to Mudlet
fix#1460
#### Other info (issues closed, discussion etc)
Error messages say things like "near X" instead of citing column
numbers, so I don't think there are any negative consequences for first
line being on same line after `function X()`. It's how aliases are
handled already.
---------
Co-authored-by: Stephen Lyons <slysven@virginmedia.com>
<!-- Keep the title short & concise so anyone non-technical can
understand it,
the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Mark read-only variables as const - that is, not intended to change
after they've been declared
#### Motivation for adding to Mudlet
So we don't change them by accident later on, and also to clearly state
intentions for the variables
#### Other info (issues closed, discussion etc)
This is also recommended by the [C++ Core
Guidelines](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#es25-declare-an-object-const-or-constexpr-unless-you-want-to-modify-its-value-later-on)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Rename a few variables shadowing global ones
#### Motivation for adding to Mudlet
Remove potential sources of issues, however small
#### Other info (issues closed, discussion etc)
---------
Co-authored-by: keneanung <keneanung@googlemail.com>
#### Brief overview of PR changes/additions
Refactored some code, removed whitespace from texts for translation.
#### Motivation for adding to Mudlet
Translators don't need to worry matching a number of empty lines at the
edge of a translated text.
#### Other info (issues closed, discussion etc)
Fix#3466
This better reflects the intention of such checks.
A couple of the changes originated in #6330 but they were not germane to
the issue being addressed there, so factored out to a separate PR which
I extended to cover all (a lot more) cases .
Signed-off-by: Stephen Lyons <slysven@virginmedia.com>