hack のためのネタ帳, etc,,,

状況

vscode 標準添付されている言語モード "Shell Script" で、文字列として与えた AWK Script に syntax highright を施したい。
AWK Script の syntax highlight 自体は
  • Visual Studio Marketplace / AWK
つぅのが存在しているので、これを here document の内部等に適用してやれれば楽ちん。
emacs や xyzzy の mmm-mode のようなノリで、適当なパターンにマッチした箇所を別の言語モードに移行させるイメージで。

Markdown とかだと Fenced Code Block の1行目の末尾に syntax highlight したい言語名を
```awk
BEGIN {print "hello";}
END { print "world";}
```
みたいに指定するので、
bash の here document の delimiter を
cat <<-AWK
BEGIN {print "hello";}
END { print "world";}
AWK
みたいに出来ると嬉しいところ。

で、しばらく前に、公式ドキュメントの 辺りに埋め込み言語(embedded language)の syntax highlight のやり方については解説があることまで把握していて、
embedded language の syntax highlight 指定したい箇所の構文を extension に定義してやれば良さそうな雰囲気だったので、今回ようやく重い腰を上げてみた次第。

他にも「embed syntax highlight vscode」辺りでググるといろいろ見つかるのだが、今の所やりたいことズバリな解説は残念ながら見つからない感じだった。

一応、 みたいなのが存在することも確認していたので、これを参考にすればどうにかなりそうと言う目論見もあった。

まず、上記公式の解説を見ると、
package.json で "InjectTo" とかいうパラメータがあって、これでベースとなる言語モードを指定するようになっている。
更に、文法の定義ファイル(TextMate grammars で構文規則を定義した JSON ファイル)の方には、"injectionSelector" ってパラメータで定義した構文規則をどこに注入(inject)するかを指定するらしい。
注入先の構文は、vscode に Scope inspector って便利機能があるので、
コマンドパレット(SHIFT+CTRL+P)開いて「Developer: Inspect Editor Tokens and Scopes」って入力すると、カーソル位置の構文木の状況が確認出来るとの事。

とりあえず、Shell Script モードは Visual Studio Code 同梱なので、以下にソースコードが転がっている。
  • GitHub / microsoft / vscode / extensions / shellscript
これを見ると、"InjectTo" すべきは "source.script" であることが分かった。
で、syntaxes/ shell-unix-bash.tmLanguage.json が構文定義なんだけど、なんと既に上で相対した方法で RUBY, PYTHON, APPLESCRIPT, HTML, MARKDOWN, TEXTILE, SHELL の syntax highlight には対応済みであることを発見。
実際、
cat <<-RUBY
  # hoge
  a = [1,2,3].map{|v| v+1}
RUBY
とか
cat <<-HTML
<!DOCTYPE html>
<html>
<head>
<title>hello</title>
</head>
<body>
<p>hello</p>
</body>
</html>
HTML
みたいにしてみると syntax highlight されるではないか。
素晴らしい。このまま真似て差分を AWK にすれば完成じゃないか!
と思ったのだが、これがなかなか、undocumented なのか、読み漏らしているのか、いろいろと難儀する羽目になった。

まず、debug が結構難儀で、JSON の文法に不正(例えば末尾要素が "," で終わってるとか)があると動かんし、そのことについて error や warning がどこに出てるのか見つからない。
設定の効果が発現しているのかどうか調べようにも、色が付かない、設定した構文木の名称が Scope inspector に反映されない、printf debug のように出力仕掛ける方法がないというところで、まず躓く。
"scopeName" や "name" や "contentName" や "embeddedLanguages" その他諸々、名前の対応の問題かとあれこれ組み合わせを試してみたのだが、どうやっても embedded language として AWK の設定が反映されない。
仕方なく、基本に立ち戻り解説のサンプル(TODO)をやってみると、ちゃんと動く。

結局、これは、graTextMate grammars で仕掛ける "injectionSelector" の問題だったのだが、
  • "keyword.control.heredoc-token.shell"
  • "string.unquoted.heredoc.no-indent.awk.shell"
  • "source.shell"
  • "L:keyword.control.heredoc-token.shell"
  • "L:string.unquoted.heredoc.no-indent.awk.shell"
  • "L:source.shell"
のいずれでもなく、
  • "L:"
という正解にたどり着くまで数時間。

追記: 2021-10-27
あれ?
  • "L:source.shell"
だとちゃんと機能するぞ?
別のところで誤記があったということだろうか?
追記: /2021-10-27

更に、AWK の syntax highlight が適用出来はしたものの、highlight されない箇所が片っ端から文字列の配色になってしまう。
これは、RUBY や HTML 等、デフォルトで対応している言語も同じだったのだが、解説をよく読んでみると、
There is one additional complication for injection languages embedded languages: by default, VS Code treats all tokens within a string as string contents and all tokens with a comment as token content. Since features such as bracket matching and auto closing pairs are disabled inside of strings and comments, if the embedded language appears inside a string or comment, these features will also be disabled in the embedded language.
みたいな補足がしてあって、文字列の箇所は埋め込み言語の箇所でも文字列扱いが解除されないのでこれを解除する設定が必要なんだそうな。
で、それを解除してないと
Since features such as bracket matching and auto closing pairs are disabled inside of strings and comments, if the embedded language appears inside a string or comment, these features will also be disabled in the embedded language.
ってことで、カッコの対応とかが出来ないぞと。あぁ、確かに機能してないですね。
つまり、現状 Shell Script モードは文字列トークン扱いされるデフォルトの設定を解除してない不備がある模様。後日パッチでも投げとくか。

解除する方法は、
To override this behavior, you can use a meta.embedded.* scope to reset VS Code's marking of tokens as string or comment content. It is a good idea to always wrap embedded language in a meta.embedded.* scope to make sure VS Code treats the embedded language properly.
If you can't add a meta.embedded.* scope to your grammar, you can alternatively use tokenTypes in the grammar's contribution point to map specific scopes to content mode. The tokenTypes section below ensures that any content in the my.sql.template.string scope is treated as source code:
ってことで、
  • "embeddedLanguages" に紐付ける "contentName" の名前を "meta.embedded.*" にするか、
  • それが出来ないなら、"tokenTypes" で構文木のノード名に "other" を指定して文字列扱いからその他扱いに変更
しとけと。

結局、前者で解決出来たんだけど、後者が曲者で、これいろいろ試してみたんだけど、今の所何を設定すればいいのかさっぱりわからない。
ひょっとしてこの機能、機能してないんじゃ???疑惑。

とりあえず、プロトタイプは完成したので、整理出来たら公開する方向で。

その他参考

Markdown や JavaScript, TypeScript 関連のモードにも embedded Languages の参考になる箇所が何箇所かあった。

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

Wiki内検索

フリーエリア

管理人/副管理人のみ編集できます