Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
58c9b77
add expr replace needs testing
novystar Apr 23, 2026
da375d8
add regex support, clean up getter, change non-regex to use StringUtils
novystar Apr 23, 2026
d50b03a
add regex support, clean up getter, change non-regex to use StringUtils
novystar Apr 23, 2026
23425bd
Merge remote-tracking branch 'origin/master'
novystar Apr 23, 2026
932c8a6
fix replacement null case returning null
novystar Apr 23, 2026
d2095f7
support for case sensitive config value
novystar Apr 23, 2026
a2c3703
add exprReplace, add test
novystar Apr 23, 2026
fd02ec2
fix some coding conventions
novystar Apr 23, 2026
3d2c06a
case sensitivity test
novystar Apr 23, 2026
782920e
change method order, change getAll to getArray
novystar Apr 23, 2026
c0bf02a
relocate ExprReplace to skriptlang common module,
novystar Apr 23, 2026
e7e5811
add new syntaxes that are more fitting for an expression,
novystar Apr 23, 2026
87180be
rework syntaxes so they are more fitting for an expression,
novystar Apr 23, 2026
cd2a7c0
Apply suggestions from code review
novystar Apr 25, 2026
b6893ef
address missing overrides
novystar Apr 25, 2026
74376bd
Apply suggestions from code review
novystar Apr 25, 2026
75f0dca
Merge branch 'dev/feature' into feature/replace_expression
novystar May 3, 2026
42b3e6f
Merge branch 'dev/feature' into feature/replace_expression
novystar May 6, 2026
0c15105
Merge branch 'dev/feature' into feature/replace_expression
novystar May 8, 2026
c72da7c
Update src/main/java/org/skriptlang/skript/common/elements/expression…
novystar May 10, 2026
ddda881
Add more examples and Fix unhandled exception on improper use of grou…
novystar May 10, 2026
fc83b3d
Add more examples and Fix unhandled exception on improper use of grou…
novystar May 10, 2026
1717e88
Merge remote-tracking branch 'origin/feature/replace_expression' into…
novystar May 10, 2026
e55dc9e
make is|are required in regex pattern
novystar May 10, 2026
93d8d4b
double escape so word boundries are visible in examples
novystar May 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ protected void loadSelf(SkriptAddon addon) {
register(addon,
ExprColorFromHexCode::register,
ExprHexCode::register,
ExprRecursiveSize::register
ExprRecursiveSize::register,
ExprReplace::register
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package org.skriptlang.skript.common.elements.expressions;

import ch.njol.skript.SkriptConfig;
import ch.njol.skript.doc.Description;
import ch.njol.skript.doc.Example;
import ch.njol.skript.doc.Name;
import ch.njol.skript.doc.Since;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.SyntaxStringBuilder;
import ch.njol.skript.lang.util.SimpleExpression;
import ch.njol.util.Kleenean;
import ch.njol.util.StringUtils;
import org.bukkit.event.Event;
import org.jetbrains.annotations.Nullable;
import org.skriptlang.skript.registration.DefaultSyntaxInfos;
import org.skriptlang.skript.registration.SyntaxRegistry;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


@Name("Text Replacement")
Comment thread
novystar marked this conversation as resolved.
Outdated
@Description("Performs a text replacement on a given value, returning the result. Supports regex and case sensitive replacement.")
@Example("send \"Welcome [player]\" where \"[player]\" is replaced with \"%player%\" to player")
Comment thread
novystar marked this conversation as resolved.
@Since("2.16")
Comment thread
novystar marked this conversation as resolved.
Outdated
public class ExprReplace extends SimpleExpression<String> {

public static void register(SyntaxRegistry registry) {
registry.register(
SyntaxRegistry.EXPRESSION,
DefaultSyntaxInfos.Expression.builder(ExprReplace.class, String.class)
.addPatterns(
"%strings% where [(first:first instance[s]|all instances) of] %strings% [is|are] replaced with %string% [regex:using regex|case:with case sensitivity]",
"%strings% where [(first:first instance[s]|all instances) of] regex [pattern[s]] %strings% [is|are] replaced with %string%"
Comment thread
novystar marked this conversation as resolved.
Outdated
)
.supplier(ExprReplace::new)
.build()
);
}

private Expression<String> needleExpr;
private Expression<String> haystackExpr;
private Expression<String> replacementExpr;

private boolean isFirst;
private boolean isRegex = false;
private boolean isCaseSensitive = false;

@SuppressWarnings("unchecked")
@Override
public boolean init(Expression<?>[] expr, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {

haystackExpr = (Expression<String>) expr[0];
needleExpr = (Expression<String>) expr[1];
replacementExpr = (Expression<String>) expr[2];

if (matchedPattern == 1 || parseResult.hasTag("regex")) {
isRegex = true;
}

isFirst = parseResult.hasTag("first");

if (SkriptConfig.caseSensitive.value() || parseResult.hasTag("case")) {
isCaseSensitive = true;
}
return true;
}

@Override
protected String @Nullable [] get(Event event) {
String replacement = replacementExpr.getSingle(event);
Comment thread
novystar marked this conversation as resolved.
String[] needles = needleExpr.getArray(event);
String[] haystacks = haystackExpr.getArray(event);

if (replacement == null) {
return haystacks;
}

List<String> result = new ArrayList<>(haystacks.length);

if (isRegex) {
List<Pattern> patterns = new ArrayList<>(needles.length);
for (String needle : needles) {
try { // Pre compile regex for use with multiple haystacks
patterns.add(Pattern.compile(needle));
} catch (Exception ignored) {
}
}

for (String haystack : haystacks) {
for (Pattern pattern : patterns) {
Matcher matcher = pattern.matcher(haystack);
if (isFirst) {
haystack = matcher.replaceFirst(replacement);
} else {
haystack = matcher.replaceAll(replacement);
}
}
result.add(haystack);
}
} else {
for (String haystack : haystacks) {
for (String needle : needles) {
if (isFirst) {
haystack = StringUtils.replaceFirst(haystack, needle, replacement, isCaseSensitive);
} else {
haystack = StringUtils.replace(haystack, needle, replacement, isCaseSensitive);
}
}
result.add(haystack);
}
}

return result.toArray(new String[0]);

}
Comment thread
novystar marked this conversation as resolved.

public boolean isSingle() {
return false;
Comment thread
novystar marked this conversation as resolved.
Outdated
}
public boolean canBeSingle() {
Comment thread
novystar marked this conversation as resolved.
Outdated
return true;
}
Comment thread
novystar marked this conversation as resolved.

@Override
public Class<? extends String> getReturnType() {
return String.class;
}

@Override
public String toString(@Nullable Event event, boolean debug) {
SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug);

builder.append("replace");

if (isFirst) {
builder.append("first");
}
if (isRegex) {
builder.append("regex");
}

builder.append(needleExpr, "in", haystackExpr, "with", replacementExpr);

if (isCaseSensitive) {
builder.append("with case sensitivity");
}

return builder.toString();
Comment thread
novystar marked this conversation as resolved.
}
Comment thread
novystar marked this conversation as resolved.
}
35 changes: 35 additions & 0 deletions src/test/skript/tests/syntaxes/expressions/ExprReplace.sk
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
test "replace expression":

assert "hello this is a test" where "hello" is replaced with "goodbye" is "goodbye this is a test" with "replace"


assert "hello this is a test" where regex pattern "[el]" is replaced with "!" is "h!!!o this is a t!st" with "regex replace pattern 1"

assert "hello this is a test" where "[el]" is replaced with "!" using regex is "h!!!o this is a t!st" with "regex replace pattern 2"


set {_test::*} to "hello", "this", "is", "a", "test"

assert {_test::*} where "hello", "tes" are replaced with "!" is ("!", "this", "is", "a", "!t") with "replace list"

assert {_test::*} where regex "[el]" is replaced with "!" is "h!!!o", "this", "is", "a", "t!st" with "regex replace list"


assert "aaaaAAAAbbbbBBBB" where "a" is replaced with "!" with case sensitivity is "!!!!AAAAbbbbBBBB" with "replace case sensitivity"


assert "test hello test hello this is a test" where first instance of "hello" is replaced with "!" is "test ! test hello this is a test" with "replace first"

assert "test hello test hello this is a test" where all instances of "hello" are replaced with "!" is "test ! test ! this is a test" with "replace all"


assert "abc abcd" where first instance of regex "[ac]" is replaced with "!" is "!bc abcd" with "regex replace first"

assert "abc abcd" where first instance of regex "[ac]", "[acd]" is replaced with "!" is "!b! abcd" with "regex replace multi first"


set {_test::*} to "hello", "this", "is", "a", "test"

assert {_test::*} where first instance of "hel", "this" are replaced with "!" is ("!lo", "!", "is", "a", "test") with "first replace multi list"

assert {_test::*} where first instances of regex patterns "[he]", "l" are replaced with "!" is ("!e!lo", "t!is", "is", "a", "t!st") with "first regex replace multi list"