转载请注明出处:【huachao1001的专栏:http://blog.csdn.net/huachao1001/article/details/53885981】

我们开发AndroidStudio插件,绝大多数插件功能是用在编辑文本上面,让用户开发更便捷。这篇文章主要是介绍Editor部分,看完之后可以开发简单实用的插件啦!在看本文之前,请先确定已经看完《AndroidStudio插件开发(Hello World篇)》《 AndroidStudio插件开发(进阶篇之Action机制)》。因为这两篇是基础,没有这些基础就无法继续往下读。

在本文的最后使用简单的代码实现简单的插件:自动生成Getter和Setter函数的插件。如下图所示,下图中,分别演示了通过点击和使用快捷键的方式触发Action。

1. 文本编辑

1.1 CaretModel和SelectionModel

为了能够更灵活地控制Editor,IDEA插件开发中将Editor细分为多个模型。在本文中只简单介绍CaretModel和SelectionModel,除了CaretModel和SelectionModel以外,还有如下几种模型:

  • FoldingModel
  • IndentsModel
  • ScrollingModel
  • ScrollingModel
  • SoftWrapModel

获取Editor的CaretModel和SelectionModel对象方法如下:

  1. @Override
  2. public void actionPerformed(AnActionEvent e) {
  3. Editor editor = e.getData(PlatformDataKeys.EDITOR);
  4. if (editor == null)
  5. return;
  6.  
  7. SelectionModel selectionModel = editor.getSelectionModel();
  8. CaretModel caretModel=editor.getCaretModel();
  9. }

  

其他模型对象获取方式类似,通过Editor对象的相应函数即可得到。

1.1.1 CaretModel对象

CaretModel对象用于描述插入光标,通过CaretModel对象,可以实现如下功能:

  1. moveToOffset(int offset):将光标移动到指定位置(offset)
  2. getOffset():获取当前光标位置偏移量
  3. getCaretCount:获取光标数量(可能有多个位置有光标)
  4. void addCaretListener(CaretListener listener) ,void removeCaretListener(CaretListener listener):添加或移除光标监听器(CareListener)
  5. Caret addCaret(VisualPosition visualPosition):加入新的光标
  6. ……

1.1.2 SelectionModel对象

SelectionModel对象用于描述光标选中的文本段,通过SelectionModel对象可以实现如下功能:

  1. String getSelectedText() :获取选中部分字符串。
  2. int getSelectionEnd():获取选中文本段末尾偏移量
  3. int getSelectionStart():获取选中文本段起始位置偏移量
  4. void setSelection(int start, int end):设置选中,将staert到end部分设置为选中
  5. void removeSelection():将选中文本段删除
  6. void addSelectionListener(SelectionListener listener):添加监听器,用于监听光标选中变化。
  7. void selectLineAtCaret():将光标所在的行设置为选中。
  8. void selectWordAtCaret(boolean honorCamelWordsSettings):将光标所在的单词设置为选中。honorCamelWordsSettings表示是否驼峰命名分隔,如果为true,则大写字母为单词的边界
  9. ……

1.2 Document对象

与Editor中的其他对象一样,通过Editor对象的一个getter函数即可得到Document对象:

  1. Document document = editor.getDocument();

  

Document对象用于描述文档文件,通过Document对象可以很方便的对Editor中的文件进行操作。可以做如下这些事情:

  1. String getText()String getText( TextRange range):获取Document对象对应的文件字符串。
  2. int getTextLength():获取文件长度。
  3. int getLineCount():获取文件的行数
  4. int getLineNumber(int offset):获取指定偏移量位置对应的行号offset取值为[0,getTextLength()-1]
  5. int getLineStartOffset(int line):获取指定行的第一个字符在全文中的偏移量,行号的取值范围为:[0,getLineCount()-1]
  6. int getLineEndOffset(int line):获取指定行的最后一个字符在全文中的偏移量,行号的取值范围为:[0,getLineCount()-1]
  7. void insertString(int offset, CharSequence s):在指定偏移位置插入字符串
  8. void deleteString(int startOffset, int endOffset):删除[startOffset,endOffset]位置的字符串,如果文件为只读,则会抛异常。
  9. void replaceString(int startOffset, int endOffset, CharSequence s):替换[startOffset,endOffset]位置的字符串为s
  10. void addDocumentListener( DocumentListener listener):添加Document监听器,在Document内容发生变化之前和变化之后都会回调相应函数。
  11. ……

