🌰

そうだ、エディタを変えよう 〜ZedでLSPを使用する〜

2024/12/04に公開

この記事は Magic Moment Advent Calendar 2024 3 日目の記事です。

Magic Moment でエンジニアをしている 栗原 です。

みなさん。マイクロサービスアーキテクチャのプロダクトの開発していると、大量に起動したサービスがローカル環境のリソースを圧迫してしまい、エディタが思ったように動いてくれないといったことはありませんか。

GCP workstationやGitHub Codespacesなどクラウドベースの開発をすると解決できますがコストが見合わないですし、使うサービスだけ立ち上げてというのも開発している機能によってはできません。PCのスペックを上げて。。。きりが無いですね。もう駄目だと思った時閃いたのです。

「そうだ、エディタを変えよう」

谷村新司さんが、昨日、今日、明日。変わり行く私と歌ったようにエディタの世界も日々変わっているはず。

そして見つけたのがZed。

Zedは、「Fast」Rustを使用した軽量かつ圧倒的なパフォーマンス、「Intelligent」LLMを使用したコードの生成、変換、分析、「Collaborative」チームメンバーとの共同作業の3つの特徴を持っている次世代エディタです。

実際に使ってみると特に「Fast」について実感でき、リソースが厳しい状況下でも満足できるコーディング体験を提供してくれそうでした。

ただ、一点大きな問題がありました。それは拡張機能が少ないことです。

これは新興エディタあるあるですが、会社で使用している言語に対応していないと使い物になりません。
実際に弊社ではprotobufのスキーマ定義でBufを使用していますが、ZedがBufのスキーマに対応しておらず、拡張機能もありませんでした。

そこでvimのようにLSPを使ってこの問題を解決しようと考えたわけですが、Zedの標準設定ではできないことがわかり、Buf LSPを利用するための拡張機能を自作することにしました。

その時の手順を解説します。

BufのLanguage Serverのインストール

以前はLanguage Server単体で公開されていましたが、現在はBuf CLIで利用できるようになったようなのでBuf CLIのインストールを行います。
インストール方法はこちらに記載されていますが、私はMacなので一番手軽なhomebrewでインストールをしました。

brew install bufbuild/buf/buf

拡張機能のプロジェクトの作成

Zedの拡張機能はRustのためRustのプロジェクトを作成し、 こちら のドキュメントに従いファイルの追加/変更します。

cargo new bufls

cd bufls

mv main.rs lib.rs

touch extension.toml

ディレクトリ構成

.
├── .git/
├── .gitignore
├── Cargo.toml
├── extension.toml
└── src/
   └── lib.rs

Cargo.tomlの変更

[package]
name = "bufls-zed-extension"
version = "0.1.0"
edition = "2021"

[lib]
path = "src/lib.rs"
crate-type = ["cdylib"]

[dependencies]
zed_extension_api = "0.1.0"

extension.tomlの変更

# 拡張機能のID(一意であればなんでもよい)
id = "bufls-zed-extension"
# 拡張機能名
name = "Buf LSP"
# バージョン
version = "0.0.1"
# バージョンの番号
schema_version = 1
# 作成者
authors = ["Marronmaro <>"]
# 拡張機能の説明文
description = "bufls extension"
# 公開リポジトリ
repository = "https://github.com/marronmaro/bufls-zed-extension"

[language_servers.bufls]
# LSPの名前
name = "bufls"
# このLSPを利用するフォーマット
languages = ["Proto"]

src/lib.rsにロジックを記述

BufのLanguage Serverを起動するようにRustのロジックを書きます。

※ BufのLanguage Serverのコマンドについてはこちら にドキュメントが公開されています。

use zed::LanguageServerId;
use zed_extension_api::{self as zed, serde_json, settings::LspSettings};

struct BuflsZedExtension {}

// https://buf.build/docs/reference/cli/buf/beta/lsp/?h=lsp
impl zed::Extension for BuflsZedExtension {
    fn new() -> Self {
        Self {}
    }

    fn language_server_command(
        &mut self,
        _language_server_id: &LanguageServerId,
        worktree: &zed::Worktree,
    ) -> Result<zed::Command, String> {
        match worktree.which("buf") {
            Some(path) => Ok(zed::Command {
                command: path,
                args: vec![
                    String::from("beta"),
                    String::from("lsp"),
                    String::from("--debug"),
                    String::from("--timeout=5m"),
                ],
                env: vec![],
            }),
            None => Err("buf in not found.".into()),
        }
    }

    fn language_server_initialization_options(
        &mut self,
        language_server_id: &LanguageServerId,
        worktree: &zed::Worktree,
    ) -> Result<Option<serde_json::Value>, String> {
        let settings = LspSettings::for_worktree(language_server_id.as_ref(), worktree)
            .ok()
            .and_then(|lsp_settings| lsp_settings.initialization_options.clone())
            .unwrap_or_default();
        Ok(Some(settings))
    }
}

zed::register_extension!(BuflsZedExtension);

拡張機能のインストール

  1. ZedExtensions の画面(コマンドパレットのzed:extensions)を開く

  2. 画面右上の Install Dev Extension を押下し作成したディレクトリを選択

    install.png

Zedの設定

今回作った拡張機能のLSPを優先するように設定を変更します。

"languages": {
    "Proto": {
        "language_servers": ["bufls", "!..."]
    },
}

これでProtoファイルを開いた際にBufのLSPが参照されるされるようになります。

Bufのバリデーションスキーマでヒントが表示される様子

hint.png

まとめ

今回はZedの拡張機能を利用してLSPを使う方法について解説しました。

思ったよりも大変だと感じた方もいるかもしれませんが、一度作ってしまえばロジック内のコマンドの部分だけ変更すれば他のLanguage Serverに横展開できます。

是非、Zedを使っている方やZedをこれから使ってみようと考えている方は参考にしてみてください。

次回のアドベントカレンダー@morishin の 「長い処理には通知音コマンドを仕込んでおくと捗るぞ」 です。お楽しみに!

Discussion