RStudioにショートカットを追加する(shrtctsパッケージ)

前置き

RStudioはキーボードショートカットのカスタマイズができる。

だが、好きなキーボードショートカットを追加しようとするとRStudioの設定だけでは完結しない。具体的にはアドインを作る必要がある。アドインにはショートカットが設定できるので、目的の動きをするアドインを作ってキーボードショートカットを割り当てれば良い。

しかしアドインを作るのはそこそこ面倒だ。そこで今回紹介するshrtctsパッケージだ。このパッケージを使えばアドインを自作することなく*1、ショートカットを追加できる。

pkg.garrickadenbuie.com

インストール

CRANにはないので、remotesパッケージを使うなどしてgithubからインストールする。

# install.packages("remotes")
remotes::install_github("gadenbuie/shrtcts")

初期設定

ショートカットの登録はshrtcts::add_rstudio_shortcuts()関数により行う。この関数を後述する設定ファイルの記述後に実行するか、R起動時に自動的に実行されるように.Rprofileに次の記述を追加しておく。

if (interactive() && requireNamespace("shrtcts", quietly = TRUE)) {
  shrtcts::add_rstudio_shortcuts()
}

後述するが、ショートカットはまずアドインとして登録され、それに対するキーボードショートカットは設定から編集するという手順をとる。そうではなく、設定時にキーボードショートカットを記述しておき、それに基づいてキーボードショートカットの登録までやってしまうということもできる。その場合は次のようにset_keyboard_shortcuts = TRUEを指定しておく。

if (interactive() && requireNamespace("shrtcts", quietly = TRUE)) {
  shrtcts::add_rstudio_shortcuts(set_keyboard_shortcuts = TRUE)
}

ショートカットの設定

ショートカットの定義は設定ファイルに記述する。設定ファイルはホームディレクト~/または~/.config/以下に.shrcts.Rまたは.shrtcts.yamlを作成し、そこに記述する。拡張子から分かるように前者はR、後者はYAMLで記述する。Rだと微妙にハマった*2ので、以下ではYAMLでの説明をする。

なお、設定ファイルはshrtcts::edit_shortcuts()関数を実行することでも開ける*3

設定ファイルには最低限NameBindingの2つが必要となる。

- Name: test
  Binding: praise::praise
  • Nameにはショートカットの名前を記述する。アドインのリストや、キーボードショートカットの設定画面で使われるので分かりやすく書いておく。
  • Bindingには実行するRの関数か、Rのコードを書いておく。
    • ちなみにpraise::praise()は実行すると励ましの言葉が出力される便利な関数だ。
    • アドインの設定ファイル(addins.dcf)だと関数名を書いておく必要がある箇所だが、.shrtcts.yamlでは任意のRコードを記述できる。shrtctsが記述に基づいて関数を定義してくれるためだ。
    • 複数行に渡るような複雑な例は後ほど示す。

設定を記述して保存したら、ショートカットをアドインとして登録するためにRStudioを再起動する(もし.Rprofileを編集していないのであればshrtcts::add_rstudio_shortcuts()を実行してから再起動する)。

これにより、アドインとしてショートカットが実行可能になる。あとはTools > Modify Keyboard Shortcuts...から作成したショートカットにキーを割り当てれば良い。

なお、前述のようにキーボードショートカットの割当てまでやってしまいたかったら、次のようにShortcutへショートカットの内容を記述しておく。

- Name: test
  Binding: praise::praise
  Shortcut: Ctrl+Alt+P

あとはshrtcts::add_rstudio_shortcuts(set_keyboard_shortcuts = TRUE)を実行して再起動すれば良い(または.Rprofileに記述しておく)。ただこれはキーボードショートカットの設定のJSONが標準的ではない方法で編集されるということなので、その点は留意しておく。

なお、ショートカットは最大100件まで登録できるらしい。

オプション

上述のName, Binding, Shortcut以外にも指定できるものがあり、ドキュメントに解説がある(ドキュメントにあるように、R形式の設定ファイルの場合はroxygen2のタグとして記述する)。

