AndroidStudio插件開發(fā)(進(jìn)階篇之Editor)

轉(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漓库。

自動生成Getter和Setter函數(shù)

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)如下功能:

  1. moveToOffset(int offset):將光標(biāo)移動到指定位置(offset)
  2. getOffset():獲取當(dāng)前光標(biāo)位置偏移量
  3. getCaretCount:獲取光標(biāo)數(shù)量(可能有多個(gè)位置有光標(biāo))
  4. void addCaretListener(CaretListener listener) 少态,void removeCaretListener(CaretListener listener):添加或移除光標(biāo)監(jiān)聽器(CareListener)
  5. Caret addCaret(VisualPosition visualPosition):加入新的光標(biāo)
  6. ......

1.1.2 SelectionModel對象

SelectionModel對象用于描述光標(biāo)選中的文本段城侧,通過SelectionModel對象可以實(shí)現(xiàn)如下功能:

  1. String getSelectedText() :獲取選中部分字符串。
  2. int getSelectionEnd():獲取選中文本段末尾偏移量
  3. int getSelectionStart():獲取選中文本段起始位置偏移量
  4. void setSelection(int start, int end):設(shè)置選中彼妻,將staert到end部分設(shè)置為選中
  5. void removeSelection():將選中文本段刪除
  6. void addSelectionListener(SelectionListener listener):添加監(jiān)聽器嫌佑,用于監(jiān)聽光標(biāo)選中變化。
  7. void selectLineAtCaret():將光標(biāo)所在的行設(shè)置為選中澳骤。
  8. void selectWordAtCaret(boolean honorCamelWordsSettings):將光標(biāo)所在的單詞設(shè)置為選中歧强。honorCamelWordsSettings表示是否駝峰命名分隔澜薄,如果為true为肮,則大寫字母為單詞的邊界
  9. ......

1.2 Document對象

與Editor中的其他對象一樣,通過Editor對象的一個(gè)getter函數(shù)即可得到Document對象:

Document document = editor.getDocument();

Document對象用于描述文檔文件肤京,通過Document對象可以很方便的對Editor中的文件進(jìn)行操作颊艳∶┨兀可以做如下這些事情:

  1. String getText()String getText( TextRange range):獲取Document對象對應(yīng)的文件字符串棋枕。
  2. int getTextLength():獲取文件長度白修。
  3. int getLineCount():獲取文件的行數(shù)
  4. int getLineNumber(int offset):獲取指定偏移量位置對應(yīng)的行號offset取值為[0,getTextLength()-1]
  5. int getLineStartOffset(int line):獲取指定行的第一個(gè)字符在全文中的偏移量重斑,行號的取值范圍為:[0,getLineCount()-1]
  6. int getLineEndOffset(int line):獲取指定行的最后一個(gè)字符在全文中的偏移量兵睛,行號的取值范圍為:[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監(jiān)聽器,在Document內(nèi)容發(fā)生變化之前和變化之后都會回調(diào)相應(yīng)函數(shù)漾脂。
  11. ......

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)行后如下:

自動生成Getter和Setter函數(shù)

注意:在對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ū)分開來。

LogicalPosition與VisualPosition

上如中端衰,光標(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é)果如下:


重復(fù)輸入

參考資料

Document類源碼:點(diǎn)擊這里
官方文檔:http://www.jetbrains.org/intellij/sdk/docs/tutorials/editor_basics.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末剖笙,一起剝皮案震驚了整個(gè)濱河市卵洗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌枯途,老刑警劉巖忌怎,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異酪夷,居然都是意外死亡榴啸,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門晚岭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鸥印,“玉大人,你說我怎么就攤上這事坦报】馑担” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵片择,是天一觀的道長潜的。 經(jīng)常有香客問我,道長字管,這世上最難降的妖魔是什么啰挪? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮嘲叔,結(jié)果婚禮上亡呵,老公的妹妹穿的比我還像新娘。我一直安慰自己硫戈,他們只是感情好锰什,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著丁逝,像睡著了一般汁胆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上霜幼,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天沦泌,我揣著相機(jī)與錄音,去河邊找鬼辛掠。 笑死谢谦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的萝衩。 我是一名探鬼主播回挽,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼猩谊!你這毒婦竟也來了千劈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤牌捷,失蹤者是張志新(化名)和其女友劉穎墙牌,沒想到半個(gè)月后涡驮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡喜滨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年捉捅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片虽风。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡棒口,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辜膝,到底是詐尸還是另有隱情无牵,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布厂抖,位于F島的核電站茎毁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏忱辅。R本人自食惡果不足惜充岛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望耕蝉。 院中可真熱鬧崔梗,春花似錦、人聲如沸垒在。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽场躯。三九已至谈为,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間踢关,已是汗流浹背伞鲫。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留签舞,地道東北人秕脓。 一個(gè)月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像儒搭,于是被迫代替她去往敵國和親吠架。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容