[javadoc] Region rendering for external snippets (IDEA-314507)

Java highlighting is turned off when replace/highlight/link are used

GitOrigin-RevId: eb63ab475dadfa4b3f1e26478ddef18b4807955f
This commit is contained in:
Tagir Valeev
2023-03-10 09:49:37 +00:00
committed by intellij-monorepo-bot
parent 2e3c106b5d
commit 542e82b9d3
14 changed files with 388 additions and 128 deletions
@@ -22,6 +22,7 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.DefaultLanguageHighlighterColors;
import com.intellij.openapi.editor.HighlighterColors;
import com.intellij.openapi.editor.colors.CodeInsightColors;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.markup.TextAttributes;
@@ -72,11 +73,15 @@ import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.*;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.intellij.codeInsight.javadoc.SnippetMarkup.*;
public class JavaDocInfoGenerator {
private static final Logger LOG = Logger.getInstance(JavaDocInfoGenerator.class);
@@ -1817,7 +1822,7 @@ public class JavaDocInfoGenerator {
List<Pair<PsiElement, TextRange>> files = InjectedLanguageManager.getInstance(snippetTag.getProject()).getInjectedPsiFiles(snippetTag);
PsiElement element = files != null ? files.get(0).first : null;
if (element != null && element.getLanguage().isKindOf(JavaLanguage.INSTANCE)) {
generateJavaSnippetBody(buffer, element);
generateJavaSnippetBody(buffer, element, e -> true);
}
else {
for (PsiElement contentElement : body.getContent()) {
@@ -1827,6 +1832,9 @@ public class JavaDocInfoGenerator {
buffer.append("</pre>");
} else {
PsiSnippetAttribute[] attributes = value.getAttributeList().getAttributes();
PsiSnippetAttribute regionAttribute = ContainerUtil.find(attributes, attr -> attr.getName().equals(PsiSnippetAttribute.REGION_ATTRIBUTE));
String region = regionAttribute == null || regionAttribute.getValue() == null ? null :
regionAttribute.getValue().getValue();
for (PsiSnippetAttribute attribute : attributes) {
PsiSnippetAttributeValue attrValue = attribute.getValue();
if (attrValue != null) {
@@ -1835,16 +1843,11 @@ public class JavaDocInfoGenerator {
PsiElement resolved = ref.resolve();
if (resolved instanceof PsiFile file) {
buffer.append("<pre>");
if (file instanceof PsiJavaFile) {
generateJavaSnippetBody(buffer, file);
}
else {
buffer.append(file.getText());
}
generateSnippetBody(buffer, file, region);
buffer.append("</pre>");
} else {
buffer.append(getSpanForUnresolvedItem()).append(JavaBundle.message("javadoc.snippet.not.found"))
.append(attrValue.getValue()).append("</span>");
buffer.append(getSpanForUnresolvedItem()).append(JavaBundle.message("javadoc.snippet.not.found", attrValue.getValue()))
.append("</span>");
}
}
}
@@ -1852,20 +1855,84 @@ public class JavaDocInfoGenerator {
}
}
private void generateJavaSnippetBody(@NotNull StringBuilder buffer, PsiElement element) {
private void generateSnippetBody(@NotNull StringBuilder buffer, @NotNull PsiFile file, @Nullable String region) {
SnippetMarkup markup = forFile(file);
if (!markup.hasMarkup(region)) {
TextRange range = markup.getRegionRange(region);
if (range == null) {
buffer.append(getSpanForUnresolvedItem()).append(JavaBundle.message("javadoc.snippet.region.not.found", region))
.append("</span>");
} else if (file instanceof PsiJavaFile) {
// Normal Java highlighting is only for regions without markup
generateJavaSnippetBody(buffer, file,
e -> {
TextRange textRange = e.getTextRange();
return range.intersects(textRange) && markup.isTextPart(textRange);
});
} else {
buffer.append(markup.getTextWithoutMarkup(region));
}
return;
}
markup.visitSnippet(region, new SnippetVisitor() {
@Override
public void visitPlainText(@NotNull PlainText plainText,
@NotNull List<@NotNull LocationMarkupNode> activeNodes) {
String content = plainText.content();
for (LocationMarkupNode node : activeNodes) {
UnaryOperator<String> replacement;
if (node instanceof Replace replace) {
replacement = orig -> replace.replacement();
}
else if (node instanceof Highlight highlight) {
replacement = switch (highlight.type()) {
case BOLD -> orig -> "<b>"+orig+"</b>";
case ITALIC -> orig -> "<i>"+orig+"</i>";
case HIGHLIGHTED -> {
TextAttributes attributes =
EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.TEXT_SEARCH_RESULT_ATTRIBUTES);
yield orig -> getStyledSpan(true, attributes, orig);
}
};
}
else if (node instanceof Link link) {
replacement = orig -> {
StringBuilder buffer = new StringBuilder();
DocumentationManagerUtil.createHyperlink(buffer, link.target(), orig, link.linkType() == LinkType.LINKPLAIN);
return buffer.toString();
};
}
else {
throw new AssertionError(node.toString());
}
if (node.regex() != null) {
content = content.replaceAll(node.regex(), replacement.apply("$0"));
}
else if (node.substring() != null) {
content = content.replace(node.substring(), replacement.apply(node.substring()));
}
else {
content = replacement.apply(content);
}
}
buffer.append(content);
}
@Override
public void visitError(@NotNull ErrorMarkup errorMarkup) {
buffer.append(getSpanForUnresolvedItem()).append("[").append(errorMarkup.message()).append("]</span>");
}
});
}
private void generateJavaSnippetBody(@NotNull StringBuilder buffer, @NotNull PsiElement element, @NotNull Predicate<PsiElement> filter) {
PsiFile containingFile = element.getContainingFile();
SyntaxTraverser.psiTraverser(containingFile)
.filter(e -> e.getFirstChild() == null)
.filter(e -> e.getTextLength() > 0)
.filter(filter::test)
.forEach(e -> {
String text = e.getText();
if (text.isEmpty()) return;
if (e instanceof PsiComment && text.startsWith("//")) { //todo: ignore markup as in JDK now
int idx = text.lastIndexOf("// @");
if (idx >= 0) {
buffer.append(text, 0, idx);
return;
}
}
JavaDocHighlightingManager manager = getHighlightingManager();
if (e instanceof PsiIdentifier) {
PsiElement parent = e.getParent();
@@ -6,10 +6,14 @@ import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.javadoc.PsiSnippetDocTagBody;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Stack;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -21,11 +25,13 @@ import java.util.stream.Stream;
public class SnippetMarkup {
private final @NotNull List<@NotNull MarkupNode> myNodes;
private final @NotNull Map<@NotNull String, @NotNull MarkupNode> myRegionStarts;
private final @NotNull BitSet myTextOffsets = new BitSet();
// \\S+ = language-dependent comment start token, like "//" or "#"
private static final Pattern MARKUP_TAG = Pattern.compile("\\S+\\s*@(start|end|highlight|replace|link)\\s*(\\S.+?)?\\s*(:?)\\s*$");
private static final Pattern ATTRIBUTE = Pattern.compile("(\\w+)\\s*(=\\s*(['\"]?)(\\S+)\\3)?");
private static final Pattern ATTRIBUTE = Pattern.compile("(\\w+)\\s*(=\\s*('([^']*)'|\"([^\"]*)\"|(\\S*)))?");
private static final Map<String, Set<String>> ALLOWED_ATTRS =
Map.of(
@@ -38,6 +44,70 @@ public class SnippetMarkup {
private SnippetMarkup(@NotNull List<@NotNull MarkupNode> nodes) {
myNodes = nodes;
myRegionStarts = StreamEx.of(nodes)
.mapToEntry(n -> n instanceof StartRegion start ? start.region() :
n instanceof LocationMarkupNode node ? node.region() :
null, Function.identity())
.removeKeys(k -> k == null || k.isEmpty())
.distinctKeys()
.toMap();
StreamEx.of(nodes)
.select(PlainText.class)
.forEach(text -> myTextOffsets.set(text.range().getStartOffset(), text.range().getEndOffset()));
}
/**
* @param region region to test; null for the whole snippet
* @return true if any of {@code @replacement}, {@code @highlight}, or {@code @link} tags exist within the region
*/
public boolean hasMarkup(@Nullable String region) {
var visitor = new SnippetVisitor() {
boolean hasMarkup = false;
@Override
public void visitPlainText(@NotNull PlainText plainText, @NotNull List<@NotNull LocationMarkupNode> activeNodes) {
hasMarkup |= !activeNodes.isEmpty();
}
};
visitSnippet(region, visitor);
return visitor.hasMarkup;
}
public @Nullable TextRange getRegionRange(@Nullable String region) {
var visitor = new SnippetVisitor() {
TextRange myRange = null;
@Override
public void visitPlainText(@NotNull PlainText plainText, @NotNull List<@NotNull LocationMarkupNode> activeNodes) {
myRange = myRange == null ? plainText.range() : myRange.union(plainText.range());
}
};
visitSnippet(region, visitor);
return visitor.myRange;
}
/**
* @param range range to test
* @return true if the range completely or partially contains plain text chunk(s) to display
*/
public boolean isTextPart(@NotNull TextRange range) {
int nextSetBit = myTextOffsets.nextSetBit(range.getStartOffset());
return nextSetBit != -1 && nextSetBit < range.getEndOffset();
}
/**
* @param region region to extract; null for the whole snippet
* @return text of a given region, ignoring markup
*/
public String getTextWithoutMarkup(@Nullable String region) {
StringBuilder result = new StringBuilder();
visitSnippet(region, new SnippetVisitor() {
@Override
public void visitPlainText(@NotNull PlainText plainText, @NotNull List<@NotNull LocationMarkupNode> activeNodes) {
result.append(plainText.content());
}
});
return result.toString();
}
/**
@@ -63,6 +133,7 @@ public class SnippetMarkup {
/**
* @return range in the parent PSI element that corresponds to this element
*/
@Contract(pure = true)
@NotNull TextRange range();
}
@@ -73,17 +144,20 @@ public class SnippetMarkup {
/**
* @return substring text to which this node is applicable
*/
@Contract(pure = true)
@Nullable String substring();
/**
* @return regular expression, so this node is applicable to the matching substrings
*/
@Contract(pure = true)
@Nullable String regex();
/**
* @return if present, defines a region name to which this tag is applicable. If null, then the tag is applicable to the next
* {@link PlainText} node.
*/
@Contract(pure = true)
@Nullable String region();
}
@@ -93,21 +167,6 @@ public class SnippetMarkup {
* @param content text content (excluding the final linebreak)
*/
public record PlainText(@NotNull TextRange range, @NotNull String content) implements MarkupNode {
public PlainText {
if (range.getLength() != content.length()) {
throw new IllegalArgumentException();
}
}
/**
* @param start start offset of substring
* @param end end offset of substring
* @return PlainText object that corresponds to the substring of this object
*/
@NotNull PlainText substring(@SuppressWarnings("SameParameterValue") int start, int end) {
int offset = range().getStartOffset();
return new PlainText(TextRange.create(offset + start, offset + end), content.substring(start, end));
}
}
private record Attribute(@NotNull TextRange range, @NotNull String key, @Nullable String value) implements MarkupNode {
@@ -167,6 +226,10 @@ public class SnippetMarkup {
public static @NotNull SnippetMarkup parse(@NotNull String text) {
return parse(preparse(text));
}
public static @NotNull SnippetMarkup forFile(@NotNull PsiFile file) {
return CachedValuesManager.getCachedValue(file, () -> new CachedValueProvider.Result<>(parse(file.getText()), file));
}
public static @NotNull SnippetMarkup parse(@NotNull PsiSnippetDocTagBody body) {
return parse(preparse(body));
@@ -182,7 +245,7 @@ public class SnippetMarkup {
break;
}
else {
output.add(new PlainText(TextRange.create(pos, nextPos), text.substring(pos, nextPos)));
output.add(new PlainText(TextRange.create(pos, nextPos + 1), text.substring(pos, nextPos + 1)));
pos = nextPos + 1;
}
}
@@ -206,13 +269,14 @@ public class SnippetMarkup {
Matcher matcher = MARKUP_TAG.matcher(content);
if (!matcher.find()) return Stream.of(text);
int start = matcher.start();
PlainText prev = text.substring(0, start);
int offset = text.range().getStartOffset();
PlainText prev = new PlainText(TextRange.create(offset, offset + start), text.content.substring(0, start) + "\n");
String tagName = matcher.group(1);
List<MarkupNode> attrs = parseAttributes(text.range().getStartOffset() + matcher.start(2), matcher.group(2),
ALLOWED_ATTRS.get(tagName));
List<ErrorMarkup> errors = new ArrayList<>(ContainerUtil.filterIsInstance(attrs, ErrorMarkup.class));
Map<String, String> attrValues = StreamEx.of(attrs).select(Attribute.class).toMap(Attribute::key, Attribute::value);
TextRange range = TextRange.create(matcher.start(1), matcher.end()).shiftRight(text.range().getStartOffset());
TextRange range = TextRange.create(matcher.start(1), matcher.end()).shiftRight(offset);
boolean hasColon = !matcher.group(3).isEmpty();
MarkupNode node = switch (tagName) {
case "start" -> {
@@ -302,7 +366,11 @@ public class SnippetMarkup {
result.add(new ErrorMarkup(range, JavaBundle.message("javadoc.snippet.error.duplicate.attribute", key)));
}
else {
result.add(new Attribute(range, key, Objects.requireNonNullElse(matcher.group(4), "")));
String value = matcher.group(4);
if (value == null) value = matcher.group(5);
if (value == null) value = matcher.group(6);
if (value == null) value = "";
result.add(new Attribute(range, key, value));
}
pos = matcher.end();
}
@@ -310,11 +378,26 @@ public class SnippetMarkup {
}
public interface SnippetVisitor {
/**
* Called for every plain text chunk
* @param plainText text chunk
* @param activeNodes active markup nodes to apply to this chunk
*/
void visitPlainText(@NotNull PlainText plainText, @NotNull List<@NotNull LocationMarkupNode> activeNodes);
void visitError(@NotNull ErrorMarkup errorMarkup);
/**
* Called for every markup error visited
* @param errorMarkup error information
*/
default void visitError(@NotNull ErrorMarkup errorMarkup) {}
}
/**
* Visit elements of the snippet within given region
*
* @param region region to visit
* @param visitor visitor to use
*/
public void visitSnippet(@Nullable String region, SnippetVisitor visitor) {
Stack<String> regions = new Stack<>();
Map<String, List<LocationMarkupNode>> active = new LinkedHashMap<>();
@@ -15,6 +15,7 @@ import org.jetbrains.annotations.Nullable;
public interface PsiSnippetAttribute extends PsiElement {
String CLASS_ATTRIBUTE = "class";
String FILE_ATTRIBUTE = "file";
String REGION_ATTRIBUTE = "region";
String SNIPPETS_FOLDER = "snippet-files";
PsiSnippetAttribute[] EMPTY_ARRAY = new PsiSnippetAttribute[0];
@@ -0,0 +1,3 @@
<html><head><base href="placeholder"></head><body><div class='definition'><pre><span style="color:#000080;font-weight:bold;">class</span> <span style="color:#000000;">NoRegion</span></pre></div><div class='content'>
<pre><span style="color:#ff0000">Region not found: noregion</span></pre>
</div><table class='sections'><p></table>
@@ -0,0 +1,5 @@
/**
* {@snippet class=Region region=noregion}
*/
class NoRegion {
}
@@ -0,0 +1,7 @@
<html><head><base href="placeholder"></head><body><div class='definition'><pre><span style="color:#000080;font-weight:bold;">class</span> <span style="color:#000000;">Region</span></pre></div><div class='content'>
<pre> public static void main(String[] args) {
...
System.out.println("<span style="color:#000000;background-color:#ffff00;">Hello</span>");
}
</pre>
</div><table class='sections'><p></table>
@@ -0,0 +1,5 @@
/**
* {@snippet class=Region region=main}
*/
class Region {
}
@@ -0,0 +1,7 @@
<html><head><base href="placeholder"></head><body><div class='definition'><pre><span style="color:#000080;font-weight:bold;">class</span> <span style="color:#000000;">RegionNoMarkup</span></pre></div><div class='content'>
<pre>
<span style="color:#000080;font-weight:bold;">public</span> <span style="color:#000080;font-weight:bold;">void</span> test() <span style="">{</span>
<a href="psi_element://java.lang.System"><code><span style="color:#000000;">System</span></code></a><span style="">.</span><a href="psi_element://java.lang.System#out"><code><span style="color:#660e7a;">out</span></code></a><span style="">.</span><a href="psi_element://java.io.PrintStream#println(java.lang.String)"><code><span style="color:#000000;">println</span></code></a>("Region no markup");
<span style="">}</span>
</pre>
</div><table class='sections'><p></table>
@@ -0,0 +1,5 @@
/**
* {@snippet class=Region region=test}
*/
class RegionNoMarkup {
}
@@ -0,0 +1,14 @@
public class Region {
// @start region=main
public static void main(String[] args) {
code: // @replace regex=code: replacement="..."
System.out.println("Hello"); // @highlight substring=Hello
}
// @end
// @start region=test
public void test() {
System.out.println("Region no markup");
}
// @end region=test
}
@@ -1,5 +1,5 @@
<html><head><base href="placeholder"></head><body><div class='definition'><pre><span style="color:#000080;font-weight:bold;">class</span> <span style="color:#000000;">Main</span></pre></div><div class='content'>
Attributes:
<pre><a href="psi_element://java.lang.System"><code><span style="color:#000000;">System</span></code></a><span style="">.</span><a href="psi_element://java.lang.System#out"><code><span style="color:#660e7a;">out</span></code></a><span style="">.</span><a href="psi_element://java.io.PrintStream#println(java.lang.String)"><code><span style="color:#000000;">println</span></code></a>("Hello, world!");
<span style="color:#000080;font-weight:bold;">int</span> <span style="color:#000000;">i</span> <span style="">=</span> 0; //empty line </pre>
<pre><a href="psi_element://java.lang.System"><code><span style="color:#000000;">System</span></code></a><span style="">.</span><a href="psi_element://java.lang.System#out"><code><span style="color:#660e7a;">out</span></code></a><span style="">.</span><a href="psi_element://java.io.PrintStream#println(java.lang.String)"><code><span style="color:#000000;">println</span></code></a>("Hello, world!"); // @link substring=System target=java.lang.System
<span style="color:#000080;font-weight:bold;">int</span> <span style="color:#000000;">i</span> <span style="">=</span> 0; //empty line // @highlight substring=0</pre>
</div><table class='sections'><p></table>
@@ -128,6 +128,9 @@ public class JavaDocInfoGeneratorTest extends JavaCodeInsightTestCase {
public void testInlineTagSnippetWithoutBody() { doTestClass(); }
public void testExternalSnippet() {
createProjectStructure(getTestDataPath() + TEST_DATA_FOLDER + "externalSnippet");
verifyJavadocFor("Region");
verifyJavadocFor("RegionNoMarkup");
verifyJavadocFor("NoRegion");
verifyJavadocFor("Main");
verifyJavadocFor("TextFile");
verifyJavadocFor("Unresolved");
@@ -20,7 +20,8 @@ public class SnippetMarkupTest {
"""
Hello!!!
World!!!""", """
PlainText[range=(0,8), content=Hello!!!]
PlainText[range=(0,9), content=Hello!!!
]
PlainText[range=(9,17), content=World!!!]""");
}
@@ -42,21 +43,29 @@ public class SnippetMarkupTest {
// @end
}
""", """
PlainText[range=(0,16), content=public class X {]
StartRegion[range=(23,40), region=test]
PlainText[range=(41,56), content= void main() {]
Replace[range=(94,133), substring=Hello, regex=null, region=null, replacement=...]
PlainText[range=(57,90), content= System.out.println("Hello"); ]
Highlight[range=(142,169), substring=Hello, regex=null, region=null, type=HIGHLIGHTED]
PlainText[range=(170,202), content= System.out.println("Hello");]
Highlight[range=(211,228), substring=null, regex=null, region=, type=HIGHLIGHTED]
PlainText[range=(229,262), content= System.out.println("Hello1");]
Link[range=(301,344), substring=null, regex=H\\w+, region=null, target=Hello, linkType=LINKPLAIN]
PlainText[range=(263,297), content= System.out.println("Hello2"); ]
EndRegion[range=(353,356), region=null]
PlainText[range=(357,360), content= }]
EndRegion[range=(367,370), region=null]
PlainText[range=(371,372), content=}]
PlainText[range=(0,17), content=public class X {
]
StartRegion[range=(23,41), region=test]
PlainText[range=(41,57), content= void main() {
]
Replace[range=(94,134), substring=Hello, regex=null, region=null, replacement=...]
PlainText[range=(57,90), content= System.out.println("Hello");\s
]
Highlight[range=(142,170), substring=Hello, regex=null, region=null, type=HIGHLIGHTED]
PlainText[range=(170,203), content= System.out.println("Hello");
]
Highlight[range=(211,229), substring=null, regex=null, region=, type=HIGHLIGHTED]
PlainText[range=(229,263), content= System.out.println("Hello1");
]
Link[range=(301,345), substring=null, regex=H\\w+, region=null, target=Hello, linkType=LINKPLAIN]
PlainText[range=(263,297), content= System.out.println("Hello2");\s
]
EndRegion[range=(353,357), region=null]
PlainText[range=(357,361), content= }
]
EndRegion[range=(367,371), region=null]
PlainText[range=(371,373), content=}
]
PlainText[range=(373,373), content=]""");
}
@@ -74,23 +83,29 @@ public class SnippetMarkupTest {
// @end test
}
""", """
PlainText[range=(0,16), content=public class X {]
ErrorMarkup[range=(23,28), message=@start: missing 'region' attribute]
PlainText[range=(29,44), content= void main() {]
Replace[range=(82,128), substring=Hello, regex=null, region=null, replacement=]
PlainText[range=(0,17), content=public class X {
]
ErrorMarkup[range=(23,29), message=@start: missing 'region' attribute]
PlainText[range=(29,45), content= void main() {
]
Replace[range=(82,129), substring=Hello, regex=null, region=null, replacement=]
ErrorMarkup[range=(108,128), message=Unsupported attribute: 'substring2']
ErrorMarkup[range=(82,128), message=@replace: missing 'replacement' attribute]
PlainText[range=(45,78), content= System.out.println("Hello"); ]
Highlight[range=(137,161), substring=null, regex=null, region=null, type=HIGHLIGHTED]
ErrorMarkup[range=(82,129), message=@replace: missing 'replacement' attribute]
PlainText[range=(45,78), content= System.out.println("Hello");\s
]
Highlight[range=(137,162), substring=null, regex=null, region=null, type=HIGHLIGHTED]
ErrorMarkup[range=(147,161), message=Unknown type 'underline'; only 'bold', 'italic', and 'highlighted' are supported]
Link[range=(199,213), substring=null, regex=null, region=null, target=, linkType=LINK]
ErrorMarkup[range=(199,213), message=@link: missing 'target' attribute]
Link[range=(199,214), substring=null, regex=null, region=null, target=, linkType=LINK]
ErrorMarkup[range=(199,214), message=@link: missing 'target' attribute]
ErrorMarkup[range=(204,213), message=Unknown type 'pfff'; only 'link' and 'linkplain' are supported]
PlainText[range=(162,195), content= System.out.println("Hello"); ]
PlainText[range=(214,217), content= }]
EndRegion[range=(224,232), region=null]
PlainText[range=(162,195), content= System.out.println("Hello");\s
]
PlainText[range=(214,218), content= }
]
EndRegion[range=(224,233), region=null]
ErrorMarkup[range=(228,232), message=Unsupported attribute: 'test']
PlainText[range=(233,234), content=}]
PlainText[range=(233,235), content=}
]
PlainText[range=(235,235), content=]""");
}
@@ -100,9 +115,9 @@ public class SnippetMarkupTest {
public class Hello {
// @start region=main
public static void main(String[] args) {
code: // @replace regex='code:' replacement="..."
// @highlight substring="Hello"
System.out.println("Hello");
code: // @replace regex='code:' replacement=""
// @highlight substring="Hello World"
System.out.println("Hello World");
// @highlight region substring="idx"
for(int idx=0; idx<10; idx++) {
System.out.println(idx);
@@ -113,43 +128,71 @@ public class SnippetMarkupTest {
}
""";
testParsing(text, """
PlainText[range=(0,20), content=public class Hello {]
StartRegion[range=(27,44), region=main]
PlainText[range=(45,87), content= public static void main(String[] args) {]
Replace[range=(102,141), substring=null, regex=code:, region=null, replacement=...]
PlainText[range=(88,98), content= code: ]
Highlight[range=(150,177), substring=Hello, regex=null, region=null, type=HIGHLIGHTED]
PlainText[range=(178,210), content= System.out.println("Hello");]
Highlight[range=(219,251), substring=idx, regex=null, region=, type=HIGHLIGHTED]
PlainText[range=(252,287), content= for(int idx=0; idx<10; idx++) {]
PlainText[range=(288,318), content= System.out.println(idx);]
PlainText[range=(319,324), content= }]
EndRegion[range=(333,336), region=null]
PlainText[range=(337,340), content= }]
EndRegion[range=(347,350), region=null]
PlainText[range=(351,352), content=}]
PlainText[range=(353,353), content=]""");
PlainText[range=(0,21), content=public class Hello {
]
StartRegion[range=(27,45), region=main]
PlainText[range=(45,88), content= public static void main(String[] args) {
]
Replace[range=(102,139), substring=null, regex=code:, region=null, replacement=]
PlainText[range=(88,98), content= code:\s
]
Highlight[range=(147,181), substring=Hello World, regex=null, region=null, type=HIGHLIGHTED]
PlainText[range=(181,220), content= System.out.println("Hello World");
]
Highlight[range=(228,261), substring=idx, regex=null, region=, type=HIGHLIGHTED]
PlainText[range=(261,297), content= for(int idx=0; idx<10; idx++) {
]
PlainText[range=(297,328), content= System.out.println(idx);
]
PlainText[range=(328,334), content= }
]
EndRegion[range=(342,346), region=null]
PlainText[range=(346,350), content= }
]
EndRegion[range=(356,360), region=null]
PlainText[range=(360,362), content=}
]
PlainText[range=(362,362), content=]""");
testVisitor(text, null, """
public class Hello {
public static void main(String[] args) {
code: // [Replace[range=(102,141), substring=null, regex=code:, region=null, replacement=...]]
System.out.println("Hello"); // [Highlight[range=(150,177), substring=Hello, regex=null, region=null, type=HIGHLIGHTED]]
for(int idx=0; idx<10; idx++) { // [Highlight[range=(219,251), substring=idx, regex=null, region=, type=HIGHLIGHTED]]
System.out.println(idx); // [Highlight[range=(219,251), substring=idx, regex=null, region=, type=HIGHLIGHTED]]
} // [Highlight[range=(219,251), substring=idx, regex=null, region=, type=HIGHLIGHTED]]
// [Replace[range=(102,139), substring=null, regex=code:, region=null, replacement=]]
code:\s
// [Highlight[range=(147,181), substring=Hello World, regex=null, region=null, type=HIGHLIGHTED]]
System.out.println("Hello World");
// [Highlight[range=(228,261), substring=idx, regex=null, region=, type=HIGHLIGHTED]]
for(int idx=0; idx<10; idx++) {
// [Highlight[range=(228,261), substring=idx, regex=null, region=, type=HIGHLIGHTED]]
System.out.println(idx);
// [Highlight[range=(228,261), substring=idx, regex=null, region=, type=HIGHLIGHTED]]
}
}
}
""");
testVisitor(text, "main", """
public static void main(String[] args) {
code: // [Replace[range=(102,141), substring=null, regex=code:, region=null, replacement=...]]
System.out.println("Hello"); // [Highlight[range=(150,177), substring=Hello, regex=null, region=null, type=HIGHLIGHTED]]
for(int idx=0; idx<10; idx++) { // [Highlight[range=(219,251), substring=idx, regex=null, region=, type=HIGHLIGHTED]]
System.out.println(idx); // [Highlight[range=(219,251), substring=idx, regex=null, region=, type=HIGHLIGHTED]]
} // [Highlight[range=(219,251), substring=idx, regex=null, region=, type=HIGHLIGHTED]]
// [Replace[range=(102,139), substring=null, regex=code:, region=null, replacement=]]
code:\s
// [Highlight[range=(147,181), substring=Hello World, regex=null, region=null, type=HIGHLIGHTED]]
System.out.println("Hello World");
// [Highlight[range=(228,261), substring=idx, regex=null, region=, type=HIGHLIGHTED]]
for(int idx=0; idx<10; idx++) {
// [Highlight[range=(228,261), substring=idx, regex=null, region=, type=HIGHLIGHTED]]
System.out.println(idx);
// [Highlight[range=(228,261), substring=idx, regex=null, region=, type=HIGHLIGHTED]]
}
}
""");
assertEquals("""
public static void main(String[] args) {
code:\s
System.out.println("Hello World");
for(int idx=0; idx<10; idx++) {
System.out.println(idx);
}
}
""", SnippetMarkup.parse(text).getTextWithoutMarkup("main"));
}
@Test
@@ -167,36 +210,53 @@ public class SnippetMarkupTest {
// @end region="r2"
""";
testParsing(text, """
Highlight[range=(4,51), substring=xxx, regex=null, region=r1, type=BOLD]
PlainText[range=(52,64), content=r1 start xxx]
Highlight[range=(69,118), substring=yyy, regex=null, region=r2, type=ITALIC]
PlainText[range=(119,131), content=r2 start yyy]
Highlight[range=(136,179), substring=zzz, regex=null, region=null, type=HIGHLIGHTED]
PlainText[range=(180,210), content=one line with zzz highlighting]
PlainText[range=(211,220), content=r1+r2 xxx]
EndRegion[range=(225,240), region=r1]
PlainText[range=(241,257), content=r2 continues yyy]
EndRegion[range=(262,277), region=r2]
Highlight[range=(4,52), substring=xxx, regex=null, region=r1, type=BOLD]
PlainText[range=(52,65), content=r1 start xxx
]
Highlight[range=(69,119), substring=yyy, regex=null, region=r2, type=ITALIC]
PlainText[range=(119,132), content=r2 start yyy
]
Highlight[range=(136,180), substring=zzz, regex=null, region=null, type=HIGHLIGHTED]
PlainText[range=(180,211), content=one line with zzz highlighting
]
PlainText[range=(211,221), content=r1+r2 xxx
]
EndRegion[range=(225,241), region=r1]
PlainText[range=(241,258), content=r2 continues yyy
]
EndRegion[range=(262,278), region=r2]
PlainText[range=(278,278), content=]""");
testVisitor(text, null, """
r1 start xxx // [Highlight[range=(4,51), substring=xxx, regex=null, region=r1, type=BOLD]]
r2 start yyy // [Highlight[range=(4,51), substring=xxx, regex=null, region=r1, type=BOLD], Highlight[range=(69,118), substring=yyy, regex=null, region=r2, type=ITALIC]]
one line with zzz highlighting // [Highlight[range=(4,51), substring=xxx, regex=null, region=r1, type=BOLD], Highlight[range=(69,118), substring=yyy, regex=null, region=r2, type=ITALIC], Highlight[range=(136,179), substring=zzz, regex=null, region=null, type=HIGHLIGHTED]]
r1+r2 xxx // [Highlight[range=(4,51), substring=xxx, regex=null, region=r1, type=BOLD], Highlight[range=(69,118), substring=yyy, regex=null, region=r2, type=ITALIC]]
r2 continues yyy // [Highlight[range=(69,118), substring=yyy, regex=null, region=r2, type=ITALIC]]
// [Highlight[range=(4,52), substring=xxx, regex=null, region=r1, type=BOLD]]
r1 start xxx
// [Highlight[range=(4,52), substring=xxx, regex=null, region=r1, type=BOLD], Highlight[range=(69,119), substring=yyy, regex=null, region=r2, type=ITALIC]]
r2 start yyy
// [Highlight[range=(4,52), substring=xxx, regex=null, region=r1, type=BOLD], Highlight[range=(69,119), substring=yyy, regex=null, region=r2, type=ITALIC], Highlight[range=(136,180), substring=zzz, regex=null, region=null, type=HIGHLIGHTED]]
one line with zzz highlighting
// [Highlight[range=(4,52), substring=xxx, regex=null, region=r1, type=BOLD], Highlight[range=(69,119), substring=yyy, regex=null, region=r2, type=ITALIC]]
r1+r2 xxx
// [Highlight[range=(69,119), substring=yyy, regex=null, region=r2, type=ITALIC]]
r2 continues yyy
""");
testVisitor(text, "r1", """
r1 start xxx // [Highlight[range=(4,51), substring=xxx, regex=null, region=r1, type=BOLD]]
r2 start yyy // [Highlight[range=(4,51), substring=xxx, regex=null, region=r1, type=BOLD], Highlight[range=(69,118), substring=yyy, regex=null, region=r2, type=ITALIC]]
one line with zzz highlighting // [Highlight[range=(4,51), substring=xxx, regex=null, region=r1, type=BOLD], Highlight[range=(69,118), substring=yyy, regex=null, region=r2, type=ITALIC], Highlight[range=(136,179), substring=zzz, regex=null, region=null, type=HIGHLIGHTED]]
r1+r2 xxx // [Highlight[range=(4,51), substring=xxx, regex=null, region=r1, type=BOLD], Highlight[range=(69,118), substring=yyy, regex=null, region=r2, type=ITALIC]]
// [Highlight[range=(4,52), substring=xxx, regex=null, region=r1, type=BOLD]]
r1 start xxx
// [Highlight[range=(4,52), substring=xxx, regex=null, region=r1, type=BOLD], Highlight[range=(69,119), substring=yyy, regex=null, region=r2, type=ITALIC]]
r2 start yyy
// [Highlight[range=(4,52), substring=xxx, regex=null, region=r1, type=BOLD], Highlight[range=(69,119), substring=yyy, regex=null, region=r2, type=ITALIC], Highlight[range=(136,180), substring=zzz, regex=null, region=null, type=HIGHLIGHTED]]
one line with zzz highlighting
// [Highlight[range=(4,52), substring=xxx, regex=null, region=r1, type=BOLD], Highlight[range=(69,119), substring=yyy, regex=null, region=r2, type=ITALIC]]
r1+r2 xxx
""");
testVisitor(text, "r2", """
r2 start yyy // [Highlight[range=(4,51), substring=xxx, regex=null, region=r1, type=BOLD], Highlight[range=(69,118), substring=yyy, regex=null, region=r2, type=ITALIC]]
one line with zzz highlighting // [Highlight[range=(4,51), substring=xxx, regex=null, region=r1, type=BOLD], Highlight[range=(69,118), substring=yyy, regex=null, region=r2, type=ITALIC], Highlight[range=(136,179), substring=zzz, regex=null, region=null, type=HIGHLIGHTED]]
r1+r2 xxx // [Highlight[range=(4,51), substring=xxx, regex=null, region=r1, type=BOLD], Highlight[range=(69,118), substring=yyy, regex=null, region=r2, type=ITALIC]]
r2 continues yyy // [Highlight[range=(69,118), substring=yyy, regex=null, region=r2, type=ITALIC]]
// [Highlight[range=(4,52), substring=xxx, regex=null, region=r1, type=BOLD], Highlight[range=(69,119), substring=yyy, regex=null, region=r2, type=ITALIC]]
r2 start yyy
// [Highlight[range=(4,52), substring=xxx, regex=null, region=r1, type=BOLD], Highlight[range=(69,119), substring=yyy, regex=null, region=r2, type=ITALIC], Highlight[range=(136,180), substring=zzz, regex=null, region=null, type=HIGHLIGHTED]]
one line with zzz highlighting
// [Highlight[range=(4,52), substring=xxx, regex=null, region=r1, type=BOLD], Highlight[range=(69,119), substring=yyy, regex=null, region=r2, type=ITALIC]]
r1+r2 xxx
// [Highlight[range=(69,119), substring=yyy, regex=null, region=r2, type=ITALIC]]
r2 continues yyy
""");
}
@@ -211,11 +271,10 @@ public class SnippetMarkupTest {
@Override
public void visitPlainText(@NotNull PlainText plainText,
@NotNull List<@NotNull LocationMarkupNode> activeNodes) {
sb.append(plainText.content());
if (!activeNodes.isEmpty()) {
sb.append(" // ").append(activeNodes);
sb.append("// ").append(activeNodes).append("\n");
}
sb.append("\n");
sb.append(plainText.content());
}
@Override
@@ -1818,7 +1818,8 @@ intention.family.name.ignore.project=Do not show again in this project
fix.move.to.source.root=Move to source root
warning.java.file.outside.source.root=Java file is located outside of the module source root, so it won't be compiled
inspection.message.snippet.file.not.found=Snippet file is not found
javadoc.snippet.not.found=Snippet not found:
javadoc.snippet.not.found=Snippet not found: {0}
javadoc.snippet.region.not.found=Region not found: {0}
javadoc.snippet.error.unknown.highlight.type=Unknown type ''{0}''; only ''bold'', ''italic'', and ''highlighted'' are supported
javadoc.snippet.error.unknown.link.type=Unknown type ''{0}''; only ''link'' and ''linkplain'' are supported
javadoc.snippet.error.malformed.attribute=Malformed attribute