phpstorm 插件开发实例

0 简介

PhpStorm 是 JetBrains 公司开发的一款商业的 PHP 集成开发工具,是我用过的最好的php ide,比zend studio,netbeans 强大太多。phpstorm的强大,很大程度是依赖安装各种插件的。 phpstorm的底层是IntelliJ IDEA,一款java开发ide,JetBrains旗下的PyCharm、WebStorm 都是如此。可以理解为通过对IntelliJ IDEA各种高级制定、开发各种插件组合形成不同语言特色的ide。

0.1目标

java 零基础开始写一个phpstorm的小插件, 实现按快捷键快速输入 Yii::t('category','lng') ,这样用Yii框架写多语言的网站就轻松多了。
效果图:

0.2文档

http://www.jetbrains.org/intellij/sdk/docs/welcome.html

有很详细的介绍,都是英文

2 环境准备

2.1 安装jdk

http://www.oracle.com/technetwork/java/javase/downloads/index.html

2.2 安装IntelliJ IDEA Community Edition 社区版就够了

http://www.jetbrains.com/idea/download/#section=windows

2.3 安装phpstorm

http://www.jetbrains.com/phpstorm/download/#section=windows

3 代码

3.1 新建项目

3.2 添加java jdk,IntelliJ IDEA Community Edition lib

2017-08-29-14-06-45

3.3 添加 phpstorm 相关库php-openapi.jar、php.jar

在 【phpstorm安装目录】\plugins\php\lib 下

2017-08-29-14-09-31

3.4 设置引用库的依赖关系 comppile => provided

不然打包的时候phpstorm会把php-openapi.jar、php.jar 也一起加上
2017-08-29-14-10-43

打开META-INF/plugin.xml
添加

  <depends>com.jetbrains.php</depends>
  <depends>com.intellij.modules.platform</depends>

文档说明:http://www.jetbrains.org/intellij/sdk/docs/phpstorm/setting_up_environment.html

3.5 新建package

src 目录,右键 new -> package => com.xu

3.6 新建action

2017-08-29-14-23-02
2017-08-29-14-46-18
生成 com/xu/YiiAction.java,META-INF/plugin.xml 会自动增加配置

  <action id="com.xu.YiiAction" class="com.xu.YiiAction" text="Yii::t complete">
          <add-to-group group-id="EditorPopupMenu" anchor="first"/>
          <keyboard-shortcut keymap="$default" first-keystroke="alt 3"/>
      </action>

在 com/xu/YiiAction.java 的actionPerformed() 入口方法写个hello 弹窗

    public void actionPerformed(AnActionEvent e) {
        Messages.showMessageDialog(e.getProject(), "Hello", "Information", Messages.getInformationIcon());
    }

以上操作就定义了一个action,在ide编辑器右键菜单就会出现 Yii::t complete了

3.7 打包测试一下

2017-08-29-14-29-00

将生成的插件Yiitest.jar 安装到phpstorm
最快捷的方法是直接将Yiitest.jar 复制到phpstorm的插件安装目录,再重启phpstorm(C:\Users\sherman.PhpStorm2017.2\config\plugins)

效果:
2017-08-29-14-51-08
2017-08-29-14-51-21

3.8 具体功能逻辑的实现

3.8.1 大概步骤

    1. 获取选中文本的值 lng
    1. 获取当前打开文件的路径,分析路径,取得 Yii::t() 中的第一个参数 cate
    1. 清空选中的文本,Yii::('cate','lng') 替换。

3.8.2 具体代码

com/xu/YiiAction.java

package com.xu;

import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;

import static com.intellij.openapi.actionSystem.CommonDataKeys.VIRTUAL_FILE;

public class YiiAction extends AnAction {

