✍️

テキスト編集の最後の一歩をいい感じに

2024/12/18に公開

こちらはMOSHのアドベントカレンダーの18日目の記事です。
当アドベントカレンダーはバラエティ豊かで、アートからビジネスまで各種取り揃えている感じになってきております!お祭り感があっていい感じです😊
が!当記事はゴリゴリのエンジニア向けトピックで、微に入り細を穿つ感じのテーマでお送りしていきたいと思いますのでよろしくお願いします!!

はじめに

筆者のエディタ/IDE遍歴はかなりフラフラしておりますが、vimに最初に触れたのは、当時所属していた会社が共有のLinuxサーバーを開発環境としていたのがきっかけだったと記憶しています。それからはvimとemacsを交互に使ったりしましたが、neovimの登場以降は基本的にはneovimを使い続けていました。

が、2015年にVS Codeが登場してからエンジニアのIDEのシェアは一変したように思います。VS Codeは無料で使うことができ、複雑さを増していく開発環境に対応するための3種の神器的な機能を備えました。強力な静的解析を伴ったコードアシスト機能、デバッガ、devcontainerです。もうneovimでは無理なのか・・?と何度も移行を考えながらも、どうしても「テキスト編集の最後の一歩」の快適さを手放すことができず苦悩する日々でしたが、なんとMicrosoftさまは件の3種の神器を独立した機能として公開してくれたのです!言わずとしれたLSP、DAP、devcontainerですが、これらをneovimに組み込むことにより奇跡の延命を遂げ、設定の複雑さというところはありつつも、起動/実行速度では圧倒的に優位で「テキスト編集の最後の一歩」に強みを持つという特徴を備えた、機能的には既存IDEと遜色のない開発環境を手に入れるに至ったわけです。

前置きが長くなりましたが、本稿では再三強調した「テキスト編集の最後の一歩」というものがなんなのか?ということについて紹介できればと思います!

要は編集作業のこと

端的に書いてしまうとそういうことなのですが、例えばあるクラスに定義されているあるメソッド内の、ローカル変数A=引数Bの処理を、ローカル変数C=引数Bに変更するという作業があるとします。工程を分解してみましょう。

  1. 対象のクラスが定義されているファイルを探し、開く
  2. 目的のメソッドにジャンプする
  3. ローカル変数Aにジャンプする
  4. ローカル変数Aの記述をローカル変数Bに変更する
  5. 保存して終了

ざっとこのくらいの工程がありますね。AIに頼むにはプロンプトを考えるほうが面倒そうですし、手でサクッと対応したいところです。

実際の操作

実際にコードを書くときも正常系だけ網羅すればOKということはほぼないことでもわかると思いますが、当然ながら編集作業でもこういうケースってよくあるし、それが起きるたびに面倒な思いをしてきたわ、、ということは少なくないのではないでしょうか?
以降は工程ごとに発生しがちなトラブルシュートや、あったら便利な設定を紹介していこうと思います!

ファイルを開きたい

ファイル検索といえばfuzzy finderとなって久しいですが、neovimで最も人気の高いと思われるそれはtelescope.nvimというプラグインです。

自分が使いやすいのが一番

一発で見つかればこれで完了ですが、そんなに都合のよくいくことばかりではないですよね?

目的のファイルは一つ下の行に出ていたやつだった

たまによくあると思います!原因はフィジカル的な問題だったりPCのシステム的な問題だったり色々あると思いますが、とにかく起きてしまうものはしょうがないので、直前の検索結果を表示し直します。
幸いtelescopeにはbuiltin.resumeという標準のpickerがあるので、これのキーバインドを入れておけば対策は完璧です!

候補が複数あって、実際開いてチェックしないとわからない

これもあるんじゃないでしょうか?前述の例でいうと、クラス名で検索すればかぶることはそうそうないと思いますが、メソッド名や変数名はしばしば重複するので、キーワード次第では複数ファイルをチェックする羽目になります。前述のresumeを繰り返すことでもまあ良いんですが、できれば表示しっぱなしにして、ウィンドウの行き来によって解決したいと筆者は思いました[1]

さっきの検索結果をポイッと放り込む
これを担うのがtrouble.nvimというプラグインです。前述のtelescopeとのintegrationも用意されているので、連携設定は簡単です!

あの差分どこにあったっけ?

trouble.nvimが登場したので、せっかくなのでついでにもうひとネタ紹介させてください!
ふとコミット前に編集した内容が気になり、それがどこにあったかイマイチ思い出せず、シェルに戻ってgit diff | grep hoge | fzfなどやり始めたことがある方もいるのではないでしょうか??
この場合、gitsigns.nvimとtrouble.nvimのintegrationが強力です!開いているバッファ内もしくはワークスペース全体の差分をリストアップしてくれます!

各差分にジャンプ

目的のメソッド/変数にジャンプする

めでたくファイルが開けたところで、目的の編集箇所に移動したいのですが、ここでもまた考えることが出てくるケースがあります。
まずは目的の変数めがけてジャンプした場合は特に何もすることはないので割愛するとして、変数名が目的地だけどカーソル位置はファイルの先頭だったりクラス名の場所だったりすると、微妙に目的地までの距離がまだあることがあります。ここでおもむろにマウスでエディタ画面をスクロールしはじめる・・という方はもう当記事の読者には残っていないと思いますが、手段としては下記のようなものがありそうです。

  • ファイル内検索する
  • コードのアウトライン表示(静的解析結果のツリー表示ウィンドウ?)から目的の要素にジャンプ
  • ↓キーや半画面スクロールのキーバインドを押す🥹

