■ Windows Subsystem for Linux の Emacs で利用できる設定


【お知らせ】


<2021/06/28 追記>
proxy サーバ配下の環境にある PC で Remote-WSL を使った場合、WSL に接続するまでに時間が掛かる問題が発生していました。次のページで紹介してある設定を行ったところ改善しましたので、紹介しておきます。

<2021/02/05 追記>
本ページの内容は WSL2 でも動作します。DrvFs や VolFs の記載がある場合には、Windows 側のファイルシステム、WSL2 側のファイルシステムと読み替えてください。

<2020/09/16 追記>
WSL2 の対策を行いました。(UNC パスのディレクトリで C-u を前置して実行した際、正常に動作するようになります。wslpath2 コマンドを使うように見直ししています。)

<2019/10/31 追記>
code を cmd.exe からコールする場合の引数のエスケープ処理を追加しました。

<2019/10/20 追記>
本設定と逆方向の操作をしたい場合には、次の設定を参考としてください。

<2019/09/10 追記>
本設定は次の情報に基づき、作成しています。

<2019/09/10 追記>
Remote Development に対応しました。

<2019/03/13 追記>
VSCode を Emacsキーバインドにするための新しい Extension、Awesome Emacs Keymap が出ているようです。
Command Palette 内での文字入力時に Emacsキーバインドが使えないのは変わっていないようです。

【本題】


Windows Subsystem for Linux で起動している Emacs から Visual Studio Code でファイルを開くための設定です。

1) Visual Studio Code の Windows版 をインストールする。

2) Remote-SSH を使う場合は、コマンドプロンプトから ssh コマンドが使えることを確認し、さらに Windows と WSL の ssh が同じホスト名で接続できるように設定を行う。(%USERPROFILE%/.ssh/config や ~/.ssh/config の設定を行うことで、ホスト名の略称が使える。)また、接続先と公開鍵認証で接続できるようにし、ssh-agent の設定をすることでパスフレースの入力を省略できるようにする。

※ ssh-agent-wsl を利用すると、Windows 側の ssh-agent に WSL から秘密鍵を登録でき、また Windows の ssh-agent を WSL からも利用できるようになります。

3) Remote-Containers を使う場合は、Docker の設定をし、コンテナを立ち上げておく。

4) 拡張機能 Remote Development をインストールする。

※ Remote-WSL、Remote-SSH、Remote-Containers の機能を最初に利用する際にサーバモジュールがインストールされます。Remote-WSL、Remote-SSH のサーバモジュールは sh -c で起動されるスクリプト内で wget によりインターネットから取得されるため、接続環境によっては .wgetrc にプロキシの設定を行う必要があるようです。

5) PC を一旦ログインしなおす。(VSCode の再起動だけで良いようにも思いますが、念の為)

6) 次のリポジトリの内容を WSL/WSL2 にインストールし、コマンドパスがとおた状態で wslpath2 コマンドが動作するようにする。

7) Emacs を立ち上げ、以下の設定を有効にする。
(defun vscode-cmd-escape (arg)
  (replace-regexp-in-string "[&|<>^\"%]" "^\\&" arg))