1.3 实现自动生成Getter和Setter函数的插件

有了上面的认识后,我们可以开始写个简单的Getter和Setter函数插件了。首先创建一个Action,名为GetterAndSetter,并在plugin.xml中注册。plugin.xml的<acitons>标签部分如下:

  1. <actions>
  2. <action id="StudyEditor.GetterAndSetter" class="com.huachao.plugin.GetterAndSetter" text="Getter And Setter"
  3. description="生成Getter和Setter方法">
  4. <add-to-group group-id="EditorPopupMenu" anchor="first"/>
  5. <keyboard-shortcut keymap="$default" first-keystroke="ctrl alt G"/>
  6. </action>
  7. </actions>

  

通过前面两篇文章的学习,我们知道,定义Action时需要重写actionPerformed和update函数。

  1. @Override
  2. public void actionPerformed(AnActionEvent e) {
  3. //获取Editor和Project对象
  4. Editor editor = e.getData(PlatformDataKeys.EDITOR);
  5. Project project = e.getData(PlatformDataKeys.PROJECT);
  6. if (editor == null||project==null)
  7. return;
  8.  
  9. //获取SelectionModel和Document对象
  10. SelectionModel selectionModel = editor.getSelectionModel();
  11. Document document = editor.getDocument();
  12.  
  13. //拿到选中部分字符串
  14. String selectedText = selectionModel.getSelectedText();
  15.  
  16. //得到选中字符串的起始和结束位置
  17. int startOffset = selectionModel.getSelectionStart();
  18. int endOffset = selectionModel.getSelectionEnd();
  19.  
  20. //得到最大插入字符串(即生成的Getter和Setter函数字符串)位置
  21. int maxOffset = document.getTextLength() - 1;
  22.  
  23. //计算选中字符串所在的行号,并通过行号得到下一行的第一个字符的起始偏移量
  24. int curLineNumber = document.getLineNumber(endOffset);
  25. int nextLineStartOffset = document.getLineStartOffset(curLineNumber + 1);
  26.  
  27. //计算字符串的插入位置
  28. int insertOffset = maxOffset > nextLineStartOffset ? nextLineStartOffset : maxOffset;
  29.  
  30. //得到选中字符串在Java类中对应的字段的类型
  31. String type = getSelectedType(document, startOffset);
  32.  
  33. //对文档进行操作部分代码,需要放入Runnable接口中实现,由IDEA在内部将其通过一个新线程执行
  34. Runnable runnable = new Runnable() {
  35. @Override
  36. public void run() {
  37. //genGetterAndSetter为生成getter和setter函数部分
  38. document.insertString(insertOffset, genGetterAndSetter(selectedText, type));
  39. }
  40. };
  41.  
  42. //加入任务,由IDEA调度执行这个任务
  43. WriteCommandAction.runWriteCommandAction(project, runnable);
  44.  
  45. }
  46.  
  47. @Override
  48. public void update(AnActionEvent e) {
  49. Editor editor = e.getData(PlatformDataKeys.EDITOR);
  50. SelectionModel selectionModel = editor.getSelectionModel();
  51.  
  52. //如果没有字符串被选中,那么无需显示该Action
  53. e.getPresentation().setVisible(editor != null && selectionModel.hasSelection());
  54. }

 

剩下的还有获取选中字段的类型和生成Getter、Setter函数两个部分,两个函数如下:

  1. private String getSelectedType(Document document, int startOffset) {
  2.  
  3. String text = document.getText().substring(0, startOffset).trim();
  4. int startIndex = text.lastIndexOf(' ');
  5.  
  6. return text.substring(startIndex + 1);
  7. }
  8.  
  9. private String genGetterAndSetter(String field, String type) {
  10. if (field == null || (field = field.trim()).equals(""))
  11. return "";
  12. String upperField = field;
  13. char first = field.charAt(0);
  14. if (first <= 'z' && first >= 'a') {
  15. upperField = String.valueOf(first).toUpperCase() + field.substring(1);
  16. }
  17. String getter = "\tpublic TYPE getUpperField(){ \n\t\treturn this.FIELD;\n\t}";
  18. String setter = "\tpublic void setUpperField(TYPE FIELD){\n\t\tthis.FIELD=FIELD;\n\t}";
  19.  
  20. String myGetter = getter.replaceAll("TYPE", type).replaceAll("UpperField", upperField).replaceAll("FIELD", field);
  21. String mySetter = setter.replaceAll("TYPE", type).replaceAll("UpperField", upperField).replaceAll("FIELD", field);
  22.  
  23. return "\n"+myGetter + "\n" + mySetter + "\n";
  24. }

  运行后如下:

