轉(zhuǎn)載請注明出處:【huachao1001的簡書:http://www.reibang.com/users/0a7e42698e4b/latest_articles】
我們開發(fā)AndroidStudio插件瓣喊,絕大多數(shù)插件功能是用在編輯文本上面,讓用戶開發(fā)更便捷蠢笋。這篇文章主要是介紹Editor部分冻押,看完之后可以開發(fā)簡單實(shí)用的插件啦驰贷!在看本文之前,請先確定已經(jīng)看完《AndroidStudio插件開發(fā)(Hello World篇)》和《 AndroidStudio插件開發(fā)(進(jìn)階篇之Action機(jī)制)》洛巢。因?yàn)檫@兩篇是基礎(chǔ)括袒,沒有這些基礎(chǔ)就無法繼續(xù)往下讀。
在本文的最后使用簡單的代碼實(shí)現(xiàn)簡單的插件:自動生成Getter和Setter函數(shù)的插件稿茉。如下圖所示锹锰,下圖中,分別演示了通過點(diǎn)擊和使用快捷鍵的方式觸發(fā)Action漓库。
1. 文本編輯
1.1 CaretModel和SelectionModel
為了能夠更靈活地控制Editor恃慧,IDEA插件開發(fā)中將Editor細(xì)分為多個(gè)模型。在本文中只簡單介紹CaretModel和SelectionModel渺蒿,除了CaretModel和SelectionModel以外痢士,還有如下幾種模型:
- FoldingModel
- IndentsModel
- ScrollingModel
- ScrollingModel
- SoftWrapModel
獲取Editor的CaretModel和SelectionModel對象方法如下:
@Override
public void actionPerformed(AnActionEvent e) {
Editor editor = e.getData(PlatformDataKeys.EDITOR);
if (editor == null)
return;
SelectionModel selectionModel = editor.getSelectionModel();
CaretModel caretModel=editor.getCaretModel();
}
其他模型對象獲取方式類似,通過Editor對象的相應(yīng)函數(shù)即可得到茂装。
1.1.1 CaretModel對象
CaretModel對象用于描述插入光標(biāo)怠蹂,通過CaretModel對象,可以實(shí)現(xiàn)如下功能:
- moveToOffset(int offset):將光標(biāo)移動到指定位置(offset)
- getOffset():獲取當(dāng)前光標(biāo)位置偏移量
- getCaretCount:獲取光標(biāo)數(shù)量(可能有多個(gè)位置有光標(biāo))
- void addCaretListener(CaretListener listener) 少态,void removeCaretListener(CaretListener listener):添加或移除光標(biāo)監(jiān)聽器(CareListener)
- Caret addCaret(VisualPosition visualPosition):加入新的光標(biāo)
- ......
1.1.2 SelectionModel對象
SelectionModel對象用于描述光標(biāo)選中的文本段城侧,通過SelectionModel對象可以實(shí)現(xiàn)如下功能:
- String getSelectedText() :獲取選中部分字符串。
- int getSelectionEnd():獲取選中文本段末尾偏移量
- int getSelectionStart():獲取選中文本段起始位置偏移量
- void setSelection(int start, int end):設(shè)置選中彼妻,將staert到end部分設(shè)置為選中
- void removeSelection():將選中文本段刪除
- void addSelectionListener(SelectionListener listener):添加監(jiān)聽器嫌佑,用于監(jiān)聽光標(biāo)選中變化。
- void selectLineAtCaret():將光標(biāo)所在的行設(shè)置為選中澳骤。
- void selectWordAtCaret(boolean honorCamelWordsSettings):將光標(biāo)所在的單詞設(shè)置為選中歧强。honorCamelWordsSettings表示是否駝峰命名分隔澜薄,如果為true为肮,則大寫字母為單詞的邊界
- ......
1.2 Document對象
與Editor中的其他對象一樣,通過Editor對象的一個(gè)getter函數(shù)即可得到Document對象:
Document document = editor.getDocument();
Document對象用于描述文檔文件肤京,通過Document對象可以很方便的對Editor中的文件進(jìn)行操作颊艳∶┨兀可以做如下這些事情:
String getText()
、String getText( TextRange range)
:獲取Document對象對應(yīng)的文件字符串棋枕。int getTextLength()
:獲取文件長度白修。int getLineCount()
:獲取文件的行數(shù)int getLineNumber(int offset)
:獲取指定偏移量位置對應(yīng)的行號offset取值為[0,getTextLength()-1]
。int getLineStartOffset(int line)
:獲取指定行的第一個(gè)字符在全文中的偏移量重斑,行號的取值范圍為:[0,getLineCount()-1]
int getLineEndOffset(int line)
:獲取指定行的最后一個(gè)字符在全文中的偏移量兵睛,行號的取值范圍為:[0,getLineCount()-1]
void insertString(int offset, CharSequence s)
:在指定偏移位置插入字符串void deleteString(int startOffset, int endOffset)
:刪除[startOffset,endOffset]位置的字符串,如果文件為只讀窥浪,則會拋異常祖很。void replaceString(int startOffset, int endOffset, CharSequence s)
:替換[startOffset,endOffset]位置的字符串為svoid addDocumentListener( DocumentListener listener)
:添加Document監(jiān)聽器,在Document內(nèi)容發(fā)生變化之前和變化之后都會回調(diào)相應(yīng)函數(shù)漾脂。- ......
1.3 實(shí)現(xiàn)自動生成Getter和Setter函數(shù)的插件
有了上面的認(rèn)識后假颇,我們可以開始寫個(gè)簡單的Getter和Setter函數(shù)插件了。首先創(chuàng)建一個(gè)Action骨稿,名為GetterAndSetter笨鸡,并在plugin.xml中注冊。plugin.xml的<acitons>
標(biāo)簽部分如下:
<actions>
<action id="StudyEditor.GetterAndSetter" class="com.huachao.plugin.GetterAndSetter" text="Getter And Setter"
description="生成Getter和Setter方法">
<add-to-group group-id="EditorPopupMenu" anchor="first"/>
<keyboard-shortcut keymap="$default" first-keystroke="ctrl alt G"/>
</action>
</actions>
通過前面兩篇文章的學(xué)習(xí)坦冠,我們知道形耗,定義Action時(shí)需要重寫actionPerformed和update函數(shù)。
@Override
public void actionPerformed(AnActionEvent e) {
//獲取Editor和Project對象
Editor editor = e.getData(PlatformDataKeys.EDITOR);
Project project = e.getData(PlatformDataKeys.PROJECT);
if (editor == null||project==null)
return;
//獲取SelectionModel和Document對象
SelectionModel selectionModel = editor.getSelectionModel();
Document document = editor.getDocument();
//拿到選中部分字符串
String selectedText = selectionModel.getSelectedText();
//得到選中字符串的起始和結(jié)束位置
int startOffset = selectionModel.getSelectionStart();
int endOffset = selectionModel.getSelectionEnd();
//得到最大插入字符串(即生成的Getter和Setter函數(shù)字符串)位置
int maxOffset = document.getTextLength() - 1;
//計(jì)算選中字符串所在的行號蓝牲,并通過行號得到下一行的第一個(gè)字符的起始偏移量
int curLineNumber = document.getLineNumber(endOffset);
int nextLineStartOffset = document.getLineStartOffset(curLineNumber + 1);
//計(jì)算字符串的插入位置
int insertOffset = maxOffset > nextLineStartOffset ? nextLineStartOffset : maxOffset;
//得到選中字符串在Java類中對應(yīng)的字段的類型
String type = getSelectedType(document, startOffset);
//對文檔進(jìn)行操作部分代碼趟脂,需要放入Runnable接口中實(shí)現(xiàn),由IDEA在內(nèi)部將其通過一個(gè)新線程執(zhí)行
Runnable runnable = new Runnable() {
@Override
public void run() {
//genGetterAndSetter為生成getter和setter函數(shù)部分
document.insertString(insertOffset, genGetterAndSetter(selectedText, type));
}
};
//加入任務(wù)例衍,由IDEA調(diào)度執(zhí)行這個(gè)任務(wù)
WriteCommandAction.runWriteCommandAction(project, runnable);
}
@Override
public void update(AnActionEvent e) {
Editor editor = e.getData(PlatformDataKeys.EDITOR);
SelectionModel selectionModel = editor.getSelectionModel();
//如果沒有字符串被選中昔期,那么無需顯示該Action
e.getPresentation().setVisible(editor != null && selectionModel.hasSelection());
}
剩下的還有獲取選中字段的類型和生成Getter、Setter函數(shù)兩個(gè)部分佛玄,兩個(gè)函數(shù)如下:
private String getSelectedType(Document document, int startOffset) {
String text = document.getText().substring(0, startOffset).trim();
int startIndex = text.lastIndexOf(' ');
return text.substring(startIndex + 1);
}
private String genGetterAndSetter(String field, String type) {
if (field == null || (field = field.trim()).equals(""))
return "";
String upperField = field;
char first = field.charAt(0);
if (first <= 'z' && first >= 'a') {
upperField = String.valueOf(first).toUpperCase() + field.substring(1);
}
String getter = "\tpublic TYPE getUpperField(){ \n\t\treturn this.FIELD;\n\t}";
String setter = "\tpublic void setUpperField(TYPE FIELD){\n\t\tthis.FIELD=FIELD;\n\t}";
String myGetter = getter.replaceAll("TYPE", type).replaceAll("UpperField", upperField).replaceAll("FIELD", field);
String mySetter = setter.replaceAll("TYPE", type).replaceAll("UpperField", upperField).replaceAll("FIELD", field);
return "\n"+myGetter + "\n" + mySetter + "\n";
}
運(yùn)行后如下:
注意:在對Document進(jìn)行修改時(shí)硼一,需要實(shí)現(xiàn)Runnable接口并將修改部分代碼寫入run函數(shù)中,最后通過 WriteCommandAction的runWriteCommandAction函數(shù)執(zhí)行梦抢。
2. Editor的坐標(biāo)系統(tǒng):位置和偏移量
前面小節(jié)我們知道般贼,通過CaretModel對象我們可以獲取當(dāng)前光標(biāo)位置。但在Editor中位置分為兩種奥吩,一種是邏輯位置哼蛆,對應(yīng)LogicalPosition類;另一種是視覺位置霞赫,對應(yīng)VisualPosition類腮介。
LogicalPosition與VisualPosition的區(qū)別通過如下圖很顯然能區(qū)分開來。
上如中端衰,光標(biāo)的坐標(biāo)為:
LogicalPosition:(13,6)
VisualPosition:(9,6)
注意叠洗,行號和列號都是從0開始甘改。
另外,獲取LogicalPosition和VisualPosition方法如下:
@Override
public void actionPerformed(AnActionEvent e) {
//獲取Editor和Project對象
Editor editor = e.getData(PlatformDataKeys.EDITOR);
Project project = e.getData(PlatformDataKeys.PROJECT);
if (editor == null || project == null)
return;
CaretModel caretModel = editor.getCaretModel();
LogicalPosition logicalPosition = caretModel.getLogicalPosition();
VisualPosition visualPosition = caretModel.getVisualPosition();
System.out.println(logicalPosition + "," + visualPosition);
}
3. Editor中的按鍵事件
為了監(jiān)聽按鍵時(shí)間灭抑,專門提供了TypedActionHandler類十艾,我們只需繼承TypedActionHandler,并重寫execute函數(shù)即可腾节。注意忘嫉,只能監(jiān)聽可打印字符對應(yīng)的按鍵。
package com.huachao.plugin;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.actionSystem.TypedActionHandler;
import org.jetbrains.annotations.NotNull;
/**
* Created by huachao on 2016/12/26.
*/
public class MyTypedActionHandler implements TypedActionHandler {
@Override
public void execute(@NotNull Editor editor, char c, @NotNull DataContext dataContext) {
System.out.println(c);
}
}
TypedAction專門處理按鍵相關(guān)操作案腺,定義了TypedActionHandler后榄融,接下來就是將自定義的TypedActionHandler加入到TypedAction中。如何獲取TypedAction對象呢救湖?具體如下:
final EditorActionManager actionManager = EditorActionManager.getInstance();
final TypedAction typedAction = actionManager.getTypedAction();
typedAction.setupHandler(new MyTypedActionHandler());
上述代碼即可將自定義的按鍵處理器成功加入愧杯,現(xiàn)在有個(gè)問題是,上面這段代碼應(yīng)該放入到哪里呢鞋既?之前我們都是重寫AnAction的actionPerformed和update函數(shù)就行力九,能不能將上面這段代碼放入到actionPerformed中呢?顯然這是可以的邑闺,但是這樣的話就得先點(diǎn)擊當(dāng)前Action后才能使MyTypedActionHandler被加入跌前,并且每點(diǎn)擊一次,就會創(chuàng)建新的MyTypedActionHandler并將原先的替換陡舅。我們可以把上面這段代碼加入到Action的構(gòu)造函數(shù)中抵乓,或者是在Action中創(chuàng)建static塊。
注意:只能設(shè)置一個(gè)監(jiān)聽靶衍,如果自定義了按鍵監(jiān)聽灾炭,而不做其他處理的話,會使得原先IDEA中的按鍵監(jiān)聽無法處理颅眶,導(dǎo)致無法正常在輸入框中輸入蜈出。
為了能更充分理解TypedActionHandler,我們實(shí)現(xiàn)一個(gè)簡單功能的插件:在輸入字符的同時(shí)涛酗,在文檔的開頭也插入同樣的字符铡原。
首先定義TypedActionHandler:
package com.huachao.plugin;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.editor.actionSystem.TypedActionHandler;
import org.jetbrains.annotations.NotNull;
/**
* Created by huachao on 2016/12/26.
*/
public class MyTypedActionHandler implements TypedActionHandler {
private TypedActionHandler oldHandler;
private boolean isBegin = true;
private int caretLine = 0;
@Override
public void execute(@NotNull Editor editor, char c, @NotNull DataContext dataContext) {
if (oldHandler != null)
oldHandler.execute(editor, c, dataContext);
Document document = editor.getDocument();
CaretModel caretModel = editor.getCaretModel();
int caretOffset = caretModel.getOffset();
int line = document.getLineNumber(caretOffset);
if (isBegin) {
document.insertString(document.getLineStartOffset(line), String.valueOf(c) + "\n");
caretLine = line + 1;
isBegin = false;
} else {
if (line != caretLine) {
isBegin = true;
execute(editor, c, dataContext);
} else {
document.insertString(document.getLineEndOffset(line - 1), String.valueOf(c));
}
}
System.out.println(caretLine + "," + line);
}
public void setOldHandler(TypedActionHandler oldHandler) {
this.oldHandler = oldHandler;
}
}
將我們定義的TypedActionHandler設(shè)置進(jìn)去,只需實(shí)現(xiàn)一個(gè)簡單Action商叹。
```java
package com.huachao.plugin;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.editor.actionSystem.EditorActionManager;
import com.intellij.openapi.editor.actionSystem.TypedAction;
import com.intellij.openapi.editor.actionSystem.TypedActionHandler;
/**
* Created by huachao on 2016/12/26.
*/
public class InsertCharAction extends AnAction {
public InsertCharAction() {
final EditorActionManager actionManager = EditorActionManager.getInstance();
final TypedAction typedAction = actionManager.getTypedAction();
MyTypedActionHandler handler = new MyTypedActionHandler();
//將自定義的TypedActionHandler設(shè)置進(jìn)去后燕刻,
//返回舊的TypedActionHandler,即IDEA自身的TypedActionHandler
TypedActionHandler oldHandler = typedAction.setupHandler(handler);
handler.setOldHandler(oldHandler);
}
@Override
public void actionPerformed(AnActionEvent e) {
}
}
運(yùn)行結(jié)果如下:
參考資料
Document類源碼:點(diǎn)擊這里
官方文檔:http://www.jetbrains.org/intellij/sdk/docs/tutorials/editor_basics.html