Visual Studio Code に ReadOnlyMode を追加してみた。
このブログはeeic2019の後期実験「大規模ソフトウェアを手探る」、保留班のブログです。
動機
手探る対象ですが、学科同期を始め、プログラミングの機会がある方の多くが使っている、知っているVSCodeにしようということになりました。追加する機能に関しましては、GitHubのIssuesよりこちらを参考に「ReadOnlyMode」を実装してみることにしました。
目標とするReadOnlyModeの機能は、ショートカットキーで通常のエディターモードと 書き込み不可モードを切り替える!です。
環境構築
VSCodeの開発環境は、非常に手厚くサポートされていますので、基本的にはこちらのページを参考にすれば、デバッグ環境を構築できます。
VSCodeは、主にTypeScriptやJavaScriptで記述されているのですが、このページを参考にすると「Chrome Developer Tools」という、Google ChromeでJavaScriptなどのデバッグをするときと同様のツールを用いることができます。webページを作成したことのある人などには取っつきやすいかと思います。
以下、ビルドに際して私が遭遇したエラーについて記述しておきます。同様のエラーが出た人は参考にしてください。Linuxです。
- ./scripts/code.sh でRunされない。
私の場合、NODE_MODULE_VERSION が違うと怒られました。(上のページの指示に従っていたのでそんなはずはないのですが...)
なかなか解決策が見つからず苦戦したのですが結果としては、GitHubより入手したvscodeフォルダ内に生成されているnode_moduleフォルダを削除して、もう一度yarnからやり直すとうまく行きました。
- これは実際にソースコードに変更を加えてリビルドする段階ででたエラーなのですが、yarn watch 中に
Error: System limit for number of file watchers reached
と出てきました。
検索すればすぐ分かるのですが、一応ここにも記述しておきます。
$ sudo gedit /etc/sysctl.conf
で開かれるファイルの最後に
fs.inotify.max_user_watches=524288
と追加しましょう。以上。
既存の機能の調査
・ToggleTabFocusMode
環境構築を終えたあとさっそくソースコードの解読に取り掛かりました。とはいってもはじめはTypeScriptという慣れない言語や抽象化された記述に苦しめられましたが、Contributorのための公式のWikiを読み漁ったり、ソースコード内を検索して調べたところ、「ToggleTabFocusMode」というキーボードショートカットの存在を知りました。
ToggleTabFocusModeとは、通常のTabキーを押すと空白が出力される状態と、Tabキーを押すとクリック可能なボタンへ飛んでいくTabFocusの状態を切り替える機能のことです。VS codeを開いた状態でctrl+Mを押す、あるいはメニューバーを操作するとTabFocusModeのオンオフを切り替えることができます。
この機能の特徴は、「Editor全体の状態を切り替えることができる」「Tabキーを入力しても反映されなくなる」などの特徴が我々の目標とするRead-Only Modeの実装と似ています。これを発展させれば完成するだろう…と思ったのですが、これをどのように改造したらいいか手がかりが掴めず、1,2回ほど停滞してしまいました。
・特定条件下でのRead-Only状態
少々手詰まり気味になっていたので毎回の進捗報告でその旨を報告すると、TAの方にVS Codeのエディタが特定の条件を満たすとRead-Onlyになることを教えていただきました。
VS Codeはgitとの連携機能があり、サイドバーのSource Controlでファイルごとにmasterとの差分を表示するという便利な機能があります(下画像)。この時、差分を表示しているエディタ内はread-only状態になっていて、文字を入力しようとしても編集することができない仕様になっています。これが求めていた機能だ!!!!!!!
このデフォルトで実装されているRead-Only ModeはGit関連の表示をしたときにしか呼び出されず、ショートカットや設定で今開いているファイルをRead-Only Modeに変更するという操作はできないようです。
デバッガを用いて上記の機能がいつ呼び出されているのかを調べたところ、/src/vs/editor/common/controller/cursor.tsの677行目、
if (this._configuration.options.get(EditorOption.readOnly))
の条件分岐がTrueであるとRead-Onlyモードに入ることがわかりました。実際に上記のif文を常にTrueになるように書き換えたところ、常時Read-Only Modeで編集が不可能なエディタが完成しました。(編集不可能なエディタとは一体…)
Read-Only Modeの呼び出しに無事成功したので、あとは前半で手探ったToggleTabFocusModeとうまく組み合わせればショートカットが作れることになります。これで着地点がようやく見えてきました。
(ここからToggleTabFocusModeのショートカット周りを深く読み込む流れになりましたがそのあたりは実装編と内容がかぶるのでそちらで説明します。)
実装
・概要(ToggleReadOnlyMode)
ソースコードを一通り読み終えて実装のめどが立ったので、実装に移ります。おおまかなフローを掴めていたおかげで実装は1回半程度で無事終えることができました。
実装をした全ての機能を箇条書きでまとめると以下のようになります。
- 名称は「ToggleReadOnlyMode」で、Read-only Mode(=編集が不可能な状態)と編集が可能な状態を切り替えられるようにする機能である。
- Read-Only Modeの時はキー入力やカット・ペースト等を行っても文字列に変更はなされず、「Cannot edit in read-only mode」と表示される。
- Read-Only Modeはエディタ全体に反映される。すなわち、Read-Only Modeがオンの状態だと開いたファイルは全てRead-Onlyになる。
- ショートカットキーにShift+Alt+Rキーが設定されており、キーを押すとRead-Only Modeのオンオフを切り替えられる。
- メニューバーの「Edit」に" ToggleReadOnlyMode"メニューが追加されていて、クリックでオンオフを切り替えられる。
- Read-Only時はステータスバーに"Read-Only"と表示され、表示されているところをクリックするとRead-Only Modeが解除される。
こうして書くとけっこう追加した機能が多くて大変そうに見えますが、基本的にはToggleTabFocusModeで呼び出されている関数と似たような関数を新規に作成するといいう流れで機能を追加できたので比較的容易に実装できました。(ソースコードの行数で言えば166行の追加で済みました。)このように機能が容易に追加できたのは、VS code自体が高度に抽象化されたオブジェクト指向で書かれていることの恩恵を受けたと思います。
実際に動作している様子を以下に貼ります。
・詳細
とはいえ新しく追加した構造体や関数は両手で数えられる量を超えていて、一つ一つ仕様を説明すると途方もない量になってしまうので、実装の肝となる部分をかいつまんで説明しようと思います。
先程貼ったgitのRead-Onlyのオンオフを判定する部分を再掲します。
if (this._configuration.options.get(EditorOption.readOnly))
この部分をデバッガを使って詳しく調べると次のような事実がわかりました。エディタの状態はEditorOptions.ts内のIEditorOptionsクラスのオブジェクト(正確には、IEditorOptions内の配列EditorOption[101]の63番目の要素がreadOnlyのtrue/falseを指定している)に保存されていて、このクラスのオブジェクトを参照・変更することでエディタの状態を確認・変更することができるようです。
そこで、IEditorOptions内に新たにEditorOption.readOnlyModeなる要素を追加して、この要素を参照してTrueのときにReadOnlyModeになるようにしました。こうすればエディタ側にRead-Only Modeかどうかを記憶させることができるようになります。
次に、Read-Only Modeのオンオフを切り替えるためのキーボードショートカット/メニューバーです。こちらはToggleTabFocusModeの仕様を調べたところ、EditorActionクラスを継承した以下のようなオブジェクトを作成すればうまくいくことがわかりました。
export class ToggleReadOnlyModeAction extends EditorAction { public static readonly ID = 'editor.action.toggleReadOnlyMode'; constructor() { super({ id: ToggleReadOnlyModeAction.ID, label: nls.localize({ key: 'toggle.readOnly', comment: ['Turn on/off read only mode around VS Code'] }, "Toggle Read Only Mode"), alias: 'Toggle Tab Key Moves Focus', precondition: undefined, kbOpts: { kbExpr: null, primary: KeyMod.Alt | KeyMod.Shift | KeyCode.KEY_R, mac: { primary: KeyMod.Alt | KeyMod.Shift | KeyCode.KEY_R }, weight: KeybindingWeight.EditorContrib }, menubarOpts: { menuId: MenuId.MenubarEditMenu, group: '5_insert', title: nls.localize({ key: 'miToggleReadOnlyMode', comment: ['&& denotes a mnemonic'] }, "Toggle &&Read Only Mode"), order: 5 } }); } public run(accessor: ServicesAccessor, editor: ICodeEditor): void { const oldValue = ReadOnly.getReadOnlyMode(); const newValue = !oldValue; ReadOnly.setReadOnlyMode(newValue); if (newValue) { alert(nls.localize('toggle.readOnly.on', "Editor is read only mode now")); } else { alert(nls.localize('toggle.readOnly.off', "Editor is not red only mode now")); } } } registerEditorAction(ToggleReadOnlyModeAction);
最終行の
registerEditorAction(ToggleReadOnlyModeAction);
Pull Request送ってみた
Read-Only Modeを作り終えた後に少し時間が余ってしまったのでせっかくだからPull Request(プルリク)を送ってみることになりました。
大規模OSSにプルリクを送ったことがなかったので少し緊張していたのですが、手順自体はすごく単純で
- フォークしたレポジトリからプルリクを作成
- 作成したプルリクに対してbotが自動テスト
- 自動テストを通過したらえらい人が実際にコードを見て返信をくれる
といった流れでした。
個人的に面白いなと思ったのは2番の自動テストで、このテストでは送ったコードがコーディング規約をちゃんと満たしているかや異なるOSを使った際におかしい挙動をしないかなどをbotが自動でチェックしてくれます。
このテストは失敗しても何回でも挑戦可能で、実際のテストでは空白やインデントの数が規約と違うといったかなり細かい項目で何回か引っかかってしまいました。
このテストのジャッジを眺めているときは競技プログラミングのジャッジを眺めているようなわくわく感があって楽しかったです。
自動テストを通過した翌日にMicrosoftの開発チームの方から連絡がありました。
その結果は...
ダメでした(´;ω;`)
どうやら既存のシステムのバグを避けてとった行動が裏目に出て、開発方針とあわなかったみたいです...残念!
感想
今回はチームメンバー三人とも大規模なソフトウェアに対して手を加えるのが初めてだった上にVSCodeが普段書かないTypeScriptで書かれていたということもあって、開始直後はコードのどの部分でどのような処理をしているのかを把握するだけでも苦戦を強いられました。
また、VSCodeのソースコードでは多数の人が手を加えてもバグを生みにくいようにするためにコードが全体的に抽象化されているなどといった工夫がなされていて感心しました。でもできればもう少し人が読めるように書いてほしい。
あとは開発環境の導入やデバッグ方法などが英語ではありますが公式のサイトでまとまっていたので、実際に手探り始めるまでの敷居はそこまで高くないと感じました。
他にも、イベント駆動型プログラミングへの理解や大規模OSSのプルリクの作法など全体を通してとてもいい経験を積むことができました。
あと、デバッグは偉大