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


【お知らせ】


<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 が出ているようです。
Open command palette での文字入力時に Emacsキーバインドが使えないのは変わっていないようです。
Open File 等のダイヤログボックスのテキスト入力フィールドで Emacsキーバインドが使えないのは、Fakeymacs と組み合わせることで使えるようになります。

【本題】


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) 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 "wslpath -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)で開く際には機能しません。(ファイルの先頭にカーソルが位置します。)

7) Emacs から 6) で設定したキーを入力することにより、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 からコールする場合の引数のエスケープ処理を追加した。