(defun vscode-open-command (filename &optional keep-position)
  (interactive)
  (let* ((filename (expand-file-name filename))
         (default-directory "/mnt/c/")
         authority
         target
         command
         filepath)
    (cond ((file-remote-p filename)
           (setq command "cmd.exe /c code")
           (if (file-directory-p filename)
               (setq command (format "%s --folder-uri" command))
             (setq command (format "%s --file-uri" command)))
           (let* ((vec (tramp-dissect-file-name filename))
                  (method (tramp-file-name-method vec))
                  (host (tramp-file-name-host vec))
                  (user (tramp-file-name-user vec))
                  (localname (tramp-file-name-localname vec)))
             (cond ((or (string= method "scp")
                        (string= method "ssh"))
                    (setq authority "ssh-remote")
                    (setq target (if user
                                     (format "%s@%s" user host)
                                   host))
                    (setq filepath (format "vscode-remote://%s+%s%s" authority target localname)))
                   ((string= method "docker")
                    (setq authority "attached-container")
                    (setq dockerid (shell-command-to-string
                                    (format "cmd.exe /c docker container ls --filter 'name=%s' --format '{{.ID}}'"
                                            host)))
                    (when (not (string= dockerid ""))
                      (setq dockerid (substring dockerid 0 -1))
                      (setq target (mapconcat (lambda (x)
                                                (format "%02x" (aref x 0)))
                                              (split-string dockerid "" t) ""))
                      (setq filepath (format "vscode-remote://%s+%s%s" authority target localname))
                      (setq filepath (vscode-cmd-escape filepath))
                      (setq filepath (vscode-cmd-escape filepath)))))))
          (t
           (cond (current-prefix-arg
                  (setq command "cmd.exe /c code")
                  (let ((winpath (shell-command-to-string
                                  (format "wslpath2 -w %s 2> /dev/null"
                                          (shell-quote-argument (file-truename filename))))))
                    (when (not (string= winpath ""))
                      (setq filepath (substring winpath 0 -1))
                      (setq filepath (vscode-cmd-escape filepath))
                      (setq filepath (vscode-cmd-escape filepath)))))
                 (t
                  (setq command "code")
                  (setq filepath filename)))
           (when keep-position
             (setq command (format "%s -g" command))
             (setq filepath (format "%s:%d:%d" filepath (line-number-at-pos) (+ (- (point)
                                                                                   (save-excursion
                                                                                     (beginning-of-line)
                                                                                     (point)))
                                                                                1))))))
    (if (null filepath)
        (message "VSCodeで開くことができません")
      (message (format "%s %s" command filepath))
      (shell-command-to-string (format "%s %s" command (shell-quote-argument filepath))))))

;; dired で開いているディレクトリを開く
(define-key dired-mode-map (kbd "V")
  (lambda ()
    (interactive)
    (save-some-buffers)
    (vscode-open-command (dired-current-directory) nil)))

;; dired でカーソルがある位置のファイルを開く
(define-key dired-mode-map (kbd "C-c v")
  (lambda ()
    (interactive)
    (save-some-buffers)
    (vscode-open-command (dired-get-file-for-visit))))

;; 開いているファイルをカーソルの位置を維持して開く
(global-set-key (kbd "C-c v")
                (lambda ()
                  (interactive)
                  (save-some-buffers)
                  (vscode-open-command buffer-file-name t)))
※ キーの設定は使いやすいように変更してご利用ください。
※ キーから呼ばれるコマンド内で「(save-some-buffers)」を呼んでいます。これは、Emacs で編集中のファイルが VSCode から二重に編集されないようにするための対策です。不要であれば削除してご利用ください。
※ Emacs 開いているファイルを VSCode で開く場合にカーソル位置を維持する機能をサポートしていますが、この機能は tramp での接続先のファイルを VSCode(の Remote-SSH、Remote-Containers)で開く際には機能しません。(ファイルの先頭にカーソルが位置します。)

8) Emacs から 7) で設定したキーを入力することにより、VSCode と連携する。

※ Remote-SSH、Remote-Containers で接続した VSCode を起動したい場合には、Emacs から接続先に tramp で接続し、その状態で設定したキーを入力してください。
※ tramp で Docker に接続するには、 docker-tramp.el が必要です。詳しくは、次のページの<2018/07/20 追記>の内容を参考としてください。
※ ローカルPC上では、数引数(C-u)を付けないで設定したキーを入力すると、Remote-WSL 機能を使って VSCode と連携します。(Windows 10 のバージョン関係なく、DrvFs、VolFs 上のどちらにあるファイルやディレクトリも開けます。)
※ ローカルPC上では、数引数(C-u)を付けて設定したキーを入力すると、Remote-WSL 機能を使わないで VSCode と連携します。(Windows 10 1809 までは、VolFs 上にあるファイルやディレクトリは開けません。)

※ Fakeymacs をインストールすると、Emacs から起動した VSCode との行き来を Alt-o のキーで行うことができるようになります。さらに VSCode に Emacs のキーバインドの機能拡張をインストールしておけば、VSCode のウィンドウを Emacs の一フレームのような感覚で利用することができます。


<変更履歴>
  • 2018/11/26 このページを作成した。
  • 2019/08/23 パスにシンボリックリンクが含まれる場合の対策を行った。
  • 2019/08/23 wslpath 変換時のエラー対策を行った。
  • 2019/08/27 Remote-WSL に対応した。
  • 2019/09/10 Remote-SSH、Remote-Containers に対応した。
  • 2019/10/31 code を cmd.exe からコールする場合の引数のエスケープ処理を追加した。


最終更新:2021年06月28日 11:40