小鸭子的学习笔记duck

Duck Blog

唐如飞

( ^∀^)/欢迎\( ^∀^)

79 文章数
14 评论数

Idea插件开发之自定义语言

tangrufei
2023-02-28 / 0 评论 / 323 阅读 / 0 点赞

1.定义语言

package com.hyway.html;

import com.intellij.lang.html.HTMLLanguage;

public class HywayLanguage extends HTMLLanguage {
  public static final HywayLanguage INSTANCE = new HywayLanguage();

  private HywayLanguage() {
    super(HywayLanguage.INSTANCE, "Hyway", "text/hyway", "application/xhtml+xml");
  }

}

2.定义图标

package com.hyway.html;

import com.intellij.openapi.util.IconLoader;

import javax.swing.*;

/**
 * @author tangrufei
 * @date 2023-02-20 14:35
 */
public class HywayIcons {
    public static final Icon FILE = IconLoader.getIcon("/icons/icon_file.png", HywayIcons.class);

    public static final Icon TYPE = IconLoader.getIcon("/icons/icon_type.png", HywayIcons.class);
}

3.定义文件类型

package com.hyway.html;

import com.intellij.lang.html.HTMLLanguage;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;

/**
 * @author tangrufei
 * @date 2023-02-20 14:39
 */
public class HywayFileType extends LanguageFileType {

    public static final HywayFileType INSTANCE = new HywayFileType();

    private HywayFileType() {
        super(HTMLLanguage.INSTANCE);
    }

    @Override
    public @NonNls @NotNull String getName() {
        return "Hyway File Type";
    }

    @Override
    public @NlsContexts.Label @NotNull String getDescription() {
        return  "Hyway language file";
    }

    @Override
    public @NlsSafe @NotNull String getDefaultExtension() {
        return "hyway";
    }

    @Override
    public @NonNls @Nullable String getCharset(@NotNull VirtualFile file, byte @NotNull [] content) {
        return "UTF-8";
    }

    @Override
    public Icon getIcon() {
        return HywayIcons.FILE;
    }
}

4.定义一个文件类型工厂(可有可无,最新idea已经可以不需要)

/*
package com.hyway;

import com.intellij.openapi.fileTypes.FileTypeConsumer;
import com.intellij.openapi.fileTypes.FileTypeFactory;
import org.jetbrains.annotations.NotNull;

*/
/**
 *//*

public class HywayFileTypeFactory extends FileTypeFactory {
    @Override
    public void createFileTypes(@NotNull FileTypeConsumer fileTypeConsumer) {
        fileTypeConsumer.consume(HywayFileType.INSTANCE);
    }
}
*/

5.注册文件类型

位于resource下面的META-INFO文件夹下的plug.xml文件中申明拓展

<extensions defaultExtensionNs="com.intellij">
<!--        <fileTypeFactory implementation="com.hyway.HywayFileTypeFactory" >-->
<!--        </fileTypeFactory>-->
        <fileType name="Hyway File Type" language="HTML" fieldName="INSTANCE" extensions="hyway"
       implementationClass="com.hyway.html.HywayFileType"
        >
        </fileType>
    </extensions>

6.定义一个文件创建窗口(基于Swing创建)

方式一:通过自带的Swing ui-Desiger(通过拖拽)->然后创建一个action,action为new group->new file
方式二:自己手写去创建 这里提供我的工具类

package com.hyway.base.action;

import com.hyway.html.HywayTemplateFactory;
import com.intellij.CommonBundle;
import com.intellij.ide.actions.CreateElementActionBase;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NonNls;

import javax.swing.*;

/** create by tangrufei by 2023/2/27  */
public abstract class NewHywayFileActionBase extends CreateElementActionBase {

    public NewHywayFileActionBase(String text, String description, Icon icon) {
        super(text, description, icon);
    }

    public NewHywayFileActionBase() {

    }


    @Override
    protected PsiElement[] invokeDialog(Project project, PsiDirectory psiDirectory) {
        MyInputValidator inputValidator = new MyInputValidator(project, psiDirectory);
        Messages.showInputDialog(project, getDialogPrompt(), getDialogTitle(), null, "", inputValidator);
        return inputValidator.getCreatedElements();
    }

    @Override
    protected PsiElement[] create(String s, PsiDirectory psiDirectory) throws Exception {
        return doCreate(s, psiDirectory);
    }

    @Override
    protected String getErrorTitle() {
        return CommonBundle.getErrorTitle();
    }

    public PsiFile createFileFromTemplate(final PsiDirectory directory,
                                             String className,
                                             @NonNls String templateName,
                                             String defaultExtension,
                                             @NonNls String... parameters
                                             ) throws IncorrectOperationException {
        final String ext = "." +defaultExtension;
        String filename = (className.endsWith(ext)) ? className : className + ext;
        return new HywayTemplateFactory(){

        }.createFromTemplate(directory, className, filename, templateName, parameters);
    }

    protected abstract PsiElement[] doCreate(String name, PsiDirectory directory);

    protected abstract String getDialogPrompt();

    protected abstract String getDialogTitle();
}

具体实现类

package com.hyway.html;

import com.hyway.base.action.NewHywayFileActionBase;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;

/**
 * Created by moxun on 16/10/11.
 */
public class NewHywayFileAction extends NewHywayFileActionBase {
    public NewHywayFileAction() {
        super(HywayBundle.message("newfile.menu.action.text"),
                HywayBundle.message("newfile.menu.action.description"),
                HywayIcons.FILE);
    }

    @Override
    protected PsiElement[] doCreate(String name, PsiDirectory directory) {
        PsiFile file = createFileFromTemplate(directory, name, HywayTemplateFactory.NEW_Hyway_TEMPLATE_NAME,HywayFileType.INSTANCE.getDefaultExtension() );
        PsiElement child = file.getLastChild();
        return child != null ? new PsiElement[]{file, child} : new PsiElement[]{file};
    }

    @Override
    protected String getDialogPrompt() {
        return HywayBundle.message("newfile.dialog.prompt");
    }

    @Override
    protected String getDialogTitle() {
        return HywayBundle.message("newfile.dialog.title");
    }

    @Override
    protected String getCommandName() {
        return HywayBundle.message("newfile.command.name");

    }

    @Override
    protected String getActionName(PsiDirectory psiDirectory, String s) {
        return HywayBundle.message("newfile.menu.action.text");
    }
}

7.创建文件视图提供程序

package com.hyway.html;

import com.intellij.lang.Language;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import org.jetbrains.annotations.NotNull;
                //文件视图提供程序
public class HywayFileViewProviderFactory implements FileViewProviderFactory {
    @NotNull
    @Override
    public FileViewProvider createFileViewProvider(@NotNull VirtualFile file, @NotNull Language language, @NotNull PsiManager psiManager, boolean eventSystemEnabled) {
        return new SingleRootFileViewProvider(psiManager, file, eventSystemEnabled) {
            @NotNull
            @Override
            protected PsiFile createFile(@NotNull Language lang) {
                return lang == HywayLanguage.INSTANCE ? new HywayFile(this) : super.createFile(lang);
            }
        };
    }
}

8.定义语法与词法规范(这里我继承HTML语言并在此基础上在做拓展的)

9.定义Token和Element(这里偷了个懒用的kotlin写的)

package com.hyway.js.token

import com.hyway.js.language.HywayJSLanguage
import com.intellij.psi.tree.IElementType
import org.jetbrains.annotations.NonNls

class HywayJSTokenType(@NonNls debugName: String) : IElementType(debugName, HywayJSLanguage.INSTANCE) {

    override fun toString(): String {
        return "HywayJSToken." + super.toString()
    }

}
class HywayJSElementType(debugName:String) : IElementType (debugName, HywayJSLanguage.INSTANCE) {
}

10.定义Lexer

词法分析器定义了一个文件的内容是如何被分解为Token的,创建Lexer的一个简单的方式是使用JFlex。定义一个lexer规则文件然后通过IDE即可生成Lexer。

实现 ParserDefinition 接口,创建一个Parser定义,并在plugin.xml中的 extensions 节点中使用 lang.parserDefinition 标签注册该parser

11.注释器

package com.hyway.html.annotator;

import com.hyway.base.util.CodeUtil;
import com.hyway.html.lint.*;
import com.hyway.html.quickfix.QuickFixAction;
import com.hyway.html.utils.HywayFileUtil;
import com.intellij.lang.annotation.Annotation;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.annotation.Annotator;
import com.intellij.lang.javascript.psi.*;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.html.HtmlTag;
import com.intellij.psi.xml.*;
import org.jetbrains.annotations.NotNull;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
/** create by tangrufei by 2023/2/21  */

/**
 *  注释器
 */
public class HywayAnnotator implements Annotator {
    /**
     * js嵌入内容
     */

    private JSEmbeddedContent script;
    /**
     * JS对象文字表达式
     */
    private JSObjectLiteralExpression moduleExports;

