mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[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:
committed by
intellij-monorepo-bot
parent
2e3c106b5d
commit
542e82b9d3
@@ -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 {
|
||||
}
|
||||
+14
@@ -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>
|
||||
+3
@@ -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");
|
||||
|
||||
+145
-86
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user