html/xml: introduce custom tag, first try WEB-21035

GitOrigin-RevId: 52cc76651023375bd054d165139c9b1edcba4023
This commit is contained in:
Andrey Starovoyt
2021-12-02 14:00:53 +03:00
committed by intellij-monorepo-bot
parent a62d684562
commit 25cb1ae200
11 changed files with 262 additions and 9 deletions

View File

@@ -107,7 +107,7 @@
<extensions defaultExtensionNs="com.intellij">
<highlightingPassFactory implementation="com.intellij.codeInsight.daemon.impl.tagTreeHighlighting.XmlTagTreeHighlightingPassFactory"/>
<highlightingPassFactory implementation="com.intellij.html.HtmlCustomTagHighlightingPassFactory" />
<fileType name="HTML" implementationClass="com.intellij.ide.highlighter.HtmlFileType" fieldName="INSTANCE" language="HTML" extensions="htm;html;sht;shtm;shtml"/>
<fileType name="XHTML" implementationClass="com.intellij.ide.highlighter.XHtmlFileType" fieldName="INSTANCE" language="XHTML" extensions="xhtml"/>
<fileType name="DTD" implementationClass="com.intellij.ide.highlighter.DTDFileType" fieldName="INSTANCE" language="DTD" extensions="dtd;elt;ent;mod"/>

View File

@@ -27,6 +27,7 @@ import com.intellij.openapi.options.OptionsBundle;
import com.intellij.openapi.options.colors.AttributesDescriptor;
import com.intellij.openapi.options.colors.ColorDescriptor;
import com.intellij.openapi.options.colors.ColorSettingsPage;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.xml.XmlBundle;
import org.jetbrains.annotations.NotNull;
@@ -38,6 +39,8 @@ public class HTMLColorsPage implements ColorSettingsPage {
new AttributesDescriptor(XmlBundle.message("options.html.attribute.descriptor.code"), XmlHighlighterColors.HTML_CODE),
new AttributesDescriptor(XmlBundle.message("options.html.attribute.descriptor.comment"), XmlHighlighterColors.HTML_COMMENT),
new AttributesDescriptor(XmlBundle.message("options.html.attribute.descriptor.tag"), XmlHighlighterColors.HTML_TAG),
new AttributesDescriptor(XmlBundle.message("options.xml.attribute.descriptor.tag.custom"), XmlHighlighterColors.HTML_CUSTOM_TAG),
new AttributesDescriptor(XmlBundle.message("options.xml.attribute.descriptor.tag.name.custom"), XmlHighlighterColors.HTML_CUSTOM_TAG_NAME),
new AttributesDescriptor(XmlBundle.message("options.html.attribute.descriptor.tag.name"), XmlHighlighterColors.HTML_TAG_NAME),
new AttributesDescriptor(XmlBundle.message("options.html.attribute.descriptor.attribute.name"), XmlHighlighterColors.HTML_ATTRIBUTE_NAME),
new AttributesDescriptor(XmlBundle.message("options.html.attribute.descriptor.attribute.value"), XmlHighlighterColors.HTML_ATTRIBUTE_VALUE),
@@ -98,12 +101,18 @@ public class HTMLColorsPage implements ColorSettingsPage {
"<h1>" + FULL_PRODUCT_NAME + "</h1>\n" +
"<p><br><b><IMG border=0 height=12 src=\"images/hg.gif\" width=18 >\n" +
"What is " + FULL_PRODUCT_NAME.replaceAll(" ", "&nbsp;") + "? &#x00B7; &Alpha; </b><br><br>\n" +
"<custom_tag><<custom_tag_name>custom-tag</custom_tag_name>></custom_tag>" +
"hello" +
"<custom_tag></<custom_tag_name>custom_tag</custom_tag_name>></custom_tag>\n" +
"</body>\n" +
"</html>";
}
@Override
public Map<String, TextAttributesKey> getAdditionalHighlightingTagToDescriptorMap() {
return null;
return new ContainerUtil.ImmutableMapBuilder<String, TextAttributesKey>()
.put("custom_tag", XmlHighlighterColors.HTML_CUSTOM_TAG)
.put("custom_tag_name", XmlHighlighterColors.HTML_CUSTOM_TAG_NAME)
.build();
}
}

View File