    @Override
    public void annotate(@NotNull PsiElement psiElement, @NotNull AnnotationHolder annotationHolder) {
        /**
         *  判断项目树是否包含.hyway结尾的文件,不包含则不启用·
         */

        if (!psiElement.getContainingFile().getVirtualFile().getName().toLowerCase().endsWith(".hyway")) {
            return;
        }
        if (psiElement instanceof XmlDocument) {
            checkStructure(psiElement, annotationHolder);
        }
        /** 通过检查 XML 标签的属性来判断数据类型。*/
        if (psiElement instanceof XmlTag && ((XmlTag) psiElement).getName().equals("script")) {
            label:
            for (PsiElement element : psiElement.getChildren()) {
                if (element instanceof JSEmbeddedContent) {
                    script = (JSEmbeddedContent) element;
                    for (PsiElement element1 : script.getChildren()) {
                        if (element1 instanceof JSExpressionStatement) {
                            for (PsiElement element2 : element1.getChildren()) {
                                if (element2 instanceof JSAssignmentExpression) {
                                    PsiElement[] children = element2.getChildren();
                                    if (children.length == 2) {
                                        if (children[0].getText().equals("module.exports")) {
                                            moduleExports = (JSObjectLiteralExpression) children[1];
                                            break label;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    private void checkAttributes(XmlTag tag, @NotNull AnnotationHolder annotationHolder) {
        if (tag.getLocalName().toLowerCase().equals("template")) {
            for (XmlTag child : tag.getSubTags()) {
                checkAttributeValue(child, annotationHolder);
            }
        }
    }

    private
    @NotNull
    LintResult verifyDataType(String type, String s) {

        if ("function".equals(type.toLowerCase())) {
            return verifyFunction(s);
        }

        LintResult result = new LintResult();

        if (s.contains(".") || Pattern.compile("\\[\\d+\\]").matcher(s).matches()) {
            result.setCode(LintResultType.PASSED);
            result.setDesc("Skip analyse array or object");
            return result;
        }

        s = CodeUtil.getVarNameFromMustache(s);

        if (type != null && type.equalsIgnoreCase("boolean")) {
            if (CodeUtil.maybeBooleanExpression(s)) {
                result.setCode(LintResultType.PASSED);
                result.setDesc("Boolean Expression");
                return result;
            }
        }

        if (moduleExports == null) {
            result.setCode(LintResultType.UNRESOLVED_VAR);
            result.setDesc("Unresolved property '" + s + "'");
            return result;
        }
        JSProperty data = moduleExports.findProperty("data");

        if (data == null || data.getValue() == null) {
            result.setCode(LintResultType.UNRESOLVED_VAR);
            result.setDesc("Unresolved property '" + s + "'");
            return result;
        }

        for (PsiElement element : data.getValue().getChildren()) {

            if (!(element instanceof JSProperty)) {
                result.setCode(LintResultType.PASSED);
                result.setDesc("Unsupported data type: " + element.getClass().getSimpleName());
                return result;
            }

            String varName = ((JSProperty) element).getName();
            if (varName == null) {
                result.setCode(LintResultType.UNRESOLVED_VAR);
                result.setDesc("Unresolved property '" + s + "'");
                return result;
            }
            if (varName.equals(s)) {
                String typeString = HywayFileUtil.getJSPropertyType((JSProperty) element);
                if (match(type, typeString)) {
                    result.setCode(LintResultType.PASSED);
                    result.setDesc("Lint passed");
                    return result;
                } else {
                    JSExpression expression = ((JSProperty) element).getValue();
                    if (expression != null) {
                        String value = expression.getText();
                        String guessedType = CodeUtil.guessStringType(value);
                        if (match(type, guessedType)) {
                            result.setCode(LintResultType.PASSED);
                            result.setDesc("Lint passed");
                            return result;
                        }
                    }
                    result.setCode(LintResultType.WRONG_VALUE_TYPE);
                    result.setDesc("Wrong property type. expect " + type + ", found " + typeString);
                    return result;
                }
            }
        }
        result.setCode(LintResultType.UNRESOLVED_VAR);
        result.setDesc("Unresolved property '" + s + "'");
        return result;
    }

    private
    @NotNull
    LintResult verifyFunction(String s) {

        s = CodeUtil.getFunctionNameFromMustache(s);

        LintResult result = new LintResult();

        if (moduleExports == null) {
            result.setCode(LintResultType.UNRESOLVED_VAR);
            result.setDesc("Unresolved function '" + s + "'");
            return result;
        }
        JSProperty data = moduleExports.findProperty("methods");

        if (data == null || data.getValue() == null) {
            result.setCode(LintResultType.UNRESOLVED_VAR);
            result.setDesc("Unresolved function '" + s + "'");
            return result;
        }

        for (PsiElement e : data.getValue().getChildren()) {
            if (e instanceof JSProperty) {
                for (PsiElement e1 : e.getChildren()) {
                    if (e1 instanceof JSFunctionExpression) {
                        if (s.equals(((JSFunctionExpression) e1).getName())) {
                            result.setCode(LintResultType.PASSED);
                            result.setDesc("Lint passed");
                            return result;
                        }
                    }
                }
            }
        }

        result.setCode(LintResultType.UNRESOLVED_VAR);
        result.setDesc("Unresolved function '" + s + "'");
        return result;
    }

    private LintResult verifyVarAndFunction(String type, String s) {
        LintResult l1 = verifyDataType(type, s);
        if (!l1.passed()) {
            LintResult l2 = verifyFunction(s);
            if (l2.passed()) {
                return l2;
            } else {
                return l1;
            }
        } else {
            return l1;
        }
    }

    private boolean match(String type, String jsType) {
        if (type.equals(jsType.toLowerCase())) {
            return true;
        }
        if (type.equals("var")) {
            return true;
        }
        return false;
    }

    /**
     * 检查属性值
     * @param xmlTag
     * @param annotationHolder
     */
    private void checkAttributeValue(XmlTag xmlTag, @NotNull AnnotationHolder annotationHolder) {
        HywayTag tag = DirectiveLint.getHywayTag(xmlTag.getName());
        if (tag != null) {
            //check self attrs

            List<String> parent = tag.parent;
            if (xmlTag.getParentTag() != null) {
                String name = xmlTag.getParentTag().getName();
                if (parent != null && !parent.contains(name)) {
                    annotationHolder.createErrorAnnotation(xmlTag, "Element '" + xmlTag.getName()
                            + "' only allowed " + tag.parent.toString() + " as parent element");
                }
            }

            Set<String> extAttrs = tag.getExtAttrs();
            for (XmlAttribute attr : xmlTag.getAttributes()) {

                String attrName = attr.getName();
                String attrValue = attr.getValue();
                Attribute validAttr = tag.getAttribute(attrName);

                if (HywayFileUtil.isMustacheValue(attrValue)) {
                    String bindVar = attrValue.replaceAll("\\{+", "").replaceAll("\\}+", "");
                    LintResult ret = null;
                    String type = "var";
                    if (validAttr == null) {
                        ret = verifyVarAndFunction("var", bindVar);
                    } else {
                        if (!inRepeat(xmlTag) || "repeat".equals(attrName)) {
                            type = validAttr.valueType;
                            ret = verifyDataType(validAttr.valueType, bindVar);
                        }
                    }
                    if (inRepeat(xmlTag) && !"repeat".equals(attrName)) {
                        //repeat 绑定数组内的数据在lint时可能不存在, 跳过检测
                        ret = new LintResult(LintResultType.PASSED, "Skip repeat tag");
                    }
                    if (!ret.passed()) {
                        Annotation annotation = annotationHolder
                                .createErrorAnnotation(attr.getValueElement(), ret.getDesc());
                        if (ret.getCode() == LintResultType.UNRESOLVED_VAR) {
                            annotation.registerFix(new QuickFixAction(bindVar, type));
                        }
                    }
                }

                if (extAttrs.contains(attrName)) {
                    if (!HywayFileUtil.isMustacheValue(attrValue) && validAttr != null && !CodeUtil.maybeInLineExpression(attrValue)) {

                        //正则匹配
                        if (validAttr.valuePattern != null) {
                            if (!validAttr.match(attrValue)) {
                                annotationHolder.createErrorAnnotation(attr.getValueElement(), "Attribute '" + attr.getName()
                                        + "' only allowed value that match '" + validAttr.valuePattern + "'");
                            }
                        } else {
                            //枚举匹配
                            if (validAttr.valueEnum != null) {
                                if (!validAttr.valueEnum.contains(attr.getValue()) && attr.getValueElement() != null) {
                                    annotationHolder.createErrorAnnotation(attr.getValueElement(), "Attribute '" + attr.getName()
                                            + "' only allowed " + validAttr.valueEnum.toString() + " or mustache template as value");
                                }
                            }
                        }
                    }
                } else {
                    if (CodeUtil.maybeInLineExpression(attrValue)) {
                        //TODO: checking expression
                    } else {
                        annotationHolder.createInfoAnnotation(attr, "Not weex defined attribute '" + attrName + "'");
                    }
                }
            }

            if (xmlTag.getSubTags().length == 0 && !inRepeat(xmlTag)) {
                String value = xmlTag.getValue().getText();
                if (HywayFileUtil.containsMustacheValue(value)) {
                    Map<String, TextRange> vars = HywayFileUtil.getVars(value);
                    for (Map.Entry<String, TextRange> entry : vars.entrySet()) {
                        String bindVar = entry.getKey();
                        LintResult ret = verifyDataType("var", bindVar);
                        if (!ret.passed()) {
                            TextRange base = xmlTag.getValue().getTextRange();
                            TextRange range = new TextRange(base.getStartOffset() + entry.getValue().getStartOffset() + 2,
                                    base.getStartOffset() + entry.getValue().getEndOffset() - 2);
                            Annotation annotator = annotationHolder.
                                    createErrorAnnotation(range, ret.getDesc());
                            if (ret.getCode() == LintResultType.UNRESOLVED_VAR) {
                                annotator.registerFix(new QuickFixAction(bindVar, "var"));
                            }
                        }
                    }
                }
            }

            //check sub tags
            XmlTag[] subTags = xmlTag.getSubTags();
            for (XmlTag t : subTags) {
                List<String> validChild = tag.child;
                if (validChild != null && !validChild.contains(t.getName().toLowerCase())) {
                    annotationHolder.createErrorAnnotation(t, "Element '" + xmlTag.getName()
                            + "' only allowed " + tag.child.toString() + " as child element");
                }
                checkAttributeValue(t, annotationHolder);
            }
        } else {
            //unsupported tag
        }
    }

    private boolean inRepeat(XmlTag tag) {
        if (tag == null) {
            return false;
        }

        if ("template".equals(tag.getName())) {
            return false;
        }

        if (tag.getAttribute("repeat") != null) {
            return true;
        } else {
            return inRepeat(tag.getParentTag());
        }
    }

    private void checkStructure(PsiElement document, @NotNull AnnotationHolder annotationHolder) {
        PsiElement[] children = document.getChildren();
        List<String> acceptedTag = Arrays.asList("template","element","script", "style");
        for (PsiElement element : children) {
            if (element instanceof HtmlTag) {
                if (!acceptedTag.contains(((HtmlTag) element).getName().toLowerCase())) {
                    annotationHolder.createErrorAnnotation(element, "Invalid tag '"
                            + ((HtmlTag) element).getName() + "', only the [template, element, script, style] tags are allowed here.");
                }
                checkAttributes((XmlTag) element, annotationHolder);
            } else {
                if (!(element instanceof PsiWhiteSpace)
                        && !(element instanceof XmlProlog)
                        && !(element instanceof XmlText)
                        && !(element instanceof XmlComment)) {
                    String s = element.getText();
                    if (s.length() > 20) {
                        s = s.substring(0, 20);
                    }
                    annotationHolder.createErrorAnnotation(element, "Invalid content '" + s +
                            "', only the [template, script, style] tags are allowed here.");
                }
            }
        }
    }
}

12.属性描述符

package com.hyway.html.attributes;

import com.intellij.lang.javascript.psi.JSImplicitElementProvider;
import com.intellij.psi.PsiElement;
import com.intellij.psi.stubs.StubIndexKey;
import com.intellij.util.ArrayUtil;
import com.intellij.xml.impl.BasicXmlAttributeDescriptor;
import com.intellij.xml.impl.XmlAttributeDescriptorEx;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.List;

/**
 * @desc desc
 * @author tangrufei
 * @date 2023/2/21 15:01
 * @return null
 */

/**
 这段代码是实现一个自定义的 XML 属性描述符类,用于在编辑器中显示和编辑具有特定属性的 XML 元素。该类实现了

 XmlAttributeDescriptorEx 接口,并继承了 BasicXmlAttributeDescriptor 类,实现了接口中的方法

 ,以提供对 XML 元素属性的访问和编辑支持。在该类中,定义了 XML 元素的属性名称和属性值(如果该属性具有固定的值域)

 ,以及一个 StubIndexKey 对象,用于检索指定名称的元素。通过实现 XmlAttributeDescriptorEx 接口,

 该类提供了获取属性的默认值、属性的可能值、属性是否是必需的等方法。

 具体而言,该类的作用是为自定义的 XML 语言插件提供对特定属性的访问和编辑支持,方便用户在编辑器中更好地编写和修改 XML 文件。
 */
public class HywayAttrDescriptor extends BasicXmlAttributeDescriptor implements XmlAttributeDescriptorEx {

    private final String attributeName;
    private final List<String> enumValue;
    private final StubIndexKey<String, JSImplicitElementProvider> index;

    public HywayAttrDescriptor(String attributeName, List<String> enumValue, final StubIndexKey<String, JSImplicitElementProvider> index) {
        this.enumValue = enumValue;
        this.attributeName = attributeName;
        this.index = index;
    }


    @Nullable
    @Override
    public String handleTargetRename(@NotNull @NonNls String newTargetName) {
        return newTargetName;
    }

    @Override
    public boolean isRequired() {
        return false;
    }

    @Override
    public boolean hasIdType() {
        return false;
    }

    @Override
    public boolean hasIdRefType() {
        return false;
    }

    @Override
    public boolean isEnumerated() {
        return index != null;
    }

    @Override
    public PsiElement getDeclaration() {
        return null;
    }

    @Override
    public String getName() {
        return attributeName;
    }

    @Override
    public void init(PsiElement element) {

    }

    @Override
    public Object[] getDependences() {
        return ArrayUtil.EMPTY_OBJECT_ARRAY;
    }

    @Override
    public boolean isFixed() {
        return false;
    }

    @Override
    public String getDefaultValue() {
        return null;
    }

    @Override
    public String[] getEnumeratedValues() {
        if (enumValue == null) {
            return ArrayUtil.EMPTY_STRING_ARRAY;
        }
        return enumValue.toArray(new String[enumValue.size()]);
    }
}

13.自定义语言标签XML属性处理器

package com.hyway.html.attributes;

import com.hyway.html.lint.DirectiveLint;
import com.hyway.html.lint.HywayTag;
import com.hyway.html.utils.HywayFileUtil;
import com.intellij.psi.xml.XmlTag;
import com.intellij.xml.XmlAttributeDescriptor;
import com.intellij.xml.XmlAttributeDescriptorsProvider;
import org.jetbrains.annotations.Nullable;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @desc desc
 * @author tangrufei
 * @date 2023/2/20 15:59
 * @param null
 * @return null
 */

/**
 * 属性描述提供程序
 */

/**

 * 这段代码是为自定义的 Hyway 语言的标签提供 XML 属性描述符的提供者,用于支持代码自动完成、语法高亮和代码导航等功能。
 *
 * HywayAttrDescriptorProvider 实现了 XmlAttributeDescriptorsProvider 接口,提供了两个方法,
 *
 * getAttributeDescriptors 和 getAttributeDescriptor。getAttributeDescriptors 方法用于返回指定标签的所有
 *
 * XML 属性描述符。该方法首先通过 HywayFileUtil.isOnHywayFile 方法判断当前文件是否为 Hyway 文件,如果不是,
 *
 * 则返回空数组。如果当前标签为 Hyway 标签,该方法将遍历 Hyway 标签定义的所有扩展属性,并将每个属性名称及其描述符添加到
 *
 * result 中。其中,HywayAttrDescriptor 是自定义的 XML 属性描述符,用于描述 Hyway 标签的扩展属性。
 *
 * getAttributeDescriptor 方法用于返回指定标签的指定属性描述符。该方法的实现类似于 getAttributeDescriptors,
 *
 * 首先判断当前文件是否为 Hyway 文件,如果不是,则返回 null。如果当前标签为 Hyway 标签,该方法将检查 Hyway 标签
 *
 * 定义的扩展属性是否包含指定的属性名称,如果是,则返回相应的 HywayAttrDescriptor 对象,否则返回 null。
 */
public class HywayAttrDescriptorProvider implements XmlAttributeDescriptorsProvider {
    @Override
    public XmlAttributeDescriptor[] getAttributeDescriptors(XmlTag xmlTag) {
        if (!HywayFileUtil.isOnHywayFile(xmlTag)) {
            return new XmlAttributeDescriptor[0];
        }
        final Map<String, XmlAttributeDescriptor> result = new LinkedHashMap<String, XmlAttributeDescriptor>();
        HywayTag tag = DirectiveLint.getHywayTag(xmlTag.getName());
        if (tag == null) {
            return new XmlAttributeDescriptor[0];
        }
        for (String attributeName : tag.getExtAttrs()) {
            result.put(attributeName, new HywayAttrDescriptor(attributeName,
                    tag.getAttribute(attributeName).valueEnum,
                    null));
        }
        return result.values().toArray(new XmlAttributeDescriptor[result.size()]);
    }

    @Nullable
    @Override
    public XmlAttributeDescriptor getAttributeDescriptor(String s, XmlTag xmlTag) {
        if (!HywayFileUtil.isOnHywayFile(xmlTag)) {
            return null;
        }
        HywayTag tag = DirectiveLint.getHywayTag(xmlTag.getName());
        if (tag == null) {
            return null;
        }
        if (tag.getExtAttrs().contains(s)) {
            return new HywayAttrDescriptor(s, tag.getAttribute(s).valueEnum, null);
        }
        return null;
    }
}

14.代码补全功能

package com.hyway.html.complection;

import com.hyway.base.util.CodeUtil;
import com.hyway.html.HywayIcons;
import com.hyway.html.utils.HywayFileUtil;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.lang.javascript.psi.JSProperty;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiElement;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;

import java.util.Map;

/**
 * @desc desc
 * @author tangrufei
 * @date 2023/2/20 16:48
 * @return null
 */

/**
 这段代码是一个 Java 类的构造函数,该类实现了一个 IntelliJ IDEA 的代码补全功能扩展,

 名为 "HywayCompletionContributor"。该构造函数通过调用 extend() 方法,注册了一个 CompletionProvider 对象

 ,用于处理基础代码补全(CompletionType.BASIC)。注册时,使用了 PlatformPatterns.psiElement(PsiElement.class)

 ,表示对所有类型的 PsiElement 元素进行代码补全。在代码补全时,首先通过 HywayFileUtil.isOnHywayFile()

 方法判断当前的文件是否是 Hyway 文件,如果不是,则直接返回。如果是 Hyway 文件,则检查当前光标所在位置的上下文

 是否是 XML 属性值(XmlAttributeValue)。如果是 XML 属性值,则根据该属性值的类型进行代码补全,如果类型为 "function",

 则自动填充所有函数名称;否则自动填充所有变量名称,并根据变量类型为代码补全项添加类型信息。

 该代码补全功能通过 HywayFileUtil 和 CodeUtil 类提供的工具方法实现。其中,HywayFileUtil 提供了与 Hyway 文件相关

 的一些辅助方法,如获取 Hyway 文件中所有变量和函数的名称,以及获取变量的声明语句。而 CodeUtil

 则提供了一些常见的代码分析方法,如猜测一个字符串的类型。
 */

public class HywayCompletionContributor extends CompletionContributor {
    public HywayCompletionContributor() {
        extend(CompletionType.BASIC,
                PlatformPatterns.psiElement(PsiElement.class),
                new CompletionProvider<CompletionParameters>() {
                    @Override
                    protected void addCompletions(@NotNull CompletionParameters completionParameters, ProcessingContext processingContext, @NotNull CompletionResultSet resultSet) {

                        if (!HywayFileUtil.isOnHywayFile(completionParameters.getPosition())) {
                            return;
                        }

                        if (completionParameters.getPosition().getContext() instanceof XmlAttributeValue) {
                            final XmlAttributeValue value = (XmlAttributeValue) completionParameters.getPosition().getContext();
                            if (HywayFileUtil.getValueType(value).equals("function")) {
                                for (String s : HywayFileUtil.getAllFunctionNames(value)) {
                                    resultSet.addElement(LookupElementBuilder.create("{{" + s + "}}")
                                            .withLookupString(s)
                                            .withIcon(HywayIcons.TYPE)
                                            .withInsertHandler(new InsertHandler<LookupElement>() {
                                                @Override
                                                public void handleInsert(InsertionContext insertionContext, LookupElement lookupElement) {

                                                    performInsert(value, insertionContext, lookupElement);
                                                }
                                            })
                                            .withBoldness(true)
                                            .withTypeText("Function"));
                                }
                            } else {
                                Map<String, String> vars = HywayFileUtil.getAllVarNames(value);
                                for (String s : vars.keySet()) {
                                    String typeText = vars.get(s);
                                    String vType = HywayFileUtil.getValueType(value);
                                    if (!HywayFileUtil.hasSameType(vType, vars.get(s))) {
                                        JSProperty property = HywayFileUtil.getVarDeclaration(completionParameters.getPosition(), s);
                                        if (property == null || property.getValue() == null) {
                                            continue;
                                        } else {
                                            String v = property.getValue().getText();
                                            String guessedType = CodeUtil.guessStringType(v);
                                            if (!HywayFileUtil.hasSameType(vType, guessedType)) {
                                                continue;
                                            } else {
                                                typeText = guessedType + " string";
                                            }
                                        }
                                    }
                                    resultSet.addElement(LookupElementBuilder.create("{{" + s + "}}")
                                            .withLookupString(s)
                                            .withIcon(HywayIcons.TYPE)
                                            .withInsertHandler(new InsertHandler<LookupElement>() {
                                                @Override
                                                public void handleInsert(InsertionContext insertionContext, LookupElement lookupElement) {
                                                    performInsert(value, insertionContext, lookupElement);
                                                }
                                            })
                                            .withBoldness(true)
                                            .withTypeText(typeText));
                                }
                            }
                        }
                    }
                });
    }

    private void performInsert(XmlAttributeValue value, InsertionContext insertionContext, LookupElement lookupElement) {
        if (value.getText().startsWith("\"")) {
            insertionContext.getDocument().replaceString(
                    value.getTextOffset(),
                    value.getTextOffset() + getTailLength(value) + lookupElement.getLookupString().length(),
                    lookupElement.getLookupString());
        } else {
            insertionContext.getDocument().replaceString(
                    value.getTextOffset() - 1,
                    value.getTextOffset() + getTailLength(value) + lookupElement.getLookupString().length() - 1,
                    "\"" + lookupElement.getLookupString() + "\"");
        }
    }
    /** 获取尾部长度 */
    private int getTailLength(XmlAttributeValue value) {
        String[] temp = value.getValue().split(CompletionInitializationContext.DUMMY_IDENTIFIER);
        if (temp.length == 2) {
            return temp[1].length();
        }
        return 0;
    }
}

15.样例代码编辑器

package com.hyway.html.intention;

import com.google.gson.Gson;
import com.hyway.html.HywayFileType;
import com.hyway.html.lint.DirectiveLint;
import com.hyway.html.lint.HywayTag;
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.PopupStep;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.psi.PsiElement;
import com.intellij.psi.xml.XmlToken;
import com.intellij.ui.EditorTextField;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;

import java.awt.*;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;

/**
 *用于编辑 XML 文件时,当用户选中某个 XML 标签名后,可以通过快捷键或右键菜单打开一个浏览器页面或样例代码编辑器。
 *
 * 具体实现方式如下:
 *
 * isAvailable 方法会检查传入的 PsiElement 对象是否为 XML 标签名,如果是,并且这个标签名存在于某个列表中,
 *
 * 那么返回 true,表示这个操作可用。否则返回 false。getText 和 getFamilyName 方法分别返回右键菜单中这个操作
 *
 * 的名称和组名。invoke 方法会根据用户选择的选项,调用 openDocument 或 openSample 方法。前者会根据 XML标签名
 *
 * 找到相应的文档链接,并用系统默认浏览器打开该链接;后者会打开一个样例代码编辑器。具体来说,openSample 方法会创建一个
 *
 * EditorTextField 组件,然后将其放入一个弹出窗口中,并显示在合适的位置。
 */
public class DocumentIntention extends PsiElementBaseIntentionAction {
    @Override
    public void invoke(@NotNull final Project project, final Editor editor, @NotNull final PsiElement psiElement) throws IncorrectOperationException {

        JBPopupFactory.getInstance().createListPopup(new BaseListPopupStep<String>("Chosen", "Document", "Sample") {

            @Override
            public PopupStep onChosen(String selectedValue, boolean finalChoice) {

                if ("Document".equals(selectedValue)) {
                    openDocument(psiElement.getText());
                } else if ("Sample".equals(selectedValue)) {
                    openSample(project, editor);
                }

                return super.onChosen(selectedValue, finalChoice);
            }
        }).showInBestPositionFor(editor);
    }

    @Override
    public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement psiElement) {
        if (psiElement instanceof XmlToken && false) {
            String tokenType = ((XmlToken) psiElement).getTokenType().toString();
            if ("XML_NAME".equals(tokenType)) {
                String tagName = psiElement.getText();
                return DirectiveLint.containsTag(tagName);
            }
        }
        return false;
    }

    @NotNull
    @Override
    public String getText() {
        return "Open document or sample";
    }

    @Nls
    @NotNull
    @Override
    public String getFamilyName() {
        return getText();
    }

    private void openDocument(String tagName) {
        HywayTag tag = DirectiveLint.getHywayTag(tagName);
        if (tag != null && tag.document != null) {
            Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
            if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) {
                try {
                    desktop.browse(URI.create(tag.document));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void openSample(Project project, Editor editor) {

        EditorTextField field = new EditorTextField(editor.getDocument(), project, HywayFileType.INSTANCE, true, false) {
            @Override
            protected EditorEx createEditor() {
                EditorEx editor1 = super.createEditor();
                //设置滚动条可见
                editor1.setVerticalScrollbarVisible(true);
                //设置水平滚动条可见
                editor1.setHorizontalScrollbarVisible(true);
                return editor1;

            }
        };
                        //获取组件字体并设置
        field.setFont(editor.getContentComponent().getFont());

        JBPopup jbPopup = JBPopupFactory.getInstance().createComponentPopupBuilder(field, null)
                .createPopup();

        jbPopup.setSize(new Dimension(500, 500));
        //显示最佳位置
        jbPopup.showInBestPositionFor(editor);
    }

    private ComponentBean load(String name) {
        InputStream is = DirectiveLint.class.getResourceAsStream("/samples/index.json");
        Gson gson = new Gson();
        ComponentBean[] beans = gson.fromJson(new InputStreamReader(is), ComponentBean[].class);
        for (ComponentBean bean : beans) {
            if (name.equals(bean.component)) {
                return bean;
            }
        }
        return null;
    }
}

16.属性填充器(胡须表达式)

package com.hyway.html.intention;

import com.hyway.html.utils.HywayFileUtil;
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
import com.intellij.lang.javascript.psi.JSBlockStatement;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.ListPopup;
import com.intellij.openapi.ui.popup.PopupStep;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.css.CssDeclaration;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlText;
import com.intellij.psi.xml.XmlToken;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.PlatformIcons;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import java.awt.*;
import java.util.HashSet;
import java.util.Set;

/**
 * @desc desc
 * @author tangrufei
 * @date 2023/2/21 15:09
 * @return null
 */

/**
 * 这段代码是一个Intention Action,它的作用是在编辑Hyway文件时,当光标位于文本或属性值中时
 *
 * ,提供插入Mustache变量的快捷方式。在插入之前,该Intention Action会扫描当前光标所在位置的上下文,
 *
 * 从而猜测是插入一个变量还是一个函数。在弹出列表中选择变量或函数后,它将在光标位置插入相应的胡须表达式。
 */
public class TagTextIntention extends PsiElementBaseIntentionAction {
    @Nls
    @NotNull
    @Override
    public String getText() {
        return "Insert mustache variable";/** 插入胡须表达式变量 */
    }

    @Nls
    @NotNull
    @Override
    public String getFamilyName() {
        return "Hyway";
    }

    @Override
    public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) {

        if (!HywayFileUtil.isOnHywayFile(element)) {
            return false;
        }

        int offset = editor.getCaretModel().getOffset();
        Document document = editor.getDocument();
        if (!element.isWritable() || element.getContext() == null || !element.getContext().isWritable()) {
            return false;
        }

        if (element instanceof XmlToken && ((XmlToken) element).getTokenType().toString().equals("XML_END_TAG_START")) {
            String next = document.getText(new TextRange(offset, offset + 1));
            if (next != null && next.equals("<")) {
                return true;
            }
        }

        return available(element);
    }

    @Override
    public void invoke(@NotNull final Project project, final Editor editor, @NotNull PsiElement element) throws IncorrectOperationException {

        Set<String> vars = new HashSet<String>();
        boolean isFunc = false;

        if (guessFunction(element)) {
            vars = HywayFileUtil.getAllFunctionNames(element);
            isFunc = true;
        } else {
            vars = HywayFileUtil.getAllVarNames(element).keySet();
            isFunc = false;
        }
        final boolean isFuncFinal = isFunc;

        ListPopup listPopup = JBPopupFactory.getInstance()
                .createListPopup(new BaseListPopupStep<String>(null, vars.toArray(new String[vars.size()])) {

                    @Override
                    public Icon getIconFor(String value) {
                        return isFuncFinal ? PlatformIcons.FUNCTION_ICON : PlatformIcons.VARIABLE_ICON;
                    }

                    @Override
                    public PopupStep onChosen(final String selectedValue, final boolean finalChoice) {
                        new WriteCommandAction(project) {
                            @Override
                            protected void run(@NotNull Result result) throws Throwable {
                                editor.getDocument().insertString(editor.getCaretModel().getOffset(), "{{" + selectedValue + "}}");
                                int start = editor.getSelectionModel().getSelectionStart();
                                int end = editor.getSelectionModel().getSelectionEnd();
                                editor.getDocument().replaceString(start, end, "");
                            }
                        }.execute();
                        return super.onChosen(selectedValue, finalChoice);

                    }
                });
        listPopup.setMinimumSize(new Dimension(240, -1));
        listPopup.showInBestPositionFor(editor);
    }

    @Override
    public boolean startInWriteAction() {
        return true;
    }

    private boolean available(PsiElement element) {
        PsiElement context = element.getContext();
        return context instanceof JSBlockStatement
                || context instanceof CssDeclaration
                || context instanceof XmlAttributeValue
                || context instanceof XmlText;
    }
    /** 判断是否为XML文件 */
    private boolean guessFunction(PsiElement element) {
        if (element.getContext() instanceof XmlAttributeValue) {
            try {
                XmlAttributeValue value = (XmlAttributeValue) element.getContext();
                return ((XmlAttribute) value.getContext()).getName().startsWith("on");
            } catch (Exception e) {
                return false;
            }
        }
        return element.getContext() instanceof JSBlockStatement;
    }
}

17.标签自定义

package com.hyway.html.lint;

import com.google.gson.Gson;
import com.hyway.html.custom.Settings;
import com.hyway.html.utils.ExtraModulesUtil;
import com.hyway.html.utils.Logger;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;

/** create by tangrufei by 2023/2/21  */

/**
 这是一个名为“DirectiveLint”的Java类。它提供了一组方法,用于管理、合并和检索基于HTML的应用程序中使用的各种类型的标记,
 */
public class DirectiveLint {

    private static HywayTag[] tags = null;
    private static Set<String> tagNames = null;
    private static String[] htmlTags = {
            "div",
            "text",
            "video",
            "a",
            "qunimade"
    };
    /** 打印内存中当前标记和标记名称的信息*/
    public static void dump() {
        if (tags != null) {
            List list = Arrays.asList(tags);
            Logger.warn("Tags: (" + list.size() + ") " + list.toString());
        } else {
            Logger.warn("Tags: null");
        }
        if (tagNames != null) {
            Logger.warn("Tag names: (" + tagNames.size() + ") " + tagNames.toString());
        } else {
            Logger.warn("Tag names: null");
        }
        List list1 = ExtraModulesUtil.getTagsFromNodeModules();
        Logger.warn("Tag from node_modules: (" + list1.size() + ") " + list1.toString());
    }
    /**重新设置(加载内置规则,将其与自定义规则合并,并清除内存中的任何现有标记。 */
    public static void reset() {
        loadBuiltInRules();
        if (tagNames != null) {
            tagNames.clear();
        }
        mergeToBuiltIn(Settings.getRules());
        Logger.info("Rebuild Hyway component caches, " + tags.length + " tags found.");
    }
    /** 载内置规则并将其与自定义规则合并。合并标签(list<HywayTag>from):将标签列表与现有内置标签合并。
     loadBuiltinRules(:从JSON文件加载内置规则 */
    public static void prepare() {
        if (tags == null) {
            loadBuiltInRules();
        }
        mergeToBuiltIn(Settings.getRules());
    }
    /**  标签合并  */
    private static void mergeToTags(List<HywayTag> from) {
        List<HywayTag> builtIn = new ArrayList<HywayTag>(Arrays.asList(tags));
        if (from != null) {
            for (HywayTag tag : from) {
                if (containsTag(tag.tag)) {
                    HywayTag builtInTag = getBuiltInHywayTag(tag.tag);
                    if (builtInTag != null) {
                        mergeCustom(builtInTag, tag);
                    }
                } else {
                    builtIn.add(tag);
                }
            }
        }
        tags = builtIn.toArray(new HywayTag[builtIn.size()]);
        if (tagNames != null) {
            tagNames.clear();
        }
    }
    /** (:从JSON文件加载内置规则 */
    private static void loadBuiltInRules() {
        InputStream is = DirectiveLint.class.getResourceAsStream("/directives/directives.json");
        Gson gson = new Gson();
        tags = gson.fromJson(new InputStreamReader(is), HywayTag[].class);
        if (tags == null) {
            tags = new HywayTag[0];
        }
        loadNodeModules();
    }
    /** 加载节点模块(:将来自外部模块的标签与现有的内置标签合并 */
    private static void loadNodeModules() {
        List<HywayTag> HywayTags = ExtraModulesUtil.getTagsFromNodeModules();
        mergeToTags(HywayTags);
        Logger.debug(HywayTags.toString());
    }
    /** 将一系列自定义规则与现有的内置规则合并 */
    private static void mergeToBuiltIn(HywayTag[] customRules) {
        if (customRules != null) {
            mergeToTags(Arrays.asList(customRules));
        }
    }
    /** HywayTag内置,HywayTag自定义) :将单个自定义标记与内置标记合并。 */
    private static void mergeCustom(HywayTag builtIn, HywayTag custom) {
        if (custom.parent != null && custom.parent.size() > 0) {
            if (builtIn.parent != null) {
                builtIn.parent.addAll(custom.parent);
            } else {
                builtIn.parent = custom.parent;
            }
        }

        if (custom.child != null && custom.child.size() > 0) {
            if (builtIn.child != null) {
                builtIn.child.addAll(custom.child);
            } else {
                builtIn.child = custom.child;
            }
        }

        if (custom.attrs != null && custom.attrs.size() > 0) {
            if (builtIn.attrs != null) {
                for (Attribute attr : builtIn.attrs) {
                    if (custom.getAttribute(attr.name) != null) {
                        attr.merge(custom.getAttribute(attr.name));
                    }
                }
            }
            for (Attribute attr1 : custom.attrs) {
                if (builtIn.getAttribute(attr1.name) == null) {
                    builtIn.attrs.add(attr1);
                }
            }
        }
    }
    /** GetHywayTagNames ():重新生成响应和标记名 */
    public static Set<String> getHywayTagNames() {
        if (tagNames == null) {
            tagNames = new HashSet<String>();
        }

        if (tagNames.size() == 0) {
            if (tags == null) {
                prepare();
            }
            for (HywayTag tag : tags) {
                if (!"common".equals(tag.tag)) {
                    tagNames.add(tag.tag);
                }
            }
        }

        Logger.debug(tagNames.toString());
        Logger.debug(Arrays.asList(tags).toString());

        return tagNames;
    }
    /** 标签(标签):返回基本HTML标记的列表 */
    public static List<String> getHtmlTags() {
        return Arrays.asList(htmlTags);
    }

    public static boolean containsTag(String tagName) {
        if (tagName == null || "common".equals(tagName)) {
            return false;
        }
        if (tags == null) {
            prepare();
        }
        for (HywayTag tag : tags) {
            if (tagName.equals(tag.tag)) {
                return true;
            }
        }
        return false;
    }

    private static HywayTag getBuiltInHywayTag(String tagName) {
        if (tags == null) {
            loadBuiltInRules();
        }
        for (HywayTag tag : tags) {
            if (tag.tag.equals(tagName)) {
                return tag;
            }
        }
        return null;
    }

    public static HywayTag getCommonTag() {
        if (tags == null) {
            prepare();
        }
        for (HywayTag tag : tags) {
            if (tag.tag.equals("common")) {
                return tag;
            }
        }
        return null;
    }

    public static HywayTag getHywayTag(String tagName) {
        if (tags == null) {
            prepare();
        }
        HywayTag common = null;
        HywayTag target = null;
        for (HywayTag tag : tags) {
            if (tag.tag.equals(tagName)) {
                target = tag;
            }
            if (tag.tag.equals("common")) {
                common = tag;
            }
        }
        if (common != null) {
            if (target != null) {
                return merge(target, common);
            } else {
                return common;
            }
        } else {
            if (target != null) {
                return target;
            }
        }
        return null;
    }

    private static HywayTag merge(HywayTag target, HywayTag common) {
        target.attrs.addAll(common.attrs);
        return target;
    }
}

18.基本类型定义

package com.hyway.html.quickfix;

import com.hyway.html.utils.HywayFileUtil;
import com.hyway.html.utils.MessageUtil;
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * @desc desc
 * @author tangrufei
 * @date 2023/2/20 15:57
 * @param null
 * @return null
 */

/**
 * 基本操作
 */
public class QuickFixAction extends BaseIntentionAction {

    private String name = null;
    private String type = null;
    private static Map<String, String> defValues = new HashMap<String, String>();

    static {
        defValues.put("var", "null");
        defValues.put("boolean", "false");
        defValues.put("number", "0");
        defValues.put("object", "{}");
        defValues.put("array", "[]");
        defValues.put("string", "''");
    }

    public QuickFixAction(String name, String type) {
        this.name = name;
        this.type = type;
    }

    @NotNull
    @Override
    public String getText() {
        String t = "function".equals(type) ? "function " : "variable ";
        return "Create " + t + "'" + name + "'";
    }

    @Nls
    @NotNull
    @Override
    public String getFamilyName() {
        return "Hyway properties";
    }

    @Override
    public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile psiFile) {
        return true;
    }

    @Override
    public void invoke(@NotNull Project project, Editor editor, PsiFile psiFile) throws IncorrectOperationException {
        if (!"function".equals(type)) {
            int offset = HywayFileUtil.getExportsEndOffset(psiFile, "data");
            if (offset < 0) {
                MessageUtil.showTopic(project, "QuickFix failed", "module.exports.data not found", NotificationType.WARNING);
                return;
            }
            String template = name + ": " + getDefaultValue(type) + ",\n";

            if (!hasComma(offset, editor)) {
                template = "," + template;
            }

            editor.getDocument().insertString(offset, template);
            PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
            CodeStyleManager.getInstance(project).reformat(psiFile);
        } else {
            int offset = HywayFileUtil.getExportsEndOffset(psiFile, "methods");
            if (offset < 0) {
                MessageUtil.showTopic(project, "QuickFix failed", "module.exports.methods not found", NotificationType.WARNING);
                return;
            }

            String template = name + ": " + "function () {\n\n}\n";

            if (!hasComma(offset, editor)) {
                template = "," + template;
            }

            editor.getDocument().insertString(offset, template);
            PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
            CodeStyleManager.getInstance(project).reformat(psiFile);
        }
    }

    private boolean hasComma(int start, Editor editor) {
        Document document = editor.getDocument();
        TextRange range = new TextRange(start - 1, start);
        while (true) {
            String s = document.getText(range);
            if (range.getStartOffset() <= 0) {
                return false;
            }
            if (s.equals(",") || s.equals("{")) {
                return true;
            } else if (Pattern.compile("\\s+").matcher(s).matches()) {
                range = new TextRange(range.getStartOffset() - 1, range.getStartOffset());
            } else {
                return false;
            }
        }
    }

    private String getDefaultValue(String name) {
        return defValues.get(name);
    }
}

19.变量处理

package com.hyway.html.refrences;

import com.hyway.html.utils.HywayFileUtil;
import com.intellij.lang.html.HTMLLanguage;
import com.intellij.openapi.util.TextRange;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.patterns.XmlPatterns;
import com.intellij.psi.*;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/** create by tangrufei by 2023/2/21  */

/**
 *主要作用是注册用于处理 HTML 文件中文本变量

 */

/**
 具体来说,它通过调用 registerReferenceProvider 方法注册了两个参考提供者。第一个参考提供者使用 HtmlLanguage 实例

 和 XmlAttributeValue 类作为参考元素,将其绑定到 HywayReferenceProvider 实例,以处理 HTML 文件中属性值

 (即以双引号或单引号括起来的字符串)中的文本变量。第二个参考提供者使用 HtmlLanguage 实例和 XmlTag 类作为参考元素,

 将其绑定到一个匿名的 PsiReferenceProvider 实例,以处理 HTML 文件中标签中的文本变量,但不包括 script 和 style 标签。

 对于第二个参考提供者,当它找到一个标签时,它首先检查标签是否包含子标签,如果没有子标签,则它会获取标签内的文本,并使用

 HywayFileUtil.containsMustacheValue 方法检查文本是否包含文本变量。如果包含,则它会使用 HywayFileUtil.getVars方法

 获取文本变量,并使用 HywayFileUtil.getAllVarNames 方法检查变量名是否在当前标签及其祖先标签中定义过,如果是,

 则会为该变量创建一个 DataReference 实例,并将其添加到返回的参考数组中。
 */
public class HywayReferenceContributor extends PsiReferenceContributor {
    @Override
    public void registerReferenceProviders(@NotNull PsiReferenceRegistrar psiReferenceRegistrar) {
        psiReferenceRegistrar.registerReferenceProvider(
                PlatformPatterns.psiElement(XmlAttributeValue.class).withLanguage(HTMLLanguage.INSTANCE), new HywayReferenceProvider());

        psiReferenceRegistrar.registerReferenceProvider(
                XmlPatterns.xmlTag().withLanguage(HTMLLanguage.INSTANCE)
                        .andNot(XmlPatterns.xmlTag().withLocalName("script"))
                        .andNot(XmlPatterns.xmlTag().withLocalName("style")),
                new PsiReferenceProvider() {
                    @NotNull
                    @Override
                    public PsiReference[] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) {
                        if (psiElement instanceof XmlTag) {
                            if (((XmlTag) psiElement).getSubTags().length == 0) {
                                String text = ((XmlTag) psiElement).getValue().getText();
                                if (HywayFileUtil.containsMustacheValue(text)) {
                                    List<PsiReference> references = new ArrayList<PsiReference>();
                                    Map<String, TextRange> vars = HywayFileUtil.getVars(text);
                                    for (Map.Entry<String, TextRange> entry : vars.entrySet()) {
                                        if (HywayFileUtil.getAllVarNames(psiElement).keySet().contains(entry.getKey())) {
                                            references.add(new DataReference((XmlTag) psiElement, entry.getValue(), entry.getKey()));
                                        }
                                    }
                                    return references.toArray(new PsiReference[references.size()]);
                                }
                            }
                        }
                        return new PsiReference[0];
                    }
                }
        );
    }
}

20.胡须表达式处理器

package com.hyway.html.refrences;

import com.hyway.html.lint.DirectiveLint;
import com.hyway.html.lint.HywayTag;
import com.hyway.html.utils.HywayFileUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiReferenceProvider;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;

import java.util.regex.Pattern;

/** create by tangrufei by 2023/2/21  */

/**  胡须表达式处理器 */

/**
 * getReferencesByElement 方法是必须实现的方法,它接受两个参数 PsiElement 和 ProcessingContext,并返回一个
 *
 * PsiReference 数组。当 Hyway 文件中出现一个插值表达式 {{...}} 时,这个方法将会被调用。首先,
 *
 * 使用 HywayFileUtil.isOnHywayFile 方法检查当前的 PsiElement 是否在一个 Hyway 文件中。如果不是,
 *
 * 那么将会直接返回一个空的 PsiReference 数组。replaceAll 方法将 text 中所有的双引号 " 都替换为空格,
 *
 * 这样可以获取到插值表达式的内容。接着使用正则表达式 \\{\\{.*\\}\\} 来匹配这个内容是否符合插值表达式的形式,
 *
 * 也就是是否以两个花括号开始和结束。如果匹配上了,并且内容长度大于 4,那么就执行接下来的逻辑。
 *
 * 使用 psiElement 的父节点来确定这个插值表达式所在的标签以及它的属性类型,根据属性类型的不同,生成一个 MustacheVarReference 引用对象,并将其作为数组返回。如果匹配不上,那么将会返回一个空的 PsiReference 数组。
 */
public class HywayReferenceProvider extends PsiReferenceProvider {
    @NotNull
    @Override
    public PsiReference[] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) {

        if (!HywayFileUtil.isOnHywayFile(psiElement)) {
            return new PsiReference[0];
        }

        String text = psiElement.getText().replaceAll("\"+", "");
        if (Pattern.compile("\\{\\{.*\\}\\}").matcher(text).matches() && text.length() > 4) {
            String valueType = "var";
            if (psiElement.getParent() != null && psiElement.getParent().getParent() != null) {
                PsiElement tag = psiElement.getParent().getParent();
                String attr = null;
                if (psiElement.getContext() != null) {
                    attr = ((XmlAttribute) psiElement.getContext()).getName();
                }
                if (attr != null && tag instanceof XmlTag) {
                    String tagName = ((XmlTag) tag).getName();
                    HywayTag weexTag = DirectiveLint.getHywayTag(tagName);
                    if (weexTag != null && weexTag.getAttribute(attr) != null) {
                        valueType = weexTag.getAttribute(attr).valueType;
                    }
                }
            }
            return new PsiReference[]{new MustacheVarReference(psiElement, valueType.toLowerCase())};
        }
        return new PsiReference[0];
    }
}

21.上下文解析程序

package com.hyway.html.tags;

import com.hyway.html.lint.Attribute;
import com.hyway.html.lint.DirectiveLint;
import com.hyway.html.lint.HywayTag;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;


import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 上下文标记解析程序
 */
public class ContextTagResolver {
    public static List<HywayTag> resolve(PsiElement any) {
        List<HywayTag> result = new ArrayList<HywayTag>();
        PsiFile file = any.getContainingFile();
        PsiDirectory directory = file.getOriginalFile().getContainingDirectory();
        if (directory == null) {
            directory = file.getOriginalFile().getParent();
        }
        if (directory == null) {
            return result;
        }
        PsiFile[] files = directory.getFiles();
        for (PsiFile p : files) {
            if (!p.getName().equals(any.getContainingFile().getName())) {
                HywayTag common = DirectiveLint.getCommonTag();
                if (common != null) {
                    HywayTag fake = new HywayTag();
                    fake.tag = p.getName().replace(".hyway", "");
                    fake.attrs = new CopyOnWriteArrayList<Attribute>(common.attrs);
                    fake.declare = p;
                    result.add(fake);
                }
            }
        }
        return result;
    }
}

22.标签信息描述

package com.hyway.html.tags;

import com.intellij.openapi.util.Condition;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.source.html.dtd.HtmlNSDescriptorImpl;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.xml.XmlAttributeDescriptor;
import com.intellij.xml.XmlElementDescriptor;
import com.intellij.xml.XmlElementsGroup;
import com.intellij.xml.XmlNSDescriptor;
import com.intellij.xml.impl.schema.AnyXmlAttributeDescriptor;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;

/** create by tangrufei by 2023/2/21  */

/** HywayTagDescriptor 主要是为 Hyway 标签提供 XML 元素描述信息,其中包含了标签的名称、默认名称、属性列表等。
 *
 * getQualifiedName() 方法返回标签的全限定名。
 *
 * getAttributesDescriptors() 方法返回标签的所有属性列表,包括 HTML 的公共属性以及自定义属性
 *
 * getAttributeDescriptor() 方法返回一个指定属性的描述器,用于描述属性的名称、类型和值等信息。
 *
 * 其他方法用于返回标签的各种描述信息,例如默认名称、声明、依赖关系等。
 *
 * 该类的主要作用是为了让 IDE 在编辑 Hyway 模板时,能够提供正确的代码补全、语法高亮等功能。
 *
 */
public class HywayTagDescriptor implements XmlElementDescriptor{
    protected final String name;
    private final PsiFile file;

    public HywayTagDescriptor(String name, PsiFile file) {
        this.name = name;
        this.file = file;
    }
        /** 获取全限定名 */
    @Override
    public String getQualifiedName() {
        return name;
    }

    @Override
    public String getDefaultName() {
        return name;
    }

    @Override
    public XmlElementDescriptor[] getElementsDescriptors(XmlTag context) {
        return EMPTY_ARRAY;
    }
    /** getAttributeDescriptor() 方法返回一个指定属性的描述器,用于描述属性的名称、类型和值等信息 */
    @Override
    public XmlElementDescriptor getElementDescriptor(XmlTag childTag, XmlTag contextTag) {
        return null;
    }
    /** getAttributesDescriptors() 方法返回标签的所有属性列表,包括 HTML 的公共属性以及自定义属性 */
    @Override
    public XmlAttributeDescriptor[] getAttributesDescriptors(@Nullable XmlTag context) {
        XmlAttributeDescriptor[] attributeDescriptors = HtmlNSDescriptorImpl.getCommonAttributeDescriptors(context);
        XmlAttributeDescriptor[] customAttributes = new XmlAttributeDescriptor[1];
        customAttributes[0] = new AnyXmlAttributeDescriptor("hyway");
        return ArrayUtil.mergeArrays(attributeDescriptors, customAttributes);
    }

    @Nullable
    @Override
    public XmlAttributeDescriptor getAttributeDescriptor(XmlAttribute attribute) {
        return getAttributeDescriptor(attribute.getName(), attribute.getParent());
    }

    @Nullable
    @Override
    public XmlAttributeDescriptor getAttributeDescriptor(@NonNls final String attributeName, @Nullable XmlTag context) {
        final XmlAttributeDescriptor descriptor = ContainerUtil.find(getAttributesDescriptors(context), new Condition<XmlAttributeDescriptor>() {
            @Override
            public boolean value(XmlAttributeDescriptor descriptor) {
                return attributeName.equals(descriptor.getName());
            }
        });
        if (descriptor != null) return descriptor;
        return null;
    }

    @Override
    public XmlNSDescriptor getNSDescriptor() {
        return null;
    }

    @Nullable
    @Override
    public XmlElementsGroup getTopGroup() {
        return null;
    }

    @Override
    public int getContentType() {
        return CONTENT_TYPE_ANY;
    }

    @Nullable
    @Override
    public String getDefaultValue() {
        return null;
    }

    @Override
    public PsiFile getDeclaration() {
        return file;
    }

    @Override
    public String getName(PsiElement context) {
        return getName();
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void init(PsiElement element) {
    }

    @Override
    public Object[] getDependences() {
        return ArrayUtil.EMPTY_OBJECT_ARRAY;
    }
}

23.标签名处理器

package com.hyway.html.tags;

import com.hyway.html.HywayIcons;
import com.hyway.html.lint.DirectiveLint;
import com.hyway.html.lint.HywayTag;
import com.hyway.html.utils.ArchiveUtil;
import com.hyway.html.utils.HywayFileUtil;
import com.intellij.codeInsight.completion.XmlTagInsertHandler;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.impl.source.xml.XmlElementDescriptorProvider;
import com.intellij.psi.xml.XmlTag;
import com.intellij.xml.XmlElementDescriptor;
import com.intellij.xml.XmlTagNameProvider;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.List;
import java.util.Set;

/**
 * @desc desc
 * @author tangrufei
 * @date 2023/2/20 16:03
 * @param null
 * @return null
 */

/**
 * 标签名称提供程序
 */
public class HywayTagNameProvider implements XmlTagNameProvider, XmlElementDescriptorProvider {
    @Override
    public void addTagNameVariants(List<LookupElement> list, @NotNull XmlTag xmlTag, String prefix) {

        if (!HywayFileUtil.isOnHywayFile(xmlTag)) {
            return;
        }

        //if (!(xmlTag instanceof HtmlTag)) {
        //    return;
        //}

        Set<String> tags = DirectiveLint.getHywayTagNames();

        for (String s : tags) {
            /** 查找元素生成器**/
            LookupElement element = LookupElementBuilder
                    .create(s)
                    .withInsertHandler(XmlTagInsertHandler.INSTANCE)
                    .withBoldness(true)
                    .withIcon(HywayIcons.TYPE)
                    .withTypeText("hyway component");
            list.add(element);
        }

        /*List<HywayTag> contextTags = ContextTagResolver.resolve(xmlTag);
        for (HywayTag tag : contextTags) {
            LookupElement element = LookupElementBuilder
                    .create(tag.tag)
                    .withInsertHandler(XmlTagInsertHandler.INSTANCE)
                    .withIcon(HywayIcons.TYPE)
                    .withTypeText("local component");
            list.add(element);
        }*/
    }

    @Nullable
    @Override
    public XmlElementDescriptor getDescriptor(XmlTag xmlTag) {

        if (!HywayFileUtil.isOnHywayFile(xmlTag)) {
            return null;
        }

        Set<String> tags = DirectiveLint.getHywayTagNames();
        List<String> htmlTags = DirectiveLint.getHtmlTags();
        if (tags.contains(xmlTag.getName()) && !htmlTags.contains(xmlTag.getName())) {
            PsiFile declare = null;
            HywayTag tag = DirectiveLint.getHywayTag(xmlTag.getName());
            if (tag != null) {
                declare = tag.declare;
            }
            if (declare == null) {

                VirtualFile virtualFile = ArchiveUtil.getFileFromArchive("constants/hyway-built-in-components.xml");
                if (virtualFile != null) {
                    declare = PsiManager.getInstance(xmlTag.getProject()).findFile(virtualFile);
                }
            }
            if (declare == null) {
                declare = xmlTag.getContainingFile();
            }
            return new HywayTagDescriptor(xmlTag.getName(), declare);
        }

        List<HywayTag> contextTags = ContextTagResolver.resolve(xmlTag);
        for (HywayTag tag : contextTags) {
            if (xmlTag.getName().equals(tag.tag)) {
                return new HywayTagDescriptor(tag.tag, tag.declare);
            }
        }

        return new HywayTagDescriptor(xmlTag.getName(), xmlTag.getContainingFile());
    }
}

24.其他工具类

存档

package com.hyway.html.utils;

import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;

import java.net.URL;
import java.net.URLDecoder;

/** create by tangrufei by 2023/2/28  */ 
public class ArchiveUtil {
    public static VirtualFile getFileFromArchive(String name) {
        String[] subPath = name.split("/");
        try {
            URL def = ArchiveUtil.class.getClassLoader().getResource("/");
            if (def != null) {
                String path = URLDecoder.decode(def.getPath(), "utf-8").replace("file:", "");
                String[] temp = path.split("!");
                if (temp.length > 1 && path.toLowerCase().contains(".jar")) {
                    path = temp[0];
                }
                VirtualFile root = JarFileSystem.getInstance().findLocalVirtualFileByPath(path);
                if (root == null) {
                    root = LocalFileSystem.getInstance().refreshAndFindFileByPath(path);
                }

                VirtualFile target = root;
                for (String s : subPath) {
                    if (target != null) {
                        target = target.findChild(s);
                    }
                }
                return target;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

额外模块

package com.hyway.html.utils;

import com.hyway.html.custom.Settings;
import com.hyway.html.lint.Attribute;
import com.hyway.html.lint.HywayTag;
import com.intellij.json.psi.JsonFile;
import com.intellij.json.psi.JsonObject;
import com.intellij.json.psi.JsonProperty;
import com.intellij.json.psi.JsonStringLiteral;
import com.intellij.lang.javascript.psi.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.file.PsiDirectoryFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

/** create by tangrufei by 2023/2/27  */
public class ExtraModulesUtil {
    public static List<HywayTag> getTagsFromNodeModules() {
        List<HywayTag> list = new ArrayList<HywayTag>();
        Project project = ProjectUtil.guessCurrentProject(null);
        Logger.debug(project.toString());
        VirtualFile vf = project.getBaseDir();
        PsiDirectory[] localModules = new PsiDirectory[0];
        if (vf != null && vf.isDirectory()) {
            Logger.debug("Project root dir: " + vf.toString());
            PsiDirectory dir = PsiDirectoryFactory.getInstance(project).createDirectory(vf);
            localModules = getNodeModules(dir);
            for (PsiDirectory directory : localModules) {
                List<PsiFile> comps = getComponents(directory, getMain(directory));
                String homePage = getHomePage(directory);
                for (PsiFile comp : comps) {
                    HywayTag tag = parseToTag(comp);
                    tag.document = homePage;
                    list.add(tag);
                }
            }
        } else {
            Logger.debug("Project base dir is null");
        }

        for (PsiDirectory dir : getGlobalModules(localModules)) {
            List<PsiFile> comps = getComponents(dir, getMain(dir));
            String homePage = getHomePage(dir);
            Logger.debug(comps.toString());
            for (PsiFile comp : comps) {
                HywayTag tag = parseToTag(comp);
                tag.document = homePage;
                list.add(tag);
            }
        }

        return list;
    }

    private static String getHomePage(PsiDirectory directory) {
        PsiFile pkg = directory.findFile("package.json");
        if (pkg != null && pkg instanceof JsonFile) {
            if (((JsonFile) pkg).getTopLevelValue() instanceof JsonObject) {
                JsonObject object = (JsonObject) ((JsonFile) pkg).getTopLevelValue();
                if (object != null) {
                    JsonProperty homePage = object.findProperty("homepage");
                    if (homePage != null && homePage.getValue() != null && homePage.getValue() instanceof JsonStringLiteral) {
                        JsonStringLiteral propValue = (JsonStringLiteral) homePage.getValue();
                        return propValue.getValue();
                    }
                }
            }
        }
        return null;
    }

    private static List<PsiDirectory> getGlobalModules(PsiDirectory[] localModules) {
        if (localModules == null) {
            localModules = new PsiDirectory[0];
        }
        List<PsiDirectory> result = new ArrayList<PsiDirectory>();
        List<String> globals = Settings.getGlobalModules();
        List<String> locals = new ArrayList<String>();
        for (PsiDirectory dir : localModules) {
            if (dir != null) {
                locals.add(dir.getVirtualFile().getCanonicalPath());
            }
        }
        for (String global : globals) {
            if (!locals.contains(global)) {
                VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByPath(global);
                if (vf != null && vf.isDirectory()) {
                    PsiDirectory dir = PsiDirectoryFactory.getInstance(ProjectUtil.guessCurrentProject(null)).createDirectory(vf);
                    if (dir != null) {
                        result.add(dir);
                    }
                }
            } else {
                Logger.info("Module " + global + " already exists locally, skip it.");
            }
        }
        Logger.debug(result.toString());
        return result;
    }

    private static HywayTag parseToTag(PsiFile comp) {
        HywayTag weexTag = new HywayTag();
        weexTag.tag = comp.getContainingFile().getName().replace(".hyway", "");
        weexTag.attrs = new CopyOnWriteArrayList<Attribute>();
        weexTag.declare = comp;
        Map<String, String> vars = HywayFileUtil.getAllVarNames(comp);
        for (Map.Entry<String, String> entry : vars.entrySet()) {
            Attribute attribute = new Attribute();
            attribute.name = convertAttrName(entry.getKey());
            attribute.valueType = getType(entry.getValue());
            weexTag.attrs.add(attribute);
        }
        return weexTag;
    }

    private static String convertAttrName(String name) {
        char[] chars = name.toCharArray();
        StringBuilder sb = new StringBuilder();
        for (char c: chars) {
            if (Character.isUpperCase(c)) {
                if (sb.length() == 0) {
                    sb.append(Character.toLowerCase(c));
                } else {
                    sb.append('-').append(Character.toLowerCase(c));
                }
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    private static String getType(String realType) {
        realType = realType.toLowerCase();
        if ("number".equals(realType) || "boolean".equals(realType) || "array".equals(realType)) {
            return realType.toLowerCase();
        }
        return "var";
    }

    private static PsiDirectory[] getNodeModules(PsiDirectory root) {
        PsiDirectory node_modules = root.findSubdirectory("node_modules");
        if (node_modules != null) {
            return node_modules.getSubdirectories();
        }
        return new PsiDirectory[0];
    }

    private static PsiFile getMain(PsiDirectory moduleRoot) {
        PsiFile pkg = moduleRoot.findFile("package.json");
        if (pkg != null && pkg instanceof JsonFile) {
            if (((JsonFile) pkg).getTopLevelValue() instanceof JsonObject) {
                JsonObject object = (JsonObject) ((JsonFile) pkg).getTopLevelValue();
                if (object != null) {
                    JsonProperty property = object.findProperty("main");
                    if (property != null && property.getValue() != null && property.getValue() instanceof JsonStringLiteral) {
                        JsonStringLiteral propValue = (JsonStringLiteral) property.getValue();
                        String value = propValue.getValue();
                        PsiFile psiFile = moduleRoot.findFile(value.replace("./", ""));
                        return psiFile;
                    }
                }
            }
        }
        return null;
    }

    private static List<PsiFile> getComponents(PsiDirectory root, PsiFile file) {
        List<PsiFile> results = new ArrayList<PsiFile>();
        if (file != null && file instanceof JSFile) {
            for (PsiElement element : file.getChildren()) {
                if (element instanceof JSExpressionStatement) {
                    JSExpression expression = ((JSExpressionStatement) element).getExpression();
                    if (expression instanceof JSCallExpression
                            && ((JSCallExpression) expression).getArguments().length == 1
                            && ((JSCallExpression) expression).getArguments()[0] instanceof JSLiteralExpression) {
                        JSLiteralExpression expression1 = (JSLiteralExpression) ((JSCallExpression) expression).getArguments()[0];
                        Object val = expression1.getValue();
                        if (val != null) {
                            String[] temp = val.toString().replace("./", "").split("/");
                            if (temp != null && temp.length > 0) {
                                String fileName = temp[temp.length - 1];
                                if (fileName.toLowerCase().endsWith(".hyway")) {
                                    PsiDirectory start = root;
                                    for (int i = 0; i < temp.length - 1; i++) {
                                        PsiDirectory dir = start.findSubdirectory(temp[i]);
                                        if (dir != null) {
                                            start = dir;
                                        }
                                    }
                                    PsiFile weexScript = start.findFile(temp[temp.length - 1]);
                                    if (weexScript != null) {
                                        results.add(weexScript);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return results;
    }

    public static boolean isNodeModule(PsiDirectory dir) {
        PsiFile pkg = dir.findFile("package.json");
        return pkg != null && pkg instanceof JsonFile;
    }

    public static String getModuleName(PsiDirectory dir) {
        PsiFile pkg = dir.findFile("package.json");
        String name = dir.getName();
        if (pkg != null && pkg instanceof JsonFile) {
            if (((JsonFile) pkg).getTopLevelValue() instanceof JsonObject) {
                JsonObject object = (JsonObject) ((JsonFile) pkg).getTopLevelValue();
                if (object != null) {
                    JsonProperty property = object.findProperty("name");
                    JsonProperty property1 = object.findProperty("version");
                    if (property != null && property.getValue() != null && property.getValue() instanceof JsonStringLiteral) {
                        JsonStringLiteral propValue = (JsonStringLiteral) property.getValue();
                        name = propValue.getValue();
                        if (property1 != null && property1.getValue() != null && property1.getValue() instanceof JsonStringLiteral) {
                            JsonStringLiteral propValue1 = (JsonStringLiteral) property1.getValue();
                            name = name + ":" + propValue1.getValue();
                        }
                    }
                }
            }
        }
        return name;
    }

    public static void main(String[] args) {
        System.out.println(convertAttrName("Foo"));
        System.out.println(convertAttrName("HowAreYou"));
        System.out.println(convertAttrName("areYouOK"));
    }
}

文件处理

package com.hyway.html.utils;

import com.hyway.base.util.CodeUtil;
import com.hyway.html.lint.Attribute;
import com.hyway.html.lint.DirectiveLint;
import com.hyway.html.lint.HywayTag;
import com.intellij.lang.javascript.psi.*;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiInvalidElementAccessException;
import com.intellij.psi.xml.*;


import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/** create by tangrufei by 2023/2/27  */
public class HywayFileUtil {
    /**当前文件名*/
    private static String currentFileName = "";
    /**缓存的导出语句*/
    private static JSObjectLiteralExpression cachedExportsStatement;
    private static Map<String, String> vars = new ConcurrentHashMap<String, String>();
    /** 所有方法函数 **/
    private static Set<String> functions = new HashSet<String>();
    /**判断当前编辑器是否处于.hyway结尾文件中*/
    public static boolean isOnHywayFile(PsiElement element) {
        if (element.getContainingFile() != null) {
            return element.getContainingFile().getName().toLowerCase().endsWith(".hyway");
        } else {
            return false;
        }
    }

    public static String getValueType(XmlAttributeValue value) {
        if (value.getContext() != null && value.getContext() instanceof XmlAttribute) {
            String attrName = ((XmlAttribute) value.getContext()).getName();
            if (value.getContext().getContext() != null && value.getContext().getContext() instanceof XmlTag) {
                String tagName = ((XmlTag) value.getContext().getContext()).getName();
                HywayTag tag = DirectiveLint.getHywayTag(tagName);
                if (tag != null) {
                    Attribute attribute = tag.getAttribute(attrName);
                    if (attribute != null) {
                        return attribute.valueType;
                    }
                }
            }
        }
        return "var";
    }

    public static String getJSPropertyType(JSProperty jsProperty) {
        JSType t = jsProperty.getJSType();
        String typeString = "var";
        if (t == null) {
            if (jsProperty.getValue() instanceof JSObjectLiteralExpression) {
                typeString = "Object";
            }
        } else {
            typeString = t.getResolvedTypeText();
        }
        return typeString;
    }

    public static boolean hasSameType(XmlAttributeValue value, JSProperty property) {
        String valueType = getValueType(value);
        String JsType = getJSPropertyType(property);
        return hasSameType(valueType, JsType);
    }

    public static boolean hasSameType(String value, String property) {
        if (value.toLowerCase().equals(property.toLowerCase())) {
            return true;
        }
        if (value.equals("var")) {
            return true;
        }
        return false;
    }

    private static void ensureFile(PsiElement element) {
        String path = String.valueOf(System.currentTimeMillis());
        if (element != null
                && element.getContainingFile() != null
                && element.getContainingFile().getVirtualFile() != null) {
            path = element.getContainingFile().getVirtualFile().getPath();
        }
        if (!currentFileName.equals(path)) {
            cachedExportsStatement = null;
            vars.clear();
            functions.clear();
            currentFileName = path;
        }
    }

    public static JSObjectLiteralExpression getExportsStatement(PsiElement anyElementOnHywayScript) {
        ensureFile(anyElementOnHywayScript);

        if (isValid(cachedExportsStatement)) {
            return cachedExportsStatement;
        }

        //WELCOME TO HELL!!!
        PsiFile file = anyElementOnHywayScript.getContainingFile();
        if (file instanceof XmlFile) {
            XmlDocument document = ((XmlFile) file).getDocument();
            if (document != null) {
                for (PsiElement e : document.getChildren()) {
                    if (e instanceof XmlTag) {
                        if ("script".equals(((XmlTag) e).getName())) {
                            for (PsiElement e1 : e.getChildren()) {
                                if (e1 instanceof JSEmbeddedContent) {
                                    for (PsiElement e2 : e1.getChildren()) {
                                        if (e2 instanceof JSExpressionStatement) {
                                            for (PsiElement e3 : e2.getChildren()) {
                                                if (e3 instanceof JSAssignmentExpression) {
                                                    PsiElement[] children = e3.getChildren();
                                                    if (children.length == 2) {
                                                        if (children[0].getText().equals("module.exports")) {
                                                            if (children[1] instanceof JSObjectLiteralExpression) {
                                                                cachedExportsStatement = (JSObjectLiteralExpression) children[1];
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return cachedExportsStatement;
    }

    private static boolean isValid(JSObjectLiteralExpression expression) {
        if (expression == null) {
            return false;
        }
        try {
            PsiFile file = expression.getContainingFile();
            if (file == null) {
                return false;
            }
        } catch (PsiInvalidElementAccessException e) {
            return false;
        }
        JSProperty data = expression.findProperty("data");
        if (data == null || data.getValue() == null) {
            return false;
        }
        return true;
    }

    public static Map<String, String> getAllVarNames(PsiElement any) {
        ensureFile(any);
        getVarDeclaration(any, String.valueOf(System.currentTimeMillis()));
        return vars;
    }

    public static Set<String> getAllFunctionNames(PsiElement any) {
        ensureFile(any);
        getFunctionDeclaration(any, String.valueOf(System.currentTimeMillis()));
        return functions;
    }

    public static JSProperty getVarDeclaration(PsiElement anyElementOnHywayScript, String valueName) {
        valueName = CodeUtil.getVarNameFromMustache(valueName);
        JSObjectLiteralExpression exports = getExportsStatement(anyElementOnHywayScript);
        vars.clear();
        JSProperty ret = null;
        if (exports != null) {
            try {
                PsiFile file = exports.getContainingFile();
                if (file == null) {
                    return null;
                }
            } catch (PsiInvalidElementAccessException e) {
                return null;
            }
            JSProperty data = exports.findProperty("data");
            if (data == null || data.getValue() == null) {
                return null;
            }
            for (PsiElement pe : data.getValue().getChildren()) {
                if (pe instanceof JSProperty) {
                    String varName = ((JSProperty) pe).getName();
                    String varValue = getJSPropertyType((JSProperty) pe);
                    if (varName != null && varValue != null) {
                        vars.put(varName, varValue);
                    }
                    if (valueName.equals(varName)) {
                        ret = (JSProperty) pe;
                    }
                }

            }
        }
        return ret;
    }

    public static JSFunctionExpression getFunctionDeclaration(PsiElement anyElementOnHywayScript, String valueName) {
        valueName = CodeUtil.getFunctionNameFromMustache(valueName);
        JSObjectLiteralExpression exports = getExportsStatement(anyElementOnHywayScript);
        functions.clear();
        JSFunctionExpression ret = null;
        if (exports != null) {
            try {
                PsiFile file = exports.getContainingFile();
                if (file == null) {
                    return null;
                }
            } catch (PsiInvalidElementAccessException e) {
                return null;
            }
            JSProperty data = exports.findProperty("methods");
            if (data != null && data.getValue() != null) {
                for (PsiElement e : data.getValue().getChildren()) {
                    if (e instanceof JSProperty) {
                        for (PsiElement e1 : e.getChildren()) {
                            if (e1 instanceof JSFunctionExpression) {
                                functions.add(((JSFunctionExpression) e1).getName());
                                if (valueName.equals(((JSFunctionExpression) e1).getName())) {
                                    ret = (JSFunctionExpression) e1;
                                }
                            }
                        }
                    }
                }
            }
        }
        return ret;
    }

    public static int getExportsEndOffset(PsiElement anyElementOnHywayScript, String name) {
        JSObjectLiteralExpression exports = getExportsStatement(anyElementOnHywayScript);
        if (exports != null) {
            try {
                PsiFile file = exports.getContainingFile();
                if (file == null) {
                    return -1;
                }
            } catch (PsiInvalidElementAccessException e) {
                return -1;
            }
            JSProperty data = exports.findProperty(name);
            if (data == null || data.getValue() == null) {
                return -1;
            }
            return data.getValue().getTextRange().getEndOffset() - 1;
        }
        return -1;
    }

    public static boolean isMustacheValue(String value) {
        if (value == null) {
            return false;
        }
        return Pattern.compile("\\{\\{.+?\\}\\}").matcher(value).matches();
    }

    public static boolean containsMustacheValue(String value) {
        if (value == null) {
            return false;
        }
        return Pattern.compile(".*\\{\\{.+?\\}\\}.*").matcher(value).matches();
    }

    public static Map<String, TextRange> getVars(String src) {
        Map<String, TextRange> results = new IdentityHashMap<String, TextRange>();
        Pattern p = Pattern.compile("\\{\\{.+?\\}\\}");
        Matcher m = p.matcher(src);
        while (m.find()) {
            String g = m.group().replaceAll("\\{+", "").replaceAll("\\}+", "").trim();
            TextRange textRange = new TextRange(m.start(), m.end());
            results.put(g, textRange);
        }
        return results;
    }
}
文章不错,扫码支持一下吧~
上一篇 下一篇
评论
来首音乐
光阴似箭
今日已经过去小时
这周已经过去
本月已经过去
今年已经过去个月