WSL+TeX+VSCode環境で、Remote WSLなしでSyncTeXを使う

# 概要
　WSL上でTeXを使うとWindows上よりも圧倒的に高速にコンパイルできる。その一方、ファイルパスがLinux形式になるため、Windows側のエディタ・ビューワからはSyncTeXが効かなくなってしまう問題があった。これを解決するために、2つの方法を実装した。1つはVSCodeからSyncTeXを実行する際に間にバッチファイルを1つ噛ませる方法で、もう一つはWSL形式で出力されたsynctexファイルをWindows形式に変換する方法である。

# 背景: TeX on WSLとSyncTeX
　WSL (Windows Subsystem for Linux) は、Windows上でLinuxプログラムを動作させるための仮想環境である。Windows 10 x64の バージョン 1903 以降、ビルド 18362 以上では、最新の環境であるWSL2を動作させることができ、さまざまなLinuxディストリビューションが利用可能となっている。
　WSLの利点の1つにTeXがある。なぜかはわからないが、Windows 10上でのTeXの実行速度はLinuxやMacに比べ遅い。しかし、WindowsマシンであってもWSL上でTeXを実行すると圧倒的に高速化することができる。筆者の環境では、A4 約100ページの文章のコンパイルにWindows上のTeX Live 2020で226秒かかっていたものが、同じマシンのWSL上で実行することで76秒まで高速化することができた。
　このようにWSL上のTeXを利用することで高速化が達成できるが、この手法には一つ問題があった。それはSyncTeXが効かなくなるという点である。SyncTeXは、TeX ソースファイルを開いたエディタと PDF ビューワとの間での相互ジャンプを実現するプログラムである。VSCodeであればソース上でCtrl+Alt+Jを押すか、ビューワ上でCtrl+ダブルクリックすることによって、それぞれ対応するPDF/ソースファイル上の所定の位置に移動することができる。この機能は、TeXコンパイル時に.synctex (または.synctex.gz)というファイルにTeXソースとPDFの位置関係を出力し、それを逐次読み取ることによって実現されている。
 WSL上でTeXをコンパイルすると、.synctexファイルに記録されるファイルパス情報が、Windowsの形式(C:\Users\\<ユーザ名>\うんたらかんたら)ではなくLinuxの形式(/mnt/c/Users/<ユーザ名>/...)になる。したがって、通常通りWindows側からSyncTeXを実行しようとしても、ファイルを相互参照できずエラーとなってしまう。
　この問題自体はWSL+TeX+VSCode界隈では知られていたようではある[^1] [^2]が、具体的な解決手順が記載されているようなサイトには残念ながら行きあたらなかった。そこで、試行錯誤の結果、泥縄的ではあるが一応の解決策ができたのでここに記す。

# 解決方針
WSL+TeXでSyncTeXを使うためには下記の方法が考えられる。
1. Remote - WSL を経由してWSL上でVSCodeを実行する
2. 出力された.synctexファイル (.synctex.gzファイル)を解析してLinux形式のファイルパスをWindows形式に変換する
3. VSCode上で実行されるsynctexコマンドをインタラプトし、適切に変換したうえでWSL上のSyncTeXに渡すバッチファイルを作る。

1.の方法についてはいくつかのサイトで報告されていたが、Windows上のVSCodeとRemote WSL上のVSCodeでそれぞれ拡張機能を準備したり、ファイルをWSL側に保存しなければならないなど、いろいろとめんどくさそうであった。一方、WSLにはwslpathというコマンドが用意されており、Windows形式とLinux形式でそれぞれファイルパスを変換できる。これを使えば2か3は実行できそうである。今回、方法2と3で思ったとおりの動作が実現できた。方法3は、synctexを利用するたびにwslpathによるパス変換を複数回繰り返すため、実行速度の点ではやや劣っている。また、SyncTeXの対象となるビューワ（たとえば、LaTeX Workshopの内部ビューワかSumatraPDFか）ごとに設定を切り替えなければならないという欠点がある。2の方法は、TeXビルド時に少し時間がかかるが、一度できてしまえば実行速度は速いうえ、PDFビューワが何だろうが問題なく動作する。そのため現時点でのおすすめは2の方法である。

