CodeMirrorでショートカットキーを追加する、あるいはJenkins Coreへの初めてのコミット

ブログの出落ちが好評との噂ですが、そのプレッシャーゆえにネタ探しに疲れてきているikikkoです、こんばんは。

http://serif.hatelabo.jp/images/cache/7acc714e78ac1911acd9a7c74b7db66ca3eb26e1/1bd1cfc82cb42540b01e5da7c1e363394ca2fb06.gif


今回、CodeMirrorをちょっと触ったので、自分の理解がてら触った範囲をまとめてみます。

概要

CodeMirrorというJSのライブラリをご存知でしょうか。テキストエリアをシンタックスハイライト可能な要素に変えてくれるライブラリです。ハイライト可能な言語はC, JavaRuby/PythonからHTML/CSS/JavaScriptなど、幅広く対応しています。

原理的には、↓な感じ。用意されたテキストエリアをCodeMirrorが提供するシンタックスハイライト付きのIFrameに置き換えます。元のテキストエリアはdisplay:noneにして非表示に。ユーザはIFrameに対してテキストを編集していき、submit時に元のテキストエリアに戻すという仕組みになっています。

https://cacoo.com/diagrams/jOKLDCwnkbRzt7jo-56185.png

一番単純な使い方はこのような感じ。シンタックスハイライトさせたいテキストエリアの要素をわたしてやります。

CodeMirror.fromTextArea(document.getElementById("textarea"),{
  mode:"text/x-java",
}

拡張:ショートカットキーの追加

他のライブラリ同様、CodeMirrorではいくつかの設定によって拡張することができます。対応言語なども増やすことができるのですが、ここでやったのはキーイベントを取得して特定の動作を行う、いわゆるキーボードショートカットを追加してみました。具体的には、Windows/LinuxではCtrl+Enter、MacではCommand+Enterでサブミットを行うというもの。

CodeMirrorでキーイベントを取得するには、onKeyEvent()をCodeMirror生成時のオプションで指定します。これでkeydown/keyup/keypressイベントを取得することができます。なお、戻り値にtrueを返すことによって、後続のイベントを全部キャンセルすることができます。サブミット後にはイベントは基本なくてもいいはずなので、trueを指定することになります。

CodeMirror.fromTextArea(document.getElementById("textarea"),{
  mode:"text/x-java",
  onKeyEvent: function(editor, event){
    console.log(event);
    ...
    return true;
  }
}

で、あとはCtrl or Command+Enterにフックしてsubmitすればいいだけです。が、macのCommandはCtrlキーと違って、メタキーとしてキーイベントの取得ができません。仕方ないので、keydown/keyupにひっかけて、Commandキーが押されているかどうかを判定しました。

完成コードのJS部分を抜粋したものがこちら。

(function() {
  var cmdKeyDown = false;
  
  CodeMirror.fromTextArea(document.getElementById("textarea"),{
    mode:"text/x-java",
    onKeyEvent: function(editor, event){
      function isGeckoCommandKey() {
        return Prototype.Browser.Gecko && event.keyCode == 224
      }
      function isOperaCommandKey() {
        return Prototype.Browser.Opera && event.keyCode == 17
      }
      function isWebKitCommandKey() {
        return Prototype.Browser.WebKit && (event.keyCode == 91 || event.keyCode == 93)
      }
      function isCommandKey() {
        return isGeckoCommandKey() || isOperaCommandKey() || isWebKitCommandKey();
      }
      function saveAndSubmit() {
        editor.save();
        document.form1.submit();
        event.stop();
      }
      
      // Mac (Command + Enter)
      if (navigator.userAgent.indexOf('Mac') > -1) {
        if (event.type == 'keydown' && isCommandKey()) {
          cmdKeyDown = true;
        }
        if (event.type == 'keyup' && isCommandKey()) {
          cmdKeyDown = false;
        }
        if (cmdKeyDown && event.keyCode == Event.KEY_RETURN) {
          saveAndSubmit();
          return true;
        }
        
      // Windows, Linux (Ctrl + Enter)
      } else {
        if (event.ctrlKey && event.keyCode == Event.KEY_RETURN) {
          saveAndSubmit();
          return true;
        }
      }
    }
  })
})();

どこで使ってる?

Jenkinsのスクリプトコンソールで使ってます。いつの頃からか、Jenkins上のスクリプトコンソールはCodeMirrorが採用されていますが、このおかげでちょっと困ったことになっていました。具体的には、サブミットにマウスを使用しないといけなくなったということです。

http://twitter.com/#!/ikikko/status/90756645495382016:twitter:detail:left

http://twitter.com/#!/kohsukekawa/status/90815059693019136:twitter:detail:right

http://twitter.com/#!/ikikko/status/90816199021170688:twitter:detail:left

CodeMirror採用前は、TabキーでSubmitボタンに遷移することができ、そこでSpaceキーを押せばキーボードだけで操作することができました。しかし、CodeMirror採用後はTabキーはそのままTabが入力されるようになったため、このテクニックが使えなくなったのです。スクリプトコンソールは結構頻繁に使っていたため、コンソール上ので操作が結構面倒になりました。

シンタックスハイライトが導入されて感じたことをふと思い出して、ショートカットキーを実装してプルリクエストした次第です。この対応は、Ver 1.432 (2011/09/25) 以降に入っています。スクリプトコンソールを使う方は、頭の片隅にでも留めておいてもらえると、ちょっと楽になるかもですね。


ちなみに。

CodeMirrorのユーザマニュアル見ていると、自動補完などもできるみたい。簡単にできそうなら、これも対応してみようかな。簡単にできそうなら。。。