2026年2月7日土曜日

https://github.com/wezterm/wezterm - - weztermターミナルエミュレーターの研究、2回目

hiroppy123@fedora:~/ダウンロード/wezterm-main$ cd wezterm | cargo depgraph | dot -Tsvg > wezterm.svg

hiroppy123@fedora:~/ダウンロード/wezterm-main$ cd wezterm-gui | cargo depgraph | dot -Tsvg > wezterm-gui.svg

hiroppy123@fedora:~/ダウンロード/wezterm-main$ cd mux | cargo depgraph | dot -Tsvg > mux.svg

hiroppy123@fedora:~/ダウンロード/wezterm-main$ cd termwiz | cargo depgraph | dot -Tsvg > termwiz.svg

hiroppy123@fedora:~/ダウンロード/wezterm-main$        ーーーーー      もう少し構造を抽象化して、別の似た名前で保存する方法はありますか?

👉 依存グラフそのものを“構造的に抽象化したい”
という話ですね。これはかなりエンジニア寄りで、良い視点です。Rust大型プロジェクトを読むときに皆ぶつかる壁です。

結論から言うと、cargo depgraphgraphviz だけでも、かなり構造を簡略化できます。


■抽象化の方向は主に4種類あります

① 外部クレートを省く(かなり効果大)

WezTermは依存が大量なので、まずここを削るだけでかなり見やすくなります。

▼内部クレートだけ表示

cargo depgraph --no-extern | dot -Tsvg > internal.svg

👉 これは超おすすめ
「プロジェクト内部の構造だけ」見れます。


② 間接依存を削る(直接関係だけ)

依存関係は普通、

AB → C

でも図では

A → C

みたいに見えてしまうことがあります。

▼直接依存のみ表示

cargo depgraph --dedupe | dot -Tsvg > simplified.svg

③ 特定クレート中心に絞る(視点抽象化)

例えば wezterm-gui の周辺構造だけ見る:

cargo depgraph --root wezterm-gui | dot -Tsvg > gui_focus.svg

これは

👉 「コンポーネント境界を理解する」

ときにかなり使われます。


④ 階層(クラスタ)で抽象化

Graphvizは「グループ化」が出来ます。
少し上級ですが超強力です。

cargo depgraph | dot -Tsvg -Gconcentrate=true > clustered.svg

👉 線をまとめて整理してくれます。


■さらに抽象化したい場合(かなり効く)

ノード形状を単純化

cargo depgraph | dot -Tsvg -Nshape=box > abstract.svg

レイアウトを変えて構造理解を優先

cargo depgraph | dot -Tsvg -Grankdir=LR > horizontal.svg

横方向に並びます。
巨大Rustプロジェクトではかなり見やすくなります。


■実務的に一番よく使われる組み合わせ

WezTermみたいな規模なら👇

cargo depgraph --no-extern --dedupe \ | dot -Tsvg -Grankdir=LR -Gconcentrate=true \ > architecture.svg

これかなり「設計図っぽい」見え方になります。


■さらに上級:レイヤー構造として抽象化

Rust界隈ではよく、

GUI層 ↓ MUX層 ↓ Terminal Engine ↓ Core

みたいに概念レイヤーで理解します。

これをやる場合は、

👉 grepsedでノードをまとめる
👉 graphvizクラスタを手書きする

などになります。

(ここはちょっと研究寄り)


■そもそもWezTerm構造を抽象化すると

実はかなり綺麗で、

wezterm-gui → UI mux → セッション管理 termwiz → 端末描画エンジン wezterm → コア

Rust界隈ではこういう

👉 「責務分離型クレート設計」

はかなり理想的と言われます。


■もし「もっと抽象化した図」が欲しいなら

depgraphは「依存構造」なので、

設計レベル抽象ならむしろ

👉 cargo metadata
👉 rust-analyzer
👉 manually layered diagram

の方が向いています。


ちょっと確認させてください(ここすごく重要なので1個だけ)。

👉 あなたがやりたい抽象化はどっち寄りですか?

A:図を見やすくしたい(視覚整理)

B:アーキテクチャ理解したい(設計理解)

実はこの2つ、最適なツールや手法が全然変わるんですよ。

はい、依存関係グラフをより抽象化して保存する方法がいくつかあります。以下のようなアプローチが考えられます:

1. ワークスペース全体の依存関係を1つのグラフに

bash
# プロジェクトルートで実行
cargo depgraph --all-features --workspace-only | dot -Tsvg > workspace-deps.svg

2. 依存関係の深さを制限して簡略化

bash
cd wezterm
cargo depgraph --depth 2 | dot -Tsvg > wezterm-shallow.svg

3. 外部クレートを除外してプロジェクト内部の構造のみ表示

bash
cargo depgraph --workspace-only | dot -Tsvg > internal-structure.svg

4. 特定の依存関係のみをフィルタリング

bash
# 例: tokio関連の依存のみ
cargo depgraph | grep -E "tokio|async" | dot -Tsvg > async-deps.svg

5. より読みやすいレイアウトで保存

bash
# 左から右へのレイアウト
cargo depgraph | dot -Tsvg -Grankdir=LR > wezterm-lr.svg

# より詳細な情報を含める
cargo depgraph --all-features | dot -Tsvg > wezterm-detailed.svg

6. テキスト形式で階層構造を保存

bash
cargo tree > dependency-tree.txt
cargo tree --workspace > workspace-tree.txt

これらの方法で、異なる抽象度やフォーカスの依存関係図を作成できます。特に --workspace-only オプションは、外部クレートを除外してプロジェクト内部の構造を明確にするのに便利です。

これらのコマンドをより抽象化し、似た名前で保存する方法は以下の通りです:

方法1: シェルスクリプト化

bash
#!/bin/bash
# generate_depgraphs.sh

BASE_DIR="$PWD"
PROJECTS=("wezterm" "wezterm-gui" "mux" "termwiz")

for project in "${PROJECTS[@]}"; do
    if [ -d "$project" ]; then
        cd "$project"
        cargo depgraph | dot -Tsvg > "${BASE_DIR}/${project}_deps.svg"
        cd "$BASE_DIR"
        echo "Generated: ${project}_deps.svg"
    else
        echo "Warning: $project directory not found"
    fi
done

方法2: 関数定義

bash
# 関数を定義
gen_depgraph() {
    local project="$1"
    local suffix="${2:-_deps.svg}"
    
    if [ -d "$project" ]; then
        (cd "$project" && cargo depgraph | dot -Tsvg > "../${project}${suffix}")
        echo "Generated: ${project}${suffix}"
    else
        echo "Error: $project directory not found"
        return 1
    fi
}

# 使用例
gen_depgraph wezterm _dependency_graph.svg
gen_depgraph wezterm-gui
gen_depgraph mux _dependencies.svg
gen_depgraph termwiz

方法3: ワンライナー(ループ版)

bash
for p in wezterm wezterm-gui mux termwiz; do (cd "$p" && cargo depgraph | dot -Tsvg > "../${p}_depgraph.svg"); done

方法4: Makefileを使う

makefile
# Makefile
PROJECTS := wezterm wezterm-gui mux termwiz
SVG_FILES := $(addsuffix _dependency_graph.svg, $(PROJECTS))

.PHONY: all clean

all: $(SVG_FILES)

%_dependency_graph.svg: %
	cd $< && cargo depgraph | dot -Tsvg > ../$@

clean:
	rm -f *.svg

方法5: パターンを使った柔軟な命名

bash
#!/bin/bash
# 動的に命名パターンを変更

TIMESTAMP=$(date +%Y%m%d_%H%M%S)
PREFIX="depgraph"
SUFFIX="diagram.svg"

for project in wezterm wezterm-gui mux termwiz; do
    if [ -d "$project" ]; then
        output_name="${PREFIX}_${project}_${TIMESTAMP}.${SUFFIX}"
        # または: output_name="${project}_architecture.svg"
        # または: output_name="deps_${project}.svg"
        
        (cd "$project" && cargo depgraph | dot -Tsvg > "../$output_name")
        echo "Generated: $output_name"
    fi
done

実行方法

最もシンプルな方法(方法3)なら:

bash
for p in wezterm wezterm-gui mux termwiz; do 
    (cd "$p" && cargo depgraph | dot -Tsvg > "../${p}_deps.svg")
done

ファイル名のパターン例:

  • wezterm_deps.svg → wezterm_dependency_graph.svg

  • wezterm_arch.svg → wezterm_architecture.svg

  • wezterm_modules.svg → wezterm_module_deps.svg

  • depgraph_wezterm.svg → 接頭辞を追加

お好みの命名規則に合わせて調整してください。

https://github.com/wezterm/wezterm - - weztermターミナルエミュレーターの研究

deps.svg
mermaid-diagram.svg
termwiz.svg
diagramusvg01.html
diagramusvg02.html
diagramusvg03.html

そのMermaid記法で書かれたコード、非常に整理されていて素晴らしいですね!Spring Bootアプリケーションの多層アーキテクチャ(MVC + Service/Repository)が明確に定義されています。

この「テキスト形式の設計図」を、視覚的な図(ダイアグラム)として出力・管理するためのおすすめの方法を、目的別にいくつか紹介します。


1. 手軽さ重視:オンラインエディタで即座に表示

最も早く、インストール不要で図式化できる方法です。

  • Mermaid Live Editor: 貼り付けるだけで即座に図が表示されます。

    • メリット: SVGやPNGとして書き出せるほか、URLを共有するだけで他の人も図を見ることができます。

    • やり方: サイトを開き、左側のエディタにあなたのコードをペーストするだけです。

2. 開発効率重視:VS Code エクステンション

コードを書きながらリアルタイムで図を確認したい場合に最適です。

  • Markdown Preview Mermaid Support: VS CodeのMarkdownファイル(.md)内にあなたのコードを貼り付けると、プレビュー画面に図が表示されます。

  • Mermaid Editor: .mmd.mermaid という拡張子で保存し、専用のエディタで編集・プレビューできます。

