You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+6Lines changed: 6 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,12 +8,18 @@ Updates should follow the [Keep a CHANGELOG](https://keepachangelog.com/) princi
8
8
9
9
### Added
10
10
- Added a new `table_of_contents/max_placeholder_entries` option to limit how many table of contents entries a document may render across all of its placeholders (#1134)
11
+
- Added `Cursor::matchInPlace()`, which matches a regular expression at the cursor's position within the line using PCRE's native offset semantics instead of copying the remainder (#1145)
12
+
-`\G` anchors at the cursor, `^` anchors at the start of the line, and lookbehinds and `\b` see the characters actually preceding the cursor; this keeps scanning loops linear and enables left-context assertions that `match()` cannot express
13
+
- Added `RegexHelper::PARTIAL_LINK_TITLE_UNANCHORED` and `RegexHelper::PARTIAL_LINK_DESTINATION_BRACES`, unanchored fragments so each call site can supply its own anchor
11
14
12
15
### Changed
13
16
- Changed the `TableOfContents` extension to render the table of contents once and share it across all placeholders instead of cloning it into each one (#1134)
14
17
- A custom renderer registered for the `TableOfContents` node is no longer called once per placeholder, so it must return the same markup each time it is called for a given document (#1134)
15
18
- The first placeholder receives the table of contents itself, so a document still contains a `TableOfContents` node for listeners which locate and reposition it (#1143)
16
19
20
+
### Deprecated
21
+
- Deprecated `RegexHelper::PARTIAL_LINK_TITLE` and `RegexHelper::REGEX_LINK_DESTINATION_BRACES`; use the unanchored variants with an explicit anchor instead
22
+
17
23
## [2.9.2] - 2026-08-10
18
24
19
25
This release fixes a regression introduced in 2.9.0 which changed the behavior of `Cursor::match()` for certain regular expression patterns.
Copy file name to clipboardExpand all lines: docs/2.x/customization/cursor.md
+23-1Lines changed: 23 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -57,11 +57,33 @@ You can then call any of the following methods to parse the string within that `
57
57
|`advanceToNextNonSpaceOrTab()`| Advances forward past all spaces and tabs found, returning the number of such characters found |
58
58
|`advanceToNextNonSpaceOrNewline()`| Advances forward past all spaces and newlines found, returning the number of such characters found |
59
59
|`advanceToEnd()`| Advances the position to the very end of the string, returning the number of such characters passed |
60
-
|`match(string $regex)`| Attempts to match the given `$regex`; returns `null` if matching fails, otherwise it advances past and returns the matched text |
60
+
|`match(string $regex)`| Attempts to match the given `$regex` against the remainder; returns `null` if matching fails, otherwise it advances past and returns the matched text |
61
+
|`matchInPlace(string $regex)`| Like `match()`, but matches at the cursor's position within the whole line instead of copying the remainder; see below |
61
62
|`getPreviousText()`| Returns the text that was just advanced through during the last `advance__()` or `match()` operation |
62
63
|`getRemainder()`| Returns the contents of the string from the current position through the end of the string |
63
64
|`isBlank()`| Returns whether the remainder is blank (we're at the end or only space characters remain) |
64
65
|`isAtEnd()`| Returns whether the cursor has reached the end of the string |
65
66
|`saveState()`| Encapsulates the current state of the cursor into an `array` in case you need to `restoreState()` later |
66
67
|`restoreState($state)`| Pass the result of `saveState()` back into here to restore the original state of the `Cursor`|
67
68
|`getLine()`| Returns the entire string (not taking the position into account) |
69
+
70
+
## Regular Expression Matching
71
+
72
+
The `Cursor` offers two ways to match a regular expression at the current position. They differ in what the pattern is matched against:
73
+
74
+
-**`match()`** copies the remainder and matches against that copy, so the subject begins at the cursor. `^` and `\A` anchor at the cursor, and constructs which examine what precedes the match position (lookbehinds, `\b`) see the start of the subject there — never the actual preceding characters.
75
+
-**`matchInPlace()`** (available since 2.10) matches against the whole line starting at the cursor's position, using PCRE's native offset semantics. `\G` anchors at the cursor, `^` means the start of the line, and lookbehinds and `\b` see the characters actually preceding the cursor.
76
+
77
+
`matchInPlace()` has two advantages:
78
+
79
+
- It avoids copying the remainder, so repeated calls (such as a scanning loop) stay fast instead of paying for a copy of everything left in the line on every call.
80
+
- Because the text before the cursor stays visible to the pattern, it can answer contextual questions `match()` cannot — for example, `/(?<!\w)@\w+/` only matching a mention when the preceding character isn't a word character.
81
+
82
+
One exception: when the cursor has partially consumed a tab, no position within the line can represent it, so `matchInPlace()` falls back to matching a copy of the remainder with the leftover tab expanded into spaces. `\G` still anchors at the cursor there, but the text before the cursor is not visible to the pattern in that state.
83
+
84
+
To migrate a pattern from `match()` to `matchInPlace()`, replace its leading `^` (or `\A`) with `\G`, and double-check that any `\b` or lookbehind still means what you want now that it can see the preceding text:
85
+
86
+
```php
87
+
$cursor->match('/^#+/'); // anchors at the cursor
88
+
$cursor->matchInPlace('/\G#+/'); // equivalent, without copying the remainder
0 commit comments