プラグイン作って覚えたことを淡々と記録するよ(カーソル位置の語を取得するぞ編)

#ようやく遊びの時間が終焉を迎えるので,急いでまとめとかないと。


まずはこれ。「カーソル位置の語を取得する」。
楽勝かと思いきや,意外(?)とそうでもない。といっても,BrowseWordAtCaretちゅうプラグインがすでにあるので,こいつのコード丸パクリで済んだから,結果的には楽勝と言えよう。
ぐだぐだ書くよりコード見た方が早い。

// EditorActionはAnActionの子ども。
// Editor内だけで反応するAnAction作るのに便利。
public class PopupAction extends EditorAction {
    public PopupAction() {
        super(new BrowseWordAtCaretHandler());
    }

    // EditorActionHandlerがEditorActionのキモで...
    private static class BrowseWordAtCaretHandler extends EditorActionHandler {

        // Actionが発火するとこいつが呼ばれる
        public void execute(Editor editor, DataContext dataContext) {
            String targetString = null;

            SelectionModel selection = editor.getSelectionModel();
            if (selection.hasSelection()) {
                // 何か選択中だったら,そのテキストを抜く
                targetString = selection.getSelectedText();
            }
            else {
                // 未選択ならカーソル位置の語を抜く
                Document document = editor.getDocument();
                if (document.getTextLength() > 0) {
                    String text = document.getText();

                    // どうもdocument.getText()だと,テキスト全体が抜き出されるんで,
                    // こんな具合に,カーソル位置にある語を抜き出さないとダメっぽい。
                    int currentOffset = editor.getCaretModel().getOffset();
                    int wordStartOffset = getWordStartOffset(text, currentOffset);
                    int wordEndOffset = getWordEndOffset(text, currentOffset);
                    targetString = text.substring(wordStartOffset, wordEndOffset);

                    // ついでだから,抜き出した語を選択状態にしちゃる
                    selection.setSelection(wordStartOffset, wordEndOffset);
                }
            }
            if (targetString != null && targetString.trim().length() > 0) {
                // 選択中のテキストまたは,カーソル位置の語を抜けたので,
                // なんかする(...ポップアップを表示するんだけど)
            }
        }

        // ここは,BrowseWordAtCaretをまんまパクっただけ。
        // 何やってるかは,推して知るべし。
        private int getWordStartOffset(String text, int currentOffset) {
            StringCharacterIterator sci = new StringCharacterIterator(text, currentOffset);
            for (; isWordchar(sci.previous()); )
                ;
            return sci.getIndex() + 1;
        }
        private int getWordEndOffset(String text, int currentOffset) {
            StringCharacterIterator sci = new StringCharacterIterator(text, currentOffset <= 0 ? 0 : currentOffset - 1);
            for (; isWordchar(sci.next()); )
                ;
            return sci.getIndex();
        }

        // ここを旨い具合に実装すれば,2バイト文字も抜き出せるんだろうな...
        private boolean isWordchar(char currentChar) {
            return 'A' <= currentChar && currentChar <= 'Z'
                   || 'a' <= currentChar && currentChar <= 'z'
                   || currentChar == '_'
                   || '0' <= currentChar && currentChar <= '9'
                   || '\300' <= currentChar && currentChar <= '\377'
                      && currentChar != '\327' && currentChar != '\367';
        }
    }
}

OpenAPIで全部出来なかったとは言え,こんだけで組めた事を考えれば簡単なほうか。


おまけ:こんな感じに選択状態になったらIntentionを表示するって方法もあるよ。


これは簡単で,さっきのPopupActionにIntentionActionを実装して...

public class PopupAction extends EditorAction implements IntentionAction {
    public PopupAction() {
        super(new BrowseWordAtCaretHandler());
    }

    //----> ここから
    @NotNull public String getText() {
        // Intentionの項目名称
        return "ここを翻訳";
    }
    @NotNull public String getFamilyName() {
        return "popup";
    }
    public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
        // このIntentionの発動条件(選択してれば発動する)
        return editor.getSelectionModel().hasSelection();
    }
    public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
        // このIntentionの処理を書く
        // ...選択中のテキストを抜いてポップアップを表示するのだ。
    }
    public boolean startInWriteAction() {
        return false;
    }
    //<---- ここまでが IntentionAction で要るメソッド
    :


なんかテキトウなProjectComponentを作り,IntentionManagerに登録したら,おしまい。
赤子の手をひねるくらい簡単だの。

public class PopupProjectComponent implements ProjectComponent {
    public void projectOpened() {
        IntentionManager.getInstance().addAction(new PopupAction());
    }
    :


ちなみに,参考にしたのはSyncEditプラグインだ。