3. ドキュメント管理重視:GitHub / Notion

あなたのコードにはGitHubのリンクが含まれていますが、これらはツール上で直接レンダリングされます。

  • GitHub: リポジトリ内の README.md に、以下のようにバッククォート3つと mermaid 指定で貼り付けてください。

    Markdown
    ```mermaid
    (ここにあなたのコードを貼る)
    
    これだけで、GitHub上で美しい図が表示されます。
    
  • Notion: /code ブロックを作成し、言語設定を「Mermaid」にするだけで図式化されます。


さらに設計をブラッシュアップするためのヒント

現在の図は「構造」が非常に分かりやすいですが、より「設計図」としての精度を上げるなら、以下の要素を検討してみても面白いかもしれません。

A. シーケンス図の併用

現在の「フローチャート」は静的な構造(どの部品がどこにあるか)を示していますが、**「データがどう流れるか」**という時間軸の動きには Sequence Diagram が向いています。

B. 外部連携の明示

データベース以外に、例えば外部APIや認証プロバイダ(OAuth等)がある場合は、それらを別の subgraph として定義すると、システムの境界線がより明確になります。

C. 凡例(Legend)の追加

クラス定義(classDef)で色分けされていますが、図の隅に「青=Controller」「オレンジ=Service」といった凡例を subgraph で作っておくと、初見の人への親切心が高まります。


次にお手伝いできることはありますか? 例えば、このフローチャートを元に「特定の機能(記事投稿など)に絞ったシーケンス図」のコードを作成することも可能です。いかがいたしましょうか?

https://github.com/wezterm/wezterm/        ーーーーー      このリポジトリのマーメイド図やシーケンス図を生成し、理解を得る方法はありますか?

WezTermのような巨大なRustリポジトリ(20万行以上のコード)の構造を理解するために、Mermaid図やシーケンス図を生成・活用する非常に効果的な方法がいくつかあります。

手動で書くのは大変ですが、**「AIに書かせる」「ツールで自動生成する」「既存のドキュメントを探す」**の3つのアプローチが現実的です。


1. AIツールを使って「リポジトリ全体」を可視化する

最近では、GitHubリポジトリのURLを入力するだけで、ディレクトリ構造やコンポーネントの依存関係をMermaid図にしてくれるサービスが登場しています。

  • GitDiagram: GitHubのURL(github.com/wezterm/wezterm)の hubdiagram に書き換えて gitdiagram.com/wezterm/wezterm にアクセスするだけで、リポジトリの構造をインタラクティブな図として表示してくれます。

    • メリット: 全体のファイル構成や依存関係がひと目で分かり、Mermaid形式での書き出しも可能です。

  • [Grok (X) や Claude 3.5 Sonnet / GPT-4o]: リポジトリの特定のファイル(例えば wezterm-gui/src/main.rs など)の内容をコピー&ペーストし、「このコードの処理フローをMermaidのシーケンス図で書いてください」と依頼するのが最も正確な図を得る近道です。

2. Rust特有のツールで「依存関係」を自動生成する

WezTermは多くの自作クレート(ライブラリ)に分割されています。これらをMermaid図にするには、Rustのビルドツールを活用します。

  • cargo modules を使う: プロジェクト内のモジュール構造を可視化するツールです。

    Bash
    cargo modules generate graph --layout neato | dot -Tsvg > structure.svg
    

    (※WezTermは巨大なため、特定ディレクトリに絞って実行するのがコツです)

  • cargo deps を使う: Cargo.toml の依存関係をGraphviz形式で出力し、それをMermaidに変換することで、WezTermがどのライブラリ(window, termwiz, config など)に依存しているかを図式化できます。

3. WezTerm内部の主要コンポーネントを理解するための「設計図」

WezTermを理解するために、まず以下のような「構成図(Mermaid)」を頭に入れておくと解析がスムーズになります。

コード スニペット
graph TD
    subgraph Frontend
        CLI[wezterm CLI]
        GUI[wezterm-gui]
        MuxClient[Multiplexer Client]
    end

    subgraph Core_Logic
        Config[wezterm-config / Lua]
        Term[termwiz: Terminal Emulation]
        MuxServer[wezterm-mux-server]
    end

    subgraph OS_Interaction
        Window[wezterm-font / Windowing]
        PTY[portable-pty]
    end

    GUI --> Config
    GUI --> Term
    GUI --> Window
    Term --> PTY
    CLI --> MuxClient
    MuxClient --> MuxServer

4. シーケンス図で「起動フロー」を理解する

WezTermがどのように立ち上がるかを知るには、以下のシーケンスを参考にソースを追うのが効率的です。

  1. wezterm-gui/src/main.rs: エントリポイント

  2. wezterm-config: Lua設定ファイルの読み込み

  3. portable-pty: PTY(擬似端末)のオープン

  4. termwiz: エスケープシーケンスの解析

おすすめの学習手順

  1. README.mdと公式ドキュメント(wezfurlong.org)を読む: WezTermはドキュメントが非常に充実しており、設計思想が詳しく書かれています。

  2. Cargo.toml[workspace] を見る: どのディレクトリがどの役割(フォント、PTY、GUI)を担っているか、分割の単位がわかります。

  3. 特定のフローに絞ってAIに図解させる: 「キー入力が画面に描画されるまでのシーケンス図を書いて」など、目的を絞るのがコツです。

何か特定の機能(例:Lua設定の反映フロー、レンダリングの仕組みなど)について詳しく知りたい場合は、そこを重点的に図解することも可能です。

hiroppy
hiroppy123@fedora:~/ダウンロード/wezterm-main$ cargo modules generate graph --layout neato | dot -Tsvg > structure.svg
error: unexpected argument 'generate' found

Usage: cargo-modules 

For more information, try '--help'.
hiroppy123@fedora:~/ダウンロード/wezterm-main$
ーーーーー     これはどうしたら良いですか?

あー、これ系は cargo-modules のバージョン差異あるある ですね。
結論から言うと、そのコマンドは古い書き方で、今の cargo-modules ではサブコマンドが変わっています。

落ち着いて直せばすぐ動きます 👍


■ エラーの意味

error: unexpected argument 'generate' found

つまり

👉 generate というサブコマンドは存在しない
と言われています。


■ 正しいコマンド(現在のcargo-modules)

今のバージョンでは generate は廃止されていて、

structure

を使います。


✅ 修正版コマンド

cargo modules structure --layout neato --format dot | dot -Tsvg > structure.svg

■ それぞれ何をしているか

cargo modules structure

Rustプロジェクトの

👉 モジュール構造を解析


--layout neato

Graphvizのレイアウト指定

主な種類

レイアウト 特徴
dot 階層型(王道)
neato ネットワーク型
fdp ばらけた配置
circo 円形

--format dot

Graphviz用のDOT形式で出力


dot -Tsvg

DOT → SVG変換


■ もし動かない場合のチェック

① cargo-modulesのバージョン確認

cargo modules --version

② Graphviz入っているか確認

dot -V

無ければ

sudo dnf install graphviz

■ weztermでこの作業をする意味(ちょい業界話)

WezTermみたいなRustプロジェクトは

👉 モジュールが巨大
👉 非同期処理が複雑
👉 UI層とバックエンドが混在

なので、

構造図を自動生成する文化が強いです。

Rust界隈は特に

「コード読む前に構造を見る」

という思想が強いですね。


■ Rust開発者の裏話

実は多くのRust開発者は

cargo modules cargo tree cargo expand

この三種の神器でコード理解してます。


■ さらに見やすくする裏ワザ

SVG生成後👇

xdg-open structure.svg

でブラウザ表示できます。


■ もし構造がぐちゃぐちゃになる場合

巨大プロジェクトではよくあります。

フィルタ可能です👇

cargo modules structure --lib

または

cargo modules structure --package wezterm

■ Rust界隈のちょっと面白い文化

Rustは

👉 型安全が強すぎる
👉 モジュール分割が細かい

ので、

「設計図を書かないと読めない言語」

と言われることがあります。

逆に言うと
MermaidやGraphvizと相性がめちゃ良いんです。


■ まず試してほしい最短コマンド

これコピペでOKです👇

cargo modules structure --layout neato --format dot | dot -Tsvg > structure.svg

もしよければちょっと教えてください。

👉 weztermの構造を調べたい目的は
・学習目的
・改造したい
・後継ターミナルを作りたい

どのあたりですか?

目的によって「見るべきモジュール」が結構変わるので、そこ一緒に整理できます。Rustの巨大コード読むの、最初かなり沼だけど、ハマると楽しいですよ。

hiroppy123@fedora:~/ダウンロード/wezterm-main$ cargo modules structure --layout neato --format dot | dot -Tsvg > structure.svg
error: unexpected argument '--layout' found

Usage: cargo-modules structure [OPTIONS]

For more information, try '--help'.
hiroppy123@fedora:~/ダウンロード/wezterm-main$ 


お、かなり惜しいところまで来ています 👍
これは cargo-modulesの仕様変更(かなり頻繁に変わるツール) に当たっています。

安心してください。壊れているわけではなく、オプションの渡し方が変わっただけです。


■ なぜエラーが出たのか

unexpected argument '--layout'

つまり

👉 cargo modules 側に --layout は存在しない

です。


■ 実はレイアウト指定は Graphviz 側の役目

昔のサンプル記事では

cargo modules generate graph --layout neato

みたいに書いてありますが、

現在は

👉 cargo-modules は「DOTを吐くだけ」
👉 レイアウトは Graphviz が担当

という設計に変わりました。


■ ✅ 正しい現在の書き方

まずDOTを出力

cargo modules structure --format dot > structure.dot

次にGraphvizで描画

neato -Tsvg structure.dot > structure.svg

■ 1行で書くなら

cargo modules structure --format dot | neato -Tsvg > structure.svg

■ Graphvizレイアウト解説(雑学)

Graphvizは実はかなり歴史が古く、

👉 AT&Tベル研究所生まれ(1990年代)

UNIX文化ど真ん中のツールです。


主なレイアウト

⭐ dot(王道)

dot -Tsvg structure.dot > structure.svg

特徴
→ 階層型
→ 依存関係を見るのに最強


⭐ neato

neato -Tsvg structure.dot > structure.svg

特徴
→ ネットワーク型
→ 見た目が綺麗


⭐ fdp

fdp -Tsvg structure.dot > structure.svg

特徴
→ 巨大プロジェクト向け


⭐ circo

circo -Tsvg structure.dot > structure.svg

特徴
→ 円形配置
→ 依存ループ検出に便利


■ WezTermみたいな巨大Rustでおすすめ

個人的には👇

dot

が一番理解しやすいです。


■ なのでまずはこれ試してください

cargo modules structure --format dot | dot -Tsvg > structure.svg

■ 表示

xdg-open structure.svg

■ もし構造が巨大すぎたら

WezTermはかなりモジュール多いです。

絞れます👇


libだけ

cargo modules structure --lib --format dot

package単位

cargo modules structure --package wezterm --format dot

■ Rust業界の裏話

Rustコミュニティは

👉 「構造を可視化してから読む」

文化がかなり強いです。

GoやPythonよりも

  • 依存関係

  • trait構造

  • async設計

が複雑なので、図がほぼ必須なんです。


■ ちなみに…

WezTermはRust界でも

👉 かなり設計が綺麗
👉 非同期設計の教材

としてよく研究されています。


もし良ければちょっと聞いてもいいですか?

WezTermを見ている理由って

  • Rust学習

  • ターミナル開発

  • GUI設計

  • 非同期処理研究

どれ寄りです?

ここわかると、「どのモジュールから読むべきか」かなりピンポイントで案内できます。

hiroppy123@fedora:~/ダウンロード/wezterm-main$ cargo modules structure --format dot | neato -Tsvg > structure.svg
error: unexpected argument '--format' found

Usage: cargo-modules structure [OPTIONS]

For more information, try '--help'.
hiroppy123@fedora:~/ダウンロード/wezterm-main$ 

いいところに気付きました。これ、さらにcargo-modulesのバージョン差に当たっています。Rustツール界隈は本当にここがコロコロ変わるので、正直みんな一回はハマるやつです 😅
落ち着いて「今あなたの環境の書き方」に合わせれば解決できます。


■ 今起きていること

unexpected argument '--format'

つまり

👉 あなたの cargo-modules では
--format も存在しない

という状態です。


■ 実は最新版は「標準出力が既にDOT」

最近のcargo-modulesは設計が変わって、

structure → そのままDOTを出力

になっています。


■ ✅ まず試す最短コマンド

これだけでOKの可能性が高いです。

cargo modules structure | dot -Tsvg > structure.svg

■ もし dot が見づらかったら

cargo modules structure | neato -Tsvg > structure.svg

■ そのあと表示

xdg-open structure.svg

■ そもそも仕様確認(超大事)

Rustツールは記事より

👉 --help が真実

です。

一度これを見てください。

cargo modules structure --help

■ なぜこうなるのか(Rust界隈あるある)

cargo-modulesは実は

👉 個人メンテ寄りツール
👉 CLI仕様が結構変わる

Python界隈でいう

pipツール系

と似たポジションです。


■ Graphvizとの役割分担(理解すると楽)

cargo-modules

👉 Rustコード解析
👉 依存関係をDOT形式で出力


Graphviz

👉 DOTを図にする


つまりパイプ処理は

Rust構造 → DOT → SVG

■ もしエラーが続く場合の確認

cargo-modulesバージョン

cargo modules --version

Graphviz存在確認

dot -V

■ WezTerm分析ちょい裏話

WezTermってRust界では

👉 非同期設計の教材
👉 クロスプラットフォームUI教材
👉 GPUレンダリング教材

として結構有名なんです。

特に

wezterm-gui wezterm-client termwiz

この辺が面白いです。


■ 巨大すぎて見づらい場合(かなり重要)

WezTermは規模が大きいので、まず絞ると理解しやすいです。

例えば👇

cargo modules structure --lib

■ Rust開発者がよくやる読み方

① modules構造を見る
② cargo tree で依存確認
③ 気になるcrateだけ読む

この順番が鉄板です。


■ 次にもしよければ教えてください

structure.svgは作れそうですか?

もし出来たら、

👉 見たとき「ごちゃごちゃ」で困る
👉 どこから読むか分からない

ここがほぼ全員の悩みなので、そこを一緒に整理できます。

WezTermは最初の入口だけ乗り越えると、かなり良いRust教材になりますよ。

hiroppy123@fedora:~/ダウンロード/wezterm-main$ cargo modules structure | neato -Tsvg > structure.svg
Error: Multiple packages present in workspace,
please explicitly select one via --package flag.

Packages present in workspace:
- async_ossl
- base91
- wezterm-bidi
- generate-bidi
- bintree
- codec
- wezterm-color-types
- config
- wezterm-config-derive
- cairo-sys-rs
- fontconfig
- freetype
- harfbuzz
- env-bootstrap
- filedescriptor
- frecency
- lfucache
- battery
- color-funcs
- filesystem
- logging
- mux-lua
- plugin
- procinfo-funcs
- serde-funcs
- share-data
- spawn-funcs
- ssh-funcs
- termwiz-funcs
- time-funcs
- url-funcs
- window-funcs
- luahelper
- mux
- procinfo
- promise
- portable-pty
- rangeset
- ratelim
- strip-ansi-escapes
- sync-color-schemes
- tabout
- wezterm-term
- termwiz
- umask
- vtparse
- wezterm
- wezterm-blob-leases
- wezterm-cell
- wezterm-char-props
- wezterm-client
- wezterm-dynamic
- wezterm-dynamic-derive
- wezterm-escape-parser
- wezterm-font
- wezterm-gui
- wezterm-gui-subcommands
- wezterm-input-types
- wezterm-mux-server
- wezterm-mux-server-impl
- wezterm-open-url
- wezterm-ssh
- wezterm-surface
- wezterm-toast-notification
- wezterm-uds
- wezterm-version
- window

hiroppy123@fedora:~/ダウンロード/wezterm-main$ xdg-open structure.svg

おぉ、ここまで来たのめちゃ良いです。
完全に正常なエラーなので安心してください 👍
むしろ WezTerm を解析しようとしている人が必ず通る関門です。


■ 何が起きているのか

エラーの核心はこれです👇

Multiple packages present in workspace

つまり

👉 WezTermは「単一プロジェクト」じゃない
👉 巨大な Rust workspace(モノレポ)

なんです。


■ Rust workspace とは(雑学)

簡単に言うと

👉 1つの巨大アプリを
👉 複数のcrateに分割

して管理する仕組みです。


例えると

WezTermは

会社全体

で、

表示されている一覧は

部署一覧

みたいなものです。


■ cargo-modulesが怒っている理由

「どの部署を図にするの?」
と聞かれています。


■ ⭐ まず見るべきcrate(超重要)

WezTermを理解するなら、普通はここからです。


✅ GUI本体(最重要)

cargo modules structure --package wezterm-gui | dot -Tsvg > gui.svg

問題の原因 cargo modules structure はデフォルトでANSIカラーコード\033[38;2;...m など)を出力しています。 これを neato にパイプすると、Graphviz がカラーコードを DOT言語の構文として誤認識し、大量の syntax errorsyntax ambiguity 警告が発生しています。


✅ 一番おすすめの解決方法

以下のコマンドを使ってください:

# 最もおすすめ(カラーと装飾を完全に除去)
cargo modules structure --package wezterm-gui --plain | neato -Tsvg > gui.svg

その他の有効な方法(優先度順)

方法 コマンド 説明
1位 --plain 最も安定。装飾・カラー・スタイルをすべて除去
2位 --no-color カラーだけ無効化(少し装飾が残る場合あり)
3位 NO_COLOR=1 環境変数でカラー無効化
4位 strip-ansi 別ツールで後からカラー除去

実践例:

# おすすめ1(これを試してみてください)
cargo modules structure --package wezterm-gui --plain | neato -Tsvg > gui.svg

# または
cargo modules structure --package wezterm-gui --no-color | neato -Tsvg > gui.svg

# 環境変数を使う場合
NO_COLOR=1 cargo modules structure --package wezterm-gui | neato -Tsvg > gui.svg

追加Tips

  • --plain が効かない場合は --no-color を試してみてください
  • まだ警告が出る場合は以下のようにパイプを2段階にする:
    cargo modules structure --package wezterm-gui --plain > structure.dot
    neato -Tsvg structure.dot > gui.svg
  • SVGが生成されたら、ブラウザや eog gui.svg で開いて確認してください。

まずは --plain オプションを試してみてください! これでほとんどの場合、警告が消えて正しいSVGが生成されます。

試してみて、まだエラーが出る場合は教えてください!

これは典型的な「cargo modules structure の出力が Graphviz (neato) に合わない」問題です。 原因はシンプルですが、Rust プロジェクトの可視化で非常によくハマる落とし穴なので、詳しく解説します。

なぜこんな大量のエラーが出るのか(根本原因)

  • cargo modules structureテキストベースの階層ツリーを出力します(例: モジュール名に色付きで pub/private をハイライト)。
  • その出力には ANSI エスケープシーケンス\033[32m など)が大量に混入しています。これは「ターミナルで色を付けるため」の制御コードです。
  • neato -TsvgGraphviz の DOT 言語digraph { ... } のような純粋なテキスト)を期待しています。
  • 結果、neato が「\033[ なんて DOT の構文じゃない!」とパニック → syntax error + 数百行の "badly delimited number '2m'" 警告。

これは cargo-modules の設計Graphviz の期待 のミスマッチです。 (歴史的雑学:ANSI エスケープは 1970 年代の VT100 端末から来ていて、現代の Rust CLI ツールはデフォルトで「ターミナルなら色付け」をします。Graphviz は 1990 年代の AT&T Bell Labs 製で、純テキスト前提です。)

正しい使い方(これでほぼ解決します)

structure ではなく dependencies コマンドを使います。 dependencies内部モジュール依存関係を DOT 言語で直接出力するので、neato/dot と完璧に噛み合います。

 
# 1. 最もシンプル(おすすめ)
NO_COLOR=1 cargo modules dependencies --package wezterm-gui | neato -Tsvg > gui.svg

# 2. もっとクリーンに(外部クレート・関数・トレイトなどを除外してグラフを小さく)
NO_COLOR=1 cargo modules dependencies \
  --package wezterm-gui \
  --no-externs --no-fns --no-sysroot --no-traits --no-types --no-uses \
  | neato -Tsvg > gui.svg

# 3. レイアウトを変更(wezterm-gui は巨大なので neato が詰まりやすい)
#    dot(階層的) / circo(円形) / fdp(力指向)などがおすすめ
NO_COLOR=1 cargo modules dependencies --package wezterm-gui \
  | dot -Tsvg > gui.svg          # 階層が綺麗
# または
NO_COLOR=1 cargo modules dependencies --package wezterm-gui \
  | circo -Tsvg > gui.svg
  • NO_COLOR=1(または NO_COLOR=anything)で ANSI カラー完全無効 → 警告が全部消えます。 (cargo-modules は公式に NO_COLOR をサポートしています。--color never はありませんが、これで十分です。)

さらに洗練したい場合のTips

  • グラフが巨大すぎて SVG がゴミになる → wezterm-gui はモジュール数が異常(数百〜千単位)。 --max-depth 4--focus-on crate::termwindow などで絞ってください。
  • もっと綺麗にしたい → xdot でインタラクティブに確認
     
    NO_COLOR=1 cargo modules dependencies --package wezterm-gui | xdot -
  • 代替ツール(もっとモダンなもの)
    • cargo depgraph + dot(クレート間依存)
    • cargo-modules dependencies の他に cargo graph(旧 cargo-graph)も便利です。

WezTerm 特有の面白ポイント

wezterm-gui は Wez Furlong さんがほぼ一人で書いた巨大 Rust プロジェクトで、 mux(サーバー側)・gui(クライアント側)・configfontwindow などが完全に分離されています。 この構造図を見ると「なぜこんなに速くて安定しているのか」が一目で分かります(シングルプロセス GUI とマルチプロセス mux の設計が美しい)。 昔のターミナル(xterm, iTerm2)は全部一つのプロセスで動いていましたが、WezTerm は「mux を別プロセスに分離」したことで、クラッシュ耐性とパフォーマンスが段違いになりました。

このコマンドで SVG が綺麗に出力されたら、ぜひ見せてください! (特に --no-externs 版は「wezterm 独自のモジュール構造」が一望できるのでおすすめです)

何かまだ警告が出る・グラフが変・もっと絞りたい、などあればすぐに次のコマンドを提案しますよ。 がんばってください!(WezTerm のソース解析、最高に楽しいですよね)


✅ ターミナル処理コア

cargo modules structure --package termwiz | dot -Tsvg > termwiz.svg

✅ 多重セッション管理(tmux的)

cargo modules structure --package mux | dot -Tsvg > mux.svg

✅ CLI本体

cargo modules structure --package wezterm | dot -Tsvg > wezterm.svg

■ どれから読むべきか(Rust研究者ルート)

かなり王道順です👇

① termwiz

👉 ターミナル制御の基礎
👉 ANSI escape解析
👉 vt100処理


② mux

👉 タブ
👉 ペイン
👉 セッション管理


③ wezterm-gui

👉 UI
👉 GPU描画
👉 ウィンドウ管理


■ WezTerm設計が美しい理由(業界話)

WezTermは実は

👉 「ターミナルエンジン」と「UI」を完全分離

しています。

昔のターミナル(xtermなど)は

全部混ざってる

でした。


■ ちなみに開発者の思想

作者 Wez Furlong さんは

元 Facebook / AWS 系エンジニアで、

かなり

👉 分離設計
👉 非同期設計

にこだわっています。


■ まず一番おすすめ

これ実行してみてください👇

cargo modules structure --package termwiz | dot -Tsvg > termwiz.svg xdg-open termwiz.svg

termwizは

「ターミナルとは何か」

が見えるので理解が一気に進みます。


■ もし図が巨大すぎたら

Rust開発者はよくこうします👇

cargo modules structure --package termwiz --lib

■ さらにRust読み解き三種の神器

かなり重要です。


依存関係を見る

cargo tree -p wezterm-gui

実装展開を見る

cargo expand

モジュール構造

cargo modules

■ WezTermが教材として優秀な理由

実はRust界では

👉 async設計
👉 GPU描画
👉 クロスプラットフォーム

全部入りの「実践サンプル」としてかなり評価高いです。


■ ここちょっと気になります(1つだけ)

あなたがWezTermを見ている動機って、

A Rust学習

B ターミナル作りたい

C WezTerm改造したい

D UI / GPU勉強

どれに近いですか?

これで「読む順番」かなり変わるので、そこ合わせてガイドできます。

 
 
// Don't create a new standard console window when launched from the windows GUI.
#![cfg_attr(not(test), windows_subsystem = "windows")]

use crate::customglyph::BlockKey;
use crate::glyphcache::GlyphCache;
use crate::utilsprites::RenderMetrics;
use ::window::*;
use anyhow::{anyhow, Context};
use clap::builder::ValueParser;
use clap::{Parser, ValueHint};
use config::keyassignment::{SpawnCommand, SpawnTabDomain};
use config::{ConfigHandle, SerialDomain, SshDomain, SshMultiplexing};
use mux::activity::Activity;
use mux::domain::{Domain, LocalDomain};
use mux::Mux;
use mux_lua::MuxDomain;
use portable_pty::cmdbuilder::CommandBuilder;
use promise::spawn::block_on;
use std::borrow::Cow;
use std::collections::HashMap;
use std::env::current_dir;
use std::ffi::OsString;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
use termwiz::cell::CellAttributes;
use termwiz::surface::{Line, SEQ_ZERO};
use unicode_normalization::UnicodeNormalization;
use wezterm_bidi::Direction;
use wezterm_client::domain::ClientDomain;
use wezterm_font::shaper::PresentationWidth;
use wezterm_font::FontConfiguration;
use wezterm_gui_subcommands::*;
use wezterm_mux_server_impl::update_mux_domains;
use wezterm_toast_notification::*;

mod colorease;
mod commands;
mod customglyph;
mod download;
mod frontend;
mod glyphcache;
mod inputmap;
mod overlay;
mod quad;
mod renderstate;
mod resize_increment_calculator;
mod scripting;
mod scrollbar;
mod selection;
mod shapecache;
mod spawn;
mod stats;
mod tabbar;
mod termwindow;
mod unicode_names;
mod uniforms;
mod update;
mod utilsprites;

#[cfg(feature = "dhat-heap")]
#[global_allocator]
static ALLOC: dhat::Alloc = dhat::Alloc;

pub use selection::SelectionMode;
pub use termwindow::{set_window_class, set_window_position, TermWindow, ICON_DATA};

#[derive(Debug, Parser)]
#[command(
    about = "Wez's Terminal Emulator\nhttp://github.com/wezterm/wezterm",
    version = config::wezterm_version()
)]
struct Opt {
    /// Skip loading wezterm.lua
    #[arg(long, short = 'n')]
    skip_config: bool,

    /// Specify the configuration file to use, overrides the normal
    /// configuration file resolution
    #[arg(
        long = "config-file",
        value_parser,
        conflicts_with = "skip_config",
        value_hint=ValueHint::FilePath,
    )]
    config_file: Option,

    /// Override specific configuration values
    #[arg(
        long = "config",
        name = "name=value",
        value_parser=ValueParser::new(name_equals_value),
        number_of_values = 1)]
    config_override: Vec<(String, String)>,

    /// On Windows, whether to attempt to attach to the parent
    /// process console to display logging output
    #[arg(long = "attach-parent-console")]
    #[allow(dead_code)]
    attach_parent_console: bool,

    #[command(subcommand)]
    cmd: Option,
}

#[derive(Debug, Parser, Clone)]
enum SubCommand {
    #[command(
        name = "start",
        about = "Start the GUI, optionally running an alternative program [aliases: -e]"
    )]
    Start(StartCommand),

    /// Start the GUI in blocking mode. You shouldn't see this, but you
    /// may see it in shell completions because of this open clap issue:
    /// <https://github.com/clap-rs/clap/issues/1335>
    #[command(short_flag_alias = 'e', hide = true)]
    BlockingStart(StartCommand),

    #[command(name = "ssh", about = "Establish an ssh session")]
    Ssh(SshCommand),

    #[command(name = "serial", about = "Open a serial port")]
    Serial(SerialCommand),

    #[command(name = "connect", about = "Connect to wezterm multiplexer")]
    Connect(ConnectCommand),

    #[command(name = "ls-fonts", about = "Display information about fonts")]
    LsFonts(LsFontsCommand),

    #[command(name = "show-keys", about = "Show key assignments")]
    ShowKeys(ShowKeysCommand),
}

async fn async_run_ssh(opts: SshCommand) -> anyhow::Result<()> {
    let mut ssh_option = HashMap::new();
    if opts.verbose {
        ssh_option.insert("wezterm_ssh_verbose".to_string(), "true".to_string());
    }
    for (k, v) in opts.config_override {
        ssh_option.insert(k.to_lowercase().to_string(), v);
    }

    let dom = SshDomain {
        name: format!("SSH to {}", opts.user_at_host_and_port),
        remote_address: opts.user_at_host_and_port.host_and_port.clone(),
        username: opts.user_at_host_and_port.username.clone(),
        multiplexing: SshMultiplexing::None,
        ssh_option,
        ..Default::default()
    };

    let start_command = StartCommand {
        always_new_process: true,
        class: opts.class,
        cwd: None,
        no_auto_connect: true,
        position: opts.position,
        workspace: None,
        prog: opts.prog.clone(),
        ..Default::default()
    };

    let cmd = if !opts.prog.is_empty() {
        let builder = CommandBuilder::from_argv(opts.prog);
        Some(builder)
    } else {
        None
    };

    let domain: Arc = Arc::new(mux::ssh::RemoteSshDomain::with_ssh_domain(&dom)?);
    let mux = Mux::get();
    mux.add_domain(&domain);
    mux.set_default_domain(&domain);

    let should_publish = false;
    async_run_terminal_gui(cmd, start_command, should_publish).await
}

fn run_ssh(opts: SshCommand) -> anyhow::Result<()> {
    if let Some(cls) = opts.class.as_ref() {
        crate::set_window_class(cls);
    }
    if let Some(pos) = opts.position.as_ref() {
        set_window_position(pos.clone());
    }

    build_initial_mux(&config::configuration(), None, None)?;

    let gui = crate::frontend::try_new()?;

    promise::spawn::spawn(async {
        if let Err(err) = async_run_ssh(opts).await {
            terminate_with_error(err);
        }
    })
    .detach();

    maybe_show_configuration_error_window();
    gui.run_forever()
}

async fn async_run_serial(opts: SerialCommand) -> anyhow::Result<()> {
    let serial_domain = SerialDomain {
        name: format!("Serial Port {}", opts.port),
        port: Some(opts.port.clone()),
        baud: opts.baud,
    };

    let start_command = StartCommand {
        always_new_process: true,
        class: opts.class,
        cwd: None,
        no_auto_connect: true,
        position: opts.position,
        workspace: None,
        domain: Some(serial_domain.name.clone()),
        ..Default::default()
    };

    let cmd = None;

    let domain: Arc = Arc::new(LocalDomain::new_serial_domain(serial_domain)?);
    let mux = Mux::get();
    mux.add_domain(&domain);

    let should_publish = false;
    async_run_terminal_gui(cmd, start_command, should_publish).await
}

fn run_serial(config: config::ConfigHandle, opts: SerialCommand) -> anyhow::Result<()> {
    if let Some(cls) = opts.class.as_ref() {
        crate::set_window_class(cls);
    }
    if let Some(pos) = opts.position.as_ref() {
        set_window_position(pos.clone());
    }

    build_initial_mux(&config, None, None)?;

    let gui = crate::frontend::try_new()?;

    promise::spawn::spawn(async {
        if let Err(err) = async_run_serial(opts).await {
            terminate_with_error(err);
        }
    })
    .detach();

    maybe_show_configuration_error_window();
    gui.run_forever()
}

fn have_panes_in_domain_and_ws(domain: &Arc, workspace: &Option) -> bool {
    let mux = Mux::get();
    let have_panes_in_domain = mux
        .iter_panes()
        .iter()
        .any(|p| p.domain_id() == domain.domain_id());

    if !have_panes_in_domain {
        return false;
    }

    if let Some(ws) = &workspace {
        for window_id in mux.iter_windows_in_workspace(ws) {
            if let Some(win) = mux.get_window(window_id) {
                for t in win.iter() {
                    for p in t.iter_panes_ignoring_zoom() {
                        if p.pane.domain_id() == domain.domain_id() {
                            return true;
                        }
                    }
                }
            }
        }
        false
    } else {
        true
    }
}

async fn spawn_tab_in_domain_if_mux_is_empty(
    cmd: Option,
    is_connecting: bool,
    domain: Option<Arc>,
    workspace: Option,
) -> anyhow::Result<()> {
    let mux = Mux::get();

    let domain = domain.unwrap_or_else(|| mux.default_domain());

    if !is_connecting {
        if have_panes_in_domain_and_ws(&domain, &workspace) {
            return Ok(());
        }
    }

    let window_id = {
        // Force the builder to notify the frontend early,
        // so that the attach await below doesn't block it.
        // This has the consequence of creating the window
        // at the initial size instead of populating it
        // from the size specified in the remote mux.
        // We use the TabAddedToWindow mux notification
        // to detect and adjust the size later on.
        let position = None;
        let builder = mux.new_empty_window(workspace.clone(), position);
        *builder
    };

    let config = config::configuration();
    config.update_ulimit()?;

    domain.attach(Some(window_id)).await?;

    if have_panes_in_domain_and_ws(&domain, &workspace) {
        trigger_and_log_gui_attached(MuxDomain(domain.domain_id())).await;
        return Ok(());
    }

    let _config_subscription = config::subscribe_to_config_reload(move || {
        promise::spawn::spawn_into_main_thread(async move {
            if let Err(err) = update_mux_domains(&config::configuration()) {
                log::error!("Error updating mux domains: {:#}", err);
            }
        })
        .detach();
        true
    });

    let dpi = config.dpi.unwrap_or_else(|| ::window::default_dpi());
    let _tab = domain
        .spawn(
            config.initial_size(dpi as u32, Some(cell_pixel_dims(&config, dpi)?)),
            cmd,
            None,
            window_id,
        )
        .await?;
    trigger_and_log_gui_attached(MuxDomain(domain.domain_id())).await;
    Ok(())
}

async fn connect_to_auto_connect_domains() -> anyhow::Result<()> {
    let mux = Mux::get();
    let domains = mux.iter_domains();
    for dom in domains {
        if let Some(dom) = dom.downcast_ref::() {
            if dom.connect_automatically() {
                dom.attach(None).await?;
            }
        }
    }
    Ok(())
}

async fn trigger_gui_startup(
    lua: Option<Rc>,
    spawn: Option,
) -> anyhow::Result<()> {
    if let Some(lua) = lua {
        let args = lua.pack_multi(spawn)?;
        config::lua::emit_event(&lua, ("gui-startup".to_string(), args)).await?;
    }
    Ok(())
}

async fn trigger_and_log_gui_startup(spawn_command: Option) {
    if let Err(err) =
        config::with_lua_config_on_main_thread(move |lua| trigger_gui_startup(lua, spawn_command))
            .await
    {
        let message = format!("while processing gui-startup event: {:#}", err);
        log::error!("{}", message);
        persistent_toast_notification("Error", &message);
    }
}

async fn trigger_gui_attached(lua: Option<Rc>, domain: MuxDomain) -> anyhow::Result<()> {
    if let Some(lua) = lua {
        let args = lua.pack_multi(domain)?;
        config::lua::emit_event(&lua, ("gui-attached".to_string(), args)).await?;
    }
    Ok(())
}

async fn trigger_and_log_gui_attached(domain: MuxDomain) {
    if let Err(err) =
        config::with_lua_config_on_main_thread(move |lua| trigger_gui_attached(lua, domain)).await
    {
        let message = format!("while processing gui-attached event: {:#}", err);
        log::error!("{}", message);
        persistent_toast_notification("Error", &message);
    }
}

fn cell_pixel_dims(config: &ConfigHandle, dpi: f64) -> anyhow::Result<(usize, usize)> {
    let fontconfig = Rc::new(FontConfiguration::new(Some(config.clone()), dpi as usize)?);
    let render_metrics = RenderMetrics::new(&fontconfig)?;
    Ok((
        render_metrics.cell_size.width as usize,
        render_metrics.cell_size.height as usize,
    ))
}

async fn async_run_terminal_gui(
    cmd: Option,
    opts: StartCommand,
    should_publish: bool,
) -> anyhow::Result<()> {
    let unix_socket_path =
        config::RUNTIME_DIR.join(format!("gui-sock-{}", unsafe { libc::getpid() }));
    std::env::set_var("WEZTERM_UNIX_SOCKET", unix_socket_path.clone());
    wezterm_blob_leases::register_storage(Arc::new(
        wezterm_blob_leases::simple_tempdir::SimpleTempDir::new_in(&*config::CACHE_DIR)?,
    ))?;
    if let Err(err) = spawn_mux_server(unix_socket_path, should_publish) {
        log::warn!("{:#}", err);
    }

    if !opts.no_auto_connect {
        connect_to_auto_connect_domains().await?;
    }

    let spawn_command = match &cmd {
        Some(cmd) => Some(SpawnCommand::from_command_builder(cmd)?),
        None => None,
    };

    // Apply the domain to the command
    let spawn_command = match (spawn_command, &opts.domain) {
        (Some(spawn), Some(name)) => Some(SpawnCommand {
            domain: SpawnTabDomain::DomainName(name.to_string()),
            ..spawn
        }),
        (None, Some(name)) => Some(SpawnCommand {
            domain: SpawnTabDomain::DomainName(name.to_string()),
            ..SpawnCommand::default()
        }),
        (spawn, None) => spawn,
    };
    let mux = Mux::get();

    let domain = if let Some(name) = &opts.domain {
        let domain = mux
            .get_domain_by_name(name)
            .ok_or_else(|| anyhow!("invalid domain {name}"))?;
        Some(domain)
    } else {
        None
    };

    if !opts.attach {
        trigger_and_log_gui_startup(spawn_command).await;
    }

    let is_connecting = opts.attach;

    if let Some(domain) = &domain {
        if !opts.attach {
            let window_id = {
                // Force the builder to notify the frontend early,
                // so that the attach await below doesn't block it.
                let workspace = None;
                let position = None;
                let builder = mux.new_empty_window(workspace, position);
                *builder
            };

            domain.attach(Some(window_id)).await?;
            let config = config::configuration();
            let dpi = config.dpi.unwrap_or_else(|| ::window::default_dpi());
            let tab = domain
                .spawn(
                    config.initial_size(dpi as u32, Some(cell_pixel_dims(&config, dpi)?)),
                    cmd.clone(),
                    None,
                    window_id,
                )
                .await?;
            let mut window = mux
                .get_window_mut(window_id)
                .ok_or_else(|| anyhow!("failed to get mux window id {window_id}"))?;
            if let Some(tab_idx) = window.idx_by_id(tab.tab_id()) {
                window.set_active_without_saving(tab_idx);
            }
            trigger_and_log_gui_attached(MuxDomain(domain.domain_id())).await;
        }
    }
    spawn_tab_in_domain_if_mux_is_empty(cmd, is_connecting, domain, opts.workspace).await
}

#[derive(Debug)]
enum Publish {
    TryPathOrPublish(PathBuf),
    NoConnectNoPublish,
    NoConnectButPublish,
}

impl Publish {
    pub fn resolve(mux: &Arc, config: &ConfigHandle, always_new_process: bool) -> Self {
        if mux.default_domain().domain_name() != config.default_domain.as_deref().unwrap_or("local")
        {
            return Self::NoConnectNoPublish;
        }

        if always_new_process {
            return Self::NoConnectNoPublish;
        }

        if config::is_config_overridden() {
            // They're using a specific config file: assume that it is
            // different from the running gui
            log::trace!("skip existing gui: config is different");
            return Self::NoConnectNoPublish;
        }

        match wezterm_client::discovery::resolve_gui_sock_path(
            &crate::termwindow::get_window_class(),
        ) {
            Ok(path) => Self::TryPathOrPublish(path),
            Err(_) => Self::NoConnectButPublish,
        }
    }

    pub fn should_publish(&self) -> bool {
        match self {
            Self::TryPathOrPublish(_) | Self::NoConnectButPublish => true,
            Self::NoConnectNoPublish => false,
        }
    }

    pub fn try_spawn(
        &mut self,
        cmd: Option,
        config: &ConfigHandle,
        workspace: Option<&str>,
        domain: SpawnTabDomain,
        new_tab: bool,
    ) -> anyhow::Result {
        if let Publish::TryPathOrPublish(gui_sock) = &self {
            let dom = config::UnixDomain {
                socket_path: Some(gui_sock.clone()),
                no_serve_automatically: true,
                ..Default::default()
            };
            let mut ui = mux::connui::ConnectionUI::new_headless();
            match wezterm_client::client::Client::new_unix_domain(None, &dom, false, &mut ui, true)
            {
                Ok(client) => {
                    let executor = promise::spawn::ScopedExecutor::new();
                    let command = cmd.clone();
                    let res = block_on(executor.run(async move {
                        let vers = client.verify_version_compat(&mut ui).await?;

                        if vers.executable_path != std::env::current_exe().context("resolve executable path")? {
                            *self = Publish::NoConnectNoPublish;
                            anyhow::bail!(
                                "Running GUI is a different executable from us, will start a new one");
                        }
                        if vers.config_file_path
                            != std::env::var_os("WEZTERM_CONFIG_FILE").map(Into::into)
                        {
                            *self = Publish::NoConnectNoPublish;
                            anyhow::bail!(
                                "Running GUI has different config from us, will start a new one"
                            );
                        }

                        let window_id = if new_tab || config.prefer_to_spawn_tabs {
                            if let Ok(pane_id) = client.resolve_pane_id(None).await {
                                let panes = client.list_panes().await?;

                                let mut window_id = None;
                                'outer: for tabroot in panes.tabs {
                                    let mut cursor = tabroot.into_tree().cursor();

                                    loop {
                                        if let Some(entry) = cursor.leaf_mut() {
                                            if entry.pane_id == pane_id {
                                                window_id.replace(entry.window_id);
                                                break 'outer;
                                            }
                                        }
                                        match cursor.preorder_next() {
                                            Ok(c) => cursor = c,
                                            Err(_) => break,
                                        }
                                    }
                                }
                                window_id

                            } else {
                                None
                            }
                        } else {
                            None
                        };

                        client
                            .spawn_v2(codec::SpawnV2 {
                                domain,
                                window_id,
                                command,
                                command_dir: None,
                                size: config.initial_size(0, None),
                                workspace: workspace.unwrap_or(
                                    config
                                        .default_workspace
                                        .as_deref()
                                        .unwrap_or(mux::DEFAULT_WORKSPACE)
                                ).to_string(),
                            })
                            .await
                    }));

                    match res {
                        Ok(res) => {
                            log::info!(
                                "Spawned your command via the existing GUI instance. \
                             Use wezterm start --always-new-process if you do not want this behavior. \
                             Result={:?}",
                                res
                            );
                            Ok(true)
                        }
                        Err(err) => {
                            log::trace!(
                                "while attempting to ask existing instance to spawn: {:#}",
                                err
                            );
                            Ok(false)
                        }
                    }
                }
                Err(err) => {
                    // Couldn't connect: it's probably a stale symlink.
                    // That's fine: we can continue with starting a fresh gui below.
                    log::trace!("{:#}", err);
                    Ok(false)
                }
            }
        } else {
            Ok(false)
        }
    }
}

fn spawn_mux_server(unix_socket_path: PathBuf, should_publish: bool) -> anyhow::Result<()> {
    let mut listener =
        wezterm_mux_server_impl::local::LocalListener::with_domain(&config::UnixDomain {
            socket_path: Some(unix_socket_path.clone()),
            ..Default::default()
        })?;
    std::thread::spawn(move || {
        let name_holder;
        if should_publish {
            name_holder = wezterm_client::discovery::publish_gui_sock_path(
                &unix_socket_path,
                &crate::termwindow::get_window_class(),
            );
            if let Err(err) = &name_holder {
                log::warn!("{:#}", err);
            }
        }

        listener.run();
        std::fs::remove_file(unix_socket_path).ok();
    });

    Ok(())
}

fn setup_mux(
    local_domain: Arc,
    config: &ConfigHandle,
    default_domain_name: Option<&str>,
    default_workspace_name: Option<&str>,
) -> anyhow::Result<Arc> {
    let mux = Arc::new(mux::Mux::new(Some(local_domain.clone())));
    Mux::set_mux(&mux);
    let client_id = Arc::new(mux::client::ClientId::new());
    mux.register_client(client_id.clone());
    mux.replace_identity(Some(client_id));
    let default_workspace_name = default_workspace_name.unwrap_or(
        config
            .default_workspace
            .as_deref()
            .unwrap_or(mux::DEFAULT_WORKSPACE),
    );
    mux.set_active_workspace(&default_workspace_name);
    crate::update::load_last_release_info_and_set_banner();
    update_mux_domains(config)?;

    let default_name =
        default_domain_name.unwrap_or(config.default_domain.as_deref().unwrap_or("local"));

    let domain = mux.get_domain_by_name(default_name).ok_or_else(|| {
        anyhow::anyhow!(
            "desired default domain '{}' was not found in mux!?",
            default_name
        )
    })?;
    mux.set_default_domain(&domain);

    Ok(mux)
}

fn build_initial_mux(
    config: &ConfigHandle,
    default_domain_name: Option<&str>,
    default_workspace_name: Option<&str>,
) -> anyhow::Result<Arc> {
    let domain: Arc = Arc::new(LocalDomain::new("local")?);
    setup_mux(domain, config, default_domain_name, default_workspace_name)
}

fn run_terminal_gui(opts: StartCommand, default_domain_name: Option) -> anyhow::Result<()> {
    if let Some(cls) = opts.class.as_ref() {
        crate::set_window_class(cls);
    }
    if let Some(pos) = opts.position.as_ref() {
        set_window_position(pos.clone());
    }

    let config = config::configuration();
    let need_builder = !opts.prog.is_empty() || opts.cwd.is_some();

    let cmd = if need_builder {
        let prog = opts.prog.iter().map(|s| s.as_os_str()).collect::<Vec<_>>();
        let mut builder = config.build_prog(
            if prog.is_empty() { None } else { Some(prog) },
            config.default_prog.as_ref(),
            config.default_cwd.as_ref(),
        )?;
        if let Some(cwd) = &opts.cwd {
            builder.cwd(if cwd.is_relative() {
                current_dir()?.join(cwd).into_os_string().into()
            } else {
                Cow::Borrowed(cwd.as_ref())
            });
        }
        Some(builder)
    } else {
        None
    };

    let mux = build_initial_mux(
        &config,
        default_domain_name.as_deref(),
        opts.workspace.as_deref(),
    )?;

    // First, let's see if we can ask an already running wezterm to do this.
    // We must do this before we start the gui frontend as the scheduler
    // requirements are different.
    let mut publish = Publish::resolve(
        &mux,
        &config,
        opts.always_new_process || opts.position.is_some(),
    );
    log::trace!("{:?}", publish);
    if publish.try_spawn(
        cmd.clone(),
        &config,
        opts.workspace.as_deref(),
        match &opts.domain {
            Some(name) => SpawnTabDomain::DomainName(name.to_string()),
            None => SpawnTabDomain::DefaultDomain,
        },
        opts.new_tab,
    )? {
        return Ok(());
    }

    let gui = crate::frontend::try_new()?;
    let activity = Activity::new();

    promise::spawn::spawn(async move {
        if let Err(err) = async_run_terminal_gui(cmd, opts, publish.should_publish()).await {
            terminate_with_error(err);
        }
        drop(activity);
    })
    .detach();

    maybe_show_configuration_error_window();
    gui.run_forever()
}

fn fatal_toast_notification(title: &str, message: &str) {
    persistent_toast_notification(title, message);
    // We need a short delay otherwise the notification
    // will not show
    #[cfg(windows)]
    std::thread::sleep(std::time::Duration::new(2, 0));
}

fn notify_on_panic() {
    let default_hook = std::panic::take_hook();
    std::panic::set_hook(Box::new(move |info| {
        if let Some(s) = info.payload().downcast_ref::<&str>() {
            fatal_toast_notification("Wezterm panic", s);
        }
        default_hook(info);
    }));
}

fn terminate_with_error_message(err: &str) -> ! {
    log::error!("{}; terminating", err);
    fatal_toast_notification("Wezterm Error", &err);
    std::process::exit(1);
}

fn terminate_with_error(err: anyhow::Error) -> ! {
    let mut err_text = format!("{err:#}");

    let warnings = config::configuration_warnings_and_errors();
    if !warnings.is_empty() {
        let err = warnings.join("\n");
        err_text = format!("{err_text}\nConfiguration Error: {err}");
    }

    terminate_with_error_message(&err_text)
}

fn main() {
    #[cfg(feature = "dhat-heap")]
    let _profiler = dhat::Profiler::new_heap();

    config::designate_this_as_the_main_thread();
    config::assign_error_callback(mux::connui::show_configuration_error_message);
    notify_on_panic();
    if let Err(e) = run() {
        terminate_with_error(e);
    }
    Mux::shutdown();
    frontend::shutdown();
}

fn maybe_show_configuration_error_window() {
    let warnings = config::configuration_warnings_and_errors();
    if !warnings.is_empty() {
        let err = warnings.join("\n");
        mux::connui::show_configuration_error_message(&err);
    }
}

fn run_show_keys(config: config::ConfigHandle, cmd: &ShowKeysCommand) -> anyhow::Result<()> {
    let map = crate::inputmap::InputMap::new(&config);
    if cmd.lua {
        map.dump_config(cmd.key_table.as_deref());
    } else {
        map.show_keys();
    }
    Ok(())
}

pub fn run_ls_fonts(config: config::ConfigHandle, cmd: &LsFontsCommand) -> anyhow::Result<()> {
    use wezterm_font::parser::ParsedFont;

    if let Err(err) = config::configuration_result() {
        log::error!("{}", err);
        return Ok(());
    }

    // Disable the normal config error UI window, as we don't have
    // a fully baked GUI environment running
    config::assign_error_callback(|err| eprintln!("{}", err));

    let font_config = Rc::new(wezterm_font::FontConfiguration::new(
        Some(config.clone()),
        config.dpi.unwrap_or_else(|| ::window::default_dpi()) as usize,
    )?);

    let render_metrics = crate::utilsprites::RenderMetrics::new(&font_config)?;

    let bidi_hint = if config.bidi_enabled {
        Some(config.bidi_direction)
    } else {
        None
    };

    let unicode_version = config.unicode_version();

    let text = match (&cmd.text, &cmd.codepoints) {
        (Some(text), _) => Some(text.to_string()),
        (_, Some(codepoints)) => {
            let mut s = String::new();
            for cp in codepoints.split(",") {
                let cp = u32::from_str_radix(cp, 16)
                    .with_context(|| format!("{cp} is not a hex number"))?;
                let c = char::from_u32(cp)
                    .ok_or_else(|| anyhow!("{cp} is not a valid unicode codepoint value"))?;
                s.push(c);
            }
            Some(s)
        }
        _ => None,
    };

    if let Some(text) = &text {
        // Emulate the effect of output normalization
        let text = if config.normalize_output_to_unicode_nfc {
            text.nfc().collect()
        } else {
            text.to_string()
        };

        let line = Line::from_text(
            &text,
            &CellAttributes::default(),
            SEQ_ZERO,
            Some(&unicode_version),
        );
        let cell_clusters = line.cluster(bidi_hint);
        let ft_lib = wezterm_font::ftwrap::Library::new()?;

        let mut glyph_cache = GlyphCache::new_in_memory(&font_config, 256)?;

        for cluster in cell_clusters {
            let style = font_config.match_style(&config, &cluster.attrs);
            let font = font_config.resolve_font(style)?;
            let presentation_width = PresentationWidth::with_cluster(&cluster);
            let infos = font
                .blocking_shape(
                    &cluster.text,
                    Some(cluster.presentation),
                    cluster.direction,
                    None,
                    Some(&presentation_width),
                )
                .unwrap();

            // We must grab the handles after shaping, so that we get the
            // revised list that includes system fallbacks!
            let handles = font.clone_handles();
            let faces: Vec<_> = handles
                .iter()
                .map(|p| ft_lib.face_from_locator(&p.handle).ok())
                .collect();

            let mut iter = infos.iter().peekable();

            let mut byte_lens = vec![];
            for c in cluster.text.chars() {
                let len = c.len_utf8();
                for _ in 0..len {
                    byte_lens.push(len);
                }
            }
            println!("{:?}", cluster.direction);

            while let Some(info) = iter.next() {
                let idx = cluster.byte_to_cell_idx(info.cluster as usize);
                let followed_by_space = match line.get_cell(idx + 1) {
                    Some(cell) => cell.str() == " ",
                    None => false,
                };

                let text = if cluster.direction == Direction::LeftToRight {
                    if let Some(next) = iter.peek() {
                        line.columns_as_str(idx..cluster.byte_to_cell_idx(next.cluster as usize))
                    } else {
                        let last_idx = cluster.byte_to_cell_idx(cluster.text.len() - 1);
                        line.columns_as_str(idx..last_idx + 1)
                    }
                } else {
                    let info_len = byte_lens[info.cluster as usize];
                    let last_idx = cluster.byte_to_cell_idx(info.cluster as usize + info_len - 1);
                    line.columns_as_str(idx..last_idx + 1)
                };

                let parsed = &handles[info.font_idx];
                let escaped = format!("{}", text.escape_unicode());
                let mut is_custom = false;

                let cached_glyph = glyph_cache.cached_glyph(
                    &info,
                    &style,
                    followed_by_space,
                    &font,
                    &render_metrics,
                    info.num_cells,
                )?;

                let mut texture = cached_glyph.texture.clone();

                if config.custom_block_glyphs {
                    if let Some(block) = info.only_char.and_then(BlockKey::from_char) {
                        texture.replace(glyph_cache.cached_block(block, &render_metrics)?);
                        println!(
                            "{:2} {:4} {:12} drawn by wezterm because custom_block_glyphs=true: {:?}",
                            info.cluster, text, escaped, block
                        );
                        is_custom = true;
                    }
                }

                if !is_custom {
                    let glyph_name = faces[info.font_idx]
                        .as_ref()
                        .and_then(|face| {
                            face.get_glyph_name(info.glyph_pos)
                                .map(|name| format!("{},", name))
                        })
                        .unwrap_or_else(String::new);

                    println!(
                        "{:2} {:4} {:12} x_adv={:<2} cells={:<2} glyph={}{:<4} {}\n{:38}{}",
                        info.cluster,
                        text,
                        escaped,
                        cached_glyph.x_advance.get(),
                        info.num_cells,
                        glyph_name,
                        info.glyph_pos,
                        parsed.lua_name(),
                        "",
                        parsed.handle.diagnostic_string()
                    );
                }

                if cmd.rasterize_ascii {
                    let mut glyph = String::new();

                    if let Some(texture) = &cached_glyph.texture {
                        use ::window::bitmaps::ImageTexture;
                        if let Some(tex) = texture.texture.downcast_ref::() {
                            for y in texture.coords.min_y()..texture.coords.max_y() {
                                for &px in tex.image.borrow().horizontal_pixel_range(
                                    texture.coords.min_x() as usize,
                                    texture.coords.max_x() as usize,
                                    y as usize,
                                ) {
                                    let px = u32::from_be(px);
                                    let (b, g, r, a) = (
                                        (px >> 8) as u8,
                                        (px >> 16) as u8,
                                        (px >> 24) as u8,
                                        (px & 0xff) as u8,
                                    );
                                    // Use regular RGB for other terminals, but then
                                    // set RGBA for wezterm
                                    glyph.push_str(&format!(
                                "\x1b[38:2::{r}:{g}:{b}m\x1b[38:6::{r}:{g}:{b}:{a}m\u{2588}\x1b[0m"
                            ));
                                }
                                glyph.push('\n');
                            }
                        }
                    }

                    if !is_custom {
                        println!(
                            "bearing: x={} y={}, offset: x={} y={}",
                            cached_glyph.bearing_x.get(),
                            cached_glyph.bearing_y.get(),
                            cached_glyph.x_offset.get(),
                            cached_glyph.y_offset.get(),
                        );
                    }
                    println!("{glyph}");
                }
            }
        }
        return Ok(());
    }

    println!("Primary font:");
    let default_font = font_config.default_font()?;
    println!(
        "{}",
        ParsedFont::lua_fallback(&default_font.clone_handles())
    );
    println!();

    for rule in &config.font_rules {
        println!();

        let mut condition = "When".to_string();
        if let Some(intensity) = &rule.intensity {
            condition.push_str(&format!(" Intensity={:?}", intensity));
        }
        if let Some(underline) = &rule.underline {
            condition.push_str(&format!(" Underline={:?}", underline));
        }
        if let Some(italic) = &rule.italic {
            condition.push_str(&format!(" Italic={:?}", italic));
        }
        if let Some(blink) = &rule.blink {
            condition.push_str(&format!(" Blink={:?}", blink));
        }
        if let Some(rev) = &rule.reverse {
            condition.push_str(&format!(" Reverse={:?}", rev));
        }
        if let Some(strikethrough) = &rule.strikethrough {
            condition.push_str(&format!(" Strikethrough={:?}", strikethrough));
        }
        if let Some(invisible) = &rule.invisible {
            condition.push_str(&format!(" Invisible={:?}", invisible));
        }

        println!("{}:", condition);
        let font = font_config.resolve_font(&rule.font)?;
        println!("{}", ParsedFont::lua_fallback(&font.clone_handles()));
        println!();
    }

    println!("Title font:");
    let title_font = font_config.title_font()?;
    println!("{}", ParsedFont::lua_fallback(&title_font.clone_handles()));
    println!();

    if cmd.list_system {
        let font_dirs = font_config.list_fonts_in_font_dirs();
        println!(
            "{} fonts found in your font_dirs + built-in fonts:",
            font_dirs.len()
        );
        for font in font_dirs {
            let pixel_sizes = if font.pixel_sizes.is_empty() {
                "".to_string()
            } else {
                format!(" pixel_sizes={:?}", font.pixel_sizes)
            };
            println!(
                "{} -- {}{}{}",
                font.lua_name(),
                font.aka(),
                font.handle.diagnostic_string(),
                pixel_sizes
            );
        }

        match font_config.list_system_fonts() {
            Ok(sys_fonts) => {
                println!(
                    "{} system fonts found using {:?}:",
                    sys_fonts.len(),
                    config.font_locator
                );
                for font in sys_fonts {
                    let pixel_sizes = if font.pixel_sizes.is_empty() {
                        "".to_string()
                    } else {
                        format!(" pixel_sizes={:?}", font.pixel_sizes)
                    };
                    println!(
                        "{} -- {}{}{}",
                        font.lua_name(),
                        font.aka(),
                        font.handle.diagnostic_string(),
                        pixel_sizes
                    );
                }
            }
            Err(err) => log::error!("Unable to list system fonts: {}", err),
        }
    }

    Ok(())
}

fn run() -> anyhow::Result<()> {
    // Inform the system of our AppUserModelID.
    // Without this, our toast notifications won't be correctly
    // attributed to our application.
    #[cfg(windows)]
    {
        unsafe {
            ::windows::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID(
                ::windows::core::PCWSTR(wide_string("org.wezfurlong.wezterm").as_ptr()),
            )
            .unwrap();
        }
    }

    let opts = Opt::parse();

    // This is a bit gross.
    // In order to not to automatically open a standard windows console when
    // we run, we use the windows_subsystem attribute at the top of this
    // source file.  That comes at the cost of causing the help output
    // to disappear if we are actually invoked from a console.
    // This AttachConsole call will attach us to the console of the parent
    // in that situation, but since we were launched as a windows subsystem
    // application we will be running asynchronously from the shell in
    // the command window, which means that it will appear to the user
    // that we hung at the end, when in reality the shell is waiting for
    // input but didn't know to re-draw the prompt.
    #[cfg(windows)]
    unsafe {
        if opts.attach_parent_console {
            winapi::um::wincon::AttachConsole(winapi::um::wincon::ATTACH_PARENT_PROCESS);
        }
    };

    env_bootstrap::bootstrap();
    // window_funcs is not set up by env_bootstrap as window_funcs is
    // GUI environment specific and env_bootstrap is used to setup the
    // headless mux server.
    config::lua::add_context_setup_func(window_funcs::register);
    config::lua::add_context_setup_func(crate::scripting::register);
    config::lua::add_context_setup_func(crate::stats::register);

    stats::Stats::init()?;
    let _saver = umask::UmaskSaver::new();

    config::common_init(
        opts.config_file.as_ref(),
        &opts.config_override,
        opts.skip_config,
    )?;
    let config = config::configuration();
    if let Some(value) = &config.default_ssh_auth_sock {
        std::env::set_var("SSH_AUTH_SOCK", value);
    }

    let sub = match opts.cmd.as_ref().cloned() {
        Some(SubCommand::BlockingStart(start)) => {
            // Act as if the normal start subcommand was used,
            // except that we always start a new instance.
            // This is needed for compatibility, because many tools assume
            // that "$TERMINAL -e $COMMAND" blocks until the command finished.
            SubCommand::Start(StartCommand {
                always_new_process: true,
                ..start
            })
        }
        Some(sub) => sub,
        None => {
            // Need to fake an argv0
            let mut argv = vec!["wezterm-gui".to_string()];
            for a in &config.default_gui_startup_args {
                argv.push(a.clone());
            }
            SubCommand::try_parse_from(&argv).with_context(|| {
                format!(
                    "parsing the default_gui_startup_args config: {:?}",
                    config.default_gui_startup_args
                )
            })?
        }
    };

    match sub {
        SubCommand::Start(start) => {
            log::trace!("Using configuration: {:#?}\nopts: {:#?}", config, opts);
            let res = run_terminal_gui(start, None);
            wezterm_blob_leases::clear_storage();
            res
        }
        SubCommand::BlockingStart(_) => unreachable!(),
        SubCommand::Ssh(ssh) => run_ssh(ssh),
        SubCommand::Serial(serial) => run_serial(config, serial),
        SubCommand::Connect(connect) => run_terminal_gui(
            StartCommand {
                domain: Some(connect.domain_name.clone()),
                class: connect.class,
                workspace: connect.workspace,
                position: connect.position,
                prog: connect.prog,
                new_tab: connect.new_tab,
                always_new_process: true,
                attach: true,
                _cmd: false,
                no_auto_connect: false,
                cwd: None,
            },
            Some(connect.domain_name),
        ),
        SubCommand::LsFonts(cmd) => run_ls_fonts(config, &cmd),
        SubCommand::ShowKeys(cmd) => run_show_keys(config, &cmd),
    }
}
 

       ーーーーー     このコードの処理フローをMermaidのシーケンス図で書いてください。

あなた:
Wordpress で   マークダウンやマーメイドの表示をさせるためのプラグインやJavascriptやその使い方について、雑学や業界話を交えながら、解説してください。
ChatGPT:

WordPressで Markdown や Mermaid(図表記法) を表示させる話って、実は「技術ブログ文化」とかなり深く結びついていて、開発者界隈では“読みやすさ=信用”みたいな価値観にも繋がっています。
そのあたりの業界事情や雑学も混ぜつつ、実用的な導入方法を整理していきますね。


■ ① なぜWordPressでMarkdownやMermaidを使いたがるのか

◆ Markdown

Markdownはもともと

👉 「HTMLを書くのが面倒くさい」
👉 「文章中心で管理したい」

というエンジニア文化から生まれました。

GitHub、Qiita、Zennなどが全部Markdownを採用しているのは、

  • バージョン管理しやすい

  • 可読性が高い

  • テキストだけで完結

  • AIや生成ツールと相性が良い

という理由があります。


◆ Mermaid

Mermaidはかなり面白い存在で、

👉 「図をテキストで書く」

という思想のツールです。

例えば普通のフローチャートって、

  • draw.io

  • PowerPoint

  • Illustrator

などで描きますよね。

でもMermaidならこう書くだけ👇

graph TD A[ログイン] --> B{認証成功?} B -->|Yes| C[ダッシュボード] B -->|No| D[エラー]

これがそのまま図になります。


◆ 業界小話

最近のITドキュメントは

  • GitHub README

  • 技術設計書

  • API仕様書

ほぼ全部が

👉 Markdown + Mermaid

になってきています。
つまりWordPressに導入するのは「ブログを技術ドキュメント化する流れ」なんですね。


■ ② WordPressでMarkdownを表示する方法

⭐ 一番簡単:プラグイン


【A】WP Markdown Editor(おすすめ)

特徴

  • Gutenberg対応

  • ライブプレビュー

  • GitHub風表示

使い方

① プラグイン検索

WP Markdown Editor

② 有効化

③ 投稿画面でMarkdown入力


【B】Jetpack Markdown

Automattic公式なので安定しています。

有効化

Jetpack → 設定 → 執筆 → Markdown

【C】Advanced Markdown Editor

開発者ブログ寄りで、コード表示が強いです。


■ ③ Mermaidを表示する方法

ここが少しクセあります。

WordPressはデフォルトでMermaid未対応です。


⭐ 方法①:Mermaid専用プラグイン

「Mermaid」

そのまんまの名前ですが存在します。

手順

① プラグイン検索

Mermaid

② 有効化

③ 投稿内で

[mermaid] graph TD A --> B [/mermaid]

⭐ 方法②:JavaScriptで直接読み込む(技術者向け)

こっちが「エンジニアっぽい王道」です。


Step1:Mermaid CDN読み込み

WordPressテーマの

functions.php

に追加します。

function add_mermaid_script() { wp_enqueue_script( 'mermaid', 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js', array(), null, true ); } add_action('wp_enqueue_scripts', 'add_mermaid_script');

Step2:初期化JSを書く

<script> document.addEventListener("DOMContentLoaded", function() { mermaid.initialize({ startOnLoad: true }); }); </script>

Step3:記事に書く

<div class="mermaid"> graph TD A --> B </div>

■ ④ Markdown + Mermaidを同時に扱う方法

技術ブログではここが重要。


⭐ MarkDownブロック+MermaidJS

例えば

```mermaid graph TD A --> B ```

を使いたい場合は、

👉 Prism.js
👉 remark
👉 marked.js

などと組み合わせます。


■ ⑤ 実務でよくある構成(業界パターン)

企業技術ブログではだいたい3タイプあります。


パターン①

WordPress + Markdownプラグイン

👉 初心者〜中級
👉 Qiita風ブログ


パターン②

WordPress + headless CMS化

  • WordPress → API専用

  • Next.js → 表示

最近めちゃ多いです。

(Zenn風)


パターン③

完全Git管理

  • Markdown

  • Mermaid

  • CIで静的サイト生成

GitHub Pages系ですね。


■ ⑥ Mermaidが流行った背景(雑学)

Mermaidは元々

👉 DevOps文化

から広まりました。

CI/CDの設計書を

  • 図で

  • Gitで管理

したかったんです。

特にDockerやKubernetesの設計では

Mermaidはほぼ標準装備になりつつあります。


■ ⑦ 注意点(地味に重要)

✔ セキュリティ制限

WordPressは

<script>

をブロックする事があります。

対策:

  • カスタムHTMLブロック

  • functions.php読み込み


✔ キャッシュ問題

MermaidはJS描画なので

👉 キャッシュプラグインと衝突

する場合があります。


✔ テーマ依存

Gutenbergテーマは比較的安全
クラシックテーマは崩れる事あり


■ ⑧ 個人的おすすめ構成(開発者ブログ用途)

かなりバランス良いです👇

  • WP Markdown Editor

  • Mermaid CDN読み込み

  • Prism.jsでコード装飾


■ ⑨ 未来トレンド(業界話)

今はさらに進んで

👉 AIがMarkdown生成
👉 Mermaid自動生成

が増えています。

つまり将来は

「文章を書く=設計図が完成」

という世界になりつつあります。


■ もし興味あれば…

あなたの環境に合わせて、

  • Gutenbergですか?

  • クラシックエディタですか?

  • 技術ブログ用途ですか?

  • 社内ドキュメント用途ですか?

このあたり分かると、最適構成まで一緒に設計できます。

WordPressは「ブログ」というより、
実は ドキュメントCMSとしてもかなり強い ので、そこを伸ばすと面白いですよ。