Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ruby から Emacs を操作したい

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on. →
Avatar for phigasui phigasui
October 28, 2026

Ruby から Emacs を操作したい

Avatar for phigasui

phigasui

October 28, 2026

More Decks by phigasui

Other Decks in Programming

Transcript

  1. やりたいこと Ruby のプロセスから いま起動している Emacs を操作したい ファイルを開いて ある行に飛ぶ その行を ハイライトする

    エディタ上に 任意の文字列を書き出す たとえばローカルでアプリケーションを動かしている時 エラーが出たら 「対象行」を Emacs が開いてくれたら 嬉しい 3
  2. Emacs Server elisp のスクリプト (server-start) Emacs が 外からのリクエストを待ち受ける 待ち受け先は Unix

    ドメインソケット $TMPDIR/emacs<uid>/server など 届いた Elisp を その Emacs の中で評価 してくれる 多くはターミナルから起動済みの Emacs でファイルを開くために使われている 4
  3. emacsclient emacs に同梱されている emacsclient コマンド $ emacsclient -e '(message "Hello,

    world!")' "Hello, world!" Emacs のエコー領域に Hello, world! が出る -e には 任意の Lisp が書ける: $ emacsclient -e '(progn (find-file "~/app/user.rb") (goto-line 42))' 外から Emacs をどうにでも操作できる 5
  4. Ruby の値を Lisp に埋め込む def elisp_escape(text) text.gsub("\\") { "\\\\" }.gsub('"')

    { '\\"' } end file, line = "/Users/me/app/models/user.rb", 42 lisp = <<~LISP (progn (find-file "#{elisp_escape(file)}") (goto-line #{line}) (pulse-momentary-highlight-one-line (point))) LISP system("emacsclient", "-e", lisp) ファイルを開いて 42行目に飛んでハイライト 7
  5. でも困ることがある 実行環境に emacsclient が要る Docker の中 には Emacs も emacsclient

    も無い → コンテナで動くアプリから操作できない 呼ぶたびに 外部プロセスを起動 Ruby だけで完結できないか? 8
  6. emacsclient は「ただのクライアント」 が開くのは 普通の Unix ドメインソケット emacsclient はそこに繋いで テキストのプロトコル を喋っているだけ

    server-start → 同じプロトコルを Ruby で喋れば emacsclient は要らない 参照: Emacs の lisp/server.el(server-quote-arg / server--process-filter-1) 10
  7. Ruby だけで Hello, world! require "socket" def quote(s) = s.gsub(/[&\-

    \n]/, "&" => "&&", "-" => "&-", " " => "&_", "\n" => "&n") path = File.join(ENV.fetch("TMPDIR", "/tmp"), "emacs#{Process.uid}", "server") UNIXSocket.open(path) do |sock| sock.write("-eval #{quote('(message "Hello from UNIXSocket!")')}\n") sock.each_line { |l| puts l; break if l.start_with?("-print", "-error") } end -emacs-pid 68800 -print "Hello&_from&_UNIXSocket!"&n emacsclient なしで Emacs が動いた 12
  8. 返信の読み方 返信も同じルールでクォートされて返ってくる -print "Hello&_from&_UNIXSocket!"&n 値の中の - は全部 &- → 生の

    -print / -error は 本物の返信行だけ なので その行が来た時点で読むのをやめてよい 「接続が閉じるまで待つ」だとエラー時に待たされる(server-return-error は返信前に 2 秒 sit-for し返 信後も 5 秒接続を開けたまま) 13
  9. TCP サーバーにする Unix ソケットではコンテナからホストマシンにアクセスできない → TCP を 使う (setq server-use-tcp

    t) (setq server-host "0.0.0.0") (server-start) Emacs 起動後に ~/.emacs.d/server/server に接続情報が出力される 127.0.0.1:54321 68800 <64 > 文字の認証キー 15
  10. Ruby の TCPSocket で address, auth_key = File.read(server_file).split("\n", 2) host,

    port = address.split(" ").first.split(":") TCPSocket.open(host, Integer(port)) do |sock| sock.write("-auth #{auth_key.chomp} -eval #{quote(lisp)}\n") # Unix end 返信の読み方は ソケットと同じ Unix ソケットとの違いは -auth <key> が前に付くだけ コンテナにはアドレスをホストに向けたサーバーファイルをマウントして渡す(bin/emacs-server-file-fordocker) 16
  11. Ruby から Emacs を操作する 3 つの方法 方法 必要なもの 届く範囲 system("emacsclient",

    ...) emacsclient 同じマシン UNIXSocket Ruby だけ 同じマシン TCPSocket + -auth Ruby だけ コンテナ・別マシン 17
  12. emacs_debug require "emacs_debug" EmacsDebug.debug(order) begin checkout(order) rescue => e EmacsDebug.open_error(e)

    end # 呼んだ行を開いて光らせて *Debug* に中身を表示 # 例外が起きた自分のコードの行に飛ぶ エラーを補足して Emacs 上で表示 Docker 内のパスはホストのパスに変換(EMACS_DEBUG_HOST_ROOT) 19
  13. 中身は今日の話そのもの def eval_lisp(lisp) [SocketClient, TcpClient].each do |client| next unless client.available?

    begin client.eval(lisp); return rescue EmacsServerProtocol::Error # TCP end end end 応答タイムアウトや # Unix ソケット → TCP の順 の接続失敗なら次の経路へ 生成した Lisp を 届く経路で送るだけ。emacsclient はどこにも出てこ ない 光らせ方・開き方は Emacs 側の変数で差し替え可能 20
  14. まとめ emacsclient -e で外から Emacs を 何でも 操作できる(Emacs は素晴 らしい)

    emacsclient は ただのクライアント。プロトコルは 1 行のテキスト Ruby の UNIXSocket / TCPSocket で 簡単に再現できる → バイナリ不要で Docker の中からも操作可能 22