注意:在对Document进行修改时,需要实现Runnable接口并将修改部分代码写入run函数中,最后通过 WriteCommandAction的runWriteCommandAction函数执行。

2. Editor的坐标系统:位置和偏移量

前面小节我们知道,通过CaretModel对象我们可以获取当前光标位置。但在Editor中位置分为两种,一种是逻辑位置,对应LogicalPosition类;另一种是视觉位置,对应VisualPosition类。

LogicalPosition与VisualPosition的区别通过如下图很显然能区分开来。

上如中,光标的坐标为:

LogicalPosition:(13,6) 
VisualPosition:(9,6)

注意,行号和列号都是从0开始。

另外,获取LogicalPosition和VisualPosition方法如下:

  1. @Override
  2. public void actionPerformed(AnActionEvent e) {
  3. //获取Editor和Project对象
  4. Editor editor = e.getData(PlatformDataKeys.EDITOR);
  5. Project project = e.getData(PlatformDataKeys.PROJECT);
  6. if (editor == null || project == null)
  7. return;
  8. CaretModel caretModel = editor.getCaretModel();
  9. LogicalPosition logicalPosition = caretModel.getLogicalPosition();
  10. VisualPosition visualPosition = caretModel.getVisualPosition();
  11.  
  12. System.out.println(logicalPosition + "," + visualPosition);
  13. }

  

3. Editor中的按键事件

为了监听按键时间,专门提供了TypedActionHandler类,我们只需继承TypedActionHandler,并重写execute函数即可。注意,只能监听可打印字符对应的按键。

  1. package com.huachao.plugin;
  2.  
  3. import com.intellij.openapi.actionSystem.DataContext;
  4. import com.intellij.openapi.editor.Editor;
  5. import com.intellij.openapi.editor.actionSystem.TypedActionHandler;
  6. import org.jetbrains.annotations.NotNull;
  7.  
  8. /**
  9. * Created by huachao on 2016/12/26.
  10. */
  11. public class MyTypedActionHandler implements TypedActionHandler {
  12. @Override
  13. public void execute(@NotNull Editor editor, char c, @NotNull DataContext dataContext) {
  14. System.out.println(c);
  15. }
  16. }

  

TypedAction专门处理按键相关操作,定义了TypedActionHandler后,接下来就是将自定义的TypedActionHandler加入到TypedAction中。如何获取TypedAction对象呢?具体如下:

  1. final EditorActionManager actionManager = EditorActionManager.getInstance();
  2. final TypedAction typedAction = actionManager.getTypedAction();
  3. typedAction.setupHandler(new MyTypedActionHandler());

 

上述代码即可将自定义的按键处理器成功加入,现在有个问题是,上面这段代码应该放入到哪里呢?之前我们都是重写AnAction的actionPerformed和update函数就行,能不能将上面这段代码放入到actionPerformed中呢?显然这是可以的,但是这样的话就得先点击当前Action后才能使MyTypedActionHandler被加入,并且每点击一次,就会创建新的MyTypedActionHandler并将原先的替换。我们可以把上面这段代码加入到Action的构造函数中,或者是在Action中创建static块。

注意:只能设置一个监听,如果自定义了按键监听,而不做其他处理的话,会使得原先IDEA中的按键监听无法处理,导致无法正常在输入框中输入。

为了能更充分理解TypedActionHandler,我们实现一个简单功能的插件:在输入字符的同时,在文档的开头也插入同样的字符。