実際のコードで見てみましょう。こちらはpythonのAWS SDKであるところのboto3のコードからの引用になります。

class Session:
    def __init__(
        self,
        aws_access_key_id=None,
        aws_secret_access_key=None,
        aws_session_token=None,
        region_name=None,
        botocore_session=None,
        profile_name=None,
    ):
        if botocore_session is not None:
            self._session = botocore_session
        else:
            self._session = botocore.session.get_session()

        if self._session.user_agent_name == 'Botocore':
            botocore_info = f'Botocore/{self._session.user_agent_version}'

目的の変数がbotocore_infoだったとすると、わざわざ検索するのも別ウィンドウから操作するのも微妙な、絶妙な距離ですね。
こんなときに活躍するのがleap.nvimのようなモーション強化プラグインです。

この状態でuを押せばbotocore_infoにジャンプ!
ジャンプの対象を複数ウィンドウにしたり、逆にカーソル行だけに絞ったりと、何かと使い所の多い機能です。

目的のキーワードを編集する

さて、話はますます細かいところに入っていきますが、前述のコードでbotocore_infoを編集する手順も例によって分解してみましょう。

  1. botocore_infoを選択する
  2. 選択部分を削除
  3. 正しい文字列に修正

2と3は補完機能などはあるにしても、エディタによってそう差異はないと思いますが、1に関してはだいぶ違ってきます。マウスカーソルをおもむろにbotocore_infoの上にもっていってダブルクリック・・では辛いのです。
(neo)vimにはテキストオブジェクトという概念があり、テキストをあるルールに基づいた塊として扱う機能です。・・といってもなんのことやらですよね?かくいう筆者もこの便利さに気づき、理解するまでは数年の月日を要しました😂
なんとなくですが、ルールにどんなものがあるか?それに対してどんなことができる[2]か?がわかると一気に理解が進む気がします。
まとめると、(neo)vimではbotocore_infoの上にカーソルがあれば、ciwのストロークで1と2の工程が完了している状態になります。change inner wordですね。すごい!!

この__init__消して書き直したい

話は飛びますが、せっかく見つけたこの修正箇所だけど、メソッド自体が全体的にイマイチだし書き直そう・・😵‍💫と気の迷いが高じて考えてしまったと仮定します。
そうするとメソッド全体をいったん消したいとなるわけですが、その際にメソッドというのもテキストの塊では?と気づくことでしょう。それを叶えるのが構文解析部分を受け持つnvim-treesitterとその拡張機能であるnvim-treesitter-textobjectsです!
これを導入すれば、class、conditional、loopのinner/outerなどがそれぞれテキストオブジェクトとして操作できるようになるのです。仮に今回の目的であるfunctionのouterにafというキーバインドを割り当てておけば、dafのストロークでdelete outer functionができるようになるわけですね!最高!!

iはinnerでaはouterはわかったけど、その後に続くwとかfとか覚えきれるわけないのでは?

ごもっともです。いくらwordのwやfunctionのfなど、連想しやすく設定するといっても限界があります。functionだっけ?methodだっけ?みたいに紛らわしく思うこともあるでしょう。
だったら次のストロークで何が起こるか表示されていればいいじゃない?ということで誕生した(かどうかは定かではないです)のがwhich-keyのようなプラグインです。もはや無いと困るレベルで依存してます🥹

次にfを押せばformatしてくれるのがわかる

保存して終了

現代はコードはバージョン管理していて当たり前になっています。なので、間違ったらgit restoreすればいいという強い安心感を背景に、編集したらその場で勝手にファイルが保存されるのが圧倒的にラクです。システムトラブル時に編集内容が消えるみたいな事故や、終了時に保存しますか[y/n]みたいなやりとりをする手間もなくなります。なので、筆者はauto-save.nvimというその名前の通りのプラグインを導入しています[3]
とはいえ好みの分かれる部分かもしれず、興味のある方は導入してみるといいかも?という提案にとどめておきます!

まとめ

いかがだったでしょうか?
たかが編集作業というところでも、いざ説明しようとすると結構なボリュームになりますね!
本稿を読んで、(neo)vimに興味を持つ人が増えたり、VS Codeに今回紹介した機能を取り込むような人が出てきたりすると嬉しいと思います!
年末年始こそdotfilesチャンス!色々設定など見直していきましょう!!

脚注
  1. VS Codeの場合は検索結果ウィンドウが残ったはずなので、メインのエディタウィンドウと検索結果ウィンドウを行き来できるようなキーバインドを覚えておくと良さそうですね! ↩︎

  2. 知っている方には言わずとしれたオペレータの概念ですね。さすがにボリューム過多かなと思うので割愛 ↩︎

  3. 自動保存プラグインは選択肢多めです!これを導入したのもだいぶ前の話なので、もしより良いプラグインをご存じの方がいましたら、ぜひご紹介いただければと思います🙇 ↩︎

Discussion