# 準備:VSCodeからWSL上のTeXでコンパイルするまで
これらの記事が参考になる。
* [[LaTex]VSCodeとWSLで作るLaTex環境構築の備忘録 - Qiita](https://qiita.com/uoyuki/items/c0b3feeb80f9a2699759)
* [Windows Subsystem for LinuxとVSCodeで快適LaTeX - Qiita](https://qiita.com/Engr_Coal33/items/bc1dc187f8b993afc2f9)
* [WSLにセットアップしたLaTeXをVSCodeで使う - Qiita](https://qiita.com/fhill/items/ff94764e03a00428afd8)

具体的な手順の概略は次の通り。
1. WSLをセットアップする。
　　[Microsoftの解説](https://docs.microsoft.com/ja-jp/windows/wsl/install-win10)の「手動インストールの手順」に従ってWSLをセットアップする。筆者はLinuxディストリビューションはUbuntuを使用しているので、これ以降はUbuntuの手順で説明する。
2. UbuntuにTeXをインストールする。1.4GBytesくらい。余裕があれば下記の代わりにtexlive-full (4GBytes)を入れてください。
　　`sudo apt install texlive-language-japanese texlive-science texlive-latex-recommended texlive-latex-extra`
3. Ubuntuから下記コマンドを実行する。
   `sudo kanji-config-updmap-sys auto`
   このコマンドは、`dvipdfmx:warning: CMap has higher supplement number than CIDFont:`のエラーを回避するために https://ntyaan.hatenablog.com/entry/2019/02/17/185244 の記述を参考にした。
4. Windows側のVSCodeからWSL上のTeXを利用するための設定をする。
　すでにVSCodeからWindows上のTeXを実行できているのであれば、settings.jsonを編集してWSL上のTeXを実行するように変更する。具体的には、"latex-workshop.latex.tools"に記載されている"command" (uplatex, pbibtex, dvipdfmx など)をすべて"wsl.exe"に変更し、もともとのコマンドを"args"の一番目に移動させる。（すなわち、wsl uplatex -hogehoge のような形にする）　%DOC% は %DOCFILE%に変更しておく。

ここまでやれば、Windowsに比べ圧倒的に高速なTeXコンパイル環境ができているはずである。しかし、SyncTeXが効かないという当初の問題が発生していることだろう。

# インストール方法
(2021/3/10追記)　現時点でのおすすめは方法2-2である。
## 方法2 WSL形式で出力された.synctexファイルをWindows形式に変換する

### 方法2-1 Python経由でwslpathを動かして.synctex ファイルを変換する
VSCodeのsettings.jsonの"latex-workshop.latex.tools"および"latex-workshop.latex.recipes"に次を追記する。

``` settings.json
"latex-workshop.latex.tools": [
  //WSLでコンパイルしたSyncTeXをWindows形式に変換
  {
    "name": "convwslsynctextowin",
    "command": "python",
    "args": [
      "ConvWSLSyncTeXtoWin.py", "%DOCFILE%"
    ]
  }

// 使いたいレシピのtoolsの最後(dvipdfmxの後ろ)に"convwslsynctextowin"を追加する
"latex-workshop.latex.recipes": [
  {
    "name": "build_thesis",
    "tools": [
       "upLaTeX","hoge","fuga","foo","bar","dvipdfmx","convwslsynctextowin"
    ]
  },
```

また、TeXルートドキュメントとおなじフォルダに次のConvWSLSyncTeXtoWin.pyを置いておく

``` ConvWSLSyncTeXtoWin.py 
import os
import gzip
import subprocess
import sys

argc = len(sys.argv)
print(argc)
if (argc != 2):
    print("Usage: > python "+sys.argv[0]+" %DOCFILE%")
    quit(0)

docfile = sys.argv[1]

if os.path.exists("./"+docfile+".synctex.gz"):
    print ("./"+docfile+".synctex.gz found.")
    encode = True
    with gzip.open("./"+docfile+".synctex.gz", "rt", encoding="utf_8", newline='\n') as fi:
        text = fi.readlines()
elif os.path.exists("./"+docfile+".synctex"):
    print ("./"+docfile+".synctex found.")
    encode = False
    with open("./"+docfile+".synctex", "r", encoding="utf_8", newline='\n') as fi:
        text = fi.readlines()
else:
    print("File not found.")
    quit(0)

outlist = list()
for line in text:
    if ("Input" in line):
        dummy, num, path = line.split(':',2)
        cp = subprocess.run(['wsl', 'wslpath', '-w', path], encoding='utf-8', stdout=subprocess.PIPE)
        if cp.returncode == 0: 
            if cp.stdout[1]== ":":
                drive, path2 = cp.stdout.split(':',1)
                lowdrive = drive.lower()
                newpath = lowdrive+":"+path2
            else :
                newpath = cp.stdout
            buf = "Input:"+num+":"+newpath
            outlist.append(buf)
            print(buf)
    else:
        outlist.append(line)

# 改行コードはLFじゃないとだめ
if encode is True:
    with gzip.open("./"+docfile+".synctex.gz", "wt", encoding="utf_8", newline='\n') as fo:
        fo.writelines(outlist)
else:
    with open("./"+docfile+".synctex", "w", encoding="utf_8", newline='\n') as fo:
        fo.writelines(outlist)
```

## 方法2-2 sedを使って.synctexファイルを編集

この記事をご覧いただいた東京学芸大学の山田陽先生の方法を教えていただきました。なんと、VSCode上のSynctexは、.synctex ファイルに書かれたパスがWindows形式（\区切り）でもUnix形式(/区切り)でもどちらも通してしまう。なので、wslpathを使わずとも、WSLで生成された .synctex ファイル中にある*.texファイルのドライブ表記を、/mnt/c/ から c: に変えるだけでSynctexが通ってしまう。これがsynctexの標準動作かどうかはわからないが、VSCodeのTeX-WorkshopのPDFビューワ、およびSumatraPDFでは問題なく動作したのでここに記す。

まず、VSCodeのsettings.jsonを開き、使いたいTeXエンジン(uplatex, PdfLatex, LuaLaTeXなど)に対応する"latex-workshop.latex.tools"エントリに書いてある -synctexオプションを、"-synctex=-1"のように負の数値に書き換える。これで非圧縮の .synctex ファイルが出力される。

次に、settings.json の "latex-workshop.latex.tools":に次のsedとgzipエントリを追加する。

``` settings.json
    "latex-workshop.latex.tools": [
      {
        "name": "sed",
        "command": "wsl",
        "args": [
          "sed","-i","-r","'s/\\\/mnt\\\/(.)/\\1:/g'","%DOCFILE%.synctex"
        ]
      },
      {
        "name": "gzip",
        "command": "wsl",
        "args": [
          "gzip","%DOCFILE%.synctex"
        ]
      },
    ]
```

また、settings.jsonを開き、"latex-workshop.latex.recipes" の一番使いたいレシピのargsの末尾に、"sed"と"gzip" を追加する。これで、ビルドのたびに*.synctex.gzファイルが出力される。gz圧縮させたくなければ、gzipコマンドは省略してもよい。

## 方法3 3. synctexコマンドをインタラプトするバッチファイル
VSCodeのsettings.jsonに次の二行を追記する。TeXのルートドキュメントがあるフォルダ（*.synctex.gzがあるフォルダ）またはWindowsのPATHが通っているフォルダにsynctex.batを設置する。

``` settings.json
    "latex-workshop.synctex.synctexjs.enabled": false, // VSCodeのLaTeX-workshopのビルトインsynctexは利用しない
    "latex-workshop.synctex.path": "synctex.bat",      // TeXルートドキュメントとおなじフォルダにsynctex.batを置いておく
```

``` synctex.bat
@echo OFF
setlocal

if %1==view (
    call :Viewfunc %1 %2 %3 %4 %5
) else (
    call :Editfunc %1 %2 %3
)
endlocal
exit /b 0

REM Viewfunc
:Viewfunc
for /f "tokens=1-4 delims=:" %%a in ("%3%") do (
    set arg31=%%a
    set arg32=%%b
    set drive=%%c
    set filepath=%%d
)
set wintexpath=%drive%:%filepath%
set winpdfpath=%5
for /f %%i in ('wsl wslpath "%wintexpath%"') do set linuxtexpath=%%i
for /f %%j in ('wsl wslpath "%winpdfpath%"') do set linuxpdfpath=%%j
set arg3=%arg31%:%arg32%:%linuxtexpath%
wsl synctex %1 %2 %arg3% %4 %linuxpdfpath% > hoge.txt
for /f "delims=" %%k in (hoge.txt) do (
    echo %%k | find /i "Output" > nul
    if not ERRORLEVEL 1 (
        call :PdfRevfunc %%k
    ) else echo %%k
)
del hoge.txt
exit /b 0

:PdfRevfunc
for /f "tokens=1-2 delims=:" %%a in ("%1%") do (
    set arg1=%%a
    set linuxpdfpath=%%b
)
for /f %%i in ('wsl wslpath -w "%linuxpdfpath%"') do set winpdfpath=%%i
echo Output:%winpdfpath%
exit /b 0

REM Editfunc
:Editfunc
for /f "tokens=1-5 delims=:" %%a in ("%3%") do (
    set arg31=%%a
    set arg32=%%b
    set arg33=%%c
    set drive=%%d
    set pdfpath=%%e
)
set winpdfpath=%drive%:%pdfpath%
for /f %%i in ('wsl wslpath "%winpdfpath%"') do set linuxpdfpath=%%i
set arg3=%arg31%:%arg32%:%arg33%:%linuxpdfpath%
wsl synctex %1 %2 %arg3% > hoge.txt
for /f "delims=" %%k in (hoge.txt) do (
    echo %%k | find /i "Input" > nul
    if not ERRORLEVEL 1 (
        call :TexRevfunc %%k
    ) else echo %%k
)
del hoge.txt
exit /b 0

:TexRevfunc
for /f "tokens=1-2 delims=:" %%a in ("%1%") do (
    set arg1=%%a
    set linuxtexpath=%%b
)
for /f %%i in ('wsl wslpath -w "%linuxtexpath%"') do set wintexpath=%%i
echo Input:%wintexpath%
exit /b 0
```

筆者がバッチファイルに慣れていないこともあり、いまいちこなれていないソースになっていると思う（中間ファイル吐くところとか）。改善提案とかぜひお寄せいただければ幸いです。

# 利用方法
LateX Workshopの内部ビューワは[pdf.js](https://mozilla.github.io/pdf.js/)を用いているため遅い。軽量化のためにSumatraPDFなどの外部PDFビューワを利用する場合は[こちら](https://qiita.com/hakos/items/7178c9bdd0fbbf8f78db)が参考になる。

## Forward Search
VSCodeのエディタ上でCtrl+Alt+J する。

## Reverse Search
PDFビューワ上でCtrl+ダブルクリックすると実行できる。



# 解説
元気が出たら追記します。少しお待ちください。

[^1]: [[LaTex]VSCodeとWSLで作るLaTex環境構築の備忘録 - Qiita](https://qiita.com/uoyuki/items/c0b3feeb80f9a2699759)
[^2]: [WSL+LaTeX+VScodeにおけるsynctexの利用](https://yoshidate.hatenablog.jp/entry/2019/07/23/005607)