首先定义TypedActionHandler:

  1. package com.huachao.plugin;
  2.  
  3. import com.intellij.openapi.actionSystem.DataContext;
  4. import com.intellij.openapi.editor.CaretModel;
  5. import com.intellij.openapi.editor.Document;
  6. import com.intellij.openapi.editor.Editor;
  7. import com.intellij.openapi.editor.LogicalPosition;
  8. import com.intellij.openapi.editor.actionSystem.TypedActionHandler;
  9. import org.jetbrains.annotations.NotNull;
  10.  
  11. /**
  12. * Created by huachao on 2016/12/26.
  13. */
  14. public class MyTypedActionHandler implements TypedActionHandler {
  15. private TypedActionHandler oldHandler;
  16. private boolean isBegin = true;
  17. private int caretLine = 0;
  18.  
  19. @Override
  20. public void execute(@NotNull Editor editor, char c, @NotNull DataContext dataContext) {
  21. if (oldHandler != null)
  22. oldHandler.execute(editor, c, dataContext);
  23.  
  24. Document document = editor.getDocument();
  25. CaretModel caretModel = editor.getCaretModel();
  26. int caretOffset = caretModel.getOffset();
  27. int line = document.getLineNumber(caretOffset);
  28. if (isBegin) {
  29. document.insertString(document.getLineStartOffset(line), String.valueOf(c) + "\n");
  30. caretLine = line + 1;
  31. isBegin = false;
  32. } else {
  33. if (line != caretLine) {
  34. isBegin = true;
  35. execute(editor, c, dataContext);
  36. } else {
  37. document.insertString(document.getLineEndOffset(line - 1), String.valueOf(c));
  38. }
  39. }
  40. System.out.println(caretLine + "," + line);
  41.  
  42. }
  43.  
  44. public void setOldHandler(TypedActionHandler oldHandler) {
  45. this.oldHandler = oldHandler;
  46. }
  47. }
  48.  
  49. 将我们定义的TypedActionHandler设置进去,只需实现一个简单Action
  50. ```java
  51. package com.huachao.plugin;
  52.  
  53. import com.intellij.openapi.actionSystem.AnAction;
  54. import com.intellij.openapi.actionSystem.AnActionEvent;
  55. import com.intellij.openapi.editor.actionSystem.EditorActionManager;
  56. import com.intellij.openapi.editor.actionSystem.TypedAction;
  57. import com.intellij.openapi.editor.actionSystem.TypedActionHandler;
  58.  
  59. /**
  60. * Created by huachao on 2016/12/26.
  61. */
  62. public class InsertCharAction extends AnAction {
  63. public InsertCharAction() {
  64. final EditorActionManager actionManager = EditorActionManager.getInstance();
  65. final TypedAction typedAction = actionManager.getTypedAction();
  66. MyTypedActionHandler handler = new MyTypedActionHandler();
  67. //将自定义的TypedActionHandler设置进去后,
  68. //返回旧的TypedActionHandler,即IDEA自身的TypedActionHandler
  69. TypedActionHandler oldHandler = typedAction.setupHandler(handler);
  70. handler.setOldHandler(oldHandler);
  71. }
  72.  
  73. @Override
  74. public void actionPerformed(AnActionEvent e) {
  75.  
  76. }
  77. }

  

运行结果如下: 

参考资料

Document类源码:点击这里 
官方文档:http://www.jetbrains.org/intellij/sdk/docs/tutorials/editor_basics.html

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huachao1001/article/details/53885981
文章标签: AndroidStudio插件
个人分类: Android
所属专栏: AndroidStudio插件开发

[Android Pro] AndroidStudio IDE界面插件开发(进阶篇之Editor)的更多相关文章

  1. [Android Pro] AndroidStudio IDE界面插件开发(进阶篇之Action机制)

    转载请注明出处:[huachao1001的专栏:http://blog.csdn.net/huachao1001/article/details/53883500] 从上一篇<AndroidSt ...

  2. [Android Pro] AndroidStudio IDE界面插件开发(Hello World篇)

    转载请注明出处:[huachao1001的专栏:http://blog.csdn.net/huachao1001/article/details/53856916] 工欲善其事必先利其器,自打从Ecl ...

  3. [Android Pro] AndroidStudio导出jar包

    reference :  http://blog.csdn.net/beijingshi1/article/details/38681281 不像在Eclipse,可以直接导出jar包.Android ...

  4. Android攻城狮学习笔记-进阶篇一

    点击快速抵达: 第1章 AndroidManifest配置文件 第2章 使用ListView显示信息列表 第3章 使用DatePicker及TimePicker显示当前日期和时间 第4章 使用Grid ...

  5. Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)

    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码) 来源 https://blog.csdn.net/jiangwei0910410003/article/details/51 ...

  6. Android动态方式破解apk进阶篇(IDA调试so源码)

    一.前言 今天我们继续来看破解apk的相关知识,在前一篇:Eclipse动态调试smali源码破解apk 我们今天主要来看如何使用IDA来调试Android中的native源码,因为现在一些app,为 ...

  7. Android UI开发第三十九篇——Tab界面实现汇总及比较

    Tab布局是iOS的经典布局,Android应用中也有大量应用,前面也写过Android中TAb的实现,<Android UI开发第十八篇——ActivityGroup实现tab功能>.这 ...

  8. Java IDE 编辑器 --- IntelliJ IDEA 进阶篇 生成 hibernate 实体与映射文件

    原文:转:Java IDE 编辑器 --- IntelliJ IDEA 进阶篇 生成 hibernate 实体与映射文件 2011-04-30 12:50 很多人不知道怎么用 IntelliJ IDE ...

  9. ESP8266开发之旅 进阶篇② 闲聊Arduino IDE For ESP8266烧录配置

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

随机推荐

  1. CCF计算机职业资格认证考试题解

    CCF计算机职业资格认证考试题解系列文章为meelo原创,请务必以链接形式注明本文地址 CCF计算机职业资格认证考试题解 CCF计算机软件能力认证(简称CCF CSP认证)是CCF计算机职业资格认证系 ...

  2. 【LOJ】#2186. 「SDOI2015」道路修建

    题解 就是线段树维护一下转移矩阵 分成两种情况,一种是前面有两个联通块,一种是前面有一个联通块 从一个联通块转移到一个联通块 也就是新加一列的三个边选其中两条即可 从一个联通块转移到两个联通块 不连竖 ...

  3. JSR教程2——Spring MVC数据校验与国际化

    SpringMVC数据校验采用JSR-303校验. • Spring4.0拥有自己独立的数据校验框架,同时支持JSR303标准的校验框架. • Spring在进行数据绑定时,可同时调用校验框架完成数据 ...

  4. 006.FTP用户访问控制配置

    一 FTP控制文件 1.1 文件说明 /etc/vsftpd/ftpusers:黑名单,优先级高 #通常不修改此文件 /etc/vsftpd/user_list:黑名单,优先级相对低 注意:Linux ...

  5. 转载-解决ORACLE 在控制台进行exp,导出时,空表不能导出

    一.问题原因: 11G中有个新特性,当表无数据时,不分配segment,以节省空间 1.insert一行,再rollback就产生segment了. 该方法是在在空表中插入数据,再删除,则产生segm ...

  6. JSP(Servlet)中从连接池获取连接

    1) 建立连接. 2) 执行SQL. 3) 处理结果. 4) 释放资源. Connection pool:连接池 DataSource: LDAP ( Light directory access p ...

  7. BZOJ.3624.[APIO2008]免费道路(Kruskal)

    题目链接 我们发现有些白边是必须加的,有些是多余的. 那么我们先把所有黑边加进去,然后把必须要加的白边找出来. 然后Kruskal,把必须要加的白边先加进去,小于K的话再加能加的白边.然后加黑边. 要 ...

  8. BZOJ2976 : [Poi2002]出圈游戏

    首先模拟一遍得到n个同余方程,然后用扩展欧几里得求出最小的可行解即可,时间复杂度$O(n^2)$. #include<cstdio> #define N 30 int n,i,j,k,x, ...

  9. 20172330 2017-2018-1 《Java程序设计》第六周学习总结

    学号 2017-2018-2 <程序设计与数据结构>第六周学习总结 教材学习内容总结 这一章主要是对数组的学习: 数组是一种简单而功能强大的编程语言结构,用于分组和组织数据.在java中, ...

  10. Mac下配置Apache服务器

    有的时候,我们需要在内网工作组中分享一些文件或是后台接口没有及时给出,你又想要模拟真实数据,直接在项目里创建plist也可以做到这种需求,但难免让工程变得冗余且看起来比较Low.这个时候就看出配置本地 ...