@@ -24,7 +24,6 @@ import com.intellij.openapi.options.OptionsBundle;
import com.intellij.openapi.options.colors.AttributesDescriptor;
import com.intellij.openapi.options.colors.ColorDescriptor;
import com.intellij.openapi.options.colors.ColorSettingsPage;
import com.intellij.openapi.util.Pair;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.xml.XmlBundle;
import org.jetbrains.annotations.NotNull;
@@ -38,6 +37,8 @@ public class XMLColorsPage implements ColorSettingsPage {
new AttributesDescriptor(XmlBundle.message("options.xml.attribute.descriptor.comment"), XmlHighlighterColors.XML_COMMENT),
new AttributesDescriptor(XmlBundle.message("options.xml.attribute.descriptor.tag"), XmlHighlighterColors.XML_TAG),
new AttributesDescriptor(XmlBundle.message("options.xml.attribute.descriptor.tag.name"), XmlHighlighterColors.XML_TAG_NAME),
new AttributesDescriptor(XmlBundle.message("options.xml.attribute.descriptor.tag.custom"), XmlHighlighterColors.XML_CUSTOM_TAG),
new AttributesDescriptor(XmlBundle.message("options.xml.attribute.descriptor.tag.name.custom"), XmlHighlighterColors.XML_CUSTOM_TAG_NAME),
new AttributesDescriptor(XmlBundle.message("options.xml.attribute.descriptor.matched.tag.name"), XmlHighlighterColors.MATCHED_TAG_NAME),
new AttributesDescriptor(XmlBundle.message("options.xml.attribute.descriptor.namespace.prefix"), XmlHighlighterColors.XML_NS_PREFIX),
new AttributesDescriptor(XmlBundle.message("options.xml.attribute.descriptor.attribute.name"), XmlHighlighterColors.XML_ATTRIBUTE_NAME),
@@ -94,14 +95,21 @@ public class XMLColorsPage implements ColorSettingsPage {
" ]]>\n" +
" </withCData>\n" +
" <indexitem text=\"project\" target=\"project.management\"/>\n" +
" <custom_tag><<custom_tag_name>custom-tag</custom_tag_name>></custom_tag>" +
"hello" +
"<custom_tag></<custom_tag_name>custom_tag</custom_tag_name>></custom_tag>\n" +
" <<bg><np>pf</np></bg>:foo <bg><np>pf</np></bg>:bar=\"bar\"/>\n" +
"</index>";
}
@Override
public Map<String, TextAttributesKey> getAdditionalHighlightingTagToDescriptorMap() {
return ContainerUtil.newHashMap(Pair.create("np", XmlHighlighterColors.XML_NS_PREFIX),
Pair.create("bg", XmlHighlighterColors.XML_TAG),
Pair.create("matched", XmlHighlighterColors.MATCHED_TAG_NAME));
return new ContainerUtil.ImmutableMapBuilder<String, TextAttributesKey>()
.put("custom_tag", XmlHighlighterColors.XML_CUSTOM_TAG)
.put("custom_tag_name", XmlHighlighterColors.XML_CUSTOM_TAG_NAME)
.put("np", XmlHighlighterColors.XML_NS_PREFIX)
.put("bg", XmlHighlighterColors.XML_TAG)
.put("matched", XmlHighlighterColors.MATCHED_TAG_NAME)
.build();
}
}

View File

@@ -296,6 +296,8 @@ options.xml.attribute.descriptor.tag=Tag
options.xml.attribute.descriptor.tag.data=Tag Data
action.name.show.history.for.text=Text
options.html.attribute.descriptor.tag=Tag
options.xml.attribute.descriptor.tag.custom=Custom Tag
options.xml.attribute.descriptor.tag.name.custom=Custom Tag Name
inspection.javadoc.html.not.required.label.text=Additional not required html attributes:
inspection.javadoc.html.not.required.dialog.title=Edit Additional Not Required Html Attributes
checkbox.uniform.indent=Use HTML indents within <style> and <script> tags

View File

@@ -15,5 +15,6 @@
<orderEntry type="module" module-name="intellij.platform.projectModel" />
<orderEntry type="module" module-name="intellij.platform.util.ui" />
<orderEntry type="module" module-name="intellij.platform.editor" />
<orderEntry type="module" module-name="intellij.platform.editor.ex" />
</component>
</module>

View File

