Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Escape from the Planet of the Quotes #2655

Draft
wants to merge 1 commit into
base: version/7.3.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -21,7 +21,7 @@

import com.sk89q.worldedit.event.Event;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.internal.util.Substring;
import com.sk89q.worldedit.internal.command.FullStringSuggestion;

import java.util.Collections;
import java.util.List;
Expand All @@ -35,7 +35,7 @@ public class CommandSuggestionEvent extends Event {

private final Actor actor;
private final String arguments;
private List<Substring> suggestions = Collections.emptyList();
private List<FullStringSuggestion> suggestions = Collections.emptyList();

/**
* Create a new instance.
Expand Down Expand Up @@ -72,14 +72,9 @@ public String getArguments() {
/**
* Get the list of suggestions that are to be presented.
*
* <p>
* Each Substring holds the replacement as the substring,
* and the replacement range as the original substring range.
* </p>
*
* @return the list of suggestions
*/
public List<Substring> getSuggestions() {
public List<FullStringSuggestion> getSuggestions() {
return suggestions;
}

Expand All @@ -88,7 +83,7 @@ public List<Substring> getSuggestions() {
*
* @param suggestions the list of suggestions
*/
public void setSuggestions(List<Substring> suggestions) {
public void setSuggestions(List<FullStringSuggestion> suggestions) {
checkNotNull(suggestions);
this.suggestions = suggestions;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,16 @@
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.internal.annotation.OptionalArg;
import com.sk89q.worldedit.internal.annotation.Selection;
import com.sk89q.worldedit.internal.command.CommandArgParseException;
import com.sk89q.worldedit.internal.command.CommandArgParser;
import com.sk89q.worldedit.internal.command.CommandLoggingHandler;
import com.sk89q.worldedit.internal.command.CommandRegistrationHandler;
import com.sk89q.worldedit.internal.command.ExtractedArg;
import com.sk89q.worldedit.internal.command.FullStringSuggestion;
import com.sk89q.worldedit.internal.command.exception.ExceptionConverter;
import com.sk89q.worldedit.internal.command.exception.WorldEditExceptionConverter;
import com.sk89q.worldedit.internal.util.ErrorReporting;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.internal.util.Substring;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.session.request.Request;
import com.sk89q.worldedit.util.eventbus.Subscribe;
Expand Down Expand Up @@ -465,21 +467,32 @@ void removeCommands() {
dynamicHandler.setHandler(null);
}

private Stream<Substring> parseArgs(String input) {
return CommandArgParser.forArgString(input.substring(1)).parseArgs();
private Stream<ExtractedArg> parseArgs(String input, boolean forCompletions) {
if (input.isEmpty() || input.charAt(0) != '/') {
throw new IllegalStateException("Command input must start with /: " + input);
}
return CommandArgParser.parse(input.substring(1), forCompletions);
}

@Subscribe
public void handleCommand(CommandEvent event) {
Request.reset();

Actor actor = platformManager.createProxyActor(event.getActor());
String[] split = parseArgs(event.getArguments())
.map(Substring::getSubstring)
.toArray(String[]::new);
List<String> split;
try {
split = parseArgs(event.getArguments(), false)
.map(ExtractedArg::parsedText)
.collect(ImmutableList.toImmutableList());
} catch (CommandArgParseException e) {
actor.printError(TranslatableComponent.of(
"worldedit.error.command-parse", TextComponent.of(e.getMessage())
));
return;
}

// No command found!
if (!commandManager.containsCommand(split[0])) {
if (!commandManager.containsCommand(split.getFirst())) {
return;
}

Expand All @@ -501,10 +514,10 @@ public void handleCommand(CommandEvent event) {
// This is a bit of a hack, since the call method can only throw CommandExceptions
// everything needs to be wrapped at least once. Which means to handle all WorldEdit
// exceptions without writing a hook into every dispatcher, we need to unwrap these
// exceptions and rethrow their converted form, if their is one.
// exceptions and rethrow their converted form, if there is one.
try {
try {
commandManager.execute(context, ImmutableList.copyOf(split));
commandManager.execute(context, split);
} finally {
Optional<EditSession> editSessionOpt =
context.snapshotMemory().injectedValue(Key.of(EditSession.class));
Expand Down Expand Up @@ -605,14 +618,20 @@ private void handleUnknownException(Actor actor, Throwable t) {
public void handleCommandSuggestion(CommandSuggestionEvent event) {
try {
String arguments = event.getArguments();
List<Substring> split = parseArgs(arguments).toList();
List<String> argStrings = split.stream()
.map(Substring::getSubstring)
.collect(Collectors.toList());
List<ExtractedArg> extractedArgs;
try {
extractedArgs = parseArgs(arguments, true).toList();
} catch (CommandArgParseException e) {
LOGGER.debug("Failed to parse command arguments for suggestions: " + arguments, e);
return;
}
List<String> extractedArgStrings = extractedArgs.stream()
.map(ExtractedArg::parsedText)
.collect(ImmutableList.toImmutableList());
MemoizingValueAccess access = initializeInjectedValues(() -> arguments, event.getActor());
ImmutableSet<Suggestion> suggestions;
try {
suggestions = commandManager.getSuggestions(access, argStrings);
suggestions = commandManager.getSuggestions(access, extractedArgStrings);
} catch (Throwable t) { // catch errors which are *not* command exceptions generated by parsers/suggesters
if (!(t instanceof CommandException)) {
LOGGER.debug("Unexpected error occurred while generating suggestions for input: " + arguments, t);
Expand All @@ -623,16 +642,20 @@ public void handleCommandSuggestion(CommandSuggestionEvent event) {

event.setSuggestions(suggestions.stream()
.map(suggestion -> {
int noSlashLength = arguments.length() - 1;
Substring original = suggestion.getReplacedArgument() == split.size()
? Substring.from(arguments, noSlashLength, noSlashLength)
: split.get(suggestion.getReplacedArgument());
// increase original points by 1, for removed `/` in `parseArgs`
return Substring.wrap(
suggestion.getSuggestion(),
original.getStart() + 1,
original.getEnd() + 1
);
String escapedText = escape(suggestion.getSuggestion());
if (suggestion.getReplacedArgument() == extractedArgs.size()) {
// Argument addition, use end of string as target
// Also add a space on the front to cause it to be a new argument
return new FullStringSuggestion(
" " + escapedText, arguments.length(), arguments.length()
);
} else {
ExtractedArg arg = extractedArgs.get(suggestion.getReplacedArgument());
// +1 to compensate for the removal of / in parseArgs
return new FullStringSuggestion(
escapedText, arg.originalStart() + 1, arg.originalEnd() + 1
);
}
}).collect(Collectors.toList()));
} catch (ConditionFailedException e) {
if (e.getCondition() instanceof PermissionCondition) {
Expand All @@ -641,6 +664,28 @@ public void handleCommandSuggestion(CommandSuggestionEvent event) {
}
}

private static String escape(String suggestion) {
if (suggestion.contains(" ")) {
// It might be nice to do smart quoting, since we support both ' and " in arguments
// For now, I'm just doing the simple option
StringBuilder builder = new StringBuilder(suggestion.length() + 2);
builder.append('"');
for (int i = 0; i < suggestion.length(); i++) {
char c = suggestion.charAt(i);
if (c == '\\') {
builder.append("\\\\");
} else if (c == '"') {
builder.append("\\\"");
} else {
builder.append(c);
}
}
builder.append('"');
return builder.toString();
}
return suggestion;
}

/**
* Get the command manager instance.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package com.sk89q.worldedit.internal.command;

public class CommandArgParseException extends RuntimeException {
public CommandArgParseException(String message) {
super(message);
}
}
Loading
Loading