TeXでソースコードを"Zennっぽく"貼り付ける.

:::note alert
Zennでも同じ記事を投稿しています.バージョンにズレがある可能性があるのでこちらも合わせてご覧ください.

:::

https://zenn.dev/cyber_hacnosuke/articles/41275710b8f594
# はじめに 
 みなさん.$\TeX$してますか？ 
 $\TeX$してるよってかた,$\TeX$にソースコードは載せたことがありますでしょうか. 
 ソースコードを載せる際によく使われる`lstlisting`ですが,そのままだと殺風景ですし,色も見にくかったり今の時代にそぐわない形になってます.また,シンタックスハイライトが意図したとおりについてくれない場合もあります. 
  
 そこでソースコードを"Zenn"っぽく貼り付けて見ようと思います. 
  
 こちらを元にして書かせていただきました.非常にお世話になりました.ありがとうございます. 
 https://zenn.dev/kyaon/articles/68867e2657e605 
  
 # 学べること 
 この記事では以下のことに触れていきます. 
  
 - シンタックスハイライトの設定方法全般 
 - ハイライト対象の設定 
 - ハイライト書式の設定 
 - 区切り文字の書式設定 
 - `tcolorbox`よる枠の装飾 
 - `lua`言語で$\TeX$を定義する 
  
  
 # 環境 
 `Overleaf`で`luaLaTeX`を使用しています.`luaLaTeX`が必要だと明記しない限り他ののでも構わないと思います.もし,動かないなどあればご連絡ください. 
  

:::note info
 ここでは例として`Python`を貼り付けてみることにします.シンタックスハイライトの設定や予約語の設定はその言語に合わせてください. 