    @Override
    public void actionPerformed(AnActionEvent e) {
        final Editor editor = e.getRequiredData(CommonDataKeys.EDITOR);
        final Project project = e.getRequiredData(CommonDataKeys.PROJECT);
        final PsiElement psiElement = e.getData(CommonDataKeys.PSI_ELEMENT);
        final PsiFile psiFile = e.getData(CommonDataKeys.PSI_FILE);


        final Document document = editor.getDocument();
        final SelectionModel selectionModel = editor.getSelectionModel();

        int start = selectionModel.getSelectionStart();
        int end = selectionModel.getSelectionEnd();
        String replaceStr = selectionModel.getSelectedText();

        final VirtualFile file = e.getData(VIRTUAL_FILE);
        if (file == null) {
            Messages.showMessageDialog("No active file", "Error",Messages.getErrorIcon());
            return;
        }
        if(psiElement!= null){
            Messages.showMessageDialog("Please select the text", "Error",Messages.getErrorIcon());
            return;
        }
        final String findName = Common.getYiiTName(file.getCanonicalPath());

        assert psiFile != null;
        if(psiFile.findElementAt(editor.getCaretModel().getOffset()).getLanguage().getDisplayName().equals("PHP")){
            replaceStr = "Yii::t('"+findName+"','"+replaceStr+"')";
            start--;
            end++;
        }else{
            replaceStr = "<?=Yii::t('"+findName+"','"+replaceStr+"')?>";
        }

        final String str = replaceStr;
        final int LastStart = start;
        final int LastEnd= end;
        //替换选中的文本
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                document.replaceString(LastStart,LastEnd,str);
            }
        };
        WriteCommandAction.runWriteCommandAction(project,runnable);
        selectionModel.removeSelection();
    }

    /**
     *  判断文本被选中,才触发插件显示
     * @param e  AnActionEvent
     */
    @Override
    public void update(AnActionEvent e) {
        final Project project = e.getData(CommonDataKeys.PROJECT);
        final Editor editor = e.getData(CommonDataKeys.EDITOR);
        e.getPresentation().setVisible(project != null && editor != null && editor.getSelectionModel().hasSelection());
    }
}

com/xu/Common.java

package com.xu;

import org.jetbrains.annotations.NotNull;

/**
 * zhenjun.xu
 * 2016/12/30
 */
class Common {

    /**
     *  根据文件路径,生成 Yii::t 所需的分类名称
     * @param filePath 路径
     * @return 分类名称
     */
    static String getYiiTName(String filePath){
        String[] pathArray = filePath.split("/");
        String module = "";
        String controller = "";
        String name = "site";
        int modulePosition = 0;
        /*
         遍历字符串数组,查找module,controller
         */
        for (int i=0;i<pathArray.length;i++){
            if(pathArray[i].contentEquals("modules")){
                module = pathArray[i+1];
                modulePosition = i;
            }
            if(pathArray[i].contentEquals("controllers")){
                controller = pathArray[pathArray.length-1].replace("Controller.php","");
            }
            if(pathArray[i].contentEquals("views")){
                controller = toUpperCaseFirstOne(pathArray[i+1]);
            }
        }
        if(module.length() >0  && controller.length()>0){
            name = module+controller;
        }
        if(module.length()==0 && controller.length()>0){
            name = toLowerCaseFirstOne(controller);
        }
        if(module.length()>0 && controller.length()==0){
            name = pathArray[modulePosition+3];
        }
        if(module.length()==0 && controller.length()==0){
            name = reverse(pathArray)[1];
        }
        return name;
    }

    /**
     * 首字母大写
     * @param name 字符串
     * @return 首字母大写
     */
    private static String toUpperCaseFirstOne(String name) {
        char[] cs=name.toCharArray();
        cs[0]-=32;
        return String.valueOf(cs);
    }
    /**
     * 首字母转小写
     * @param s  字符串
     * @return 首字母大写
     */
    private static String toLowerCaseFirstOne(String s){
        if(Character.isLowerCase(s.charAt(0)))
            return s;
        else
            return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
    }

    /**
     *
     * 返回一个单元顺序相反的数组
     * 当 arr 为 null 时返回 new String[0] 。
     *
     * @param arr 数组
     * @return 数组
     */
    private static String[] reverse(String[] arr) {
        String[] rarr = new String[0];
        if (arr != null) {
            rarr = new String[arr.length];
            int j = 0;
            for (int i = arr.length - 1; i >= 0; i--) {
                rarr[j++] = arr[i];
            }
        }
        return rarr;
    }

}


以上源码已上传到github: https://github.com/xuzhenjun130/Yii-t

phpstorm 可以搜索下载

2017-08-29-15-04-18