diff --git a/org.eclipse.tm4e.core/src/main/java/org/eclipse/tm4e/core/internal/oniguruma/OnigRegExp.java b/org.eclipse.tm4e.core/src/main/java/org/eclipse/tm4e/core/internal/oniguruma/OnigRegExp.java
index 96f11a6fc..90caeaf2a 100644
--- a/org.eclipse.tm4e.core/src/main/java/org/eclipse/tm4e/core/internal/oniguruma/OnigRegExp.java
+++ b/org.eclipse.tm4e.core/src/main/java/org/eclipse/tm4e/core/internal/oniguruma/OnigRegExp.java
@@ -94,7 +94,7 @@ private Regex parsePattern(final String pattern, final boolean ignoreCase) throw
}
/**
- * Rewrites the given pattern to work around limitations of the Joni library, which does not support variable-length lookbehinds.
+ * Rewrites the given pattern to work around lookbehind limitations of the Joni library.
*
* Strategy:
*
@@ -107,6 +107,7 @@ private Regex parsePattern(final String pattern, final boolean ignoreCase) throw
*
*
* @see github.com/eclipse-tm4e/tm4e/issues/677
+ * @see github.com/eclipse-tm4e/tm4e/issues/1027
*/
private String rewritePatternIfRequired(final String pattern) {
if (pattern.isEmpty())
@@ -144,7 +145,12 @@ private String rewritePatternIfRequired(final String pattern) {
if (pattern.startsWith(negLB))
return "(? (?!(?<=_)\1\w)(\1)
+ // Joni rejects backreferences inside lookbehinds. Test Markdown's closing delimiter before consuming it so the
+ // backreference stays outside the lookbehind and the original capture groups and boundary semantics are preserved.
+ // Keep this rewrite narrow because arbitrary backreference lookbehinds cannot be moved without changing their meaning.
+ return pattern.replace("(\\1)(?!(?<=_\\1)\\w)", "(?!(?<=_)\\1\\w)(\\1)");
}
/**
diff --git a/org.eclipse.tm4e.core/src/test/java/org/eclipse/tm4e/core/internal/oniguruma/OnigRegExpTest.java b/org.eclipse.tm4e.core/src/test/java/org/eclipse/tm4e/core/internal/oniguruma/OnigRegExpTest.java
index f14d59f2f..83bf4272f 100644
--- a/org.eclipse.tm4e.core/src/test/java/org/eclipse/tm4e/core/internal/oniguruma/OnigRegExpTest.java
+++ b/org.eclipse.tm4e.core/src/test/java/org/eclipse/tm4e/core/internal/oniguruma/OnigRegExpTest.java
@@ -98,4 +98,22 @@ void testNegativeLookBehinds() {
assertOnigRegExpSearch("(?<=\\s*\\.)\\w+", ".foo", 0, true, ".foo");
assertOnigRegExpSearch("(?<=\\s*\\.)\\w+", " .foo", 0, true, " .foo");
}
+
+ @Test
+ void testMarkdownBackReferenceInLookBehind() {
+ // Keep the exact Markdown strikethrough pattern so this test exercises the same Joni compatibility path as the grammar.
+ final var pattern =
+ "(? patterns) {
@@ -224,10 +234,7 @@ private void assertParseablePatterns(final @Nullable Collection patter
return;
for (final var rule : patterns) {
- assertParseablePattern(rule.getBegin());
- assertParseablePattern(rule.getEnd());
- assertParseablePattern(rule.getMatch());
- assertParseablePattern(rule.getWhile());
+ assertParseableRule(rule);
assertParseablePatterns(rule.getPatterns());
}
}
@@ -254,6 +261,10 @@ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr
final var patterns = castNonNull(rawGrammar.getPatterns());
assertThat(patterns).isNotEmpty();
assertParseablePatterns(patterns);
+ // TextMate include rules reference named repository entries by string, so walking
+ // getPatterns() never reaches their regex fields. Keep this check to the named rule;
+ // recursively expanding repository subtrees exposes separate existing incompatibilities.
+ rawGrammar.getRepository().putEntries((name, rule) -> assertParseableRule(rule));
final var reg = new Registry();
final var grammar = reg.addGrammar(IGrammarSource.fromFile(file));