diff --git a/plugins/grazie/json/src/main/kotlin/com/intellij/grazie/ide/language/json/JsonTextExtractor.kt b/plugins/grazie/json/src/main/kotlin/com/intellij/grazie/ide/language/json/JsonTextExtractor.kt index 6c4cbfff464a..eaf43f0a0d8f 100644 --- a/plugins/grazie/json/src/main/kotlin/com/intellij/grazie/ide/language/json/JsonTextExtractor.kt +++ b/plugins/grazie/json/src/main/kotlin/com/intellij/grazie/ide/language/json/JsonTextExtractor.kt @@ -2,6 +2,7 @@ package com.intellij.grazie.ide.language.json import com.intellij.grazie.text.* +import com.intellij.grazie.text.TextContent.TextDomain import com.intellij.grazie.utils.replaceBackslashEscapes import com.intellij.json.JsonSpellcheckerStrategy.JsonSchemaSpellcheckerClientForJson import com.intellij.json.psi.JsonStringLiteral @@ -9,10 +10,11 @@ import com.intellij.psi.PsiComment import com.intellij.psi.PsiElement class JsonTextExtractor : TextExtractor() { - override fun buildTextContent(element: PsiElement, allowedDomains: MutableSet): TextContent? { - if (element is JsonStringLiteral && JsonSchemaSpellcheckerClientForJson(element).matchesNameFromSchema()) return null + override fun buildTextContent(element: PsiElement, allowedDomains: MutableSet): TextContent? { if (element is PsiComment || element is JsonStringLiteral) { - val domain = if (element is PsiComment) TextContent.TextDomain.COMMENTS else TextContent.TextDomain.LITERALS + val domain = if (element is PsiComment) TextDomain.COMMENTS else TextDomain.LITERALS + if (domain !in allowedDomains) return null + if (element is JsonStringLiteral && JsonSchemaSpellcheckerClientForJson(element).matchesNameFromSchema()) return null val content = TextContentBuilder.FromPsi.build(element, domain) ?: return null return content.replaceBackslashEscapes() } diff --git a/plugins/grazie/properties/src/main/kotlin/com/intellij/grazie/ide/language/properties/PropertyTextExtractor.java b/plugins/grazie/properties/src/main/kotlin/com/intellij/grazie/ide/language/properties/PropertyTextExtractor.java index 9ce9d6a4344f..81921c0c0591 100644 --- a/plugins/grazie/properties/src/main/kotlin/com/intellij/grazie/ide/language/properties/PropertyTextExtractor.java +++ b/plugins/grazie/properties/src/main/kotlin/com/intellij/grazie/ide/language/properties/PropertyTextExtractor.java @@ -2,6 +2,7 @@ package com.intellij.grazie.ide.language.properties; import com.intellij.grazie.text.TextContent; import com.intellij.grazie.text.TextContent.Exclusion; +import com.intellij.grazie.text.TextContent.TextDomain; import com.intellij.grazie.text.TextContentBuilder; import com.intellij.grazie.text.TextExtractor; import com.intellij.grazie.utils.HtmlUtilsKt; @@ -34,16 +35,16 @@ final class PropertyTextExtractor extends TextExtractor { private static final Pattern trailingSlash = Pattern.compile("\\\\\n"); @Override - protected @NotNull List buildTextContents(@NotNull PsiElement root, @NotNull Set allowedDomains) { - if (root instanceof PsiComment) { + protected @NotNull List buildTextContents(@NotNull PsiElement root, @NotNull Set allowedDomains) { + if (root instanceof PsiComment && allowedDomains.contains(COMMENTS)) { List roots = PsiUtilsKt.getNotSoDistantSimilarSiblings(root, e -> PropertiesTokenTypes.COMMENTS.contains(PsiUtilCore.getElementType(e))); return ContainerUtil.createMaybeSingletonList( TextContent.joinWithWhitespace('\n', ContainerUtil.mapNotNull(roots, c -> TextContentBuilder.FromPsi.removingIndents(" \t#!").build(c, COMMENTS)))); } - if (PsiUtilCore.getElementType(root) == PropertiesTokenTypes.VALUE_CHARACTERS) { - TextContent content = TextContent.builder().build(root, TextContent.TextDomain.PLAIN_TEXT); + if (PsiUtilCore.getElementType(root) == PropertiesTokenTypes.VALUE_CHARACTERS && allowedDomains.contains(TextDomain.PLAIN_TEXT)) { + TextContent content = TextContent.builder().build(root, TextDomain.PLAIN_TEXT); if (content != null) { content = content.excludeRanges(ContainerUtil.map(Text.allOccurrences(apostrophes, content), Exclusion::exclude)); content = content.excludeRanges(ContainerUtil.map(Text.allOccurrences(continuationIndent, content), Exclusion::exclude)); diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/PlainTextExtractor.java b/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/PlainTextExtractor.java index 009656c17557..dc3a8501c46e 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/PlainTextExtractor.java +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/PlainTextExtractor.java @@ -1,5 +1,6 @@ package com.intellij.grazie.text; +import com.intellij.grazie.text.TextContent.TextDomain; import com.intellij.grazie.utils.Text; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; @@ -14,19 +15,21 @@ import java.util.List; import java.util.Set; import java.util.regex.Pattern; +import static com.intellij.grazie.text.TextContent.TextDomain.PLAIN_TEXT; + public class PlainTextExtractor extends TextExtractor { private static final Pattern paragraphEnd = Pattern.compile("\\n\\s*?\\n\\s*"); @Override - protected @NotNull List buildTextContents(@NotNull PsiElement root, @NotNull Set allowedDomains) { - if (root instanceof PsiPlainText && root.getContainingFile().getName().endsWith(".txt")) { + protected @NotNull List buildTextContents(@NotNull PsiElement root, @NotNull Set allowedDomains) { + if (root instanceof PsiPlainText && root.getContainingFile().getName().endsWith(".txt") && allowedDomains.contains(PLAIN_TEXT)) { String text = root.getText(); List result = new ArrayList<>(); int[] ends = StreamEx.of(Text.allOccurrences(paragraphEnd, text)).mapToInt(TextRange::getStartOffset).append(text.length()).toArray(); for (int i = 0; i < ends.length; i++) { int start = i == 0 ? 0 : ends[i - 1]; int end = ends[i]; - ContainerUtil.addIfNotNull(result, TextContent.builder().build(root, TextContent.TextDomain.PLAIN_TEXT, new TextRange(start, end))); + ContainerUtil.addIfNotNull(result, TextContent.builder().build(root, PLAIN_TEXT, new TextRange(start, end))); } return result; } diff --git a/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/TextExtractor.java b/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/TextExtractor.java index 6d2cd5732909..7b0d95fe17b8 100644 --- a/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/TextExtractor.java +++ b/plugins/grazie/src/main/kotlin/com/intellij/grazie/text/TextExtractor.java @@ -49,12 +49,29 @@ public abstract class TextExtractor { /** * Extract text from the given PSI element, if possible. * The returned text is most often fully embedded in {@code element}, - * but it may also include other PSI elements (e.g. adjacent comments). + * but it may also include other PSI elements (e.g., adjacent comments). * In the latter case, this extension should return an equal {@link TextContent} for every one of those adjacent elements. + *

+ * Typical usage: + * + *

TextContentBuilder.FromPsi.build(element, textDomain)
+ * + * Implementation guidance: + *

+ * To maximize performance, guard against unnecessary (and sometimes quite expensive) operations by checking that + * the requested textDomain is contained in allowedDomains before extracting. + * + *


+   * if (shouldExtractTextContent(root) && allowedDomains.contains(textDomain)) {
+   *   // some other potentially performance-intensive operations
+   *   return TextContentBuilder.FromPsi.build(root, textDomain)
+   * }
+   * 
+ * + * See concrete implementations (e.g., in ChatInputTextExtractor, JsonTextExtractor, GoTextExtractor, etc.) for + * examples. + * @param allowedDomains the set of the text domains that are expected by the caller. - * The extension may check this set before doing unnecessary expensive PSI traversal - * to improve the performance, - * but it's not necessary. * @see TextContentBuilder * @see #buildTextContents */ diff --git a/plugins/grazie/yaml/main/kotlin/com/intellij/grazie/ide/language/yaml/YamlTextExtractor.kt b/plugins/grazie/yaml/main/kotlin/com/intellij/grazie/ide/language/yaml/YamlTextExtractor.kt index bad5ae5c9110..ad497d2597d9 100644 --- a/plugins/grazie/yaml/main/kotlin/com/intellij/grazie/ide/language/yaml/YamlTextExtractor.kt +++ b/plugins/grazie/yaml/main/kotlin/com/intellij/grazie/ide/language/yaml/YamlTextExtractor.kt @@ -2,6 +2,7 @@ package com.intellij.grazie.ide.language.yaml import com.intellij.grazie.text.TextContent +import com.intellij.grazie.text.TextContent.TextDomain import com.intellij.grazie.text.TextContentBuilder import com.intellij.grazie.text.TextExtractor import com.intellij.grazie.utils.getNotSoDistantSimilarSiblings @@ -17,19 +18,19 @@ import org.jetbrains.yaml.psi.impl.YAMLAnchorImpl private class YamlTextExtractor : TextExtractor() { private val commentBuilder = TextContentBuilder.FromPsi.removingIndents(" \t#") - override fun buildTextContent(root: PsiElement, allowedDomains: MutableSet): TextContent? { - if (root is PsiComment) { + override fun buildTextContent(root: PsiElement, allowedDomains: MutableSet): TextContent? { + if (TextDomain.COMMENTS in allowedDomains && root is PsiComment) { val siblings = getNotSoDistantSimilarSiblings(root, TokenSet.create(WHITESPACE, INDENT, EOL)) { it.elementType == COMMENT } - return TextContent.joinWithWhitespace('\n', siblings.mapNotNull { commentBuilder.build(it, TextContent.TextDomain.COMMENTS) }) + return TextContent.joinWithWhitespace('\n', siblings.mapNotNull { commentBuilder.build(it, TextDomain.COMMENTS) }) } - if (root is YAMLScalar || (root.node != null && root.node.elementType == SCALAR_KEY)) { + if (TextDomain.LITERALS in allowedDomains && (root is YAMLScalar || (root.node != null && root.node.elementType == SCALAR_KEY))) { if (JsonSchemaSpellcheckerClientForYaml(root).matchesNameFromSchema()) { return null } - return TextContentBuilder.FromPsi.excluding { isStealth(it) }.build(root, TextContent.TextDomain.LITERALS) + return TextContentBuilder.FromPsi.excluding { isStealth(it) }.build(root, TextDomain.LITERALS) } - if (root is YAMLAnchorImpl && root.parent !is YAMLScalar) { - return TextContentBuilder.FromPsi.excluding { isStealth(it) }.build(root, TextContent.TextDomain.LITERALS) + if (TextDomain.LITERALS in allowedDomains && (root is YAMLAnchorImpl && root.parent !is YAMLScalar)) { + return TextContentBuilder.FromPsi.excluding { isStealth(it) }.build(root, TextDomain.LITERALS) } return null } diff --git a/python/python-grazie/src/com/intellij/python/grazie/PythonTextExtractor.kt b/python/python-grazie/src/com/intellij/python/grazie/PythonTextExtractor.kt index 3419148320f8..d11f686e9362 100644 --- a/python/python-grazie/src/com/intellij/python/grazie/PythonTextExtractor.kt +++ b/python/python-grazie/src/com/intellij/python/grazie/PythonTextExtractor.kt @@ -25,6 +25,7 @@ internal class PythonTextExtractor : TextExtractor() { val elementType = PsiUtilCore.getElementType(root) if (elementType in PyTokenTypes.STRING_NODES) { val domain = if (elementType == PyTokenTypes.DOCSTRING) TextDomain.DOCUMENTATION else TextDomain.LITERALS + if (domain !in allowedDomains) return null val stringContent = TextContentBuilder.FromPsi.removingIndents(" \t") .removingLineSuffixes(" \t") .withUnknown(this::isUnknownFragment) @@ -35,7 +36,7 @@ internal class PythonTextExtractor : TextExtractor() { return stringContent } - if (root is PsiCommentImpl) { + if (root is PsiCommentImpl && TextDomain.COMMENTS in allowedDomains) { val siblings = getNotSoDistantSimilarSiblings(root) { it is PsiCommentImpl } return TextContent.joinWithWhitespace('\n', siblings.mapNotNull { TextContent.builder().build(it, TextDomain.COMMENTS) }) }