:::
  
 # 色を定義する 
 まずは色を定義してみましょう. 
  
 ```tex 
 \usepackage{color} 
 \usepackage{xcolor} 
  
  
 % 背景色 
 \definecolor{bg}{HTML}{1a2638} 
 % タイトルの背景色 
 \definecolor{titlebg}{HTML}{323e52} 
 % 以下シンタックスハイライト設定 
 % リテラルと演算記号 
 \definecolor{literal}{HTML}{efba69} 
 % 関数/変数の識別子（青） 
 \definecolor{identifier}{HTML}{35b7eb} 
 % コメント 
 \definecolor{comment}{HTML}{818ea0} 
 % 予約語 
 \definecolor{reserved}{HTML}{f88ca0} 
 % 区切り文字 
 \definecolor{delimiter}{HTML}{8a92b6} 
 ``` 
  
 # 言語設定 
 続いて,`Python`の文法を設定に落とし込みます.`mypy`という新しい言語設定として実装していきます. 
  
 ```tex 
 \usepackage{etoolbox} 
 \usepackage{listings} 
  
 % 閉じ括弧バグ修正 
 \makeatletter 
 \patchcmd{\lsthk@SelectCharTable}{\lst@ifbreaklines\lst@Def{`)}{\lst@breakProcessOther)}\fi}{}{}{} 
 \makeatother 
  
 \lstdefinelanguage{mypy}{ 
   % リテラルと演算記号 
   morekeywords=[1]{+=,=,==,!=,!,>,<,>=,<=,++,-,+,*,\%,/}, 
   % 予約語 
   morekeywords=[2]{False,None,True,and,as,assert,async,await,break, 
     class,continue,def,del,elif,else,except,finally,for,from,global, 
     if,import,in,is,lambda,nonlocal,not,or,pass,raise,return,try,while, 
     with,yield,print,match,case 
   }, 
   % 識別子 
   morekeywords=[3]{defined_func}, 
   % 区切り文字を強制的に色付け 
   literate=*{.}{{\color{delimiter}.}}1 
             {,}{{\color{delimiter},}}1 {:}{{\color{delimiter}:}}1 
             {)}{{\color{delimiter})}}1 {(}{{\color{delimiter}(}}1 
             {[}{{\color{delimiter}[}}1 {]}{{\color{delimiter}]}}1 
             {\{}{{\color{delimiter}\{}}1 {\}}{{\color{delimiter}\}}}1, 
   % 大文字小文字を区別 
   sensitive=true, 
   % 行コメントの設定 
   morecomment=[l]{\#}, 
   % Stringリテラルの設定 
   morestring=[b]{\'}, 
   morestring=[b]{\"}, 
   % 単語として扱う文字 
   alsoletter={\%<>=+-*\/1234567890!}, 
   % 枠 
   frame=none, 
   % 長くなったら途中で改行 
   breaklines=true, 
   % 自動改行時のインデント 
   breakindent=12pt, 
   % 文字間隔を一定に 
   columns=fixed, 
   % 文字の横のサイズを小さく 
   basewidth=0.5em, 
   % 行番号を左に 
   numbers=left, 
   % 行番号の書式 
   numberstyle={\scriptsize\color{white}}, 
   % 行番号の増加数は1=連番に 
   stepnumber=1, 
   % フレームの左の余白 
   framexleftmargin=18pt, 
   % スペースを省略せず保持 
   keepspaces=true, 
   % インデントサイズ 
   tabsize=4, 
   backgroundcolor={},                                   % 背景色=透明 
   basicstyle={\small\ttfamily\color{white}},            % 通常部分の書式 
   identifierstyle={\small\color{white}},                % 識別子の書式 
   commentstyle={\small\color{comment}},                 % コメントの書式 
   keywordstyle=[1]{\small\bfseries\color{literal}},     % リテラルと演算記号の書式 
   keywordstyle=[2]{\small\bfseries\color{reserved}},    % 予約語の書式 
   keywordstyle=[3]{\small\bfseries\color{identifier}},  % 自分で定義した識別子の書式 
   stringstyle={\small\ttfamily\color{literal}},         % 文字列の書式 
 } 
 ``` 
  
 なっが！えぇ,申し訳ありませんが長いです.それなりにコメントは付けたつもりですが色んな所から引っ張ってきていますので中には意味をなしてないパラメータもあるかもしれません. 
  
 みなさんがカスタマイズするときのことを考えて3点ほど補足したいと思います. 
 ## morekeywords 
 まず,`listings`はスペースや区切り文字を目印に単語を認識しています.その単語が`morekeywords`に登録されていれば,`keywordstyle`に設定した書式が適応されます.`[1]`や`[2]`は単語の種別です.番号に意味はなくてただのグループ番号です.上のコードでは`[1]`がリテラルと演算記号,`[2]`が予約語,`[3]`が自分で定義した識別子です. 
  
 `listings`は言語の文法を理解することはできません.ので,これが関数定義だ,これは関数の呼び出しだ,と判別することはできません.（私が思いつかないだけでなにか方法があるかもしれないですが.）下の画像を見てください。
  
![Screenshot_20230620-110742.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263233/27ba503c-8adb-4b56-761e-8d570d72d1c7.png)

  
 `defined_func`関数の定義時には青になっていますが,最後の呼び出しは白色のままです.これに対応できませんでした...そこで開き直って「呼び出しでも青くしちゃえばいいじゃん.そっちのほうがわかりやすくない？」ということで,`morekeywords`の`[3]`にハイライトしたい関数名や変数名を入れられるようにしました.何をハイライトするそこはお好みでいいと思います.これが`Zennっぽい`に留まってしまう理由の1つです. 
  
 1つということは当然2つ目もあります.それが数字がハイライトされないということです.`morekeywords`は「単語単位」でハイライトを行いますので`alsoletter`で数字を登録しても実際にすべての数字を単語として`morekeywords`に列挙しなければなりません.後述しますが,`literate`で強制的に変えることもできます.が,変数名の数字やコメントの数字もこれの対象となってしまいます.`luaLaTeX`の方はお手上げですので,ソースコードで使った数字だけでも`morekeywords[1]`に追加してください.ん？`luaLaTeX`だったら？それは後ほどのお楽しみです. 
  
 ともかく,登録した語句を`morekeywords`にそれぞれ適切なところに追加すればハイライトされるようになります. 
  
:::note warn
 `morekeywords`の`{}`内では一切コマンドが展開―つまりは実行されません. 
::: 
  
 ## literate 
 `literate`は強制的に文字を置き換える命令です.`literate=*`の後に 
  
 ```TeX 
 {置き換える文字}{置き換え先}文字数 
 ``` 
  
 を羅列して書くことで置き換えられます.主に`()`や`{}`,`,`といったいわゆる「区切り文字」を登録しています.`;`もこれに当たると思います.ただこれにバグがありまして,`)`,`]`といった閉じ括弧がなぜか登録されません.それを修正しているのが`% 閉じ括弧バグ修正`の部分です.詳しくは参考リンクをご覧ください. 
  
 ## alsoletter 
 `alsoletter`は本来,単語として認識されない文字を認識させるパラメータです.`{}`内のすべての文字が単語を形成するようになり,`morekeywords`に含めることができるようになります. 
  
 # 枠設定 
 先程,背景色を透明にし,枠も設定しておりません.なぜなら枠と背景色は別途`tcolorbox`で作ることで自由度が高くなり角を丸くしたりタイトルをつけることができるようになるからです. 
  
 ```TeX 
 \usepackage{tcolorbox} 
 \tcbuselibrary{breakable, skins} 
  
 \newtcolorbox{zennwt}[2][]{ 
     enhanced,                         %tikzを用いた記法の処理 
     left=22pt,right=22pt,             %box内左右の余白 
     fonttitle=\small,                 %タイトルの書式 
     coltitle=white,                   %タイトルの文字の色 
     colbacktitle=titlebg,             %タイトルの背景の色 
     attach boxed title to top left={},%タイトルを左寄せに,少し微調整 
     boxed title style={               %タイトルボックスの装飾 
       skin=enhancedfirst jigsaw, 
       arc=1.5mm,                      %タイトルボックスの角の弧 
       bottom=0mm, 
       boxrule=0mm 
     }, 
     boxrule=0pt,                      %枠線の太さ 
     colback=bg,                       %本文の背景色 
     colframe=bg,                      %本文の枠の色 
     sharp corners=northwest,          %左上の角の調整 
     breakable,                        %ページ跨ぎOK 
     title=\texttt{#2},                %タイトル 
     arc=2.5mm,                        %角の弧の背景 
     #1 
 } 
  
 \newtcolorbox{zenn}[1][]{ 
     left=22pt,right=22pt, 
     boxrule=0pt, 
     colback=bg 
     colframe=bg, 
     breakable, 
     arc=2.5mm, 
     #1 
 } 
 ``` 
  
 運良く,"Zennっぽい"枠の作り方がありましたのでほとんどパクリです.（参考リンク参照） 
 タイトル付きとタイトル無しを作ってみました.タイトルというのは 
  
 ```TeX:これのことです. 
 ``` 
  
 # 使い方 
 ここまで来たら完成です！すごい量でしたね...早速使ってみましょう. 
  
 ```TeX 
 \begin{zennwt}{test.py} 
 \begin{lstlisting}[language= mypy] 
 # プリミティブ型の例 
 num1 = 10 
 num2 = num1 
  
 print("num1:", num1)  # 出力: num1: 10 
 print("num2:", num2)  # 出力: num2: 10 
  
 num1 = 20 
  
 print("num1:", num1)  # 出力: num1: 20 
 print("num2:", num2)  # 出力: num2: 10 (num2は変更されていない) 
  
 # 参照型の例 
 list1 = [1, 2, 3] 
 list2 = list1 
  
 print("list1:", list1)  # 出力: list1: [1, 2, 3] 
 print("list2:", list2)  # 出力: list2: [1, 2, 3] 
  
 list1.append(4) 
  
 print("list1:", list1)  # 出力: list1: [1, 2, 3, 4] (list1が変更された) 
 print("list2:", list2)  # 出力: list2: [1, 2, 3, 4] (list2も同じ変更が反映されている) 
 \end{lstlisting} 
 \end{zennwt} 
  
 \begin{zenn} 
 \begin{lstlisting}[language= mypy] 
 # 関数の定義 
 def my_func(): 
   print("Hello, World!") 
 \end{lstlisting} 
 \end{zenn} 
 ``` 
  
 # やっぱり数字も含めたいよなぁ！ 
  
:::note warn
 この方法は`luaLaTeX`でしか使用できません. 
::: 
  
 やっぱ数字もハイライトしたいなぁ...手でいちいち入力するのだるいなぁ... 
 わかりますわかります！`luaLaTeX`を使ってるそこのあなたのために頑張っちゃいました！下のように定義丸々を`lua`にやらせばいいんです！`\lstdefinelanguage`の定義全体を以下のように置き換えます. 
  
  
 ```TeX 
 \usepackage{luacode} 
  
 \begin{luacode*} 
 function init() 
   local numlist = "" 
   for i = -100, 1000 do 
     numlist = numlist .. tostring(i) 
     if i < 1000 then 
       numlist = numlist .. "," 
     end 
   end 
   output = [[ 
   \lstdefinelanguage{mypy}{ 
   morekeywords=[1]{+=,=,==,!=,!,>,<,>=,<=,++,-,+,*,\%,/,]]..numlist..[[}, 
   morekeywords=[2]{False,None,True,and,as,assert,async,await,break,class,continue,def,del,elif,else,except,finally,for,from,global,if,import,in,is,lambda,nonlocal,not,or,pass,raise,return,try,while,with,yield,print,match,case}, 
   morekeywords=[3]{defined_func}, 
   literate=*{.}{{\color{delimiter}.}}1 
             {,}{{\color{delimiter},}}1 {:}{{\color{delimiter}:}}1 
             {)}{{\color{delimiter})}}1 {(}{{\color{delimiter}(}}1 
             {[}{{\color{delimiter}[}}1 {]}{{\color{delimiter}]}}1 
             {\{}{{\color{delimiter}\{}}1 {\}}{{\color{delimiter}\}}}1, 
   sensitive=true, 
   morecomment=[l]{\#}, 
   morestring=[b]{\'}, 
   morestring=[b]{\"}, 
   alsoletter={\%<>=+-*\/1234567890!}, 
   frame=none, 
   breaklines=true, 
   breakindent=12pt, 
   columns=fixed, 
   basewidth=0.5em, 
   numbers=left, 
   numberstyle={\scriptsize\color{white}}, 
   stepnumber=1, 
   framexleftmargin=18pt, 
   keepspaces=true, 
   lineskip=-0.1ex, 
   tabsize=4, 
   backgroundcolor={}, 
   basicstyle={\small\ttfamily\color{white}}, 
   identifierstyle={\small\color{white}}, 
   commentstyle={\small\color{comment}}, 
   keywordstyle=[1]{\small\bfseries\color{literal}}, 
   keywordstyle=[2]{\small\bfseries\color{reserved}}, 
   keywordstyle=[3]{\small\bfseries\color{identifier}}, 
   keywordstyle=[4]{\small\bfseries\color{delimiter}}, 
   keywordstyle=[5]{\small\bfseries\color{keyword4}}, 
   stringstyle={\small\ttfamily\color{literal}},} 
   ]] 
   output_ = string.gsub(output, "\n", "") 
   tex.sprint(output_) 
 end 
 \end{luacode*} 
  
 % 実行 
 \directlua{init()} 
  
 ``` 
  
 まず,`-100`から`1000`まで数え上げてそれらすべてを`1,2,3...`という形にします.あとは`[[]]`の中に先程書いた`mypy`の定義を入れて,数え上げた文字列をぶちこみます.このままだと改行が多く,謎のエラーが起こってしまうので`string.gsub`で改行をすべて消し,`tex.sprinr()`で出力します.というような関数`init()`で定義し`\directlua{init()}`でそれを実行しています.出力された文字はあたかも$\TeX$のコードのように振る舞い,$\TeX$のタイプセットがそれを実際に出力内容を実行してくれます. 
  
 当然,`for`の範囲が広くなるとタイプセットが遅くなるという欠点はありますが,それでもいちいち値を入力するのよりはマシです.ものすごく力技ですが全ては`morekeywords`でコマンドが展開しないことが原因です. 
  
 一部のエディタではシンタックスハイライトがイかれる可能性があります. 
  
 #### シンタックスハイライトを整備してたらこっちのハイライトがイかれちゃう. 
  
 ということで本日のオチでした. 
  
 最終的に以下のように表示されます. 
  
![Screenshot_20230619-223618.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263233/a5698923-3aff-6ccf-b353-61a8b5c6eb9e.png)

  
 # まとめ 
 ということで,$TeX$にソースコードを"Zennっぽく"表示してみました.まだまだ改善の余地がありそうですがある程度形になったと思います.これで美文書を制作したらめっちゃ気持ちよさそうですね.では良き$\TeX$ライフを! 
  
 # 参考リンク 
 ほとんどコードはパクらせて頂いて,修正や最適化を行いました.ありがとうございました. 
  
 #### 元ネタ 
  
 https://zenn.dev/kyaon/articles/68867e2657e605 
  
  
 #### `listing`について 
  
 https://e8l.hatenablog.com/entry/2015/11/29/232800 
  
 http://xyoshiki.web.fc2.com/tex/listings.html 
  
 https://whitecat-student.hatenablog.com/entry/2016/09/05/180705 
  
 https://www.reddit.com/r/LaTeX/comments/u98usu/listings_using_literate_to_color_single_symbols/ 
  
 #### 閉じ括弧のバグ修正 
  
 https://tex.stackexchange.com/questions/69472/closing-parenthesis-as-delimiter-not-matched-when-breaklines-true 
  
 #### `tcolorbox`について 
  
 https://texmedicine.hatenadiary.jp/entry/2015/12/17/000339 
  
 #### `lua`について 
  
 https://qiita.com/zr_tex8r/items/8fad42e4b8bad284e984