※この投稿はRStudio Advent Calendar 2016の20日目の記事です。
あれはちょうど去年の今頃でした。RStudioにアドインという機能拡張の仕組みができ、Preview版のRStudioに搭載されました。
公式サイト:http://rstudio.github.io/rstudioaddins/
RStudioオタクであれば、クリスマスがあることも忘れてアドインと戯れたあの日々を懐かしく思い出すことでしょう。
そして、アドインは満を持して今年の2月に正式公開されました。
アドインがあればRStudioを無限に改造できる! よーしパパ自分の好きな壁紙のRStudioをつくっちゃうぞー!
...
そんな風に思っていた時期が私にもありました。
目を覚ましてください。
現実です‥‥‥! これが現実‥!
なんということでしょう…。
壁紙ひとつ変えるためにもRStudioを自前ビルドしなければいけない。なんと野蛮な21世紀を私たちは生きているのでしょう。
つらい。
なんたるマッポーの世か。
アドオンさえあれば未来はバラ色なのではなかったのか。
あなたはそんな自問自答を繰り返し、運命を呪い、嗚咽し、拳を地面に叩きつけることでしょう。何度も、何度も。血が滲むまで。
なかったら作る
いいえ、そんなことをしている暇はありません。涙を拭いて前を向きましょう。
アドインが盛り上がっていないのは、これまでアドインをつくってこなかった私たちにも責任があります。
ということで突然ですが、エディタに突然の死AAを挿入する突然の死アドインを作ってみたいと思います。
addins.dcf
まずは、アドインの説明をaddins.dcf
というファイルに書き、inst/rstudio/addins.dcf
に配置します。例えばこんな感じです。
Name: 突然の死 Description: 突然の死のAAを生成します。 Binding: insertSuddenDeath Interactive: false
Name
: アドインの名前です。日本語もOKです。Description
: アドインの説明です。Binding
: Rの関数名です。このアドインが実行されるときはこの関数が実行されます。Interactive
: インタラクティブなアドイン(Shinyなど)ならtrue
を、バックグラウンドで動くアドインならfalse
を指定します。今回はインタラクティブな要素はないのでfalse
です。
この時点でのファイル配置はこんな感じです。
. ├─inst/ │ └─rstudio/ │ └─addins.dcf ├─man/ ├─R/ ├─NAMESPACE ├─DESCRIPTION ...
実際に実行される関数insertSuddenDeath()
はまだ定義していませんが、実はこれだけでもインストールすることができます。インストールするとエディタペインの上部にある「Addins」の選択肢に「突然の死」が加わります(クリックしてもエラーになるだけですけど)。
getActiveDocumentContext()
で選択範囲を取得
次に、選択範囲の文字列を突然の死AAに変換するinsertSuddenDeath()
関数を実装します。
RStudioのエディタから選択範囲の文字列を取得したり、逆にエディタに文字列を挿入したりするのは、rstudioapiというパッケージを使います。
まず、選択範囲の文字列を取得するのはrstudioapi::getActiveDocumentContext()
という関数があります。これは以下のような構造のリストを返します。selection
が選択範囲で、RStudioでは複数の選択範囲を指定できるのでリストになっています。それぞれの要素はdocument_selection
というクラスのオブジェクトで、range
が選択範囲を表すdocument_range
というクラスのオブジェクト、text
が選択範囲内の文字列です。range
はさらに、開始位置と終了位置のdocument_position
オブジェクトのリストでもあります。
a <- rstudioapi::getActiveDocumentContext() str(a) #> List of 4 #> $ id : chr "#console" #> $ path : chr "" #> $ contents : chr "" #> $ selection:List of 1 #> ..$ :List of 2 #> .. ..$ range:List of 2 #> .. .. ..$ start:Class 'document_position' Named num [1:2] 1 1 #> .. .. .. .. ..- attr(*, "names")= chr [1:2] "row" "column" #> .. .. ..$ end :Class 'document_position' Named num [1:2] 1 1 #> .. .. .. .. ..- attr(*, "names")= chr [1:2] "row" "column" #> .. .. ..- attr(*, "class")= chr "document_range" #> .. ..$ text : chr "" #> ..- attr(*, "class")= chr "document_selection" #> - attr(*, "class")= chr "document_context"
modifyRange()
で選択範囲を置き換え
document_selection
からエディタ内を書き換えるには2つのアプローチがあります。
ひとつは、document_position
オブジェクトに対してinsertText()
関数を使う方法です。例えば、以下のようにすれば選択範囲の文字列を<
と>
で挟みます(end
に+ 1
しているのは、<
が入って位置がずれるからです)。
rstudioapi::insertText(a$selection[[1]]$range$start, "<") rstudioapi::insertText(a$selection[[1]]$range$end + 1, ">")
もう一つは、document_range
オブジェクトに対してmodifyRange()
関数を使う方法です。例えば、以下のようにすれば上と同じく選択範囲の文字列を<
と>
で挟みます。
rstudioapi::modifyRange(a$selection[[1]]$range, sprintf("<%s>", a$selection[[1]]$text))
今回は後者のやり方でやってみます。
突然の死をつくる
次に、突然の死の構成要素を見てみましょう。
_人人人人人人_ > 突然の死 <  ̄Y^Y^Y^Y^Y^Y^ ̄
以下の順序で処理すると突然の死ができます。
まず、ベースの文字列があります。
突然の死
これの左右に空白を2つづつ付け加えます。
突然の死
これに、上部は人
の繰り返し、下部はY^
の繰り返しをつけます
人人人人人人 突然の死 Y^Y^Y^Y^Y^Y^
さらにこの左右に文字を付け加えれば完成です。
_人人人人人人_ > 突然の死 <  ̄Y^Y^Y^Y^Y^Y^ ̄
insertSuddenDeath()
を実装する
いよいよ関数を作ってみます。ここでは簡単のため、
- 選択範囲はひとつだけ
- 改行は含まない
とします。
insertSuddenDeath <- function() { ctx <- rstudioapi::getActiveDocumentContext() txt <- ctx$selection[[1]]$text w <- stringi::stri_width(txt) # 奇数の場合は1を足す w <- ceiling(w/2) * 2 + 4 # Windowsだと文字コードを明示的に指定する必要あり Encoding(txt) <- "UTF-8" top <- paste0("_", stringi::stri_dup("人", w/2), "_") middle <- paste0(">", stringi::stri_pad_both(txt, w), "<") bottom <- paste0(" ̄", stringi::stri_dup("Y^", w/2), " ̄") rstudioapi::modifyRange(ctx$selection[[1]]$range, paste(top, middle, bottom, sep = "\n")) }
と書いているあたりでもう日付が変わってしまったので細かい説明は割愛します。
キーボードショートカットを設定
アドインにはキーボードショートカットが設定できます。以下のように「Browse Addins...」「Keyboard Shortcuts...」とたどっていくと設定画面が出てきます。
まとめ
ということで、思い立って以下のアドインを作りました。みなさんもガンガンつくってアドインとRStudioの発展に貢献していきましょう。
アドインのリストは以下のレポジトリに集まっているようです。もっと増えてほしい…