エディタ対応
.pw.html を、それを知らないエディタで開くと、宣言ヘッダーも {} の式も HTML 本体も、
区別のない 1 塊のテキストになります。新しいソース言語ではごく普通の状態です。ただ、
見た目の問題で済むかというと、そうでもありません。テンプレートで人が間違えるのは
たいてい波括弧か閉じ形式で、その 2 つはまさに、色が付いていれば見えていたものです。
Visual Studio Code 用の Popcorn Web 拡張は 3 方言すべてを扱います。Marketplace か Open VSX で Popcorn Web を検索してインストールしてください。
何も入れなくても動く範囲
Section titled “何も入れなくても動く範囲”ハイライト、括弧の対応、コメントの切り替え、スニペット、そして Format Document。
ここまでは pw バイナリも popcornweb.toml もネットワークも要りません。整形はフレー
ムワーク自身のフォーマッタを WebAssembly にコンパイルして拡張に同梱したもので、
プロジェクトのないレビュー用チェックアウトで開いた 1 ファイルでも、信頼していない
ワークスペースでも動きます。
この性質は意図したものです。プルリクエストをブラウザ上のエディタで読むときこそ、 テンプレートは読めてほしいのに、道具立ては最も乏しい。
pw があって、ワークスペースが信頼済みなら、整形は代わりに pw fmt --stdin を走らせ
ます。ビルドが走らせるのと同じバイナリなので、正規形についてエディタと CI が食い違い
ようがありません。同梱モジュールは固定の tinybind バージョンで、プロジェクトのそれとは
違いうるので、どちらが結果を出したかをセッションごとに 1 度だけ出力チャンネルに出します。
解決した pw は信用する前に 1 度だけ検査されます。検査するのはバージョンではなく性質
です。tinybind v0.3.2 より前に不安定だったソース——<style> の中のむき出しの波括弧——を
整形し、2 回目が 1 回目をそのまま返すことを要求します。落ちた pw は使いません。
エディタは整形結果を検証し直さずに適用するので、フォーマッタ自身のガードがそれを
安全にしているからです。
# 必要なら、この言語だけ保存時整形にする# "[pw-html]": { "editor.formatOnSave": true }パースできないソースはそのまま置かれ、警告が行番号を示します。中途半端に適用されて 壊れることはありません。
pw lsp が足すもの
Section titled “pw lsp が足すもの”信頼済みのワークスペースで pw が見つかると、拡張は pw lsp を起動します。入力中に出る
構文エラー、開いているファイルの宣言のアウトライン、Go to Symbol in Workspace による
プロジェクト全体の宣言検索、そして宣言名に対するホバーと定義ジャンプが増えます。
どれも pw generate が走らせるパーサーそのものから出ています。TypeScript で書き直し
たパーサーは存在しません。だからこそ、ファイルがパースできるかどうかについてエディタと
ビルドが食い違うことがない。もう一方の道を選べばフレームワークのリリースごとにずれて
いくもので、しかもバイト単位の差がそのまま答えになる機能でそれをやることになります。
ホバー、ナビゲーション、補完、ヒント
Section titled “ホバー、ナビゲーション、補完、ヒント”名前の上にカーソルを置くと、それが何なのかが出ます。ソースが書いているとおりの宣言、 レコードならフィールド、そしてモジュールが解析を通るコンポーネントなら引数が落ちる先の Go 型——テンプレートが唯一書いていない情報です。定義ジャンプはそこへ飛びます。ファイルを またいでもパッケージをまたいでも、生成と同じ規則で解決します。自分のパッケージの宣言が 勝ち、import はそのパッケージの export された宣言を連れてくる。
生成物が 1 つも無くても動きます。
ナビゲーションは Go 側にも渡ります。宣言に対する参照検索は、テンプレート側の参照と 並べて、その宣言が生んだ関数を呼んでいる手書きの Go も列挙します。このフレームワークの 間接がまさにその境界にあり、テンプレートで止まる結果一覧はそこを隠すことになるからです。 探す名前は宣言から導出するのではなく生成ファイルから読み取ります。命名規則は ジェネレータのもので、こちら側に写しを持つと次のリリースでずれるからです。
逆方向は .go ファイルのシンボル上で Popcorn Web: Go to Template Declaration です。
_pw_gen.go を飛ばして、それを生んだ .pw.* の宣言を開きます。定義ジャンプの
プロバイダではなくコマンドなのは、Go ファイルは gopls のもので、その前に立ちはだかる
くらいならメニューに 1 項目足すほうがましだからです。
参照検索は同じ規則を逆向きに走らせます。走査するのはその宣言が実際に見えているファイル
だけなので、別パッケージの無関係な Card があなたの Card の使用箇所として報告される
ことはありません。
補完はその位置が取りうるものを出します。ヘッダーならルートキーワードと、その方言が許す
出力型。: の後なら基本型と宣言済みレコード。< の後なら到達できるコンポーネント——
必要な引数を埋めた形で挿入されます。{ } の中ならスコープにある引数と束縛、それに制御
構文が閉じ形式込みで出るので、受け入れて本体が書きかけになることはありません。プロジェクト
が読み込まれていなくてもキーワードと基本型は出て、解決を要するものは 1 つも出ません。
インレイヒントはソースが書いていないものを注記します。{val} 束縛が持つ型、ループが束縛
する要素の型、{await} 束縛が落ち着く型。引数には付きません。自分で型を書いているから
です。このサーバーが評価しない式の束縛にはヒントを付けません。推測した型を出すよりましだ
からです。種類ごとに個別に切り替えられます。
正直な限界が 1 つ、これら全部に通っています。解決するのはカーソル下の識別子であって、それ が書かれた構文上の位置ではありません。宣言名と一致する語は、SQL の文字列リテラルの中に あっても解決します。区別するには名前グラフではなく本体の構文木が要ります。
ワークスペース検索が見る範囲
Section titled “ワークスペース検索が見る範囲”サーバーは popcornweb.toml を読み、[generate] の purpose が挙げたディレクトリの
.pw.* だけをインデックスします。どの purpose も挙げていないディレクトリのテンプレート
は検索に出ません。pw generate から見えないのと同じ理由です。purpose は自分が挙げた
ディレクトリしか読まないので、生成より広く見つかる検索は、ビルドが決してコンパイル
しないファイルについて教えていることになります。そのファイルにも構文診断は出ます。
消えるのはプロジェクトとしての答えだけです。
打ったばかりで保存していない宣言も見つかります。インデックスが持つのはディスク上の 内容ですが、エディタが開いているバッファがそれを上書きするので、結果は最後に保存した 位置ではなく今の位置に着きます。
popcornweb.toml を編集するとモデルはその場で読み直されます。generate.templates に
ディレクトリを足せば、何も再起動せずにその宣言が検索に乗る。読み込めない内容なら、
エラーは popcornweb.toml 自身の上に報告され、ハイライトと整形と構文診断はそのまま
続きます。上に popcornweb.toml が 1 つもない場所で開いた場合は、その旨を 1 度だけ
伝えて構文診断だけを出します。プロジェクトが要るものを推測することはありません。
pw lsp を自分で叩くことはありません。標準入出力で Language Server Protocol を話すので、
そのストリームに何か書けばセッションは終わります。起動するのはエディタです。コマンドと
して存在しているのは、VS Code 以外のエディタからも同じものを起動できるようにするため
です。
バイナリは次の順に探されます。ダウンロードもインストールもしません。
- ワークスペースの devbox 環境、
.devbox/nix/profile/default/bin/pw PATHpopcornweb.pw.pathに絶対パスを設定していれば、それ
devbox が先頭なのは、ツールチェーンを固定しているプロジェクトなら、エディタにも
PATH の誰かではなくプロジェクトが固定した版を走らせてほしいからです。設定値が最後な
のも同じ理由です。これは前の 2 つのどちらにも pw がない環境のための答えであって、
エディタとビルドを別々の pw に静かに振り分ける上書きではありません。
起動しないとき
Section titled “起動しないとき”信頼していないワークスペースでは何も起動しません。ワークスペース相対のバイナリパスは
ワークスペース側が決める入力であり、ファイルを開いただけでプロジェクトの道具が走っては
いけないからです。pw がどこにもない場合も起動しません。どちらの場合もハイライトと
整形は動き続け、Popcorn Web 出力チャンネルが、何が起動しなかったかとその理由を
1 度だけ伝えます。
pw を入れ直した後は、ウィンドウを再読み込みするのではなく
Popcorn Web: Restart Language Server を実行してください。
| 設定 | 既定値 | 効果 |
|---|---|---|
popcornweb.languageServer.enabled |
true |
pw lsp を起動するかどうか。 |
popcornweb.pw.path |
"" |
pw の絶対パス。devbox にも PATH にもないときだけ使われる。 |
popcornweb.languageServer.log |
"" |
プロトコルのトレースを追記するファイル。空なら何も書かない。 |
サーバーが読むのはエディタが送ったバッファだけです。ワークスペースには 1 ファイルも 書かず、どこにも接続しません。
ルート、生成コード、ストーリー
Section titled “ルート、生成コード、ストーリー”LSP にメソッドが無いものが 3 つあり、それらは拡張がサーバーに直接尋ねます。
エクスプローラの Popcorn Web Routes が、ページツリーが提供する全ルートを、ページ
テンプレート・レイアウトの連なり・専用の page.go を持つかどうかと一緒に並べます。選ぶと
テンプレートが開きます。カバーしていない範囲もビューが明示します。Go で登録されたルートは
ディレクトリではなく呼び出しで、見つけるには解決済みインポートグラフが要り、これは読み
込みません。
.pw.* のコンテキストメニューの Peek Generated Code は、宣言が生んだ Go を読み取り
専用のビューで横に開きます。開いても何も生成しません——生成はファイルを書くので明示的な
操作のままです——なので pw generate を走らせる前は、その旨と何を実行すべきかが出ます。
最後の生成以降にソースが変わっていれば、現在のものとして見せずに stale と表示します。
Preview Story は、カーソル下のコンポーネントの storybook ペインを開きます。どの URL かを計算して開く だけで、拡張自身は何も描画しません。
動いているループからの失敗
Section titled “動いているループからの失敗”pw dev はパーサーが見つけられない失敗を見つけます。生成エラー、ビルドエラー、描画して
初めて壊れるテンプレート。popcornweb.runtimeDiagnostics.enabled を有効にすると、それらが
ループの示した位置に Problems ビューへ出ます。pw dev という source が付くので、
pw generate が出すものと混ざりません。
これは拡張がネットワーク越しに読む唯一のもので、しかもあなたが起動した開発コンソールへの ループバックです。既定は無効で、無効の間は何にも接続せず、マシンの外へは何も出ません。 ループが再ビルドすると指摘は消えます。ビルドより長生きした指摘は、もう存在しないかも しれないコードを指しているからです。
エディタから pw を走らせる
Section titled “エディタから pw を走らせる”編集中に叩くコマンドはパレットの Popcorn Web の下にあります。自分で打つときと 同じコマンド、同じ引数です。
| コマンド | 実行するもの | 補足 |
|---|---|---|
| Generate | pw generate --code-only |
診断が指す先の生成 Go を書く。アセットツリーはビルドの仕事で、キーストロークの仕事ではない。 |
| Check | pw check |
古い・欠けている生成 Go を報告する。何も書かない。 |
| Doctor | pw doctor --format=json |
指摘は、それが名指したファイルと設定行の上に出る。 |
| Migrate | pw migrate up |
先に確認する。 |
| Dev | pw dev |
専用の端末を 1 つだけ持つ。 |
どれもタスクとしても解決できるので、tasks.json に組み込むのに問題マッチャーを書く必要は
ありません。
{ "version": "2.0.0", "tasks": [ { "type": "pw", "command": "check", "problemMatcher": ["$pw", "$pw-source"] } ]}保存に紐づけるなら pw check です。何も書かず、しかも言語サーバーが出さない生成時の
エラー——見つからない外部関数、statement が満たせない結果型——を報告します。それらは
バッファ 1 つのパースではなくプロジェクト全体の解析を必要とするからです。Generate の方は
保存ではなくキーストロークに割り当ててください。こちらはファイルを書きます。
pw dev はタスクではなく端末です。ループがサービスと開発用 IdP とテレメトリビューアを
抱えていて、出力は問題マッチャーが読むものではなく人が眺めるものだからです。表示される
URL がクリックできるのは端末がそうしているからで、拡張自身はビューアを持ちません。もう
一度コマンドを叩くと、2 つ目のループを起こすのではなく走っている端末に移ります。
pw migrate は先に確認します。マイグレーションは実データベースに対して前方向にしか
進まず、パレットのコマンドは意図しないクリックまで 1 キーストロークだからです。
ここにあるものは何ひとつ自動では動きません。保存が pw generate を走らせることも、
ファイルを開いて pw dev が始まることもなく、信頼していないワークスペースではどれも
動きません。
宣言のリネーム
Section titled “宣言のリネーム”宣言の名前が決めているのは宣言だけではありません。pw generate が出す export された
Go 関数、それを呼ぶ手書きの Go、そしてテンプレートからの参照すべて。Rename Symbol
はそれらを一度に書き換え、エディタが編集集合を先に見せます。開いていないファイルにまで
届くからです。
pw rename <宣言> <新しい名前> はエディタ無しで同じことをします。既定はプレビューで、
--apply で書き込みます。どちらでも生成 Go は編集しません。次に pw generate が走った
ときに新しい名前で書き直されるからです。
すでに何かが宣言している名前へのリネームは拒否します。パーサーが受け付けない名前も同様 です——コンポーネントと statement は PascalCase。どちらも書き込む前に分かることで、 半分だけリネームされたプロジェクトはリネームしないより悪い。URL は動きません。ページの ルートはディレクトリから来るもので、その中の宣言から来るものではないからです。
まだやらないこと
Section titled “まだやらないこと”外部関数が見つからない、statement が満たせない結果型を宣言している——この種の指摘は
入力中には出ません。プロジェクト全体の解析が要るからで、そのための pw check タスクが
上にあります。
プロジェクトモデルから出る指摘が 1 つだけあります。どの [generate] purpose もコンパイル
しない .pw.* ソースです。文言は pw generate が同じ条件に対して出すものと同じで、
クイックフィックスが付きます — そのファイルが今いるディレクトリを、コンパイルできる
purpose の下に載せる popcornweb.toml の 1 行編集です。ファイルの移動は提案しません。
どこへ置くべきかはプロジェクトの構成に関する判断で、すでに選んだディレクトリを載せる
ことは判断ではないからです。
ページツリーの中にある page.pw.html・layout.pw.html・document.pw.html 以外の
テンプレートも同じ扱いになります。ツリーが予約した名前しかコンパイルしないからです。
つまり、打っている最中の間違いはエディタに任せ、プロジェクト全体を見ないと分からない
ものはビルドの pw check に残す、という分担になります。すでに pw dev をエディタの
隣の端末で回しているなら、拡張が足すのは色と正規形の整形で、そのループ自体は何も
変わりません。
ほかのエディタ
Section titled “ほかのエディタ”解析は拡張の機能ではなく pw のサブコマンドです。そのため、言語サーバーを起動できる
エディタなら、薄いクライアント 1 つで——Popcorn Web 固有のコードを 1 行も持たずに——診断・
アウトライン・ホバー・定義ジャンプ・参照検索・補完・インレイヒントに届きます。
設定するのは 3 つです。コマンドは pw lsp --stdio、ファイルタイプは .pw.html・
.pw.sql・.pw.dynamo、ルートマーカーは最も近い popcornweb.toml。
Neovim(nvim-lspconfig)なら:
vim.filetype.add({ pattern = { [".*%.pw%.html"] = "pw-html", [".*%.pw%.sql"] = "pw-sql", [".*%.pw%.dynamo"] = "pw-dynamo",} })
vim.lsp.config("pw", { cmd = { "pw", "lsp", "--stdio" }, filetypes = { "pw-html", "pw-sql", "pw-dynamo" }, root_markers = { "popcornweb.toml" },})vim.lsp.enable("pw")Zed なら、拡張の extension.toml に同じ 3 つを書きます:
[language_servers.pw]name = "Popcorn Web"languages = ["Popcorn Web HTML", "Popcorn Web SQL", "Popcorn Web DynamoDB"]サーバーは拡張の Rust エントリポイントから pw lsp --stdio として起動します。持ち越せ
ないのはハイライトだけです。Zed は tree-sitter 文法を読み、このリポジトリが配っているのは
TextMate 文法なので、この系統のエディタは自前の文法を用意するか、サーバーが送るセマンティック
トークンに頼るか——後者はサーバーが動いている間しか色が付きません。
このサーバーのメソッドのうち 4 つは LSP のものではなく独自のもので、知らないクライアントは
そもそも送りません。pw/routes はページツリーを返し、pw/generatedFor は宣言が生んだ Go を
返し、pw/storyFor はコンポーネントの storybook URL を返し、pw/project は読み込まれた
プロジェクトを報告します。