これらのうち、Interactiveは挙動に影響するので理解しておく必要がある。

  • true(既定値)の場合、関数がインタラクティブに実行される。つまりコンソールで実行され、コンソールに結果が返る。
  • falseの場合、関数はバックグラウンドで実行され、コンソールには出力されない。次に例を示すが、この指定はエディタ中で文字列を編集するような場合に役立つ。

ショートカットの例

単にインタラクティブに関数を実行するだけならあまり迷うことはないが、編集中のファイルに文字列を挿入したり削除したりしようとすると少々複雑になる。

具体的にはrstudioapiパッケージの力を借りる必要がある。

rstudio.github.io

例を2つあげる。

1. Pythonのコードチャンクを挿入する

RStudioには、R Markdown編集中にCtrl+Alt+IでRのコードチャンクを挿入するという便利なショートカットがある。R MarkdownPythonを書くのにも便利なので、Pythonのコードチャンクが欲しいという場合があるだろう。次のようなショートカットを書けばPythonのコードチャンクを挿入できる。

- Name: Insert Python code chunk
  Binding: |
      context <- rstudioapi::getActiveDocumentContext()
      id <- context$id
      location <- c(context$selection[[1]]$range$start)
      text <- "```{python}\n\n```"
      rstudioapi::insertText(location, text, id)
      rstudioapi::setCursorPosition(location + c(1, 0))
  Interactive: false
  • rstudioapi::getActiveDocumentContext()は編集中のドキュメントや、カーソル位置に関する情報を取得する。
  • rstudioapi::insertText(location, text, id)idで指定したドキュメントのlocationの位置にtextを挿入する。
  • rstudioapi::setCursorPosition(location)locationの位置にカーソルを移動する。上記コード中で+ c(1, 0)としているのは、コードチャンクの中にカーソルを移動させるため。

ちなみに実際のCtrl+Alt+Iは割と高機能で、コードチャンク中で実行するとチャンクオプションを引き継いだままコードチャンクの分割ができたりする。したがって上記は簡易版である。

2. バックスペース

私はCtrl+Hにバックスペースが割り当てられていないと辛いタイプの人間なのだが、標準ではバックスペースにキーボードショートカットが割り当てられていないので、特にWindows上のRStudioでこれを実現しようとすると結構面倒くさい。多分(アドインを使わない限りは)RStudioだけだと完結しない。ではrstudioapiを使えば簡単にできるかというと、キー入力を送信するような関数はなさそうで、これも難しそうだった。

rstudioapiのドキュメントを見ると、文字を消す例ではmodifyRange()が使用されていた。

最終的に次のようにした。バックスペースの挙動は意外と複雑という気付きがあった。

- Name: Backspace
  Binding: |
    context <- rstudioapi::getActiveDocumentContext()
    id <- context$id
    location_start <- c(context$selection[[1]]$range$start)
    location_end <- c(context$selection[[1]]$range$end)
    ## スタート位置の調整
    if (!all(location_start == location_end)) {
      # pass (選択が範囲の場合、範囲内のみを消す)
    } else if (location_start[2] == 1) { # 行頭である
      if (location_start[1] > 1) { # 先頭行ではない
        # 前の行の末尾が開始点(前の行末の改行を消す)
        location_start = c(location_start[1] - 1, Inf)
      }
    } else {
      # 行の途中であり、範囲選択でなければカーソルの前の1文字を消す
      location_start = location_start - c(0, 1)
    }
    rstudioapi::modifyRange(rstudioapi::document_range(location_start, location_end), "", id)
  Interactive: false

shrtctsパッケージを使ったショートカットの登録は多少は準備は要るものの、アドインを自作して登録するよりずっと簡単で、設定の共有もしやすい。上記例はgistに公開しておいた。

*1:中身を軽く見てみたところ、設定ファイルに基づいて関数を定義し、その関数をshrtctsパッケージのアドインとして登録することで目的を達成しているようだ。賢い。

*2:(私が書き方を間違えているだけかもしれないが)Rで記述する場合はfunction() {}で囲って関数にしておかないとrstudioapiを利用した関数を記述した場合にRStudio not runningの注意が表示されるようだった。

*3:このときファイルが無ければ.shrtcts.Rを作成できるが、手元(macOS)で試したところロケーションは/Library/Application Support/shrtcts/.shrtcts.Rだった。ドキュメントの記載と場所が違うが、一応これでも動くらしい。