selected.el で「選択して右クリック」的な概念を

# はじめに

この記事は，Emacs 使いに「右クリックしてメニューから選択」的な概念をもたらす `selected.el` と `helm-selected.el`/`counsel-selected.el` の紹介です．Macユーザを前提としていますが，他のプラットフォームでも通用する話です．

簡単な(?)設定をするだけで，作業効率が劇的に向上します．

![Screen Shot 2017-12-11 at 1.18.11 PM.png](https://qiita-image-store.s3.amazonaws.com/0/12232/623866e5-5e10-1d5c-282a-ca72f16a1fb4.png)

Note - ガチンコの右クリック実装には[@tad3](https://twitter.com/tadsan)氏の「[right-click-context](https://github.com/zonuexe/right-click-context)」があります！オススメ！

# 選択して，実行

マウス操作の「右クリック」はとても便利で，メニューから選択領域に限定したアクションを選択するだけで，所定の処理を簡単かつ高速に行えます．Emacsにおいても，選択領域を引数とするコマンドを容易に発行できますが，各コマンドが独立していてグループ化されていません．「ユーザが領域選択した時だけ発行できるコマンド群」をグループ化し，かつ，簡単な操作でそれらを選ぶことができれば，選択領域に対する追加処理を最小の操作で実行できます．

わかりやすい例では「Emacsバッファ内の言葉をブラウザでググりたい」があります．通常のシーケンスですと，

1. 領域指定: `C-<space>` + `C-M-<right>` or `C-<space>` + `<right>`連打 or `C-M-<space> ` 等
2. 領域複製: `M-w`
3. ブラウザを開く（ランチャーから選ぶ，/Applications フォルダから選ぶ，`M-<tab>`でアクティブ化，等）
4. フォームに単語・文章をペースト: `M-v`
5. 検索実行: `<ENT>`

と5ステップです．こんな複雑な処理を日々，何度も繰り返すのは嫌ですね．

一方，`selected.el`（と`helm-selected.el`）を使えば，もっと楽です．

1. 領域指定: `C-<space>` + `C-M-<right>` or `C-<space>` + `<right>|C-f`連打 or `C-M-<space>` 等
2. 検索実行: `g`（設定済みのシングルキー）

わずか2ステップです！

前出の処理の2-4が所定のコマンドで自動化されているので，ちょうど「右クリックして実行」の感覚です．「この英単語を辞書で調べたい！」というような，選択領域に対する同様の処理事例がたくさんあるはずです．

# selected.el （とhelm, ivy/counsel拡張）の導入

## インストール

`selected.el` は，MELPAからインストールできます．
Githubから取り寄せる場合は，https://github.com/Kungsgeten/selected.el から．

次に，`helm-selected.el`です．これは `selected.el` で設定したコマンド群を helm インターフェイスで絞り込む拡張で， Githubで公開しています（拙作）．
https://github.com/takaxp/helm-selected

~~MELPA登録は[申請中](https://github.com/melpa/melpa/pull/5185)で，しばらく先になりそうです．~~また，記事に合わせて急いで作成したので，まだバグが残っているかもしれません．

(Updated: 2017-12-23) [マージ](https://github.com/melpa/melpa/pull/5185)されました． Many thanks to purcell!!

(Updated: 2019-05-31) [counsel-selected.el](https://github.com/takaxp/counsel-selected) を公開しました．

## 設定

### selected.el

まず`selected.el`の設定です．以下は一例で，選択領域の処理に紐付けたいコマンドを`selected-keymap`に追加していくだけです．また，特定のメジャーモードに限定したコマンドの設定も可能です．

```el
(when (require 'selected nil t)

    ;; コメントアウト・アンコメントアウト
    (define-key selected-keymap (kbd ";") #'comment-dwim)

    ;; 選択領域の単語数をカウント，ミニバッファに表示   
    (define-key selected-keymap (kbd "=") #'count-words-region)

    ;; 選択した関数のヘルプを表示
    (define-key selected-keymap (kbd "f") #'describe-function)

    ;; 選択した変数のヘルプを表示
    (define-key selected-keymap (kbd "v") #'describe-variable)

    ;; 選択した単語・文をググる
    (define-key selected-keymap (kbd "g") #'my:google-this)

    ;; 選択した単語をOSに発声させる
    ;; 要osx-lib.el (https://github.com/raghavgautam/osx-lib/blob/master/osx-lib.el)
    (define-key selected-keymap (kbd "s") #'osx-lib-say-region)

    ;; 選択領域を評価して結果をミニバッファに表示（結構危険かも...）
    (define-key selected-keymap (kbd "e") #'my:eval-region)

    ;; 英単語を調べて結果を表示
    ;; 要osx-dictionary.el (https://github.com/xuchunyang/osx-dictionary.el)
    (define-key selected-keymap (kbd "w") #'osx-dictionary-search-pointer)

    ;; 選択した単語を一括変換する
    ;; 要replace-from-region.el (replace+.elでも良いらしい）
    (define-key selected-keymap (kbd "5") #'query-replace-from-region)

    ;; 実行中止（ただし，selecte.elでは selected-offの紐付けが推奨されている）
    (define-key selected-keymap (kbd "q") #'keyboard-quit)

    ;; メジャーモードがOrg Modeの時，選択領域をテーブルに変換する
    (setq selected-org-mode-map (make-sparse-keymap))
    (define-key selected-org-mode-map (kbd "t") #'org-table-convert-region)

    (selected-global-mode 1))
```

上記設定に出てくるプライベート関数は次のようなものです．

```el
(defun my:eval-region ()
  (interactive)
  (when mark-active
    (eval-region (region-beginning) (region-end) t)))

;; 要google-this.el (https://github.com/Malabarba/emacs-google-this)
(with-eval-after-load "google-this"
    (defun my:google-this ()
      "検索確認をスキップして直接検索実行"
      (interactive)
      (google-this (current-word) t)))
```

### helm-selected.el

ほぼ設定不要で，`helm-selected`の発動コマンドを設定するだけです．ただし，次の章で説明しますが，IMEの切り替えをコマンドでできない環境では，ASCIIのシングルキーがIMEに取られてしまうので，その場合は `<f5>`や`S-<f5>`などを指定しておくべきです．

```el
(with-eval-after-load "selected"
  (when (require 'helm-selected nil t)
    (define-key selected-keymap (kbd "h") 'helm-selected)))
```

領域選択後に`h`押下すると，helmインターフェイスが出てきますので，希望のコマンドを選んで`<ENT>`すればOKです．

`selected.el`にたくさんのコマンドを紐付けると，どのキーに何を割り振ったのか忘れてしまいがちです．そんな時に`helm-selected`が役に立ちます．

![Screen Shot 2017-12-11 at 3.47.56 PM.png](https://qiita-image-store.s3.amazonaws.com/0/12232/a1b3f360-51d4-387f-cf54-4617e8835530.png)


### counsel-selected.el

Ivy/counsel用のインターフェイスを準備しました．helm-selected.el と同じように使えます．

![Screen Shot 2019-05-31 at 16.08.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12232/b6c07691-c360-ff5a-227d-4646164a95b1.png)

# IMEユーザの一手間

`selected.el` の最大の欠点は，IMEとの相性が悪いことです．つまり，IMEがオンの時，選択領域に対するコマンドを選択すると，押下キーがバッファにそのまま入力されてしまいます．

これを避ける方法は二つあります（あるいは，毎回IMEを手動で切り替えるか...Orz）．

一つは，`helm-selected`の発動をASCII以外のキー（`S-<f5>`など）に割り当てて，各コマンドは`helm`インターフェイスから選択することです．コマンド実行までのステップが一つ増えますが，右クリック相当の処理1回分と考えれば許容範囲です．

もう一つの方法は，IMEパッチを適用して IMEのオン・オフを制御可能にする方法です．そうすることで，領域を選択し始める時にIMEをオフにして，コマンド発行後にIMEを元に戻すことができます．同制御は，マークの`hook`を使って自動化できます．正しく動作すれば，任意のコマンドにシングルキーを割り当てることができ，IMEユーザも `selected.el`の本来の性能を引き出せます．

```el
(when (and (fboundp 'mac-get-current-input-source)
           (fboundp 'mac-toggle-input-method))

  ;; IME ON/OFF ステータスフラグ
  (defvar my:ime-flag nil)
  ;; 判定
  (defun my:ime-active-p ()
    (not (string-match "\\.Roman$" (mac-get-current-input-source))))
  ;; IME ON
  (defun my:ime-on ()
    (interactive)
    (mac-toggle-input-method t))
  ;; IME OFF
  (defun my:ime-off ()
    (interactive)
    (mac-toggle-input-method nil))

  ;; 領域選択開始に合わせて IMEを OFF
  (add-hook 'activate-mark-hook
            #'(lambda ()
                (when (setq my:ime-flag (my:ime-active-p))
                  (my:ime-off))))
  ;; 選択解除で IME ONを復帰
  (add-hook 'deactivate-mark-hook
            #'(lambda ()
                (when my:ime-flag
                  (my:ime-on)))))
```

IMEパッチの適用については，[emacs-25.3 をインラインパッチをあてて使う（OSX）](https://qiita.com/takaxp/items/e07bb286d80fa9dd8e05)をご参照ください．

# おわりに

本記事では，`selected.el`と`helm-selected.el`を紹介しました．選択した領域に対する処理は，今回紹介した事例以外にもたくさん存在すると思います．「右クリックして実行」に相当する任意の関数を好みに合わせて設定してみましょう．作業効率が向上すること間違い無しです．

紐付けるべきオススメのコマンドがありましたら，コメント欄へお願いします m(\_''\_)m

また，hydra使いさんからのツッコミもお待ちしております．

---

<< [10日目](https://qiita.com/howking/items/bcc4e05bfb16777747fa) ・ [Emacs Advent Calendar 2017](https://qiita.com/advent-calendar/2017/emacs) ・ [11日目](http://kazkn.com/post/2017/emacs-tmux-sh/) >>