@@ -54,7 +54,7 @@ public final class XmlTagTreeHighlightingUtil {
return true;
}
private static boolean hasXmlViewProvider(@NotNull PsiFile file) {
public static boolean hasXmlViewProvider(@NotNull PsiFile file) {
for (PsiFile f : file.getViewProvider().getAllFiles()) {
if (f instanceof XmlFile) {
return true;

View File

@@ -0,0 +1,183 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.html
import com.intellij.codeHighlighting.TextEditorHighlightingPass
import com.intellij.codeInsight.daemon.impl.HighlightInfo
import com.intellij.codeInsight.daemon.impl.HighlightInfoType
import com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil
import com.intellij.codeInsight.daemon.impl.analysis.HighlightInfoHolder
import com.intellij.lang.ASTNode
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.XmlHighlighterColors
import com.intellij.openapi.editor.colors.EditorColorsUtil
import com.intellij.openapi.editor.colors.TextAttributesKey
import com.intellij.openapi.editor.ex.util.LayeredTextAttributes
import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiFile
import com.intellij.psi.XmlRecursiveElementWalkingVisitor
import com.intellij.psi.impl.source.html.dtd.HtmlElementDescriptorImpl
import com.intellij.psi.impl.source.html.dtd.HtmlNSDescriptorImpl
import com.intellij.psi.impl.source.tree.LeafElement
import com.intellij.psi.tree.IElementType
import com.intellij.psi.tree.xml.IXmlLeafElementType
import com.intellij.psi.xml.XmlAttribute
import com.intellij.psi.xml.XmlAttributeValue
import com.intellij.psi.xml.XmlTag
import com.intellij.psi.xml.XmlTokenType
import com.intellij.xml.XmlElementDescriptor
val attributeKeyMapping = mapOf<TextAttributesKey, TextAttributesKey>(
XmlHighlighterColors.HTML_TAG_NAME to XmlHighlighterColors.HTML_CUSTOM_TAG_NAME,
XmlHighlighterColors.XML_TAG_NAME to XmlHighlighterColors.XML_CUSTOM_TAG_NAME,
XmlHighlighterColors.HTML_TAG to XmlHighlighterColors.HTML_CUSTOM_TAG,
XmlHighlighterColors.XML_TAG to XmlHighlighterColors.XML_CUSTOM_TAG
)
class HtmlCustomTagHighlightingPass(val file: PsiFile, editor: Editor) : TextEditorHighlightingPass(file.project, editor.document, true) {
private val myHolder: HighlightInfoHolder = HighlightInfoHolder(file)
private val myHighlighter = SyntaxHighlighterFactory.getSyntaxHighlighter(file.language, file.project, file.virtualFile)
override fun doCollectInformation(progress: ProgressIndicator) {
file.acceptChildren(object : XmlRecursiveElementWalkingVisitor() {
override fun visitXmlTag(tag: XmlTag) {
super.visitXmlTag(tag)
val descriptor = tag.descriptor ?: return
if (descriptor.isCustomElement || !isHtmlTagName(descriptor, tag)) {
tag.node?.let { addLexerBasedHighlighting(it) }
}
}
})
}
private fun isHtmlTagName(descriptor: XmlElementDescriptor, tag: XmlTag): Boolean {
if (descriptor is HtmlElementDescriptorImpl) return true
val nsDescriptor = tag.getNSDescriptor(tag.namespace, true)
if (nsDescriptor is HtmlNSDescriptorImpl) {
val htmlDescriptor = nsDescriptor.getElementDescriptorByName(tag.name)
if (htmlDescriptor != null) return true
}
return false
}
/**
* We have to use highlighting lexer for the mappings current token text attributes -> custom token text attributes
* because XML/HTML highlighting lexer produces in some cases different tokens, and we have to map these tokens, and not real one
*/
fun addLexerBasedHighlighting(node: ASTNode) {
val (ranges, excludedRanges) = collectRanges(node)
if (ranges.isEmpty()) return
val highlightingLexer = myHighlighter.highlightingLexer
val chars = node.chars
val text = ranges.joinToString("") { it.subSequence(chars) }
highlightingLexer.start(text)
val startOffset = node.startOffset
var indexOfCurrentRange = 0
var currentRange = ranges[0]
var offsetOfCurrentRange = 0
while (highlightingLexer.tokenType != null) {
if (highlightingLexer.tokenStart >= currentRange.length + offsetOfCurrentRange) {
offsetOfCurrentRange += currentRange.length
currentRange = ranges[++indexOfCurrentRange]
}
val relativeOffsetStart = (highlightingLexer.tokenStart - offsetOfCurrentRange) + currentRange.startOffset
val relativeOffsetEnd = (highlightingLexer.tokenEnd - offsetOfCurrentRange) + currentRange.startOffset
if (excludedRanges.none { it.containsRange(relativeOffsetStart, relativeOffsetEnd) }) {
val absoluteStart = relativeOffsetStart + startOffset
val absoluteEnd = relativeOffsetEnd + startOffset
applyHighlighting(TextRange(absoluteStart, absoluteEnd), highlightingLexer.tokenType!!)
}
highlightingLexer.advance()
}
}
private fun collectRanges(node: ASTNode): Pair<List<TextRange>, List<TextRange>> {
var tagStarted = -1
val includeRanges = mutableListOf<TextRange>()
val excludedRanges = mutableListOf<TextRange>()
for (child in node.getChildren(null)) {
val elementType = child.elementType
if (tagStarted == -1) {
if (elementType == XmlTokenType.XML_START_TAG_START ||
elementType == XmlTokenType.XML_END_TAG_START) {
tagStarted = child.startOffsetInParent
}
}
if (tagStarted >= 0) {
if (elementType !is IXmlLeafElementType) {
addExcludedRangesForComposite(child, excludedRanges)
}
else if (elementType == XmlTokenType.XML_EMPTY_ELEMENT_END || elementType == XmlTokenType.XML_TAG_END) {
includeRanges.add(TextRange(tagStarted, child.startOffsetInParent + child.textLength))
tagStarted = -1
}
}
}
return includeRanges to excludedRanges
}
fun addExcludedRangesForComposite(child: ASTNode, excludedRanges: MutableList<TextRange>) {
if (child is LeafElement) return
when (child.psi) {
is XmlAttribute -> {
for (attrPartNode in child.getChildren(null)) {
addExcludedRangesForComposite(attrPartNode, excludedRanges)
}
}
is XmlAttributeValue -> {
for (attValuePartNode in child.getChildren(null)) {
addExcludedRangesForComposite(attValuePartNode, excludedRanges)
}
}
else -> {
excludedRanges.add(TextRange(child.startOffsetInParent, child.startOffsetInParent + child.textLength))
}
}
}
private fun applyHighlighting(textRange: TextRange, elementType: IElementType) {
val attributesKeys = myHighlighter.getTokenHighlights(elementType)
val newAttributesKeys = replaceTextAttributeKeys(attributesKeys)
if (!newAttributesKeys.contentEquals(attributesKeys)) {
myHolder.add(highlight(textRange, newAttributesKeys))
}
}
private fun replaceTextAttributeKeys(newAttributesKeys: Array<TextAttributesKey>): Array<TextAttributesKey> {
when {
hasKey(newAttributesKeys) -> {
return newAttributesKeys.map { attributeKeyMapping[it] ?: it }.toTypedArray()
}
else -> return newAttributesKeys
}
}
private fun hasKey(keys: Array<TextAttributesKey>): Boolean {
return keys.firstOrNull { attributeKeyMapping.containsKey(it) } != null
}
private fun highlight(range: TextRange, key: Array<TextAttributesKey>): HighlightInfo? {
return HighlightInfo.newHighlightInfo(HighlightInfoType.INFORMATION)
.severity(HighlightInfoType.SYMBOL_TYPE_SEVERITY)
.range(range)
.textAttributes(LayeredTextAttributes.create(colorsScheme ?: EditorColorsUtil.getGlobalOrDefaultColorScheme(), key)).create()
}
override fun doApplyInformationToEditor() {
val highlights: MutableList<HighlightInfo> = ArrayList()
for (i in 0 until myHolder.size()) {
highlights.add(myHolder[i])
}
UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument, 0, file.textLength, highlights, colorsScheme, id)
}
}

View File

@@ -0,0 +1,31 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.html
import com.intellij.codeHighlighting.*
import com.intellij.codeInsight.daemon.impl.tagTreeHighlighting.XmlTagTreeHighlightingUtil
import com.intellij.lang.html.HtmlCompatibleFile
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import com.intellij.psi.impl.source.html.HtmlFileImpl
import com.intellij.psi.xml.XmlFile
import com.intellij.xml.util.HtmlUtil
class HtmlCustomTagHighlightingPassFactory : TextEditorHighlightingPassFactory, TextEditorHighlightingPassFactoryRegistrar {
override fun registerHighlightingPassFactory(registrar: TextEditorHighlightingPassRegistrar, project: Project) {
registrar.registerTextEditorHighlightingPass(this, null, null, false, -1)
}
override fun createHighlightingPass(file: PsiFile, editor: Editor): TextEditorHighlightingPass? {
if (!hasHtmlViewProvider(file) && !HtmlUtil.supportsXmlTypedHandlers(file)) {
return null
}
return HtmlCustomTagHighlightingPass(file, editor)
}
private fun hasHtmlViewProvider(file: PsiFile): Boolean {
return file.viewProvider.allFiles.any { it is HtmlCompatibleFile }
}
}

View File

@@ -17,8 +17,12 @@ public final class XmlHighlighterColors {
TextAttributesKey.createTextAttributesKey("XML_COMMENT", DefaultLanguageHighlighterColors.LINE_COMMENT);
public static final TextAttributesKey XML_TAG =
TextAttributesKey.createTextAttributesKey("XML_TAG", DefaultLanguageHighlighterColors.MARKUP_TAG);
public static final TextAttributesKey XML_CUSTOM_TAG =
TextAttributesKey.createTextAttributesKey("XML_CUSTOM_TAG", XML_TAG);
public static final TextAttributesKey XML_TAG_NAME =
TextAttributesKey.createTextAttributesKey("XML_TAG_NAME", DefaultLanguageHighlighterColors.KEYWORD);
public static final TextAttributesKey XML_CUSTOM_TAG_NAME =
TextAttributesKey.createTextAttributesKey("XML_CUSTOM_TAG_NAME", XML_TAG_NAME);
public static final TextAttributesKey XML_NS_PREFIX =
TextAttributesKey.createTextAttributesKey("XML_NS_PREFIX", DefaultLanguageHighlighterColors.INSTANCE_FIELD);
public static final TextAttributesKey XML_ATTRIBUTE_NAME =
@@ -34,8 +38,15 @@ public final class XmlHighlighterColors {
TextAttributesKey.createTextAttributesKey("HTML_COMMENT", DefaultLanguageHighlighterColors.LINE_COMMENT);
public static final TextAttributesKey HTML_TAG =
TextAttributesKey.createTextAttributesKey("HTML_TAG", DefaultLanguageHighlighterColors.MARKUP_TAG);
public static final TextAttributesKey HTML_CUSTOM_TAG =
TextAttributesKey.createTextAttributesKey("HTML_CUSTOM_TAG", HTML_TAG);
public static final TextAttributesKey HTML_TAG_NAME =
TextAttributesKey.createTextAttributesKey("HTML_TAG_NAME", DefaultLanguageHighlighterColors.KEYWORD);
public static final TextAttributesKey HTML_CUSTOM_TAG_NAME =
TextAttributesKey.createTextAttributesKey("HTML_CUSTOM_TAG_NAME", HTML_TAG_NAME);
public static final TextAttributesKey HTML_ATTRIBUTE_NAME =
TextAttributesKey.createTextAttributesKey("HTML_ATTRIBUTE_NAME", DefaultLanguageHighlighterColors.MARKUP_ATTRIBUTE);
public static final TextAttributesKey HTML_ATTRIBUTE_VALUE =

View File

@@ -81,4 +81,12 @@ public interface XmlElementDescriptor extends PsiMetaData {
@Nullable
String getDefaultValue();
/**
* @return true, if the element should be highlighted as "Custom tag name".
* For HTML files, there is also independent logic that checks that if no html tags with such name,
* then IDE will use "Custom tag name" highlighting
*/
default boolean isCustomElement() {
return false;
}
}

View File

@@ -35,7 +35,7 @@ import static com.intellij.psi.xml.XmlTokenType.*;
public class HtmlFileHighlighter extends SyntaxHighlighterBase {
private static final MultiMap<IElementType, TextAttributesKey> ourMap = MultiMap.create();
static {
ourMap.putValue(XML_TAG_CHARACTERS, XmlHighlighterColors.HTML_TAG);
@@ -76,7 +76,7 @@ public class HtmlFileHighlighter extends SyntaxHighlighterBase {
public Lexer getHighlightingLexer() {
return new HtmlHighlightingLexer(FileTypeRegistry.getInstance().findFileTypeByName("CSS"));
}
@Override
public TextAttributesKey @NotNull [] getTokenHighlights(IElementType tokenType) {
//noinspection SynchronizationOnGetClass,SynchronizeOnThis