zoukankan      html  css  js  c++  java
  • PyCharm插件开发实践-PyGetterAndSetter

    背景需求

    在面向对象的设计中,典型如Java语言,为了控制对象属性的修改入口,我们常用的做法是把属性设置为private,然后通过getter和setter方法访问、修改该属性。

    但是在Python语言中,并没有Java的访问控制符,对象的属性可以直接访问、修改。

    为了良好的设计规范,我们可以规定,在Python类中,所有的对象属性均以下划线"_"前缀开头,同时编写该属性的getter和setter方法,在其他地方引用的时候,禁止出现直接引用。

    在IDEA等IDE中,可以对Java的对象属性直接生成getter和setter方法,但是针对Python没有这样的功能。大量的getter和setter方法,很耗费精力,所以需要一款插件来辅助自动化生成Python对象属性的getter和setter方法。

    搭建环境

    编写IDEA系列的插件开发环境,可以看我之前的一篇文章:《IntelliJ IDEA/Android Studio插件开发指南》

    官方开发文档:IntelliJ Platform SDK

    过程拆解

    Python文件例子:

    class Test(object):
         def __init__(self):
             self._var1 = ""
             self._var2 = 0
    

    明确了需求、输入(python对象属性定义代码)、输出(PyCharm插件自动生成getter和setter)后,我们针对这个插件的流程进行拆解:

    1. 首先,用户选中了对应行的文本内容,插件获取到该内容文本
    2. 在内容文本中过滤出变量,在本例中,就是过滤出_var1, _var2
    3. 拼装变量的getter和setter方法
    4. 计算出要插入的位置
    5. 回写到编辑器中

    1. 获取文本

    在PyCharm插件中,Editor对象是编辑器的总览,其中包含很多Model,比如

    CaretModel caretModel=editor.getCaretModel(); // 用于描述插入光标
    SelectionModel selectionModel = editor.getSelectionModel();  // 用于描述选中的文本
    FoldingModel foldingModel = editor.getFoldingModel();  // 用于描述代码折叠区域
    IndentsModel indentModel = editor.getIndentsModel();  // 用于描述缩进
    ……
    

    在这里,我们只需要SelectionModel。

            // 获取光标选中文本段对象
            SelectionModel selectionModel = editor.getSelectionModel();
            // 拿到选中部分字符串
            String selectedText = selectionModel.getSelectedText();
    

    2. 正则匹配

    拿到选中文本后,有可能选择了多行,里面包含多个变量,所以我们需要获取到变量列表。
    观察到所有的变量都是self.abc=xxx的模式,我们可以考虑用正则匹配把其中的abc获取到。
    Java中负责正则匹配并获取匹配字符串的类是PatternMatcher

        /**
         * 获取选中文本中所有的self.value中的value    <br>
         * e.g. self.value = xxx,or self._value = xxx,     <br>
         * 可以获取到其中的value
         *
         * @param selectedText 选中文本
         * @return 变量字符串列表
         */
        public ArrayList<String> getFieldList(String selectedText) {
            ArrayList<String> list = new ArrayList<>();
            // 删除所有空格
            selectedText = selectedText.replaceAll(" ", "");
            // 正则匹配获得变量字符串
            String reg = "self.(.*?)=";
            Pattern pattern = Pattern.compile(reg);
            Matcher matcher = pattern.matcher(selectedText);
            while (matcher.find()) {
                list.add(matcher.group(1));
            }
            return list;
        }
    

    3. 拼装方法

    Python中的getter和setter方法都非常简单,我们可以先创造一个模板:

            // 定义Getter和Setter的模板
            String getterTemplate = "    def get_word(self):
            return self.field
        ";
            String setterTemplate = "    def set_word(self, word):
            self.field = word
        ";
    

    之所以存在空格,是为了匹配PyCharm的缩进,我这里使用的4个空格做缩进,如果你使用两个空格的话,在这里修改成两个空格即可。
    在这里不能使用 ,我尝试了 ,在PyCharm中无法自动转换为4个空格,会报错。

    上一步获取到的变量,有可能不存在下换线前缀,也有可能存在1个或者2个下划线前缀,比如var,_var,__var,他们对应的gett和setter如下:

    # 假如变量为_var
    def get_var(self):
        return self._var;
    
    def set_var(self, var):
        self._var = var;
    

    可以看到在self.xxx中需要使用变量,而在get_xxx和setter的参数中,需要删除对应的下划线。所以有:

            ……
            // 对于 “_value” 类型的变量,在set方法参数中,只需要“value”
            for (String field : fieldList) {
                String tmp = field;
                int i = 0;
                while (tmp.charAt(i) == '_') {
                    tmp = tmp.substring(1);
                }
                // 替换掉模板中的变量
                String customGetter = getterTemplate.replaceAll("word", tmp).replaceAll("field", field);
                String customSetter = setterTemplate.replaceAll("word", tmp).replaceAll("field", field);
                stringBuilder.append("
    ").append(customGetter).append("
    ").append(customSetter);
            }
           ……
    

    4. 计算位置

    首先需要获取到Document对象,这是负责描述文档的,里面有很多负责文档的方法,比如在文件中插入字符串,计算文件行数,计算文档长度,删除相应内容等等。

    Document document = editor.getDocument();
    

    为了方便简单,我们设定在选中文本的下一行生成getter和setter。

            // 得到选中字符串的结束位置
            int endOffset = selectionModel.getSelectionEnd();
            // 得到最大插入字符串(生成的Getter和Setter函数字符串)的位置
            int maxOffset = document.getTextLength();
            // 计算选中字符串所在的行号,通过行号得到下一行的第一个字符的起始偏移量
            int curLineNumber = document.getLineNumber(endOffset);
            int docLineCount = document.getLineCount();
            // 如果目前文件行数不足以支持选中文本的下一行,也就是选中文本包含最后一行,就插入一个空行
            if (docLineCount - 1 < curLineNumber + 1) {
                Runnable runnable = () -> document.insertString(maxOffset,"
    ");
                WriteCommandAction.runWriteCommandAction(project, runnable);
            }
            int nextLineStartOffset = document.getLineStartOffset(curLineNumber + 1);
    

    5. 回写

    将字符串插入文档中,不能直接使用document.insertString,会error: Assertion failed: Write access is allowed inside write-action only (see com.intellij.openapi.application.Application.runWriteAction())

    需要把这个任务放入一个Runnable中,然后由WriteCommandAction来调度。

    参考:Write access is allowed inside write-action only

             // 对文档进行操作部分代码,需要放入runnable,不然IDEA会卡住
            Runnable runnable = () -> document.insertString(nextLineStartOffset, genGetterAndGetter(fieldList));
    
            // 加入任务,由IDE调度任务
            WriteCommandAction.runWriteCommandAction(project, runnable);
    

    效果

    目前来看效果还不错,关于安装方法、使用方法,见github的README。

    资源

    github链接:https://github.com/mybichu/PyGetterAndSetter

  • 相关阅读:
    CSS背景background、backgroundposition使用详解
    为何img、input等内联元素可以设置宽、高
    各种Js封装
    CSS布局奇淫技巧之各种居中
    document.compatMode属性
    jquery的each()详细介绍
    jQuery所支持的css样式
    js,jQuery获取html5的data*属性
    PHP中MVC的编程思想浅谈
    php读取xml的神器
  • 原文地址:https://www.cnblogs.com/yuxiuyan/p/15359781.html
Copyright © 2011-2022 走看看