aboutsummaryrefslogtreecommitdiff
path: root/runtime/src/main/java
diff options
context:
space:
mode:
authorshedaniel <daniel@shedaniel.me>2022-02-17 15:38:59 +0800
committershedaniel <daniel@shedaniel.me>2022-02-18 10:24:53 +0800
commit732e0640dbf1dbe4b98d2a7f8682e9d10dcfac8f (patch)
tree9d93217409cc6387b9a90d576320f792bb7b1365 /runtime/src/main/java
parente0c9b792af72d971bdf59debd1f065521c37c126 (diff)
downloadRoughlyEnoughItems-732e0640dbf1dbe4b98d2a7f8682e9d10dcfac8f.tar.gz
RoughlyEnoughItems-732e0640dbf1dbe4b98d2a7f8682e9d10dcfac8f.tar.bz2
RoughlyEnoughItems-732e0640dbf1dbe4b98d2a7f8682e9d10dcfac8f.zip
Implement changelog, close #772
Diffstat (limited to 'runtime/src/main/java')
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigManagerImpl.java2
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/ScreenOverlayImpl.java19
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/changelog/ChangelogLoader.java117
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/changelog/JParseDown.java1316
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/changelog/JParseDownToMinecraft.java70
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/error/ErrorsEntryListWidget.java322
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/error/ErrorsScreen.java23
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/CatchingExceptionUtils.java2
8 files changed, 1858 insertions, 13 deletions
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigManagerImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigManagerImpl.java
index 6f9c5a085..485b90271 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigManagerImpl.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigManagerImpl.java
@@ -80,7 +80,6 @@ import net.minecraft.nbt.TagParser;
import net.minecraft.network.chat.*;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionResult;
-import org.apache.commons.lang3.mutable.MutableLong;
import org.jetbrains.annotations.ApiStatus;
import java.util.*;
@@ -371,7 +370,6 @@ public class ConfigManagerImpl implements ConfigManager {
((GlobalizedClothConfigScreen) screen).listWidget.children().add(0, (AbstractConfigEntry) feedbackEntry);
((GlobalizedClothConfigScreen) screen).listWidget.children().add(0, (AbstractConfigEntry) new EmptyEntry(4));
ScreenHooks.addRenderableWidget(screen, new Button(screen.width - 104, 4, 100, 20, new TranslatableComponent("text.rei.credits"), button -> {
- MutableLong current = new MutableLong(0);
CreditsScreen creditsScreen = new CreditsScreen(screen);
Minecraft.getInstance().setScreen(creditsScreen);
}));
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/ScreenOverlayImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/ScreenOverlayImpl.java
index 067e8d32e..7724f6d0a 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/ScreenOverlayImpl.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/ScreenOverlayImpl.java
@@ -62,6 +62,7 @@ import me.shedaniel.rei.impl.client.ClientHelperImpl;
import me.shedaniel.rei.impl.client.REIRuntimeImpl;
import me.shedaniel.rei.impl.client.config.ConfigManagerImpl;
import me.shedaniel.rei.impl.client.config.ConfigObjectImpl;
+import me.shedaniel.rei.impl.client.gui.changelog.ChangelogLoader;
import me.shedaniel.rei.impl.client.gui.craftable.CraftableFilter;
import me.shedaniel.rei.impl.client.gui.dragging.CurrentDraggingStack;
import me.shedaniel.rei.impl.client.gui.modules.Menu;
@@ -265,6 +266,21 @@ public class ScreenOverlayImpl extends ScreenOverlay {
.containsMousePredicate((button, point) -> button.getBounds().contains(point) && isNotInExclusionZones(point.x, point.y))
.tooltipLine(new TranslatableComponent("text.rei.previous_page"))
.focusable(false));
+ Button changelogButton;
+ widgets.add(changelogButton = Widgets.createButton(new Rectangle(bounds.x + bounds.width - 18 - 18, bounds.y + (ConfigObject.getInstance().getSearchFieldLocation() == SearchFieldLocation.TOP_SIDE ? 24 : 0) + 5, 16, 16), new TranslatableComponent(""))
+ .onClick(button -> {
+ ChangelogLoader.show();
+ })
+ .containsMousePredicate((button, point) -> button.getBounds().contains(point) && isNotInExclusionZones(point.x, point.y))
+ .tooltipLine(new TranslatableComponent("text.rei.changelog.title"))
+ .focusable(false));
+ widgets.add(Widgets.createDrawableWidget((helper, matrices, mouseX, mouseY, delta) -> {
+ helper.setBlitOffset(helper.getBlitOffset() + 1);
+ RenderSystem.setShaderTexture(0, CHEST_GUI_TEXTURE);
+ Rectangle bounds = changelogButton.getBounds();
+ helper.blit(matrices, bounds.x + 1, bounds.y + 2, !ChangelogLoader.hasVisited() ? 28 : 14, 0, 14, 14);
+ helper.setBlitOffset(helper.getBlitOffset() - 1);
+ }));
widgets.add(rightButton = Widgets.createButton(new Rectangle(bounds.x + bounds.width - 18, bounds.y + (ConfigObject.getInstance().getSearchFieldLocation() == SearchFieldLocation.TOP_SIDE ? 24 : 0) + 5, 16, 16), new TranslatableComponent("text.rei.right_arrow"))
.onClick(button -> {
ENTRY_LIST_WIDGET.nextPage();
@@ -332,6 +348,7 @@ public class ScreenOverlayImpl extends ScreenOverlay {
helper.setBlitOffset(helper.getBlitOffset() + 1);
RenderSystem.setShaderTexture(0, CHEST_GUI_TEXTURE);
helper.blit(matrices, configButtonArea.x + 3, configButtonArea.y + 3, 0, 0, 14, 14);
+ helper.setBlitOffset(helper.getBlitOffset() - 1);
})
),
0, 0, 600
@@ -349,7 +366,7 @@ public class ScreenOverlayImpl extends ScreenOverlay {
}), 0, 0, 600)));
}
if (!ConfigObject.getInstance().isEntryListWidgetScrolled()) {
- widgets.add(Widgets.createClickableLabel(new Point(bounds.x + (bounds.width / 2), bounds.y + (ConfigObject.getInstance().getSearchFieldLocation() == SearchFieldLocation.TOP_SIDE ? 24 : 0) + 10), NarratorChatListener.NO_TITLE, label -> {
+ widgets.add(Widgets.createClickableLabel(new Point(bounds.x + ((bounds.width - 18) / 2), bounds.y + (ConfigObject.getInstance().getSearchFieldLocation() == SearchFieldLocation.TOP_SIDE ? 24 : 0) + 10), NarratorChatListener.NO_TITLE, label -> {
if (!Screen.hasShiftDown()) {
ENTRY_LIST_WIDGET.setPage(0);
ENTRY_LIST_WIDGET.updateEntriesPosition();
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/changelog/ChangelogLoader.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/changelog/ChangelogLoader.java
new file mode 100644
index 000000000..cf0b3fc10
--- /dev/null
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/changelog/ChangelogLoader.java
@@ -0,0 +1,117 @@
+package me.shedaniel.rei.impl.client.gui.changelog;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import dev.architectury.platform.Platform;
+import me.shedaniel.rei.impl.client.gui.error.ErrorsEntryListWidget;
+import me.shedaniel.rei.impl.client.gui.error.ErrorsScreen;
+import net.minecraft.client.Minecraft;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.TranslatableComponent;
+import org.apache.commons.io.IOUtils;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.function.Function;
+
+public class ChangelogLoader {
+ public interface Builder {
+ default void add(Component component) {
+ add(width -> new ErrorsEntryListWidget.TextEntry(component, width));
+ }
+
+ void add(Function<Integer, ErrorsEntryListWidget.Entry> function);
+ }
+
+ private static Boolean visited = false;
+
+ public static boolean hasVisited() {
+ if (visited == null) {
+ visited = false;
+ File file = Platform.getConfigFolder().resolve("roughlyenoughitems/changelog.txt").toFile();
+ if (file.exists()) {
+ try (InputStreamReader reader = new FileReader(file)) {
+ String version = IOUtils.toString(reader).trim();
+
+ InputStream changesJsonStream = ChangelogLoader.class.getClassLoader().getResourceAsStream("roughlyenoughitems.changes.json");
+ if (changesJsonStream != null) {
+ JsonObject object = JsonParser.parseReader(new InputStreamReader(changesJsonStream))
+ .getAsJsonObject();
+ String currentVersion = object.getAsJsonPrimitive("version").getAsString();
+ if (currentVersion.equals(version)) {
+ visited = true;
+ }
+ }
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return visited;
+ }
+
+ public static void show() {
+ class BuilderImpl implements Builder {
+ private final List<Object> components = new ArrayList<>();
+
+ @Override
+ public void add(Function<Integer, ErrorsEntryListWidget.Entry> function) {
+ components.add(function);
+ }
+ }
+
+ visited = true;
+ BuilderImpl builder = new BuilderImpl();
+
+ InputStream changesJsonStream = ChangelogLoader.class.getClassLoader().getResourceAsStream("roughlyenoughitems.changes.json");
+ if (changesJsonStream == null) {
+ builder.add(new TranslatableComponent("rei.changelog.error.missingChangelogFile"));
+ } else {
+ JsonObject object = JsonParser.parseReader(new InputStreamReader(changesJsonStream))
+ .getAsJsonObject();
+ String version = object.getAsJsonPrimitive("version").getAsString();
+ Path file = Platform.getConfigFolder().resolve("roughlyenoughitems/changelog.txt");
+ try {
+ Files.write(file, version.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ InputStream changelogStream = ChangelogLoader.class.getClassLoader().getResourceAsStream("roughlyenoughitems/" + version + "/changelog.md");
+
+ if (changelogStream == null) {
+ builder.add(new TranslatableComponent("rei.changelog.error.missingChangelogFile"));
+ } else {
+ try {
+ JParseDown parseDown = new JParseDown();
+ LinkedList<JParseDown.Block> blocks = parseDown.linesElements(IOUtils.readLines(changelogStream, StandardCharsets.UTF_8).toArray(new String[0]));
+ for (JParseDown.Block block : blocks) {
+ if (block.autoBreak) {
+ builder.add(width -> new ErrorsEntryListWidget.EmptyEntry(6));
+ }
+ Builder blockBuilder = builder;
+ if (block instanceof JParseDown.BlockHeader) {
+ blockBuilder = function -> {
+ builder.add(width -> new ErrorsEntryListWidget.ScaledEntry(function.apply(Math.round(width / 1.5F)), 1.5F));
+ };
+ }
+ JParseDownToMinecraft.build(blockBuilder, block);
+ if (block.autoBreak) {
+ builder.add(width -> new ErrorsEntryListWidget.EmptyEntry(6));
+ }
+ }
+ } catch (IOException e) {
+ builder.add(new TranslatableComponent("rei.changelog.error.failedToReadChangelogFile"));
+ }
+ }
+ }
+
+ Minecraft.getInstance().setScreen(new ErrorsScreen(new TranslatableComponent("text.rei.changelog.title"), builder.components, Minecraft.getInstance().screen, true));
+ }
+}
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/changelog/JParseDown.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/changelog/JParseDown.java
new file mode 100644
index 000000000..6165f6a7a
--- /dev/null
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/changelog/JParseDown.java
@@ -0,0 +1,1316 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.impl.client.gui.changelog;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/*
+Copyright (c) 2019 Ashur Rafiev
+https://github.com/ashurrafiev/JParsedown
+MIT Licence: https://github.com/ashurrafiev/JParsedown/blob/master/LICENSE
+
+This work is derived from Parsedown version 1.8.0-beta-5:
+Copyright (c) 2013-2018 Emanuil Rusev
+http://parsedown.org
+*/
+@SuppressWarnings("RegExpRedundantEscape")
+public class JParseDown {
+ public static final String version = "1.0.4";
+
+ public static class ReferenceData {
+ public String url;
+ public String title;
+
+ public ReferenceData(String url, String title) {
+ this.url = url;
+ this.title = title;
+ }
+ }
+
+ public static class Line {
+ public String body;
+ public String text;
+ public int indent;
+
+ public Line(String line) {
+ body = line;
+ text = line.replaceFirst("^\\s+", "");
+ indent = line.length() - text.length();
+ }
+ }
+
+ public abstract static class Component {
+ public String markup = null;
+ public boolean hidden = false;
+ public HashSet<Class<?>> nonNestables = new HashSet<>();
+ }
+
+ public interface BlockType<B extends Block> {
+ Block startBlock(JParseDown parseDown, Line line, Block block);
+ }
+
+ public interface InlineType<L extends Inline> {
+ Inline inline(JParseDown parseDown, String text, String context);
+ }
+
+ public abstract static class Block extends Component {
+ public boolean identified = false;
+ public int interrupted = 0;
+ public List<Inline> inlines;
+ public Boolean autoBreak = null;
+
+ public boolean isContinuable() {
+ return false;
+ }
+
+ public boolean isCompletable() {
+ return false;
+ }
+
+ public Block continueBlock(Line line) {
+ return null;
+ }
+
+ public Block completeBlock() {
+ return null;
+ }
+
+ public abstract Collection<Inline> inline(JParseDown parseDown);
+ }
+
+ public static class BlockParagraph extends Block {
+ public String text;
+
+ public BlockParagraph(String text) {
+ this.text = text;
+ }
+
+ public static Block startBlock(JParseDown parseDown, Line line, Block block) {
+ return new BlockParagraph(line.text);
+ }
+
+ @Override
+ public boolean isContinuable() {
+ return false;
+ }
+
+ @Override
+ public Block continueBlock(Line line) {
+ if (interrupted > 0)
+ return null;
+ text += "\n" + line.text;
+ return this;
+ }
+
+ @Override
+ public Collection<Inline> inline(JParseDown parseDown) {
+ return parseDown.lineElements(text, nonNestables);
+ }
+
+ @Override
+ public String toString() {
+ return "BlockParagraph{" +
+ "text='" + text + '\'' +
+ '}';
+ }
+ }
+
+ public static class BlockCode extends Block {
+ public String text;
+
+ public BlockCode(String text) {
+ this.text = text;
+ }
+
+ public static Block startBlock(JParseDown parseDown, Line line, Block block) {
+ if (block != null && block instanceof BlockParagraph && block.interrupted == 0)
+ return null;
+ if (line.indent >= 4) {
+ return new BlockCode(line.body.substring(4));
+ } else
+ return null;
+ }
+
+ @Override
+ public boolean isContinuable() {
+ return true;
+ }
+
+ @Override
+ public Block continueBlock(Line line) {
+ if (line.indent >= 4) {
+ while (interrupted > 0) {
+ text += "\n";
+ interrupted--;
+ }
+ text += "\n";
+ text += line.body.substring(4);
+ return this;
+ } else
+ return null;
+ }
+
+ @Override
+ public boolean isCompletable() {
+ return true;
+ }
+
+ @Override
+ public Block completeBlock() {
+ return this;
+ }
+
+ @Override
+ public Collection<Inline> inline(JParseDown parseDown) {
+ return parseDown.lineElements(text, nonNestables);
+ }
+
+ @Override
+ public String toString() {
+ return "BlockCode{" +
+ "text='" + text + '\'' +
+ '}';
+ }
+ }
+
+ public static class BlockComment extends Block {
+ public String text;
+ public boolean closed = false;
+
+ public BlockComment(String text) {
+ this.text = text;
+ }
+
+ public static Block startBlock(JParseDown parseDown, Line line, Block block) {
+ if (parseDown.markupEscaped || parseDown.safeMode)
+ return null;
+ if (line.text.indexOf("<!--") == 0) {
+ BlockComment b = new BlockComment(line.body);
+ if (line.text.contains("-->"))
+ b.closed = true;
+ return b;
+ } else
+ return null;
+ }
+
+ @Override
+ public boolean isContinuable() {
+ return true;
+ }
+
+ @Override
+ public Block continueBlock(Line line) {
+ if (closed)
+ return null;
+ text += "\n" + line.body;
+ if (line.text.contains("-->"))
+ closed = true;
+ return this;
+ }
+
+ @Override
+ public Collection<Inline> inline(JParseDown parseDown) {
+ return parseDown.lineElements(text, nonNestables);
+ }
+
+ @Override
+ public String toString() {
+ return "BlockComment{" +
+ "text='" + text + '\'' +
+ '}';
+ }
+ }
+
+ public static class BlockFencedCode extends Block {
+ public final char marker;
+ public final int openerLength;
+ public final String infoString;
+ public String text = "";
+ public boolean complete = false;
+
+ public BlockFencedCode(char marker, int openerLength, String infoString) {
+ this.marker = marker;
+ this.openerLength = openerLength;
+ this.infoString = infoString;
+ }
+
+ public static Block startBlock(JParseDown parseDown, Line line, Block block) {
+ char marker = line.text.charAt(0);
+ int openerLength = startSpan(line.text, marker);
+ if (openerLength < 3)
+ return null;
+ String infoString = line.text.substring(openerLength).trim();
+ if (infoString.contains("`"))
+ return null;
+ return new BlockFencedCode(marker, openerLength, infoString);
+ }
+
+ @Override
+ public boolean isContinuable() {
+ return true;
+ }
+
+ @Override
+ public Block continueBlock(Line line) {
+ if (complete)
+ return null;
+ while (interrupted > 0) {
+ text += "\n";
+ interrupted--;
+ }
+ int len = startSpan(line.text, marker);
+ if (len >= openerLength && line.text.substring(len).trim().isEmpty()) {
+ if (!text.isEmpty())
+ text = text.substring(1);
+ complete = true;
+ return this;
+ }
+ text += "\n" + line.body;
+ return this;
+ }
+
+ @Override
+ public boolean isCompletable() {
+ return true;
+ }
+
+ @Override
+ public Block completeBlock() {
+ return this;
+ }
+
+ @Override
+ public Collection<Inline> inline(JParseDown parseDown) {
+ return parseDown.lineElements(text, nonNestables);
+ }
+
+ @Override
+ public String toString() {
+ return "BlockFencedCode{" +
+ "infoString='" + infoString + '\'' +
+ ", text='" + text + '\'' +
+ '}';
+ }
+ }
+
+ public static class BlockHeader extends Block {
+ public final int level;
+ public final String line;
+
+ public BlockHeader(int level, String line) {
+ this.level = level;
+ this.line = line;
+ this.autoBreak = true;
+ }
+
+ public static Block startBlock(JParseDown parseDown, Line line, Block block) {
+ int level = startSpan(line.text, '#');
+ if (level > 6)
+ return null;
+
+ String text = line.text.substring(level);
+ if (parseDown.strictMode && !text.isEmpty() && text.charAt(0) != ' ')
+ return null;
+ text = text.trim();
+
+ return new BlockHeader(level, text);
+ }
+
+ @Override
+ public Collection<Inline> inline(JParseDown parseDown) {
+ return parseDown.lineElements(line, nonNestables);
+ }
+
+ @Override
+ public String toString() {
+ return "BlockHeader{" +
+ "level=" + level +
+ ", line='" + line + '\'' +
+ '}';
+ }
+ }
+
+ public class BlockList extends Block {
+ public int indent;
+ public String pattern;
+ public boolean loose = false;
+
+ public boolean ordered;
+ public String marker;
+ public String markerType;
+ public String markerTypeRegex;
+
+ public LinkedList<String> lines = new LinkedList<>();
+
+ public BlockList() {
+ autoBreak = true;
+ }
+
+ public static Block startBlock(JParseDown parseDown, Line line, Block block) {
+ boolean ordered;
+ String pattern;
+ if (Character.isDigit(line.text.charAt(0))) {
+ ordered = true; // ol
+ pattern = "[0-9]{1,9}+[.\\)]";
+ } else {
+ ordered = false; // ul
+ pattern = "[*+-]";
+ }
+ Matcher m = Pattern.compile("^(" + pattern + "([ ]++|$))(.*+)").matcher(line.text);
+ if (m.find()) {
+ String marker = m.group(1);
+ String body = m.group(3);
+
+ int contentIndent = m.group(2).length();
+ if (contentIndent >= 5) {
+ contentIndent--;
+ marker = marker.substring(0, -contentIndent);
+ while (contentIndent > 0) {
+ body = " " + body;
+ contentIndent--;
+ }
+ } else if (contentIndent == 0) {
+ marker += " ";
+ }
+ String markerWithoutWhitespace = marker.substring(0, marker.indexOf(' '));
+
+ BlockList b = parseDown.new BlockList();
+ b.indent = line.indent;
+ b.pattern = pattern;
+ b.ordered = ordered;
+ b.marker = marker;
+ b.markerType = !ordered ?
+ markerWithoutWhitespace :
+ markerWithoutWhitespace.substring(markerWithoutWhitespace.length() - 1, markerWithoutWhitespace.length());
+ b.markerTypeRegex = Pattern.quote(b.markerType);
+
+ b.lines.add(body);
+
+ return b;
+ } else
+ return null;
+ }
+
+ @Override
+ public boolean isContinuable() {
+ return true;
+ }
+
+ @Override
+ public Block continueBlock(Line line) {
+ if (interrupted > 0 && lines.isEmpty())
+ return null;
+
+ int requiredIndent = indent + marker.length();
+ Matcher m;
+ if (line.indent < requiredIndent && (
+ (ordered && (m = Pattern.compile("^[0-9]++" + markerTypeRegex + "(?:[ ]++(.*)|$)").matcher(line.text)).find()) ||
+ (!ordered && (m = Pattern.compile("^" + markerTypeRegex + "(?:[ ]++(.*)|$)").matcher(line.text)).find())
+ )) {
+ if (interrupted > 0) {
+ lines.add("");
+ loose = true;
+ interrupted = 0;
+ }
+ String text = m.group(1) != null ? m.group(1) : "";
+ indent = line.indent;
+ lines.add(text);
+ return this;
+ } else if (line.indent < requiredIndent && BlockList.startBlock(JParseDown.this, line, null) != null) {
+ return null;
+ }
+
+ if (line.text.charAt(0) == '[' && BlockReference.startBlock(JParseDown.this, line, null) != null) {
+ return this;
+ }
+
+ if (line.indent >= requiredIndent) {
+ if (interrupted > 0) {
+ lines.add("");
+ loose = true;
+ interrupted = 0;
+ }
+ String text = line.body.substring(requiredIndent);
+ lines.add(text);
+ return this;
+ }
+
+ if (interrupted == 0) {
+ String text = line.body.replaceAll("^[ ]{0," + requiredIndent + "}+", "");
+ lines.add(text);
+ return this;
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean isCompletable() {
+ return true;
+ }
+
+ @Override
+ public Block completeBlock() {
+ if (loose) {
+ if (!lines.getLast().isEmpty())
+ lines.add("");
+ }
+ return this;
+ }
+
+ @Override
+ public Collection<Inline> inline(JParseDown parseDown) {
+ LinkedList<Inline> inlines = new LinkedList<>();
+ for (String line : lines) {
+ if (!inlines.isEmpty())
+ inlines.add(new InlineLineBreak());
+ inlines.addAll(parseDown.lineElements(line, nonNestables));
+ }
+ return inlines;
+ }
+
+ @Override
+ public String toString() {
+ return "BlockList{" +
+ "lines=[" + String.join(", ", lines) +
+ "]}";
+ }
+ }
+
+ public static class BlockQuote extends Block {
+ public final LinkedList<String> lines = new LinkedList<>();
+
+ public BlockQuote(String text) {
+ if (text != null)
+ lines.add(text);
+ }
+
+ public static Block startBlock(JParseDown parseDown, Line line, Block block) {
+ Matcher m;
+ if ((m = Pattern.compile("^>[ ]?+(.*+)").matcher(line.text)).find()) {
+ return new BlockQuote(m.group(1));
+ } else
+ return null;
+ }
+
+ @Override
+ public boolean isContinuable() {
+ return true;
+ }
+
+ @Override
+ public Block continueBlock(Line line) {
+ if (interrupted > 0)
+ return null;
+ Matcher m;
+ if (line.text.charAt(0) == '>' && (m = Pattern.compile("^>[ ]?+(.*+)").matcher(line.text)).find()) {
+ lines.add(m.group(1));
+ return this;
+ }
+ if (interrupted == 0) {
+ lines.add(line.text);
+ return this;
+ }
+ return null;
+ }
+
+ @Override
+ public Collection<Inline> inline(JParseDown parseDown) {
+ LinkedList<Inline> inlines = new LinkedList<>();
+ for (String line : lines) {
+ if (!inlines.isEmpty())
+ inlines.add(new InlineLineBreak());
+ inlines.addAll(parseDown.lineElements(line, nonNestables));
+ }
+ return inlines;
+ }
+
+ @Override
+ public String toString() {
+ return "BlockQuote{" +
+ "lines=[" + String.join(", ", lines) +
+ "]}";
+ }
+ }
+
+ public static class BlockRule {
+ public static Block startBlock(JParseDown parseDown, Line line, Block block) {
+ char marker = line.text.charAt(0);
+ int count = startSpan(line.text, marker);
+ if (count >= 3 && line.text.trim().length() == count) {
+ return new BlockHorizontalRule();
+ } else
+ return null;
+ }
+ }
+
+ public static class BlockHorizontalRule extends Block {
+ public BlockHorizontalRule() {
+ autoBreak = true;
+ }
+
+ @Override
+ public Collection<Inline> inline(JParseDown parseDown) {
+ return Collections.singletonList(new InlineHorizontalRule());
+ }
+
+ @Override
+ public String toString() {
+ return "BlockHorizontalRule{}";
+ }
+ }
+
+ public static class InlineLineBreak extends Inline {
+ @Override
+ public String toString() {
+ return "InlineLineBreak{}";
+ }
+ }
+
+ public static class InlineHorizontalRule extends Inline {
+ @Override
+ public String toString() {
+ return "InlineHorizontalRule{}";
+ }
+ }
+
+ public static String regexHtmlAttribute = "[a-zA-Z_:][\\w:.-]*+(?:\\s*+=\\s*+(?:[^\"\\'=<>`\\s]+|\"[^\"]*+\"|\\'[^\\']*+\\'))?+";
+
+ public static class BlockReference extends Block {
+ public final String id;
+ public final ReferenceData data;
+
+ public BlockReference(String id, ReferenceData data) {
+ this.id = id;
+ this.data = data;
+ }
+
+ public static Block startBlock(JParseDown parseDown, Line line, Block block) {
+ Matcher m;
+ if (line.text.indexOf(']') >= 0 && (m = Pattern.compile("^\\[(.+?)\\]:[ ]*+<?(\\S+?)>?(?:[ ]+[\"\\'(](.+)[\"\\')])?[ ]*+$").matcher(line.text)).find()) {
+ String id = m.group(1).toLowerCase();
+ ReferenceData data = new ReferenceData(parseDown.convertUrl(m.group(2)), m.group(3));
+ parseDown.referenceDefinitio